WAF 原理浅析:它到底在“看”什么,又为什么有时候会看错
开发过程中偶尔会遇到请求莫名其妙返回 403,查了应用日志发现请求根本没到业务代码,最后定位是被 WAF 拦截了。本文从开发视角介绍 WAF 的工作原理、部署方式、检测机制,以及为什么它经常“过度保护”,帮助读者更快地排查和规避这类问题。
在日常开发中,偶尔会遇到这样的情况:一个接口在本地和测试环境都正常,上线后却开始返回 403,查应用日志发现根本没有请求记录,最后在运维或安全团队的协助下才定位到是被 WAF 拦截了。
WAF 到底是怎么工作的?它凭什么判断一个请求是恶意还是正常?为什么经常有“误杀”?作为开发,理解它的基本原理之后,写接口和排查问题时都能省不少力。
WAF 长在网络的哪个位置
先看一张图。WAF 通常部署在用户请求到达业务服务器之前的链路上:
这个位置决定了 WAF 的本质:一个串联在请求路径上的检测节点。所有 HTTP/HTTPS 请求都要先经过它,它决定放行还是拦截,然后才到业务应用。
部署方式主要有三种:
反向代理模式。WAF 作为反向代理接收流量,解析请求后转发给后端服务器。这是最常见的云 WAF 部署方式,对业务代码完全透明,域名解析到 WAF 的 IP 就行了。
透明代理/桥接模式。WAF 像网桥一样串在网络链路中,不改变网络拓扑。客户端和服务端都感知不到它的存在,对业务更透明,但部署和维护相对复杂。
旁路监测模式。WAF 通过镜像流量进行分析,发现攻击后通过重置连接或联动防火墙来阻断。这种模式不影响业务链路,但拦截能力取决于联动效率,有一定延迟。
WAF 怎么判断一个请求是不是攻击
这是核心问题。WAF 的检测能力可以理解为三个层次的演进。
第一层:正则规则匹配
这是最经典的方式。WAF 维护一个庞大的规则库,每个规则对应一个攻击特征的正则表达式。
比如 SQL 注入检测,规则里可能包含这样的模式:
(\bSELECT\b.*\bFROM\b)|(\bUNION\b.*\bSELECT\b)|(\bDROP\b.*\bTABLE\b)请求参数里如果匹配到这些模式,就判定为 SQL 注入攻击。
优点是直接、高效、可解释性强。缺点是容易被绕过——攻击者可以把 SELECT 写成 SeLeCt,或者用注释、编码等方式绕过正则匹配。
这也是误报的主要来源之一。比如参数里包含 select * from user 这样的正常业务关键词(比如用户输入的查询语句),正则规则就可能误判。
第二层:语义分析引擎
正则匹配看的是“长得像不像”,语义分析引擎会尝试理解“这个请求到底想干什么”。
以 SQL 注入为例,语义分析引擎会:
- 解析请求参数,还原出完整的 SQL 语句结构
- 判断这个 SQL 语句的语义是否包含攻击意图
- 检查语法树中是否有异常节点
比如 id=1 and 1=1,正则可能会报警,但语义分析会进一步判断:这个条件是否真的改变了原始查询的语义?如果是,再告警。
语义分析能大幅降低误报,但计算开销更大,而且对复杂请求(JSON、XML、文件上传)的解析能力有限。
第三层:机器学习检测
部分 WAF 产品引入了机器学习模型,通过分析请求特征(URL 结构、参数分布、请求频率、User-Agent 等)来判断异常。
这一层主要用于检测:
- 零日漏洞攻击(规则库里没有的特征)
- 业务层攻击(撞库、暴力破解、爬虫)
- 非 HTTP 协议层面的异常行为
机器学习能覆盖正则规则覆盖不到的攻击,但结果不可解释,误报处理也更困难。
实际产品中,这三层通常是叠加使用的:先过规则库,再过语义分析,最后用行为分析补漏。
一个请求在 WAF 内部的检查流程
拿一个标准的 POST 请求举例,WAF 内部大概做了这些事情:
每一步都有细节:
协议解析。检查 HTTP 方法、URI、Header、Body 是否合规,是否有畸形请求(比如超长 URI、异常换行等)。
解码处理。这一步容易被忽视。请求可能经过 URL 编码、Base64、HTML 实体编码、JSON/XML 解析等多层编码,WAF 需要层层解码还原真实内容,再交给规则引擎匹配。如果不做解码,攻击者用两次 URL 编码就能绕过检测。
规则匹配。按优先级匹配规则库,命中后进入语义分析确认。这里的规则通常按攻击类型分组,比如 SQL 注入、XSS、命令注入、文件包含等。WAF 的检测性能很大程度取决于规则库大小和匹配效率。
拦截还是记录。WAF 有不同动作级别:放行、记录、告警、拦截。在规则调优期间通常会先设置为“记录”或“告警”,观察一段时间确认没有严重误报再切换为“拦截”。
为什么 WAF 会有误报
这是开发最容易碰到的问题。一个合法的请求被 WAF 拦了,感觉莫名其妙。为什么会这样?
特征与业务语义冲突。 WAF 规则是基于攻击特征写的,不感知业务逻辑。比如重复的 Query 参数在规则库里被标记为“参数污染”特征,但在某个业务场景下它可能就是合法的批量查询。WAF 不知道业务逻辑,只能基于特征做判断。
规则的严格程度。 安全规则为了不漏报,往往会设计得比较宽松。这是安全产品的通用策略,因为漏报一个真实攻击的后果比误报严重得多。宁可错杀一千,不放过一个。
正常业务的边界行为。 比如请求 URL 超过 1000 个字符、User-Agent 不是常见浏览器而是自定义字符串、Cookie 里包含特殊字符——这些都可能触发 WAF 的“异常请求”规则。对业务来说这些都很正常,但对 WAF 来说它们看起来“不太像正常流量”。
规则更新滞后或过于超前。 新漏洞爆发后 WAF 规则快速上线,可能把使用了同类技术的正常业务也一并拦了。反过来,某些业务的特殊写法如果没有及时更新规则,也可能造成漏拦。
那 WAF 能绕过去吗
坦白说,能。WAF 不是万能的。
常见的绕过手法包括:
- 大小写变形:
SeLeCt绕过仅匹配大写的规则 - 编码绕过:双重 URL 编码、Unicode 编码
- 注释插入:
SELECT/**/1绕过空格检测 - HTTP 参数污染:利用不同解析器的处理差异
- 分块传输绕过:利用 Transfer-Encoding: chunked 绕过检测
这也是为什么 WAF 需要不断更新规则库、引入语义分析和机器学习的原因。单靠静态正则很难应对变化多端的攻击手法。
但对开发来说,理解这些绕过方式的意义在于:不要依赖 WAF 作为唯一的安全防线。WAF 是纵深防御的一层,但不是全部。业务代码本身的安全校验(参数校验、权限校验、SQL 参数化查询)仍然是必须的。
理解了原理之后
再遇到 WAF 相关的拦截问题,排查思路会清晰很多:
先确认 WAF 的部署位置和模式 → 看 WAF 日志确认拦截原因 → 判断是规则太严还是确实有攻击 → 针对性解决。
WAF 本质上是一个用通用规则保护通用 Web 安全的组件,但它不感知业务。开发知道它怎么工作之后,写接口的时候自然会多考虑一步:这个请求从 WAF 眼里看过去,是否“长得像”攻击。这个意识能省掉不少排查时间。