7 Commits

Author SHA1 Message Date
Joe Testa 3b8a75e407 Server kex/host key parsing failures no longer output a stack trace unless in debug mode. 2024-09-25 17:34:18 -04:00
Joe Testa 67e11f82b3 Updated --targets description. 2024-09-25 17:12:16 -04:00
Joe Testa 2cd96f1785 Ensure ECDSA and DSS fingerprints are only output in verbose mode. Clean up Docker tests from merge of #286. 2024-09-25 17:05:17 -04:00
Daniel Lenski a4b78b752e Enable HostKeyTest to extract ECDSA and DSA keys (#286)
Their certificate-embedded counterparts are enabled as well.

As with RSA, it *is* possible for DSA keys to be of variable length (not
just 1024 bits), so I've added `{'variable_key_len': True}` to the relevant
`HOST_KEY_TYPES` entries, although this key-value pair is otherwise unused.
2024-09-25 16:57:03 -04:00
Joe Testa ac540c8b5f Created FUNDING.yml. 2024-09-25 16:20:45 -04:00
Joe Testa e11492b7a3 Updated shields. 2024-09-25 16:07:01 -04:00
Joe Testa 02bc48c574 Bumped supported Python range. 2024-09-25 14:18:41 -04:00
8 changed files with 98 additions and 10 deletions
+1
View File
@@ -0,0 +1 @@
github: jtesta
+12 -4
View File
@@ -1,10 +1,15 @@
# ssh-audit
[![License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](https://github.com/jtesta/ssh-audit/blob/master/LICENSE)
[![PyPI Downloads](https://img.shields.io/pypi/dm/ssh-audit?label=pypi%20downloads&color=purple)](https://pypi.org/project/ssh-audit/)
[![Docker Pulls](https://img.shields.io/docker/pulls/positronsecurity/ssh-audit)](https://hub.docker.com/r/positronsecurity/ssh-audit)
[![Build Status](https://github.com/jtesta/ssh-audit/actions/workflows/tox.yaml/badge.svg)](https://github.com/jtesta/ssh-audit/actions)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/jtesta/ssh-audit/blob/master/CONTRIBUTING.md)
[![PyPI Downloads](https://img.shields.io/pypi/dm/ssh-audit?label=pypi%20downloads&color=purple)](https://pypi.org/project/ssh-audit/)
[![Homebrew Downloads](https://img.shields.io/homebrew/installs/dy/ssh-audit?label=homebrew%20downloads&color=teal)](https://formulae.brew.sh/formula/ssh-audit)
[![Docker Pulls](https://img.shields.io/docker/pulls/positronsecurity/ssh-audit)](https://hub.docker.com/r/positronsecurity/ssh-audit)
[![Snap Downloads](https://img.shields.io/badge/snap%20downloads-no%20idea-yellow.svg)](https://snapcraft.io/ssh-audit)
[![Github Sponsors](https://img.shields.io/github/sponsors/jtesta?color=red)](https://github.com/sponsors/jtesta)
**ssh-audit** is a tool for ssh server & client configuration auditing.
[jtesta/ssh-audit](https://github.com/jtesta/ssh-audit/) (v2.0+) is the updated and maintained version of ssh-audit forked from [arthepsy/ssh-audit](https://github.com/arthepsy/ssh-audit) (v1.x) due to inactivity.
@@ -32,7 +37,7 @@
- historical information from OpenSSH, Dropbear SSH and libssh;
- policy scans to ensure adherence to a hardened/standard configuration;
- runs on Linux and Windows;
- supports Python 3.8 - 3.12;
- supports Python 3.8 - 3.13;
- no dependencies
## Usage
@@ -89,7 +94,9 @@ usage: ssh-audit.py [options] <host>
-t, --timeout=<secs> timeout (in seconds) for connection and reading
(default: 5)
-T, --targets=<hosts.txt> a file containing a list of target hosts (one
per line, format HOST[:PORT])
per line, format HOST[:PORT]). Use -p/--port
to set the default port for all hosts. Use
--threads to control concurrent scans.
--threads=<threads> number of threads to use when scanning multiple
targets (-T/--targets) (default: 32)
-v, --verbose verbose output
@@ -224,6 +231,7 @@ For convenience, a web front-end on top of the command-line tool is available at
- Fixed DHEat connection rate testing on MacOS X and BSD platforms; credit [Drew Noel](https://github.com/drewmnoel) and [Michael Osipov](https://github.com/michael-o).
- Fixed invalid JSON output when a socket error occurs while performing a client audit.
- When scanning multiple targets (using `-T`/`--targets`), the `-p`/`--port` option will now be used as the default port (set to 22 if `-p`/`--port` is not given). Hosts specified in the file can override this default with an explicit port number (i.e.: "host1:1234"). For example, when using `-T targets.txt -p 222`, all hosts in `targets.txt` that do not explicitly include a port number will default to 222; when using `-T targets.txt` (without `-p`), all hosts will use a default of 22.
- Now reports ECDSA and DSS fingerprints when in verbose mode; partial credit [Daniel Lenski](https://github.com/dlenskiSB).
- Added 1 new cipher: `grasshopper-ctr128`.
- Added 2 new key exchanges: `mlkem768x25519-sha256`, `sntrup761x25519-sha512`.
+25 -4
View File
@@ -1,7 +1,7 @@
"""
The MIT License (MIT)
Copyright (C) 2017-2023 Joe Testa (jtesta@positronsecurity.com)
Copyright (C) 2017-2024 Joe Testa (jtesta@positronsecurity.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -55,6 +55,17 @@ class HostKeyTest:
'ssh-ed448': {'cert': False, 'variable_key_len': False},
# 'ssh-ed448-cert-v01@openssh.com': {'cert': True, 'variable_key_len': False},
'ecdsa-sha2-nistp256': {'cert': False, 'variable_key_len': False},
'ecdsa-sha2-nistp384': {'cert': False, 'variable_key_len': False},
'ecdsa-sha2-nistp521': {'cert': False, 'variable_key_len': False},
'ecdsa-sha2-nistp256-cert-v01@openssh.com': {'cert': True, 'variable_key_len': False},
'ecdsa-sha2-nistp384-cert-v01@openssh.com': {'cert': True, 'variable_key_len': False},
'ecdsa-sha2-nistp521-cert-v01@openssh.com': {'cert': True, 'variable_key_len': False},
'ssh-dss': {'cert': False, 'variable_key_len': True},
'ssh-dss-cert-v01@openssh.com': {'cert': True, 'variable_key_len': True},
}
TWO2K_MODULUS_WARNING = '2048-bit modulus only provides 112-bits of symmetric strength'
@@ -140,7 +151,12 @@ class HostKeyTest:
_, payload = s.read_packet()
SSH2_Kex.parse(out, payload)
except Exception:
out.v("Failed to parse server's kex. Stack trace:\n%s" % str(traceback.format_exc()), write_now=True)
msg = "Failed to parse server's kex."
if not out.debug:
msg += " Re-run in debug mode to see stack trace."
out.v(msg, write_now=True)
out.d("Stack trace:\n%s" % str(traceback.format_exc()), write_now=True)
return
# Do the initial DH exchange. The server responds back
@@ -151,7 +167,12 @@ class HostKeyTest:
kex_reply = kex_group.recv_reply(s)
raw_hostkey_bytes = kex_reply if kex_reply is not None else b''
except KexDHException:
out.v("Failed to parse server's host key. Stack trace:\n%s" % str(traceback.format_exc()), write_now=True)
msg = "Failed to parse server's host key."
if not out.debug:
msg += " Re-run in debug mode to see stack trace."
out.v(msg, write_now=True)
out.d("Stack trace:\n%s" % str(traceback.format_exc()), write_now=True)
# Since parsing this host key failed, there's nothing more to do but close the socket and move on to the next host key type.
s.close()
@@ -191,7 +212,7 @@ class HostKeyTest:
cakey_warn_str = HostKeyTest.SMALL_ECC_MODULUS_WARNING
# Keys smaller than 2048 result in a failure. Keys smaller 3072 result in a warning. Update the database accordingly.
if (cert is False) and (hostkey_modulus_size < hostkey_min_good):
if (cert is False) and (hostkey_modulus_size < hostkey_min_good) and (host_key_type != 'ssh-dss'): # Skip ssh-dss, otherwise we get duplicate failure messages (SSH2_KexDB will always flag it).
# If the key is under 2048, add to the failure list.
if hostkey_modulus_size < hostkey_min_warn:
+10 -2
View File
@@ -360,11 +360,19 @@ def output_fingerprints(out: OutputBuffer, algs: Algorithms, is_json_output: boo
fp_types = sorted(fps.keys())
for fp_type in fp_types:
fp = fps[fp_type]
out.good('(fin) {}: {}'.format(fp_type, fp.sha256))
# Don't output any ECDSA or DSS fingerprints unless verbose mode is enabled.
if fp_type.startswith("ecdsa-") or (fp_type == "ssh-dss"):
if out.verbose:
out.warn('(fin) {}: {} -- [info] this fingerprint type is insecure and should not be relied upon'.format(fp_type, fp.sha256))
else:
continue # If verbose mode is not enabled, skip this type entirely.
else:
out.good('(fin) {}: {}'.format(fp_type, fp.sha256))
# Output the MD5 hash too if verbose mode is enabled.
if out.verbose:
out.info('(fin) {}: {} -- [info] do not rely on MD5 fingerprints for server identification; it is insecure for this use case'.format(fp_type, fp.md5))
out.warn('(fin) {}: {} -- [info] do not rely on MD5 fingerprints for server identification; it is insecure for this use case'.format(fp_type, fp.md5))
if not out.is_section_empty() and not is_json_output:
out.head('# fingerprints')
@@ -78,6 +78,26 @@
}
],
"fingerprints": [
{
"hash": "jdUfqoGCDOY1drQcoqIJm/pEix2r09hqwOs9E9GimZQ",
"hash_alg": "SHA256",
"hostkey": "ecdsa-sha2-nistp256"
},
{
"hash": "98:27:f3:12:20:f6:23:6d:1a:00:2a:6c:71:7c:1e:6b",
"hash_alg": "MD5",
"hostkey": "ecdsa-sha2-nistp256"
},
{
"hash": "NBzry0uMAX8BRsn4mv9CHpeivMOdwzGFEKrf6Hg7tIQ",
"hash_alg": "SHA256",
"hostkey": "ssh-dss"
},
{
"hash": "16:60:9e:54:d7:1e:b3:0d:97:60:12:ad:fe:83:a2:40",
"hash_alg": "MD5",
"hostkey": "ssh-dss"
},
{
"hash": "CDfAU12pjQS7/91kg7gYacza0U/6PDbE04Ic3IpYxkM",
"hash_alg": "SHA256",
@@ -253,6 +253,16 @@
}
],
"fingerprints": [
{
"hash": "sqDDYhzYz7YIQeFDc0WF8SeXtrEz+iwsV7d/FdIgztM",
"hash_alg": "SHA256",
"hostkey": "ssh-dss"
},
{
"hash": "5c:de:62:f0:60:c8:93:13:87:71:78:95:56:3f:61:51",
"hash_alg": "MD5",
"hostkey": "ssh-dss"
},
{
"hash": "YZ457EBcJTSxRKI3yXRgtAj3PBf5B9/F36b1SVooml4",
"hash_alg": "SHA256",
@@ -240,6 +240,16 @@
}
],
"fingerprints": [
{
"hash": "sqDDYhzYz7YIQeFDc0WF8SeXtrEz+iwsV7d/FdIgztM",
"hash_alg": "SHA256",
"hostkey": "ssh-dss"
},
{
"hash": "5c:de:62:f0:60:c8:93:13:87:71:78:95:56:3f:61:51",
"hash_alg": "MD5",
"hostkey": "ssh-dss"
},
{
"hash": "YZ457EBcJTSxRKI3yXRgtAj3PBf5B9/F36b1SVooml4",
"hash_alg": "SHA256",
@@ -87,6 +87,16 @@
}
],
"fingerprints": [
{
"hash": "Q6Llm0o4TrcUen4tnT2h4BDf2f+ina6dIJmVH8c40bg",
"hash_alg": "SHA256",
"hostkey": "ecdsa-sha2-nistp256"
},
{
"hash": "cc:e0:80:84:5b:05:98:64:24:43:52:3b:17:c8:94:89",
"hash_alg": "MD5",
"hostkey": "ecdsa-sha2-nistp256"
},
{
"hash": "UrnXIVH+7dlw8UqYocl48yUEcKrthGDQG2CPCgp7MxU",
"hash_alg": "SHA256",