Bash PS4 xtrace Prompt Exploit

Bash’s execution-trace mode (set -x / SHELLOPTS=xtrace) prints each command before running it, prefixed by the PS4 prompt variable. PS4 is expanded like a prompt string — including command substitution — and in Bash versions before 4.4, a SUID/SGID program that invokes a shell helper evaluates that expansion with the elevated effective UID. That turns the debug prompt into a code-execution primitive inside a privileged process.

The exploit

env -i \
SHELLOPTS=xtrace \
PS4='$(cp /bin/bash /tmp/rootbash; chmod +xs /tmp/rootbash)' \
/path/to/suid/executable

Anatomy:

  • env -i starts with a clean environment so only the two attacker-controlled variables are present.
  • SHELLOPTS=xtrace forces trace mode in every Bash the target spawns. SHELLOPTS is honored from the environment on startup.
  • PS4='$(...)' — the payload. When the traced shell prints its first command, it expands PS4, runs the command substitution as the SUID program’s euid, and produces a root-owned SUID bash at /tmp/rootbash.
  • Run /tmp/rootbash -p for the root shell (-p prevents bash from dropping the effective UID — see suid-shell-executable).

Preconditions and scope

  • Bash < 4.4 only. The fix makes SUID/SGID shells sanitize PS4 (along with the other prompt variables) before expansion.
  • The SUID program must invoke a shell (via system(), popen(), or an exec of a script) — a statically-linked or pure-binary SUID target offers no shell to inject into.
  • The same expansion-in-privileged-context logic applied to SHELLOPTS+PS4 through sudo’s environment handling in historical sudo versions (e.g. sudo 1.6.8p9-era), where an env-preserved SHELLOPTS/PS4 pair escalated through any permitted Bash-invoking command.

Defenses

  • Patch Bash; modern distributions shipped the sanitization years ago.
  • SUID programs should never route through a shell — use absolute paths and exec* directly (the same lesson as suid-shell-executable).
  • The sibling technique — exporting a function named like an absolute path — is covered in bash-exported-function-backdoor and shares the “don’t let privileged programs trust the shell environment” root cause.

Sources

Related: bash-exported-function-backdoor, suid-shell-executable, ld-preload-trick, unix-permissions