模糊测试PHP的unserialize函数
最近,PHP开发团队决定不再将unserialize函数实现中的bug视为安全问题。在这篇文章中,我想阐述为什么我认为这是一个糟糕的决定,并提供一个简单的模糊测试/持续质量保证设置,因为该函数似乎是无底洞般的bug来源,这似乎是此举的部分动机。
背景与争议
改变分类的论点有两个方面。首先,他们声称unserialize本质上是一个用于处理不可信数据的不安全函数。实质上,他们说的是将unserialize实现中的bug视为安全相关问题没有意义,因为应用程序将不可信数据传递给该函数的事实意味着该应用程序的安全性已经受到威胁。其次,他们认为将unserialize bug视为安全问题会鼓励开发者在不可信数据上使用它。
在我看来,这是一个相当薄弱的观点。文档中明确记载unserialize是不安全的,如果这个警告没有传达信息,那么我非常怀疑PHP bug跟踪器中的分类变化会产生影响。
让我们更详细地讨论第一点,因为现实情况更加微妙。通常,在不攻击unserialize实现本身的情况下,利用不可信数据的反序列化相对容易。但这有一些要求。特别是,攻击者必须能够在目标应用程序或其使用的库中找到包含PHP魔术方法的类,例如__destruct函数。此外,如果向unserialize提供了allowed_classes参数,则包含魔术方法的类必须包含在此列表中。这些条件并不总是成立。如果应用程序是闭源的,攻击者将无法直接找出此类类的名称,如果allowed_classes是空列表,那么他们将无法使用它们。
实际案例
上述并非仅仅是假设场景。这篇相当不错的文章记录了PornHub中一个漏洞的利用,该漏洞要求攻击者针对unserialize实现。如文章中所述,应用程序是闭源的事实阻止了他们发现可用于对象注入的类。
我认为大多数人会同意PHP开发团队的观点,即如果应用程序包含对不可信数据的unserialize调用,那么这是一个坏主意,应该修复。然而,将unserialize bug视为普通bug也会不必要地使PHP应用程序开发者及其应用程序用户面临进一步风险。
技术实现
PHP开发团队被unserialize bug淹没的一个原因似乎是,他们在进行更改后要么根本不模糊测试该函数,要么他们的模糊测试机制无效。我完全认识到unserialize实现的功能很复杂,添加或修改现有代码总是伴随着引入bug的风险。然而,我也相信,只需少量努力,就应该能够显著减少发布版本中低悬果实bug的数量。
无论如何,为了践行我的主张,我上传了一个脚本和辅助文件的存储库,用于构建PHP并通过AFL模糊测试unserialize。没有什么秘密配方。如果有的话,那就是重点;对现有unserialize实现及其更改的持续健全性检查可以轻松自动化。
脚本说明
这些脚本很简单,但有一些值得注意的地方:
-
驱动程序PHP脚本从文件加载表示要反序列化的数据的字符串,并将其直接传递给unserialize。这应该足以找到我之前在unserialize中修复的相当一部分bug,例如类似这样的问题。但是,如果需要一些额外的PHP代码来触发bug,则不会找到它。例如类似这样的问题。通过借鉴该主题的其他博客文章中的想法,相对容易扩展模糊测试设置,例如上述提到编写PornHub漏洞利用的人在这篇文章中有一些好主意。
-
可以使用AFL的检测功能构建整个PHP,或者仅构建认为与unserialize功能相关的组件。前者的优点是保证AFL能够智能地探索通过unserialize可能触发的所有功能,缺点是速度较慢。build.sh脚本当前使用AFL的检测功能编译所有内容。如果需要更快的二进制文件,可以编译没有AFL/ASAN的PHP,删除与unserialize相关的目标文件,重新配置构建以使用AFL/ASAN,然后重新编译这些目标文件。我过去使用过这种方法,效果很好,但需要确保重新编译所有与unserialize可能触发的代码相关的目标文件,否则AFL将无法在那里跟踪覆盖率。
-
可以向AFL提供一个令牌字典,供其在模糊测试时使用。存储库中的字典包含从unserialize解析器中提取的令牌,我认为它相当完整,但如果我遗漏了任何内容,请告诉我,我一定会包含它。
-
为AFL提供的种子文件对其发现bug的能力以及发现特定bug的速度有显著影响。我提供的种子文件是从我在PHP文档中找到的有效unserialize用法示例和先前漏洞的触发器中构建的。如果您找到有用的种子文件,请随时告诉我。
结语
我通过从我的模糊测试机器中提取文件和配置创建了这个存储库,同时撰写了这篇博客文章。目标是忠实反映我过去用于发现大量unserialize bug的设置。然而,我可能忘记了一些东西,或者在整理发布过程中破坏了一些东西。在我撰写本文时,我已经启动了fuzz.sh脚本来确保一切正常,但可能我搞砸了什么而不会注意到。如果确实如此,请告诉我!如果您想确认一切功能正常,请编译PHP版本5.6.11,您应该在大约4000万次执行内找到这个bug。
祝狩猎愉快!