Escalating from a low-privileged user (or unprivileged process) to root on macOS during authorized engagements by abusing the user-preserved sudo PATH, Dock/app masquerading, sudo-password phishing, AuthorizationExecuteWithPrivileges helpers, vulnerable privileged XPC/LaunchDaemon helpers, writable LaunchDaemon plists, PackageKit/zsh logic bombs, kernel credential races, and Time Machine snapshot mounts.
Escalating from a low-privileged user (or unprivileged process) to root on macOS during authorized engagements by abusing the user-preserved sudo PATH, Dock/app masquerading, sudo-password phishing, AuthorizationExecuteWithPrivileges helpers, vulnerable privileged XPC/LaunchDaemon helpers, writable LaunchDaemon plists, PackageKit/zsh logic bombs, kernel credential races, and Time Machine snapshot mounts.
During authorized macOS assessments after gaining initial code execution as a standard user
When a privileged helper, LaunchDaemon, or third-party updater is present and may expose a root-reachable interface
When you can social-engineer a desktop user (Dock impersonation, fake password prompts) for a sudo-capable credential
When evaluating SIP-bypass chains for persistence in protected system locations
Note that most Linux/Unix privesc tricks (sudo misconfig, writable PATH binaries) also apply to macOS
Critical: Techniques Most Often Missed
1. sudo keeps the user PATH (Homebrew binary hijack)
Unlike Linux, macOS preserves the user's PATH across sudo. If /opt/homebrew/bin (Homebrew, almost always present on developer Macs) precedes system paths, you can plant a trojaned ls, git, etc. and wait for the victim to run sudo <binary>.
How to CONFIRM: run echo $PATH and verify a user-writable dir precedes /usr/bin; check sudo env | grep PATH retains it; confirm the planted binary fires as root (/tmp/privesc contains root).
The dominant third-party privesc class: a root LaunchDaemon exposes a Mach/XPC service from /Library/PrivilegedHelperTools that fails to validate the client, validates too late (PID race), or runs a user-controlled path/script.
ls -l /Library/PrivilegedHelperTools /Library/LaunchDaemons
plutil -p /Library/LaunchDaemons/*.plist 2>/dev/null | rg 'MachServices|Program|ProgramArguments|Label'for f in /Library/PrivilegedHelperTools/*;
codesign -dvv --entitlements :- 2>&1 | rg
strings | rg
How to CONFIRM: the helper accepts requests after uninstall (job still loaded), reads scripts/config from non-root-writable /Applications/..., or relies on PID/bundle-id-only peer validation (raceable).
If a LaunchDaemon plist or its ProgramArguments target is user-writable, swap it and force launchd to reload.
sudo launchctl bootout system /Library/LaunchDaemons/com.apple.securemonitor.plist
cp /tmp/root.sh /Library/PrivilegedHelperTools/securemonitor && chmod 755 /Library/PrivilegedHelperTools/securemonitor
# rewrite the plist ProgramArguments to your binary, then:sudo launchctl bootstrap system /Library/LaunchDaemons/com.apple.securemonitor.plist
How to CONFIRM:ls -l the plist and target binary for group/other write or user ownership; after reload, your payload runs as uid 0.
Workflow
Step 1: Enumerate the user, sudo rights, and PATH
whoami; id; groupssudo -l 2>/dev/null # cached/allowed sudo commandsecho$PATH# look for writable dirs before /usr/binls -ld /opt/homebrew/bin /usr/local/bin # writable Homebrew paths
dscl . -read /Groups/admin GroupMembership # who is admin
Step 2: Hunt privileged helpers and writable autostart targets
ls -l@ /Library/LaunchDaemons /Library/LaunchAgents
ls -l /Library/PrivilegedHelperTools
# find world/group-writable plists or targets owned by non-root
find /Library/LaunchDaemons -perm -0002 -o -perm -0020 2>/dev/null
log stream --info --predicate 'eventMessage CONTAINS "security_authtrampoline"'# catch helper calls
Step 3: Social-engineering / credential capture (when a GUI user is present)
AuthorizationExecuteWithPrivileges is deprecated but still works on Sonoma/Sequoia. Many updaters invoke /usr/libexec/security_authtrampoline with an untrusted path — plant a payload at a user-writable helper and ride the legitimate root prompt. You can also masquerade as Chrome/Finder in the Dock or loop a password prompt validated with dscl:
Dock impersonation: copy a real app's .icns, build a fake Google Chrome.app/Finder.app, then defaults write com.apple.dock persistent-apps -array-add '<...>' && killall Dock.
Step 4: Abuse installer / package logic bombs
Pre-fix (Sonoma 14.5 / Ventura 13.6.7 / Monterey 12.7.5) Installer.app/PackageKit ran PKG scripts as root in the user's environment — a #!/bin/zsh installer sources the attacker's ~/.zshenv as root (CVE-2024-27822 family).
Step 5: Kernel and SIP-class escalation (advanced)
kauth credential race (CVE-2025-24118): race setgid()/getgid() loops across threads to tear proc_ro.p_ucred → uid 0 and kernel memory access.
NSPredicate XPC abuse: Apple daemons (coreduetd, contextstored) that deserialize predicates and only validate expressionType can be driven to execute arbitrary selectors as root.
Migraine (CVE-2023-32369): with root, abuse systemmigrationd's com.apple.rootless.install.heritable to patch SIP-protected paths.
Step 6: Read all files via Time Machine snapshot
Any user can mount a local snapshot noowners and read every file (the app, e.g. Terminal, needs Full Disk Access).
Expand PKG installers to inspect pre/postinstall scripts
log stream/show
Observe security_authtrampoline and daemon activity
tmutil / mount_apfs
Create and mount Time Machine snapshots for full file read
Common Scenarios
Scenario 1: Homebrew PATH hijack
A developer's $PATH lists /opt/homebrew/bin first. Planting a trojan git there runs as root the next time the user types sudo git ....
Scenario 2: Vulnerable VPN/updater helper
A VPN client ships a root LaunchDaemon whose XPC method runs a script from a user-writable /Applications/... path. Replacing the script yields root code execution without a prompt.
Scenario 3: Writable LaunchDaemon plist
A misconfigured third-party daemon plist is group-writable. Rewriting ProgramArguments and reloading with launchctl bootstrap system executes the payload as root on reboot.
Scenario 4: zsh installer logic bomb
The attacker drops id > /tmp/pkg-root into ~/.zshenv and waits; when the user installs any vulnerable zsh-based PKG, the line runs as root.
Output Format
## macOS Privilege Escalation Finding
**Vulnerability**: Local Privilege Escalation to root
**Severity**: High (CVSS 7.8)
**Technique**: Writable privileged helper / sudo PATH hijack / PackageKit logic bomb
**Host**: macHost.local (macOS 14.4, SIP enabled)
### Reproduction Steps
1. As user `staff`, enumerate helpers: ls -l /Library/PrivilegedHelperTools
2. Identify com.vendor.helper running script from user-writable /Applications/Vendor/run.sh
3. Replace run.sh payload; trigger helper via its XPC client
4. Confirm: id returns uid=0(root)
### Evidence
| Artifact | Detail |
|----------|--------|
| Helper | /Library/PrivilegedHelperTools/com.vendor.helper |
| Trigger | XPC method runHelperScript: |
| Proof | /tmp/privesc contains "root" |
### Recommendation
1. Validate XPC peers by audit token + code-signing requirement, not PID/bundle-id
2. Store helper scripts in root-owned, non-user-writable locations
3. Set LaunchDaemon plists and targets to root:wheel 644/755
4. Sign installers and avoid zsh/bash post-install scripts; patch to fixed macOS builds
5. Restrict admin group membership and audit Homebrew PATH ordering