sql二次注入和截断
这次主要是说明sql的二次注入和sql插入数据库时截断的问题而形成的一种特殊的注入手段,有时会有意想不到的效果。
sql二次注入
二次注入的原理也是非常的简单,在第一次进行数据库插入数据的时候,仅仅只是使用了addslashes
或者是借助get_magic_quotes_gpc
对其中的特殊字符进行了转义,在写入数据库的时候还是保留了原来的数据,但是数据本身还是脏数据。在将数据存入到了数据库中之后,开发者就认为数据是可信的。在下一次进行需要进行查询的时候,直接从数据库中取出了脏数据,没有进行进一步的检验和处理,这样就会造成SQL的二次注入。比如在第一次插入数据的时候,数据中带有单引号,直接插入到了数据库中;然后在下一次使用中在拼凑的过程中,就形成了二次注入。
在网上搜索的时候,发现这张图对于SQL二次注入的原理解释得很好,我就直接使用了。
对SQL二次注入不了解的,可以去看文章,dedecms鸡肋级注入与细节分析过程
sql的截断联合使用
这个是mysql数据库的特性,当插入的数据超过数据库规定的长度时,数据库会将数据自动地截断进行插入,不会有任何的报错。
下面来进行一个简单的实验。
有如下的数据库:
1 | CREATE TABLE `users` ( |
可以看到其中的username
的长度是30。如果我们使用如下的语句插入数据:
1 | insert into users(username,password,email,ip) values('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaab','1','2','127.0.0.1'); |
其中的username设置的值是30个字母a和一个字母b,最后的结果为:
可以看到最后字母b
被截断了。
以上就是一个简单的sql截断的例子。
但是需要说明的是,sql的截断是需要在非严格模式下才可以发生截断的。
在my.cnf中存在一个配置项sql-mode
,我的配置选项是:
1 | sql-mode="NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION" |
在上面的配置中,是一个非严格模式。非严格模式就可以发生我的文章中的截断的情况。
如果在sql-mode
中加入STRICT_TRANS_TABLES
,变为:
1 | sql-mode="NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES" |
在STRICT_TRANS_TABLES
严格模式下,会进行数据的严格校验,错误数据不能插入,报error错误
就会变成严格模式。
可以参考这篇文章mysql 严格模式 Strict Mode说明
我们可以通过select @@sql_mode
来查看数据库当前的模式。
在严格模式下,我们再一次进行尝试
可以看到在严格模式下,无法插入数据。所以如果mysql要进行截断,是需要在非严格模式下才可以。
实例演示
sql的二次注入,如果在第一次插入数据的时候对数据进行了转义,那么在下次使用的过程中也不会出现问题。但是借助于sql截断的方法,有时能够有一种化腐朽为神奇的效果。利用截断的功能,将最后一个的特殊字符去掉,这样就可以绕过限制了。
1 | if (!get_magic_quotes_gpc()){ |
首先分析一下逻辑,在注册逻辑中,
1 | $username = mysql_real_escape_string($_POST['username']); |
在将数据插入到数据库之前,使用了mysql_real_escape_string
对数据进行了转义处理。
在注入成功之后,
1 | $sql = "SELECT * FROM users WHERE user_id = $uid"; |
从数据库取出数据,放入到SESSION中,取出的数据是已经转义过的。
在查询逻辑中,
1 | $username = $_SESSION['username']; |
其中的$username = $_SESSION['username'];
,username是直接从session中取出来的。
可以看到上面的代码中进行了两次转义,首先使用了addslashes
进行了转义,后来又使用了mysql_real_escape_string
进行了转义,这种情况下就会多出一个\
。
还是用一段简单的代码来说明问题
可以看到'
经过两次转义之后变为\\\'
,结果长度变为了4吗?我们在mysql中看看
1 | mysql> select length('\\\''); |
可以看到mysql认为\\\'
的长度是2。这就说明了'
在经过两次转义的情况下会变为字符'\
(反斜线+单引号),长度加1了,所以最后才会发生截断。最后的结果如下:
这样我们就多一个\
可以使用了。
下面就是构造一个简单的payload了,由于search是我们可控的,所以我们最终的查询可以写为:
1 | localhost/sql4/index.php?search=union select 1,2,3,@@version%23 |
最后的输出结果为:
通过sql截断的方式得到了一个\
,注释掉后面的'
,然后就可以利用union语句注出数据了。
最后,感谢p牛的指点,指出了我之前存在的问题。