所有文章 > 技术杂货铺 > 什么是 SQL 注入?
什么是 SQL 注入?

什么是 SQL 注入?

SQL 注入是攻击者最常用的攻击媒介之一。事实上,就在 2019 年,SQL 注入攻击(也称为 SQLi 攻击)占所有 Web 应用程序攻击的近三分之二。这些数字是惊人的,放在实际情况中就更是如此。尽管自 2003 年以来,SQLi 一直是十大 CVE 漏洞之一,但它只是在过去几年才开始加速发展和普及;就在 2019 年之前的两年里,SQLi 仅占Web 应用程序攻击的 44%。这种增长表明在 DevOps 流程中添加自动化安全测试是多么重要,这样在这些错误投入生产之前就能发现它们。 

让我们分析一下这个错误类别以及如何避免它。

什么是 SQL 注入?

SQL 注入是一种 Web 应用程序攻击,攻击者“注入”SQL 语句来操纵或访问应用程序数据,无论这些数据是敏感数据还是公开数据。这些攻击利用 Web 应用程序中要求用户输入的区域。如果应用程序中的用户输入未得到适当清理,攻击者可以使用 SQL 注入来访问相关的应用程序数据存储区。 

SQL 注入示例

攻击者通常使用 SQL 注入通过用户输入渗透 Web 应用程序。这包括用户名、用户 ID、名字和姓氏等表单填写。如果您在接受这些输入之前没有对其进行清理,或者大量使用参数化 SQL 语句,攻击者可以通过该输入传递 SQL 语句,并在不知情的情况下在您的数据库上运行。

例如,假设您正在从用户那里获取用户 ID 的输入。当您的应用程序获取有关用户的信息时,URL 可能如下所示:

合法的 SQL 查询如下所示: SQL

SELECT * FROM users WHERE id = '42'

他们输入他们的用户 ID,您接受他们的输入,使用它在您的数据库中查找他们的信息,并为他们显示他们的数据。 

但是,请考虑一下:他们没有输入用户 ID,而是输入了可以解释为 SQL 查询的内容。例如:SQL

'42' OR '1'='1'

如果你按原样获取他们的输入而不进行清理,这将导致类似如下的 SQL 查询:SQL

SELECT * FROM users WHERE id = '42' OR '1'='1';

由于 1=1 始终为真,因此这将返回所有用户的每个数据字段。这是 SQL 注入的典型示例。

需要注意的是,这是数据库针对此类查询而设计的输出。在这种情况下,攻击者并不想破坏您开发的应用程序……只是利用现有资源来访问他们不应该访问的内容。在开发应用程序时,请尝试考虑哪些内容可以访问但不应该访问,然后实施方法来阻止这种访问。 

SQL 注入有哪些类型?

SQL 注入主要有三种类型:带内 SQLi、推理 SQLi 和带外 SQLi。

In-band SQLi

当攻击者从用于收集注入输出的同一位置发起 SQL 注入时,这被称为带内 SQLi。这是最常见的 SQLi 攻击类型之一,通常分为基于错误的 SQLi 和基于联合的 SQLi。

Error-based SQLi

基于错误的 SQLi 是一种输出数据库抛出的错误消息的 SQL 注入。这种类型的注入可用于向攻击者提供有关数据库的宝贵信息,例如其大小和元素。很多时候,攻击者会使用基于错误的 SQLi 对数据库进行侦察,然后最终执行 SQL 注入,以执行更复杂的任务,例如输出数据。 

Error-based SQLi示例 

假设您已在生产环境中使用 Web 应用程序登录时遗留了错误。为了收集有关数据库的信息,攻击者会使用他们知道会返回错误的内容来修改用户输入。 

这会产生以下 SQL 查询:SQL

SELECT * FROM users WHERE id = '42''

由于末尾有多余的勾号,因此会引发错误。如果错误日志已打开,则错误会呈现给攻击者:

Error: 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….

攻击者现在知道 Web 应用程序容易受到基于错误的 SQLi 的攻击,并且可以通过调用提供有关数据库的更多信息的错误消息来利用这一点。 

为了防止这种情况,应在实时 Web 应用程序中禁用错误​​日志记录,或将其输出到受限文件。 

Union-based SQLi

基于 Union 的 SQLi 使用 UNION 运算符将附加数据输出到单个结果中,通常是输出到 Web 应用程序中已经可见的表中。为了成功执行基于 Union 的 SQLi,攻击者必须掌握有关数据库的信息,例如表名、查询中的列数和数据类型。这是因为,为了使 UNION 成功,被联合的 SELECT 查询必须:

  1. 具有相同数量的列。
  2. 具有兼容的数据类型。

收集这些侦察数据的一种方法是启用错误日志记录后通过基于错误的 SQLi。从错误日志收集的信息可能为攻击者提供足够的信息来了解表的大小和使用的数据类型。 

Union-based SQLi 示例

考虑这样一种情况:攻击者设法收集有关表的大小、正在使用的数据类型以及第二个表的名称names 的信息。 

http://mycoolapp.com/allusers.php?id=' UNION SELECT * FROM names --

这将导致 SQL 查询:SQL

SELECT * FROM users WHERE id ='' UNION SELECT * FROM names -- ' and password = 'abcd'

— 是 SQL 中的注释,因此 — 之后的所有内容都会自动被注释掉。初始 SELECT 语句返回空集,因为users中没有 id 为 ” 的用户,而第二个 SELECT 语句返回names中的所有信息。 

Blind SQLi

盲 SQLi 与带内 SQLi 非常相似,但有一个区别:来自 Web 应用程序的响应不会输出查询结果或数据库错误。基本上,Web 应用程序开发人员会抑制来自数据库的错误消息,从而使攻击者更难使用 SQL 注入。但是,这并不能解决 SQL 注入问题。攻击者仍然可以使用盲 SQLi,它有两种形式:基于内容的盲 SQLi 和基于时间的盲 SQLi。 

Content-based Blind SQLi

基于内容的盲 SQLi 使用查询来获取条件响应,而不是数据输出。这些 SQL 查询会询问数据库真或假的问题,以便攻击者可以评估输出并确定 Web 应用程序是否存在漏洞。这可能非常繁琐,因此攻击者有时会自动执行这些攻击。 

Content-based Blind SQLi 示例

回到我们很酷的应用程序示例。考虑这样一种情况,您已采取了一些预防措施,并且不显示查询结果或数据库错误的输出。为了收集有关您的数据库的侦察,攻击者注入查询,希望系统返回 false。 

这会产生以下 SQL 查询:SQL

SELECT * FROM users WHERE id = 42 and 4=1

当然,四不等于一。如果应用程序容易受到基于内容的盲 SQLi 攻击,则此查询不会返回任何内容,或者页面会在某种程度上与正常功能不同。虽然这并不一定能证实应用程序存在漏洞,但实际上对攻击者来说可能是一个好兆头。为了确认应用程序存在漏洞,攻击者将注入一个应返回 true 的查询并观察输出。 

这会产生以下 SQL 查询:SQL

SELECT * FROM users WHERE id = 42 and 4=4

这将返回 true 并输出 ID 为 42 的用户的数据。 

如果 Web 应用程序对数据库返回 true 和 false 的响应不同,则攻击者知道该应用程序容易受到 SQL 注入攻击。通过继续对数据库使用 true/false 测试,攻击者可以找到有关它的其他信息,甚至可能找到数据库本身的内容。 

Time-based Blind SQLi

基于时间的盲 SQLi 查询系统以执行时间密集型操作。可用于基于时间的盲 SQLi 的典型时间密集型操作是 sleep() 操作。攻击者可以向数据库发送查询以使其休眠一段时间,如果 Web 应用程序在该时间段内延迟其响应,则它很容易受到攻击。 

Time-based Blind SQLi 示例

再次考虑这样一种情况:您已采取一些预防措施来防止查询结果输出到数据库或数据库错误。为了收集有关数据库的侦察信息,攻击者可以尝试使用 SLEEP() 函数来影响数据库。 

如果数据库响应缓慢,则意味着查询已成功执行,攻击者能够对数据库执行 SQL 查询。从那里,攻击者可以使用其他盲 SQL 注入技术来收集有关数据库的其他信息。

Out-of-Band SQLi

与带内 SQLi 相比,带外 SQLi 要求攻击者使用与收集 SQL 注入输出不同的渠道来发起 SQL 注入。例如,如果攻击者在 Web 应用程序上使用带外 SQL 注入,他们会操纵数据库服务器将数据传送到他们控制的独立服务器。这些注入会滥用 Microsoft SQL Server 的 xp_dirtree 命令等工具向攻击者控制的服务器发出 DNS 请求。带外 SQLi 比其他类型的 SQL 注入少见得多,因为它们非常依赖于数据库服务器上启用的功能。 

如何编写防止 SQL 注入的安全代码

净化用户输入

防止 SQL 注入的最佳方法是清理数据库输入。任何类型的用户输入都应进行评估,类似于检查新注册者是否向您提供了合法的电子邮件地址而不是随机字符串。在应用程序的服务器端(而不仅仅是客户端)验证用户输入。客户端验证可以使攻击者更难更改用户输入,但有许多工具可让攻击者在必要时绕过客户端验证。

净化输入的示例

在将用户输入添加到数据库之前对其进行清理的最简单方法是使用正则表达式禁止内容。

例如,假设您希望用户输入并且仅允许字母、数字和空格。正则表达式可能如下所示: 

此正则表达式将允许字母数字字符和空格。通过将您收到的输入与此进行比较,您可以识别并阻止接受任何其他字符。使用正则表达式只是如何清理输入的一个示例,除此之外,采取其他预防措施也很重要,例如下面列出的预防措施。  

通过框架防止 SQL 注入

许多框架都提供了特定的最佳实践来防止 SQLi,通常以允许开发人员使用准备好的语句或参数化查询从用户输入构建其 SQL 语句的形式出现。如果您有兴趣了解如何防止您使用的特定框架发生 SQL 注入,请务必查看我们的指南之一:

其他实用建议 

利用准备好的语句和存储过程

说到防止 SQL 注入尝试,一种标准方法是在代码中使用准备好的语句。除此之外,您可能还想利用可以在数据库内部创建的存储过程。这两个选项都可以防止用户轻易将恶意 SQL 注入数据库服务器将执行的 SQL 代码中。不要运行“原始”查询(在字符串中创建 SQL 查询,然后直接在数据库中运行它),而是确保每个 SQL 命令仅通过这些机制执行。

遵循最小特权原则

最小权限原则仅根据用户所需的权限来限制其访问权限。例如,Web 应用程序的用户可能不需要访问整个数据库。相反,授予用户访问其所需表的权限,以减少滥用的潜在影响。 

将敏感数据与公共数据分开

存储敏感数据必须以不同于存储公共数据的方式处理。敏感数据应仅在应用程序需要时才进行存储,并且应进行加密。对敏感数据采取额外的预防措施,而不必对公共数据采取这些预防措施,以确保用户的隐私。

在构建管道中自动测试 SQL 注入

虽然此类博客和针对工程团队的安全培训很有帮助,但避免应用程序中出现 SQL 注入漏洞的最佳方法是在管道中自动进行测试。

一些关于SQL的API清单

文章来源:What is SQL Injection?

#你可能也喜欢这些API文章!