Compare commits
2 commits
06036c89d9
...
af5b70920b
| Author | SHA1 | Date | |
|---|---|---|---|
| af5b70920b | |||
| a1203425ff |
4 changed files with 48 additions and 7 deletions
20
README.md
20
README.md
|
|
@ -105,6 +105,26 @@ make plugin
|
||||||
| `probeTimeoutMs` | number | `10000` | Per-endpoint dial + handshake timeout. |
|
| `probeTimeoutMs` | number | `10000` | Per-endpoint dial + handshake timeout. |
|
||||||
| `includeAuthProbe` | bool | `true` | Open a second connection to enumerate auth methods. |
|
| `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
|
## Observation key
|
||||||
|
|
||||||
Writes a single observation under `ssh`:
|
Writes a single observation under `ssh`:
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,22 @@ func (p *sshProvider) Collect(ctx context.Context, opts sdk.CheckerOptions) (any
|
||||||
ports = append([]uint16{DefaultSSHPort}, ports...)
|
ports = append([]uint16{DefaultSSHPort}, ports...)
|
||||||
}
|
}
|
||||||
|
|
||||||
host, ips := addressesFromServer(server)
|
// Origin is the FQDN where the service is mounted: svc.Domain holds the
|
||||||
|
// subdomain (relative to apex; "@" for apex), and the domain_name
|
||||||
|
// autofill carries the zone apex.
|
||||||
|
apex := ""
|
||||||
|
if v, ok := sdk.GetOption[string](opts, OptionDomainName); ok {
|
||||||
|
apex = strings.TrimSuffix(v, ".")
|
||||||
|
}
|
||||||
|
subdomain := ""
|
||||||
|
if svc, ok := sdk.GetOption[happydns.ServiceMessage](opts, OptionService); ok {
|
||||||
|
subdomain = strings.TrimSuffix(svc.Domain, ".")
|
||||||
|
}
|
||||||
|
origin := sdk.JoinRelative(subdomain, apex)
|
||||||
|
host, ips := addressesFromServer(server, origin)
|
||||||
|
if host == "" {
|
||||||
|
host = origin
|
||||||
|
}
|
||||||
if len(ips) == 0 {
|
if len(ips) == 0 {
|
||||||
return nil, fmt.Errorf("abstract.Server service has no A/AAAA records")
|
return nil, fmt.Errorf("abstract.Server service has no A/AAAA records")
|
||||||
}
|
}
|
||||||
|
|
@ -124,17 +139,16 @@ func resolveServer(opts sdk.CheckerOptions) (*abstract.Server, error) {
|
||||||
// addressesFromServer returns the service's owner domain name (used
|
// addressesFromServer returns the service's owner domain name (used
|
||||||
// for SNI-like purposes in SSH banner/hostname exchange) and the list
|
// for SNI-like purposes in SSH banner/hostname exchange) and the list
|
||||||
// of IPs to probe.
|
// of IPs to probe.
|
||||||
func addressesFromServer(server *abstract.Server) (host string, ips []string) {
|
func addressesFromServer(server *abstract.Server, origin string) (host string, ips []string) {
|
||||||
// We can't know the service's owner domain from the Server payload
|
// happyDomain encodes service-embedded record owners relative to the
|
||||||
// alone. The host value we use here is purely informational for
|
// parent zone, so we must join with origin before treating as FQDN.
|
||||||
// the report; the ssh handshake itself doesn't need it.
|
|
||||||
if server.A != nil && len(server.A.A) > 0 {
|
if server.A != nil && len(server.A.A) > 0 {
|
||||||
host = strings.TrimSuffix(server.A.Hdr.Name, ".")
|
host = sdk.JoinRelative(server.A.Hdr.Name, origin)
|
||||||
ips = append(ips, server.A.A.String())
|
ips = append(ips, server.A.A.String())
|
||||||
}
|
}
|
||||||
if server.AAAA != nil && len(server.AAAA.AAAA) > 0 {
|
if server.AAAA != nil && len(server.AAAA.AAAA) > 0 {
|
||||||
if host == "" {
|
if host == "" {
|
||||||
host = strings.TrimSuffix(server.AAAA.Hdr.Name, ".")
|
host = sdk.JoinRelative(server.AAAA.Hdr.Name, origin)
|
||||||
}
|
}
|
||||||
ips = append(ips, server.AAAA.AAAA.String())
|
ips = append(ips, server.AAAA.AAAA.String())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,12 @@ func (p *sshProvider) Definition() *sdk.CheckerDefinition {
|
||||||
AutoFill: sdk.AutoFillService,
|
AutoFill: sdk.AutoFillService,
|
||||||
Hide: true,
|
Hide: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Id: OptionDomainName,
|
||||||
|
Label: "Parent domain name",
|
||||||
|
AutoFill: sdk.AutoFillDomainName,
|
||||||
|
Hide: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Rules: Rules(),
|
Rules: Rules(),
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ const ObservationKeySSH = "ssh"
|
||||||
// Option ids on CheckerOptions.
|
// Option ids on CheckerOptions.
|
||||||
const (
|
const (
|
||||||
OptionService = "service"
|
OptionService = "service"
|
||||||
|
OptionDomainName = "domain_name"
|
||||||
OptionPorts = "ports"
|
OptionPorts = "ports"
|
||||||
OptionProbeTimeoutMs = "probeTimeoutMs"
|
OptionProbeTimeoutMs = "probeTimeoutMs"
|
||||||
OptionIncludeAuthProbe = "includeAuthProbe"
|
OptionIncludeAuthProbe = "includeAuthProbe"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue