Skip to content

feat: detect SSHole exposure, ban password logins to key-only accounts, and say what defends the box until the upgrade - #21

Merged
vsilent merged 3 commits into
mainfrom
feat/sshole-detection
Nov 9, 2026
Merged

vsilent merged 3 commits into
mainfrom
feat/sshole-detection

Conversation

@vsilent

@vsilent vsilent commented Nov 9, 2026 •

Copy link
Copy Markdown
  • What SSHole is: CVE-2026-123158, an OpenSSH authentication bypass on musl with PAM disabled (Alpine's default sshd). A 257-1024 byte password fails the hash step, the failure reads as the same * the account stores (root, alpine), and the login succeeds. Fixed in OpenSSH 10.6p2. First exploits were seen within nine hours of disclosure.
  • Detection (f76990b):
    • vuln.sshole: flags openssh-server older than 10.6p2 in apk package databases (host + containers). apk is the musl half of the vulnerable configuration; the PAM flavor (openssh-server-pam) and glibc distros are not affected and not reported. Alert only.
    • ssh.password-login: password logins to key-only accounts (root, alpine default, STACKDOG_SSHOLE_KEYONLY_USERS to extend) — the line Alpine's incident guidance says to hunt. STACKDOG_SSHOLE_ENABLED=false turns both off (default: on).
  • Immediate ban (a2d07df): one of these logins is already a foothold; waiting for the five-offense threshold gives the intruder time to switch the monitoring off. OffenseInput gains an immediate flag and ssh.password-login sets it: the first line bans. The gate still runs first; allowlisted and protected addresses are never touched.
  • Slack-ready alerts (a2d07df): exposure alerts now name the interim defense, so the notification is actionable on arrival — vuln.sshole names the first-line ban, vuln.php2shell names the out-of-memory bans, the webshell scan and session.upload_progress.enabled = 0.
  • Tests: end-to-end attack simulation on captured fixtures (Alpine 3.20's openssh 9.7_p1-r6 apk database and a real sshd login line from a lab container): exposure finding -> log findings -> active block. One-line-ban test with an allowlist control. Version parsing for 10.6_p2-r0 / OpenSSH_10.6p2 / 1:9.2p1-2+deb12u5, PAM/glibc exemption, detector positives and negatives. 469+8+108 pass, clippy -D warnings and fmt clean.
  • Article: /sshole-check — "Am I affected by SSHole?" with an in-browser check (ssh -V + platform conditions), what to do now, how StackdogApp checks it, honest limits (including the immediate-ban allowlist warning). Website build 15/15.

…unts

SSHole (CVE-2026-123158) is an OpenSSH authentication bypass on musl
with PAM disabled, Alpine's default server included: a 257-1024 byte
password makes the hash attempt fail, the failure reads as the same '*'
the account stores, and the login succeeds. Fixed in OpenSSH 10.6p2.

- vuln.sshole flags openssh-server builds older than 10.6p2 in the apk
  package database, on the host and in running containers: apk is the
  musl half of the vulnerable configuration. The PAM flavor and glibc
  distributions are not affected and are not reported. Alert only: a
  version names no attacker.
- ssh.password-login alerts on password logins to key-only accounts
  (root, alpine by default; STACKDOG_SSHOLE_KEYONLY_USERS extends the
  list) and feeds the ban engine: Alpine's own incident guidance is to
  hunt exactly these lines. One offense per line, repeats numbered so
  the registry counts them.
- STACKDOG_SSHOLE_ENABLED=false turns both off; the version check reads
  package databases through the same host-root mount as the PHP checks.

Docs: README subsection, CHANGELOG, .env.sample, and an
/am-i-affected style page at /sshole-check with an in-browser version
check (ssh -V plus the platform conditions), wired into sitemap and
footer.

Tests: 468+8+108 pass, clippy -D warnings and fmt clean, website build
15/15.
One password login to a key-only account is already a foothold: waiting
for the five-offense threshold hands the intruder time to switch the
monitoring off. OffenseInput gains an immediate flag, and
ssh.password-login sets it, so the first line bans the address. The gate
still runs first: allowlisted and protected addresses are never touched.

The exposure alerts now say what defends the box until the upgrade, so
the Slack message is actionable on arrival: vuln.sshole names the
first-line ban on password logins to key-only accounts, vuln.php2shell
names the out-of-memory bans, the webshell scan and session.upload_
progress.enabled = 0.

Tests: an end-to-end attack simulation on captured fixtures (Alpine
3.20's openssh 9.7_p1-r6 apk database and a real sshd login line from a
lab container) walks exposure finding, log findings and the ban to an
active block; a new test proves one line bans and the allowlist still
holds. 469+8+108 pass, clippy -D warnings and fmt clean, website 15/15.
@vsilent vsilent changed the title feat: detect SSHole exposure and ban password logins to key-only accounts feat: detect SSHole exposure, ban password logins to key-only accounts, and say what defends the box until the upgrade Nov 9, 2026
The two SSHole ban tests unwrapped apply_ip_ban, which panics on a Linux
runner without root: the nftables backend fails with 'Operation not
permitted' before the block is recorded. Mirror the existing pattern in
test_apply_ip_ban_blocks_and_emits_alert_after_repeated_anomalies: on
Linux without root, accept that error and skip the block assertions; the
detection assertions still run everywhere. macOS keeps asserting the
ban end to end.
@vsilent
vsilent merged commit 677368f into main Nov 9, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants