本文仅用于 CTF、靶场、本机实验和明确授权环境。

WAF 题最容易陷入“随机换大小写、随机套编码”的循环。有效的分析方法不是收集最多的变形,而是回答:谁在检查输入、检查发生在哪次解码前后、过滤之后 MySQL 最终看到了什么。

1. 先判断拦截发生在哪一层

输入通常经过:

1
2
3
4
5
6
7
浏览器或代理编码
→ Web 服务器解析
→ WAF 检查
→ 框架解码
→ 应用过滤或类型转换
→ 数据库驱动
→ MySQL 解析

不同现象提供不同线索:

现象 可能位置
固定 403 页面 WAF 或 Web Server
跳转到统一错误页 应用或安全中间件
MySQL 语法错误 输入大概率到达数据库
关键词静默消失 应用删除型过滤器
数字前缀保留 类型转换或截断

2. 常见过滤器类型

2.1 拒绝型

1
2
3
if (preg_match('/union/i', $input)) {
die('blocked');
}

2.2 删除型

1
$input = str_ireplace('union', '', $input);

2.3 转义型

1
$input = addslashes($input);

2.4 类型转换型

1
$input = intval($input);

2.5 白名单型

1
2
3
if (!ctype_digit($input)) {
die('invalid');
}

不同过滤器不能套同一种绕过方式。

3. 空格被过滤

可能的 token 分隔形式:

1
2
3
4
5
SELECT/**/1
SELECT
1
SELECT 1
SELECT(1)

但它们不是任意位置都能互换。

分析问题:

1
2
3
4
5
普通空格被删除还是请求被拒绝?
TAB、换行会不会被服务器规范化?
块注释会不会被 WAF 删除?
删除注释后两个 token 是否粘连?
当前位置能否用括号建立语法边界?

4. 单引号被过滤

先确认输入是否真的需要字符串字面量。

数字上下文:

1
WHERE id = 1

可能完全不需要引号。

字符串构造候选:

1
2
3
0x61646d696e
CHAR(97,100,109,105,110)
UNHEX('61646d696e')

这些都表示或构造 admin,但存在限制:

1
2
3
4
CHAR 需要逗号
UNHEX 本身仍需要字符串参数
十六进制值可能被当作数字或二进制串
非 ASCII 内容受字符集影响

双引号不一定永远能替代单引号。启用 ANSI_QUOTES 后,双引号用于标识符。

5. 逗号被过滤

5.1 SUBSTRING

1
SUBSTRING(value FROM 1 FOR 1)

替代:

1
SUBSTRING(value,1,1)

5.2 LIMIT

1
LIMIT 1 OFFSET 2

替代:

1
LIMIT 2,1

注意参数次序:LIMIT offset,count 对应 LIMIT count OFFSET offset

5.3 多列 UNION

多列列表天然需要分隔。此时可能需要:

1
2
3
4
寻找单列查询
改用布尔或时间盲注
使用错误通道返回单表达式
从源码得到目标表结构,减少枚举

没有统一的“逗号万能替代”。

6. 注释被过滤

MySQL 常见注释:

1
2
3
# line comment
-- line comment
/* block comment */

-- 后必须跟空白或控制字符。

如果注释全部不可用,应重新画出:

1
固定前缀 + 用户输入 + 固定后缀

通过补齐引号、括号和表达式,让固定后缀重新成为合法 SQL。这没有脱离上下文的万能模板。

7. 等号被过滤

候选表达式:

1
2
3
4
value LIKE 'admin'
value IN ('admin')
value BETWEEN 'admin' AND 'admin'
NOT STRCMP(value,'admin')

影响因素:

1
2
3
4
NULL
字符串和数字隐式转换
大小写和 collation
LIKE 通配符

8. 大于号和小于号被过滤

根据所需逻辑考虑:

1
2
3
4
value BETWEEN low AND high
STRCMP(a,b)
GREATEST(a,b)
LEAST(a,b)

先写清楚原条件想表达什么,再判断候选是否等价。

9. AND、OR、NOT 被过滤

MySQL 支持符号形式:

1
2
3
&&
||
!

|| 受 SQL mode 影响:默认通常作为逻辑 OR,启用 PIPES_AS_CONCAT 后会作为字符串拼接。使用前必须验证。

10. 关键词大小写

1
2
SeLeCt
UnIoN

只对大小写敏感且没有标准化输入的错误过滤器可能有效。成熟 WAF 通常会归一化大小写或直接分析 token。

11. 双写为什么有时有效

假设过滤器只删除一次:

1
$input = str_ireplace('union', '', $input);

嵌套字符串在删除中间部分后,可能重新拼出关键词。

双写成立需要:

1
2
3
4
过滤器执行删除而不是拒绝
只进行一轮扫描
删除后不再重新检查
拼接结果仍是合法 SQL

循环替换、token 解析或正规 WAF 下通常无效。

12. URL 编码与双编码

不要背“编码一次还是两次”,要画解码链:

1
2
3
4
5
6
原始请求
→ WAF 是否先解码
→ Web Server 解码
→ 框架参数解码
→ 应用是否手动 urldecode
→ MySQL 最终输入

复盘示例:

1
2
3
4
5
原始输入:%2527
第一次解码:%27
第二次解码:'
WAF 检查时看到:%27
应用最终得到:'

只有明确解码顺序,双编码才有解释力。

13. MySQL 条件注释

1
2
/*! SELECT 1 */
/*!80000 SELECT 1 */

MySQL 可能执行注释中的内容;版本数字表示满足最低版本时才执行。这能否通过 WAF,取决于过滤器是否理解 MySQL 方言。

14. 函数被过滤

不要只问“还有什么函数”,先问“需要什么能力”:

1
2
3
4
5
6
截取       → SUBSTR、MID、LEFT、RIGHT、LIKE、REGEXP
长度 → LENGTH、CHAR_LENGTH、OCTET_LENGTH、BIT_LENGTH
字符转数字 → ASCII、ORD、HEX、直接比较
条件 → IF、CASE WHEN、直接逻辑表达式
多行合并 → GROUP_CONCAT、LIMIT 逐行
时间反馈 → 优先换布尔或错误通道

15. WAF 复盘模板

1
2
3
4
5
6
7
8
原始请求:
HTTP 层解码后:
WAF 检查时:
应用过滤后:
驱动发送给 MySQL:
MySQL 最终解析:
页面反馈:
成功或失败原因:

16. 防御

黑名单不能替代参数化查询。数据值使用参数绑定;标识符使用固定映射;统一编码与解码流程;减少数据库权限;在 WAF 之外修复应用根因。

下一篇进入容易漏测的漏洞点:排序、搜索、登录、JSON、Cookie、请求头、ORM、二次注入和堆叠查询。

参考资料