In macOS Mojave 10.14.4 Apple patched several local privilege escalation (LPE
) flaws I reported, such as CVE-2019-8513
and CVE-2019-8530
:
They are both userspace XPC
logic bugs, simple and reliable to get root privilege escalation, just like the “Rootpipe” flaw. This writeup is for the command injection in TimeMachine
diagnose extension, which affected macOS 10.12.x
thru 10.14.3
.
Since this exploit is easy to understand and 100% reliable, please upgrade to 10.14.4 ASAP!
CVE-2019-8530
We’ll start by discussing CVE-2019-8530
which as Apple notes, allows “a malicious application [to] be able to overwrite arbitrary files”.
This BlackHat talk reveals some very interesting LPE
bugs found in diagnostic tool of the system: $hell on Earth: From Browser to System Compromise.
So I started looking at these services: launchctl dumpstate | grep diagnosticextensions
:
Functionalities of these helpers are similar. Let’s take a closer look at timemachine.helper
. The interface is extremely simple:
$ r2 /System/Library/PrivateFrameworks/DiagnosticExtensions.framework/PlugIns/osx-timemachine.appex/Contents/XPCServices/timemachinehelper — You crackme up! [0x100001830]> aaa [x] Analyze all flags starting with sym. and entry0 (aa) [x] Analyze function calls (aac) [x] Analyze len bytes of instructions for references (aar) [x] Constructing a function name for fcn.* and sym.func.* functions (aan) [x] Type matching analysis for all functions (afta) [x] Use -AA or aaaa to perform additional experimental analysis. [0x100001830]> icc @interface HelperDelegate : { } - (char) listener:shouldAcceptNewConnection: - (void) runDiagnosticWithDestinationDir:replyURL: @end
It simply takes a NSURL
as a destination directory to run command /usr/bin/tmdiagnose -r -w -f
as root, then copies the file that matches a regular expression to the destination parameter:
While it doesn’t perform any check on the destination, you can put random garbage (the diagnostic logs) to any existing directory without rootless protection. The other helpers have similar problems. Apple patched this flaw as CVE-2019-8530
:
It used to be exploitable combined with a sudo
design flaw:
Sudo supports a feature where the user does not need to enter the password again for a few minutes after typing the password (and being successfully authenticated).
The check was based on the modified time of the /var/db/sudo/{USER_NAME} directory. By setting the SubmitToLocalFolder value to be /var/db/sudo/{USER_NAME} and triggering the vulnerability, it is possible to execute sudo to gain root privileges.
This bug can modify the timestamp of the directory by writing into it. However, since sudo
has been patched long ago, it’s now pointless.
But what I want is a root shell!
CVE-2019-8513
Now, we’ll discuss CVE-2019-8513
.
The flaw resides in the tmdiagnose
binary, which is not too hard to reverse. Its implementation is just some external shell command wrapped in NSTask
calls and the terminal output is honest:
2018–06–24 18:03:46.131 tmdiagnose[15529:a03] Executing `/usr/sbin/spindump -notarget 15 -file /private/var/tmp/cc@ant.tmdiagnostic/system_state_18.03.46/spindump.txt`
2018–06–24 18:03:48.206 tmdiagnose[15529:1d03] Executing `/usr/bin/fs_usage -w -t 10 -e tmdiagnose`
2018–06–24 18:04:10.392 tmdiagnose[15529:4d03] Executing `/bin/ps auxh`
2018–06–24 18:04:10.652 tmdiagnose[15529:4d03] Executing `/usr/bin/top -l 10`
2018–06–24 18:04:20.631 tmdiagnose[15529:5203] Executing `/usr/bin/powermetrics -i 1000 -n 10 — show-all`
2018–06–24 18:04:31.227 tmdiagnose[15529:a03] Executing `/usr/bin/sample -file /private/var/tmp/cc@ant.tmdiagnostic/samples/backupd.txt backupd 5`
2018–06–24 18:04:36.915 tmdiagnose[15529:a03] Executing `/usr/bin/sample -file /private/var/tmp/cc@ant.tmdiagnostic/samples/Finder.txt Finder 5`
2018–06–24 18:04:42.351 tmdiagnose[15529:1f03] Executing `/bin/ls -la /Volumes/`
2018–06–24 18:04:42.418 tmdiagnose[15529:1f03] Executing `/bin/df -H`
2018–06–24 18:04:42.486 tmdiagnose[15529:1f03] Executing `/sbin/mount`
2018–06–24 18:04:42.556 tmdiagnose[15529:1f03] Executing `/usr/sbin/diskutil list`
2018–06–24 18:04:42.692 tmdiagnose[15529:1f03] Executing `/usr/sbin/diskutil cs list`
2018–06–24 18:04:42.760 tmdiagnose[15529:1f03] Executing `/usr/sbin/diskutil apfs list`
2018–06–24 18:04:42.956 tmdiagnose[15529:1f03] Executing `/bin/bash -c /usr/sbin/diskutil list | /usr/bin/awk ‘/disk/ {system(“/usr/sbin/diskutil info “$NF);
print “*********************”}’`
2018–06–24 18:06:54.482 mddiagnose[15688:1755714] Executing ‘/usr/local/bin/ddt mds’…
2018–06–24 18:06:54.485 mddiagnose[15688:1755714] Executing ‘/usr/local/bin/ddt mds_stores’…
2018–06–24 18:06:54.485 mddiagnose[15688:1755714] Executing ‘/usr/local/bin/ddt corespotlightd’…
Did you see the bug here? There are actually two exploitable bugs!
The executable /usr/local/bin/ddt
does not exist on a fresh installed mac. The location is not protected by rootless, and the popular package manager brew
explicitly set this directory to world writable. So on a macOS with brew
installed (don’t you?), a normal user process can overwrite this file and send an XPC
message to execute it as root.
Alright, this scenario is not the default configuration. What about this? A privileged command injection.
The tmdiagnose
calls external commands via NSTask
API, which is usually considered secure because it does not support shell operators unless you intentionally spawn a shell.
But look at this line, it lists every mounted volumes and find lines that match pattern /disk/
, then use the result to compose a new shell command!
$ strings /usr/bin/tmdiagnose | grep awk # /usr/sbin/diskutil list | /usr/bin/awk ‘/disk/ {system(“/usr/sbin/diskutil info “$NF); print “*********************”}’
Is the shell command really controllable?
$ /usr/sbin/diskutil list /dev/disk0 (internal, physical): #: TYPE NAME SIZE IDENTIFIER 0: GUID_partition_scheme *251.0 GB disk0 1: EFI EFI 209.7 MB disk0s1 2: Apple_APFS Container disk1 250.8 GB disk0s2 /dev/disk1 (synthesized): #: TYPE NAME SIZE IDENTIFIER 0: APFS Container Scheme - +250.8 GB disk1 Physical Store disk0s2 1: APFS Volume Macintosh HD 231.8 GB disk1s1 2: APFS Volume Preboot 44.5 MB disk1s2 3: APFS Volume Recovery 517.0 MB disk1s3 4: APFS Volume VM 4.3 GB disk1s4
The only controllable column is the NAME. We can create a disk image (*.dmg
) and customize its label by using -volname
argument of hdiutil
. Mounting such image an does not require root privilege.
But the problem is, the $NF
variable points to the last column, the IDENTIFIER
, (e.g. disk1s1
), which is impossible to customize.
Just don’t give up now. I used to play web challenges in CTFs years ago so I remember there’s a trick call CRLF injection
. So let’s add a line break to the volume name: hello\nworld
Now hello
is the last column:
Volume label also supports special chars like \n
`$
, so we can inject the payload here. But whitespaces will be treated as splitter and break the payload.
Besides, the label is limited to be shorter than 23 chars (including the NULL
terminator), otherwise it’ll be truncated:
So the label must:
$()
to inject shell commandSince we have bash support now, wildcard comes to the rescue. The working directory is /
. Use A*/1
or t*/1
to execute /Applications/1
or /tmp/1
, making the payload as short as possible.
The final payload is disk
`t*/1
` to execute /tmp/1
:
Apple patched this flaw as CVE-2019-8513
:
This bug can be exploited in the following steps:
timemachinehelper
and waitspindump
, fs_usage
, top
.
But anyways, it’s freaking reliable.
If you enjoyed this blog post and are interested in similar macOS/iOS security topics, check out our upcoming Mac Security Conference:
“Objective by the Sea” v2.0 (Europe, June 1st-2nd, 2019).