首页
社区
课程
招聘
[原创]死里逃生与降维打击:从 PHP 致命 0day 聊聊我自研的 Go 版 WAF(盾甲)底层机制
发表于: 2026-10-23 15:14 1654

[原创]死里逃生与降维打击:从 PHP 致命 0day 聊聊我自研的 Go 版 WAF(盾甲)底层机制

2026-10-23 15:14
1654

一、 引子:一次险些让后端原地爆炸的攻击
最近 PHP 爆出了两个 CVSS 高达 9.2 的未授权 RCE 漏洞(CVE-2026-109618 / 109619)。其利用手法非常刁钻:不注入任何恶意代码,而是通过构造畸形的请求体,触发后端的内存溢出(OOM)。利用 PHP 崩溃时清理不干净的“脏内存”(UAF),从而执行任意指令。

很多传统的正则 WAF 在这类攻击面前直接失明,因为它的请求语法是合法的。

作为一个干了 10 年渗透的老兵,我今天不想去复现那个洞,我想聊聊防御。聊聊我最近为了对抗这类高级 0day,用 Go 语言从零手搓的一套独立反向代理 WAF——盾甲(ShieldWAF)的底层实现,以及在这个过程中踩过的“引擎自爆”的坑。

二、 架构的降维:为什么放弃了进程内防护?
早期的 WAF 喜欢写成 PHP 扩展或 waf.php 文件,和业务代码共进程。这在面对内存炸弹时是致命的:WAF 解析请求需要消耗内存,业务处理也需要。一旦黑客发个大包过来,WAF 先把 PHP 的内存撑爆了,业务直接跟着 502。

我的解法:物理隔离的独立反向代理。
客户端 -> Nginx -> 盾甲 WAF (Go, 独立进程) -> PHP-FPM

我选择 Go,是因为它的协程在处理高并发 HTTP 流时极其高效,且能精准控制底层 Content-Length 的读取。在这层网关上,我做了极其暴力的硬限流:

解压限流:针对压缩炸弹,强制限制 32MB 上限,且解压比不得超过 100:1。

解析超时熔断:无论多复杂的请求,解析超过 300ms 直接强制断开连接。

这意味着,黑客用来打后端的内存炸弹,在到达 PHP 解析器之前,就已经被 Go 网关当成垃圾丢弃了。

三、 降维打击:把 0day 扒光在 AST 语法树下
防住了底层 OOM,那面对复杂的逻辑绕过和注入呢?这就得请出盾甲的核心:34 个 AST(抽象语法树)语义分析器。

看雪的老哥都知道,绕过正则太简单了。UN//ION SEL//ECT,或者各种 Unicode 嵌套。我的做法是,无论多脏的输入,先上 14 层归一化解码流水线,把伪装全部撕掉,还原成最原始的代码,然后再扔给 AST 引擎。

前两天,我的系统抓到了一段很有意思的样本,是针对 Next.js(React Server Components)的新型反序列化/原型链污染 RCE:

原始 Payload(节选):

------WebKitFormBoundaryx8jO2oVc6SWP3Sad\r\nContent-Disposition: form-data; name="0"\r\n\r\n{\"_response\":{\"_chunks\":\"$Q2\",\"_formData\":{\"get\":\"$1:constructor:constructor\"},\"_prefix\":\"var res=process.mainModule.require('child_process').execSync('echo $((4291*7494))').toString().trim();;throw Object.assign(new Error('NEXT_REDIRECT'),{digest: NEXT_REDIRECT;push;/login?a=${res};307;});\"},\"reason\":-1,\"status\":\"resolved_model\",\"then\":\"$1:__proto__:then\",\"value\":\"{\\\"then\\\":\\\"$B1337\\\"}\"}\r\n------WebKitFormBou
这堆东西,正则引擎看了一定会流泪。但经过盾甲的归一化和语料合并后,AST 引擎瞬间锁定了三个致命语法节点:process.mainModule -> require('child_process') -> execSync。直接宣判死刑,分数拉满(100分),连人带 /16 网段一块拉黑。

四、 血泪踩坑:解析器自己的 OOM 危机
在看雪,我必须得分享一个写词法/语法解析器最容易踩的坑:死循环导致的内存暴涨。

我在早期开发盾甲的 JNDI 词法分析器时,为了让解析更严谨,写了一个基于状态机的词法分析。结果遇到几个特殊字符(类似 : 或 @)时,代码没写 pos++。

黑客发了个探测 JNDI(Log4j)的畸形 Payload 过来。短短 3.3 秒内,解析器原地死循环,盾甲的单个进程内存直接从几十兆飙升到了 1085MB,直接把测试机的内存吃光,系统触发 OOM Killer。

你用来防黑客的工具,差点成了黑客打你的跳板。

从那以后,我在 Go 引擎里加入了极其严苛的协程超时控制:
// 伪代码:防止单个分析器卡死整个引擎 resultChan := make(chan Result, 1) go func() { resultChan <- analyzer.Run(input) }() select { case res := <-resultChan: return res case <-time.After(300 * time.Millisecond): return Result{Error: "timeout"} // 强制熔断 }
每个 AST 分析器都在独立的协程里跑,300ms 算不出来直接杀断。这不仅保证了引擎的稳定,也让黑客的 ReDoS 攻击(正则拒绝服务)成了笑话。

五、 从规则到证据:YSL-1 理论的探索
在拦截了大量真实流量后,我发现了一个痛点:多引擎打架与归因错乱。
有时候,api_security 模块明明检测到了针对敏感文件的扫描(85分),但 language 模块可能会因为词法误判,给出一个 NoSQL 的错误标签。虽然最终都会拦截,但事后溯源时,这会给安全运营带来极大困扰。

这也是我最近在写论文探索的 YSL-1(语枢) 理论:把每一个模块的结论,不看作简单的数字分数,而是看作一种“证据”(无证据、可疑、危险),通过代数层面的证据合成,输出带有可复核链条的结论。

六、 总结
在这个流量为王的公网环境里,一个人守住一个高价值站点,每天看着雷达大屏上来自全球各地的探针被绞杀,是一件很有成就感的事。

防护的本质,从来不是堆砌正则,而是深刻理解攻击者的底层逻辑,然后在系统架构的物理层和语义层,构筑起降维打击的壁垒。

这套 Go 版本的盾甲 WAF 目前已经稳定运行,后期我会继续在看雪分享关于 34 个 AST 分析器的具体实现,以及防篡改、文件 DNA 监控的底层设计。欢迎各位逆向、安全大佬拍砖交流。


[招生]科锐逆向工程师培训(2026年11月13日实地,远程教学同时开班, 第57期)

收藏
点赞 ・0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回