How Apple Mitigates Vulnerabilities in Installer Scripts
Vulnerabilities are hot topics inside the world of security research and—because of their potentially dramatic impacts—outside as well. Unfortunately, the strategies and tactics that companies like Apple take to prevent specific vulnerabilities—or even entire families of exploits—typically attract less attention. But the fact is that engineering high-impact mitigations is typically more challenging than finding a single vulnerability.
In this post, we’ll look at Apple's recent efforts to mitigate an entire class of installer-script vulnerabilities. We will cover:
- Why Apple-signed installers are great targets for attackers;
- A high-level overview of such vulnerabilities from the past; and
- A deep-dive into how Apple attempts to mitigate these vulnerabilities with a new design in the PackageKit private framework.
Why System Installers Are Powerful
In macOS, two daemons handle the installation of package installers: installd
and system_installd
. The first is invoked when we install third-party packages, the second is for Apple-signed packages. We will focus on system_installd
, which is more frequently abused.
Inspecting its code signature will help us understand why it’s an interesting target. Running the command:
codesign -dv --entitlements - /System/Library/PrivateFrameworks/PackageKit.framework/Versions/A/Resources/system_installd
generates a long batch of output about that installer:
Executable=/System/Library/PrivateFrameworks/PackageKit.framework/Versions/A/Resources/system_installd
Identifier=com.apple.system_installd
Format=Mach-O universal (x86_64 arm64e)
CodeDirectory v=20400 size=754 flags=0x0(none) hashes=13+7 location=embedded
Platform identifier=15
Signature size=4523
Signed Time=Dec 21, 2023 at 04:09:49
(...)
One key in that output is of particular interest:
[Key]com.apple.rootless.install.heritable
[Value]
[Bool] true
The com.apple.rootless.install.heritable
entitlement is very strong. It not only gives the process access to areas on the system that are protected by System Integrity Protection (SIP), but its child processes inherit that permission, too. In the case of an installer, that means that any embedded installer scripts—typically bash or perl—will run with those special privileges as well.
If such a script has an exploitable vulnerability or if we can subvert the installation process in any way, attackers could therefore bypass SIP. Because of this, Apple-signed installers have historically been a target of abuse.
SIP-Bypass Vulnerabilities
There are two main approaches when targeting the package installation process.
The first method is to find a vulnerability in the PackageKit framework and subvert the installation process to run the attacker’s code; overwriting one of the scripts embedded in an installer package is a common technique. The other method relies on finding a logic bug in the installation script itself and exploiting that to bypass SIP.
Here are a few of the vulnerabilities that have taken these two approaches. (If you’re interested in the early history of SIP bypasses, check out Howard Oakley's Why Catalina has got a read-only system volume.)
CVE-2019-8561
This vulnerability was found by Jaron Bradley, who presented it at the Objective By The Sea v2 conference in his talk Bad Things in Small Packages. The vulnerability was really simple: It allowed an attacker to swap the installer package after the system verified its code signature; the system would thus install the supplied package instead of the original. As the original package was an Apple-signed binary, the system_installd
process would install it and thus, an attacker could bypass SIP. Apple’s fix was to improve the verification of packages.
CVE-2020–9854: Unauthd Chain
The unauthd
exploit chain was developed by Ilias Morad back in 2020 and is documented in his blog post. The chain exploited three vulnerabilities: One of these was a SIP bypass, which exploited a badly written installer script.
The script executed a binary after installation, which normally presents on macOS systems in a SIP-protected location. However, if we install the package on a mounted image or external drive, the installer could execute the binary from our supplied file system, and it would thus run with SIP-bypass privileges. Apple’s fix was to ensure that, if we install the package on a nonsystem volume, then installd
—which doesn't have that dangerous entitlement—is invoked instead of system_installd.
Mickey Jin's CVEs
In 2021, Mickey Jin started to explore the inner details of the PackageKit framework, which is used by system_installd
to manage package files. He discovered a number of vulnerabilities.
One such bug, as documented here, bypassed the fix for CVE-2019-8561. Mickey then focused on bypassing the fixes for the unauthd
patch; that research resulted in the discovery of three new bugs, which he duly documented.
Then there were CVE-2022-22583 and CVE-2022-32800. The first was also found by Ron Hass and documented here. The core idea was that the scripts were extracted to a location that was not protected by SIP; an attacker could then replace them with their own. The second vulnerability was a bypass of the fix for the first; Mickey wrote about both of them. He discussed some of the other vulnerabilities he’s uncovered in his Package Disaster talk at POC 2022. And he’s not finished yet: One recent macOS Sonoma 14.4 security advisory contains five PackageKit CVEs attributed to him.
CVE-2021-30892: Shrootless
Shrootless was found by Jonathan Bar Or and was detailed in his blog post. The idea here was, that if an installer script was run by zsh, the environment file /etc/zshenv
was read by the process; if bad actors put a script inside this file, it would be executed. Again, because the process was ultimately a child of system_installd
, it could be used to write to a SIP-protected location. Apple fixed this by restricting the use of zshenv
in case the process has SIP bypass privileges.
CVE-2023-23533
Credit for this vulnerability was shared by Mickey Jin, Koh M. Nakagawa, and Csaba Fitzl; Koh presented it in his Bypassing macOS Security & Privacy Mechanisms: From Gatekeeper to System Integrity Protection talk at Code Blue 2023. This issue was again related to an installer script bug: An insecure file copy in one of the post-install action scripts could be used to copy files into SIP-protected areas.
CVE-2023-42860: A Detailed Look
This vulnerability was explored by Mickey Jin and Csaba Fitzl. We are not aware of any public disclosures of it, so we wanted to take a deeper dive into it here, to explain the issue and how it could be exploited.
This vulnerability was present in the same script as the previous CVE-2023-23533, notably link_shared_support.bash, which can be found inside the OS install InstallAssistant.pkg packages.
#!/bin/bash
SHARED_SUPPORT_PATH="${3}Applications/Install macOS Ventura.app/Contents/SharedSupport"
/bin/mkdir -p "${SHARED_SUPPORT_PATH}"
/bin/chmod 0755 "${SHARED_SUPPORT_PATH}"
SOURCE_DEVICE=$(/usr/bin/stat -n -f '%d' "${PACKAGE_PATH}")
TARGET_DEVICE=$(/usr/bin/stat -n -f '%d' "${SHARED_SUPPORT_PATH}")
if [ ${SOURCE_DEVICE} -eq ${TARGET_DEVICE} ]; then
echo "Linking ${PACKAGE_PATH} into ${SHARED_SUPPORT_PATH}"
/bin/ln -fFh "${PACKAGE_PATH}" "${SHARED_SUPPORT_PATH}/SharedSupport.dmg"
/bin/chmod 0644 "${SHARED_SUPPORT_PATH}/SharedSupport.dmg"
/usr/sbin/chown -R root:wheel "${SHARED_SUPPORT_PATH}/SharedSupport.dmg"
else
echo "${PACKAGE_PATH} on different device than ${SHARED_SUPPORT_PATH} ... copying"
/bin/cp "${PACKAGE_PATH}" "${SHARED_SUPPORT_PATH}/SharedSupport.dmg"
fi
/usr/bin/chflags -h norestricted "${SHARED_SUPPORT_PATH}/SharedSupport.dmg"
As noted before, any command executed here will inherit the com.apple.rootless.install
entitlement. The following command is vulnerable to a race condition attack, and it allows an attacker to fully bypass SIP:
/bin/ln -fFh "${PACKAGE_PATH}" "${SHARED_SUPPORT_PATH}/SharedSupport.dmg"
When this command is run, it will create a hard link to the original package path (which is attacker-controlled); this is shown below in the logs, which we captured using ProcessMonitor.
{
"event":"ES_EVENT_TYPE_NOTIFY_EXEC",
"timestamp":"2022-11-16 12:26:57 +0000",
"process":{
"pid":14382,
"path":"/bin/ln",
"uid":0,
"arguments":[
"/bin/ln",
"- fFh",
"/Users/user/InstallAssistant_13.0_22A380.pkg",
"/Applications/Install macOS Ventura.app/Contents/SharedSupport/SharedSupport.dmg"
],
"ppid":14376,
"ancestors":[
14376
],
"signing info (reported)":{
"csFlags":578911001,
"platformBinary":1,
"signingID":"com.apple.link",
"teamID":"(null)",
"cdHash":"D1B679F3523D0EF514A58F7ACDB85A38EA4DC170",
},
"signing info (computed)":{
"signatureID":"com.apple.link",
"signatureStatus":0,
"signatureSigner":"Apple",
"signatureAuthorities":[
"Software Signing",
"Apple Code Signing Certification Authority",
"Apple Root CA"
]
}
}
}
Since the command runs as rootless, it can create a hard link even to a file that’s protected by SIP. If we replace the PKG file (namely, /Users/user/InstallAssistant_13.0_22A380.pkg
) with a symbolic link pointing to a file in a SIP-protected location, the hard link that’s created will point to the original SIP-protected path. Since SIP protection is based on file-system paths, once that hard link is created we can modify the protected file, as its path is no longer protected by SIP.
There was an extra twist in this vulnerability, due to the very last line of the installer script:
/usr/bin/chflags -h norestricted "${SHARED_SUPPORT_PATH}/SharedSupport.dmg"
This command made the file unrestricted (i.e., no SIP protection), which meant that we could modify the file directly; we didn't even need the hard link.
Apple's New Mitigations
Now that we’ve reviewed some past system installer exploits, we can look at one of the new mitigations that Apple has introduced, which should universally address some of these vulnerabilities.
The problem with vulnerabilities inside the packages, rather than in the installer framework itself, is that even if we fix the package, an attacker can still use an old version of it. This is possible because the signature of the package remains valid, and thus can be executed on any system. Because of this, mitigating such vulnerabilities is often not easy.
Similar problems existed with regular Apple-signed apps that had powerful entitlements. Because their code signature was valid, those apps could be reused on new versions of the OS, and attackers could exploit them to gain access to their entitlements. This was solved with the use of the trust cache on Apple Silicon along with Launch and Environment Constraints.
To address the problems with installer scripts, Apple introduced a new mitigation into PackageKit, which should prevent such vulnerabilities and allow developers to easily patch such bugs at the OS level. The mitigation consists of two parts: Install Script Actions and Install Script Mutations.
The Install Script Action is configured in the InstallScriptActions.plist
(located in/System/Library/PrivateFrameworks/PackageKit.framework/Resources/
). Among its entries:
<dict>
<key>ScriptTypes</key>
<array>
<string>postinstall</string>
</array>
<key>RelativePath</key>
<string>postinstall_actions/link_shared_support.bash</string>
<key>DropSIP</key>
<true/>
<key>PerformMutation</key>
<string>LinkSharedSupport</string>
<key>ComponentPackageIdentifiersRegex</key>
<array>
<string>^com\.apple\.pkg\.InstallAssistant\S*$</string>
</array>
</dict>
This section defines what the system should do if a specific script is found in the package. Let’s review its keys from the bottom up.
ComponentPackageIdentifiersRegex
identifies the package. In the case above, it is the ID of all InstallAssistant* installers, for which we showed the exploit above.PerformMutation
specifies the name of the script mutation that should happen. We will discuss this in more detail in a minute.DropSIP
specifies whether theCS_INSTALLER
code-signing flag should be dropped before running the script. This flag essentially translates to thecom.apple.rootless.install
entitlement. (This is unique, as entitlements don't typically map to code-signing bit flags). If this is dropped, the installer would no longer be able to bypass SIP—an important step, given recent vulnerabilities.RelativePath
refers to the actual script within the package—again, in our previous example, this is the vulnerablelink_shared_support.bash
.- Finally,
ScriptTypes
refers to the type of script, such as pre- or post-install or pre- or post-flight.
Essentially what we have here is a property list, embedded in the OS, that enforces special treatment for some Apple installers. Most of the cases are about dropping SIP, so attackers can't use these installers for exploitation, even if they possess a vulnerable one.
Apple also introduced Install Script Mutations. This allows PackageKit to replace the contents of predefined scripts. The associated regular expressions of what to replace can be found in the file InstallScriptMutations.plist
(in /System/Library/PrivateFrameworks/PackageKit.framework/Resources/
):
<?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>LinkSharedSupport</key>
<dict>
<key>PreReplacementCaptures</key>
<array>
<string>^SHARED_SUPPORT_PATH\=.*$</string>
</array>
<key>ReplacementContent</key>
<string>#!/bin/bash
SHARED_SUPPORT_PATH="${3}Applications/%%%IA_NAME%%%/Contents/SharedSupport"
/bin/mkdir -m 755 -p "${SHARED_SUPPORT_PATH}"
echo "Copying ${PACKAGE_PATH} into ${SHARED_SUPPORT_PATH}"
/bin/cp -fc "${PACKAGE_PATH}" "${SHARED_SUPPORT_PATH}/SharedSupport.dmg" ||
/bin/cp -f "${PACKAGE_PATH}" "${SHARED_SUPPORT_PATH}/SharedSupport.dmg"</string>
<key>PostReplacementChanges</key>
<array>
<dict>
<key>RegexMatch</key>
<string>^SHARED_SUPPORT_PATH\=.*$</string>
<key>PreMutationCaptureIndex</key>
<integer>0</integer>
</dict>
</array>
</dict>
<key>LinkPackage</key>
<dict>
<key>PreReplacementCaptures</key>
<array>
<string>^SHARED_SUPPORT_PATH\=.*$</string>
</array>
<key>ReplacementContent</key>
<string>#!/bin/bash
SHARED_SUPPORT_PATH="${3}Applications/%%%IA_NAME%%%/Contents/SharedSupport"
/bin/mkdir -m 755 -p "${SHARED_SUPPORT_PATH}"
/bin/chmod 0755 "${SHARED_SUPPORT_PATH}"
echo "Copying ${PACKAGE_PATH} into ${SHARED_SUPPORT_PATH}"
/bin/cp -fc "${PACKAGE_PATH}" "${SHARED_SUPPORT_PATH}/InstallESD.dmg" ||
/bin/cp -f "${PACKAGE_PATH}" "${SHARED_SUPPORT_PATH}/InstallESD.dmg"</string>
<key>PostReplacementChanges</key>
<array>
<dict>
<key>RegexMatch</key>
<string>^SHARED_SUPPORT_PATH\=.*$</string>
<key>PreMutationCaptureIndex</key>
<integer>0</integer>
</dict>
</array>
</dict>
</dict>
</plist>
This property list contains two entries: LinkSharedSupport
and LinkPackage
. The first of these keys is referenced from the previously discussed InstallScriptActions.plist
and the entry we checked earlier referred to LinkSharedSupport
. Both entries contain essentially the same data.
The most interesting part is the data under ReplacementContent
and RegexMatch
. Essentially, the first is the new content and the second is the regex is used for matching. Without delving too deeply into the regular expression black magic, this will essentially transform the original link_shared_support.bash
script:
#!/bin/bash
SHARED_SUPPORT_PATH="${3}Applications/Install macOS Ventura.app/Contents/SharedSupport"
/bin/mkdir -p "${SHARED_SUPPORT_PATH}"
/bin/chmod 0755 "${SHARED_SUPPORT_PATH}"
SOURCE_DEVICE=$(/usr/bin/stat -n -f '%d' "${PACKAGE_PATH}")
TARGET_DEVICE=$(/usr/bin/stat -n -f '%d' "${SHARED_SUPPORT_PATH}")
if [ ${SOURCE_DEVICE} -eq ${TARGET_DEVICE} ]; then
echo "Linking ${PACKAGE_PATH} into ${SHARED_SUPPORT_PATH}"
/bin/ln -fFh "${PACKAGE_PATH}" "${SHARED_SUPPORT_PATH}/SharedSupport.dmg"
/bin/chmod 0644 "${SHARED_SUPPORT_PATH}/SharedSupport.dmg"
/usr/sbin/chown -R root:wheel "${SHARED_SUPPORT_PATH}/SharedSupport.dmg"
else
echo "${PACKAGE_PATH} on different device than ${SHARED_SUPPORT_PATH} ... copying"
/bin/cp "${PACKAGE_PATH}" "${SHARED_SUPPORT_PATH}/SharedSupport.dmg"
fi
/usr/bin/chflags -h norestricted "${SHARED_SUPPORT_PATH}/SharedSupport.dmg"
into:
#!/bin/bash
SHARED_SUPPORT_PATH="${3}Applications/Install macOS 14 beta.app/Contents/SharedSupport"
/bin/mkdir -m 755 -p "${SHARED_SUPPORT_PATH}"
echo "Copying ${PACKAGE_PATH} into ${SHARED_SUPPORT_PATH}"
/bin/cp -fc "${PACKAGE_PATH}" "${SHARED_SUPPORT_PATH}/SharedSupport.dmg" ||
/bin/cp -f "${PACKAGE_PATH}" "${SHARED_SUPPORT_PATH}/SharedSupport.dmg"%
The resulting installer will not only drop SIP for the installation, but remove the two vulnerable code entries from the script before execution.
Conclusion
After years of people exploiting installer scripts to bypass SIP, Apple has introduced an easily extendable configuration in PackageKit that allows them to modify the content of script files before they’re executed and also to specify whether SIP should be disabled or not.
This is a good approach, as the mitigation sits inside the operating system, and future vulnerabilities can be easily patched by adding entries to the configuration files.
This approach does have one drawback: If a new installer comes out or an old one exists that was not considered before, attackers can again exploit them for SIP bypasses. But if Apple wants to allow these scripts to write to SIP-protected locations, this is likely one of the best solutions they can come up with, without causing legitimate installations to fail because of a general rule disallowing all scripts.
About Kandji
Kandji is the Apple device management and security platform that empowers secure and productive global work. With Kandji, Apple devices transform themselves into enterprise-ready endpoints, with all the right apps, settings, and security systems in place. Through advanced automation and thoughtful experiences, we’re bringing much-needed harmony to the way IT, InfoSec, and Apple device users work today and tomorrow.
See Kandji in Action
Experience Apple device management and security that actually gives you back your time.
See Kandji in Action
Experience Apple device management and security that actually gives you back your time.