mirror of
https://github.com/jtesta/ssh-audit.git
synced 2026-07-24 19:45:37 +02:00
Compare commits
13
Commits
0ccb915f37
..
v3.9.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dbf8b69633 | ||
|
|
13fd7102d9 | ||
|
|
419df1b788 | ||
|
|
859cb7012e | ||
|
|
0f8bbdbfd9 | ||
|
|
1eb11050b3 | ||
|
|
240840f580 | ||
|
|
36def4b5ac | ||
|
|
f369689cd1 | ||
|
|
3bd2dd95a9 | ||
|
|
b7749a7e17 | ||
|
|
974dd324a6 | ||
|
|
e42d993b07 |
@@ -48,7 +48,7 @@ usage: ssh-audit.py [-h] [-4] [-6] [-b] [-c] [-d]
|
|||||||
[--conn-rate-test N[:max_rate]] [--dheat N[:kex[:e_len]]]
|
[--conn-rate-test N[:max_rate]] [--dheat N[:kex[:e_len]]]
|
||||||
[--get-hardening-guide platform] [--list-hardening-guides]
|
[--get-hardening-guide platform] [--list-hardening-guides]
|
||||||
[--lookup alg1[,alg2,...]] [--skip-rate-test]
|
[--lookup alg1[,alg2,...]] [--skip-rate-test]
|
||||||
[--threads N]
|
[--socks5 host:port] [--threads N]
|
||||||
[host]
|
[host]
|
||||||
|
|
||||||
# ssh-audit.py v3.4.0-dev, https://github.com/jtesta/ssh-audit
|
# ssh-audit.py v3.4.0-dev, https://github.com/jtesta/ssh-audit
|
||||||
@@ -128,6 +128,7 @@ optional arguments:
|
|||||||
--skip-rate-test skip the connection rate test during standard audits
|
--skip-rate-test skip the connection rate test during standard audits
|
||||||
(used to safely infer whether the DHEat attack is
|
(used to safely infer whether the DHEat attack is
|
||||||
viable)
|
viable)
|
||||||
|
--socks5 host:port connect via a SOCKS5 proxy (implies --skip-rate-test)
|
||||||
--threads N number of threads to use when scanning multiple
|
--threads N number of threads to use when scanning multiple
|
||||||
targets (-T/--targets) (default: 32)
|
targets (-T/--targets) (default: 32)
|
||||||
```
|
```
|
||||||
@@ -251,8 +252,9 @@ For convenience, a web front-end on top of the command-line tool is available at
|
|||||||
|
|
||||||
## ChangeLog
|
## ChangeLog
|
||||||
|
|
||||||
### v3.4.0-dev
|
### v3.9.0 (2026-07-04)
|
||||||
- BIG THANKS to [realmiwi](https://github.com/realmiwi) for being the project's *very first sponsor!!*
|
- BIG THANKS to [realmiwi](https://github.com/realmiwi) for being the project's *very first sponsor!!*
|
||||||
|
- Added support for Python 3.14.
|
||||||
- Dropped support for Python 3.8, as it reached end-of-life in October 2024.
|
- Dropped support for Python 3.8, as it reached end-of-life in October 2024.
|
||||||
- Dropped support for Python 3.9, as it reached end-of-life in October 2025.
|
- Dropped support for Python 3.9, as it reached end-of-life in October 2025.
|
||||||
- Added warning to all key exchanges that do not include protections against quantum attacks due to the Harvest Now, Decrypt Later strategy (see https://en.wikipedia.org/wiki/Harvest_now,_decrypt_later).
|
- Added warning to all key exchanges that do not include protections against quantum attacks due to the Harvest Now, Decrypt Later strategy (see https://en.wikipedia.org/wiki/Harvest_now,_decrypt_later).
|
||||||
@@ -263,10 +265,14 @@ For convenience, a web front-end on top of the command-line tool is available at
|
|||||||
- When running against multiple hosts, now prints each target host regardless of output level.
|
- When running against multiple hosts, now prints each target host regardless of output level.
|
||||||
- Batch mode (`-b`) no longer automatically enables verbose mode, due to sometimes confusing results; users can still explicitly enable verbose mode using the `-v` flag.
|
- Batch mode (`-b`) no longer automatically enables verbose mode, due to sometimes confusing results; users can still explicitly enable verbose mode using the `-v` flag.
|
||||||
- Added UNIX server socket scanning (specify the target with `unix:///path/to/socket`).
|
- Added UNIX server socket scanning (specify the target with `unix:///path/to/socket`).
|
||||||
- Added built-in policy for OpenSSH 10.0.
|
- Added SOCKS5 proxy support (specify the proxy with `--socks5 host:port`); partial credit [Michał Majchrowicz](https://github.com/sectroyer).
|
||||||
|
- Updated built-in policy for Debian 12.
|
||||||
|
- Added built-in policies for OpenSSH 10.0, 10.1, 10.2, 10.3, and 10.4.
|
||||||
- Added hardening guides and policies for Debian 13.
|
- Added hardening guides and policies for Debian 13.
|
||||||
|
- Added hardening guides and policies for Rocky Linux 10.
|
||||||
|
- Added hardening guides and policies for Ubuntu 26.04.
|
||||||
- Added 2 new key exchanges: `mlkem768nistp256-sha256`, `mlkem1024nistp384-sha384`.
|
- Added 2 new key exchanges: `mlkem768nistp256-sha256`, `mlkem1024nistp384-sha384`.
|
||||||
- Added 1 new host key: `webauthn-sk-ecdsa-sha2-nistp256-cert-v01@openssh.com`.
|
- Added 11 new host keys: `webauthn-sk-ecdsa-sha2-nistp256-cert-v01@openssh.com`, `mldsa-44`, `ssh-mldsa44-ed25519@openssh.com`, `mldsa-65`, `mldsa-87`, `ssh-mldsa-44`, `ssh-mldsa-65`, `ssh-mldsa-87`, `ssh-mldsa44`, `ssh-mldsa65`, `ssh-mldsa87`.
|
||||||
- Added 2 new ciphers: `AEAD_CAMELLIA_128_GCM`, `AEAD_CAMELLIA_256_GCM`.
|
- Added 2 new ciphers: `AEAD_CAMELLIA_128_GCM`, `AEAD_CAMELLIA_256_GCM`.
|
||||||
|
|
||||||
### v3.3.0 (2024-10-15)
|
### v3.3.0 (2024-10-15)
|
||||||
|
|||||||
@@ -117,8 +117,6 @@ class Algorithms:
|
|||||||
sshv, alg_db = alg_pair.sshv, alg_pair.db
|
sshv, alg_db = alg_pair.sshv, alg_pair.db
|
||||||
rec[sshv] = {}
|
rec[sshv] = {}
|
||||||
for alg_type, alg_list in alg_pair.items():
|
for alg_type, alg_list in alg_pair.items():
|
||||||
if alg_type == 'aut':
|
|
||||||
continue
|
|
||||||
rec[sshv][alg_type] = {'add': {}, 'del': {}, 'chg': {}}
|
rec[sshv][alg_type] = {'add': {}, 'del': {}, 'chg': {}}
|
||||||
for n, alg_desc in alg_db[alg_type].items():
|
for n, alg_desc in alg_db[alg_type].items():
|
||||||
versions = alg_desc[0]
|
versions = alg_desc[0]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (C) 2017-2025 Joe Testa (jtesta@positronsecurity.com)
|
Copyright (C) 2017-2026 Joe Testa (jtesta@positronsecurity.com)
|
||||||
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
|
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
@@ -67,6 +67,7 @@ class AuditConf:
|
|||||||
self.conn_rate_test_enabled = False
|
self.conn_rate_test_enabled = False
|
||||||
self.conn_rate_test_threads = 0
|
self.conn_rate_test_threads = 0
|
||||||
self.conn_rate_test_target_rate = 0
|
self.conn_rate_test_target_rate = 0
|
||||||
|
self.socks5_proxy: Optional[str] = None # SOCKS5 proxy in "host:port" format
|
||||||
|
|
||||||
|
|
||||||
def __setattr__(self, name: str, value: Union[str, int, float, bool, Sequence[int]]) -> None:
|
def __setattr__(self, name: str, value: Union[str, int, float, bool, Sequence[int]]) -> None:
|
||||||
@@ -185,6 +186,12 @@ class AuditConf:
|
|||||||
if not isinstance(value, int):
|
if not isinstance(value, int):
|
||||||
valid = False
|
valid = False
|
||||||
|
|
||||||
|
elif name == "socks5_proxy":
|
||||||
|
valid = True
|
||||||
|
if isinstance(value, str):
|
||||||
|
host, port = Utils.parse_host_and_port(value, 0)
|
||||||
|
if len(host) == 0 or port <= 0 or port >= 65535:
|
||||||
|
raise ValueError("SOCKS5 host must be in host:port format.")
|
||||||
|
|
||||||
if valid:
|
if valid:
|
||||||
object.__setattr__(self, name, value)
|
object.__setattr__(self, name, value)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (C) 2020-2024 Joe Testa (jtesta@positronsecurity.com)
|
Copyright (C) 2020-2026 Joe Testa (jtesta@positronsecurity.com)
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -38,14 +38,18 @@ BUILTIN_POLICIES: Dict[str, Dict[str, Union[Optional[str], Optional[List[str]],
|
|||||||
|
|
||||||
'Hardened Debian 12 (version 2)': {'version': '2', 'changelog': 'Re-ordered host keys to prioritize ED25519 due to efficiency. Re-ordered cipher list to prioritize larger key sizes as a countermeasure to quantum attacks.', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519', 'rsa-sha2-512', 'rsa-sha2-256'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['sntrup761x25519-sha512@openssh.com', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256', 'kex-strict-s-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-gcm@openssh.com', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {"rsa-sha2-256": {"hostkey_size": 4096}, "rsa-sha2-256-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "rsa-sha2-512": {"hostkey_size": 4096}, "rsa-sha2-512-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "sk-ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}, "sk-ssh-ed25519@openssh.com": {"hostkey_size": 256}, "ssh-ed25519": {"hostkey_size": 256}, "ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}}, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 3072}, 'server_policy': True},
|
'Hardened Debian 12 (version 2)': {'version': '2', 'changelog': 'Re-ordered host keys to prioritize ED25519 due to efficiency. Re-ordered cipher list to prioritize larger key sizes as a countermeasure to quantum attacks.', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519', 'rsa-sha2-512', 'rsa-sha2-256'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['sntrup761x25519-sha512@openssh.com', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256', 'kex-strict-s-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-gcm@openssh.com', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {"rsa-sha2-256": {"hostkey_size": 4096}, "rsa-sha2-256-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "rsa-sha2-512": {"hostkey_size": 4096}, "rsa-sha2-512-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "sk-ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}, "sk-ssh-ed25519@openssh.com": {"hostkey_size": 256}, "ssh-ed25519": {"hostkey_size": 256}, "ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}}, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 3072}, 'server_policy': True},
|
||||||
|
|
||||||
|
'Hardened Debian 12 (version 3)': {'version': '3', 'changelog': 'Added sntrup761x25519-sha512 key exchange.', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519', 'rsa-sha2-512', 'rsa-sha2-256'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['sntrup761x25519-sha512', 'sntrup761x25519-sha512@openssh.com', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256', 'kex-strict-s-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-gcm@openssh.com', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {"rsa-sha2-256": {"hostkey_size": 4096}, "rsa-sha2-256-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "rsa-sha2-512": {"hostkey_size": 4096}, "rsa-sha2-512-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "sk-ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}, "sk-ssh-ed25519@openssh.com": {"hostkey_size": 256}, "ssh-ed25519": {"hostkey_size": 256}, "ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}}, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 3072}, 'server_policy': True},
|
||||||
|
|
||||||
'Hardened Debian 13 (version 1)': {'version': '1', 'changelog': 'Initial revision. As compared to the Debian 12 guide, the following changes were made: 1.) all non-post-quantum key exchanges were removed, and mlkem768x25519-sha256 and sntrup761x25519-sha512 were added, 2.) editing the /etc/ssh/moduli file is no longer done, as we no longer use any group-exchange algorithms (none of them currently protect against quantum attacks), 3.) editing the sshd_config file is no longer done, in order to make system upgrades easier for users, 4.) network-level connection throttling is no longer done, as OpenSSH prevents the DHEat attack by default now.', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519', 'rsa-sha2-512', 'rsa-sha2-256'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['mlkem768x25519-sha256', 'sntrup761x25519-sha512', 'sntrup761x25519-sha512@openssh.com', 'ext-info-s', 'kex-strict-s-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-gcm@openssh.com', 'aes128-ctr'], 'macs': ['hmac-sha2-512-etm@openssh.com', 'hmac-sha2-256-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {"rsa-sha2-256": {"hostkey_size": 4096}, "rsa-sha2-256-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "rsa-sha2-512": {"hostkey_size": 4096}, "rsa-sha2-512-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "sk-ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}, "sk-ssh-ed25519@openssh.com": {"hostkey_size": 256}, "ssh-ed25519": {"hostkey_size": 256}, "ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}}, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 3072}, "allow_hostkey_subset_and_reordering": True, 'server_policy': True},
|
'Hardened Debian 13 (version 1)': {'version': '1', 'changelog': 'Initial revision. As compared to the Debian 12 guide, the following changes were made: 1.) all non-post-quantum key exchanges were removed, and mlkem768x25519-sha256 and sntrup761x25519-sha512 were added, 2.) editing the /etc/ssh/moduli file is no longer done, as we no longer use any group-exchange algorithms (none of them currently protect against quantum attacks), 3.) editing the sshd_config file is no longer done, in order to make system upgrades easier for users, 4.) network-level connection throttling is no longer done, as OpenSSH prevents the DHEat attack by default now.', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519', 'rsa-sha2-512', 'rsa-sha2-256'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['mlkem768x25519-sha256', 'sntrup761x25519-sha512', 'sntrup761x25519-sha512@openssh.com', 'ext-info-s', 'kex-strict-s-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-gcm@openssh.com', 'aes128-ctr'], 'macs': ['hmac-sha2-512-etm@openssh.com', 'hmac-sha2-256-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {"rsa-sha2-256": {"hostkey_size": 4096}, "rsa-sha2-256-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "rsa-sha2-512": {"hostkey_size": 4096}, "rsa-sha2-512-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "sk-ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}, "sk-ssh-ed25519@openssh.com": {"hostkey_size": 256}, "ssh-ed25519": {"hostkey_size": 256}, "ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}}, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 3072}, "allow_hostkey_subset_and_reordering": True, 'server_policy': True},
|
||||||
|
|
||||||
|
|
||||||
# Rocky Linux 9
|
# Rocky Linux
|
||||||
'Hardened Rocky Linux 9 (version 1)': {'version': '1', 'changelog': 'Initial version', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['sntrup761x25519-sha512@openssh.com', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256', 'kex-strict-s-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {"rsa-sha2-256": {"hostkey_size": 4096}, "rsa-sha2-256-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "rsa-sha2-512": {"hostkey_size": 4096}, "rsa-sha2-512-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "sk-ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}, "sk-ssh-ed25519@openssh.com": {"hostkey_size": 256}, "ssh-ed25519": {"hostkey_size": 256}, "ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}}, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 3072}, 'server_policy': True},
|
'Hardened Rocky Linux 9 (version 1)': {'version': '1', 'changelog': 'Initial version', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['sntrup761x25519-sha512@openssh.com', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256', 'kex-strict-s-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {"rsa-sha2-256": {"hostkey_size": 4096}, "rsa-sha2-256-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "rsa-sha2-512": {"hostkey_size": 4096}, "rsa-sha2-512-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "sk-ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}, "sk-ssh-ed25519@openssh.com": {"hostkey_size": 256}, "ssh-ed25519": {"hostkey_size": 256}, "ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}}, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 3072}, 'server_policy': True},
|
||||||
|
|
||||||
'Hardened Rocky Linux 9 (version 2)': {'version': '2', 'changelog': 'Re-ordered host keys to prioritize ED25519 due to efficiency. Re-ordered cipher list to prioritize larger key sizes as a countermeasure to quantum attacks.', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519', 'rsa-sha2-512', 'rsa-sha2-256'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['sntrup761x25519-sha512@openssh.com', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256', 'kex-strict-s-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-gcm@openssh.com', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {"rsa-sha2-256": {"hostkey_size": 4096}, "rsa-sha2-256-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "rsa-sha2-512": {"hostkey_size": 4096}, "rsa-sha2-512-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "sk-ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}, "sk-ssh-ed25519@openssh.com": {"hostkey_size": 256}, "ssh-ed25519": {"hostkey_size": 256}, "ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}}, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 3072}, 'server_policy': True},
|
'Hardened Rocky Linux 9 (version 2)': {'version': '2', 'changelog': 'Re-ordered host keys to prioritize ED25519 due to efficiency. Re-ordered cipher list to prioritize larger key sizes as a countermeasure to quantum attacks.', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519', 'rsa-sha2-512', 'rsa-sha2-256'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['sntrup761x25519-sha512@openssh.com', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256', 'kex-strict-s-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-gcm@openssh.com', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {"rsa-sha2-256": {"hostkey_size": 4096}, "rsa-sha2-256-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "rsa-sha2-512": {"hostkey_size": 4096}, "rsa-sha2-512-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "sk-ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}, "sk-ssh-ed25519@openssh.com": {"hostkey_size": 256}, "ssh-ed25519": {"hostkey_size": 256}, "ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}}, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 3072}, 'server_policy': True},
|
||||||
|
|
||||||
|
'Hardened Rocky Linux 10 (version 1)': {'version': '1', 'changelog': 'Initial revision. In comparison to the Rocky Linux 9 hardening guide, the following changes were made: the key exchange list was set to include post-quantum algorithms only.', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519', 'rsa-sha2-512', 'rsa-sha2-256'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['mlkem768x25519-sha256', 'sntrup761x25519-sha512', 'sntrup761x25519-sha512@openssh.com', 'ext-info-s', 'kex-strict-s-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-gcm@openssh.com', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {"rsa-sha2-256": {"hostkey_size": 4096}, "rsa-sha2-256-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "rsa-sha2-512": {"hostkey_size": 4096}, "rsa-sha2-512-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "sk-ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}, "sk-ssh-ed25519@openssh.com": {"hostkey_size": 256}, "ssh-ed25519": {"hostkey_size": 256}, "ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}}, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 3072}, 'server_policy': True},
|
||||||
|
|
||||||
|
|
||||||
# Ubuntu Server policies
|
# Ubuntu Server policies
|
||||||
|
|
||||||
@@ -63,6 +67,8 @@ BUILTIN_POLICIES: Dict[str, Dict[str, Union[Optional[str], Optional[List[str]],
|
|||||||
|
|
||||||
'Hardened Ubuntu Server 24.04 LTS (version 1)': {'version': '1', 'changelog': 'Initial version.', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519', 'rsa-sha2-512', 'rsa-sha2-256'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['sntrup761x25519-sha512@openssh.com', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256', 'diffie-hellman-group16-sha512', 'ext-info-s', 'kex-strict-s-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-gcm@openssh.com', 'aes128-ctr'], 'macs': ['hmac-sha2-512-etm@openssh.com', 'hmac-sha2-256-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {"rsa-sha2-256": {"hostkey_size": 4096}, "rsa-sha2-256-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "rsa-sha2-512": {"hostkey_size": 4096}, "rsa-sha2-512-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "sk-ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}, "sk-ssh-ed25519@openssh.com": {"hostkey_size": 256}, "ssh-ed25519": {"hostkey_size": 256}, "ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}}, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 3072}, 'server_policy': True},
|
'Hardened Ubuntu Server 24.04 LTS (version 1)': {'version': '1', 'changelog': 'Initial version.', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519', 'rsa-sha2-512', 'rsa-sha2-256'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['sntrup761x25519-sha512@openssh.com', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256', 'diffie-hellman-group16-sha512', 'ext-info-s', 'kex-strict-s-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-gcm@openssh.com', 'aes128-ctr'], 'macs': ['hmac-sha2-512-etm@openssh.com', 'hmac-sha2-256-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {"rsa-sha2-256": {"hostkey_size": 4096}, "rsa-sha2-256-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "rsa-sha2-512": {"hostkey_size": 4096}, "rsa-sha2-512-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "sk-ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}, "sk-ssh-ed25519@openssh.com": {"hostkey_size": 256}, "ssh-ed25519": {"hostkey_size": 256}, "ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}}, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 3072}, 'server_policy': True},
|
||||||
|
|
||||||
|
'Hardened Ubuntu Server 26.04 LTS (version 1)': {'version': '1', 'changelog': 'Initial revision. As compared to the Ubuntu Server 24.04 guide, the following changes were made: 1.) all non-post-quantum key exchanges were removed, and mlkem768x25519-sha256 and sntrup761x25519-sha512 were added, 2.) editing the /etc/ssh/moduli file is no longer done, as we no longer use any group-exchange algorithms (none of them currently protect against quantum attacks), 3.) editing the sshd_config file is no longer done, in order to make system upgrades easier for users, 4.) network-level connection throttling is no longer done, as OpenSSH prevents the DHEat attack by default now.', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519', 'rsa-sha2-512', 'rsa-sha2-256'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['mlkem768x25519-sha256', 'sntrup761x25519-sha512', 'sntrup761x25519-sha512@openssh.com', 'ext-info-s', 'kex-strict-s-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-gcm@openssh.com', 'aes128-ctr'], 'macs': ['hmac-sha2-512-etm@openssh.com', 'hmac-sha2-256-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {"rsa-sha2-256": {"hostkey_size": 4096}, "rsa-sha2-256-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "rsa-sha2-512": {"hostkey_size": 4096}, "rsa-sha2-512-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "sk-ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}, "sk-ssh-ed25519@openssh.com": {"hostkey_size": 256}, "ssh-ed25519": {"hostkey_size": 256}, "ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}}, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 3072}, "allow_hostkey_subset_and_reordering": True, 'server_policy': True},
|
||||||
|
|
||||||
# Generic OpenSSH Server policies
|
# Generic OpenSSH Server policies
|
||||||
|
|
||||||
'Hardened OpenSSH Server v7.7 (version 4)': {'version': '4', 'changelog': 'No change log available.', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {"rsa-sha2-256": {"hostkey_size": 4096}, "rsa-sha2-256-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "rsa-sha2-512": {"hostkey_size": 4096}, "rsa-sha2-512-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "ssh-ed25519": {"hostkey_size": 256}, "ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}}, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 3072}, 'server_policy': True},
|
'Hardened OpenSSH Server v7.7 (version 4)': {'version': '4', 'changelog': 'No change log available.', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {"rsa-sha2-256": {"hostkey_size": 4096}, "rsa-sha2-256-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "rsa-sha2-512": {"hostkey_size": 4096}, "rsa-sha2-512-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "ssh-ed25519": {"hostkey_size": 256}, "ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}}, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 3072}, 'server_policy': True},
|
||||||
@@ -113,6 +119,16 @@ BUILTIN_POLICIES: Dict[str, Dict[str, Union[Optional[str], Optional[List[str]],
|
|||||||
|
|
||||||
'Hardened OpenSSH Server v10.0 (version 1)': {'version': '1', 'changelog': 'Initial version.', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['mlkem768x25519-sha256', 'sntrup761x25519-sha512', 'sntrup761x25519-sha512@openssh.com', 'ext-info-s', 'kex-strict-s-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-gcm@openssh.com', 'aes128-ctr'], 'macs': ['hmac-sha2-512-etm@openssh.com', 'hmac-sha2-256-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {"rsa-sha2-256": {"hostkey_size": 4096}, "rsa-sha2-256-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "rsa-sha2-512": {"hostkey_size": 4096}, "rsa-sha2-512-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "sk-ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}, "sk-ssh-ed25519@openssh.com": {"hostkey_size": 256}, "ssh-ed25519": {"hostkey_size": 256}, "ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}}, 'dh_modulus_sizes': {}, 'server_policy': True},
|
'Hardened OpenSSH Server v10.0 (version 1)': {'version': '1', 'changelog': 'Initial version.', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['mlkem768x25519-sha256', 'sntrup761x25519-sha512', 'sntrup761x25519-sha512@openssh.com', 'ext-info-s', 'kex-strict-s-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-gcm@openssh.com', 'aes128-ctr'], 'macs': ['hmac-sha2-512-etm@openssh.com', 'hmac-sha2-256-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {"rsa-sha2-256": {"hostkey_size": 4096}, "rsa-sha2-256-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "rsa-sha2-512": {"hostkey_size": 4096}, "rsa-sha2-512-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "sk-ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}, "sk-ssh-ed25519@openssh.com": {"hostkey_size": 256}, "ssh-ed25519": {"hostkey_size": 256}, "ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}}, 'dh_modulus_sizes': {}, 'server_policy': True},
|
||||||
|
|
||||||
|
'Hardened OpenSSH Server v10.1 (version 1)': {'version': '1', 'changelog': 'Initial version.', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['mlkem768x25519-sha256', 'sntrup761x25519-sha512', 'sntrup761x25519-sha512@openssh.com', 'ext-info-s', 'kex-strict-s-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-gcm@openssh.com', 'aes128-ctr'], 'macs': ['hmac-sha2-512-etm@openssh.com', 'hmac-sha2-256-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {"rsa-sha2-256": {"hostkey_size": 4096}, "rsa-sha2-256-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "rsa-sha2-512": {"hostkey_size": 4096}, "rsa-sha2-512-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "sk-ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}, "sk-ssh-ed25519@openssh.com": {"hostkey_size": 256}, "ssh-ed25519": {"hostkey_size": 256}, "ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}}, 'dh_modulus_sizes': {}, 'server_policy': True},
|
||||||
|
|
||||||
|
'Hardened OpenSSH Server v10.2 (version 1)': {'version': '1', 'changelog': 'Initial version.', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['mlkem768x25519-sha256', 'sntrup761x25519-sha512', 'sntrup761x25519-sha512@openssh.com', 'ext-info-s', 'kex-strict-s-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-gcm@openssh.com', 'aes128-ctr'], 'macs': ['hmac-sha2-512-etm@openssh.com', 'hmac-sha2-256-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {"rsa-sha2-256": {"hostkey_size": 4096}, "rsa-sha2-256-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "rsa-sha2-512": {"hostkey_size": 4096}, "rsa-sha2-512-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "sk-ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}, "sk-ssh-ed25519@openssh.com": {"hostkey_size": 256}, "ssh-ed25519": {"hostkey_size": 256}, "ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}}, 'dh_modulus_sizes': {}, 'server_policy': True},
|
||||||
|
|
||||||
|
'Hardened OpenSSH Server v10.3 (version 1)': {'version': '1', 'changelog': 'Initial version.', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['mlkem768x25519-sha256', 'sntrup761x25519-sha512', 'sntrup761x25519-sha512@openssh.com', 'ext-info-s', 'kex-strict-s-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-gcm@openssh.com', 'aes128-ctr'], 'macs': ['hmac-sha2-512-etm@openssh.com', 'hmac-sha2-256-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {"rsa-sha2-256": {"hostkey_size": 4096}, "rsa-sha2-256-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "rsa-sha2-512": {"hostkey_size": 4096}, "rsa-sha2-512-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "sk-ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}, "sk-ssh-ed25519@openssh.com": {"hostkey_size": 256}, "ssh-ed25519": {"hostkey_size": 256}, "ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}}, 'dh_modulus_sizes': {}, 'server_policy': True},
|
||||||
|
|
||||||
|
# Note that the "ssh-mldsa44-ed25519@openssh.com" host key was added in this release as an experimental non-default option. Once it is no longer considered experimental by the OpenSSH developers, it will become a requirement.
|
||||||
|
'Hardened OpenSSH Server v10.4 (version 1)': {'version': '1', 'changelog': 'Initial version.', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['mlkem768x25519-sha256', 'sntrup761x25519-sha512', 'sntrup761x25519-sha512@openssh.com', 'ext-info-s', 'kex-strict-s-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-gcm@openssh.com', 'aes128-ctr'], 'macs': ['hmac-sha2-512-etm@openssh.com', 'hmac-sha2-256-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {"rsa-sha2-256": {"hostkey_size": 4096}, "rsa-sha2-256-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "rsa-sha2-512": {"hostkey_size": 4096}, "rsa-sha2-512-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "sk-ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}, "sk-ssh-ed25519@openssh.com": {"hostkey_size": 256}, "ssh-ed25519": {"hostkey_size": 256}, "ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}}, 'dh_modulus_sizes': {}, 'server_policy': True},
|
||||||
|
|
||||||
|
|
||||||
# Amazon Linux Policies
|
# Amazon Linux Policies
|
||||||
|
|
||||||
'Hardened Amazon Linux Client 2023 (version 1)': {'version': '1', 'changelog': 'Initial version.', 'banner': None, 'compressions': None, 'host_keys': ['sk-ssh-ed25519-cert-v01@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'sk-ssh-ed25519@openssh.com', 'ssh-ed25519', 'rsa-sha2-512', 'rsa-sha2-256'], 'optional_host_keys': None, 'kex': ['sntrup761x25519-sha512@openssh.com', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256', 'ext-info-c', 'kex-strict-c-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'dh_modulus_sizes': None, 'server_policy': False},
|
'Hardened Amazon Linux Client 2023 (version 1)': {'version': '1', 'changelog': 'Initial version.', 'banner': None, 'compressions': None, 'host_keys': ['sk-ssh-ed25519-cert-v01@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'sk-ssh-ed25519@openssh.com', 'ssh-ed25519', 'rsa-sha2-512', 'rsa-sha2-256'], 'optional_host_keys': None, 'kex': ['sntrup761x25519-sha512@openssh.com', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256', 'ext-info-c', 'kex-strict-c-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'dh_modulus_sizes': None, 'server_policy': False},
|
||||||
@@ -135,6 +151,8 @@ BUILTIN_POLICIES: Dict[str, Dict[str, Union[Optional[str], Optional[List[str]],
|
|||||||
|
|
||||||
'Hardened Rocky Linux Client 9 (version 2)': {'version': '2', 'changelog': 'Re-ordered cipher list to prioritize larger key sizes as a countermeasure to quantum attacks.', 'banner': None, 'compressions': None, 'host_keys': ['sk-ssh-ed25519-cert-v01@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'sk-ssh-ed25519@openssh.com', 'ssh-ed25519', 'rsa-sha2-512', 'rsa-sha2-256'], 'optional_host_keys': None, 'kex': ['sntrup761x25519-sha512@openssh.com', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256', 'ext-info-c', 'kex-strict-c-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-gcm@openssh.com', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'dh_modulus_sizes': None, 'server_policy': False},
|
'Hardened Rocky Linux Client 9 (version 2)': {'version': '2', 'changelog': 'Re-ordered cipher list to prioritize larger key sizes as a countermeasure to quantum attacks.', 'banner': None, 'compressions': None, 'host_keys': ['sk-ssh-ed25519-cert-v01@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'sk-ssh-ed25519@openssh.com', 'ssh-ed25519', 'rsa-sha2-512', 'rsa-sha2-256'], 'optional_host_keys': None, 'kex': ['sntrup761x25519-sha512@openssh.com', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256', 'ext-info-c', 'kex-strict-c-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-gcm@openssh.com', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'dh_modulus_sizes': None, 'server_policy': False},
|
||||||
|
|
||||||
|
'Hardened Rocky Linux Client 10 (version 1)': {'version': '1', 'changelog': 'Initial revision. In comparison to the Rocky Linux 9 hardening guide, the following changes were made: the key exchange list was set to include post-quantum algorithms only.', 'banner': None, 'compressions': None, 'host_keys': ['sk-ssh-ed25519-cert-v01@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'sk-ssh-ed25519@openssh.com', 'ssh-ed25519', 'rsa-sha2-512', 'rsa-sha2-256'], 'optional_host_keys': None, 'kex': ['mlkem768x25519-sha256', 'sntrup761x25519-sha512', 'sntrup761x25519-sha512@openssh.com', 'ext-info-c', 'kex-strict-c-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-gcm@openssh.com', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'dh_modulus_sizes': None, 'server_policy': False},
|
||||||
|
|
||||||
# Ubuntu Client policies
|
# Ubuntu Client policies
|
||||||
|
|
||||||
'Hardened Ubuntu Client 16.04 LTS (version 2)': {'version': '2', 'changelog': 'No change log available.', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519', 'ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256', 'rsa-sha2-512'], 'optional_host_keys': None, 'kex': ['curve25519-sha256@libssh.org', 'diffie-hellman-group-exchange-sha256', 'ext-info-c'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'dh_modulus_sizes': None, 'server_policy': False},
|
'Hardened Ubuntu Client 16.04 LTS (version 2)': {'version': '2', 'changelog': 'No change log available.', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519', 'ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256', 'rsa-sha2-512'], 'optional_host_keys': None, 'kex': ['curve25519-sha256@libssh.org', 'diffie-hellman-group-exchange-sha256', 'ext-info-c'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'dh_modulus_sizes': None, 'server_policy': False},
|
||||||
@@ -149,4 +167,6 @@ BUILTIN_POLICIES: Dict[str, Dict[str, Union[Optional[str], Optional[List[str]],
|
|||||||
|
|
||||||
'Hardened Ubuntu Client 24.04 LTS (version 1)': {'version': '1', 'changelog': 'Initial version.', 'banner': None, 'compressions': None, 'host_keys': ['sk-ssh-ed25519-cert-v01@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'sk-ssh-ed25519@openssh.com', 'ssh-ed25519', 'rsa-sha2-512', 'rsa-sha2-256'], 'optional_host_keys': None, 'kex': ['sntrup761x25519-sha512@openssh.com', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256', 'diffie-hellman-group16-sha512', 'ext-info-c', 'kex-strict-c-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-gcm@openssh.com', 'aes128-ctr'], 'macs': ['hmac-sha2-512-etm@openssh.com', 'hmac-sha2-256-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'dh_modulus_sizes': None, 'server_policy': False},
|
'Hardened Ubuntu Client 24.04 LTS (version 1)': {'version': '1', 'changelog': 'Initial version.', 'banner': None, 'compressions': None, 'host_keys': ['sk-ssh-ed25519-cert-v01@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'sk-ssh-ed25519@openssh.com', 'ssh-ed25519', 'rsa-sha2-512', 'rsa-sha2-256'], 'optional_host_keys': None, 'kex': ['sntrup761x25519-sha512@openssh.com', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256', 'diffie-hellman-group16-sha512', 'ext-info-c', 'kex-strict-c-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-gcm@openssh.com', 'aes128-ctr'], 'macs': ['hmac-sha2-512-etm@openssh.com', 'hmac-sha2-256-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'dh_modulus_sizes': None, 'server_policy': False},
|
||||||
|
|
||||||
|
'Hardened Ubuntu Client 26.04 LTS (version 1)': {'version': '1', 'changelog': 'Initial revision. As compared to the Ubuntu Client 24.04 guide, the following changes were made: all non-post-quantum key exchanges were removed, and mlkem768x25519-sha256 and sntrup761x25519-sha512 were added.', 'banner': None, 'compressions': None, 'host_keys': ['sk-ssh-ed25519-cert-v01@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'sk-ssh-ed25519@openssh.com', 'ssh-ed25519', 'rsa-sha2-512', 'rsa-sha2-256'], 'optional_host_keys': None, 'kex': ['mlkem768x25519-sha256', 'sntrup761x25519-sha512', 'sntrup761x25519-sha512@openssh.com', 'ext-info-c', 'kex-strict-c-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-gcm@openssh.com', 'aes128-ctr'], 'macs': ['hmac-sha2-512-etm@openssh.com', 'hmac-sha2-256-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'dh_modulus_sizes': None, 'server_policy': False},
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+67
-37
@@ -194,7 +194,9 @@ class DHEat:
|
|||||||
self.num_connect_timeouts = 0
|
self.num_connect_timeouts = 0
|
||||||
self.num_read_timeouts = 0
|
self.num_read_timeouts = 0
|
||||||
self.num_socket_exceptions = 0
|
self.num_socket_exceptions = 0
|
||||||
self.num_openssh_throttled_connections = 0
|
self.num_em = 0 # Number of "Exceeded MaxStartups" messages.
|
||||||
|
self.num_naatt = 0 # Number of "No allowed at this time" messages.
|
||||||
|
self.num_invalid_banners = 0
|
||||||
|
|
||||||
# The time we started the attack.
|
# The time we started the attack.
|
||||||
self.start_timer = 0.0
|
self.start_timer = 0.0
|
||||||
@@ -351,10 +353,10 @@ class DHEat:
|
|||||||
|
|
||||||
rate_str = ""
|
rate_str = ""
|
||||||
if aconf.conn_rate_test_target_rate > 0:
|
if aconf.conn_rate_test_target_rate > 0:
|
||||||
rate_str = " at a max rate of %s%u%s connections per second" % (DHEat.WHITEB, aconf.conn_rate_test_target_rate, DHEat.CLEAR)
|
rate_str = f" at a max rate of {DHEat.WHITEB}{aconf.conn_rate_test_target_rate:d}{DHEat.CLEAR} connections per second"
|
||||||
|
|
||||||
print()
|
print()
|
||||||
print("Performing non-disruptive rate test against %s[%s]:%u%s with %s%u%s concurrent sockets%s. No Diffie-Hellman requests will be sent." % (DHEat.WHITEB, target_ip_address, aconf.port, DHEat.CLEAR, DHEat.WHITEB, concurrent_sockets, DHEat.CLEAR, rate_str))
|
print(f"Performing non-disruptive rate test against {DHEat.WHITEB}[{target_ip_address}]:{aconf.port:d}{DHEat.CLEAR} with {DHEat.WHITEB}{concurrent_sockets:d}{DHEat.CLEAR} concurrent sockets{rate_str}. No Diffie-Hellman requests will be sent.\n")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
# Make room for the multi-line output.
|
# Make room for the multi-line output.
|
||||||
@@ -369,14 +371,17 @@ class DHEat:
|
|||||||
server_dh_kex.append(server_kex)
|
server_dh_kex.append(server_kex)
|
||||||
|
|
||||||
if len(server_dh_kex) == 0:
|
if len(server_dh_kex) == 0:
|
||||||
out.d("Skipping DHEat.dh_rate_test() since server does not support any DH algorithms: [%s]" % ", ".join(kex.kex_algorithms))
|
kex_list = ", ".join(kex.kex_algorithms)
|
||||||
|
out.d(f"Skipping DHEat.dh_rate_test() since server does not support any DH algorithms: [{kex_list}]")
|
||||||
return ""
|
return ""
|
||||||
else:
|
else:
|
||||||
out.d("DHEat.dh_rate_test(): starting test; parameters: %f seconds, %u max connections, %u concurrent sockets." % (max_time, max_connections, concurrent_sockets), write_now=True)
|
out.d(f"DHEat.dh_rate_test(): starting test; parameters: {max_time:,.1f} seconds, {max_connections:,d} max connections, {concurrent_sockets:,d} concurrent sockets.", write_now=True)
|
||||||
|
|
||||||
num_attempted_connections = 0
|
num_attempted_connections = 0
|
||||||
num_opened_connections = 0
|
num_opened_connections = 0
|
||||||
num_exceeded_maxstartups = 0
|
num_em = 0 # Number of "Exceeded MaxStartups" messages.
|
||||||
|
num_naatt = 0 # Number of "Not available at this time" messages.
|
||||||
|
num_invalid_banners = 0
|
||||||
socket_dict: Dict[socket.socket, float] = {}
|
socket_dict: Dict[socket.socket, float] = {}
|
||||||
start_timer = time.time()
|
start_timer = time.time()
|
||||||
now = start_timer
|
now = start_timer
|
||||||
@@ -395,13 +400,15 @@ class DHEat:
|
|||||||
if (now - last_update) >= 1.0:
|
if (now - last_update) >= 1.0:
|
||||||
seconds_running = now - start_timer
|
seconds_running = now - start_timer
|
||||||
if multiline_output:
|
if multiline_output:
|
||||||
print("\033[5ARun time: %s%.1f%s seconds" % (DHEat.WHITEB, seconds_running, DHEat.CLEAR))
|
print(f"\033[7A Run time: {DHEat.WHITEB}{seconds_running:,.1f}{DHEat.CLEAR} seconds")
|
||||||
print("TCP SYNs: %s%u%s (total); %s%.1f%s (per second)" % (DHEat.WHITEB, num_attempted_connections, DHEat.CLEAR, DHEat.BLUEB, num_attempted_connections / seconds_running, DHEat.CLEAR))
|
print(f" TCP SYNs: {DHEat.WHITEB}{num_attempted_connections / seconds_running:,.1f}/sec{DHEat.CLEAR}; {DHEat.WHITEB}{num_attempted_connections:,d}{DHEat.CLEAR} total ")
|
||||||
print("Completed connections: %s%u%s (total); %s%.1f%s (per second)" % (DHEat.WHITEB, num_opened_connections, DHEat.CLEAR, DHEat.BLUEB, num_opened_connections / seconds_running, DHEat.CLEAR))
|
print(f" Completed connections: {DHEat.WHITEB}{num_opened_connections / seconds_running:,.1f}/sec{DHEat.CLEAR}; {DHEat.WHITEB}{num_opened_connections:,d}{DHEat.CLEAR} total ")
|
||||||
print("\"Exceeded MaxStartups\" responses: %s%u%s (total); %s%.1f%s (per second)" % (DHEat.WHITEB, num_exceeded_maxstartups, DHEat.CLEAR, DHEat.BLUEB, num_exceeded_maxstartups / seconds_running, DHEat.CLEAR))
|
print(f" \"Exceeded MaxStartups\" responses: {DHEat.WHITEB}{num_em / seconds_running:,.1f}/sec{DHEat.CLEAR}; {DHEat.WHITEB}{num_em:,d}{DHEat.CLEAR} total ")
|
||||||
print("%s%s%s" % (DHEat.WHITEB, spinner[spinner_index], DHEat.CLEAR))
|
print(f"\"Not allowed at this time\" responses: {DHEat.WHITEB}{num_naatt / seconds_running:,.1f}/sec{DHEat.CLEAR}; {DHEat.WHITEB}{num_naatt:,d}{DHEat.CLEAR} total ")
|
||||||
|
print(f" Invalid banners: {DHEat.WHITEB}{num_invalid_banners / seconds_running:,.1f}/sec{DHEat.CLEAR}; {DHEat.WHITEB}{num_invalid_banners:,d}{DHEat.CLEAR} total ")
|
||||||
|
print(DHEat.WHITEB + spinner[spinner_index] + DHEat.CLEAR)
|
||||||
else:
|
else:
|
||||||
print("%s%s%s Run time: %s%.1f%s; TCP SYNs: %s%u%s; Compl. conns: %s%u%s; TCP SYNs/sec: %s%.1f%s; Compl. conns/sec: %s%.1f%s \r" % (DHEat.WHITEB, spinner[spinner_index], DHEat.CLEAR, DHEat.WHITEB, seconds_running, DHEat.CLEAR, DHEat.WHITEB, num_attempted_connections, DHEat.CLEAR, DHEat.WHITEB, num_opened_connections, DHEat.CLEAR, DHEat.BLUEB, num_attempted_connections / seconds_running, DHEat.CLEAR, DHEat.BLUEB, num_opened_connections / seconds_running, DHEat.CLEAR), end="")
|
print(f"{DHEat.WHITEB}{spinner[spinner_index]}{DHEat.CLEAR} Run time: {DHEat.WHITEB}{seconds_running:,.1f}{DHEat.CLEAR}; TCP SYNs: {DHEat.WHITEB}{num_attempted_connections:,d}{DHEat.CLEAR}; Compl. conns: {DHEat.WHITEB}{num_opened_connections:,d}{DHEat.CLEAR}; TCP SYNs/sec: {DHEat.BLUEB}{num_attempted_connections / seconds_running:,.1f}{DHEat.CLEAR}; Compl. conns/sec: {DHEat.BLUEB}{num_opened_connections / seconds_running:,.1f}{DHEat.CLEAR} \r", end="")
|
||||||
last_update = now
|
last_update = now
|
||||||
spinner_index = (spinner_index + 1) % 4
|
spinner_index = (spinner_index + 1) % 4
|
||||||
|
|
||||||
@@ -467,8 +474,14 @@ class DHEat:
|
|||||||
num_opened_connections += 1
|
num_opened_connections += 1
|
||||||
# out.d("Number of opened connections: %u (max: %u)." % (num_opened_connections, max_connections))
|
# out.d("Number of opened connections: %u (max: %u)." % (num_opened_connections, max_connections))
|
||||||
elif buf == b"Exceeded":
|
elif buf == b"Exceeded":
|
||||||
num_exceeded_maxstartups += 1
|
num_em += 1
|
||||||
# out.d("Number of \"Exceeded MaxStartups\": %u" % num_exceeded_maxstartups)
|
# out.d("Number of \"Exceeded MaxStartups\": %u" % num_exceeded_maxstartups)
|
||||||
|
elif buf == b"Not allo":
|
||||||
|
num_naatt += 1
|
||||||
|
# out.d("Number of \"Not allowed at this time\": {num_naatt:,d}")
|
||||||
|
else:
|
||||||
|
num_invalid_banners += 1
|
||||||
|
# out.d("Number of invalid banners: {num_invalid_banners:,d}")
|
||||||
|
|
||||||
_close_socket(socket_dict, s)
|
_close_socket(socket_dict, s)
|
||||||
|
|
||||||
@@ -720,35 +733,39 @@ class DHEat:
|
|||||||
# Print extensive statistics on what just happened.
|
# Print extensive statistics on what just happened.
|
||||||
seconds_running = time.time() - self.start_timer
|
seconds_running = time.time() - self.start_timer
|
||||||
print("\n\n")
|
print("\n\n")
|
||||||
print(" %s %sSTATISTICS%s %s" % (self.BAR_CHART, self.WHITEB, self.CLEAR, self.CHART_UPWARDS))
|
print(f" {self.BAR_CHART} {self.WHITEB}STATISTICS{self.CLEAR} {self.CHART_UPWARDS}")
|
||||||
print(" %s----------%s" % (self.WHITEB, self.CLEAR))
|
print(f" {self.WHITEB}----------{self.CLEAR}")
|
||||||
print()
|
print()
|
||||||
print(" Run time: %s%.1f seconds%s" % (self.WHITEB, seconds_running, self.CLEAR))
|
print(f" Run time: {self.WHITEB}{seconds_running:,.1f} seconds{self.CLEAR}")
|
||||||
print()
|
print()
|
||||||
print(" Attempted TCP connections: %s%.1f/sec, %u total%s" % (self.WHITEB, self.num_attempted_tcp_connections / seconds_running, self.num_attempted_tcp_connections, self.CLEAR))
|
print(f" Attempted TCP connections: {self.WHITEB}{self.num_attempted_tcp_connections / seconds_running:,.1f}/sec, {self.num_attempted_tcp_connections:,d} total{self.CLEAR}")
|
||||||
print(" Successful TCP connections: %s%.1f/sec, %u total%s" % (self.WHITEB, self.num_successful_tcp_connections / seconds_running, self.num_successful_tcp_connections, self.CLEAR))
|
print(f" Successful TCP connections: {self.WHITEB}{self.num_successful_tcp_connections / seconds_running:,.1f}/sec, {self.num_successful_tcp_connections:,d} total{self.CLEAR}")
|
||||||
print()
|
print()
|
||||||
print(" Bytes written: %s%s/sec, %s total%s" % (self.WHITEB, DHEat.add_byte_units(self.num_bytes_written / seconds_running), DHEat.add_byte_units(self.num_bytes_written), self.CLEAR))
|
print(f" Bytes written: {self.WHITEB}{DHEat.add_byte_units(self.num_bytes_written / seconds_running)}/sec, {DHEat.add_byte_units(self.num_bytes_written)} total{self.CLEAR}")
|
||||||
print()
|
print()
|
||||||
print(" Successful DH KEX replies: %s%.1f/sec, %u total%s" % (self.WHITEB, self.num_successful_dh_kex / seconds_running, self.num_successful_dh_kex, self.CLEAR))
|
print(f" Successful DH KEX replies: {self.WHITEB}{self.num_successful_dh_kex / seconds_running:,.1f}/sec, {self.num_successful_dh_kex:,d} total{self.CLEAR}")
|
||||||
print(" Unexpected DH KEX replies: %s%.1f/sec, %u total%s" % (self.WHITEB, self.num_failed_dh_kex / seconds_running, self.num_failed_dh_kex, self.CLEAR))
|
print(f" Unexpected DH KEX replies: {self.WHITEB}{self.num_failed_dh_kex / seconds_running:,.1f}/sec, {self.num_failed_dh_kex:,d} total{self.CLEAR}")
|
||||||
print("\"Exceeded MaxStartups\" replies*: %s%.1f/sec, %u total%s" % (self.WHITEB, self.num_openssh_throttled_connections / seconds_running, self.num_openssh_throttled_connections, self.CLEAR))
|
print(f" \"Exceeded MaxStartups\" replies*: {self.WHITEB}{self.num_em / seconds_running:,.1f}/sec, {self.num_em:,d} total{self.CLEAR}")
|
||||||
|
print(f"\"Not allowed at this time\" replies+: {self.WHITEB}{self.num_naatt / seconds_running:,.1f}/sec, {self.num_naatt:,d} total{self.CLEAR}")
|
||||||
|
print(f" Invalid banners: {self.WHITEB}{self.num_invalid_banners / seconds_running:,.1f}/sec, {self.num_invalid_banners:,.1f} total{self.CLEAR}")
|
||||||
print()
|
print()
|
||||||
print(" Connection timeouts: %s%.1f/sec, %u total%s (timeout setting: %.1f sec)" % (self.WHITEB, self.num_connect_timeouts / seconds_running, self.num_connect_timeouts, self.CLEAR, self.connect_timeout))
|
print(f" Connection timeouts: {self.WHITEB}{self.num_connect_timeouts / seconds_running:,.1f}/sec, {self.num_connect_timeouts:,d} total{self.CLEAR} (timeout setting: {self.connect_timeout:,.1f} sec)")
|
||||||
print(" Read timeouts: %s%.1f/sec, %u total%s (timeout setting: %.1f sec)" % (self.WHITEB, self.num_read_timeouts / seconds_running, self.num_read_timeouts, self.CLEAR, self.read_timeout))
|
print(f" Read timeouts: {self.WHITEB}{self.num_read_timeouts / seconds_running:,.1f}/sec, {self.num_read_timeouts:,d} total{self.CLEAR} (timeout setting: {self.read_timeout:,.1f} sec)")
|
||||||
print(" Socket exceptions: %s%.1f/sec, %u total%s" % (self.WHITEB, self.num_socket_exceptions / seconds_running, self.num_socket_exceptions, self.CLEAR))
|
print(f" Socket exceptions: {self.WHITEB}{self.num_socket_exceptions / seconds_running:,.1f}/sec, {self.num_socket_exceptions:,d} total{self.CLEAR}")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
if seconds_running < 5.0:
|
if seconds_running < 5.0:
|
||||||
print("%sTotal run time was under 5 seconds; try running it for longer to get more accurate analysis.%s" % (DHEat.YELLOWB, DHEat.CLEAR))
|
print(f"{DHEat.YELLOWB}Total run time was under 5 seconds; try running it for longer to get more accurate analysis.{DHEat.CLEAR}")
|
||||||
elif self.num_successful_tcp_connections / seconds_running < DHEat.MAX_SAFE_RATE:
|
elif self.num_successful_tcp_connections / seconds_running < DHEat.MAX_SAFE_RATE:
|
||||||
print("Because the number of successful TCP connections per second (%.1f) is less than %.1f, it appears that the target %sis using rate limiting%s to prevent CPU exaustion." % (self.num_successful_tcp_connections / seconds_running, DHEat.MAX_SAFE_RATE, DHEat.GREENB, DHEat.CLEAR))
|
print(f"Because the number of successful TCP connections per second ({self.num_successful_tcp_connections / seconds_running:,.1f}) is less than {DHEat.MAX_SAFE_RATE:,.1f}, it appears that the target {DHEat.GREENB}is using rate limiting{DHEat.CLEAR} to prevent CPU exaustion.")
|
||||||
else:
|
else:
|
||||||
print("Because the number of successful TCP connections per second (%.1f) is greater than %.1f, it appears that the target %sis NOT using rate limiting%s to prevent CPU exaustion." % (self.num_successful_tcp_connections / seconds_running, DHEat.MAX_SAFE_RATE, DHEat.REDB, DHEat.CLEAR))
|
print(f"Because the number of successful TCP connections per second ({self.num_successful_tcp_connections / seconds_running:,.1f}) is greater than {DHEat.MAX_SAFE_RATE:,.1f}, it appears that the target {DHEat.REDB}is NOT using rate limiting{DHEat.CLEAR} to prevent CPU exaustion.")
|
||||||
|
|
||||||
print()
|
print()
|
||||||
print()
|
print()
|
||||||
print(" * OpenSSH has a throttling mechanism (controlled by the MaxStartups directive) to prevent too many pre-authentication connections from overwhelming the server. When triggered, the server will probabilistically return \"Exceeded MaxStartups\" instead of the usual SSH banner, then terminate the connection. In order to maximize the DoS effectiveness, this metric should be greater than zero, though the ideal rate of rejections depends on the target server's CPU resources.")
|
print(" [*] Prior to OpenSSH 9.8, the sole connection throttling mechanism is controlled by the MaxStartups directive. This prevents too many pre-authentication connections from overwhelming the server. When triggered, the server will probabilistically return \"Exceeded MaxStartups\" instead of the usual SSH banner, then terminate the connection. In order to maximize the DoS effectiveness, this metric should be greater than zero, though the ideal rate of rejections depends on the target server's CPU resources.")
|
||||||
|
print()
|
||||||
|
print(" [+] Starting in OpenSSH 9.8, a more fine-gained connection throttling mechanism was added via the PerSourcePenalties directive. When triggered, the server returns \"Not allowed at this time\" instead of the usual SSH banner. This countermeasure is more effective than simply relying on the MaxStartups directive, and seems to block this attack under its default settings.")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
|
|
||||||
@@ -775,7 +792,6 @@ class DHEat:
|
|||||||
def _run(self) -> bool:
|
def _run(self) -> bool:
|
||||||
'''Where all the magic happens.'''
|
'''Where all the magic happens.'''
|
||||||
|
|
||||||
|
|
||||||
self.output()
|
self.output()
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
self.output("%sWARNING:%s this feature has not been thoroughly tested on Windows. It may perform worse than on UNIX OSes." % (self.YELLOWB, self.CLEAR))
|
self.output("%sWARNING:%s this feature has not been thoroughly tested on Windows. It may perform worse than on UNIX OSes." % (self.YELLOWB, self.CLEAR))
|
||||||
@@ -887,14 +903,16 @@ class DHEat:
|
|||||||
self.num_connect_timeouts += thread_statistics['num_connect_timeouts']
|
self.num_connect_timeouts += thread_statistics['num_connect_timeouts']
|
||||||
self.num_read_timeouts += thread_statistics['num_read_timeouts']
|
self.num_read_timeouts += thread_statistics['num_read_timeouts']
|
||||||
self.num_socket_exceptions += thread_statistics['num_socket_exceptions']
|
self.num_socket_exceptions += thread_statistics['num_socket_exceptions']
|
||||||
self.num_openssh_throttled_connections += thread_statistics['num_openssh_throttled_connections']
|
self.num_em += thread_statistics['num_em']
|
||||||
|
self.num_naatt += thread_statistics['num_naatt']
|
||||||
|
self.num_invalid_banners += thread_statistics['num_invalid_banners']
|
||||||
except queue.Empty: # If Queue.get() timeout exceeded.
|
except queue.Empty: # If Queue.get() timeout exceeded.
|
||||||
pass
|
pass
|
||||||
|
|
||||||
now = time.time()
|
now = time.time()
|
||||||
if (now - last_update) >= 1.0:
|
if (now - last_update) >= 1.0:
|
||||||
seconds_running = now - self.start_timer
|
seconds_running = now - self.start_timer
|
||||||
print("%s%s%s TCP SYNs/sec: %s%u%s; Compl. conns/sec: %s%u%s; Bytes sent/sec: %s%s%s; DH kex/sec: %s%u%s \r" % (self.WHITEB, spinner[spinner_index], self.CLEAR, self.BLUEB, self.num_attempted_tcp_connections / seconds_running, self.CLEAR, self.BLUEB, self.num_successful_tcp_connections / seconds_running, self.CLEAR, self.BLUEB, DHEat.add_byte_units(self.num_bytes_written / seconds_running), self.CLEAR, self.PURPLEB, self.num_successful_dh_kex / seconds_running, self.CLEAR), end="")
|
print(f"{self.WHITEB}{spinner[spinner_index]}{self.CLEAR} TCP SYNs/sec: {self.BLUEB}{int(self.num_attempted_tcp_connections / seconds_running):,d}{self.CLEAR}; Compl. conns/sec: {self.BLUEB}{int(self.num_successful_tcp_connections / seconds_running):,d}{self.CLEAR}; Bytes sent/sec: {self.BLUEB}{DHEat.add_byte_units(self.num_bytes_written / seconds_running)}{self.CLEAR}; DH kex/sec: {self.PURPLEB}{self.num_successful_dh_kex / seconds_running:,.1f}{self.CLEAR} \r", end="")
|
||||||
last_update = now
|
last_update = now
|
||||||
spinner_index = (spinner_index + 1) % 4
|
spinner_index = (spinner_index + 1) % 4
|
||||||
|
|
||||||
@@ -942,13 +960,16 @@ class DHEat:
|
|||||||
num_connect_timeouts = 0
|
num_connect_timeouts = 0
|
||||||
num_read_timeouts = 0
|
num_read_timeouts = 0
|
||||||
num_socket_exceptions = 0
|
num_socket_exceptions = 0
|
||||||
num_openssh_throttled_connections = 0
|
num_em = 0 # Number of "Exceeded MaxStartups" messages received.
|
||||||
|
num_naatt = 0 # Number of "Not allowed at this time" messages received.
|
||||||
|
num_invalid_banners = 0
|
||||||
|
|
||||||
num_loops_since_last_statistics_sync = 0
|
num_loops_since_last_statistics_sync = 0
|
||||||
while True:
|
while True:
|
||||||
num_loops_since_last_statistics_sync += 1
|
num_loops_since_last_statistics_sync += 1
|
||||||
|
|
||||||
# Instead of flooding the parent process with statistics, report our stats only every 5 connections.
|
# Instead of flooding the parent process with statistics, report our stats only every 5 connections.
|
||||||
|
# TODO: this sends too quickly in the case of application-level rate limiting, resulting in client-side CPU exhaustion instead! :O Perhaps a dynamic number of loops should be skipped, based on what would result in 1 message per 2-3 seconds.
|
||||||
if num_loops_since_last_statistics_sync > 5:
|
if num_loops_since_last_statistics_sync > 5:
|
||||||
num_loops_since_last_statistics_sync = 0
|
num_loops_since_last_statistics_sync = 0
|
||||||
|
|
||||||
@@ -961,7 +982,9 @@ class DHEat:
|
|||||||
'num_connect_timeouts': num_connect_timeouts,
|
'num_connect_timeouts': num_connect_timeouts,
|
||||||
'num_read_timeouts': num_read_timeouts,
|
'num_read_timeouts': num_read_timeouts,
|
||||||
'num_socket_exceptions': num_socket_exceptions,
|
'num_socket_exceptions': num_socket_exceptions,
|
||||||
'num_openssh_throttled_connections': num_openssh_throttled_connections,
|
'num_em': num_em,
|
||||||
|
'num_naatt': num_naatt,
|
||||||
|
'num_invalid_banners': num_invalid_banners
|
||||||
})
|
})
|
||||||
|
|
||||||
# Since we sent our statistics, reset them all back to zero.
|
# Since we sent our statistics, reset them all back to zero.
|
||||||
@@ -973,7 +996,9 @@ class DHEat:
|
|||||||
num_connect_timeouts = 0
|
num_connect_timeouts = 0
|
||||||
num_read_timeouts = 0
|
num_read_timeouts = 0
|
||||||
num_socket_exceptions = 0
|
num_socket_exceptions = 0
|
||||||
num_openssh_throttled_connections = 0
|
num_em = 0
|
||||||
|
num_naatt = 0
|
||||||
|
num_invalid_banners = 0
|
||||||
|
|
||||||
s = socket.socket(target_address_family, socket.SOCK_STREAM)
|
s = socket.socket(target_address_family, socket.SOCK_STREAM)
|
||||||
s.settimeout(connect_timeout)
|
s.settimeout(connect_timeout)
|
||||||
@@ -1016,11 +1041,16 @@ class DHEat:
|
|||||||
if banner.startswith(b"SSH-2.0-") or banner.startswith(b"SSH-1"):
|
if banner.startswith(b"SSH-2.0-") or banner.startswith(b"SSH-1"):
|
||||||
num_successful_tcp_connections += 1
|
num_successful_tcp_connections += 1
|
||||||
elif banner == b'Exceeded MaxStartups':
|
elif banner == b'Exceeded MaxStartups':
|
||||||
num_openssh_throttled_connections += 1
|
num_em += 1
|
||||||
|
_close_socket(s)
|
||||||
|
continue
|
||||||
|
elif banner == b'Not allowed at this time':
|
||||||
|
num_naatt += 1
|
||||||
_close_socket(s)
|
_close_socket(s)
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
self.debug("Invalid banner received: %r" % banner)
|
num_invalid_banners += 1
|
||||||
|
# self.debug("Invalid banner received: %r" % banner)
|
||||||
_close_socket(s)
|
_close_socket(s)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (C) 2017-2024 Joe Testa (jtesta@positronsecurity.com)
|
Copyright (C) 2017-2026 Joe Testa (jtesta@positronsecurity.com)
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -22,10 +22,10 @@
|
|||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
"""
|
"""
|
||||||
# The version to display.
|
# The version to display.
|
||||||
VERSION = 'v3.4.0-dev'
|
VERSION = 'v3.9.0'
|
||||||
|
|
||||||
# SSH software to impersonate
|
# SSH software to impersonate
|
||||||
SSH_HEADER = 'SSH-{0}-OpenSSH_8.2'
|
SSH_HEADER = 'SSH-{0}-OpenSSH_10.3'
|
||||||
|
|
||||||
# The URL to the Github issues tracker.
|
# The URL to the Github issues tracker.
|
||||||
GITHUB_ISSUES_URL = 'https://github.com/jtesta/ssh-audit/issues'
|
GITHUB_ISSUES_URL = 'https://github.com/jtesta/ssh-audit/issues'
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (C) 2025 Joe Testa (jtesta@positronsecurity.com)
|
Copyright (C) 2025-2026 Joe Testa (jtesta@positronsecurity.com)
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -347,6 +347,52 @@ class Hardening_Guides:
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
|
"Rocky Linux 10": [
|
||||||
|
{
|
||||||
|
"server_guide": True,
|
||||||
|
"version": 1,
|
||||||
|
"version_date": "2026-06-17",
|
||||||
|
"change_log": "Initial revision. In comparison to the Rocky Linux 9 hardening guide, the following changes were made: the key exchange list was set to include post-quantum algorithms only.",
|
||||||
|
"notes": "all commands below are to be executed as the root user.",
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"heading": "Disable RedHat Crypto Policies",
|
||||||
|
"comment": "",
|
||||||
|
"command": "rm -f /etc/ssh/sshd_config.d/40-redhat-crypto-policies.conf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"heading": "Re-generate the RSA and ED25519 keys",
|
||||||
|
"comment": "",
|
||||||
|
"command": "rm -f /etc/ssh/ssh_host_*\nssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N \"\"\nssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N \"\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"heading": "Restrict supported key exchange, cipher, and MAC algorithms",
|
||||||
|
"comment": "",
|
||||||
|
"command": "echo -e \"# Restrict key exchange, cipher, and MAC algorithms, as per sshaudit.com\\n# hardening guide.\\nHostKey /etc/ssh/ssh_host_ed25519_key\\nHostKey /etc/ssh/ssh_host_rsa_key\\n\\nKexAlgorithms mlkem768x25519-sha256,sntrup761x25519-sha512,sntrup761x25519-sha512@openssh.com\\n\\nCiphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\\n\\nMACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com\\n\\nHostKeyAlgorithms sk-ssh-ed25519-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\\n\\nRequiredRSASize 3072\\n\\nCASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\\n\\nGSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\\n\\nHostbasedAcceptedAlgorithms sk-ssh-ed25519-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-256\\n\\nPubkeyAcceptedAlgorithms sk-ssh-ed25519-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\\n\\n\" > /etc/ssh/sshd_config.d/rocky10_ssh-audit_hardening.conf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"heading": "Restart OpenSSH server",
|
||||||
|
"comment": "",
|
||||||
|
"command": "systemctl restart sshd"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"server_guide": False,
|
||||||
|
"version": 1,
|
||||||
|
"version_date": "2026-06-17",
|
||||||
|
"change_log": "Initial revision. In comparison to the Rocky Linux 9 hardening guide, the following changes were made: the key exchange list was set to include post-quantum algorithms only.",
|
||||||
|
"notes": "",
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"heading": "Run the following in a terminal to harden the SSH client for the local user:",
|
||||||
|
"comment": "",
|
||||||
|
"command": "mkdir -p -m 0700 ~/.ssh; echo -e \"\\nHost *\\n Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\\n\\n KexAlgorithms mlkem768x25519-sha256,sntrup761x25519-sha512,sntrup761x25519-sha512@openssh.com\\n\\n MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com\\n\\n RequiredRSASize 3072\\n\\n HostKeyAlgorithms sk-ssh-ed25519-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\\n\\n CASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\\n\\n GSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\\n\\n HostbasedAcceptedAlgorithms sk-ssh-ed25519-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-256\\n\\n PubkeyAcceptedAlgorithms sk-ssh-ed25519-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-256\\n\\n\" >> ~/.ssh/config"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
"Ubuntu 22.04": [
|
"Ubuntu 22.04": [
|
||||||
{
|
{
|
||||||
"server_guide": True,
|
"server_guide": True,
|
||||||
@@ -517,6 +563,47 @@ class Hardening_Guides:
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
|
"Ubuntu 26.04": [
|
||||||
|
{
|
||||||
|
"server_guide": True,
|
||||||
|
"version": 1,
|
||||||
|
"version_date": "2026-06-14",
|
||||||
|
"change_log": "Initial revision.",
|
||||||
|
"notes": "all commands below are to be executed as the root user.",
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"heading": "Re-generate the ED25519 and RSA keys",
|
||||||
|
"comment": "",
|
||||||
|
"command": "rm /etc/ssh/ssh_host_*\nssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N \"\"\nssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N \"\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"heading": "Restrict supported key exchange, cipher, and MAC algorithms",
|
||||||
|
"comment": "",
|
||||||
|
"command": "echo -e \"# Restrict key exchange, cipher, and MAC algorithms, as per sshaudit.com\\n# hardening guide.\\nKexAlgorithms mlkem768x25519-sha256,sntrup761x25519-sha512,sntrup761x25519-sha512@openssh.com\\n\\nCiphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\\n\\nMACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com\\n\\nRequiredRSASize 3072\\n\\nHostKeyAlgorithms sk-ssh-ed25519-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\\n\\nCASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\\n\\nGSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\\n\\nHostbasedAcceptedAlgorithms sk-ssh-ed25519-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\\n\\nPubkeyAcceptedAlgorithms sk-ssh-ed25519-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\" > /etc/ssh/sshd_config.d/ssh-audit_hardening.conf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"heading": "Restart OpenSSH server",
|
||||||
|
"comment": "",
|
||||||
|
"command": "service ssh restart"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"server_guide": False,
|
||||||
|
"version": 1,
|
||||||
|
"version_date": "2026-06-14",
|
||||||
|
"change_log": "Initial revision.",
|
||||||
|
"notes": "",
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"heading": "Run the following in a terminal to harden the SSH client for the local user:",
|
||||||
|
"comment": "",
|
||||||
|
"command": "mkdir -p -m 0700 ~/.ssh; echo -e \"\\nHost *\\n Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\\n\\n KexAlgorithms mlkem768x25519-sha256,sntrup761x25519-sha512,sntrup761x25519-sha512@openssh.com\\n\\n MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com\\n\\n RequiredRSASize 3072\\n\\n HostKeyAlgorithms sk-ssh-ed25519-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\\n\\n CASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\\n\\n GSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\\n\\n HostbasedAcceptedAlgorithms sk-ssh-ed25519-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-256\\n\\n PubkeyAcceptedAlgorithms sk-ssh-ed25519-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-256\\n\\n\" >> ~/.ssh/config"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
"Linux Mint 21": [
|
"Linux Mint 21": [
|
||||||
{
|
{
|
||||||
"server_guide": False,
|
"server_guide": False,
|
||||||
|
|||||||
@@ -72,10 +72,29 @@ class Software:
|
|||||||
oversion, opatch = mx.group(1), mx.group(2).strip()
|
oversion, opatch = mx.group(1), mx.group(2).strip()
|
||||||
else:
|
else:
|
||||||
oversion, opatch = other, ''
|
oversion, opatch = other, ''
|
||||||
if self.version < oversion:
|
|
||||||
return -1
|
# Attempt to parse the versions into floats.
|
||||||
elif self.version > oversion:
|
float_parse_error = False
|
||||||
return 1
|
selfversion_float = 0.0
|
||||||
|
oversion_float = 0.0
|
||||||
|
try:
|
||||||
|
selfversion_float = float(self.version)
|
||||||
|
oversion_float = float(oversion)
|
||||||
|
except ValueError:
|
||||||
|
float_parse_error = True
|
||||||
|
|
||||||
|
# If the versions could not be parsed into floats, use the old string compare method. This returns incorrect results when comparing modern OpenSSH versions ("9.6" > "10.4" evaluates to True!), but removing this may trigger other bugs, and we have no other good options here, soooo...
|
||||||
|
if float_parse_error:
|
||||||
|
if self.version < oversion:
|
||||||
|
return -1
|
||||||
|
elif self.version > oversion:
|
||||||
|
return 1
|
||||||
|
else: # Float parsing worked, so we'll use this instead. That way (9.6 > 10.4) properly evaluates to False.
|
||||||
|
if selfversion_float < oversion_float: # pylint: disable=else-if-used
|
||||||
|
return -1
|
||||||
|
elif selfversion_float > oversion_float:
|
||||||
|
return 1
|
||||||
|
|
||||||
spatch = self.patch or ''
|
spatch = self.patch or ''
|
||||||
if self.product == Product.DropbearSSH:
|
if self.product == Product.DropbearSSH:
|
||||||
if not re.match(r'^test\d.*$', opatch):
|
if not re.match(r'^test\d.*$', opatch):
|
||||||
|
|||||||
@@ -81,6 +81,11 @@ class SSH2_KexDB: # pylint: disable=too-few-public-methods
|
|||||||
INFO_EXTENSION_NEGOTIATION = 'pseudo-algorithm that denotes the peer supports RFC8308 extensions'
|
INFO_EXTENSION_NEGOTIATION = 'pseudo-algorithm that denotes the peer supports RFC8308 extensions'
|
||||||
INFO_STRICT_KEX = 'pseudo-algorithm that denotes the peer supports a stricter key exchange method as a counter-measure to the Terrapin attack (CVE-2023-48795)'
|
INFO_STRICT_KEX = 'pseudo-algorithm that denotes the peer supports a stricter key exchange method as a counter-measure to the Terrapin attack (CVE-2023-48795)'
|
||||||
|
|
||||||
|
# NIST PQC security levels: https://blog.cloudflare.com/pq-2025/
|
||||||
|
INFO_NIST_PQC_LEVEL_2 = 'rated at NIST PQC level 2 (at least as hard to break as SHA256)'
|
||||||
|
INFO_NIST_PQC_LEVEL_3 = 'rated at NIST PQC level 3 (at least as hard to break as AES-192)'
|
||||||
|
INFO_NIST_PQC_LEVEL_5 = 'rated at NIST PQC level 5 (at least as hard to break as AES-256)'
|
||||||
|
|
||||||
# Maintains a dictionary per calling thread that yields its own copy of MASTER_DB. This prevents results from one thread polluting the results of another thread.
|
# Maintains a dictionary per calling thread that yields its own copy of MASTER_DB. This prevents results from one thread polluting the results of another thread.
|
||||||
DB_PER_THREAD: Dict[int, Dict[str, Dict[str, List[List[Optional[str]]]]]] = {}
|
DB_PER_THREAD: Dict[int, Dict[str, Dict[str, List[List[Optional[str]]]]]] = {}
|
||||||
|
|
||||||
@@ -230,6 +235,9 @@ class SSH2_KexDB: # pylint: disable=too-few-public-methods
|
|||||||
'ecdsa-sha2-nistt571': [[], [FAIL_UNPROVEN], [WARN_RNDSIG_KEY]],
|
'ecdsa-sha2-nistt571': [[], [FAIL_UNPROVEN], [WARN_RNDSIG_KEY]],
|
||||||
'eddsa-e382-shake256@libassh.org': [[], [FAIL_UNPROVEN]],
|
'eddsa-e382-shake256@libassh.org': [[], [FAIL_UNPROVEN]],
|
||||||
'eddsa-e521-shake256@libassh.org': [[], [FAIL_UNPROVEN]],
|
'eddsa-e521-shake256@libassh.org': [[], [FAIL_UNPROVEN]],
|
||||||
|
'mldsa-44': [[], [], [], [INFO_NIST_PQC_LEVEL_2]],
|
||||||
|
'mldsa-65': [[], [], [], [INFO_NIST_PQC_LEVEL_3]],
|
||||||
|
'mldsa-87': [[], [], [], [INFO_NIST_PQC_LEVEL_5]],
|
||||||
'null': [[], [FAIL_PLAINTEXT]],
|
'null': [[], [FAIL_PLAINTEXT]],
|
||||||
'pgp-sign-dss': [[], [FAIL_1024BIT_MODULUS]],
|
'pgp-sign-dss': [[], [FAIL_1024BIT_MODULUS]],
|
||||||
'pgp-sign-rsa': [[], [FAIL_1024BIT_MODULUS]],
|
'pgp-sign-rsa': [[], [FAIL_1024BIT_MODULUS]],
|
||||||
@@ -259,6 +267,13 @@ class SSH2_KexDB: # pylint: disable=too-few-public-methods
|
|||||||
'ssh-gost2001': [[], [FAIL_UNTRUSTED]],
|
'ssh-gost2001': [[], [FAIL_UNTRUSTED]],
|
||||||
'ssh-gost2012-256': [[], [FAIL_UNTRUSTED]],
|
'ssh-gost2012-256': [[], [FAIL_UNTRUSTED]],
|
||||||
'ssh-gost2012-512': [[], [FAIL_UNTRUSTED]],
|
'ssh-gost2012-512': [[], [FAIL_UNTRUSTED]],
|
||||||
|
'ssh-mldsa-44': [[], [], [], [INFO_NIST_PQC_LEVEL_2]],
|
||||||
|
'ssh-mldsa-65': [[], [], [], [INFO_NIST_PQC_LEVEL_3]],
|
||||||
|
'ssh-mldsa-87': [[], [], [], [INFO_NIST_PQC_LEVEL_5]],
|
||||||
|
'ssh-mldsa44': [[], [], [], [INFO_NIST_PQC_LEVEL_2]],
|
||||||
|
'ssh-mldsa44-ed25519@openssh.com': [['10.4'], [], [], [INFO_NIST_PQC_LEVEL_2]],
|
||||||
|
'ssh-mldsa65': [[], [], [], [INFO_NIST_PQC_LEVEL_3]],
|
||||||
|
'ssh-mldsa87': [[], [], [], [INFO_NIST_PQC_LEVEL_5]],
|
||||||
'ssh-rsa1': [[], [FAIL_SHA1]],
|
'ssh-rsa1': [[], [FAIL_SHA1]],
|
||||||
'ssh-rsa': [['2.5.0,d0.28,l10.2'], [FAIL_SHA1], [], [INFO_DEPRECATED_IN_OPENSSH88]],
|
'ssh-rsa': [['2.5.0,d0.28,l10.2'], [FAIL_SHA1], [], [INFO_DEPRECATED_IN_OPENSSH88]],
|
||||||
'ssh-rsa-cert-v00@openssh.com': [['5.4', '6.9'], [FAIL_SHA1], [], [INFO_REMOVED_IN_OPENSSH70]],
|
'ssh-rsa-cert-v00@openssh.com': [['5.4', '6.9'], [FAIL_SHA1], [], [INFO_REMOVED_IN_OPENSSH70]],
|
||||||
|
|||||||
@@ -794,6 +794,7 @@ def process_commandline(out: OutputBuffer, args: List[str]) -> 'AuditConf': # p
|
|||||||
parser.add_argument("--list-hardening-guides", action="store_true", dest="list_hardening_guides", default=False, help="list all official, built-in hardening guides for common systems. Their full names can then be passed to --get-hardening-guide. Add -v to this option to view hardening guide change logs and prior versions.")
|
parser.add_argument("--list-hardening-guides", action="store_true", dest="list_hardening_guides", default=False, help="list all official, built-in hardening guides for common systems. Their full names can then be passed to --get-hardening-guide. Add -v to this option to view hardening guide change logs and prior versions.")
|
||||||
parser.add_argument("--lookup", action="store", dest="lookup", metavar="alg1[,alg2,...]", type=str, default=None, help="looks up an algorithm(s) without connecting to a server.")
|
parser.add_argument("--lookup", action="store", dest="lookup", metavar="alg1[,alg2,...]", type=str, default=None, help="looks up an algorithm(s) without connecting to a server.")
|
||||||
parser.add_argument("--skip-rate-test", action="store_true", dest="skip_rate_test", default=False, help="skip the connection rate test during standard audits (used to safely infer whether the DHEat attack is viable)")
|
parser.add_argument("--skip-rate-test", action="store_true", dest="skip_rate_test", default=False, help="skip the connection rate test during standard audits (used to safely infer whether the DHEat attack is viable)")
|
||||||
|
parser.add_argument("--socks5", action="store", dest="socks5_proxy", metavar="host:port", type=str, default=None, help="connect via a SOCKS5 proxy (implies --skip-rate-test)")
|
||||||
parser.add_argument("--threads", action="store", dest="threads", metavar="N", type=int, default=32, help="number of threads to use when scanning multiple targets (-T/--targets) (default: %(default)s)")
|
parser.add_argument("--threads", action="store", dest="threads", metavar="N", type=int, default=32, help="number of threads to use when scanning multiple targets (-T/--targets) (default: %(default)s)")
|
||||||
|
|
||||||
|
|
||||||
@@ -817,11 +818,17 @@ def process_commandline(out: OutputBuffer, args: List[str]) -> 'AuditConf': # p
|
|||||||
aconf.list_policies = argument.list_policies
|
aconf.list_policies = argument.list_policies
|
||||||
aconf.manual = argument.manual
|
aconf.manual = argument.manual
|
||||||
aconf.skip_rate_test = argument.skip_rate_test
|
aconf.skip_rate_test = argument.skip_rate_test
|
||||||
|
aconf.socks5_proxy = argument.socks5_proxy
|
||||||
oport = argument.oport
|
oport = argument.oport
|
||||||
|
|
||||||
if argument.batch is True:
|
if argument.batch is True:
|
||||||
aconf.batch = True
|
aconf.batch = True
|
||||||
|
|
||||||
|
# Skip the rate test if we're using a SOCKS5 proxy.
|
||||||
|
if aconf.socks5_proxy is not None:
|
||||||
|
out.d("Disabling rate test since SOCKS5 proxy is set.", write_now=True)
|
||||||
|
aconf.skip_rate_test = True
|
||||||
|
|
||||||
# If one -j was given, turn on JSON output. If -jj was given, enable indentation.
|
# If one -j was given, turn on JSON output. If -jj was given, enable indentation.
|
||||||
aconf.json = argument.json > 0
|
aconf.json = argument.json > 0
|
||||||
if argument.json > 1:
|
if argument.json > 1:
|
||||||
@@ -888,8 +895,8 @@ def process_commandline(out: OutputBuffer, args: List[str]) -> 'AuditConf': # p
|
|||||||
aconf.verbose = True
|
aconf.verbose = True
|
||||||
out.verbose = True
|
out.verbose = True
|
||||||
|
|
||||||
except argparse.ArgumentError as err:
|
except (argparse.ArgumentError, ValueError) as err:
|
||||||
out.fail(str(err), write_now=True)
|
out.fail(f"Error: {str(err)}", write_now=True)
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
sys.exit(exitcodes.UNKNOWN_ERROR)
|
sys.exit(exitcodes.UNKNOWN_ERROR)
|
||||||
|
|
||||||
@@ -1146,7 +1153,7 @@ def audit(out: OutputBuffer, aconf: AuditConf, print_target: bool = False) -> in
|
|||||||
out.debug = aconf.debug
|
out.debug = aconf.debug
|
||||||
out.level = aconf.level
|
out.level = aconf.level
|
||||||
out.use_colors = aconf.colors
|
out.use_colors = aconf.colors
|
||||||
s = SSH_Socket(out, aconf.host, aconf.port, aconf.ip_version_preference, aconf.timeout, aconf.timeout_set)
|
s = SSH_Socket(out, aconf.host, aconf.port, aconf.ip_version_preference, aconf.timeout, aconf.timeout_set, aconf.socks5_proxy)
|
||||||
|
|
||||||
if aconf.client_audit:
|
if aconf.client_audit:
|
||||||
out.v("Listening for client connection on port %d..." % aconf.port, write_now=True)
|
out.v("Listening for client connection on port %d..." % aconf.port, write_now=True)
|
||||||
|
|||||||
+108
-11
@@ -51,8 +51,7 @@ class SSH_Socket(ReadBuf, WriteBuf):
|
|||||||
|
|
||||||
SM_BANNER_SENT = 1
|
SM_BANNER_SENT = 1
|
||||||
|
|
||||||
|
def __init__(self, outputbuffer: 'OutputBuffer', host: Optional[str], port: int, ip_version_preference: List[int] = [], timeout: Union[int, float] = 5, timeout_set: bool = False, socks5_proxy: Optional[str] = None) -> None: # pylint: disable=dangerous-default-value
|
||||||
def __init__(self, outputbuffer: 'OutputBuffer', host: Optional[str], port: int, ip_version_preference: List[int] = [], timeout: Union[int, float] = 5, timeout_set: bool = False) -> None: # pylint: disable=dangerous-default-value
|
|
||||||
super(SSH_Socket, self).__init__()
|
super(SSH_Socket, self).__init__()
|
||||||
self.__outputbuffer = outputbuffer
|
self.__outputbuffer = outputbuffer
|
||||||
self.__sock: Optional[socket.socket] = None
|
self.__sock: Optional[socket.socket] = None
|
||||||
@@ -73,6 +72,7 @@ class SSH_Socket(ReadBuf, WriteBuf):
|
|||||||
self.__timeout_set = timeout_set
|
self.__timeout_set = timeout_set
|
||||||
self.client_host: Optional[str] = None
|
self.client_host: Optional[str] = None
|
||||||
self.client_port = None
|
self.client_port = None
|
||||||
|
self.__socks5_proxy = socks5_proxy # SOCKS5 proxy in "host:port" format, or None
|
||||||
|
|
||||||
|
|
||||||
def _resolve(self) -> Iterable[Tuple[int, Tuple[Any, ...]]]:
|
def _resolve(self) -> Iterable[Tuple[int, Tuple[Any, ...]]]:
|
||||||
@@ -161,8 +161,8 @@ class SSH_Socket(ReadBuf, WriteBuf):
|
|||||||
'''Returns None on success, or an error string.'''
|
'''Returns None on success, or an error string.'''
|
||||||
err = None
|
err = None
|
||||||
s = None
|
s = None
|
||||||
try:
|
|
||||||
|
|
||||||
|
try:
|
||||||
# If we're connecting to a UNIX socket.
|
# If we're connecting to a UNIX socket.
|
||||||
if self.__host.startswith("unix://"):
|
if self.__host.startswith("unix://"):
|
||||||
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||||
@@ -171,19 +171,24 @@ class SSH_Socket(ReadBuf, WriteBuf):
|
|||||||
self.__sock = s
|
self.__sock = s
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
# If we're connecting through a SOCKS5 proxy.
|
||||||
|
if self.__socks5_proxy is not None:
|
||||||
|
self.__sock = self._connect_via_socks5()
|
||||||
|
return None
|
||||||
|
|
||||||
# We're connecting to an Internet host.
|
# We're connecting to an Internet host.
|
||||||
else:
|
for af, addr in self._resolve():
|
||||||
for af, addr in self._resolve():
|
s = socket.socket(af, socket.SOCK_STREAM)
|
||||||
s = socket.socket(af, socket.SOCK_STREAM)
|
s.settimeout(self.__timeout)
|
||||||
s.settimeout(self.__timeout)
|
self.__outputbuffer.d(("Connecting to %s:%d..." % ('[%s]' % addr[0] if Utils.is_ipv6_address(addr[0]) else addr[0], addr[1])), write_now=True)
|
||||||
self.__outputbuffer.d(("Connecting to %s:%d..." % ('[%s]' % addr[0] if Utils.is_ipv6_address(addr[0]) else addr[0], addr[1])), write_now=True)
|
s.connect(addr)
|
||||||
s.connect(addr)
|
self.__sock = s
|
||||||
self.__sock = s
|
return None
|
||||||
return None
|
|
||||||
|
|
||||||
except socket.error as e:
|
except socket.error as e:
|
||||||
err = e
|
err = e
|
||||||
self._close_socket(s)
|
self._close_socket(s)
|
||||||
|
|
||||||
if err is None:
|
if err is None:
|
||||||
errm = 'host {} has no DNS records'.format(self.__host)
|
errm = 'host {} has no DNS records'.format(self.__host)
|
||||||
else:
|
else:
|
||||||
@@ -193,6 +198,98 @@ class SSH_Socket(ReadBuf, WriteBuf):
|
|||||||
return '[exception] {}'.format(errm)
|
return '[exception] {}'.format(errm)
|
||||||
|
|
||||||
|
|
||||||
|
def _connect_via_socks5(self) -> socket.socket:
|
||||||
|
'''Connect to the target host:port via a SOCKS5 proxy. Returns a socket on success, or raises an exception.'''
|
||||||
|
|
||||||
|
def __socks5_recv_exact(s: socket.socket, n: int) -> Optional[bytes]:
|
||||||
|
'''Read exactly n bytes from socket s, returning None on EOF.'''
|
||||||
|
buf = b''
|
||||||
|
while len(buf) < n:
|
||||||
|
chunk = s.recv(n - len(buf))
|
||||||
|
if not chunk:
|
||||||
|
return None
|
||||||
|
buf += chunk
|
||||||
|
return buf
|
||||||
|
|
||||||
|
# Parse the "host:port" string into its parts.
|
||||||
|
proxy_host, proxy_port = Utils.parse_host_and_port(self.__socks5_proxy) if self.__socks5_proxy is not None else ("", 0)
|
||||||
|
|
||||||
|
self.__outputbuffer.d("Connecting to SOCKS5 proxy %s:%d..." % (proxy_host, proxy_port), write_now=True)
|
||||||
|
s = socket.create_connection((proxy_host, proxy_port), timeout=self.__timeout)
|
||||||
|
|
||||||
|
# SOCKS5 greeting: version=5, nmethods=1, method=0 (no auth)
|
||||||
|
s.sendall(b'\x05\x01\x00')
|
||||||
|
resp = __socks5_recv_exact(s, 2)
|
||||||
|
if resp is None:
|
||||||
|
raise socket.error("no response from SOCKS5 proxy during handshake")
|
||||||
|
if resp[0] != 5:
|
||||||
|
raise socket.error("SOCKS5 proxy returned unexpected version: {}".format(resp[0]))
|
||||||
|
if resp[1] == 0xff:
|
||||||
|
raise socket.error("SOCKS5 proxy rejected all authentication methods")
|
||||||
|
if resp[1] != 0:
|
||||||
|
raise socket.error("SOCKS5 proxy requires authentication (method {:d}), but only no-auth is supported".format(resp[1]))
|
||||||
|
|
||||||
|
# Set the type and host encoding appropriately, depending on if we're sending a hostname, IPv4, or IPv6 address. The ATYP field is 3 when the client is sending a hostname.
|
||||||
|
atyp = 3
|
||||||
|
_enc_host = self.__host.encode('idna')
|
||||||
|
dst_addr = struct.pack('!B', len(_enc_host)) + _enc_host
|
||||||
|
if Utils.is_ipv4_address(self.__host):
|
||||||
|
atyp = 1
|
||||||
|
dst_addr = socket.inet_pton(socket.AF_INET, self.__host)
|
||||||
|
elif Utils.is_ipv6_address(self.__host):
|
||||||
|
atyp = 4
|
||||||
|
dst_addr = socket.inet_pton(socket.AF_INET6, self.__host)
|
||||||
|
|
||||||
|
# SOCKS5 connect request: version=5, cmd=1 (connect), rsv=0, atyp
|
||||||
|
request = struct.pack('!BBBB', 5, 1, 0, atyp) + dst_addr + struct.pack('!H', self.__port)
|
||||||
|
self.__outputbuffer.d("Requesting SOCKS5 proxy to connect to %s:%d..." % (self.__host, self.__port), write_now=True)
|
||||||
|
s.sendall(request)
|
||||||
|
|
||||||
|
# Read the fixed part of the response (4 bytes: ver, rep, rsv, atyp)
|
||||||
|
hdr = __socks5_recv_exact(s, 4)
|
||||||
|
if hdr is None:
|
||||||
|
raise socket.error("no response from SOCKS5 proxy during connect")
|
||||||
|
|
||||||
|
server_version = hdr[0]
|
||||||
|
reply = hdr[1]
|
||||||
|
# rsv = hdr[2] # Reserved, set to 0 as per RFC1928.
|
||||||
|
atyp = hdr[3]
|
||||||
|
|
||||||
|
if server_version != 5:
|
||||||
|
raise socket.error("SOCKS5 proxy returned unexpected version in connect response: {}".format(hdr[0]))
|
||||||
|
|
||||||
|
if reply != 0:
|
||||||
|
socks5_errors = {
|
||||||
|
1: "general SOCKS server failure",
|
||||||
|
2: "connection not allowed by ruleset",
|
||||||
|
3: "network unreachable",
|
||||||
|
4: "host unreachable",
|
||||||
|
5: "connection refused",
|
||||||
|
6: "TTL expired",
|
||||||
|
7: "command not supported",
|
||||||
|
8: "address type not supported",
|
||||||
|
}
|
||||||
|
|
||||||
|
err = socks5_errors[reply] if reply in socks5_errors else f"unknown error: {reply}"
|
||||||
|
raise socket.error("SOCKS5 proxy connect failed: {}".format(err))
|
||||||
|
|
||||||
|
# Read and discard the bound address from the response
|
||||||
|
if atyp == 1: # IPv4
|
||||||
|
__socks5_recv_exact(s, 4 + 2)
|
||||||
|
elif atyp == 4: # IPv6
|
||||||
|
__socks5_recv_exact(s, 16 + 2)
|
||||||
|
elif atyp == 3: # domain name
|
||||||
|
alen_data = __socks5_recv_exact(s, 1)
|
||||||
|
if alen_data is None:
|
||||||
|
raise socket.error("truncated SOCKS5 response")
|
||||||
|
__socks5_recv_exact(s, alen_data[0] + 2)
|
||||||
|
else:
|
||||||
|
raise socket.error("SOCKS5 proxy returned unknown address type: {}".format(atyp))
|
||||||
|
|
||||||
|
self.__outputbuffer.d("Successfully established SOCKS5 connection.", write_now=True)
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
def get_banner(self) -> Tuple[Optional['Banner'], List[str], Optional[str]]:
|
def get_banner(self) -> Tuple[Optional['Banner'], List[str], Optional[str]]:
|
||||||
self.__outputbuffer.d('Getting banner...', write_now=True)
|
self.__outputbuffer.d('Getting banner...', write_now=True)
|
||||||
|
|
||||||
|
|||||||
@@ -153,6 +153,17 @@ class Utils:
|
|||||||
|
|
||||||
return host, port
|
return host, port
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_ipv4_address(address: str) -> bool:
|
||||||
|
'''Returns True if address is an IPv4 address, otherwise False.'''
|
||||||
|
is_ipv4 = True
|
||||||
|
try:
|
||||||
|
ipaddress.IPv4Address(address)
|
||||||
|
except ipaddress.AddressValueError:
|
||||||
|
is_ipv4 = False
|
||||||
|
|
||||||
|
return is_ipv4
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def is_ipv6_address(address: str) -> bool:
|
def is_ipv6_address(address: str) -> bool:
|
||||||
'''Returns True if address is an IPv6 address, otherwise False.'''
|
'''Returns True if address is an IPv6 address, otherwise False.'''
|
||||||
|
|||||||
+6
-1
@@ -1,4 +1,4 @@
|
|||||||
.TH SSH-AUDIT 1 "June 14, 2026"
|
.TH SSH-AUDIT 1 "June 20, 2026"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
\fBssh-audit\fP \- SSH server & client configuration auditor
|
\fBssh-audit\fP \- SSH server & client configuration auditor
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
@@ -141,6 +141,11 @@ Runs a policy audit against a target using the specified policy (see \fBPOLICY A
|
|||||||
.br
|
.br
|
||||||
Skips the connection rate test during standard audits. By default, a few dozen TCP connections are created with the target host to see if connection throttling is implemented (this can safely infer whether the target is vulnerable to the DHEat attack; see CVE-2002-20001).
|
Skips the connection rate test during standard audits. By default, a few dozen TCP connections are created with the target host to see if connection throttling is implemented (this can safely infer whether the target is vulnerable to the DHEat attack; see CVE-2002-20001).
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B \-\-socks5 host:port
|
||||||
|
.br
|
||||||
|
Connects through a SOCKS5 proxy. Rate tests are automatically skipped (i.e.: implies \-\-skip\-rate\-test).
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B -t, \-\-timeout=<secs>
|
.B -t, \-\-timeout=<secs>
|
||||||
.br
|
.br
|
||||||
|
|||||||
@@ -165,3 +165,19 @@ class TestAuditConf:
|
|||||||
self._test_conf(conf, host='localhost', level='fail')
|
self._test_conf(conf, host='localhost', level='fail')
|
||||||
with pytest.raises(SystemExit):
|
with pytest.raises(SystemExit):
|
||||||
conf = c('-l something localhost')
|
conf = c('-l something localhost')
|
||||||
|
|
||||||
|
def test_audit_conf_process_commandline_socks5_proxy(self):
|
||||||
|
c = lambda x: self.process_commandline(self.OutputBuffer, x.split()) # noqa
|
||||||
|
|
||||||
|
assert c('--socks5 127.0.0.1:1080 localhost').socks5_proxy == '127.0.0.1:1080'
|
||||||
|
assert c('--socks5 [fe80::aaaa:bbbb:cccc:dddd]:1080 localhost').socks5_proxy == '[fe80::aaaa:bbbb:cccc:dddd]:1080'
|
||||||
|
assert c('--socks5 somehost.lol:1080 localhost').socks5_proxy == 'somehost.lol:1080'
|
||||||
|
|
||||||
|
with pytest.raises(SystemExit):
|
||||||
|
c('--socks5 localhost localhost')
|
||||||
|
|
||||||
|
with pytest.raises(SystemExit):
|
||||||
|
c('--socks5 localhost:0 localhost')
|
||||||
|
|
||||||
|
with pytest.raises(SystemExit):
|
||||||
|
c('--socks5 localhost:65536 localhost')
|
||||||
|
|||||||
Reference in New Issue
Block a user