8 Commits

Author SHA1 Message Date
Manfred Kaiser 27f215d687 Merge 3116c2e678 into 9049c8476a 2024-07-19 13:12:23 -04:00
Joe Testa 9049c8476a Updated README. 2024-07-06 21:01:19 -04:00
Daniel Lenski bbbdf71e50 Recognize LANcom LCOS software and support ed448 key extraction (#277)
* Include raw hostkey bytes in debug output

* Recognize LANcom LCOS software and support extraction of ssh-ed448 key type

LANcom router devices appear to be primarily used in Germany (see [1]
for examples on the public Internet), and they appear to support the
`ssh-ed448` key type which is documented in [2], but which has never
been supported by any as-yet-released version of OpenSSH.

[1] https://www.shodan.io/search?query=ssh+%22ed448%22
[2] https://datatracker.ietf.org/doc/html/rfc8709#name-public-key-format
2024-07-06 20:56:24 -04:00
Joe Testa 92db5f0138 Updated docker tests and README due to merge of PR #281. 2024-07-05 10:53:00 -04:00
dreizehnutters bc2a89eb11 fix for https://github.com/jtesta/ssh-audit/issues/280 (#281)
* fix for https://github.com/jtesta/ssh-audit/issues/280

* changed json format to min. the damage for a change
2024-07-05 10:49:16 -04:00
Joe Testa ea117b203b Updated README. 2024-07-05 10:16:06 -04:00
Daniel Lenski d8f8b7c57c Make HostKeyTest class reusable (#278)
Because the `HostKeyTest` class was mutating its static/global
`HOST_KEY_TYPES` dict, this class could not actually be used more than once
in a single thread!

Rather than mutate this dict after parsing each key type
(`HOST_KEY_TYPES[host_key_type]['parsed'] = True`), the `perform_test`
method should simple add the parsed key types to a local `set()`.
2024-07-05 10:11:18 -04:00
Manfred Kaiser 3116c2e678 Update versionvulnerabilitydb.py 2024-05-09 16:50:44 +02:00
25 changed files with 68 additions and 24 deletions
+3
View File
@@ -216,7 +216,10 @@ For convenience, a web front-end on top of the command-line tool is available at
### v3.3.0-dev (???) ### v3.3.0-dev (???)
- Added built-in policies for Ubuntu 24.04 LTS server and client, and OpenSSH 9.8. - 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 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 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) ### 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)). - Added implementation of the DHEat denial-of-service attack (see `--dheat` option; [CVE-2002-20001](https://nvd.nist.gov/vuln/detail/CVE-2002-20001)).
+9 -4
View File
@@ -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. # 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'] 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 = { HOST_KEY_TYPES = {
'ssh-rsa': {'cert': False, 'variable_key_len': True}, 'ssh-rsa': {'cert': False, 'variable_key_len': True},
'rsa-sha2-256': {'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': False, 'variable_key_len': False},
'ssh-ed25519-cert-v01@openssh.com': {'cert': True, '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' 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: 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 hostkey_modulus_size = 0
ca_modulus_size = 0 ca_modulus_size = 0
parsed_host_key_types = set()
# If the connection still exists, close it so we can test # If the connection still exists, close it so we can test
# using a clean slate (otherwise it may exist in a non-testable # using a clean slate (otherwise it may exist in a non-testable
@@ -106,7 +110,7 @@ class HostKeyTest:
key_warn_comments = [] key_warn_comments = []
# Skip those already handled (i.e.: those in the RSA family, as testing one tests them all). # 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 continue
# If this host key type is supported by the server, we test it. # 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_key_type = kex_group.get_ca_type()
ca_modulus_size = kex_group.get_ca_size() 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("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. # 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) 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 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: if host_key_type in HostKeyTest.RSA_FAMILY:
for rsa_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 # 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. # failure and/or warning comments.
@@ -228,7 +233,7 @@ class HostKeyTest:
db['key'][rsa_type][2].extend(key_warn_comments) db['key'][rsa_type][2].extend(key_warn_comments)
else: else:
host_key_types[host_key_type]['parsed'] = True parsed_host_key_types.add(host_key_type)
db = SSH2_KexDB.get_db() db = SSH2_KexDB.get_db()
while len(db['key'][host_key_type]) < 3: while len(db['key'][host_key_type]) < 3:
db['key'][host_key_type].append([]) db['key'][host_key_type].append([])
+3
View File
@@ -134,6 +134,9 @@ class KexDH: # pragma: nocover
if self.__hostkey_type == 'ssh-ed25519': if self.__hostkey_type == 'ssh-ed25519':
self.out.d("%s has a fixed host key modulus of 32." % self.__hostkey_type) self.out.d("%s has a fixed host key modulus of 32." % self.__hostkey_type)
self.__hostkey_n_len = 32 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: else:
# Here is the modulus size & actual modulus of the host key public key. # 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) hostkey_n, self.__hostkey_n_len, ptr = KexDH.__get_bytes(hostkey, ptr)
+4
View File
@@ -224,4 +224,8 @@ class Software:
mx = re.match(r'^PuTTY_Release_(.*)', software) mx = re.match(r'^PuTTY_Release_(.*)', software)
if mx: if mx:
return cls(None, Product.PuTTY, mx.group(1), None, None) 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 return None
+1 -1
View File
@@ -735,7 +735,7 @@ def evaluate_policy(out: OutputBuffer, aconf: AuditConf, banner: Optional['Banne
passed, error_struct, error_str = aconf.policy.evaluate(banner, kex) passed, error_struct, error_str = aconf.policy.evaluate(banner, kex)
if aconf.json: 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)) out.info(json.dumps(json_struct, indent=4 if aconf.json_print_indent else None, sort_keys=True))
else: else:
spacing = '' spacing = ''
+10
View File
@@ -35,6 +35,7 @@ class VersionVulnerabilityDB: # pylint: disable=too-few-public-methods
# then affected = 1 + 4 = 5. # then affected = 1 + 4 = 5.
CVE: Dict[str, List[List[Any]]] = { CVE: Dict[str, List[List[Any]]] = {
'Dropbear SSH': [ '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', '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', '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'], ['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-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)']], ['0.4.7', '0.5.2', 1, 'CVE-2012-4559', 6.8, 'cause DoS or execute arbitrary code (double free)']],
'OpenSSH': [ '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'], ['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'], ['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'], ['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.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)']], ['1.2', '2.1', 1, 'CVE-2000-0525', 10.0, 'execute arbitrary code (improper privileges)']],
'PuTTY': [ '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 # 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.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'], ['0.0', '0.74', 2, 'CVE-2021-33500', 5.0, 'denial of service of the complete windows desktop'],
@@ -2,5 +2,6 @@
"errors": [], "errors": [],
"host": "localhost", "host": "localhost",
"passed": true, "passed": true,
"policy": "Docker policy: test1 (version 1)" "policy": "Docker policy: test1 (version 1)",
"port": 2222
} }
@@ -27,5 +27,6 @@
], ],
"host": "localhost", "host": "localhost",
"passed": false, "passed": false,
"policy": "Docker poliicy: test10 (version 1)" "policy": "Docker poliicy: test10 (version 1)",
"port": 2222
} }
@@ -19,5 +19,6 @@
], ],
"host": "localhost", "host": "localhost",
"passed": false, "passed": false,
"policy": "Docker policy: test2 (version 1)" "policy": "Docker policy: test2 (version 1)",
"port": 2222
} }
@@ -18,5 +18,6 @@
], ],
"host": "localhost", "host": "localhost",
"passed": false, "passed": false,
"policy": "Docker policy: test3 (version 1)" "policy": "Docker policy: test3 (version 1)",
"port": 2222
} }
@@ -28,5 +28,6 @@
], ],
"host": "localhost", "host": "localhost",
"passed": false, "passed": false,
"policy": "Docker policy: test4 (version 1)" "policy": "Docker policy: test4 (version 1)",
"port": 2222
} }
@@ -27,5 +27,6 @@
], ],
"host": "localhost", "host": "localhost",
"passed": false, "passed": false,
"policy": "Docker policy: test5 (version 1)" "policy": "Docker policy: test5 (version 1)",
"port": 2222
} }
@@ -2,5 +2,6 @@
"errors": [], "errors": [],
"host": "localhost", "host": "localhost",
"passed": true, "passed": true,
"policy": "Docker poliicy: test7 (version 1)" "policy": "Docker poliicy: test7 (version 1)",
"port": 2222
} }
@@ -15,5 +15,6 @@
], ],
"host": "localhost", "host": "localhost",
"passed": false, "passed": false,
"policy": "Docker poliicy: test8 (version 1)" "policy": "Docker poliicy: test8 (version 1)",
"port": 2222
} }
@@ -15,5 +15,6 @@
], ],
"host": "localhost", "host": "localhost",
"passed": false, "passed": false,
"policy": "Docker poliicy: test9 (version 1)" "policy": "Docker poliicy: test9 (version 1)",
"port": 2222
} }
@@ -39,5 +39,6 @@
], ],
"host": "localhost", "host": "localhost",
"passed": false, "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", "host": "localhost",
"passed": false, "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": [], "errors": [],
"host": "localhost", "host": "localhost",
"passed": true, "passed": true,
"policy": "Docker policy: test11 (version 1)" "policy": "Docker policy: test11 (version 1)",
"port": 2222
} }
@@ -39,5 +39,6 @@
], ],
"host": "localhost", "host": "localhost",
"passed": false, "passed": false,
"policy": "Docker policy: test12 (version 1)" "policy": "Docker policy: test12 (version 1)",
"port": 2222
} }
@@ -2,5 +2,6 @@
"errors": [], "errors": [],
"host": "localhost", "host": "localhost",
"passed": true, "passed": true,
"policy": "Docker policy: test13 (version 1)" "policy": "Docker policy: test13 (version 1)",
"port": 2222
} }
@@ -15,5 +15,6 @@
], ],
"host": "localhost", "host": "localhost",
"passed": false, "passed": false,
"policy": "Docker policy: test14 (version 1)" "policy": "Docker policy: test14 (version 1)",
"port": 2222
} }
@@ -2,5 +2,6 @@
"errors": [], "errors": [],
"host": "localhost", "host": "localhost",
"passed": true, "passed": true,
"policy": "Docker policy: test15 (version 1)" "policy": "Docker policy: test15 (version 1)",
"port": 2222
} }
@@ -82,5 +82,6 @@
], ],
"host": "localhost", "host": "localhost",
"passed": false, "passed": false,
"policy": "Docker policy: test16 (version 1)" "policy": "Docker policy: test16 (version 1)",
"port": 2222
} }
@@ -2,5 +2,6 @@
"errors": [], "errors": [],
"host": "localhost", "host": "localhost",
"passed": true, "passed": true,
"policy": "Docker policy: test17 (version 1)" "policy": "Docker policy: test17 (version 1)",
"port": 2222
} }
@@ -2,5 +2,6 @@
"errors": [], "errors": [],
"host": "localhost", "host": "localhost",
"passed": true, "passed": true,
"policy": "Docker policy: test6 (version 1)" "policy": "Docker policy: test6 (version 1)",
"port": 2222
} }