Rootpipe Reborn (Part II)
CVE-2019-8565 Feedback Assistant race condition leads to root LPE
Previously on this series:
Background and some history
There’s a general bug type on macOS.
When a privileged (or loosely sandboxed) user space process accepts an IPC message from an unprivileged or sandboxed client, it decides whether the operation is valid by enforcing code signature (bundle id, authority or entitlements). If such security check is based on process id, it can be bypassed via pid reuse attack.
An unprivileged client can send an IPC message, then spawns an entitled process to reuse current pid. The privileged service will then validate on the new process and accept the previous IPC request, leading to privilege escalation or even sandbox escape. The attacker can stably win the race by spawning multiple child processes to fill up the message queue.
Security checks based on pid, like
SecTaskCreateWithPID suffer from this attack.
The idea and the initial PoC was borrowed from Ian Beer:
Samuel Groß has also been aware of this senario:
- Don’t Trust the PID! Stories of a simple logic bug and where to find it
- Pwn2Own: Safari sandbox part 2 — Wrap your way around to root
Put another way, the IPC server should never use
[NSXPCConnection processIdentifier] to check the validity of incoming client. It should use the
audit_token_t instead (note: there was an exception).
Unfortunately these functions are undocumented and private:
Since, as noted, these methods are private, third-party developers are trapped in this issue repeatedly:
Keybase disclosed on HackerOne: Privilege Escalation via Keybase...
In the previous [report](https://hackerone.com/reports/397478), about the privileged helper lacks of validation so any…
Code Signature validation is racy · Issue #3 · google/macops-MOLXPCConnection
See Project Zero's previous issue on macOS/iOS …
Apple please consider opening these functions to developers!
Oh wait. Actually
audit_token_t was not so trustworthy. @5aelo has just pointed out another bug before iOS 12.2 / macOS 10.14.4: Issue 1757: XNU: pidversion increment during execve is unsafe 🤦♂
The privileged XPC service
com.apple.appleseed.fbahelperd has exported the following interface:
@protocol FBAPrivilegedDaemon <NSObject>
- (void)copyLogFiles:(NSDictionary *)arg1;
- (void)gatherInstallLogsWithDestination:(NSURL *)arg1;
- (void)gatherSyslogsWithDestination:(NSURL *)arg1;
- (void)sampleProcessWithPID:(unsigned long long)arg1 withDestination:(NSURL *)arg2;
- (void)runMDSDiagnoseWithDestination:(NSURL *)arg1;
- (void)runTMDiagnoseWithDestination:(NSURL *)arg1;
- (void)runBluetoothDiagnoseWithDestination:(NSURL *)arg1 shortUserName:(NSString *)arg2;
- (void)runWifiDiagnoseWithDestination:(NSURL *)arg1;
- (void)runSysdiagnoseWithDestination:(NSURL *)arg1 arguments:(NSArray *)arg2;
- (void)runSysdiagnoseWithDestination:(NSURL *)arg1;
- (void)runMobilityReportWithDestination:(NSURL *)arg1;
- (void)runSystemProfileWithDetailLevel:(NSString *)arg1 destination:(NSURL *)arg2;
Look at the implementation of
-[FBAPrivilegedDaemon listener:shouldAcceptNewConnection:] method. It only allows XPC messages from one client:
/System/Library/CoreServices/Applications/Feedback Assistant.app/Contents/MacOS/Feedback Assistant
But since it performs the security check based on process id, we can bypass it.
You can now refer to the proof of concept by Ian Beer (https://bugs.chromium.org/p/project-zero/issues/attachmentText?aid=276656) or see my full exploit at the end.
The steps to trigger the race condition are as follows:
- Create multiple client processes via
NSTask(note: you can’t do this on iOS)
- Better not to use
forkbecause Objective-C runtime may crash if you call it between
exec, which is required by this attack. On 10.13 you can add a
OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YESenvironment variable before process creation or add a
__DATA,__objc_fork_oksection to your executable as a workaround. But these workarounds are not compatible with previous macOS. For more information, please refer to http://www.sealiesoftware.com/blog/archive/2017/6/5/Objective-C_and_fork_in_macOS_1013.html
- Send multiple XPC messages to the server to block the message queue
- Ian Beer uses
execveto replace the binary to a trusted one and write to its its buffer to prevent the new process from terminating. Instead, I chose to pass these flags
POSIX_SPAWN_SETEXEC | POSIX_SPAWN_START_SUSPENDEDto
posix_spawnto create a suspended child process and reuse the pid of the parent
- Since the child process has been replaced, there won’t be any callback. You have to use a “canary” to detect whether the race is successful based on the server’s behavior, e.g., the existence of a newly created file
From the console output, the server accepts our request:
Give me root
Now continue code audit on
copyLogFiles: accepts one
NSDictionary argument, whose keys as the sources and the correspond
NSString as destination to perform file copy. It supports multiple tasks at once, and the path can be both directory or file.
The source must start with
/var/log, and the destination must match one the following patterns:
It will not override an existing destination.
These constraints can be bypassed by path traversal. So now we can copy arbitrary file or folder to anywhere unless rootless protected.
Additionally, after each copy, it will call
-[FBAPrivilegedDaemon fixPermissionsOfURL:recursively:] to set the copied files’ owner to the XPC client process’s gid and uid. This is extremely ideal for macOS LPE CTF challenges. I used this zero day exploit during #35C3 CTF to simply copy the flag and read it. LOL.
If you don’t mind reboot, getting root privilege is simple. Copy the executable to the places that will be automatically launched with privilege during startup. For example, the bundles in
/Library/DirectoryServices/PlugIns will be loaded by the process
/usr/libexec/dspluginhelperd, who has root privilege and is not sandboxed.
Can we have an instant trigger solution?
Since it will never override existing file, we can not:
- override administrator account’s password digest (
- override suid binaries (not to mention file permission and rootless) ❌
- override one of the PrivilegedHelpers ❌
And it will fix file permissions, none of these would work:
- add sudoer ❌
- add an entry to
/Library/LaunchDaemonsto register a new XPC service ❌
We need more primitives.
The daemon has other methods named
They are various external command wrappers just like those diagnose helpers mentioned from my previous post. What’s interesting is that
runTMDiagnoseWithDestination: acts the same as
timemachinehelper , thus we can trigger the CVE-2019-8513 command injection.
At first I was looking at
runMDSDiagnoseWithDestination: , who launches
/usr/bin/mddiagnose that will finally spawn
/usr/local/bin/ddt after around 10 seconds, waiting for the
/usr/bin/top command to end. Remember the previous post? This location does not exist by default and we can put custom executable with the arbitrary file copy bug.
Another exploit path is method
runMobilityReportWithDestination:. It invokes this shell script:
The script checks the existence of
/usr/local/bin/netdiagnose. If so, execute it as root. The exploit will success within milliseconds.
By the way, I was surprised by how many diagnostic tools depending on the non-existing directory
The bug has been fixed in macOS 10.14.4 and iOS 12.2.
About the security content of macOS Mojave 10.14.4, Security Update 2019-002 High Sierra, Security…
802.1X Available for: macOS Mojave 10.14.3 Impact: An attacker in a privileged network position may be able to…
Grab the source for my PoC from https://github.com/ChiChou/sploits/tree/master/CVE-2019-8565 , it’ll give you a root shell within milliseconds.