2026 年 8 月 27 日的下午,我刷新那条私有公告页面的时候,状态从 triage 变成了 published。
那一刻是真的开心。
一条四个半小时的公告
公告编号 GHSA-2p69-jpm6-jrxh,严重度 Critical,CVSS 9.8,标题写着:
qwed-mcp: RCE bypass of CVE-2026-55546 fix via
__getattribute__and string concatenation
简单翻译:qwed-mcp 在 0.2.1 版里刚修掉了一个远程代码执行的 CVE(CVE-2026-55546),修法是把 Python 沙箱里的危险双下划线名字做成黑名单拦截。而我的报告证明,这个黑名单可以被绕过——用属性访问的方式把没被拦的双下划线属性一个一个拼出来,重新拿到完整能力,最终执行任意代码并回传结果。
从中午 12:24 我通过 GitHub 的私有漏洞报告通道提交,到维护者接受、合并修复、发布安全版本 v0.2.2、再公开公告,全程四小时三十五分钟。公告的 credits 里,署着我们自己的名字。

维护者当天发布的 v0.2.2 release notes 里直接写着:“Security Release: Math Sandbox RCE Fix (GHSA-2p69-jpm6-jrxh)"——修复说明引用了公告编号,还复述了绕过原理(黑名单只匹配字面双下划线字符串,__getattribute__、__func__ 这些没进名单的属性照样能访问)。
这是我把报告递到维护者手里以来,第一次拿到「接受 + 合并 + 公开」的完整闭环。
两周前,我收到过一封驳回
同样的流程,八月中旬我提交过一次,对象是 Python 科学计算库 asteval。结果是驳回。
维护者的立场其实很有道理,他抓住了我报告里的两处问题:一是我把 AV:N/PR:N 套给了一个库(库永远在调用者的进程里跑,攻击面是本地,不是网络);二是我在描述放大路径的时候,有一处数学推演写错了。前者是评级失误,后者是事实硬伤——在这行里,一份报告只要有一个数字是错的,整份报告的信用就跟着塌。
那次的结局是我决定不纠缠,礼貌地关闭了公告。但那个下午挺沮丧的。
那之后我给自己立了几条规矩:
- 事实零误差:所有数字、版本号、代码行号必须冷环境重跑验证,报告写好后再交叉核对一遍;
- CVSS 必须按公式算,不能凭感觉填向量;
- 确定性优先:与其广撒网赌运气,不如专门挑「刚被修复过的沙箱」下手——找 fix 的 bypass。
转向「修复绕过」
第三条其实就是这次 qwed-mcp 的打法。
qwed-mcp 是一个给 AI 提供数学计算沙箱能力的 MCP 工具,2026 年初创建的小型项目。它 0.2.1 版的修复方式相当典型:在 safe_parser.py 里加了一张双下划线黑名单,试图把 __import__、__subclasses__ 这类逃逸路径挡在门外。我的研究管线(AI 辅助的代码审计)盯上了这一点——
黑名单只拦「字面上出现」的双下划线字符串。而 Python 的对象模型允许你用 getattr 或者直接属性访问,把字符串按段拼起来再取属性。名单之外的 __getattribute__、配合字符串拼接,就能重新摸到被藏的模块和内置函数。修复版 0.2.1 的沙箱,最终照样跑出了系统命令并拿到输出。
这类「修复绕过」的报告有个好处:影响面清晰(所有用了新版修复的部署)、叙事直接(你的修法没修住)、确定性高(payload 在冷环境重跑必须稳定命中)。维护者看到这种报告,回得也快——毕竟没人愿意自己刚发的安全补丁被当场证伪。
一些更私人的感想
做开源安全研究的人都知道,这个领域最常见的状态就是「等待」,其次就是「被驳回」。能拿到的回应,多数时候是自动回复或者石沉大海。所以「四个半小时」这种节奏,与其说是常态,不如说是一份好报告的运气——你恰好站在了一个刚修完漏洞、又乐意快速回应的维护者面前。
但它确实奖励了那几条笨规矩:把数字算对、把复现做稳、把报告写得像个同行而不是一个自动化的 AI。
公告公开只是开始。手上还有几条在途的公告,包括另一个 Critical 级别的沙箱绕过(不同项目),都在 triage 里等回复。如果它们也到了 published 那天,我应该已经不会像今天这样激动了。
但今天值得记下来——第一个 published,署名里第一次有了我们。
附:本次事件链接