mirror of
https://github.com/jtesta/ssh-audit.git
synced 2026-05-25 15:31:23 +02:00
Compare commits
14 Commits
v3.2.0
...
27f215d687
| Author | SHA1 | Date | |
|---|---|---|---|
| 27f215d687 | |||
| 9049c8476a | |||
| bbbdf71e50 | |||
| 92db5f0138 | |||
| bc2a89eb11 | |||
| ea117b203b | |||
| d8f8b7c57c | |||
| e42961fa9a | |||
| dcbc43acdf | |||
| 87e22ae26b | |||
| 3116c2e678 | |||
| 46ec4e3edc | |||
| d19b154a46 | |||
| c5d90106e8 |
@@ -202,7 +202,7 @@ To install from Dockerhub:
|
||||
```
|
||||
$ docker pull positronsecurity/ssh-audit
|
||||
```
|
||||
(Then run with: `docker run -it -p 2222:2222 positronsecurity/ssh-audit 10.1.1.1`)
|
||||
(Then run with: `docker run -it --rm -p 2222:2222 positronsecurity/ssh-audit 10.1.1.1`)
|
||||
|
||||
The status of various other platform packages can be found below (via Repology):
|
||||
|
||||
@@ -213,6 +213,14 @@ For convenience, a web front-end on top of the command-line tool is available at
|
||||
|
||||
## ChangeLog
|
||||
|
||||
### v3.3.0-dev (???)
|
||||
- Added built-in policies for Ubuntu 24.04 LTS server and client, and OpenSSH 9.8.
|
||||
- Added IPv6 support for DHEat and connection rate tests.
|
||||
- Added TCP port information to JSON policy scan results; credit [Fabian Malte Kopp](https://github.com/dreizehnutters).
|
||||
- Added LANcom LCOS server recognition and Ed448 key extraction; credit [Daniel Lenski](https://github.com/dlenskiSB).
|
||||
- Fixed crash when running with `-P` and `-T` options simultaneously.
|
||||
- Fixed host key tests from only reporting a key type at most once despite multiple hosts supporting it; credit [Daniel Lenski](https://github.com/dlenskiSB).
|
||||
|
||||
### v3.2.0 (2024-04-22)
|
||||
- Added implementation of the DHEat denial-of-service attack (see `--dheat` option; [CVE-2002-20001](https://nvd.nist.gov/vuln/detail/CVE-2002-20001)).
|
||||
- Expanded filter of CBC ciphers to flag for the Terrapin vulnerability. It now includes more rarely found ciphers.
|
||||
|
||||
@@ -49,6 +49,7 @@ BUILTIN_POLICIES: Dict[str, Dict[str, Union[Optional[str], Optional[List[str]],
|
||||
|
||||
'Hardened Ubuntu Server 22.04 LTS (version 5)': {'version': '5', 'changelog': 'Added kex-strict-s-v00@openssh.com to kex list.', '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 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},
|
||||
|
||||
# Generic OpenSSH Server policies
|
||||
|
||||
@@ -94,6 +95,8 @@ BUILTIN_POLICIES: Dict[str, Dict[str, Union[Optional[str], Optional[List[str]],
|
||||
|
||||
'Hardened OpenSSH Server v9.7 (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', 'ext-info-s', '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 OpenSSH Server v9.8 (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', 'ext-info-s', '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},
|
||||
|
||||
|
||||
# Amazon Linux Policies
|
||||
|
||||
@@ -109,7 +112,6 @@ BUILTIN_POLICIES: Dict[str, Dict[str, Union[Optional[str], Optional[List[str]],
|
||||
|
||||
'Hardened Rocky Linux Client 9 (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},
|
||||
|
||||
|
||||
# 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},
|
||||
@@ -120,5 +122,6 @@ BUILTIN_POLICIES: Dict[str, Dict[str, Union[Optional[str], Optional[List[str]],
|
||||
|
||||
'Hardened Ubuntu Client 22.04 LTS (version 4)': {'version': '4', 'changelog': 'Added kex-strict-c-v00@openssh.com to kex list.', '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 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},
|
||||
|
||||
}
|
||||
|
||||
+35
-8
@@ -160,6 +160,11 @@ class DHEat:
|
||||
# The SSH2_Kex object that we recieved from the server in a prior connection. We'll use it as a template to craft our own kex.
|
||||
self.kex = kex
|
||||
|
||||
# Resolve the target to an IP address depending on the user preferences (IPv4 or IPv6).
|
||||
self.debug("Resolving target %s..." % self.target)
|
||||
self.target_address_family, self.target_ip_address = DHEat._resolve_hostname(self.target, aconf.ip_version_preference)
|
||||
self.debug("Resolved %s to %s (address family %u)" % (self.target, self.target_ip_address, self.target_address_family))
|
||||
|
||||
# The connection and read timeouts.
|
||||
self.connect_timeout = aconf.timeout
|
||||
self.read_timeout = aconf.timeout
|
||||
@@ -324,6 +329,11 @@ class DHEat:
|
||||
print("\n%sUnfortunately, this feature is not currently functional under Windows.%s This should get fixed in a future release. See: <https://github.com/jtesta/ssh-audit/issues/261>" % (DHEat.YELLOWB, DHEat.CLEAR))
|
||||
return ""
|
||||
|
||||
# Resolve the target into an IP address
|
||||
out.d("Resolving target %s..." % aconf.host)
|
||||
target_address_family, target_ip_address = DHEat._resolve_hostname(aconf.host, aconf.ip_version_preference)
|
||||
out.d("Resolved %s to %s (address family %u)" % (aconf.host, target_ip_address, target_address_family))
|
||||
|
||||
spinner = ["-", "\\", "|", "/"]
|
||||
spinner_index = 0
|
||||
|
||||
@@ -349,7 +359,7 @@ class DHEat:
|
||||
rate_str = " at a max rate of %s%u%s connections per second" % (DHEat.WHITEB, aconf.conn_rate_test_target_rate, DHEat.CLEAR)
|
||||
|
||||
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, aconf.host, aconf.port, DHEat.CLEAR, DHEat.WHITEB, concurrent_sockets, DHEat.CLEAR, rate_str))
|
||||
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()
|
||||
|
||||
# Make room for the multi-line output.
|
||||
@@ -426,11 +436,11 @@ class DHEat:
|
||||
|
||||
# Open new sockets until we've hit the number of concurrent sockets, or if we exceeded the number of maximum connections.
|
||||
while (len(socket_dict) < concurrent_sockets) and (len(socket_dict) + num_opened_connections < max_connections):
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s = socket.socket(target_address_family, socket.SOCK_STREAM)
|
||||
s.setblocking(False)
|
||||
|
||||
# out.d("Creating socket (%u of %u already exist)..." % (len(socket_dict), concurrent_sockets), write_now=True)
|
||||
ret = s.connect_ex((aconf.host, aconf.port))
|
||||
ret = s.connect_ex((target_ip_address, aconf.port))
|
||||
num_attempted_connections += 1
|
||||
if ret in [0, 115]: # Check if connection is successful or EINPROGRESS.
|
||||
socket_dict[s] = now
|
||||
@@ -743,6 +753,22 @@ class DHEat:
|
||||
print()
|
||||
|
||||
|
||||
@staticmethod
|
||||
def _resolve_hostname(host: str, ip_version_preference: List[int]) -> Tuple[int, str]:
|
||||
'''Resolves a hostname to its IPv4 or IPv6 address, depending on user preference.'''
|
||||
|
||||
family = socket.AF_UNSPEC
|
||||
if len(ip_version_preference) == 1:
|
||||
family = socket.AF_INET if ip_version_preference[0] == 4 else socket.AF_INET6
|
||||
|
||||
r = socket.getaddrinfo(host, 0, family, socket.SOCK_STREAM)
|
||||
for address_family, socktype, _, _, addr in r:
|
||||
if socktype == socket.SOCK_STREAM:
|
||||
return address_family, addr[0]
|
||||
|
||||
return -1, ''
|
||||
|
||||
|
||||
def _run(self) -> bool:
|
||||
'''Where all the magic happens.'''
|
||||
|
||||
@@ -751,7 +777,7 @@ class DHEat:
|
||||
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("Running DHEat test against %s[%s]:%u%s with %s%u%s concurrent sockets..." % (self.WHITEB, self.target, self.port, self.CLEAR, self.WHITEB, self.concurrent_connections, self.CLEAR))
|
||||
self.output("Running DHEat test against %s[%s]:%u%s with %s%u%s concurrent sockets..." % (self.WHITEB, self.target_ip_address, self.port, self.CLEAR, self.WHITEB, self.concurrent_connections, self.CLEAR))
|
||||
|
||||
# If the user didn't specify an exact kex algorithm to test, check our prioritized list against what the server supports. Larger p-values (such as group18: 8192-bits) cause the most strain on the server.
|
||||
chosen_alg = ""
|
||||
@@ -894,7 +920,8 @@ class DHEat:
|
||||
|
||||
# Copy variables from the object (which might exist in another process?). This might cut down on inter-process overhead.
|
||||
connect_timeout = self.connect_timeout
|
||||
target = self.target
|
||||
target_ip_address = self.target_ip_address
|
||||
target_address_family = self.target_address_family
|
||||
port = self.port
|
||||
|
||||
# Determine if we are attacking with a GEX.
|
||||
@@ -945,17 +972,17 @@ class DHEat:
|
||||
num_socket_exceptions = 0
|
||||
num_openssh_throttled_connections = 0
|
||||
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s = socket.socket(target_address_family, socket.SOCK_STREAM)
|
||||
s.settimeout(connect_timeout)
|
||||
|
||||
# Loop until a successful TCP connection is made.
|
||||
connected = False
|
||||
while not connected:
|
||||
|
||||
# self.debug("Connecting to %s:%d" % (self.target, self.port))
|
||||
# self.debug("Connecting to %s:%d" % (self.target_ip_address, self.port))
|
||||
try:
|
||||
num_attempted_tcp_connections += 1
|
||||
s.connect((target, port))
|
||||
s.connect((target_ip_address, port))
|
||||
connected = True
|
||||
except OSError as e:
|
||||
self.debug("Failed to connect: %s" % str(e))
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
THE SOFTWARE.
|
||||
"""
|
||||
# The version to display.
|
||||
VERSION = 'v3.2.0'
|
||||
VERSION = 'v3.3.0-dev'
|
||||
|
||||
# SSH software to impersonate
|
||||
SSH_HEADER = 'SSH-{0}-OpenSSH_8.2'
|
||||
|
||||
@@ -40,7 +40,7 @@ class HostKeyTest:
|
||||
# Tracks the RSA host key types. As of this writing, testing one in this family yields valid results for the rest.
|
||||
RSA_FAMILY = ['ssh-rsa', 'rsa-sha2-256', 'rsa-sha2-512']
|
||||
|
||||
# Dict holding the host key types we should extract & parse. 'cert' is True to denote that a host key type handles certificates (thus requires additional parsing). 'variable_key_len' is True for host key types that can have variable sizes (True only for RSA types, as the rest are of fixed-size). After the host key type is fully parsed, the key 'parsed' is added with a value of True.
|
||||
# Dict holding the host key types we should extract & parse. 'cert' is True to denote that a host key type handles certificates (thus requires additional parsing). 'variable_key_len' is True for host key types that can have variable sizes (True only for RSA types, as the rest are of fixed-size).
|
||||
HOST_KEY_TYPES = {
|
||||
'ssh-rsa': {'cert': False, 'variable_key_len': True},
|
||||
'rsa-sha2-256': {'cert': False, 'variable_key_len': True},
|
||||
@@ -52,6 +52,9 @@ class HostKeyTest:
|
||||
|
||||
'ssh-ed25519': {'cert': False, 'variable_key_len': False},
|
||||
'ssh-ed25519-cert-v01@openssh.com': {'cert': True, 'variable_key_len': False},
|
||||
|
||||
'ssh-ed448': {'cert': False, 'variable_key_len': False},
|
||||
# 'ssh-ed448-cert-v01@openssh.com': {'cert': True, 'variable_key_len': False},
|
||||
}
|
||||
|
||||
TWO2K_MODULUS_WARNING = '2048-bit modulus only provides 112-bits of symmetric strength'
|
||||
@@ -93,6 +96,7 @@ class HostKeyTest:
|
||||
def perform_test(out: 'OutputBuffer', s: 'SSH_Socket', server_kex: 'SSH2_Kex', kex_str: str, kex_group: 'KexDH', host_key_types: Dict[str, Dict[str, bool]]) -> None:
|
||||
hostkey_modulus_size = 0
|
||||
ca_modulus_size = 0
|
||||
parsed_host_key_types = set()
|
||||
|
||||
# If the connection still exists, close it so we can test
|
||||
# using a clean slate (otherwise it may exist in a non-testable
|
||||
@@ -106,7 +110,7 @@ class HostKeyTest:
|
||||
key_warn_comments = []
|
||||
|
||||
# Skip those already handled (i.e.: those in the RSA family, as testing one tests them all).
|
||||
if 'parsed' in host_key_types[host_key_type] and host_key_types[host_key_type]['parsed']:
|
||||
if host_key_type in parsed_host_key_types:
|
||||
continue
|
||||
|
||||
# If this host key type is supported by the server, we test it.
|
||||
@@ -157,6 +161,7 @@ class HostKeyTest:
|
||||
ca_key_type = kex_group.get_ca_type()
|
||||
ca_modulus_size = kex_group.get_ca_size()
|
||||
out.d("Hostkey type: [%s]; hostkey size: %u; CA type: [%s]; CA modulus size: %u" % (host_key_type, hostkey_modulus_size, ca_key_type, ca_modulus_size), write_now=True)
|
||||
out.d("Raw hostkey bytes (%d): [%s]" % (len(raw_hostkey_bytes), raw_hostkey_bytes.hex()), write_now=True)
|
||||
|
||||
# Record all the host key info.
|
||||
server_kex.set_host_key(host_key_type, raw_hostkey_bytes, hostkey_modulus_size, ca_key_type, ca_modulus_size)
|
||||
@@ -216,7 +221,7 @@ class HostKeyTest:
|
||||
# If this host key type is in the RSA family, then mark them all as parsed (since results in one are valid for them all).
|
||||
if host_key_type in HostKeyTest.RSA_FAMILY:
|
||||
for rsa_type in HostKeyTest.RSA_FAMILY:
|
||||
host_key_types[rsa_type]['parsed'] = True
|
||||
parsed_host_key_types.add(rsa_type)
|
||||
|
||||
# If the current key is a member of the RSA family, then populate all RSA family members with the same
|
||||
# failure and/or warning comments.
|
||||
@@ -228,7 +233,7 @@ class HostKeyTest:
|
||||
db['key'][rsa_type][2].extend(key_warn_comments)
|
||||
|
||||
else:
|
||||
host_key_types[host_key_type]['parsed'] = True
|
||||
parsed_host_key_types.add(host_key_type)
|
||||
db = SSH2_KexDB.get_db()
|
||||
while len(db['key'][host_key_type]) < 3:
|
||||
db['key'][host_key_type].append([])
|
||||
|
||||
@@ -134,6 +134,9 @@ class KexDH: # pragma: nocover
|
||||
if self.__hostkey_type == 'ssh-ed25519':
|
||||
self.out.d("%s has a fixed host key modulus of 32." % self.__hostkey_type)
|
||||
self.__hostkey_n_len = 32
|
||||
elif self.__hostkey_type == 'ssh-ed448':
|
||||
self.out.d("%s has a fixed host key modulus of 57." % self.__hostkey_type)
|
||||
self.__hostkey_n_len = 57
|
||||
else:
|
||||
# Here is the modulus size & actual modulus of the host key public key.
|
||||
hostkey_n, self.__hostkey_n_len, ptr = KexDH.__get_bytes(hostkey, ptr)
|
||||
|
||||
@@ -605,3 +605,29 @@ macs = %s
|
||||
dh_modulus_sizes_str = str(self._dh_modulus_sizes)
|
||||
|
||||
return "Name: %s\nVersion: %s\nAllow Algorithm Subset and/or Reordering: %r\nBanner: %s\nCompressions: %s\nHost Keys: %s\nOptional Host Keys: %s\nKey Exchanges: %s\nCiphers: %s\nMACs: %s\nHost Key Sizes: %s\nDH Modulus Sizes: %s\nServer Policy: %r" % (name, version, self._allow_algorithm_subset_and_reordering, banner, compressions_str, host_keys_str, optional_host_keys_str, kex_str, ciphers_str, macs_str, hostkey_sizes_str, dh_modulus_sizes_str, self._server_policy)
|
||||
|
||||
|
||||
def __getstate__(self) -> Dict[str, Any]:
|
||||
'''Called when pickling this object. The file descriptor isn't serializable, so we'll remove it from the state and include a string representation.'''
|
||||
|
||||
state = self.__dict__.copy()
|
||||
|
||||
if state['_warning_target'] == sys.stdout:
|
||||
state['_warning_target_type'] = 'stdout'
|
||||
else:
|
||||
state['_warning_target_type'] = 'stderr'
|
||||
|
||||
del state['_warning_target']
|
||||
return state
|
||||
|
||||
|
||||
def __setstate__(self, state: Dict[str, Any]) -> None:
|
||||
'''Called when unpickling this object. Based on the string representation of the file descriptor, we'll restore the right handle.'''
|
||||
|
||||
if state['_warning_target_type'] == 'stdout':
|
||||
state['_warning_target'] = sys.stdout
|
||||
else:
|
||||
state['_warning_target'] = sys.stderr
|
||||
|
||||
del state['_warning_target_type']
|
||||
self.__dict__.update(state)
|
||||
|
||||
@@ -224,4 +224,8 @@ class Software:
|
||||
mx = re.match(r'^PuTTY_Release_(.*)', software)
|
||||
if mx:
|
||||
return cls(None, Product.PuTTY, mx.group(1), None, None)
|
||||
mx = re.match(r'^lancom(.*)', software)
|
||||
if mx:
|
||||
v, p = 'LANcom', 'LCOS sshd'
|
||||
return cls(v, p, mx.group(1), None, None)
|
||||
return None
|
||||
|
||||
@@ -421,6 +421,8 @@ def output_recommendations(out: OutputBuffer, algs: Algorithms, algorithm_recomm
|
||||
|
||||
fn = level_to_output[level]
|
||||
|
||||
an = '?'
|
||||
sg = '?'
|
||||
if action == 'del':
|
||||
an, sg = 'remove', '-'
|
||||
ret = False
|
||||
@@ -733,7 +735,7 @@ def evaluate_policy(out: OutputBuffer, aconf: AuditConf, banner: Optional['Banne
|
||||
|
||||
passed, error_struct, error_str = aconf.policy.evaluate(banner, kex)
|
||||
if aconf.json:
|
||||
json_struct = {'host': aconf.host, 'policy': aconf.policy.get_name_and_version(), 'passed': passed, 'errors': error_struct}
|
||||
json_struct = {'host': aconf.host, 'port': aconf.port, 'policy': aconf.policy.get_name_and_version(), 'passed': passed, 'errors': error_struct}
|
||||
out.info(json.dumps(json_struct, indent=4 if aconf.json_print_indent else None, sort_keys=True))
|
||||
else:
|
||||
spacing = ''
|
||||
|
||||
@@ -35,6 +35,7 @@ class VersionVulnerabilityDB: # pylint: disable=too-few-public-methods
|
||||
# then affected = 1 + 4 = 5.
|
||||
CVE: Dict[str, List[List[Any]]] = {
|
||||
'Dropbear SSH': [
|
||||
['0.0', '2022.83', 1, 'CVE-2023-48795', 5.9, 'Terrapin attack allows bypassing integrity checks in SSH protocol'],
|
||||
['0.0', '2020.81', 2, 'CVE-2021-36369', 7.5, 'trivial authentication attack to bypass FIDO tokens and SSH-ASKPASS'],
|
||||
['0.0', '2018.76', 1, 'CVE-2018-15599', 5.0, 'remote users may enumerate users on the system'],
|
||||
['0.0', '2017.74', 5, 'CVE-2017-9079', 4.7, 'local users can read certain files as root'],
|
||||
@@ -67,6 +68,13 @@ class VersionVulnerabilityDB: # pylint: disable=too-few-public-methods
|
||||
['0.4.7', '0.5.2', 1, 'CVE-2012-4560', 7.5, 'cause DoS or execute arbitrary code (buffer overflow)'],
|
||||
['0.4.7', '0.5.2', 1, 'CVE-2012-4559', 6.8, 'cause DoS or execute arbitrary code (double free)']],
|
||||
'OpenSSH': [
|
||||
['1.0', '9.6', 1, 'CVE-2023-51767', 7.0, 'Row hammer threat allows authentication bypass in shared DRAM'],
|
||||
['1.0', '9.5', 2, 'CVE-2023-51385', 7.0, 'OS command injection through expansion tokens containing shell metacharacters'],
|
||||
['1.0', '9.5', 2, 'CVE-2023-51384', 5.5, 'ssh-agent applies destination constraints only to the first PKCS#11 key'],
|
||||
['1.0', '9.5', 1, 'CVE-2023-48795', 5.9, 'Terrapin attack allows bypassing integrity checks in SSH protocol'],
|
||||
['1.0', '9.2', 1, 'CVE-2023-38408', 9.8, 'PKCS#11 feature has a vulnerable search path in ssh-agent, enabling remote code execution'],
|
||||
['8.9', '9.2', 1, 'CVE-2023-28531', 9.8, 'ssh-add improperly adds smartcard keys to ssh-agent, bypassing intended destination restrictions'],
|
||||
['9.1', '9.1', 1, 'CVE-2023-25136', 6.5, 'A double-free vulnerability allows unauthenticated remote attackers to potentially execute code by manipulating memory'],
|
||||
['6.2', '8.7', 5, 'CVE-2021-41617', 7.0, 'privilege escalation via supplemental groups'],
|
||||
['1.0', '8.8', 2, 'CVE-2021-36368', 3.7, 'trivial authentication attack to bypass FIDO tokens and SSH-ASKPASS'],
|
||||
['8.2', '8.4', 2, 'CVE-2021-28041', 7.1, 'double free via ssh-agent'],
|
||||
@@ -140,6 +148,8 @@ class VersionVulnerabilityDB: # pylint: disable=too-few-public-methods
|
||||
['1.2.3', '2.1.1', 1, 'CVE-2001-0361', 4.0, 'recover plaintext from ciphertext'],
|
||||
['1.2', '2.1', 1, 'CVE-2000-0525', 10.0, 'execute arbitrary code (improper privileges)']],
|
||||
'PuTTY': [
|
||||
['0.68', '0.80', 2, 'CVE-2024-31497', 0.0, 'ecdsa private key recovery by malicious remote server'],
|
||||
['0.0', '0.79', 2, 'CVE-2023-48795', 5.9, 'Terrapin attack allows bypassing integrity checks in SSH protocol'],
|
||||
# info for CVE-2021-36367 - only PuTTY up to 0.71 is affected - see https://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/reject-trivial-auth.html
|
||||
['0.0', '0.71', 2, 'CVE-2021-36367', 8.1, 'trivial authentication attack to bypass FIDO tokens and SSH-ASKPASS'],
|
||||
['0.0', '0.74', 2, 'CVE-2021-33500', 5.0, 'denial of service of the complete windows desktop'],
|
||||
|
||||
@@ -2,5 +2,6 @@
|
||||
"errors": [],
|
||||
"host": "localhost",
|
||||
"passed": true,
|
||||
"policy": "Docker policy: test1 (version 1)"
|
||||
"policy": "Docker policy: test1 (version 1)",
|
||||
"port": 2222
|
||||
}
|
||||
|
||||
@@ -27,5 +27,6 @@
|
||||
],
|
||||
"host": "localhost",
|
||||
"passed": false,
|
||||
"policy": "Docker poliicy: test10 (version 1)"
|
||||
"policy": "Docker poliicy: test10 (version 1)",
|
||||
"port": 2222
|
||||
}
|
||||
|
||||
@@ -19,5 +19,6 @@
|
||||
],
|
||||
"host": "localhost",
|
||||
"passed": false,
|
||||
"policy": "Docker policy: test2 (version 1)"
|
||||
"policy": "Docker policy: test2 (version 1)",
|
||||
"port": 2222
|
||||
}
|
||||
|
||||
@@ -18,5 +18,6 @@
|
||||
],
|
||||
"host": "localhost",
|
||||
"passed": false,
|
||||
"policy": "Docker policy: test3 (version 1)"
|
||||
"policy": "Docker policy: test3 (version 1)",
|
||||
"port": 2222
|
||||
}
|
||||
|
||||
@@ -28,5 +28,6 @@
|
||||
],
|
||||
"host": "localhost",
|
||||
"passed": false,
|
||||
"policy": "Docker policy: test4 (version 1)"
|
||||
"policy": "Docker policy: test4 (version 1)",
|
||||
"port": 2222
|
||||
}
|
||||
|
||||
@@ -27,5 +27,6 @@
|
||||
],
|
||||
"host": "localhost",
|
||||
"passed": false,
|
||||
"policy": "Docker policy: test5 (version 1)"
|
||||
"policy": "Docker policy: test5 (version 1)",
|
||||
"port": 2222
|
||||
}
|
||||
|
||||
@@ -2,5 +2,6 @@
|
||||
"errors": [],
|
||||
"host": "localhost",
|
||||
"passed": true,
|
||||
"policy": "Docker poliicy: test7 (version 1)"
|
||||
"policy": "Docker poliicy: test7 (version 1)",
|
||||
"port": 2222
|
||||
}
|
||||
|
||||
@@ -15,5 +15,6 @@
|
||||
],
|
||||
"host": "localhost",
|
||||
"passed": false,
|
||||
"policy": "Docker poliicy: test8 (version 1)"
|
||||
"policy": "Docker poliicy: test8 (version 1)",
|
||||
"port": 2222
|
||||
}
|
||||
|
||||
@@ -15,5 +15,6 @@
|
||||
],
|
||||
"host": "localhost",
|
||||
"passed": false,
|
||||
"policy": "Docker poliicy: test9 (version 1)"
|
||||
"policy": "Docker poliicy: test9 (version 1)",
|
||||
"port": 2222
|
||||
}
|
||||
|
||||
@@ -39,5 +39,6 @@
|
||||
],
|
||||
"host": "localhost",
|
||||
"passed": false,
|
||||
"policy": "Hardened OpenSSH Server v8.0 (version 4)"
|
||||
"policy": "Hardened OpenSSH Server v8.0 (version 4)",
|
||||
"port": 2222
|
||||
}
|
||||
|
||||
@@ -62,5 +62,6 @@
|
||||
],
|
||||
"host": "localhost",
|
||||
"passed": false,
|
||||
"policy": "Hardened OpenSSH Server v8.0 (version 4)"
|
||||
"policy": "Hardened OpenSSH Server v8.0 (version 4)",
|
||||
"port": 2222
|
||||
}
|
||||
|
||||
@@ -2,5 +2,6 @@
|
||||
"errors": [],
|
||||
"host": "localhost",
|
||||
"passed": true,
|
||||
"policy": "Docker policy: test11 (version 1)"
|
||||
"policy": "Docker policy: test11 (version 1)",
|
||||
"port": 2222
|
||||
}
|
||||
|
||||
@@ -39,5 +39,6 @@
|
||||
],
|
||||
"host": "localhost",
|
||||
"passed": false,
|
||||
"policy": "Docker policy: test12 (version 1)"
|
||||
"policy": "Docker policy: test12 (version 1)",
|
||||
"port": 2222
|
||||
}
|
||||
|
||||
@@ -2,5 +2,6 @@
|
||||
"errors": [],
|
||||
"host": "localhost",
|
||||
"passed": true,
|
||||
"policy": "Docker policy: test13 (version 1)"
|
||||
"policy": "Docker policy: test13 (version 1)",
|
||||
"port": 2222
|
||||
}
|
||||
|
||||
@@ -15,5 +15,6 @@
|
||||
],
|
||||
"host": "localhost",
|
||||
"passed": false,
|
||||
"policy": "Docker policy: test14 (version 1)"
|
||||
"policy": "Docker policy: test14 (version 1)",
|
||||
"port": 2222
|
||||
}
|
||||
|
||||
@@ -2,5 +2,6 @@
|
||||
"errors": [],
|
||||
"host": "localhost",
|
||||
"passed": true,
|
||||
"policy": "Docker policy: test15 (version 1)"
|
||||
"policy": "Docker policy: test15 (version 1)",
|
||||
"port": 2222
|
||||
}
|
||||
|
||||
@@ -82,5 +82,6 @@
|
||||
],
|
||||
"host": "localhost",
|
||||
"passed": false,
|
||||
"policy": "Docker policy: test16 (version 1)"
|
||||
"policy": "Docker policy: test16 (version 1)",
|
||||
"port": 2222
|
||||
}
|
||||
|
||||
@@ -2,5 +2,6 @@
|
||||
"errors": [],
|
||||
"host": "localhost",
|
||||
"passed": true,
|
||||
"policy": "Docker policy: test17 (version 1)"
|
||||
"policy": "Docker policy: test17 (version 1)",
|
||||
"port": 2222
|
||||
}
|
||||
|
||||
@@ -2,5 +2,6 @@
|
||||
"errors": [],
|
||||
"host": "localhost",
|
||||
"passed": true,
|
||||
"policy": "Docker policy: test6 (version 1)"
|
||||
"policy": "Docker policy: test6 (version 1)",
|
||||
"port": 2222
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user