什么是SQL注入(SQLi)?

SQL注入是一个安全漏洞,攻击者可以利用该漏洞来干扰应用程序对其数据库的查询。它使攻击者可以查看他们无法检索的数据。这可能包括属于其他用户的数据,或者应用程序本身能够访问的任何其他数据。在许多情况下,攻击者可以修改或删除此数据,从而导致应用程序内容或行为的永久更改。

在某些情况下,攻击者可以升级SQL注入攻击以破坏基础服务器或其他后端基础结构,或者执行拒绝服务攻击。

SQL注入攻击有什么影响?

成功的SQL注入攻击可能导致未经授权访问敏感数据,例如密码,信用卡详细信息或个人用户信息。近年来,许多引人注目的数据泄露是SQL注入攻击的结果,导致声誉受损和监管罚款。在某些情况下,攻击者可以获取组织系统的持久后门,而这可能会在很长一段时间内被忽略。

SQL注入示例

在不同情况下会出现各种SQL注入漏洞,攻击和技术。一些常见的SQL注入示例包括:

  • 检索隐藏的数据:您可以在其中修改SQL查询以返回其他结果。
  • 颠覆应用程序逻辑:您可以在其中更改查询以干扰应用程序的逻辑。
  • UNION攻击:您可以在其中从不同的数据库表中检索数据。
  • 检查数据库:您可以在其中提取有关数据库版本和结构的信息。
  • 盲SQL注入:其中您控制的查询结果未在应用程序的响应中返回。

检索隐藏数据

考虑一个显示不同类别产品的购物应用程序。当用户单击“礼物”类别时,其浏览器将请求URL:

https://insecure-website.com/products?category=Gifts

这将导致应用程序执行SQL查询,以从数据库中检索相关产品的详细信息:

SELECT * FROM products WHERE category = 'Gifts' AND released = 1

此SQL查询要求数据库返回:

  • 所有详细信息(*)
  • 产品表
  • 类别是什么
  • 并发布为1。

该限制released = 1用于隐藏未发布的产品。对于未发布的产品,大概是released = 0

该应用程序未对SQL注入攻击实施任何防御措施,因此攻击者可以构建如下攻击:

https://insecure-website.com/products?category=Gifts'--

这将导致SQL查询:

SELECT * FROM products WHERE category = 'Gifts'--' AND released = 1

这里的关键是双破折号--是SQL中的注释指示符,意味着查询的其余部分将被解释为注释。这样可以有效删除查询的其余部分,因此不再包含AND released = 1。这意味着将显示所有产品,包括未发布的产品。

更进一步,攻击者可以使应用程序显示任何类别的所有产品,包括他们不知道的类别:

https://insecure-website.com/products?category=Gifts'+OR+1=1--

这将导致SQL查询:

SELECT * FROM products WHERE category = 'Gifts' OR 1=1--' AND released = 1

修改后的查询将返回类别为Gifts或1等于1的1=1所有项目。由于始终为true,因此查询将返回所有项目。

颠覆应用逻辑

考虑一个允许用户使用用户名和密码登录的应用程序。如果用户提交用户名wiener和密码bluecheese,则应用程序将通过执行以下SQL查询来检查凭据:

SELECT * FROM users WHERE username = 'wiener' AND password = 'bluecheese'

如果查询返回用户的详细信息,则登录成功。否则,它将被拒绝。

在这里,攻击者可以简单地使用SQL注释序列--WHERE查询子句中删除密码检查,而无需密码即可以以任何用户身份登录。例如,提交用户名administrator'--和空白密码将导致以下查询:

SELECT * FROM users WHERE username = 'administrator'--' AND password = ''

因--注释所以该查询返回用户名为administrator的用户,并成功以该用户身份登录。

从其他表检索数据

如果在应用程序的响应中返回了SQL查询的结果,则攻击者可以利用SQL注入漏洞从数据库中的其他表中检索数据。这是使用UNION关键字完成的,该关键字使您可以执行附加SELECT查询并将结果附加到原始查询中。

例如,如果应用程序执行以下查询,其中包含用户输入“ Gifts”:

SELECT name, description FROM products WHERE category = 'Gifts'

然后攻击者可以提交输入:

' UNION SELECT username, password FROM users--

这将导致应用程序返回所有用户名和密码以及产品名称和描述。

检查数据库

在初步识别出SQL注入漏洞之后,获取有关数据库本身的一些信息通常非常有用。这些信息通常可以为进一步攻击铺平道路。

您可以查询数据库的版本详细信息。完成此操作的方式取决于数据库类型,因此您可以从任何一种技术推断出数据库类型。例如,在Oracle上,您可以执行:

SELECT * FROM v$version

您还可以确定存在哪些数据库表以及它们包含哪些列。例如,在大多数数据库上,您可以执行以下查询以列出表:

SELECT * FROM information_schema.tables

盲SQL注入漏洞

SQL注入的许多实例都是盲目的漏洞。这意味着该应用程序不会在其响应内返回SQL查询的结果或任何数据库错误的详细信息。盲目漏洞仍然可以被利用来访问未经授权的数据,但是所涉及的技术通常更加复杂并且难以执行。

根据漏洞的性质和所涉及的数据库,可以使用以下技术来利用盲目的SQL注入漏洞:

  • 您可以更改查询的逻辑,以根据单个条件的真实性触发应用程序响应中可检测到的差异。这可能涉及将新条件注入某些布尔逻辑,或有条件地触发诸如除零的错误。
  • 您可以有条件地触发查询处理的时间延迟,从而允许您根据应用程序响应的时间推断条件的真相。
  • 您可以使用OAST技术触发带外网络交互。该技术非常强大,可以在其他技术无法使用的情况下使用。通常,您可以直接通过带外通道泄露数据,例如,将数据放入您控制的域的DNS查找中。

如何检测SQL注入漏洞

通过对应用程序中的每个入口点使用系统化的测试集,可以手动检测SQL注入。这通常涉及:

  • 提交单引号字符'并查找错误或其他异常。
  • 提交一些特定于SQL的语法,以评估入口点的基础(原始)值和其他值,并在产生的应用程序响应中寻找系统差异。
  • 提交布尔条件,例如OR 1=1OR 1=2, and在应用程序的响应中寻找差异。
  • 提交旨在在SQL查询中执行时触发时间延迟的有效负载,并寻找响应时间的差异。
  • 提交旨在在SQL查询中执行时触发带外网络交互的OAST有效负载,并监视所有结果交互。

SQL注入查询的不同部分

大多数SQL注入漏洞出现在查询的WHERE子句中。有经验的测试人员通常会很好地理解这种SQL注入。

但是,原则上,SQL注入漏洞可以出现在查询中的任何位置以及不同的查询类型中。发生SQL注入的最常见其他位置是:

  • UPDATE语句中,在更新的值或WHERE子句中。
  • INSERT语句中,在插入的值内。
  • SELECT语句中,在ORDER BY子句中。

二阶SQL注入

一阶SQL注入出现在应用程序从HTTP请求获取用户输入的情况下,并且在处理该请求的过程中,以不安全的方式将输入合并到SQL查询中。

在二阶SQL注入(也称为存储的SQL注入)中,应用程序从HTTP请求中获取用户输入并将其存储以备将来使用。这通常是通过将输入放入数据库来完成的,但是在存储数据时不会出现漏洞。稍后,当处理其他HTTP请求时,应用程序将以不安全的方式检索存储的数据并将其合并到SQL查询中。

二阶SQL注入通常发生在开发人员意识到SQL注入漏洞并安全地处理输入到数据库中的初始位置的情况下。以后处理数据时,由于先前已将其安全地放置到数据库中,因此认为该数据是安全的。此时,由于开发人员错误地认为数据是可信的,因此以不安全的方式处理数据。

如何防止SQL注入

通过使用参数化查询(也称为预处理语句)而不是查询中的字符串串联,可以防止大多数SQL注入实例。

由于用户输入直接连接到查询中,因此以下代码容易受到SQL注入的攻击:

String query = "SELECT * FROM products WHERE category = '"+ input + "'";
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(query);

以防止用户输入干扰查询结构的方式重写此代码:

PreparedStatement statement = connection.prepareStatement("SELECT * FROM products WHERE category = ?");
statement.setString(1, input);
ResultSet resultSet = statement.executeQuery();

参数化查询可用于不信任输入在查询中显示为数据的任何情况,包括or 语句中的WHERE子句和值。它们不能用于处理查询其他部分中的不可信输入,例如表或列名或子句。将不受信任的数据放入查询的那些部分的应用程序功能将需要采用不同的方法,例如将允许的输入值列入白名单,或者使用不同的逻辑来传递所需的行为。 INSERT``UPDATE``ORDER BY

为了使参数化查询有效地防止SQL注入,查询中使用的字符串必须始终是硬编码常量,并且绝不能包含来自任何来源的变量数据。不要试图判断数据项是否受信任,并继续在查询中使用字符串连接来处理认为安全的情况。可能会很容易就数据的原始来源犯错误,也可能因为其他代码的更改而违反关于污染了哪些数据的假设。