# checker-ssh Deep SSH security checker for [happyDomain](https://www.happydomain.org/). Given an `abstract.Server` service (A / AAAA / SSHFP records), the checker connects to the advertised SSH port(s) and produces a comprehensive audit: reachability, banner-to-CVE matches, full algorithm posture (KEX / host-key / cipher / MAC / compression), observed host keys, SSHFP fingerprint validation, and authentication method exposure. ## What it checks ### Reachability - TCP connect to port 22 (and optionally extra ports) on every A/AAAA address of the service. - SSH-2.0 protocol banner read. ### Banner / CVE The banner is parsed into an `OpenSSH_X.Ypz` tuple and matched against a bundled subset of the [ssh-audit](https://github.com/jtesta/ssh-audit) vulnerability database, including: | CVE | Issue | | --- | --- | | CVE-2024-6387 | regreSSHion - unauth RCE as root (8.5p1 <= v < 9.8p1) | | CVE-2023-38408 | ssh-agent PKCS#11 RCE (5.5 ≤ v < 9.3p2) | | CVE-2023-48795 | Terrapin prefix-truncation (v < 9.6p1) | | CVE-2021-41617 | AuthorizedKeysCommand privdrop (6.2 ≤ v < 8.8p1) | | CVE-2020-15778 | scp command injection (v < 8.4p1) | | CVE-2018-15473 | username enumeration (v < 7.8p1) | ### Algorithm posture A raw `SSH_MSG_KEXINIT` is exchanged with the server to enumerate every algorithm it advertises. Each entry is graded against a curated table inspired by ssh-audit: - **crit**: `diffie-hellman-group1-sha1` (Logjam), `3des-cbc` (Sweet32), `arcfour*`, `hmac-md5*`, `hmac-sha1-96`, `ssh-dss`, `none`. - **warn**: `ssh-rsa` (RFC 8332 deprecated), `diffie-hellman-group14-sha1`, AES-CBC, non-ETM MACs, `hmac-sha1`, missing strict-KEX marker (CVE-2023-48795 mitigation). - **ok**: `curve25519-sha256`, `sntrup761x25519-sha512@openssh.com`, `mlkem768x25519-sha256`, `ssh-ed25519`, AES-GCM/ChaCha20-Poly1305, SHA-2 ETM MACs. ### SSHFP validation For each observed host key, the checker: - computes SHA-1 and SHA-256 fingerprints, - matches them against the `abstract.Server.SSHFP` records declared in the zone, - flags `sshfp_missing`, `sshfp_not_covered`, `sshfp_only_sha1` or `sshfp_mismatch` as appropriate, with copy-pasteable fix snippets. ### Authentication methods A second connection is opened with a dummy user and no credentials. The server replies with the auth-method list, which is surfaced as `password`, `publickey`, `keyboard-interactive` chips. Password authentication triggers a `password_auth_enabled` warning. ## HTML report The iframe report is structured for "fix me fast": 1. **Overall status** banner + SSHFP verdict chips. 2. **What to fix**: top issues (crit -> warn), each with a copy-pasteable remediation snippet (sshd_config lines, SSHFP DNS records, ssh-keygen invocations). 3. **SSHFP table** with per-record match status. 4. **Per-endpoint details**: expandable sections with host-key fingerprints, algorithm tables (broken entries highlighted), and advertised auth methods. ## Usage ### Standalone HTTP server ```bash make ./checker-ssh -listen :8080 ``` Endpoints: - `GET /health` - `GET /definition` - `POST /collect` - `POST /evaluate` - `POST /report` ### Docker ```bash make docker docker run -p 8080:8080 happydomain/checker-ssh ``` ### happyDomain plugin ```bash make plugin # produces checker-ssh.so, loadable as a Go plugin ``` ## Options | Option | Type | Default | Description | | --- | --- | --- | --- | | `ports` | string | `""` | Comma-separated extra ports (port 22 is always probed). | | `probeTimeoutMs` | number | `10000` | Per-endpoint dial + handshake timeout. | | `includeAuthProbe` | bool | `true` | Open a second connection to enumerate auth methods. | ## Rules | Code | Description | Severity | |-------------------------------|---------------------------------------------------------------------------------------------------|---------------------| | `ssh.tcp_reachable` | Verifies that every probed (address, port) pair accepts a TCP connection. | CRITICAL | | `ssh.handshake` | Verifies that the SSH banner exchange and KEXINIT parse succeed on every reachable endpoint. | CRITICAL | | `ssh.protocol_version` | Verifies every endpoint advertises SSH-2 and rejects the legacy SSH-1 protocol. | CRITICAL | | `ssh.banner_software` | Flags servers whose banner is not a recognised OpenSSH build. | INFO | | `ssh.known_vulnerabilities` | Matches the advertised OpenSSH version against a curated catalog of remotely-observable CVEs. | CRITICAL | | `ssh.host_key_strength` | Flags SSH host keys whose size is below the currently accepted minimum (e.g. RSA < 2048 bits). | CRITICAL | | `ssh.kex_algorithms` | Flags key-exchange algorithms advertised by the server that are weak or broken. | CRITICAL | | `ssh.host_key_algorithms` | Flags server host-key algorithms that are weak or deprecated (ssh-rsa/SHA-1, ssh-dss, ...). | CRITICAL | | `ssh.cipher_algorithms` | Flags symmetric ciphers advertised by the server that are weak or broken (CBC, 3DES, RC4, ...). | CRITICAL | | `ssh.mac_algorithms` | Flags MAC algorithms advertised by the server that are weak (SHA-1, non-ETM, ...). | CRITICAL | | `ssh.strict_kex` | Verifies the server advertises the strict-KEX marker (CVE-2023-48795 Terrapin mitigation). | WARNING | | `ssh.preauth_compression` | Flags servers that offer pre-authentication zlib compression (prefer zlib@openssh.com). | INFO | | `ssh.auth_methods` | Reviews the advertised authentication methods (password exposure, public-key availability). | WARNING | | `ssh.sshfp_alignment` | Compares published SSHFP records against the observed host keys (match, missing, mismatch). | CRITICAL | | `ssh.sshfp_hash` | Flags SSHFP record sets that only publish SHA-1 (type 1) fingerprints instead of SHA-256. | WARNING | ## Observation key Writes a single observation under `ssh`: ```json { "domain": "...", "endpoints": [ { "host": "...", "port": 22, "address": "...", "banner": "SSH-2.0-OpenSSH_9.3p1", "kex_algorithms": ["curve25519-sha256", "..."], "host_keys": [{"type": "ssh-ed25519", "sha256": "abc..."}], "auth_methods": ["publickey"], "issues": [ { "code": "...", "severity": "warn", ... } ] } ], "sshfp": { "present": true, "records": [...] }, "collected_at": "..." } ``` ## License & licensing roadmap This project is currently licensed under the **GNU Affero General Public License v3.0** (see `LICENSE`), because it still imports `happydns.ServiceMessage` and `abstract.Server` from the happyDomain server module (`git.happydns.org/happyDomain/model` and `git.happydns.org/happyDomain/services/abstract`), which are themselves distributed under AGPL-3.0 and a commercial license. The core checker types (`CheckerOptions`, `CheckerDefinition`, `ObservationProvider`, `CheckRule`, …) have already been migrated to [`checker-sdk-go`](https://git.happydns.org/checker-sdk-go); only the service-message types remain on the AGPL side. **Planned relicensing:** as soon as the remaining `ServiceMessage` / `abstract.Server` dependency has been removed (moved into a dedicated permissively licensed module), this project will be relicensed under the **MIT License**, in line with the rest of the happyDomain checker ecosystem (see `checker-dummy` for the target shape). **Contributors notice:** by submitting a contribution to this repository, you accept that your contribution will be relicensed from AGPL-3.0 to MIT at the time of the relicensing described above. If you do not agree with this, please do not submit contributions until the relicensing has taken place. The third-party Apache-2.0 attributions for `checker-sdk-go` are recorded in `NOTICE` and must accompany any binary or source redistribution of this project.