name: sql-injection-comprehensive description: >- SQL 注入多策略综合检测——分诊布尔盲注 / 时间盲注 / 报错 / UNION,覆盖各主流 DBMS。 流量中参数值带引号触发响应突变、响应含数据库错误关键字、同参数二态可控、注入 SLEEP 耗时增加时使用。 when-to-use: 当存在 SQL 注入嫌疑、注入类型或数据库不明确,需要多策略分诊探测时 allowed-tools: bash,read_file,list_files,rg user-invocable: false
SQL 注入:多策略综合检测(黑盒)
1. 触发线索 / 适用信号
以下是已知的 SQLi 触发线索,作为基线起点而非必检硬清单。结合流量与响应特征动态调整:
- 适用且已完成 →
[x] done - 明确不适用 →
[-] n/a (原因)(原因要具体到响应特征) - 基线未列出但实际发现 →
[+] added (来源)
响应特征命中信号(漏洞-specific):
- 单引号触发响应突变(200 → 500 / 内容差异)
- 响应里出现数据库错误关键字(
SQL syntax/ORA-/PostgreSQL/Unclosed quotation) - 同参数
1=1vs1=2响应可区分 - 注入
SLEEP(N)耗时增加 N 秒
入口类型粗筛(仅作类似场景示例 不限于此):
- 搜索 / 列表 / 筛选接口(LIKE 拼接常见)
- 登录 / 注册接口(WHERE 拼接常见)
- 排序接口(ORDER BY 拼接,白名单常缺失)
- 详情 / 修改接口(路径 ID / 主键查询)
业务命名只作粗筛——sink 语义相同就属此范围。JSON body 任一字段、header / cookie 任一字段都是 SQLi 候选,不限于"看起来像数据库"的字段名;具体参数位置由 HAR 实际请求推导,不在此预设清单。
2. 造成原因
source 是任何用户可控输入(query / body / header / cookie / 路径参数 / 已入库再回读的字段)。sink 是 SQL 上下文:WHERE / LIKE / ORDER BY / GROUP BY 子句、INSERT 值、UPDATE SET、JOIN ON、HAVING、LIMIT / OFFSET、任何拼接进 SQL 查询的字符串位置。
任何 source 未经参数化绑定就被拼接到 sink,即构成 SQL 注入——攻击者控制的字符串改变了 SQL 语法树。预编译参数(PreparedStatement / $1 $2 / ? 占位符)把数据和指令在协议层隔离开,是默认防御。
ORM 不是免疫层:所有 ORM 都有 Raw 通道(.raw() / .query() / Raw() / nativeQuery()),且字段名 / 表名 / 排序方向不可参数化(必须白名单)。
3. 响应信号映射
列出 SQLi 黑盒可观测的响应通道集合(observation-channel)——攻击效果可被黑盒探测的侧信道。输入位置(query / body / header / cookie / path)从 HAR 实际请求推导,本节不预设清单。
响应观察通道集合(observation-channel):
error-echo:错误信息回显(数据库错误关键字 + 可控数据回显)union-echo:UNION 拼接列值直接渲染到响应boolean-diff:同参数二态稳定可区分(status / 内容 / 长度)time-sidechannel:耗时可控(SLEEP / WAITFOR / pg_sleep)oob-dns/oob-http:带外通道触发(DNS / HTTP 请求)secondary-readback:二阶注入在另一端点回读时触发
4. 常见类型
| 类型 | 触发条件 | 响应特征 | 适用 DBMS |
|---|---|---|---|
| 布尔盲注(Boolean) | 响应内容随条件 true/false 二态变化 | ' AND 1=1 -- vs ' AND 1=2 -- 响应可区分 |
全部 |
| 时间盲注(Time-based) | 响应内容不可区分但耗时可控 | SLEEP(N) / WAITFOR DELAY / pg_sleep |
MySQL / MSSQL / PG |
| 报错注入(Error-based) | 错误信息回显到响应 | updatexml() / extractvalue() / cast() |
MySQL / MSSQL |
| UNION 注入 | 查询结果直接渲染到响应 | UNION SELECT NULL, NULL, ... 列数匹配后回显 |
全部 |
| 堆叠查询(Stacked) | 驱动允许 ; 分隔多语句 |
; DROP TABLE... 副作用 |
MSSQL / PG(MySQL 默认禁) |
| 二阶注入 | 先入库后回读时触发 | source 入库不报错,回读拼接时触发 | 全部 |
| OOB(带外) | 无回显且无延时通道 | LOAD_FILE / xp_dirtree 触发 DNS / HTTP |
MySQL / MSSQL |
5. 侦察输入
按以下方式从侦察输出中筛 SQLi 候选:
从 HAR / 端点账本:
- 参数名含
q/search/keyword/id/sort/order/filter/where/category/tag - 端点路径含
/api/search//list//page//{id}//query//filter - 响应里曾出现过数据库错误关键字(作为基线参考)
- POST / PUT 端点的 body 全字段(JSON / form 各字段都是候选)
从业务场景(由 page-analysis 输出):
- 电商:搜索 / 商品列表 / 订单查询 / 评论筛选
- SaaS 多租户:跨租户的资源查询(IDOR + SQLi 复合)
- 内容 / 论坛:文章搜索 / 评论列表 / tag 筛选
- 金融:交易记录 / 账户查询 / 流水报表
从身份切换 HAR 对比:
- 普通用户 / 管理员两份 HAR 里仅在管理员端出现的端点是高价值候选(admin 端常复用普通端的查询模板但权限边界更松)
- 跨子系统的同名端点(多个端口 / 子域 / 业务条线)每个独立测,不假设"在 A 测过 B 也安全"
6. 框架 / DBMS 响应指纹
通过响应(错误关键字 / header / 行为)推断后端 DBMS / 框架 / WAF,优化 payload 选择。
DBMS 响应指纹
| 响应特征 | 推断 DBMS | payload 选择 |
|---|---|---|
You have an error in your SQL syntax / MySQL / SQLSTATE |
MySQL / MariaDB | 用 SLEEP() / -- 注释 / 0x hex / /**/ 绕过 |
syntax error at or near / PostgreSQL |
PostgreSQL | 用 pg_sleep() / -- 注释 / ` |
Unclosed quotation mark / ODBC / SQL Server / Microsoft |
MSSQL | 用 WAITFOR DELAY / xp_cmdshell / 堆叠查询 |
ORA-00933 / Oracle |
Oracle | 用 DBMS_PIPE.RECEIVE_MESSAGE 延时 / ` |
SQLite3.OperationalError / near |
SQLite | 限制:无 SLEEP、无堆叠、无 xp_cmdshell |
框架响应指纹
| 响应特征 | 推断框架 | 优化方向 |
|---|---|---|
X-Powered-By: Express / Set-Cookie: connect.sid |
Express (Node.js) | 大概率 Sequelize 或 mysql2,常见拼接形态 |
Set-Cookie: PHPSESSID / X-Powered-By: PHP/... |
PHP | mysqli / PDO 拼接 |
Server: nginx + JSESSIONID cookie |
Java / Spring | JdbcTemplate / MyBatis ${} |
Server: gunicorn / Python traceback |
Django / Flask | .raw() / .extra() |
X-Aspnet-Version |
ASP.NET | ADO.NET / EF Raw |
WAF 响应指纹
| 响应特征 | WAF | 绕过方向 |
|---|---|---|
cf-ray header / Cloudflare 拦截页 |
Cloudflare | 编码绕过 / chunked / 大小写混用 |
request blocked + 阿里云 LOGO |
阿里云 WAF | URL 编码 / 注释截断 |
imunify360 |
Imunify360 | 同上 |
| 自定义 403 + 安全狗签名 | 安全狗 | 同上 |
响应指纹仅作辅助判断——真实的 sink 验证仍需在 §9 闭环要求章节定义的可观测效果证据。
7. 思考检查点
加载本 skill 时按这些问题思考:
- 这个参数在 HAR 里出现在哪个位置?响应里有没有可观察的回显或差异通道?
- 同端点其他参数是否走同一段 SQL 模板?(同模板下其他字段往往是候选)
- 响应里有数据库错误关键字吗?没有的话,能否用时间盲注 / 带外 / 布尔差异作为侧信道?
- 是不是字段名 / 表名 / 排序方向位置(
?sort=/?order_by=)?这类不能参数化,必须看白名单。 - 是不是二阶?这个参数会被入库吗?后续哪些端点会回读它?
- 跨子系统是否有同 pattern_id 端点?(admin 端常复用普通端代码)
8. 检测方法论 / 决策树
全局约束(默认保守预算)
- 单参数最多 20 次请求(含基线 / 重试);同一轮只改 1 个参数
- 时间盲注延时选择能区分网络抖动的最小值(通常 3-5s),抖动大时增加重试次数
- 并发建议 1;抖动大时加重试而非加并发
- 一旦"确认"立即停止进一步探测
参数隔离
- 每轮只改一个参数,便于将响应差异与该参数建立因果关系
- 参数位置按优先级依次排查:Query → Body → Cookie → Header(仅改 1 个位置)
- 基线参数选择不限于"看起来像数据库"的命名——按 sink 语义判定,所有触发线索类别都是候选
Step 0:基线采集
- 发送原始请求 2-3 次,记录"基线响应特征" + "基线耗时分布"
- 标记并忽略动态字段(时间戳 / 随机 ID / sessionToken)
Step 1:闭合 / 上下文探测
参数末尾追加 ' / " / ) / --,观察响应突变 / 报错。不要全打,按 sink 语义选 2-3 个候选试:
- 引号探测:
'或",看是否出现新的语法错误特征或响应突变 - 括号探测:
)/)),观察从"错误→正常"或"正常→错误"的可复现翻转 - 注释候选:MySQL 优先
--/#;其他--/;
输出:候选上下文集合(numeric / string + quote + paren_close + comment)。
Step 2:响应指纹判断
按 §6 响应指纹表识别 DBMS 与框架。结论标注置信度(high / medium / low)。
Step 3:策略决策树
有错误回显且语法错误可触发? → 报错注入(信息量最高)
查询结果直接渲染到响应? → UNION 注入
响应内容差异稳定(TRUE/FALSE)? → 布尔盲注
响应不可区分但耗时可控? → 时间盲注(速度最慢)
无回显无延时? → OOB / 堆叠(仅 MSSQL/PG)
Step 4:确认 + 防误报
- 强证据(满足其一即 confirmed):稳定数据库错误回显且只由 payload 引发;稳定 UNION 回显;带外通道收到明确归属本注入的请求
- 弱证据(需两类同时满足才升 confirmed):布尔盲注 TRUE/FALSE 差异可复现 3 次 + 另一辅助证据;时间盲注统计显著(差异远大于抖动)+ 指纹 / 闭合推断一致
- 拒绝条件:单次报错 / 单次延时 / 响应仅 500 不带可控信息——一律 suspected
Payload 范式与编码绕过
- WAF 关键字过滤 → 大小写混用 / 内联注释
/**// 等价函数(SUBSTRING↔SUBSTR/SLEEP↔BENCHMARK) - 引号过滤 →
0x...十六进制编码 /CHAR(0x...)/ 宽字节%bf%27(GBK / 旧版本 MySQL) - 空格过滤 →
/**//+/%0a/%0b/(...) - 字段名 / 表名拼接(无引号位置)→ 直接构造子查询或 CASE 表达式:
?sort=(case when 1=1 then created_at else id end)
基线检查项(按 §4.3 三态标注)
- 单参数所有候选位置(query / body / header / cookie / path)都过了 Step 0-1
- 同端点其他参数同步分诊
- 字段名 / 排序方向位置单独评估(不可参数化,看白名单)
- 二阶通道:从入库端点追到回读端点
9. 闭环要求(必须遵守)
闭环判定(confirmed / suspected / not_vulnerable)以
common/closure-verification.md为准。下面只列本漏洞特有的可观测信号。
confirmed(必须挂可观测效果证据):
- 布尔盲注:同参数
' AND 1=1 --vs' AND 1=2 --响应可区分,重复 3 次稳定,差异点可指认 - 时间盲注:注入
SLEEP(3)耗时 > 基线 + 2.5s,重复 3 次稳定,且无延时参数耗时回到基线 - 报错注入:响应里出现攻击者可控数据库回显(如
updatexml报错带@@version/database()/ 表名) - UNION 注入:响应里出现 UNION 拼接的列值(版本号 / 当前用户 / 表名 / 业务表数据)
- OOB:带外通道(DNS / HTTP)收到明确归属本注入的请求,含时间戳和唯一 token
suspected(落 status=needs_review):
- 单次报错 / 单次延时(未稳定复现)
- 引号触发响应突变但无法构造稳定二态
- 报错只到"SQL 语法错误"层级,未拿到任何可控回显
not_vulnerable(落 status=not_vulnerable):
- 同模板下所有参数均经参数化绑定(来自 graybox 流程的白盒证据)
- 该端点不接触 SQL(静态资源 / 健康检查)
禁止仅凭"响应 500""执行无异常""绕过了某黑名单"判 confirmed——这些只到 suspected。
反例义务(必须遵守)
why:反例义务属于交付契约——"该子系统无 SQLi"或"已防护"结论是覆盖完整性产物声明,缺失反向验证清单会让下游误信"该维度全站安全"。
写"未发现 SQLi"或"已防护"前,产物必须包含:
- 测过的 SQLi 候选端点完整清单(按 sink 语义枚举,含登录账号 / 搜索 keyword / ORDER BY 字段 / ID 路径参数 / JSON body 各字段 / Header 值 / Cookie 等全部触发线索类别)
- 每端点测过的 payload 类型(引号 / 括号 / OR 永真 / UNION / 布尔盲注 / 时间盲注)
- 每端点的响应证据(基线响应 / payload 响应 / 差异点)
清单不完整 → 结论降级为 partial-coverage 并显式声明未覆盖范围。
10. 具象化反例库
FP(看似命中实际不构成)
反例 1:响应突变源于 WAF 拦截,不是 SQL 报错
- 抽象规则:响应突变 ≠ 注入成功
- 具体场景:附带引号的请求返回 403 / 自定义错误页 / 响应里出现
request blocked/security violation字样 - 关键识别特征:响应 body 含 WAF 关键字(Cloudflare / Sucuri / 阿里云 / 安全狗);响应头
cf-ray/Server: imunify360等可识别 WAF - 排除方法:去掉引号但保留其他扰动(如改大小写),若响应仍突变多半是 WAF;同时尝试合法 SQL 注释
--是否被替换 / 拦截
反例 2:错误 500 不带 SQL 关键字
- 抽象规则:500 ≠ SQLi
- 具体场景:响应 status 500,body 是通用错误页或 JSON
{"error": "internal"},无数据库关键字 - 关键识别特征:响应里无
SQL/query/database/ DBMS 名字 - 排除方法:尝试其他注入位置看是否同样 500(可能是异常处理缺失,不是 SQLi)
反例 3:MySQL LIMIT 后参数化失败的旧版拼接不一定是注入
- 抽象规则:旧版 MySQL JDBC / 旧版 PHP PDO 的
LIMIT ?占位曾不支持,开发者用拼接 - 具体场景:
SELECT ... LIMIT+ pageSize - 关键识别特征:pageSize 通常是整型校验过的;攻击面取决于上游是否做 int 解析
- 排除方法:尝试 pageSize 注入非数字内容看是否被强制转换 + 异常拒绝
FN(看似不命中实际是真洞)
反例 4:字段名拼接(无引号不报错)
- 抽象规则:
ORDER BY/GROUP BY后接的字段名不能用引号包裹,参数化对它无效 - 具体场景:
?sort=created_at→ 后端拼接 ORDER BY;改成?sort=invalid_column报错unknown column - 关键识别特征:
?sort/?order_by参数 + 改成无效字段名报错unknown column - 确认方法:尝试
?sort=(case when 1=1 then created_at else id end)看是否可控字段顺序;或?sort=if(1=1,id,name)
反例 5:二阶注入(入库时不报错,回读时触发)
- 抽象规则:source 入库走参数化不报错,不代表回读拼接安全
- 具体场景:注册时邮箱字段含
',入库正常;后台 admin 页面以邮箱查日志,回读时拼接进新查询 - 关键识别特征:「同一字段在 2 个 SQL 上下文里出现」+「其中至少 1 个用拼接」
- 确认方法:追入库字段被哪些回读端点消费,每个回读点都看是否参数化
反例 6:JSON body 字段被忽略的 SQLi 候选
- 抽象规则:JSON body 字段每个都是候选,不能只测可见 form 字段
- 具体场景:POST
/api/users的 body 含{"sortBy": "id", "filter": "..."},filter 字段被拼到 WHERE - 关键识别特征:HAR 里 POST body 字段未在 query string 里出现,但在响应中影响排序 / 筛选
- 确认方法:JSON body 每个字段单独 fuzzing,参考 Step 1 闭合探测
易混淆案例
反例 7:跨子系统隐式推广(漏报高发模式)
- 抽象规则:在子系统 A 测过 ≠ 子系统 B 也安全
- 具体场景:测了 user-portal 的搜索接口未发现 SQLi,admin-portal 的同名搜索接口未测就推断安全
- 关键识别特征:不同子系统、不同团队、不同时期开发;admin 端常复用普通端代码但实现可能各异
- 确认方法:admin / user / 第三方接入等每个子系统独立做反例义务自检
11. 测试安全边界
破坏性 / 不可逆动作的闭环边界以
common/closure-verification.md《破坏性 / 不可逆动作的闭环边界》节为准。下面只列 SQLi 特有的破坏点。
禁止对真实业务数据执行以下动作来"挂可观测效果":
; DROP TABLE/; TRUNCATE/; DELETE FROM <real_table>(堆叠查询场景)UPDATE ... SET ... WHERE 1=1(无 WHERE 限定的更新)- 任何用
LOAD_FILE读取系统文件后回写、或INTO OUTFILE写入文件系统的动作 - 任何通过
xp_cmdshell/sys_exec/COPY ... FROM PROGRAM触发 OS 命令的动作
允许的非破坏验证手段:
- 哨兵自证:建测试库 / 测试表 / 测试行(前缀
sastx_sentinel_),仅对哨兵执行可破坏操作 - 非破坏差分:仅做 SELECT 类回读 + 时间盲注 + 报错回显
- 带外通道:DNS / HTTP OOB(不修改目标状态)
- CASE 表达式:用
WHERE col = (CASE WHEN ... THEN col ELSE col END)类构造测试逻辑可控性,不改变查询语义
12. 修复建议
源头治理(首选)
- 全部走参数化 / 占位符:
- JDBC / PDO / Go database/sql:
?/$N占位 + 单独传 args - MyBatis:
#{}而非${}(${}仅限明确可信白名单内的字段名 / 表名) - ORM:用表达式对象
where: {col: val}/db.Where("col = ?", val),禁止字符串拼接进 Raw 通道
- JDBC / PDO / Go database/sql:
字段名 / 表名 / ORDER BY 子句
- 白名单:把允许的字段名 / 排序方向 enum 化,请求参数在 enum 内才进 SQL;否则 400 / 用默认值
二阶注入
- 入库即参数化:注册 / 写入时仍用参数化(防一阶)
- 回读必参数化:任何 source 字段被回读到新 SQL 上下文时,按 source 重新做参数化绑定,不复用入库时的"已校验"假设
边界过滤(次选,作为深度防御)
- WAF 规则覆盖常见 payload(UNION / SLEEP / xp_cmdshell 等关键字)——仅作辅助,不替代参数化
- 应用层拒绝单引号 / 特殊字符——易绕过,仅作日志记录与告警触发
兜底拒绝
- 数据库最小权限:业务账号禁止 DDL / 跨库查询 / 文件操作(
FILE权限) - 错误响应不暴露 SQL 语法信息(即使 dev 模式也走统一错误码)