This post is a writeup of CVE-2023-32364, a macOS application sandbox escape bug I found. It was supposed to be unveiled in my upcoming talk:
"Unexpected, Unreasonable, Unfixable: Filesystem Attacks on macOS" at OBTS v6,
but I needed to cut some bugs out. This is one of them.
macOS Sandboxing basics
If you are not a macOS researcher by trade, let me quickly explain the
Sandbox. The application sandbox is a
protection mechanism implemented on macOS/iOS to confine applications. It leverages
SIP (System Integrity Protection),
a mechanism in the kernel that's similar to AppArmor. To configure it, an application's signature can
contain information whether to confine this application or not:
$ codesign -d --entitlements - ~/myapp/build/Release/sandbox_helper.app/Contents/MacOS/sandbox_helper
If the application does not have the
com.apple.security.app-sandbox field present or the app is not signed:
it inherits the parent's sandbox. There is however a major caveat:
On macOS an application can be spawned using the
open terminal command. This instructs
launchd (basically init)
to start it.
If an application is started by
launchd and it doesn't have the
com.apple.security.app-sandbox value set,
it will run unsandboxed!
You are probably aware of the fact that on macOS the "quarantine" functionality is enforced with the help of
an extended attribute (xattr):
What you might not know is that
xattrs are pretty flimsy:
- Some filesystems simply don't support them at all (like FAT)
- symbolic links can't have them
This is already fishy as it's usually not a great idea to have security functionality rely on flimsy features, but it gets worse.
On macOS an app (like Terminal.app) is not actually a file but a directory trying (badly) to pretend to be a file. This begs the question:
How does the quarantine flag really work?
To find this out, I created a trivial app outside the sandbox using osacompile:
$ osacompile -o output.app -e 'do shell script "whoami > /tmp/x"'
$ find output.app/
$ open output.app
$ cat /tmp/x
This just runs
whoami and dumps the output to
osacompile to generate a valid
.app, but this could be any valid
.app as long as it has
sandboxing off or is not signed.
This app works okay when ran from the sandbox, since no file in it has the quarantine flag. However, if I re-create it from a sandboxed app, all the files/dirs will be marked as quarantined.
Since everything else is identical, let's start there: By randomly removing the flags until stuff works I quickly realized that macOS only enforced the quarantine flag on the top level directory and the executable
To put it another way: if I managed to remove the flag from
output.app/Contents/MacOS/applet I could
break out of the sandbox :)
Looking for a way to remove the flag I came to the conclusion that there are few ways which I could use to do so. So, it might actually be easier to not have it placed in the first place.
If we want to prevent the flag from being placed we can:
- use filesystems that don't support it
- use symlinks
Now, I'm fairly sure that there are about a million other ways, but since I'm allowed to mount filesystems as a user on macOS (an extremely powerful weapon for an attacker), I felt confident that I can leverage this.
After a couple tries it turned out that from the app sandbox I can mount stuff, but to mount anything useful (like a disk image), I need to talk to diskarbitrationd (among others). Since these entitlements are pretty suspicious I'd rather not rely on them.
From the really basic things I could mount, one stood out above the rest:
devfs is basically a highly restricted
tmpfs in macOS, that barely allows an attacker to do anything useful.
Crucially, it does not support
xattrs, but it allows me to create directories and symbolic links. Well, as
luck would have it, this is precisely what we need.
With our newfound capabilities we now have a plan:
- create the top level directory without the quarantine flag
- create the executable without the quarantine flag
2 is easy, so let's do that first:
I can simply symlink to
/bin/bash. Since I can specify ENV vars like
Info.plist I can make bash execute commands on my behalf. Done.
1 is trickier:
I need a directory that has the following name:
X doesn't matter, but the
does. When reporting this,
Y could have been pretty much anything as long as it is "app" or not another specially
handled extension like "plist", "png", etc... Now it seems that
Y is enforced to be "app" only, but that does not
To create such a directory, we can simply mount devfs (with noowners), give us permissions using chmod and create a directory on the volume. From this point onward we can symlink any other important files/directories to a location we wish.
We can use the terminal command
open to have it start the app using
bash will execute
outside the sandbox with the environment variables we specified :)
Demo and code
The full exploit code is on my github: https://github.com/gergelykalman/CVE-2023-32364-macos-app-sandbox-escape
Apple fixed this bug by preventing sandboxed applications from creating directories on devfs.
Apple apparently now also forces the app to have the
.app extension, which is a minor added hurdle.
The main fix seems like a band-aid: If an attacker can create a directory without the quarantine flag, they'll be able to escape the sandbox still.
I have tested this on Sonoma (14.0-23A344), that came out today.
- Unprivileged user mounting is an incredibly powerful attack tool
- Relying on optional filesystem features for security is a timebomb
- Mixing of unrelated identities (file / directory) in the context of apps makes defending difficult
Since the fix is band-aid and the core issues would be extremely costly to fix, my prediction is that we will see many more exploits like this.
A clear path forward for example seems to be race conditions: there's nothing that prevents an attacker from altering an .app structure while it's being inspected for the flags...
- 2022.12.15: Report sent to Apple
- 2023.01.04: Requested clarification
- 2023.01.18: Request for update
- 2023.01.18: Apple: Still working on it
- ... lots of ping/pong later
- 2023.06.07: Bug is fixed in the current beta release
- 2023.07.24: Bounty awarded
While the timeline could be shorter, my contact at Apple responded within days to my pings. Usually with the message that they're still working on it, but I'd much rather have this than ghosting.
Thanks Apple :)