- Go 99.1%
- Makefile 0.5%
- Dockerfile 0.4%
| checker | ||
| plugin | ||
| .gitignore | ||
| Dockerfile | ||
| go.mod | ||
| go.sum | ||
| LICENSE | ||
| main.go | ||
| Makefile | ||
| NOTICE | ||
| README.md | ||
checker-ssh
Deep SSH security checker for happyDomain.
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
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.SSHFPrecords declared in the zone, - flags
sshfp_missing,sshfp_not_covered,sshfp_only_sha1orsshfp_mismatchas 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":
- Overall status banner + SSHFP verdict chips.
- What to fix: top issues (crit -> warn), each with a copy-pasteable remediation snippet (sshd_config lines, SSHFP DNS records, ssh-keygen invocations).
- SSHFP table with per-record match status.
- Per-endpoint details: expandable sections with host-key fingerprints, algorithm tables (broken entries highlighted), and advertised auth methods.
Usage
Standalone HTTP server
make
./checker-ssh -listen :8080
Endpoints:
GET /healthGET /definitionPOST /collectPOST /evaluatePOST /report
Docker
make docker
docker run -p 8080:8080 happydomain/checker-ssh
happyDomain plugin
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:
{
"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; 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.