渗透测试步骤

(1)记下任何可能控制应用程序返回的结果的顺序或其中的字段类型的参数。

(2)提供一系列在参数值中提交数字值的请求,从数字1开始,然后逐个请求递增。

img002 如果更改输入中的数字会影响结果的顺序,则说明输入可能被插入到ORDER BY子句中。在SQL中,ORDER BY 1将依据第一个列进行排序。然后,将这个数字增加到2将更改数据的显示顺序,以依据第二个列进行排序。如果提交的数字大于结果集中的列数,查询将会失败。在这种情况下,你可以通过使用以下字符串,检查是否可以颠倒结果的顺序,从而确认是否可以注入其他SQL:

img242a

img002 如果提交数字1生成一组结果,其中一个列的每一行都包含一个1,则说明输入可能被插入到查询返回的列的名称中。例如:

img242b

img001  注解  在ORDER BY子句中实施SQL注入与其他注入情形有很大区别。此时,数据库不会接受查询中的UNION、WHERE、OR或AND关键字。通常,实施注入攻击需要攻击者指定一个嵌套查询来替代参数,如用(select 1 where <<condition>> or 1/0=0)替代列名称,并利用本章后面部分介绍的推断技巧。对于支持批量查询的数据库(如MS-SQL),这可能是最有效的注入攻击方法。

9.2.4 “指纹”识别数据库

迄今为止,我们所描述的大多数技巧能够向常用的数据库平台发动有效攻击,任何差别都已通过对语法进行细微调整得到解决。但是,随着我们开始分析更高级的利用技巧,各种平台之间的差异变得更加明显,因此了解所针对的是何种类型的后端数据库就变得愈发重要。

我们已经知道如何提取常见数据库的版本字符串。即使由于某种原因无法提取到版本信息,我们还是可以使用其他方法识别数据库。一种最可靠的方法是根据数据库连接字符串的不同方式 进行识别。在控制某个字符串数据项的查询中,可以在一个请求中提交一个特殊的值,然后测试各种连接方法,以生成那个字符串。如果得到相同的结果,就可以确定所使用的数据库类型。下面的示例说明常用的数据库如何构建services字符串。

img002 Oracle: ‘serv’||‘ices’

img002 MS-SQL: ‘serv’+‘ices’

img002 MySQL: ‘serv’ ‘ices’ [注意中间有空格]

如果注入数字数据,则可以使用下面的攻击字符串来识别数据库。每个数据项在目标数据库中的求值结果为0,在其他数据库中则会导致错误。

img002 Oracle: BITAND(1,1)-BITAND(1,1)

img002 MS-SQL: PACK_RECEIVED-PACK_RECEIVED

img002 MySQL: CONNECTION_ID()-CONNECTION_ID()

img001  注解  MS-SQL和Sybase数据库起源相同,因此它们在表结构、全局变量和存储过程方面存在许多相似之处。实际上,后文描述的绝大多数针对MS-SQL的攻击技巧同样也适用于Sybase。

在识别数据库时,MySQL如何处理某些行内注释(inline comment)也是一个值得关注的问题。如果一个注释以感叹号开头,接着是数据库版本字符串,那么只要数据库的实际版本等于或高于那个字符串,应用程序就会将注释内容解释为SQL;否则,应用程序就会忽略注释内容,将它作为注释处理。与C中的预处理指令类似,程序员也可以对这一点加以利用,编写出根据所使用的数据库版本进行处理的不同代码。攻击者还可以利用它来识别数据库的实际版本。例如,如果使用的MySQL版本高于或等于3.23.02,注入下面的字符串将使SELECT语句的WHERE子句为假:

img243a

9.2.5 UNION操作符

SQL使用UNION操作符将两个或几个SELECT语句的结果组合到独立一个结果中。如果一个Web应用程序的SELECT语句存在SQL注入漏洞,通常可以使用UNION操作符执行另一次完全独立的查询,并将它的结果与第一次查询的结果组合在一起。如果应用程序向浏览器返回查询结果,那么就可以使用这种技巧从应用程序中提取任意的数据。所有的主流DBMS产品者支持UNION,对于直接返回查询结果的情况,UNION是检索信息最快捷的方式。

我们再回到那个允许用户根据作者、书名、出版商和其他条件搜索书籍的应用程序。搜索由Wiley 出版的书籍将引起应用程序执行以下查询:

img243b

假设这个查询返回下面这组结果:

作 者 书 名 出版年份
Litchfield The Database Hacker's Handbook 2005
Anley The Shellcoder's Handbook 2007

前文已经介绍了攻击者是如何向搜索功能提交专门设计的输入、破坏查询中的WHERE子句、返回数据库中保存的所有书籍的。一个更有趣的攻击是使用UNION操作符注入另外一个SELECT查询,并将查询结果附加在第一次查询的结果之后。第二次查询能够从另一个完全不同的数据库表中提取数据。例如,输入以下搜索项:

img244a

应用程序将执行以下查询:

img244b

这个查询返回最初的搜索结果,接着是用户表的内容:

作 者 书 名 出版年份
Litchfield The Database Hacker's Handbook 2005
Anley The Shellcoder's Handbook 2007
admin r00tr0x 0
cliff Reboot 1

img001  注解  如果使用UNION操作符组合两个或几个SELECT查询的结果,那么组合结果的列名称与第一个SELECT查询的列名称完全相同。如前面的表格所示,用户名出现在author列中,密码出现在title列中。这表示应用程序在处理被修改的查询结果时,它无法检测出返回的数据实际上来自一个完全不同的表。

这个简单的示例说明,UNION操作符可在SQL注入攻击中发挥非常巨大的作用。但是,在利用它发动攻击之前,攻击者有必要了解它的两个重要限制。

img002 如果使用UNION操作符组合两个查询的结果,这两个结果必须结构相同。也就是说,它们的列数必须相同,必须使用按相同顺序出现的相同或兼容的数据类型。

img002 为注入另一个返回有用结果的查询,攻击者必须知道他所针对的数据库表的名称以及有关列的名称。

现在让我们更加深入地分析前一个限制。假设攻击者试图注入另一个返回错误列数的查询。他提交以下输入:

img244c

最初的查询返回3列,而注入的查询返回2列。因此,数据库返回以下错误:

img244d

假设攻击者试图注入另一个列内数据类型不兼容的查询。他提交以下输入:

img245a

这样,数据库将尝试把第二个查询的密码列(其中为字符串数据)与第一个查询的年代列(其中为数字数据)组合起来。因为字符串数据无法转换为数字数据,这个语句造成一个错误:

img245b

img001  注解  上面是Oracle返回的错误消息。其他数据库返回的相应错误消息请参阅9.2.13节。

在许多现实例子中,数据库返回的错误消息将被应用程序截获,并不显示在用户的浏览器上。因此,如果想要查明第一个查询的结构,也许只能纯粹靠猜测。但是,事实并非如此。可以利用以下三点帮助简化这项任务。

img002 为使注入的查询能够与第一个查询结合,它不一定要使用完全相同的数据类型。但是,它们必须相互兼容,也就是说,第二个查询中的每种数据类型要么必须与第一个查询中的对应类型完全相同,要么必须隐含地转换到那个类型。数据库会将一个数字值隐含地转换为一个字符串值。实际上,NULL值可被转换成任何数据类型。因此,如果不知道某个特殊字段的数据类型,只需在那个字段输入SELECT NULL即可。

img002 如果数据库返回的错误消息被应用程序截获,还是可以轻易确定注入的查询是否得以执行。因此,如果查询已经执行,那么应用程序第一个查询返回的结果后面会增加其他结果。可以据此进行系统的推测,直到查明需要注入的查询结构。

img002 许多时候,只需在第一个查询中确定一个使用字符串数据类型的字段就可以达到自己的目的。这足以允许注入任意返回字符串数据的查询并获得其结果,帮助系统性地从数据库中提取任何想要的数据。