py-libp2p是libp2p的Python实现,后者是支撑IPFS、Filecoin和以太坊类节点的点对点网络协议栈。我在为其WebRTC-Direct传输层工作时,发现并修复了一个内存放大型拒绝服务(DoS)漏洞——问题出在一个信任调用方的SDP端点上。
在加密传输建立之前,通信双方需要交换SDP offer/answer数据块。py-libp2p为此提供了一个极简的开发测试工具:一个手写的HTTP服务器(不依赖aiohttp),通过POST /sdp接收SDP offer,并将请求体交给offer处理器。漏洞就藏在这个工具的请求读取逻辑里。
问题出在哪
修复前的_aiortc_helpers.py中,处理器直接读取调用方提供的Content-Length,并精确缓冲那么多字节——没有任何上限。攻击者可以完全控制请求体大小,而readexactly()会绕过StreamReader默认的64 KiB流量控制限制,导致内存无节制增长。
更糟的是,body.decode()会创建第二份拷贝,处理器中的字符串操作又产生第三份。网络上的字节最终在内存中被放大约2倍,且没有天花板。此外,头部读取循环只设置了单行超时,攻击者可以无限发送头部行,循环永远不会计数。
这是握手前的未认证输入。任何能访问该端口的人都能触发。
发现过程
这个漏洞是在我自己的WebRTC PR(#1309)审查中被标记出来的。审查者留下一行注释:“将整个请求体读入内存且无上限,存在可用性风险。”这看起来像是个风格问题,但实际并非如此。握手前的输入本质上就是攻击者可控的,“无上限读取整个请求体”就是完整的攻击路径。
实测数据
我构建了一个复现工具,导入修复前(9506041)和修复后(759c75b)的真实run_signaling_server,发送恶意请求,并以20毫秒间隔采样RSS内存。在Python 3.11/aiortc 1.15沙箱中测得,内存放大比约为2倍。修复后的版本对请求体大小施加了上限,攻击者的放大效果被有效阻断。
这个案例说明,开发工具中的“临时”代码往往比正式代码更容易被忽视,而恰恰是这些代码可能成为攻击面。安全审查中看似不起眼的注释,有时指向的是真正的漏洞。
热门跟贴