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