diff --git a/README.md b/README.md index ddb838f4..bec6c93a 100644 --- a/README.md +++ b/README.md @@ -184,6 +184,7 @@ For convenience, a web front-end on top of the command-line tool is available at - In Ubuntu 22.04 client policy, moved host key types `sk-ssh-ed25519@openssh.com` and `ssh-ed25519` to the end of all certificate types. - Re-organized option host key types for OpenSSH 9.2 server policy to correspond with updated Debian 12 hardening guide. - Dropped support for Python 3.7 (EOL was reached in June 2023). + - Added test for the Terrapin message prefix truncation vulnerability ([CVE-2023-48795](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-48795)). ### v3.0.0 (2023-09-07) - Results from concurrent scans against multiple hosts are no longer improperly combined; bug discovered by [Adam Russell](https://github.com/thecliguy). diff --git a/src/ssh_audit/ssh2_kexdb.py b/src/ssh_audit/ssh2_kexdb.py index 61421e1b..36130daf 100644 --- a/src/ssh_audit/ssh2_kexdb.py +++ b/src/ssh_audit/ssh2_kexdb.py @@ -71,6 +71,8 @@ class SSH2_KexDB: # pylint: disable=too-few-public-methods INFO_REMOVED_IN_OPENSSH69 = 'removed in OpenSSH 6.9: https://www.openssh.com/txt/release-6.9' INFO_REMOVED_IN_OPENSSH70 = 'removed in OpenSSH 7.0: https://www.openssh.com/txt/release-7.0' INFO_WITHDRAWN_PQ_ALG = 'the sntrup4591761 algorithm was withdrawn, as it may not provide strong post-quantum security' + 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)' # 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]]]]]] = {} @@ -154,8 +156,10 @@ class SSH2_KexDB: # pylint: disable=too-few-public-methods 'ecdh-sha2-wiRIU8TKjMZ418sMqlqtvQ==': [[], [FAIL_UNPROVEN]], # sect283k1 'ecdh-sha2-zD/b3hu/71952ArpUG4OjQ==': [[], [FAIL_UNPROVEN, FAIL_SMALL_ECC_MODULUS]], # sect233k1 'ecmqv-sha2': [[], [FAIL_UNPROVEN]], - 'ext-info-c': [[]], # Extension negotiation (RFC 8308) - 'ext-info-s': [[]], # Extension negotiation (RFC 8308) + 'ext-info-c': [[], [], [], [INFO_EXTENSION_NEGOTIATION]], # Extension negotiation (RFC 8308) + 'ext-info-s': [[], [], [], [INFO_EXTENSION_NEGOTIATION]], # Extension negotiation (RFC 8308) + 'kex-strict-c-v00@openssh.com': [[], [], [], [INFO_STRICT_KEX]], # Strict KEX marker (countermeasure for CVE-2023-48795). + 'kex-strict-s-v00@openssh.com': [[], [], [], [INFO_STRICT_KEX]], # Strict KEX marker (countermeasure for CVE-2023-48795). # The GSS kex algorithms get special wildcard handling, since they include variable base64 data after their standard prefixes. 'gss-13.3.132.0.10-sha256-*': [[], [FAIL_UNKNOWN]], diff --git a/src/ssh_audit/ssh_audit.py b/src/ssh_audit/ssh_audit.py index a59a6784..fbe2e64b 100755 --- a/src/ssh_audit/ssh_audit.py +++ b/src/ssh_audit/ssh_audit.py @@ -447,7 +447,7 @@ def output_info(out: OutputBuffer, software: Optional['Software'], client_audit: out.sep() -def post_process_findings(banner: Optional[Banner], algs: Algorithms) -> List[str]: +def post_process_findings(banner: Optional[Banner], algs: Algorithms, client_audit: bool) -> List[str]: '''Perform post-processing on scan results before reporting them to the user. Returns a list of algorithms that should not be recommended''' @@ -466,6 +466,45 @@ def post_process_findings(banner: Optional[Banner], algs: Algorithms) -> List[st # Ensure that this algorithm doesn't appear in the recommendations section since the user cannot control this OpenSSH bug. algorithm_recommendation_suppress_list.append('diffie-hellman-group-exchange-sha256') + # Check for the Terrapin vulnerability (CVE-2023-48795), and mark the vulnerable algorithms. + if algs.ssh2kex is not None and \ + ((client_audit and 'kex-strict-c-v00@openssh.com' not in algs.ssh2kex.kex_algorithms) or (not client_audit and 'kex-strict-s-v00@openssh.com' not in algs.ssh2kex.kex_algorithms)): # Strict KEX marker is not present. + + def add_terrapin_warning(db: Dict[str, Dict[str, List[List[Optional[str]]]]], category: str, algorithm_name: str) -> None: + while len(db[category][algorithm_name]) < 3: + db[category][algorithm_name].append([]) + + db[category][algorithm_name][2].append("vulnerable to the Terrapin attack (CVE-2023-48795), allowing message prefix truncation") + + db = SSH2_KexDB.get_db() + + # Without the strict KEX marker, these algorithms are always vulnerable. + add_terrapin_warning(db, "enc", "chacha20-poly1305") + add_terrapin_warning(db, "enc", "chacha20-poly1305@openssh.com") + + cbc_ciphers = [] + etm_macs = [] + + # Find the list of CBC ciphers the peer supports. + ciphers_supported = algs.ssh2kex.client.encryption if client_audit else algs.ssh2kex.server.encryption + for cipher in ciphers_supported: + if cipher.endswith("-cbc"): + cbc_ciphers.append(cipher) + + # Find the list of ETM MACs the peer supports. + macs_supported = algs.ssh2kex.client.mac if client_audit else algs.ssh2kex.server.mac + for mac in macs_supported: + if mac.endswith("-etm@openssh.com"): + etm_macs.append(mac) + + # If at least one CBC cipher and at least one ETM MAC is supported, mark them all as vulnerable. + if len(cbc_ciphers) > 0 and len(etm_macs) > 0: + for cipher in cbc_ciphers: + add_terrapin_warning(db, "enc", cipher) + + for mac in etm_macs: + add_terrapin_warning(db, "mac", mac) + return algorithm_recommendation_suppress_list @@ -478,7 +517,7 @@ def output(out: OutputBuffer, aconf: AuditConf, banner: Optional[Banner], header algs = Algorithms(pkm, kex) # Perform post-processing on the findings to make final adjustments before outputting the results. - algorithm_recommendation_suppress_list = post_process_findings(banner, algs) + algorithm_recommendation_suppress_list = post_process_findings(banner, algs, client_audit) with out: if print_target: diff --git a/test/test_ssh2.py b/test/test_ssh2.py index 46748129..ddbc6af0 100644 --- a/test/test_ssh2.py +++ b/test/test_ssh2.py @@ -164,7 +164,7 @@ def test_ssh2_server_simple(self, output_spy, virtual_socket): self.audit(out, self._conf()) out.write() lines = output_spy.flush() - assert len(lines) == 70 + assert len(lines) == 83 def test_ssh2_server_invalid_first_packet(self, output_spy, virtual_socket): vsocket = virtual_socket