Chapter 9 Attacking Data Stores(2)-Injecting into SQL(1)

SQL注入(1)

Posted by D on March 14, 2020

参考:The Web Application Hackers Handbook Chapter 9

几乎每个Web应用程序都使用一个数据库来存储它需要操作的各种信息。 例如,由在线零售商部署的Web应用程序可能使用数据库来存储以下信息:

  • 用户帐户,凭据和个人信息
  • 待售商品的描述和价格
  • 订单,对帐单和付款明细
  • 应用程序中每个用户的特权

在数据库中访问信息的方法是结构化查询语言(SQL)。 SQL可用于读取,更新,添加和删除数据库中保存的信息。

SQL是一种解释型语言,Web应用程序通常构造包含用户提供的数据的SQL语句。 如果这样做是不安全的,则该应用程序可能容易受到SQL注入的攻击。 这是困扰Web应用程序的最臭名昭著的漏洞之一。 在最严重的情况下,SQL注入可以使匿名攻击者能够读取和修改数据库中存储的所有数据,甚至可以完全控制运行数据库的服务器。

随着对Web应用程序安全性的意识发展,SQL注入漏洞逐渐变得不那么普遍,并且越来越难以检测和利用。 许多现代应用程序通过使用API(如果使用得当,可以固有地抵御SQL注入攻击)来避免SQL注入。 在这些情况下,SQL注入通常发生在无法应用这些防御机制的情况下。 查找SQL注入有时是一项艰巨的任务,需要坚持不懈地在尚未应用常规控件的应用程序中定位一个或两个实例。

随着这种趋势的发展,发现和利用SQL注入漏洞的方法也在不断发展,它们使用了更细微的漏洞指示符以及更加完善和强大的利用技术。 我们将从检查最基本的案例开始,然后继续描述盲目检测和利用的最新技术。

广泛的数据库用于支持Web应用程序。 尽管SQL注入的基本原理对于其中大多数是通用的,但还是有许多差异。 这些范围从语法上的细微变化到行为和功能上的重大差异,这些差异可能会影响您可以进行的攻击类型。 出于空间和理智的考虑,我们将示例限制为您可能会遇到的三个最常见的数据库-Oracle,MS-SQL和MySQL。 在适用的情况下,我们将提请注意这三个平台之间的差异。 有了我们在此描述的技术,您应该能够通过执行一些快速的附加研究来识别和利用针对任何其他数据库的SQL注入漏洞。

TIP 在许多情况下,您可以访问目标应用程序正在使用的同一数据库的本地安装,这非常有用。 您通常会发现需要调整一段语法,或者查阅内置表或函数来实现目标。 您从目标应用程序收到的响应通常是不完整或含糊的,需要进行一些侦探工作才能理解。 如果您可以使用相关数据库的完全透明的工作版本进行交叉引用,则所有这些操作都将变得更加容易。

如果这不可行,那么一个不错的选择是找到一个可以尝试的合适的交互式在线环境,例如SQLzoo.net上的交互式教程。

1.利用基本漏洞(Exploiting a Basic Vulnerability)

考虑由书商部署的Web应用程序,该应用程序使用户可以按作者,书名,出版商等搜索产品。 整个书籍目录都保存在一个数据库中,应用程序使用SQL查询根据用户提供的搜索条件检索不同书籍的详细信息。

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

SELECT author,title,year FROM books WHERE publisher = 'Wiley' and published=1

该查询使数据库检查books表中的每一行,提取每个记录,其中publisher列的值为Wiley,而publisher列的值为1,并返回所有这些记录的集合。 然后,应用程序处理该记录集,并将其显示在HTML页面内。

在此查询中,等号左边的单词是SQL关键字以及数据库中表和列的名称。 查询的这一部分是由程序员在创建应用程序时构造的。 Wiley表达式由用户提供,其重要性是作为数据项。 SQL查询中的字符串数据必须封装在单引号中,以将其与查询的其余部分分开。

现在,考虑当用户搜索O’Reilly出版的所有书籍时会发生什么。 这将导致应用程序执行以下查询:

SELECT author,title,year FROM books WHERE publisher = 'O'Reilly' and published=1

在这种情况下,查询解释器以与以前相同的方式到达字符串数据。 它解析封装在单引号中的该数据,并获得值O。 然后,它遇到表达式Reilly',该表达式无效的SQL语法,因此会产生错误:

Incorrect syntax near 'Reilly'.
Server: Msg 105, Level 15, State 1, Line 1
Unclosed quotation mark before the character string '

当应用程序以这种方式运行时,它很容易进行SQL注入。 攻击者可以提供包含引号的输入,以终止他控制的字符串。 然后,他可以编写任意SQL来修改开发人员希望应用程序执行的查询。 例如,在这种情况下,攻击者可以通过输入以下搜索字词来修改查询,以返回零售商目录中的每本书:

Wiley' OR 1=1--

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

SELECT author,title,year FROM books WHERE publisher = 'Wiley' OR 1=1--' and published=1

这会修改开发人员查询的WHERE子句以添加第二个条件。 数据库检查books表中的每一行,并提取publisher列的值为Wiley或1等于1的每条记录。由于1始终等于1,因此数据库返回books表中的每条记录。

攻击者输入中的双连字符是SQL中有意义的表达式,该表达式告诉查询解释器该行的其余部分为注释,应忽略。 该技巧在某些SQL注入攻击中非常有用,因为它使您可以忽略应用程序开发人员创建的查询的其余部分。 在示例中,应用程序将用户提供的字符串封装在单引号中。 因为攻击者已经终止了他控制的字符串并注入了一些其他SQL,所以他需要处理尾引号以避免语法错误,如O’Reilly的示例所示。 他通过添加双连字符来实现此目的,从而将查询的其余部分视为注释。 在MySQL中,您需要在双连字符后面加上一个空格,或者使用井号字符来指定注释。

原始查询还控制了对已出版书籍的访问,因为它指定且published=1。 通过注入注释序列,攻击者可以通过返回所有已出版或未出版书籍的详细信息来获得未经授权的访问。

TIP 在某些情况下,无需使用注释符号即可处理尾随引号的另一种方法是“平衡引号”。 您需要使用字符串数据项来结束注入的输入,该字符串数据需要尾随引号将其封装起来。 例如,输入搜索词:

Wiley' OR 'a' = 'a

结果查询:

SELECT author,title,year FROM books WHERE publisher = 'Wiley' OR 'a'='a' and published=1

这是完全有效的,并且与1 = 1攻击获得相同的结果,可以返回Wiley发行的所有书籍,无论它们是否已发行。

此示例显示了如何绕过应用程序逻辑,从而允许访问控制漏洞,攻击者可以在其中查看所有书籍,而不仅仅是匹配允许过滤器的书籍(显示已发布的书籍)。 但是,我们将简短地描述如何使用这种SQL注入漏洞从不同的数据库表中提取任意数据,以及如何升级数据库和数据库服务器中的特权。 因此,无论在应用程序功能中的确切上下文如何,任何SQL注入漏洞都应被视为极其严重。

2.注入不同的语句类型(Injecting into Different Statement Types)

SQL语言包含许多可能出现在语句开头的动词。 因为它是最常用的动词,所以大多数SQL注入漏洞都出现在SELECT语句中。 确实,有关SQL注入的讨论常常给人以为,该漏洞仅与SELECT语句有关,因为使用的示例都是这种类型。 但是,SQL注入漏洞可以存在于任何类型的语句中。 您需要了解与每个方面相关的一些重要注意事项。

当然,当您与远程应用程序进行交互时,通常不可能事先知道给定的用户输入项将使用哪种类型的语句。 但是,通常可以根据要处理的应用程序功能的类型做出有根据的猜测。 此处介绍了最常见的SQL语句类型及其用法。

2.1 SELECT语句(SELECT Statements)

SELECT语句用于从数据库检索信息。 它们经常用在应用程序响应用户操作返回信息的功能中,例如浏览产品目录,查看用户个人资料或执行搜索。 它们还经常用在登录功能中,在该功能中,将根据从数据库检索到的数据检查用户提供的信息。

与前面的示例一样,SQL注入攻击的入口点通常是查询的WHERE子句。 用户提供的项目将传递到数据库,以控制查询结果的范围。 由于WHERE子句通常是SELECT语句的最后组成部分,因此攻击者可以使用注释符号将查询截断到其输入的末尾,而不会使整个查询的语法无效。

有时会发生SQL注入漏洞,这些漏洞会影响SELECT查询的其他部分,例如ORDER BY子句或表和列的名称。

2.2 INSERT语句(INSERT Statements)

INSERT语句用于在表中创建新的数据行。 当应用程序将新条目添加到审核日志,创建新用户帐户或生成新订单时,通常使用它们。

例如,一个应用程序可以允许用户自行注册,指定他们自己的用户名和密码,然后可以使用以下语句将详细信息插入到用户表中:

INSERT INTO users (username, password, ID, privs) VALUES ('daf','secret', 2248, 1)

如果usernamepassword字段易受SQL注入攻击,则攻击者可以在表中插入任意数据,包括他自己的IDprivs值。 但是,为此,他必须确保VALUES子句的其余部分正常完成。 特别是,它必须包含正确数量的正确类型的数据项。 例如,注入到username字段中,攻击者可以提供以下内容:

foo', 'bar', 9999, 0)--

这将创建一个ID9999privs0的帐户。假定privs字段用于确定帐户特权,这可能使攻击者能够创建管理用户。

在某些情况下,当完全盲目的工作时,注入INSERT语句可能使攻击者能够从应用程序中提取字符串数据。 例如,攻击者可以获取数据库的版本字符串,并将其插入他自己的用户配置文件中的字段中,然后以常规方式将其显示回浏览器。

TIP 尝试插入INSERT语句时,您可能事先不知道需要多少个参数或它们的类型。 在上述情况下,您可以继续将字段添加到VALUES子句中,直到实际创建所需的用户帐户为止。 例如,当注入用户名字段时,您可以提交以下内容:

foo')--
foo', 1)--
foo', 1, 1)--
foo', 1, 1, 1)--

由于大多数数据库都将整数隐式转换为字符串,因此可以在每个位置使用整数值。 在这种情况下,无论其他字段的顺序如何,结果都是一个用户名为foo且密码为1的帐户。

如果发现值1仍然被拒绝,则可以尝试使用值2000,许多数据库也将其隐式转换为基于日期的数据类型。

确定注入点之后的正确字段数后,在MS-SQL上,您可以添加第二个任意查询,并使用本章稍后介绍的基于推理的技术之一。

在Oracle中,可以在插入查询中发出子选择查询。 使用稍后描述的基于推理的技术,此子选择查询可能导致主查询成功或失败。

2.3 UPDATE语句(UPDATE Statements)

UPDATE语句用于修改表中的一个或多个现有数据行。 它们通常用于用户更改现有数据值的功能中,例如,更新她的联系信息,更改她的密码或更改订单行中的数量。

典型的UPDATE语句的工作原理与INSERT语句类似,不同之处在于它通常包含WHERE子句以告知数据库要更新表的哪些行。 例如,当用户更改密码时,应用程序可能会执行以下查询:

UPDATE users SET password='newsecret' WHERE user = 'marcus' and password = 'secret'

该查询实际上会验证用户的现有密码是否正确,如果正确,则使用新值对其进行更新。 如果该功能容易受到SQL注入的攻击,则攻击者可以通过输入以下用户名来绕过现有的密码检查并更新管理员用户的密码:

admin'--

NOTE 在远程应用程序中探查SQL注入漏洞总是潜在的危险,因为您无法预先知道应用程序将如何使用手工输入执行什么操作。 特别是,在UPDATE语句中修改WHERE子句可能会导致对数据库的关键表进行更改。 例如,如果刚才描述的攻击改为提供了用户名:

admin' or 1=1--

这将导致应用程序执行查询:

UPDATE users SET password='newsecret' WHERE user = 'admin' or 1=1

这将重置每个用户密码的值,因为1始终等于1!

请注意,即使您攻击的应用程序功能似乎无法更新任何现有数据(例如主登录名),也存在这种风险。 在某些情况下,成功登录后,应用程序将使用提供的用户名执行各种UPDATE查询。 这意味着对WHERE子句的任何攻击都可能在其他语句中复制,从而可能对所有应用程序用户的性能造成严重破坏。您应确保应用程序所有者在尝试探查或利用任何SQL注入之前接受这些不可避免的风险。 鞭子。 您还应该强烈鼓励所有者在开始测试之前执行完整的数据库备份。

2.4 DELETE语句(DELETE Statements)

DELETE语句用于删除表中的一行或多行数据,例如,当用户从购物篮中删除商品或从其个人详细信息中删除送货地址时。

UPDATE语句一样,通常使用WHERE子句告诉数据库要更新表的哪些行。 用户提供的数据最有可能合并到此子句中。 颠覆预期的WHERE子句可能会产生深远的影响,因此针对UPDATE语句所述的警告同样适用于此攻击。

3.查找SQL注入错误(Finding SQL Injection Bugs)

在最明显的情况下,可以通过向应用程序提供单个意外输入来发现并最终验证SQL注入错误。在其他情况下,错误可能非常微妙并且可能难以与其他类别的错误区分开来。 漏洞或来自不会带来安全威胁的良性异常。 但是,您可以按有序的方式执行各种步骤,以可靠地验证大多数SQL注入漏洞。

NOTE 在应用程序映射练习(请参见第4章)中,您应该已经确定了应用程序似乎正在访问后端数据库的实例。 所有这些都需要针对SQL注入漏洞进行探测。 实际上,提交给服务器的任何数据项绝对都可能以用户看不见的方式传递给数据库功能,并且可能以不安全的方式进行处理。 因此,您需要探查每个此类项目的SQL注入漏洞。 这包括所有URL参数,cookie,POST数据项和HTTP标头。 在所有情况下,有关参数的名称和值的处理都可能存在漏洞。

TIP 在探查SQL注入漏洞时,请确保逐步完成提交人工输入的任何多阶段过程。 应用程序经常跨多个请求收集数据集合,并且只有在收集了完整的集合之后,它们才会将其持久保存到数据库中。 在这种情况下,如果您仅在每个单独的请求中提交精心制作的数据并监视应用程序对该请求的响应,那么您将错过许多SQL注入漏洞。

3.1 Injecting into String Data

当用户提供的字符串数据合并到SQL查询中时,它将封装在单引号中。 要利用任何SQL注入漏洞,您需要使用这些引号。

HACK STEPS

  • 1.提交一个引号作为您要定位的数据项。 观察是否发生错误,或结果是否与原始形式有任何不同。 如果收到详细的数据库错误消息,请查阅本章的“SQL Syntax and Error Reference”部分以了解其含义。
  • 2.如果发现错误或其他不同行为,请一起提交两个单引号。 数据库使用两个单引号作为转义序列来表示文字单引号,因此该序列被解释为带引号的字符串中的数据,而不是结束字符串的终止符。 如果此输入导致错误或异常行为消失,则该应用程序可能容易受到SQL注入的攻击。
  • 3.为了进一步证明存在错误,您可以使用SQL连接器字符来构造与某些良性输入等效的字符串。 如果应用程序以与对应的良性输入相同的方式处理您的精心设计的输入,则它很容易受到攻击。 每种类型的数据库都使用不同的方法来进行字符串连接。 可以注入以下示例来构造与易受攻击的应用程序中的FOO等效的输入:
    • Oracle:’   ‘FOO
    • MS-SQL:’+’FOO
    • MySQL:’ ‘FOO(注意两个引号之间是空格)

TIP 确认应用程序正在与后端数据库进行交互的一种方法是在给定参数中提交SQL通配符%。 例如,在搜索字段中提交此结果通常会返回大量结果,表明输入正在传递到SQL查询中。 当然,这并不一定表示该应用程序容易受到攻击-只是您应该进一步探查以发现任何实际缺陷。

TIP 当使用单引号查找SQL注入时,请注意浏览器处理返回的页面时发生的任何JavaScript错误。 在JavaScript中返回用户提供的输入是相当普遍的,并且未经处理的单引号会在JavaScript解释器中引起错误,就像在SQL解释器中一样。 如第12章所述,将任意JavaScript注入响应的能力允许跨站点脚本攻击。

3.2 Injecting into Numeric Data

当用户提供的数字数据合并到SQL查询中时,应用程序仍可以通过将其封装在单引号中来将其作为字符串数据进行处理。 因此,对于字符串数据,您应始终遵循上述步骤。 但是,在大多数情况下,数字数据会以数字形式直接传递到数据库,因此不会放在单引号中。 如果以前的测试均未指出存在漏洞,则可以针对数字数据采取其他一些特定步骤。

HACK STEPS

  • 1.尝试提供一个与原始数值等效的简单数学表达式。 例如,如果原始值为2,请尝试提交1 + 1或3-1。 如果应用程序以相同的方式响应,则可能容易受到攻击。
  • 2.如果您确认修改的项目对应用程序的行为有明显的影响,则上述测试是最可靠的。 例如,如果应用程序使用数字PageID参数指定应返回的内容,则用1 + 1替换2并得到等效结果,这是存在SQL注入的一个好兆头。 但是,如果您可以在不更改应用程序行为的情况下将任意输入放入数字参数,则前面的测试不会提供任何漏洞证据。
  • 3.如果第一次测试成功,则可以通过使用更复杂的表达式(使用特定于SQL的关键字和语法)来获得有关该漏洞的进一步证据。 ASCII命令就是一个很好的例子,该命令返回所提供字符的数字ASCII码。 例如,由于A的ASCII值为65,因此以下表达式在SQL中等于2:67-ASCII('A')
  • 4.如果要过滤单引号,则前面的测试将不起作用。 但是,在这种情况下,您可以利用数据库在需要时将数字数据隐式转换为字符串数据这一事实。 因此,由于字符1的ASCII值为49,因此以下表达式在SQL中等于2:51-ASCII(1)

TIP 在探查应用程序中是否存在诸如SQL注入之类的缺陷时,一个常见的错误是忘记了某些字符在HTTP请求中具有特殊含义。 如果要在攻击有效载荷中包含这些字符,则必须小心对它们进行URL编码,以确保按照您想要的方式对它们进行解释。 尤其是:

  • &=用于连接名称/值对以创建查询字符串和POST数据块。 您应该分别使用%26%3d对其进行编码。
  • 查询字符串中不允许使用文字空间。 如果提交,则它们将有效终止整个字符串。 您应该使用+%20对它们进行编码。
  • 因为+用于对空格进行编码,所以如果要在字符串中包含实际的+,则必须使用%2b对其进行编码。 因此,在前面的数字示例中,应将1+1提交为1%2b1
  • 分号用于分隔Cookie字段,应使用%3b进行编码。

无论您是直接通过浏览器,使用拦截代理还是通过任何其他方式来编辑参数的值,这些编码都是必需的。 如果您未能正确编码问题字符,则可以使整个请求无效或提交您不希望的数据。

刚描述的步骤通常足以识别大多数SQL注入漏洞,包括其中许多没有有用的结果或错误信息传输回浏览器的漏洞。 但是,在某些情况下,可能需要更高级的技术,例如使用时间延迟来确认漏洞的存在。 我们将在本章稍后描述这些技术。

3.3 Injecting into the Query Structure

如果将用户提供的数据而不是查询中的数据项插入到SQL查询本身的结构中,则利用SQL注入仅涉及直接提供有效的SQL语法。 不需要“转义”即可突破任何数据上下文。

SQL查询结构中最常见的注入点在ORDER BY子句中。 ORDER BY关键字采用列名或数字,并根据该列中的值对结果集进行排序。 经常向用户公开此功能,以允许在浏览器中对表进行排序。 一个典型的示例是使用以下查询检索的可排序书籍表:

SELECT author, title, year FROM books WHERE publisher = 'Wiley' ORDER BY title ASC

如果用户指定了ORDER BY中的列名title,则不必使用单引号。 用户提供的数据已经直接修改了SQL查询的结构。

TIP 在少数情况下,用户提供的输入可以在WHERE子句中指定列名。 因为这些也没有封装在单引号中,所以会发生类似的问题。 作者还遇到过应用程序,其中表名是用户提供的参数。 最后,数量惊人的应用程序公开了由用户指定的排序顺序关键字(ASCDESC),也许认为这对SQL注入攻击没有影响。

在列名中查找SQL注入可能很困难。 如果提供的值不是有效的列名,则查询将导致错误。 这意味着无论攻击者提交路径遍历字符串,单引号,双引号还是任何其他任意字符串,响应都将相同。 因此,用于自动模糊测试和手动测试的通用技术可能会忽略此漏洞。 针对多种漏洞的标准测试字符串都将导致相同的响应,而这本身可能并未揭示错误的性质。

NOTE 本章后面介绍的某些常规SQL注入防御无法为用户指定的列名实现。 使用准备好的语句或转义单引号不会阻止这种类型的SQL注入。 结果,该向量是在现代应用中需要注意的关键之一。

HACK STEPS

  • 1.记录所有似乎可以控制应用程序返回结果中的顺序或字段类型的参数。
  • 2.发出一系列在参数值中提供数字值的请求,从数字1开始,并在随后的每个请求中递增:
    • 如果更改输入中的数字会影响结果的顺序,则可能会将输入插入到ORDER BY子句中。 在SQL中,第一列按ORDER BY 1顺序排列。 然后将此数字增加到2即可将数据的显示顺序更改为第二列的顺序。 如果提供的数目大于结果集中的列数,则查询应失败。 在这种情况下,可以使用以下方法检查结果顺序是否可以颠倒,从而确认是否可以插入其他SQL:
      1 ASC --
      1 DESC --
      
    • 如果提供数字1会导致一组结果,且每一行的列均包含1,则可能是将输入插入查询所返回的列的名称中。 例如:
      SELECT 1,title,year FROM books WHERE publisher='Wiley'
      

NOTEORDER BY子句中利用SQL注入与大多数其他情况有很大不同。 此时,数据库在查询中将不接受UNIONWHEREORAND关键字。 通常,利用漏洞需要攻击者指定一个嵌套查询来代替参数,例如用(select 1 where <<condition>> or 1/0=0)替换列名,从而利用本章后面介绍的推理技术。对于支持批处理查询的数据库(例如MS-SQL),这可能是最有效的选择。

Chapter 9 Attacking Data Stores(1)-Injecting into Interpreted Contexts
Chapter 9 Attacking Data Stores(2)-Injecting into SQL(2)

: