App Sandbox Escape vulnerability in macOS (CVE-2023-27966)
Introduction
I am Masahiro Kawada, Penetration Testing Section, Offensive Security Department.
Here, I report a macOS vulnerability that allows for App Sandbox escaping. Following the release of this information in August 2023, I write a blog post detailing the process of discovering the vulnerability.
CVE-2023-27966
Motivation and Purpose
I initiated this research with the aim of developing a C2 agent in Word document format.
To utilize a C2 agent in Word document format on macOS, it is imperative to bypass the App Sandbox. As expected, the Sandbox is designed to be difficult to penetrate. Therefore, I conducted an investigation to determine the most effective method to achieve this.
App Sandbox
The App Sandbox serves as a mechanism to limit app access in the event that an attacker compromises app permissions. Adherence to Sandbox settings is compulsory, particularly when distributing apps via the App Store. For each Entitlement, please consult the official documentation.
Reference: App Sandbox
For instance, the Slack.app downloaded from the App Store and the Slack.app downloaded from the official website exhibit certain differences in settings at the time of build, including Sandbox settings.
# App Store (https://apps.apple.com/jp/app/slack-for-desktop/id803453959)
➜ ~ codesign -dvv --entitlements - /Applications/Slack.app
...
CodeDirectory v=20400 size=477 flags=0x0(none) hashes=4+7 location=embedded
...
[Dict]
[Key] com.apple.security.app-sandbox
[Value]
[Bool] true
...
# https://slack.com/intl/ja-jp/downloads/mac
➜ ~ codesign -dvv --entitlements - /Applications/Slack.app
...
CodeDirectory v=20500 size=485 flags=0x12000(library-validation,runtime) hashes=4+7 location=embedded
...
[Dict]
...
If you are developing with Xcode, the App Sandbox can be configured as follows:
Container Directory
Apps with Sandbox are assigned a dedicated container directory, ~/Library/Containers/<Bundle ID>
. Here, the Bundle ID is a unique identifier assigned at the time of app build and can be easily identified.
➜ ~ codesign -dvv --entitlements - /Applications/Microsoft\ Word.app
Executable=/Applications/Microsoft Word.app/Contents/MacOS/Microsoft Word
Identifier=com.microsoft.Word
...
Primarily, apps with Sandbox applied utilize the Data directory within each container directory.
➜ ~ ls -al ~/Library/Containers/com.microsoft.Word/Data
total 16
lrwxr-xr-x 1 victim staff 31B Jan 9 2020 .CFUserTextEncoding -> ../../../../.CFUserTextEncoding
-rw-r--r--@ 1 victim staff 6.0K Mar 15 17:32 .DS_Store
lrwxr-xr-x 1 victim staff 19B Jan 9 2020 Desktop -> ../../../../Desktop
drwx------ 2 victim staff 64B Nov 21 2020 Documents
lrwxr-xr-x 1 victim staff 21B Jan 9 2020 Downloads -> ../../../../Downloads
drwx------ 34 victim staff 1.1K Feb 9 2023 Library
lrwxr-xr-x 1 victim staff 18B Jan 9 2020 Movies -> ../../../../Movies
lrwxr-xr-x 1 victim staff 17B Jan 9 2020 Music -> ../../../../Music
lrwxr-xr-x 1 victim staff 20B Jan 9 2020 Pictures -> ../../../../Pictures
drwx------ 2 victim staff 64B Nov 21 2020 SystemData
drwx------ 2 victim staff 64B Nov 21 2020 tmp
Although each app can use this container directory with relative freedom, it is subject to certain restrictions. For instance, binaries and shell scripts located within the container directory cannot be executed.
➜ ~ /sbin/ping -c 1 127.0.0.1
PING 127.0.0.1 (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.055 ms
--- 127.0.0.1 ping statistics ---
1 packets transmitted, 1 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 0.055/0.055/0.055/0.000 ms
➜ ~ cp /sbin/ping ~/Library/Containers/com.microsoft.Word/Data/
➜ ~ ~/Library/Containers/com.microsoft.Word/Data/ping -c 1 127.0.0.1
[1] 27348 killed ~/Library/Containers/com.microsoft.Word/Data/ping -c 1 127.0.0.1
➜ ~ open -a ~/Library/Containers/com.microsoft.Word/Data/ping --args -c 1 127.0.0.1
The application /Users/victim/Library/Containers/com.microsoft.Word/Data/ping cannot be opened for an unexpected reason, error=Error Domain=RBSRequestErrorDomain Code=5 "Launch failed." UserInfo={NSLocalizedFailureReason=Launch failed., NSUnderlyingError=0x6000007d4360 {Error Domain=NSPOSIXErrorDomain Code=162 "Unknown error: 162" UserInfo={NSLocalizedDescription=Launchd job spawn failed}}}
Microsoft Word Example
Upon checking the Sandbox settings in Microsoft Word.app, you will encounter the following unique settings.
➜ ~ codesign -dvv --entitlements - /Applications/Microsoft\ Word.app
...
[Key] com.apple.security.temporary-exception.sbpl
[Value]
[Array]
[String] (allow file-read* file-write* (require-all (vnode-type REGULAR-FILE) (regex #"(^|/)~\$[^/]+$")) )
[String] (deny file-write* (subpath (string-append (param "_HOME") "/Library/Application Scripts")) (subpath (string-append (param "_HOME") "/Library/LaunchAgents")) )
...
The first line of the rule allows reading and writing to files beginning with ~$
, which is added so that the necessary files can be generated when editing Word documents.
The second line restricts writing to the ~/Library/Application Scripts/
and ~/Library/LaunchAgents/
directories. This was added in response to a previous vulnerability and will be explained later.
Establishing a C2 session from Microsoft Word.app with Sandbox applied is not practical, as it cannot even enumerate the /tmp/
directory. We need to escape from Sandbox to bring this to a practical level of access rights.
In Microsoft Word.app's Sandbox escaping, it is important to keep in mind that it is possible to read and write files that begin with ~$
.
In order to verify from the Sandbox environment applied to Microsoft Word.app, I prepared a C2 server using Mythic and Apfell, and established a C2 session using a simple macro as shown below.
launchd and Sandbox Escaping
Since the defined Sandbox profile applies to child processes, it is necessary to create a process that is completely independent of the current process in order to perform Sandbox escaping. In principle, due to the protection by SIP (System Integrity Protection), it is difficult to inject code into another process.
Therefore, it is almost mandatory to use launchd when attempting to escape from the Sandbox.
Launchd is a framework responsible for system initialization and service management, and /sbin/launchd
is always assigned a process ID of 1.
You can create a child process of launchd mainly in the following two ways.
● /usr/bin/open command
● Persistence methods such as LaunchAgents, LaunchDaemons, or Login Items
The creation of child processes of /sbin/launchd
utilizing the open command can be easily confirmed as follows.
➜ ~ open -a /sbin/ping --args -c 32 127.0.0.1
➜ ~ ps -ef
UID PID PPID C STIME TTY TIME CMD
...
501 12024 1 0 6:35PM ?? 0:00.01 /sbin/ping -c 32 127.0.0.1
...
The following are two macOS vulnerabilities that were capable of Sandbox escaping by these methods.
Sandbox Escape via /usr/bin/open
● CVE-2021-30864 (Perception Point)
With this vulnerability, Sandbox escaping is achieved by modifying the HOME directory by setting an environment variable when launching Terminal.app with the open command, and then letting it execute .zshenv
or other files placed by the attacker. Terminal.app is executed via launchd by the open command, so it is not affected by Sandbox.
➜ ~ open --env __OSINSTALL_ENVIROMENT --env HOME=~/Library/Containers/com.microsoft.Word/Data /System/Applications/Utilities/Terminal.app
It is now fixed so that the environment variable HOME cannot be rewritten via the env option of the open command.
Reference: A Technical Analysis of CVE-2021-30864: Bypassing App Sandbox Restrictions
● CVE-2022-26706 (Microsoft)
The vulnerability allows Sandbox escaping by executing a Python script using the open command. Files generated by Sandbox-enabled apps, such as Microsoft Word.app, are assigned the com.apple.quarantine attribute. In this case, they cannot be executed as Shell or Python scripts. However, this restriction was successfully circumvented using the -stdin option of the open command.
deploy@deploy ~ % ls -l test.py
-rwxr-xr-x@ 1 deploy staff 77 Aug 31 22:07 test.py
deploy@deploy ~ % xattr test.py
com.apple.quarantine
deploy@deploy ~ % ./test.py
zsh: operation not permitted: ./test.py
This vulnerability has been fixed to prevent the open command from creating a process even if the file specified in the -stdin option has the com.apple.quarantine attribute.
➜ ~ xattr hello.py
com.apple.quarantine
➜ ~ open --stdin='hello.py' -a Python
The application /Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.9/Resources/Python.app cannot be opened for an unexpected reason, error=Error Domain=NSOSStatusErrorDomain Code=-10810 "kLSUnknownErr: Unexpected internal error" UserInfo={_LSFunction=_LSLaunchWithRunningboard, _LSLine=2735, NSUnderlyingError=0x60000232cb40 {Error Domain=RBSRequestErrorDomain Code=5 "Launch failed." UserInfo={NSLocalizedFailureReason=Launch failed., NSUnderlyingError=0x60000232cae0 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted" UserInfo={NSLocalizedDescription=Launchd job spawn failed}}}}}
Reference: Uncovering a macOS App Sandbox escape vulnerability: A deep dive into CVE-2022-26706
Sandbox Escaping by Persistence
● CVE Unnumbered (MDSec)
This is a vulnerability related to the second line of the custom rule applied to Microsoft Word.app that we have just mentioned.
[String] (deny file-write* (subpath (string-append (param "_HOME") "/Library/Application Scripts")) (subpath (string-append (param "_HOME") "/Library/LaunchAgents")) )
As this rule was not implemented at the time, Microsoft Word.app could generate a plist file in the ~/Library/LaunchAgents
directory if the name of the file started with ~$
.
The ~/Library/LaunchAgents
directory contains plist files. These are similar to configuration files in macOS and can be stored in either XML or binary format. As an example, the following plist file can be saved to execute specified commands when a user logs in.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>LaunchAtLogin</string>
<key>ProgramArguments</key>
<array>
<string>/bin/sleep</string>
<string>300</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
When the user logs in after storing the plist file, it can be confirmed that the specified sleep command is executed as a child process of launchd.
The directory is not affected by the Sandbox applied to Microsoft Word.app, as it is writable by a general user and launchd is responsible for the execution of the specified command.
Of course, the aforementioned custom rules have been added to address this vulnerability.
Reference: Escaping the Sandbox – Microsoft Office on MacOS
● CVE Unnumbered (Madhav Bhatt)
This is a Sandbox escape that makes use of Login Items, which was still working at the time of this study. It is not something that can be used for escaping from the Sandbox of any app, but it is valid in Microsoft Word.app, which can generate files starting with ~$
.
The premise is that it is possible to add Login Items even from apps that have Sandbox applied; apps and files can be added to Login Items, and if a file is added, the app will be opened by the target file's default handler when the user logs in. For example, if a zip file is added to Login Items, the file will be unzipped by Archive Utility.app.
By utilizing this specification, it is possible to achieve sandbox escaping.
First, create a ~$exploit.zip
under the user's home directory and add it to Login Items. This zip file will be extracted to .zshenv
. The ~/.zshenv
file does not exist in macOS by default and the specified OS command will be executed when zsh starts. Note that if you unzip it as an already existing .zshrc
, it will be unzip into a file name such as .zshrc 2
.
Next, add Terminal.app to the Login Items.
OS commands will then be executed on Terminal.app when a user logs in according to the following procedure.
- The
~/~$exploit.zip
added to Login Items is extracted to~/.zshenv
. - The Terminal.app added to Login Items is started
- zsh launched by Terminal.app reads
~/.zshenv
and the specified OS command is executed
Reference: Office365 MacOS Sandbox Escape
However, there is one challenge when it comes to actually exploiting this method.
As the decompression of the zip file in step 1 and the launching of Terminal.app in step 2 are executed simultaneously (in no particular order) when the user logs in, there are cases where Terminal.app is launched before the zip file is decompressed to ~/.zshenv
at the first login. In this case, you need to wait for a second login. It is a major disadvantage, given that it is possible to force a logout with commands such as launchctl bootout gui/$UID
only once.
Therefore, I conducted an investigation to find a more effective method for sandbox escaping, based on this approach.
Terminal File
The Terminal file is in XML format, which is referred to as the Terminal configuration file in macOS. The default handler for this file is Terminal.app, and it is possible to execute OS commands specified in the file from Terminal.app as follows.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CommandString</key>
<string>sleep 30</string>
<key>ProfileCurrentVersion</key>
<real>2.0600000000000001</real>
<key>RunCommandAsShell</key>
<false />
<key>name</key>
<string>exploit</string>
<key>type</key>
<string>Window Settings</string>
</dict>
</plist>
/usr/bin/open + .terminal
When the terminal configuration file touch.terminal
was placed under ~/Library/Containers/com.microsoft.Word/Data/
using Microsoft Word.app and the open command was executed, the execution failed as follows.
As mentioned when describing CVE-2022-26706, which was discovered by Microsoft, files created in the system by Microsoft Word.app with Sandbox applied are given the com.apple.quarantine attribute and the constraints are strictly enforced. In principle, it is difficult to remove this attribute without escaping the Sandbox, but for investigation purposes, I removed it from Terminal.app and ran the open command again.
deploy@deploy ~ % xattr –d com.apple.quarantine ~/Library/Containers/com.microsoft.Word/Data/touch.terminal
The execution failed with a different error message. The error message and its behaviour suggest that it is difficult to open a terminal file from a process to which Sandbox has been applied. As it could be a constraint applied to the container directory, a new ~/~$touch.terminal
was created in the home directory, but the result was similar.
No further investigation was carried out as both the constraints of the com.apple.quarantine attribute and the Sandbox applied process needed to be breached.
Login Items + .terminal
Next, an attempt was made to run the terminal file by adding it to the Login Items.
This time, as I was using Apfell's C2 session, I added Login Items using JavaScript for Automation. The easiest way to do this is to use Apfell's persist_loginitem_allusers function, as described in Madhav Bhatt's article.
I then used the launchctl command to force the user to log out, which opened the terminal file at login and confirmed that the specified OS command was executed on Terminal.app. Terminal.app is not affected by the sandbox applied to the Microsoft Word.app, so it was successfully escaped from the App Sandbox at this point.
Timeline
Date and Time | Remarks |
---|---|
2022/10/01 | Vulnerability reported |
2023/03/27 | macOS Ventura 13.3 releaseed with security patches |
2023/04/14 | Reward amount notification |
2023/05/01 | Vulnerability disclosed |
2023/05/24 | Reward payed |
2023/08/01 | Vulnerability information updated |