diff --git a/ssh-audit.py b/ssh-audit.py index 3e793e9..6c450d3 100755 --- a/ssh-audit.py +++ b/ssh-audit.py @@ -472,6 +472,7 @@ class SSH(object): class Product(object): OpenSSH = 'OpenSSH' DropbearSSH = 'Dropbear SSH' + LibSSH = 'libssh' class Software(object): def __init__(self, vendor, product, version, patch, os): @@ -632,6 +633,12 @@ class SSH(object): v = None os = cls._extract_os(banner.comments) return cls(v, p, mx.group(1), patch, os) + mx = re.match(r'^libssh-([\d\.]+\d+)(.*)', software) + if mx: + patch = cls._fix_patch(mx.group(2)) + v, p = None, SSH.Product.LibSSH + os = cls._extract_os(banner.comments) + return cls(v, p, mx.group(1), patch, os) mx = re.match(r'^RomSShell_([\d\.]+\d+)(.*)', software) if mx: patch = cls._fix_patch(mx.group(2)) @@ -718,21 +725,34 @@ class SSH(object): class Security(object): CVE = { 'Dropbear SSH': [ - ['0.44', '2015.71', 1, 'CVE-2016-3116', 5.5, 'bypass command restrictions via xauth command injection.'], - ['0.28', '2013.58', 1, 'CVE-2013-4434', 5.0, 'discover valid usernames through different time delays.'], - ['0.28', '2013.58', 1, 'CVE-2013-4421', 5.0, 'cause DoS (memory consumption) via a compressed packet.'], - ['0.52', '2011.54', 1, 'CVE-2012-0920', 7.1, 'execute arbitrary code or bypass command restrictions.'], - ['0.40', '0.48.1', 1, 'CVE-2007-1099', 7.5, 'conduct a MitM attack (no warning for hostkey mismatch).'], - ['0.28', '0.47', 1, 'CVE-2006-1206', 7.5, 'cause DoS (slot exhaustion) via large number of connections.'], - ['0.39', '0.47', 1, 'CVE-2006-0225', 4.6, 'execute arbitrary commands via scp with crafted filenames.'], - ['0.28', '0.46', 1, 'CVE-2005-4178', 6.5, 'execute arbitrary code via buffer overflow vulnerability.'], - ['0.28', '0.42', 1, 'CVE-2004-2486', 7.5, 'execute arbitrary code via DSS verification code.'], - ] + ['0.44', '2015.71', 1, 'CVE-2016-3116', 5.5, 'bypass command restrictions via xauth command injection'], + ['0.28', '2013.58', 1, 'CVE-2013-4434', 5.0, 'discover valid usernames through different time delays'], + ['0.28', '2013.58', 1, 'CVE-2013-4421', 5.0, 'cause DoS (memory consumption) via a compressed packet'], + ['0.52', '2011.54', 1, 'CVE-2012-0920', 7.1, 'execute arbitrary code or bypass command restrictions'], + ['0.40', '0.48.1', 1, 'CVE-2007-1099', 7.5, 'conduct a MitM attack (no warning for hostkey mismatch)'], + ['0.28', '0.47', 1, 'CVE-2006-1206', 7.5, 'cause DoS (slot exhaustion) via large number of connections'], + ['0.39', '0.47', 1, 'CVE-2006-0225', 4.6, 'execute arbitrary commands via scp with crafted filenames'], + ['0.28', '0.46', 1, 'CVE-2005-4178', 6.5, 'execute arbitrary code via buffer overflow vulnerability'], + ['0.28', '0.42', 1, 'CVE-2004-2486', 7.5, 'execute arbitrary code via DSS verification code']], + 'libssh': [ + ['0.1', '0.7.2', 1, 'CVE-2016-0739', 4.3, 'conduct a MitM attack (weakness in DH key generation)'], + ['0.5.1', '0.6.4', 1, 'CVE-2015-3146', 5.0, 'cause DoS via kex packets (null pointer dereference)'], + ['0.5.1', '0.6.3', 1, 'CVE-2014-8132', 5.0, 'cause DoS via kex init packet (dangling pointer)'], + ['0.4.7', '0.6.2', 1, 'CVE-2014-0017', 1.9, 'leak data via PRNG state reuse on forking servers'], + ['0.4.7', '0.5.3', 1, 'CVE-2013-0176', 4.3, 'cause DoS via kex packet (null pointer dereference)'], + ['0.4.7', '0.5.2', 1, 'CVE-2012-6063', 7.5, 'cause DoS or execute arbitrary code via sftp (double free)'], + ['0.4.7', '0.5.2', 1, 'CVE-2012-4562', 7.5, 'cause DoS or execute arbitrary code (overflow check)'], + ['0.4.7', '0.5.2', 1, 'CVE-2012-4561', 5.0, 'cause DoS via unspecified vectors (invalid pointer)'], + ['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)']] } TXT = { 'Dropbear SSH': [ - ['0.28', '0.34', 1, 'remote root exploit', 'remote format string buffer overflow exploit (exploit-db#387).'], - ] + ['0.28', '0.34', 1, 'remote root exploit', 'remote format string buffer overflow exploit (exploit-db#387)']], + 'libssh': [ + ['0.3.3', '0.3.3', 1, 'null pointer check', 'missing null pointer check in "crypt_set_algorithms_server"'], + ['0.3.3', '0.3.3', 1, 'integer overflow', 'integer overflow in "buffer_get_data"'], + ['0.3.3', '0.3.3', 3, 'heap overflow', 'heap overflow in "packet_decrypt"']] } class Socket(ReadBuf, WriteBuf): @@ -964,29 +984,29 @@ class KexDB(object): ALGORITHMS = { 'kex': { - 'diffie-hellman-group1-sha1': [['2.3.0,d0.28', '6.6', '6.9'], [FAIL_OPENSSH67_UNSAFE, FAIL_OPENSSH70_LOGJAM], [WARN_MODULUS_SIZE, WARN_HASH_WEAK]], - 'diffie-hellman-group14-sha1': [['3.9,d0.53'], [], [WARN_HASH_WEAK]], + 'diffie-hellman-group1-sha1': [['2.3.0,d0.28,l10.2', '6.6', '6.9'], [FAIL_OPENSSH67_UNSAFE, FAIL_OPENSSH70_LOGJAM], [WARN_MODULUS_SIZE, WARN_HASH_WEAK]], + 'diffie-hellman-group14-sha1': [['3.9,d0.53,l10.6.0'], [], [WARN_HASH_WEAK]], 'diffie-hellman-group14-sha256': [['7.3,d2016.73']], 'diffie-hellman-group16-sha512': [['7.3,d2016.73']], 'diffie-hellman-group18-sha512': [['7.3']], 'diffie-hellman-group-exchange-sha1': [['2.3.0', '6.6', None], [FAIL_OPENSSH67_UNSAFE], [WARN_HASH_WEAK]], 'diffie-hellman-group-exchange-sha256': [['4.4'], [], [WARN_MODULUS_CUSTOM]], - 'ecdh-sha2-nistp256': [['5.7,d2013.62'], [WARN_CURVES_WEAK]], + 'ecdh-sha2-nistp256': [['5.7,d2013.62,l10.6.0'], [WARN_CURVES_WEAK]], 'ecdh-sha2-nistp384': [['5.7,d2013.62'], [WARN_CURVES_WEAK]], 'ecdh-sha2-nistp521': [['5.7,d2013.62'], [WARN_CURVES_WEAK]], - 'curve25519-sha256@libssh.org': [['6.5,d2013.62']], + 'curve25519-sha256@libssh.org': [['6.5,d2013.62,l10.6.0']], 'kexguess2@matt.ucc.asn.au': [['d2013.57']], }, 'key': { 'rsa-sha2-256': [['7.2']], 'rsa-sha2-512': [['7.2']], - 'ssh-ed25519': [['6.5']], + 'ssh-ed25519': [['6.5,l10.7.0']], 'ssh-ed25519-cert-v01@openssh.com': [['6.5']], - 'ssh-rsa': [['2.5.0,d0.28']], - 'ssh-dss': [['2.1.0,d0.28', '6.9'], [FAIL_OPENSSH70_WEAK], [WARN_MODULUS_SIZE, WARN_RNDSIG_KEY]], - 'ecdsa-sha2-nistp256': [['5.7,d2013.62'], [WARN_CURVES_WEAK], [WARN_RNDSIG_KEY]], - 'ecdsa-sha2-nistp384': [['5.7,d2013.62'], [WARN_CURVES_WEAK], [WARN_RNDSIG_KEY]], - 'ecdsa-sha2-nistp521': [['5.7,d2013.62'], [WARN_CURVES_WEAK], [WARN_RNDSIG_KEY]], + 'ssh-rsa': [['2.5.0,d0.28,l10.2']], + 'ssh-dss': [['2.1.0,d0.28,l10.2', '6.9'], [FAIL_OPENSSH70_WEAK], [WARN_MODULUS_SIZE, WARN_RNDSIG_KEY]], + 'ecdsa-sha2-nistp256': [['5.7,d2013.62,l10.6.4'], [WARN_CURVES_WEAK], [WARN_RNDSIG_KEY]], + 'ecdsa-sha2-nistp384': [['5.7,d2013.62,l10.6.4'], [WARN_CURVES_WEAK], [WARN_RNDSIG_KEY]], + 'ecdsa-sha2-nistp521': [['5.7,d2013.62,l10.6.4'], [WARN_CURVES_WEAK], [WARN_RNDSIG_KEY]], 'ssh-rsa-cert-v00@openssh.com': [['5.4', '6.9'], [FAIL_OPENSSH70_LEGACY], []], 'ssh-dss-cert-v00@openssh.com': [['5.4', '6.9'], [FAIL_OPENSSH70_LEGACY], [WARN_MODULUS_SIZE, WARN_RNDSIG_KEY]], 'ssh-rsa-cert-v01@openssh.com': [['5.6']], @@ -996,10 +1016,10 @@ class KexDB(object): 'ecdsa-sha2-nistp521-cert-v01@openssh.com': [['5.7'], [WARN_CURVES_WEAK], [WARN_RNDSIG_KEY]], }, 'enc': { - 'none': [['1.2.2,d2013.56'], [FAIL_PLAINTEXT]], - '3des-cbc': [['1.2.2,d0.28', '6.6', None], [FAIL_OPENSSH67_UNSAFE], [WARN_CIPHER_WEAK, WARN_CIPHER_MODE, WARN_BLOCK_SIZE]], + 'none': [['1.2.2,d2013.56,l10.2'], [FAIL_PLAINTEXT]], + '3des-cbc': [['1.2.2,d0.28,l10.2', '6.6', None], [FAIL_OPENSSH67_UNSAFE], [WARN_CIPHER_WEAK, WARN_CIPHER_MODE, WARN_BLOCK_SIZE]], '3des-ctr': [['d0.52']], - 'blowfish-cbc': [['1.2.2,d0.28', '6.6,d0.52', '7.1,d0.52'], [FAIL_OPENSSH67_UNSAFE, FAIL_DBEAR53_DISABLED], [WARN_OPENSSH72_LEGACY, WARN_CIPHER_MODE, WARN_BLOCK_SIZE]], + 'blowfish-cbc': [['1.2.2,d0.28,l10.2', '6.6,d0.52', '7.1,d0.52'], [FAIL_OPENSSH67_UNSAFE, FAIL_DBEAR53_DISABLED], [WARN_OPENSSH72_LEGACY, WARN_CIPHER_MODE, WARN_BLOCK_SIZE]], 'twofish-cbc': [['d0.28', 'd2014.66'], [FAIL_DBEAR67_DISABLED], [WARN_CIPHER_MODE]], 'twofish128-cbc': [['d0.47', 'd2014.66'], [FAIL_DBEAR67_DISABLED], [WARN_CIPHER_MODE]], 'twofish256-cbc': [['d0.47', 'd2014.66'], [FAIL_DBEAR67_DISABLED], [WARN_CIPHER_MODE]], @@ -1009,27 +1029,27 @@ class KexDB(object): 'arcfour': [['2.1.0', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_CIPHER_WEAK]], 'arcfour128': [['4.2', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_CIPHER_WEAK]], 'arcfour256': [['4.2', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_CIPHER_WEAK]], - 'aes128-cbc': [['2.3.0,d0.28', '6.6', None], [FAIL_OPENSSH67_UNSAFE], [WARN_CIPHER_MODE]], - 'aes192-cbc': [['2.3.0', '6.6', None], [FAIL_OPENSSH67_UNSAFE], [WARN_CIPHER_MODE]], - 'aes256-cbc': [['2.3.0,d0.47', '6.6', None], [FAIL_OPENSSH67_UNSAFE], [WARN_CIPHER_MODE]], + 'aes128-cbc': [['2.3.0,d0.28,l10.2', '6.6', None], [FAIL_OPENSSH67_UNSAFE], [WARN_CIPHER_MODE]], + 'aes192-cbc': [['2.3.0,l10.2', '6.6', None], [FAIL_OPENSSH67_UNSAFE], [WARN_CIPHER_MODE]], + 'aes256-cbc': [['2.3.0,d0.47,l10.2', '6.6', None], [FAIL_OPENSSH67_UNSAFE], [WARN_CIPHER_MODE]], 'rijndael128-cbc': [['2.3.0', '3.0.2'], [FAIL_OPENSSH31_REMOVE], [WARN_CIPHER_MODE]], 'rijndael192-cbc': [['2.3.0', '3.0.2'], [FAIL_OPENSSH31_REMOVE], [WARN_CIPHER_MODE]], 'rijndael256-cbc': [['2.3.0', '3.0.2'], [FAIL_OPENSSH31_REMOVE], [WARN_CIPHER_MODE]], 'rijndael-cbc@lysator.liu.se': [['2.3.0', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_CIPHER_MODE]], - 'aes128-ctr': [['3.7,d0.52']], - 'aes192-ctr': [['3.7']], - 'aes256-ctr': [['3.7,d0.52']], + 'aes128-ctr': [['3.7,d0.52,l10.4.1']], + 'aes192-ctr': [['3.7,l10.4.1']], + 'aes256-ctr': [['3.7,d0.52,l10.4.1']], 'aes128-gcm@openssh.com': [['6.2']], 'aes256-gcm@openssh.com': [['6.2']], 'chacha20-poly1305@openssh.com': [['6.5'], [], [], [INFO_OPENSSH69_CHACHA]], }, 'mac': { 'none': [['d2013.56'], [FAIL_PLAINTEXT]], - 'hmac-sha1': [['2.1.0,d0.28'], [], [WARN_ENCRYPT_AND_MAC, WARN_HASH_WEAK]], + 'hmac-sha1': [['2.1.0,d0.28,l10.2'], [], [WARN_ENCRYPT_AND_MAC, WARN_HASH_WEAK]], 'hmac-sha1-96': [['2.5.0,d0.47', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_ENCRYPT_AND_MAC, WARN_HASH_WEAK]], - 'hmac-sha2-256': [['5.9,d2013.56'], [], [WARN_ENCRYPT_AND_MAC]], + 'hmac-sha2-256': [['5.9,d2013.56,l10.7.0'], [], [WARN_ENCRYPT_AND_MAC]], 'hmac-sha2-256-96': [['5.9', '6.0'], [FAIL_OPENSSH61_REMOVE], [WARN_ENCRYPT_AND_MAC]], - 'hmac-sha2-512': [['5.9,d2013.56'], [], [WARN_ENCRYPT_AND_MAC]], + 'hmac-sha2-512': [['5.9,d2013.56,l10.7.0'], [], [WARN_ENCRYPT_AND_MAC]], 'hmac-sha2-512-96': [['5.9', '6.0'], [FAIL_OPENSSH61_REMOVE], [WARN_ENCRYPT_AND_MAC]], 'hmac-md5': [['2.1.0,d0.28', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_ENCRYPT_AND_MAC, WARN_HASH_WEAK]], 'hmac-md5-96': [['2.5.0', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_ENCRYPT_AND_MAC, WARN_HASH_WEAK]], @@ -1053,6 +1073,8 @@ class KexDB(object): def get_ssh_version(version_desc): if version_desc.startswith('d'): return (SSH.Product.DropbearSSH, version_desc[1:]) + elif version_desc.startswith('l1'): + return (SSH.Product.LibSSH, version_desc[2:]) else: return (SSH.Product.OpenSSH, version_desc) @@ -1116,6 +1138,8 @@ def get_alg_since_text(alg_desc): ssh_prefix, ssh_version = get_ssh_version(v) if not ssh_version: continue + if ssh_prefix in [SSH.Product.LibSSH]: + continue if ssh_version.endswith('C'): ssh_version = '{0} (client only)'.format(ssh_version[:-1]) tv.append('{0} {1}'.format(ssh_prefix, ssh_version)) @@ -1142,12 +1166,15 @@ def get_alg_pairs(kex, pkm): def get_alg_recommendations(software, kex, pkm, for_server=True): alg_pairs = get_alg_pairs(kex, pkm) + vproducts = [SSH.Product.OpenSSH, + SSH.Product.DropbearSSH, + SSH.Product.LibSSH] if software is not None: - if software.product not in [SSH.Product.OpenSSH, SSH.Product.DropbearSSH]: + if software.product not in vproducts: software = None if software is None: ssh_timeframe = get_ssh_timeframe(alg_pairs, for_server) - for product in [SSH.Product.OpenSSH, SSH.Product.DropbearSSH]: + for product in vproducts: if product not in ssh_timeframe: continue version = ssh_timeframe[product][0] @@ -1302,7 +1329,8 @@ def output_security_sub(sub, software, padlen): continue target, name = line[2:4] is_server, is_client = target & 1 == 1, target & 2 == 2 - if is_client: + is_local = target & 4 == 4 + if not is_server: continue p = '' if out.batch else ' ' * (padlen - len(name)) if sub == 'cve': diff --git a/test/test_version_compare.py b/test/test_version_compare.py index 8829f68..a5a9790 100644 --- a/test/test_version_compare.py +++ b/test/test_version_compare.py @@ -2,10 +2,11 @@ # -*- coding: utf-8 -*- import pytest + class TestVersionCompare(object): @pytest.fixture(autouse=True) def init(self, ssh_audit): - self.ssh = ssh_audit.SSH + self.ssh = ssh_audit.SSH def get_dropbear_software(self, v): b = self.ssh.Banner.parse('SSH-2.0-dropbear_{0}'.format(v)) @@ -15,26 +16,30 @@ class TestVersionCompare(object): b = self.ssh.Banner.parse('SSH-2.0-OpenSSH_{0}'.format(v)) return self.ssh.Software.parse(b) + def get_libssh_software(self, v): + b = self.ssh.Banner.parse('SSH-2.0-libssh-{0}'.format(v)) + return self.ssh.Software.parse(b) + def test_dropbear_compare_version_pre_years(self): s = self.get_dropbear_software('0.44') assert s.compare_version('0.43') > 0 assert s.compare_version('0.44') == 0 assert s.compare_version('0.45') < 0 - assert s.between_versions('0.43', '0.45') == True + assert s.between_versions('0.43', '0.45') def test_dropbear_compare_version_with_years(self): s = self.get_dropbear_software('2015.71') assert s.compare_version('2014.67') > 0 assert s.compare_version('2015.71') == 0 assert s.compare_version('2016.74') < 0 - assert s.between_versions('2014.67', '2016.74') == True + assert s.between_versions('2014.67', '2016.74') def test_dropbear_compare_version_mixed(self): s = self.get_dropbear_software('0.53.1') assert s.compare_version('0.53') > 0 assert s.compare_version('0.53.1') == 0 assert s.compare_version('2011.54') < 0 - assert s.between_versions('0.53', '2011.54') == True + assert s.between_versions('0.53', '2011.54') def test_dropbear_compare_version_patchlevel(self): s1 = self.get_dropbear_software('0.44') @@ -85,9 +90,8 @@ class TestVersionCompare(object): assert s.compare_version('3.7') > 0 assert s.compare_version('3.7.1') == 0 assert s.compare_version('3.8') < 0 - assert s.between_versions('3.7', '3.8') == True - - + assert s.between_versions('3.7', '3.8') + def test_openssh_compare_version_patchlevel(self): s1 = self.get_openssh_software('2.1.1') s2 = self.get_openssh_software('2.1.1p2') @@ -130,3 +134,36 @@ class TestVersionCompare(object): if i + 1 < l: vnext = versions[i + 1] assert s.compare_version(vnext) < 0 + + def test_libssh_compare_version_simple(self): + s = self.get_libssh_software('0.3') + assert s.compare_version('0.2') > 0 + assert s.compare_version('0.3') == 0 + assert s.compare_version('0.3.1') < 0 + assert s.between_versions('0.2', '0.3.1') + + def test_libssh_compare_version_sequential(self): + versions = [] + for v in ['0.2', '0.3']: + versions.append(v) + for i in range(1, 5): + versions.append('0.3.{0}'.format(i)) + for i in range(0, 9): + versions.append('0.4.{0}'.format(i)) + for i in range(0, 6): + versions.append('0.5.{0}'.format(i)) + for i in range(0, 6): + versions.append('0.6.{0}'.format(i)) + for i in range(0, 4): + versions.append('0.7.{0}'.format(i)) + l = len(versions) + for i in range(l): + v = versions[i] + s = self.get_libssh_software(v) + assert s.compare_version(v) == 0 + if i - 1 >= 0: + vbefore = versions[i - 1] + assert s.compare_version(vbefore) > 0 + if i + 1 < l: + vnext = versions[i + 1] + assert s.compare_version(vnext) < 0