在这篇文章中,安全研究员Ilias Morad(又名A2nkF)发现了一个可利用的漏洞攻击漏洞链,该漏洞利用漏洞链由他在macOS中发现的几个安全漏洞组成。从用户到内核,此攻击漏洞链创建了本地特权升级(LPE)!
这篇文章是关于我在Februray撰写的《MacOS LPE攻击漏洞链》中提到的漏洞链,它有三个逻辑漏洞,从用户到root,绕过系统集成保护(SIP)到内核。由于我没有利用任何内存攻击或其他不是100%确定性的易受攻击性,因此该漏洞链是完全可靠的,它在macOS <= 10.15.5上运行。
这是我第一个真正开发的漏洞链,所以我可能犯了很多错误。如果你发现任何问题或有任何建议/改进,请在Twitter上向我发送DM或相关问题。
漏洞1
第一个漏洞位于authd(8)中,它是大多数开源Security.framework的一部分。顾名思义,此框架管理与安全相关的事物,包括钥匙串访问(通过securityd(1)),代码签名(通过trustd(8))以及授权,身份验证和生成特权进程(通过authd)。
安全框架提供了一些与authd进行交互的功能,这些API可以由第三方应用程序使用,奇热但是许多Apple自己的私有和公共框架也可以使用它们,以在非特权进程中执行特权操作。
authd提供的功能如下所示:
在authorization.plist中定义了受支持的规则集以及一些预注册的权限,此规则允许对客户机需要哪些特权才能符合规则进行非常细粒度的控制。它们包括但不限于客户端组,客户端用户(无论是以GUI或控制台运行)以及客户端的权限,另外一些权限要求用户在弹出对话框中输入密码。
如果你是macOS用户,我确定你之前已经看到过类似的弹出窗口:
在审计authd代码时,我在process.c中发现了一些有趣的内容,具体来说,负责从客户端获取代码签名相关信息的代码行:
如我们所见,它调用SecCodeCopySigningInformation从客户端[1]检索数据,如果找到任何权限,它将继续将这些权限中的值复制到dict [2]中。
乍一看,这段代码似乎不错,但阅读Apple的开发文档SecCodeCopySigningInformation就会发现问题:
“如果代码的签名数据损坏或无效,此函数可能失败或返回部分数据,为了确保只返回有效数据(无效数据会引发漏洞),你必须在调用SecCodeCopySigningInformation之前成功调用SecCodeCheckValidity或SecCodeCheckValidityWithErrors函数。”
在调用SecCodeCopySigningInformation之前都不会被调用,顺便说一下,这个文件的版权可以追溯到2012-2013年!
SecCodeCheckValidity [WithErrors]函数会将客户端磁盘上的二进制文件与其CDHash进行比较,以验证其完整性。由于这种情况从来没有发生过,所以有可能在没有authd抱怨的情况下使用任意的权利来代码签名客户机。
现在我们需要弄清楚授权机构对什么感兴趣,这是authd在内部使用的函数,用于检查进程是否具有所需的权限:
如我们所见,它查找com.apple.private.AuthorizationServices权限[3],该权限应该是字符串数组,其中每个条目都是所需权限的名称。
1.有了这些知识,触发漏洞就很简单了:创建具有所需权限的权限文件。为了演示,我们将使用system.install.apple-software:
2.编写一个等待一段时间的程序,然后创建一个AuthorizationRef并从authd请求所需的权限;
3.在程序等待时,运行codesign -f -s---entitlements entitlements.xml ./test,其中./test是程序的路径;
4.观察authd日志,你会发现类似以下内容:
如果你想知道为什么我们不能在运行程序之前先做代码签名,这是因为AppleMobileFileIntegretyDaemon或amfid(8)。这个守护进程负责获取和验证已签名二进制文件的权限/签名。amfid不允许我们运行,因为我们是非Apple签署的程序,权限有限。
如果我们只要持有适当的权限就可以得到我们想要的任何权限,那么这将是一个很棒的原始方法,在这种情况下,我们可以只获得system.privilege.admin权限,并使用AuthorizationExecuteWithPrivileges API来获取root特权,但情况并非如此。
如前所述,流程需要满足规则才能获得权限。其中一项规则(主要由Apple的私有框架使用)检查授权,然而许多规则并不关心授权。相反,它们要求客户端作为特定用户运行,或者要求用户输入密码。
在分析了authorization.plist之后,我发现默认用户只需持有相应的权限即可获得以下权限:
如上图所示,可以看出有相当多的权限。
立即引起我兴趣的三个是:system.install.apple-software,system.preferences.nvram和com.apple.ServiceManagement.daemons.modify。
遗憾的是,SystemAdministrator.framework本身会对客户端执行其他检查,因此我们无法编写nvram,而com.apple.ServiceManagement.daemons.modify比实际情况要好得多。它不允许注册守护程序,而只能启动/停止现有的守护程序。
值得一提的是com.apple.activitymonitor.kill(可用于终止任意进程)和com.apple.uninstalld.uninstall(可用于在无需用户输入密码的情况下删除文件/应用程序)。这两个很容易使系统崩溃,但这不是我们想要的。我们要执行内核代码。
因此,我们先在就有了system.install.* rights权限,逆向私有PackageKit.framework会发现它具有一些有趣的API。它们可用于将Apple签名的软件包安装到任何不受SIP保护的位置。当我们遇到下一个漏洞时,这将很有用。
我敢肯定,我错过了其他一些使用权限来执行代码的方法。如果你有兴趣,请随时进行一些逆向操作,并告诉我你发现的内容。
Apple缓解此漏洞的方法是在SecCodeCopySigningInformation中实施代码有效性检查,从而无需事先调用SecCodeCheckValidity [WithErrors]。
更新后的开发人员文档声明:
“此函数获取并验证代码对象指定的代码上的签名,它检查所有密封组件的有效性,包括资源(如果有的话)。它root据指定的代码需求验证代码,如果所有这些条件都满足,则调用成功。”
漏洞2
到目前为止,我们可以将Apple签名的软件包安装到任何位置。因此,我们绕过了你在安装软件包时通常会看到的密码提示,但仅限于Apple签名的软件包。
PKG文件基本上是归档文件,包括要安装的文件、一个可选的代码签名和安装前/安装后脚本。pkgutil(1)实用程序可用于发布此类归档文件的内容。
通常,安装前/安装后脚本由installd(8)执行,它作为root用户运行。但我们不能用恶意脚本构建我们自己的包,因为它不会是Apple签名的。因此,如果我们找到了Apple签名的程序包,可以以某种方式劫持其中一个脚本怎么办?这将比设计我们自己的软件包更好,因为如果一个软件包是由Apple签名的,则脚本不是由installd执行,而是由system_installd(8)执行!
不同之处在于system_installd拥有com.apple.rootless.install.heritable权限。这意味着它及其所有子进程都不受SIP限制地运行,这很有意义,因为它们可能必须在受SIP保护的位置安装或更新系统文件。
那么我们在哪里可以得到Apple签名的软件包? Apple的开发人员网站上有很多内容,但是在下载所有内容并阅读了大多数脚本之后,我没有找到任何东西。由于不成功,我寻找了替代下载并最终找到了一个: macOSPublicBetaAccessUtility.pkg 可从beta.apple.com下载。让我们来看看这个安装后的脚本,$3持有磁盘,这个包被安装到:
因此,只要路径匹配,我们就能够创建具有更高权限的可执行文件。嗯,我不知道为什么有人会做这样的事情,但是无论如何,我们拥有执行root代码和SIP绕过的良好功能。
现在,我们仍然需要内核代码的执行,接下来说一说下一个漏洞。
漏洞3
当我们已经有了SIP旁路并以root方式执行代码时,获得内核代码执行的相当明显的目标是kextutil(8)。这是一个实用程序,负责加载/卸载内核扩展。在禁用SIP的系统上,如果希望加载内核扩展,那么作为root用户就足够了。但是,在使用SIP的系统上,你只能加载由Apple签名的内核扩展。
由于某些原因,签名验证不在内核中进行,而是在kextutil本身中进行!?SIP限制root用户调试kextutil之类的系统进程,因此我们不能让它跳过签名检查。
但是我们还有其他事情可以做,当指示kextutil加载内核扩展时,它将执行以下操作。首先,它将内核扩展复制到受SIP保护的目录(/ Library / StagedExtensions / private /
理论上,如果kextutil只打开kext一次,加载所有文件,执行检查,然后从内存加载文件,那么这样做是可以的。使用文件描述符可以缓解这个问题,但实际上,在验证kext和将其加载到内核之间存在竞争条件。
如上所述,这个漏洞攻击链是肯定存在。我们可以使用一个技巧,即kextutil具有-interactive标志。绕过SIP后我们需要做的是:
1.将一些Apple签名的内核扩展名(例如acfs.kext)复制到不受SIP保护的位置(例如/ tmp);
2.运行kextutil -interactive /tmp/acfs.kext(kextutil将自动验证签名,但在加载kext之前等待你的交互);
3.用你自己的二进制文件覆盖二进制文件(例如mv kernelHax /Library/StagedExtensions/private/tmp/acfs.kext/Contents/MacOS/acfs);
4. kextutil继续的话,将会把包括我们的恶意代码在内的kext加载到内核中;
5.告诉kextutil再次继续,这将使其启动内核扩展;
具体演示视频,请点此。
结论
广泛使用的软件中存在许多漏洞,有些漏洞并不像人们想象的那么复杂。完整的利用漏洞链可以在这里找到。