不要对不可信输入进行递归处理 - Trail of Bits博客
Alexis Challande, Brad Swain
2025年2月21日
recursion, vulnerability-disclosure, java
目录
- 递归的危害性
- Protobuf Java案例研究
- 保护你的代码
- 了解更多
单个恶意请求就能使使用递归函数处理不可信用户输入的Web应用程序崩溃。我们开发了一个简单的CodeQL查询来帮助发现堆栈溢出问题,并用它在多个知名Java项目中发现了拒绝服务(DoS)漏洞。所有这些项目都由具有健全开发实践的安全意识组织维护:
- ElasticSearch(在PatternBank、parseGeometryCollection中)
- OpenSearch(在FilterPath、parseGeometryCollection和validatePatternBank中)
- Protocol Buffers CVE-2024-7254
- Guava Function重写
- XStream CVE-2024-47072
我们的发现表明,递归虽然是一种强大的编程工具,但在处理具有可用性要求的应用程序中的不可信数据时,会变成严重的责任。以上所有漏洞都已修复;然而,如果像这样的大规模项目都存在漏洞,你的代码中可能也有类似问题。继续阅读了解我们如何发现这些问题以及如何预防它们,或者查看我们的完整白皮书。
递归的危害性
递归可以是优雅、简单且最重要的实用的。它通常是处理嵌套结构的首选方法,无论是遍历树、访问图中的节点,还是解析像JSON这样的嵌套结构。
|
|
图1:来自Stack Overflow的递归斐波那契函数
然而,如果攻击者控制输入,通常很容易制作一个输入,在达到递归函数的基本情况之前耗尽堆栈。虽然开发人员经常考虑防止无限递归,但通过简单地提供一个触发堆栈溢出的恶意输入,就可能使应用程序崩溃。
|
|
图2:来自Stack Overflow的StackOverflowError
虽然客户端崩溃可能只是不便,但服务器端崩溃可能会使关键服务瘫痪,即使有DDoS保护。在具有可用性要求的应用程序中,这是一个具有潜在实际危害的真实风险。
Protobuf Java案例研究
为了说明这些漏洞在实践中如何表现,让我们看看我们在Google的protocol buffers(Protobuf)库中发现CVE-2024-7254的过程。这个问题展示了即使是安全意识强的组织也可能忽略递归处理漏洞。
根据Protobuf的官方文档:
Protocol buffers是Google的语言中立、平台中立、可扩展的序列化结构化数据机制——类似于XML,但更小、更快、更简单。(来源)
解析不可信数据是出了名的棘手,安全研究人员已经针对每种格式的解析器进行了研究。Google开发了protocol buffers,以提供一种序列化交换格式,并在各种语言中自动生成解析器。它们在Google内部和更大的生态系统中被广泛使用。
然而,它们也容易受到递归错误攻击。
例如,攻击者可以通过简单地发送这条消息,使使用protobuf-lite库解析外部消息的Java应用程序崩溃:
|
|
图3:Protobuf中的恶意消息
这条消息将抛出StackOverflowError。问题在于Protobuf如何解析未知字段。根据Protobuf文档:
未知字段是格式良好的protocol buffer序列化数据,表示解析器无法识别的字段。例如,当旧二进制文件解析具有新字段的新二进制文件发送的数据时,这些新字段在旧二进制文件中成为未知字段。
当这个问题与Groups(一个已弃用但仍因向后兼容性而被解析的功能)结合时,你会得到一个爆炸性的组合:
- 一个组可以包含另一个组
- 如果被攻击的模式不包含组,则新组被解析为未知字段
- 未知组可以包含另一个组
- 转到第2步
以下是负责解析的代码摘录:
|
|
图4:Protobuf中的mergeFrom函数
这个漏洞的有趣之处在于,它对被攻击目标有一个前提条件:必须使用Protocol Buffer库的Java lite版本。对目标应用程序使用的模式没有要求。
虽然C++ API的官方文档出于安全原因建议丢弃未知字段,但它建议在解析消息后执行此操作。此时已经太晚了。
虽然Protobuf解析通常对递归攻击具有弹性(使用深度计数器),但Google在开发过程中忘记了这个代码路径。我们负责任地向Google披露了这个问题,并被分配了CVE-2024-7254。
在调查这个问题时,我们发现它也适用于其他Protobuf实现,包括Rust-protobuf,这是一个用Rust编写的非官方Protocol buffers实现。
保护你的代码
随着软件系统越来越需要处理JSON、XML和Protocol Buffers等嵌套数据格式,递归处理带来的风险也在增长。我们最初的研究主要集中在Java项目上,但对不可信输入进行递归的基本模式超越了语言界限,表明存在系统性的安全风险。
以下是保护应用程序的两个具体步骤:
审计你的代码。识别处理不可信数据的递归函数,并查找对嵌套数据格式的解析操作。特别注意处理反序列化的库代码。像我们的CodeQL查询这样的静态分析工具可以帮助简化审计过程。
实施安全措施。考虑迭代替代方案,为递归操作添加明确的深度限制,并在可能的情况下在处理前验证输入大小和嵌套深度。
以下是添加深度计数器以防止恶意递归的示例:
|
|
图5:添加深度计数器的斐波那契函数
了解更多
要深入了解我们的发现:
- 阅读我们的白皮书《输入驱动递归:持续的安全风险》
- 查看我们于2025年2月22日在华盛顿特区首届DistrictCon上的演讲
- 试用我们用于帮助发现有问题递归的CodeQL查询
如果你喜欢这篇文章,请分享: Twitter、LinkedIn、GitHub、Mastodon、Hacker News
目录
- 递归的危害性
- Protobuf Java案例研究
- 保护你的代码
- 了解更多
最近文章
- 我们构建了MCP一直需要的安全层
- 利用废弃硬件中的零日漏洞
- 深入EthCC[8]:成为智能合约审计员
- 使用Vendetect大规模检测代码复制
- 构建安全消息传递很困难:对Bitchat安全辩论的细致看法
© 2025 Trail of Bits. 使用Hugo和Mainroad主题生成。