从内核到令牌:Trail of Bits漏洞挖掘之旅
我们正在梳理Trail of Bits多年来报告的漏洞档案。本文分享两个重要问题:隐藏在JSON Web Tokens(JWTs)中的拒绝服务(DoS)漏洞,以及Linux内核中可能绕过关键内核安全机制(KASLR)的疏忽。
解析JOSE库中的DoS漏洞
JWT和JSON对象签名与编码(JOSE)是描述加密和/或签名JSON令牌创建与使用的广泛标准。虽然这些标准被广泛使用且代表了身份声明解决方案的重大改进,但它们并非没有缺陷,并存在几个众所周知的陷阱,如JWT"none"签名算法。
我们的发现涉及Tom Tervoort在BlackHat USA 2023上提出的新JWT攻击系列中的一种:“Three New Attacks Against JSON Web Tokens”。其中"十亿哈希攻击"由于JWT密钥加密缺乏验证而导致拒绝服务,引起了我们同事Matt Schwager的注意。进一步检查后,他发现该问题还影响Go和Rust生态系统中的多个库:go-jose、jose2go、square/go-jose和josekit-rs。
这些库都支持使用PBES2进行密钥加密,该功能旨在允许在JSON Web加密(JWE)中基于密码对内容加密密钥(CEK)进行加密。首先通过PBES2方案从密码派生密钥,该方案执行一定次数的PBKDF2迭代。然后使用该密钥加密和解密令牌内容。
这通常不是问题,但不幸的是,迭代次数作为令牌的一部分包含在p2c头部参数中,攻击者可以轻松操纵。例如,考虑下面显示的令牌头:
图1:显示使用大量迭代进行PBES2密钥加密的JWE令牌头
通过在p2c字段中使用非常大的迭代计数,攻击者可以对任何尝试处理此令牌的应用程序造成DoS。接收并尝试验证此令牌的任何人都需要首先执行2,147,483,647次PBKDF2迭代来派生CEK,然后才能验证令牌是否有效,这将消耗大量的计算时间。
我们向go-jose、jose2go和josekit-rs库维护者报告了此问题,并通过限制p2c的最大值进行了修复:go-jose/go-jose版本3.0.1(提交65351c27657d);dvsekhvalnov/jose2go版本1.6.0(提交a4584e9dd712和8e9e0d1c6b39);以及hidekatsu-izuno/josekit-rs版本0.8.5(提交1f3278a33f0e、8b60bd0ea8ce和7e448ce66c1c)。square/go-jose仍未修复,因为该库已弃用,鼓励用户迁移到go-jose/go-jose。
或者,也可以通过不纯粹依赖令牌的alg参数来减轻风险。毕竟,如果您的应用程序不期望接收使用PBES2或任何较少使用算法的令牌,就没有理由尝试处理一个。jose2go现在允许实现对alg和enc参数的选择加入更严格验证,而go-jose的下一个主要版本将在处理令牌时要求传递可接受算法列表,允许开发人员明确列出预期算法集。
无特权容器中的KASLR绕过
接下来是一个自2020年以来已修复但从未被Linux内核维护者分配CVE的漏洞。在以下段落中,我们将详细介绍一个先前未知但已修复的KASLR绕过。
早在2020年,Trail of Bits工程师Dominik Czarnota(又名disconnect3d)发现Linux内核中的一个漏洞可能暴露无特权Docker容器内的内部指针地址,允许恶意行为者绕过内核模块的内核地址空间布局随机化(KASLR)。
KASLR是操作系统中的重要防御机制,主要用于阻止利用尝试。它是一种安全技术,在内核重启之间随机化内核内存地址位置。除此之外,内核地址必须对用户空间隐藏;否则,缓解措施将毫无意义,因为此类内核地址披露将有效绕过KASLR缓解。
虽然有些地方会向用户空间程序显示内核地址,但在许多系统上,它们应该仅在用户具有CAP_SYSQLinux能力时可用。(能力拆分root用户权限,因此可能成为root用户或具有uid 0的用户,同时拥有有限的权限集。)特别是,CAP_SYSQLOG能力的手册页写道:“当/proc/sys/kernel/kptr_restrict的值为1时,查看通过/proc和其他接口暴露的内核地址。“这意味着只有使用CAP_SYSQLOG能力执行的进程才能读取内核地址。
然而,Dominik发现,在Docker容器中情况并非如此,从root用户运行但没有CAP_SYSQLOG的进程能够观察内核地址。默认情况下,Docker容器是无特权的,这意味着root用户在其能做的事情上受到限制(例如,他们无法执行需要CAP_SYSQLOG的操作)。这也可以通过使用capsh工具从root用户运行来移除CAP_SYSQLOG能力来演示,而无需Docker:
该问题的根本原因是凭据检查不正确。sysctl切换kernel.kptr_restrict指示是否对暴露内核地址施加限制:值"2"表示地址始终隐藏;“1"表示仅当用户具有CAP_SYSQLOG时才显示;“0"表示始终显示。不是在显示地址之前确保用户具有CAP_SYSQLOG能力,而是仅考虑kptr_restrict的值来决定是否显示或隐藏地址。如果kptr_restrict为1,则地址始终暴露,而如果用户没有CAP_SYSQLOG,则应该隐藏。该问题在提交b25a7c5af905中修复。
发现此漏洞后,我们与Docker和Linux内核安全团队遵循了协调披露流程。Dominik最初通知了Docker团队,因为他认为漏洞源自Docker,并报告了其他sysfs文件系统泄漏(其他sysfs路径泄漏信息,如容器外运行的服务名称、其他容器ID和设备信息)。披露时间线在本文末尾提供。
尽管我们多次请求更新,但Docker仅保持沉默,而Linux社区迅速在内核中纠正了问题。KASLR绕过错误修复被反向移植到各种Ubuntu LTS版本,而Docker的其他sysfs泄漏根本未修复。然而,Linux内核4.19之前的版本容易受到KASLR绕过的影响。使用内核4.15的Ubuntu 18仍然容易受到攻击,因为修复未被反向移植。
无特权容器中KASLR绕过的披露时间线
- 2020年6月6日:向Docker报告漏洞。
- 2020年6月11日:Docker回复称他们可能通过"masked paths"功能阻止泄漏信息的sysfs路径,并且内存地址披露应报告给Linux内核开发人员。
- 2020年6月11日:告知有意联系security@kernel.org关于kASLR绕过。
- 2020年6月11日至6月18日:对kASLR绕过进行了更深入的分析。
- 2020年6月18日:向security@kernel.org报告错误。
- 2020年6月18日:Kees Cook确认错误。
- 2020年6月19日至6月21日:内核开发人员讨论如何修补问题。
- 2020年6月30日:向Docker请求更新。
- 2020年7月3日至7月14日:修复问题的补丁进入Linux内核。
- 2020年7月11日:再次向Docker请求更新关于其他sysfs泄漏,并告知他们KASLR绕过问题已在Linux 4.19、5.4和5.7内核中修复。
- 2020年12月3日:再次向Docker请求更新,并告知有意公开披露问题。Docker未回复。
2024年需要审计吗?
这两个漏洞截然不同:DoS问题涉及解析和解释用户输入,而内核漏洞是信息泄漏(严格来说,它是访问控制漏洞)。这些差异影响错误的可检测性:如果您导致DoS,您可能会立即注意到,因为服务的可用性将受到损害。相比之下,如果攻击者利用访问控制漏洞,您可能不会注意到服务被利用时。
这种可检测性差异对于自动化测试很重要。例如,如Trail of Bits测试手册中展示的模糊测试,通常要求程序崩溃或挂起。因此,我们主要在模糊测试的内存安全程序中发现DoS错误。通过模糊测试自动找到访问控制错误更具挑战性,因为它需要实现模糊测试不变量。
安全审计仍然是查找漏洞不可或缺的工具,就像模糊测试一样!我们的审计尽可能集成模糊测试,并寻找强制执行不变量以捕获讨厌的逻辑错误的机会。