CVE-2024-27822: macOS PackageKit Privilege Escalation
Another fun exploit! This time with local privilege escalation through Apple’s PackageKit.framework
when running ZSH-based PKGs 🎉.
- Resolved versions:
- macOS 14.5 Beta 2 (23F5059e) and newer
- macOS 13.6.7 (22G720) and newer
- macOS 12.7.5 (21H1222) and newer
- Affected versions:
- macOS 14.5 Beta 1 (23F5049f) and older
- macOS 13.6.6 (22G630) and older
- macOS 12.7.4 (21H1123) and older
- Any version of macOS 11 or older
- Proof of Concept:
- Source Code: pkg_exploit.py
- CVE Associated: CVE-2024-27822
- Compensation: None
- Terminology Used:
- PKG: macOS package file (
.pkg
, contains install scripts, and files to install on the system.) - Shebang:
#!
at the start of a script to specify the path to the interpreter to use. - ZSH: The default shell interpreter in macOS (others include sh and bash)
- MDM: Mobile Device Management.
- Munki: An open source macOS software deployment tool.
- PKG: macOS package file (
- Vulnerability Details
- Proof of Concept
- Vulnerability Discovery
- Reverse Engineering PackageKit: Apple’s Fix
- Developer Recommendations
- Conclusion
Vulnerability Details
To put it simply: Apple’s Installer.app
/PackageKit.framework
spawns installation scripts embedded in PKGs as root in the current user’s enviroment. This results in the shebangs having their own special logic kick in, such as #!/bin/zsh
loading the user’s .zshenv
file while running with root permissions.
This allows for a malicious payload to be inserted into the .zshenv
file, which would then be executed as root when a ZSH-based PKG was installed by the user.
- Note: While the majority of PKGs do run as root, some PKGs may run as the current user, such as WebEx.
This vulnerability could primarily be targeted at user-initiated PKG installations. MDM (Mobile Device Management) and Munki-based installations are not affected by this vulnerability, as they are spawned under the root user’s context.
The main attack scenario with this vulnerability is that a logic bomb-based payload could simply wait for the user to install a ZSH-based PKG at any time, and then execute the payload and gain root access.
- Fun fact: This vulnerability looks to be an extension of CVE-2021-30892: Shrootless which abused the enviroment file in a similar manner.
Proof of Concept
To demonstrate this vulnerability, all you need is a PKG with an installation script that has the #!/bin/zsh
shebang. On script execution (ie. PKG installation), the script will load the user’s .zshenv
file, which could have malicious code embedded in it.
Steps to Reproduce:
- Inject a malicious payload into the
.zshenv
file:
/usr/bin/osascript -e "display dialog \"Hello from malware!
Current user: $(/usr/bin/whoami)
UID: $UID
EUID: $EUID\""
-
Install a PKG with the
#!/bin/zsh
shebang. Ex. Generic-ZSH.pkg -
Watch the magic happen!
An automated PoC is available in pkg_exploit.py, which will create a dummy PKG and inject a payload into the .zshenv
file.
Vulnerability Discovery
On March 14th, 2024, I had noticed a new CVE pop up on the MacAdmins Slack: CVE-2024-27301
As discussed earlier, this vulnerability is based on the #!/bin/zsh
shebang in a PKG’s scripts.
When learning about this, I had assumed this is an inherent issue with using shells as shebangs, and so developers need to be more cautious about how they’re invoked (ie. ensure untrusted settings are never loaded).
Internal Audit
After reviewing the scripts used in our internal PKGs, we appended the --no-rcs
parameter to the ZSH shebang. This prevents the scripts from loading the current user’s environment file, thus preventing the vulnerability.
We then reviewed each of our vendors’ PKGs. One of them, Watchman Monitoring, had installers which were vulnerable to this exploit. We reported this to the vendor, and Allen Hancock/Watchman Monitoring suggested that we to report this to Apple while they worked on the issue. We reported to Apple on the same day, March 14th, and Apple assigned it OE1972568773511
.
- Watchman Monitoring published revised installers on April 5th in version 7.1.3.101.
Surprise macOS 14.5 release
On May 15th I got a message congratulating me on a CVE? I was so confused, but checked the macOS 14.5 security notes and there I was:
PackageKit
Available for: macOS Sonoma
Impact: An app may be able to gain root privileges
Description: A logic issue was addressed with improved restrictions.
CVE-2024-27822: Scott Johnson, Mykola Grymalyuk of RIPEDA Consulting, Jordy Witteman, and Carlos Polop
While I had no idea other researchers reported this vulnerability, I feel a bit odd being credited when in reality I only noticed another CVE and had a vendor request a report to Apple. Additionally since the initial report in March, I had not heard back from Apple on the status of the report.
However, I’ll take what I can get, and genuinely appreciate Apple crediting me instead of filing “duplicate” and moving on.
Timeline
Sender | Topic | Date |
---|---|---|
RIPEDA | Discovery of ZSH-based PKG vulnerability. | March 14th, 2024 |
RIPEDA | Initial report to Watchman Monitoring. | March 14th, 2024 |
Watchman | Triaged, recommend reporting to Apple. | March 14th, 2024 |
RIPEDA | Initial report to Apple. | March 14th, 2024 |
Apple | Assigned OE1972568773511 . |
March 14th, 2024 |
Watchman | Revised shebangs. | April 5th, 2024 |
Apple | macOS 14.5 Beta 2 seeded to developers. | April 16th, 2024 |
Apple | macOS 14.5 released to the public. | May 13th, 2024 |
Apple | Approval for public disclosure. | May 28th, 2024 |
Reverse Engineering PackageKit: Apple’s Fix
Now here comes the fun part: How did Apple fix this vulnerability?
To start, let’s load up PackageKit
in Hopper:
/System/Library/PrivateFrameworks/PackageKit.framework/Versions/A/PackageKit
The first thing we’ll notice is that Apple added a new environment variable to PackageKit
: APPLE_PKGKIT_ESCALATING_ROOT
. This is used to determine if the package will be running as root or not, and is currently only referenced in two functions:
-[PKInstallDaemon startListeningForConnectionsToService:]
-[PKRunPackageScriptInstallOperation _scriptTaskEnvironmentForPackage:destination:scriptName:]
1. startListeningForConnectionsToService
The first function is the main listener for the XPC service, and checks if the service is com.apple.installd.user
. If it is not, it will attempt to set the APPLE_PKGKIT_ESCALATING_ROOT
environment variable to 1
:
-(void)startListeningForConnectionsToService:(NSString *)process {
/// ...
if ([process isEqualToString:@"com.apple.installd.user"] == 0x0) {
setenv("APPLE_PKGKIT_ESCALATING_ROOT", "1", 1);
}
/// ...
}
2. _scriptTaskEnvironmentForPackage
The second function simply passes the environment variable to the script’s environment, for end use:
-(int)_scriptTaskEnvironmentForPackage:(int)arg2 destination:(int)arg3 scriptName:(int)arg4 {
/// ...
char *rootEnv = getenv("APPLE_PKGKIT_ESCALATING_ROOT");
if (rootEnv != NULL) {
NSString *rootEnvStr = [NSString stringWithUTF8String:rootEnv];
[environment setObject:rootEnvStr forKey:@"APPLE_PKGKIT_ESCALATING_ROOT"];
}
/// ...
}
Wait, this doesn’t do anything…
Correct! The actual fix is inside of /bin/zsh
.
3. /bin/zsh
If we load this binary up in Hopper, we notice that _run_init_scripts()
now checks for the environment variable APPLE_PKGKIT_ESCALATING_ROOT
. If it is set, it will skip loading the user’s .zshenv
file:
int _run_init_scripts(int arg0, int arg1) {
/// ...
if (getenv("APPLE_PKGKIT_ESCALATING_ROOT") == 0x0) {
_sourcehome(".zshenv");
}
/// ...
}
Yup, the fix is as simple as that!
4. /bin/bash (Bonus!)
/bin/bash
wasn’t left out either! A similar check was added in the binary’s EntryPoint:
Developer Recommendations
Something for developers to keep in mind is that they should still ensure their working environment in scripts is clean and not loading untrusted settings. This is especially important for scripts that run as root, like in PKGs, as we can’t always rely on the operating system to protect us.
Ways to prevent loading untrusted settings:
- SH:
#!/bin/sh
(sh
in macOS won’t load any external settings without being explicitly told) - ZSH:
#!/bin/zsh --no-rcs
- Bash:
#!/bin/bash --noprofile --norc
Additionally, always specify the full path to binaries in scripts when possible, as PATH
may not always be trusted and could point to a malicious binary.
- ex.
/bin/mkdir
instead ofmkdir
- Use
where <command>
to find the full path to a command - ex.
where osascript
will return/usr/bin/osascript
- Use
Poor script:
function makeDirectory() {
mkdir $1
}
Better script:
function makeDirectory() {
/bin/mkdir $1
}
Would highly recommend reading Apple’s old documentation on shell scripting guidelines for more information:
Conclusion
A simple exploit, but really interesting to see how Apple mitigated it through PackageKit/ZSH and Bash. This does however pose a concern for packages that don’t use the standard shell shebangs, such as !#/usr/bin/perl
, as they could still load untrusted settings if written poorly.
Otherwise for those wanting more PKG-based vulnerabilities, highly recommend reading the following:
- Jonathan Bar Or’s CVE-2021-30892: Shrootless.
- Nearly identical exploit, but at least there they got a System Integrity Bypass 😛.
- Csaba Fitzl’s How Apple Mitigates Vulnerabilities in Installer Scripts.
And thank you again to Allen Hancock / Watchman Monitoring for getting us to report this to Apple 🎉