连续模糊测试是一种自动化软件安全测试技术,通过向程序输入随机或变异的数据并监控其异常来发现漏洞。尽管像OSS-Fuzz这样的项目已帮助发现了数千个开源软件漏洞,但它并非万能。本文将通过三个已长期接入OSS-Fuzz但依然存留高危漏洞的开源项目案例,揭示模糊测试的局限性,并提出一套进阶的、包含五个步骤的工作流程来挖掘更隐蔽的安全问题。
模糊测试的“漏网之鱼”:三个案例分析
1. GStreamer:覆盖率过低
GStreamer是GNOME桌面环境默认的多媒体框架,已持续模糊测试七年。然而,在2024年12月,仍发现了29个新漏洞,包括多个高危问题。根本原因在于其OSS-Fuzz统计显示仅有2个活跃的模糊器,代码覆盖率约为19%。相比之下,OpenSSL有139个模糊器,bzip2库的覆盖率则高达93.03%。这暴露了OSS-Fuzz的一个关键问题:它需要人工监督来监控覆盖率并为未覆盖的代码编写新的模糊器。
2. Poppler:未测试的外部依赖
Poppler是Ubuntu默认的PDF解析库,在OSS-Fuzz中拥有16个模糊器和约60%的代码覆盖率,表现尚可。然而,一个关键的远程代码执行漏洞未被发现,原因是漏洞存在于一个未被OSS-Fuzz构建包含的默认依赖库(DjVuLibre)中。这个支持DjVu文档格式的库虽然默认安装在数百万系统上,却处于“未模糊测试”状态。这表明软件的安全性取决于其依赖链中最薄弱的一环。
3. Exiv2:被忽视的编码逻辑
Exiv2是一个用于读写图像元数据的C++库,自2021年接入OSS-Fuzz后已发现多个漏洞。但三年后,仍有新漏洞被报告。原因在于研究人员通常聚焦于解码功能(这是最明显的攻击面),而编码逻辑收到的关注较少。编码漏洞可能在文件转换、缩略图生成等后台工作流中被触发,同样具有危险性。
五步模糊测试进阶工作流程
为了提高模糊测试的有效性,发现更隐蔽的漏洞,建议遵循以下五个步骤的迭代工作流程:
步骤一:代码准备
优化目标代码以提高模糊测试效率,包括:
- 移除校验和
- 减少随机性
- 去除不必要的延迟
- 处理信号
步骤二:提高代码覆盖率
这是基础但关键的一步。目标是将代码覆盖率提升至90%以上。这需要:
- 迭代过程:运行模糊器 > 检查覆盖率报告 > 改进覆盖率 > 再次运行。
- 编写新的测试套件以覆盖无法触及的代码。
- 使用高级技术,如:
- 故障注入:模拟内存分配失败、文件缺失等异常条件。
- 快照模糊测试:对程序状态进行快照并恢复,适用于有状态程序。
步骤三:提高上下文敏感的覆盖率
传统的“边覆盖率”不跟踪代码块的执行顺序。在涉及全局状态或插件顺序的场景中,这可能漏掉许多路径。
- 上下文敏感分支覆盖:结合调用栈信息来记录覆盖情况。
- N-Gram分支覆盖:结合当前及之前N个代码位置来记录。 对于上下文敏感覆盖,达到60%以上即可视为很好的结果。
步骤四:提高值覆盖率
即使达到100%的代码覆盖率,也可能错过某些漏洞(例如除零错误)。值覆盖率关注变量取值范围的覆盖。
- 策略:对可由输入直接控制且涉及关键操作的“战略性”变量进行值覆盖。
- 实现方法:可以通过“分桶”策略将大量可能的值分组,并重点关注意义更大的取值区间。 当前工具(如AFL++ CmpLog)主要追踪比较操作的值,对此类漏洞支持有限,可能需要自定义实现。
步骤五:分类与处理
分析模糊测试产生的崩溃和超时报告,从中筛选出真正的安全漏洞。
模糊测试的终极挑战:几乎无法检测的漏洞
即使采用上述所有先进方法,两类漏洞仍然极难通过模糊测试发现:
- 需要超大输入的漏洞:例如,触发某些漏洞需要GB级别的输入文件。模糊测试出于效率考虑通常会限制输入大小,且大输入会使得搜索空间呈指数级增长。
- 需要“额外时间”触发的漏洞:例如,一个需要12小时运行才能触发的引用计数溢出漏洞。模糊测试的单次执行超时时间通常在毫秒级,根本无法触及此类问题。
对于这些“最后一英里”的漏洞,目前更有效的发现手段是静态分析、符号执行或传统的人工代码审计。
结论
模糊测试是发现复杂软件漏洞的强大工具,但它不是“一劳永逸”的解决方案。持续的安全测试需要人工的监督、经验的判断以及不断深化的技术流程。通过采用涵盖代码覆盖、上下文覆盖和值覆盖的五步工作流程,安全研究人员和开发者可以显著提升发现隐蔽漏洞的能力,弥补传统模糊测试的盲区。