mirror of
https://github.com/jtesta/ssh-audit.git
synced 2026-05-25 07:21:23 +02:00
Compare commits
6 Commits
1e060a94c0
...
v3.3.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 772204ce8b | |||
| c0133a8d5f | |||
| 3220043aaf | |||
| 40ed92bbe6 | |||
| 720150b471 | |||
| d0628f6eb4 |
@@ -7,7 +7,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13.0-rc.2"]
|
||||
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -219,7 +219,7 @@ For convenience, a web front-end on top of the command-line tool is available at
|
||||
|
||||
## ChangeLog
|
||||
|
||||
### v3.3.0-dev (???)
|
||||
### v3.3.0 (2024-10-15)
|
||||
- Added Python 3.13 support.
|
||||
- Added built-in policies for Ubuntu 24.04 LTS server & client, OpenSSH 9.8, and OpenSSH 9.9.
|
||||
- Added IPv6 support for DHEat and connection rate tests.
|
||||
|
||||
@@ -172,8 +172,11 @@ class Algorithms:
|
||||
if fc > 0:
|
||||
faults += pow(10, 2 - i) * fc
|
||||
if n not in alg_list:
|
||||
# Don't recommend certificate or token types; these will only appear in the server's list if they are fully configured & functional on the server.
|
||||
if faults > 0 or (alg_type == 'key' and (('-cert-' in n) or (n.startswith('sk-')))) or empty_version:
|
||||
# Don't recommend certificate or token types; these will only appear in the server's list if they are fully configured & functional on the server. Also don't recommend 'ext-info-[cs]' nor 'kex-strict-[cs]-v00@openssh.com' key exchanges.
|
||||
if faults > 0 or \
|
||||
(alg_type == 'key' and (('-cert-' in n) or (n.startswith('sk-')))) or \
|
||||
(alg_type == 'kex' and (n.startswith('ext-info-') or n.startswith('kex-strict-'))) or \
|
||||
empty_version:
|
||||
continue
|
||||
rec[sshv][alg_type]['add'][n] = 0
|
||||
else:
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
THE SOFTWARE.
|
||||
"""
|
||||
# The version to display.
|
||||
VERSION = 'v3.3.0-dev'
|
||||
VERSION = 'v3.3.0'
|
||||
|
||||
# SSH software to impersonate
|
||||
SSH_HEADER = 'SSH-{0}-OpenSSH_8.2'
|
||||
|
||||
+50
-8
@@ -57,6 +57,7 @@ class Policy:
|
||||
self._allow_algorithm_subset_and_reordering = False
|
||||
self._allow_larger_keys = False
|
||||
self._errors: List[Any] = []
|
||||
self._updated_builtin_policy_available = False # If True, an outdated built-in policy was loaded.
|
||||
|
||||
self._name_and_version: str = ''
|
||||
|
||||
@@ -496,6 +497,11 @@ macs = %s
|
||||
return self._name_and_version
|
||||
|
||||
|
||||
def is_outdated_builtin_policy(self) -> bool:
|
||||
'''Returns True if this is a built-in policy that has a more recent version available than currently selected.'''
|
||||
return self._updated_builtin_policy_available
|
||||
|
||||
|
||||
def is_server_policy(self) -> bool:
|
||||
'''Returns True if this is a server policy, or False if this is a client policy.'''
|
||||
return self._server_policy
|
||||
@@ -507,18 +513,46 @@ macs = %s
|
||||
server_policy_descriptions = []
|
||||
client_policy_descriptions = []
|
||||
|
||||
latest_server_policies: Dict[str, Dict[str, Union[int, str]]] = {}
|
||||
latest_client_policies: Dict[str, Dict[str, Union[int, str]]] = {}
|
||||
for policy_name, policy in BUILTIN_POLICIES.items():
|
||||
policy_description = ""
|
||||
if verbose:
|
||||
policy_description = "\"{:s}\": {:s}".format(policy_name, policy['changelog'])
|
||||
else:
|
||||
|
||||
# If not in verbose mode, only store the latest version of each policy.
|
||||
if not verbose:
|
||||
policy_description = "\"{:s}\"".format(policy_name)
|
||||
|
||||
if policy['server_policy']:
|
||||
server_policy_descriptions.append(policy_description)
|
||||
else:
|
||||
client_policy_descriptions.append(policy_description)
|
||||
# Truncate the version off the policy name and obtain the version as an integer. (i.e.: "Platform X (version 3)" -> "Platform X", 3
|
||||
policy_name_no_version = ""
|
||||
version = 0
|
||||
version_pos = policy_name.find(" (version ")
|
||||
if version_pos != -1:
|
||||
policy_name_no_version = policy_name[0:version_pos]
|
||||
version = int(cast(str, policy['version'])) # Unit tests guarantee this to be parseable as an int.
|
||||
|
||||
d = latest_server_policies if policy['server_policy'] else latest_client_policies
|
||||
if policy_name_no_version not in d:
|
||||
d[policy_name_no_version] = {}
|
||||
d[policy_name_no_version]['latest_version'] = version
|
||||
d[policy_name_no_version]['description'] = policy_description
|
||||
elif version > cast(int, d[policy_name_no_version]['latest_version']): # If an updated version of the policy was found, replace the old one.
|
||||
d[policy_name_no_version]['latest_version'] = version
|
||||
d[policy_name_no_version]['description'] = policy_description
|
||||
else: # In verbose mode, return all policy versions.
|
||||
policy_description = "\"{:s}\": {:s}".format(policy_name, policy['changelog'])
|
||||
if policy['server_policy']:
|
||||
server_policy_descriptions.append(policy_description)
|
||||
else:
|
||||
client_policy_descriptions.append(policy_description)
|
||||
|
||||
# Now that we have references to the latest policies only, add their full descriptions to the lists for returning.
|
||||
if not verbose:
|
||||
for _, dd in latest_server_policies.items():
|
||||
server_policy_descriptions.append(cast(str, dd['description']))
|
||||
|
||||
for _, dd in latest_client_policies.items():
|
||||
client_policy_descriptions.append(cast(str, dd['description']))
|
||||
|
||||
# Sort the lists for better readability.
|
||||
server_policy_descriptions.sort()
|
||||
client_policy_descriptions.sort()
|
||||
return server_policy_descriptions, client_policy_descriptions
|
||||
@@ -549,6 +583,14 @@ macs = %s
|
||||
# Ensure this struct has all the necessary fields.
|
||||
p._normalize_hostkey_sizes() # pylint: disable=protected-access
|
||||
|
||||
# Now check if an updated version of the requested policy exists. If so, set a warning for the user.
|
||||
if p is not None and p._version is not None: # pylint: disable=protected-access
|
||||
next_version = str(int(p._version) + 1) # pylint: disable=protected-access
|
||||
name_version_pos = policy_name.find("(version ")
|
||||
next_version_name = policy_name[0:name_version_pos] + "(version %s)" % next_version
|
||||
if next_version_name in BUILTIN_POLICIES:
|
||||
p._updated_builtin_policy_available = True # pylint: disable=protected-access
|
||||
|
||||
return p
|
||||
|
||||
|
||||
|
||||
@@ -160,8 +160,8 @@ 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': [[], [], [], [INFO_EXTENSION_NEGOTIATION]], # Extension negotiation (RFC 8308)
|
||||
'ext-info-s': [[], [], [], [INFO_EXTENSION_NEGOTIATION]], # Extension negotiation (RFC 8308)
|
||||
'ext-info-c': [['7.2'], [], [], [INFO_EXTENSION_NEGOTIATION]], # Extension negotiation (RFC 8308)
|
||||
'ext-info-s': [['9.6'], [], [], [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).
|
||||
|
||||
|
||||
@@ -670,7 +670,12 @@ 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, 'port': aconf.port, 'policy': aconf.policy.get_name_and_version(), 'passed': passed, 'errors': error_struct}
|
||||
warnings: List[str] = []
|
||||
if aconf.policy.is_outdated_builtin_policy():
|
||||
warnings.append("A newer version of this built-in policy is available.")
|
||||
|
||||
json_struct = {'host': aconf.host, 'port': aconf.port, 'policy': aconf.policy.get_name_and_version(), 'passed': passed, 'errors': error_struct, 'warnings': warnings}
|
||||
|
||||
out.info(json.dumps(json_struct, indent=4 if aconf.json_print_indent else None, sort_keys=True))
|
||||
else:
|
||||
spacing = ''
|
||||
@@ -703,6 +708,10 @@ def evaluate_policy(out: OutputBuffer, aconf: AuditConf, banner: Optional['Banne
|
||||
out.fail("%sFailed!" % icon_fail)
|
||||
out.warn("\nErrors:\n%s" % error_str)
|
||||
|
||||
# If the user selected an out-dated built-in policy then issue a warning.
|
||||
if aconf.policy.is_outdated_builtin_policy():
|
||||
out.warn("Note: A newer version of this built-in policy is available. Use the -L option to view all available versions.")
|
||||
|
||||
return passed
|
||||
|
||||
|
||||
@@ -773,8 +782,9 @@ def list_policies(out: OutputBuffer, verbose: bool) -> None:
|
||||
out.fail("Error: no built-in policies found!")
|
||||
else:
|
||||
out.info("\nHint: Use -P and provide the full name of a policy to run a policy scan with.\n")
|
||||
out.info("Hint: Use -L -v to also see the change log for each policy.\n")
|
||||
out.info("Hint: Use -L -v to see the change log for each policy, as well as previous versions.\n")
|
||||
out.info("Note: the general OpenSSH policies apply to the official releases only. OS distributions may back-port changes that cause failures (for example, Debian 11 back-ported the strict KEX mode into their package of OpenSSH v8.4, whereas it was only officially added to OpenSSH v9.6 and later). In these cases, consider creating a custom policy (-M option).\n")
|
||||
out.info("Note: instructions for hardening targets, which correspond to the above policies, can be found at: <https://ssh-audit.com/hardening_guides.html>\n")
|
||||
out.write()
|
||||
|
||||
|
||||
|
||||
@@ -3,5 +3,6 @@
|
||||
"host": "localhost",
|
||||
"passed": true,
|
||||
"policy": "Docker policy: test1 (version 1)",
|
||||
"port": 2222
|
||||
"port": 2222,
|
||||
"warnings": []
|
||||
}
|
||||
|
||||
@@ -28,5 +28,6 @@
|
||||
"host": "localhost",
|
||||
"passed": false,
|
||||
"policy": "Docker poliicy: test10 (version 1)",
|
||||
"port": 2222
|
||||
"port": 2222,
|
||||
"warnings": []
|
||||
}
|
||||
|
||||
@@ -20,5 +20,6 @@
|
||||
"host": "localhost",
|
||||
"passed": false,
|
||||
"policy": "Docker policy: test2 (version 1)",
|
||||
"port": 2222
|
||||
"port": 2222,
|
||||
"warnings": []
|
||||
}
|
||||
|
||||
@@ -19,5 +19,6 @@
|
||||
"host": "localhost",
|
||||
"passed": false,
|
||||
"policy": "Docker policy: test3 (version 1)",
|
||||
"port": 2222
|
||||
"port": 2222,
|
||||
"warnings": []
|
||||
}
|
||||
|
||||
@@ -29,5 +29,6 @@
|
||||
"host": "localhost",
|
||||
"passed": false,
|
||||
"policy": "Docker policy: test4 (version 1)",
|
||||
"port": 2222
|
||||
"port": 2222,
|
||||
"warnings": []
|
||||
}
|
||||
|
||||
@@ -28,5 +28,6 @@
|
||||
"host": "localhost",
|
||||
"passed": false,
|
||||
"policy": "Docker policy: test5 (version 1)",
|
||||
"port": 2222
|
||||
"port": 2222,
|
||||
"warnings": []
|
||||
}
|
||||
|
||||
@@ -3,5 +3,6 @@
|
||||
"host": "localhost",
|
||||
"passed": true,
|
||||
"policy": "Docker poliicy: test7 (version 1)",
|
||||
"port": 2222
|
||||
"port": 2222,
|
||||
"warnings": []
|
||||
}
|
||||
|
||||
@@ -16,5 +16,6 @@
|
||||
"host": "localhost",
|
||||
"passed": false,
|
||||
"policy": "Docker poliicy: test8 (version 1)",
|
||||
"port": 2222
|
||||
"port": 2222,
|
||||
"warnings": []
|
||||
}
|
||||
|
||||
@@ -16,5 +16,6 @@
|
||||
"host": "localhost",
|
||||
"passed": false,
|
||||
"policy": "Docker poliicy: test9 (version 1)",
|
||||
"port": 2222
|
||||
"port": 2222,
|
||||
"warnings": []
|
||||
}
|
||||
|
||||
@@ -40,5 +40,6 @@
|
||||
"host": "localhost",
|
||||
"passed": false,
|
||||
"policy": "Hardened OpenSSH Server v8.0 (version 4)",
|
||||
"port": 2222
|
||||
"port": 2222,
|
||||
"warnings": []
|
||||
}
|
||||
|
||||
@@ -63,5 +63,6 @@
|
||||
"host": "localhost",
|
||||
"passed": false,
|
||||
"policy": "Hardened OpenSSH Server v8.0 (version 4)",
|
||||
"port": 2222
|
||||
"port": 2222,
|
||||
"warnings": []
|
||||
}
|
||||
|
||||
@@ -3,5 +3,6 @@
|
||||
"host": "localhost",
|
||||
"passed": true,
|
||||
"policy": "Docker policy: test11 (version 1)",
|
||||
"port": 2222
|
||||
"port": 2222,
|
||||
"warnings": []
|
||||
}
|
||||
|
||||
@@ -40,5 +40,6 @@
|
||||
"host": "localhost",
|
||||
"passed": false,
|
||||
"policy": "Docker policy: test12 (version 1)",
|
||||
"port": 2222
|
||||
"port": 2222,
|
||||
"warnings": []
|
||||
}
|
||||
|
||||
@@ -3,5 +3,6 @@
|
||||
"host": "localhost",
|
||||
"passed": true,
|
||||
"policy": "Docker policy: test13 (version 1)",
|
||||
"port": 2222
|
||||
"port": 2222,
|
||||
"warnings": []
|
||||
}
|
||||
|
||||
@@ -16,5 +16,6 @@
|
||||
"host": "localhost",
|
||||
"passed": false,
|
||||
"policy": "Docker policy: test14 (version 1)",
|
||||
"port": 2222
|
||||
"port": 2222,
|
||||
"warnings": []
|
||||
}
|
||||
|
||||
@@ -3,5 +3,6 @@
|
||||
"host": "localhost",
|
||||
"passed": true,
|
||||
"policy": "Docker policy: test15 (version 1)",
|
||||
"port": 2222
|
||||
"port": 2222,
|
||||
"warnings": []
|
||||
}
|
||||
|
||||
@@ -83,5 +83,6 @@
|
||||
"host": "localhost",
|
||||
"passed": false,
|
||||
"policy": "Docker policy: test16 (version 1)",
|
||||
"port": 2222
|
||||
"port": 2222,
|
||||
"warnings": []
|
||||
}
|
||||
|
||||
@@ -3,5 +3,6 @@
|
||||
"host": "localhost",
|
||||
"passed": true,
|
||||
"policy": "Docker policy: test17 (version 1)",
|
||||
"port": 2222
|
||||
"port": 2222,
|
||||
"warnings": []
|
||||
}
|
||||
|
||||
@@ -3,5 +3,6 @@
|
||||
"host": "localhost",
|
||||
"passed": true,
|
||||
"policy": "Docker policy: test6 (version 1)",
|
||||
"port": 2222
|
||||
"port": 2222,
|
||||
"warnings": []
|
||||
}
|
||||
|
||||
@@ -52,6 +52,14 @@ class TestPolicy:
|
||||
version_str = " (version %s)" % BUILTIN_POLICIES[policy_name]['version']
|
||||
assert policy_name.endswith(version_str)
|
||||
|
||||
# Ensure version field is a string, but can be parsed as an integer.
|
||||
version_field = BUILTIN_POLICIES[policy_name]['version']
|
||||
assert type(version_field) is str
|
||||
try:
|
||||
int(version_field)
|
||||
except ValueError:
|
||||
assert False, "version field of %s policy is not parseable as an integer." % policy_name
|
||||
|
||||
# Ensure no extra fields are present.
|
||||
assert len(required_fields) == len(BUILTIN_POLICIES[policy_name])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user