关于 xpcroleaccountd
系统服务/usr/libexec/xpcroleaccountd
以 root 身份运行,并具有以下权限:
[Key] com.apple.private.xpc.launchd.ios-system-session
[Value]
[Bool] true
[Key] com.apple.rootless.storage.RoleAccountStaging
[Value]
[Bool] true
[Key] com.apple.security.exception.files.absolute-path.read-only
[Value]
[Array]
[String] /private/var/preferences/com.apple.security.xpc.plist
[Key] com.apple.security.exception.files.absolute-path.read-write
[Value]
[Array]
[String] /private/var/db/com.apple.xpc.roleaccountd.staging/
[String] /private/var/MobileSoftwareUpdate/com.apple.xpc.roleaccountd.staging/
[Key] platform-application
[Value]
[Bool] true
[Key] seatbelt-profiles
[Value]
[Array]
[String] temporary-sandbox
请注意,它能够访问SIP 保护的目录/private/var/db/com.apple.xpc.roleaccountd.staging
/
private
/
var
/db/com.apple.xpc.roleaccountd.stagin
有一些特殊的Apple 签名的XPC 服务,它们 在Info.plist文件中定义“_RoleAccount”=>“root”。例如
/Applications/Xcode.app/Contents/Frameworks/IDEKit.framework/Versions/A/XPCServices/com.apple.dt.Xcode.XcodeSelectXPCService.xpc/Contents/Info.plist:
...
"XPCService"
=> {
"_AllowedClients"
=> [
0
=>
" identifier = com.apple.dt.Xcode and (anchor apple or (anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.9]) or (anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.9.1]))"
]
"_RoleAccount"
=>
"root"
"ServiceType"
=>
"Application"
}
...
当我们尝试与此类 XPC 服务进行通信时,xpcroleaccountd将帮助将XPC 服务包复制/暂存到受SIP 保护的位置,然后从受信任的位置以 root 权限安全地启动 XPC 服务包。
通过逆向,我发现xpcroleaccountd的逻辑很简单:
-
如果特殊的 XPC 服务捆绑来自系统本身,并且捆绑路径受 SIP 保护/可信,则它会成功回复。(直接用root权限启动)
-
如果不是第一次启动特殊XPC服务,则相应的XPC包已经复制到SIP保护位置
/private/var/db/com.apple.xpc.roleaccountd.staging/exec/bundleID.size.blocks[-relaxed].xpc
,则回复成功。(从受信任的缓存位置以 root 权限启动) -
否则,就是第一次启动。它将首先将特殊的 XPC 包复制到受信任的临时位置
/private/var/db/com.apple.xpc.roleaccountd.staging/tmp/uuid
接下来,它将从受信任的临时位置检查特殊 XPC 包的签名:
特殊的 XPC 服务包必须经过Apple 签名并具有权利com.apple.private.xpc.role-account。
如果无法验证签名,则会回复错误,因此 XPC 捆绑包将不会启动。
一旦通过验证,它将 XPC 包从tmp位置移动/重命名到exec位置,并成功回复。因此,特殊的 XPC 服务将从受信任的执行位置以 root 权限启动。
问题与利用
代码签名验证过程似乎很完美:它首先将 XPC 服务包复制到受信任的临时位置,然后检查那里的签名(使用权利com.apple.private.xpc.role-account)。只有通过签名验证后,XPC服务才会以root权限启动。
这从一开始就阻止了我的TOCTOU攻击,因为我无法从 SIP 保护的位置修改 XPC 服务包。
然而,我发现我可以通过符号链接绕过它!
在第 372 行,在将特殊 XPC 捆绑包复制到受信任的临时位置之前,我可以用符号链接替换 XPC 捆绑包源路径。接下来,符号链接已成功复制到受信任位置。结果,它检查签名并通过解析我的符号链接来启动 XPC 包。
以下是我利用这个问题的方法:
- 将 Apple 签名的 XPC 服务包放在位置
/tmp/com.apple.dt.Xcode.XcodeSelectXPCService.xpc
。 - 向服务包发送空 XPC 消息以触发该问题。
- 在xpcroleaccountd调用 API之前
copyfile
,替换/tmp/com.apple.dt.Xcode.XcodeSelectXPCService.xpc
为符号链接,并让符号链接指向原始 Apple 签名的 XPC 包(可写位置)。 - 通过签名验证后,使用恶意负载修改XPC包。
- 我的恶意 XPC 包将以 root 权限启动。
顺便说一句,为了赢得竞争条件,日志字符串可能是执行某些操作的提示。例如,日志内容“stagingareaforbundle:”表明它将调用API copyfile
,现在是替换为符号链接的正确时机。
漏洞代码上传至GitHub仅用于研究目的。
苹果的补丁
该问题已在macOS 14.1中修复:
void
handle_xpc_message
(
void
*,
void
*msg
)
{
...
v70 = copyfile_state_alloc();
copyfile_state_set(v70,
6u
, ©file_callback);
// COPYFILE_STATE_STATUS_CB
v71 = copyfile(
from
, to, v70,
0xC800F
u);
copyfile_state_free(v70);
if
( v71 ) {
// "pid[%d]: copyfile(3) failed on service: %d, (errno %{errno}d)"
goto
EXIT;
}
...
}
在该copyfile_callback
函数中,它将检查目标路径:
int
copyfile_callback
(
int
what,
int
stage,
copyfile_state_t
state,
const
char
*src,
const
char
*dst,
void
*ctx)
{
memset
(&v11,
170
,
sizeof
(v11));
result =
0L
L;
if
( stage == COPYFILE_FINISH )
{
if
( lchown(dst,
0
,
0
) )
{
v7 = sub_100003DF5();
v8 = (os_log_s *)objc_retainAutoreleasedReturnValue(v7);
if
( os_log_type_enabled(v8, OS_LOG_TYPE_ERROR) )
sub_1000067AF(v8);
//"chown(2) failed during copyfile(3): %{errno}d"
}
else
if
( lstat_INODE64(dst, &v11) )
{
v9 = sub_100003DF5();
v8 = (os_log_s *)objc_retainAutoreleasedReturnValue(v9);
if
( os_log_type_enabled(v8, OS_LOG_TYPE_ERROR) )
sub_100006746(v8);
//"lstat(2) failed during copy: %{errno}d"
}
else
{
result =
0L
L;
if
( (v11.st_mode &
0xF000
) !=
0xA000
)
//dst is a symbolic link?
return
result;
v10 = sub_100003DF5();
v8 = (os_log_s *)objc_retainAutoreleasedReturnValue(v10);
if
( os_log_type_enabled(v8, OS_LOG_TYPE_ERROR) )
sub_100006712(v8);
//"encountered symbolic link during copy"
}
objc_release(v8);
return
2L
L;
//COPYFILE_QUIT, the entire copy is aborted at this stage. Any filesystem objects created up to this point will remain. copyfile() will return -1, but errno will be unmodified.
}
return
result;
}
如果目标路径是符号链接,copyfile_callback
将返回2 (COPYFILE_QUIT),整个复制将在此阶段中止。接下来,copyfile
将返回-1,但 errno 将保持不变。最后,xpcroleaccountd退出并回复错误。
原文始发于微信公众号(Ots安全):CVE-2023-42942:xpcroleaccountd Root 权限升级
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论