17 Commits

Author SHA1 Message Date
0x08 f608b7a7f8 Merge e3e3657f2b into 06ebdbd0fe 2024-09-01 23:02:32 +08:00
Joe Testa 06ebdbd0fe Updated README. 2024-08-26 16:46:34 -04:00
Drew Noel 7752023dc2 Switch connect_ex result checks to use errno lookups (#289)
* Switch connect_ex result checks to errno lookups

* Return errno strings, clean up comment
2024-08-26 16:38:44 -04:00
Joe Testa a6f02ae8e8 Added debugging output for key exchanges. 2024-08-26 16:25:32 -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
Joe Testa e42961fa9a Added built-in policy for OpenSSH 9.8. 2024-07-02 21:31:36 -04:00
Joe Testa dcbc43acdf Fixed crash when running with '-P' and '-T' options simultaneously. (#273) 2024-07-02 20:56:11 -04:00
Joe Testa 87e22ae26b Added IPv6 support for DHEat and connection rate tests. (#269) 2024-06-29 19:05:20 -04:00
0x08 e3e3657f2b fix: Add space to fix URLs in terminal
- Added space between URL and ">" character which made the link show not found page because ">" becomes part of the URL

Example URLs: - https://github.com/jtesta/ssh-audit/issues>
                       - https://github.com/jtesta/ssh-audit/issues%3E
                       - https://www.ssh-audit.com/hardening_guides.html%3E 
                        - https://www.ssh-audit.com/hardening_guides.html>

Example New URLs:  - https://github.com/jtesta/ssh-audit/issues
                               - https://www.ssh-audit.com/hardening_guides.html
2024-05-23 15:09:45 +02:00
Joe Testa 46ec4e3edc Added built-in policies for Ubuntu 24.04 LTS server and client. 2024-04-29 19:11:47 -04:00
Joe Testa d19b154a46 Bumped version to v3.3.0-dev. 2024-04-22 17:57:26 -04:00
Joe Testa c5d90106e8 Updated docker run command. 2024-04-22 17:54:37 -04:00
30 changed files with 162 additions and 41 deletions
+10 -1
View File
@@ -202,7 +202,7 @@ To install from Dockerhub:
```
$ docker pull positronsecurity/ssh-audit
```
(Then run with: `docker run -it -p 2222:2222 positronsecurity/ssh-audit 10.1.1.1`)
(Then run with: `docker run -it --rm -p 2222:2222 positronsecurity/ssh-audit 10.1.1.1`)
The status of various other platform packages can be found below (via Repology):
@@ -213,6 +213,15 @@ For convenience, a web front-end on top of the command-line tool is available at
## ChangeLog
### v3.3.0-dev (???)
- 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 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 host key tests from only reporting a key type at most once despite multiple hosts supporting it; credit [Daniel Lenski](https://github.com/dlenskiSB).
- 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).
### 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)).
- Expanded filter of CBC ciphers to flag for the Terrapin vulnerability. It now includes more rarely found ciphers.
+4 -1
View File
@@ -49,6 +49,7 @@ BUILTIN_POLICIES: Dict[str, Dict[str, Union[Optional[str], Optional[List[str]],
'Hardened Ubuntu Server 22.04 LTS (version 5)': {'version': '5', 'changelog': 'Added kex-strict-s-v00@openssh.com to kex list.', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['sntrup761x25519-sha512@openssh.com', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256', 'kex-strict-s-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {"rsa-sha2-256": {"hostkey_size": 4096}, "rsa-sha2-256-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "rsa-sha2-512": {"hostkey_size": 4096}, "rsa-sha2-512-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "sk-ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}, "sk-ssh-ed25519@openssh.com": {"hostkey_size": 256}, "ssh-ed25519": {"hostkey_size": 256}, "ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}}, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 3072}, 'server_policy': True},
'Hardened Ubuntu Server 24.04 LTS (version 1)': {'version': '1', 'changelog': 'Initial version.', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519', 'rsa-sha2-512', 'rsa-sha2-256'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['sntrup761x25519-sha512@openssh.com', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256', 'diffie-hellman-group16-sha512', 'ext-info-s', 'kex-strict-s-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-gcm@openssh.com', 'aes128-ctr'], 'macs': ['hmac-sha2-512-etm@openssh.com', 'hmac-sha2-256-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {"rsa-sha2-256": {"hostkey_size": 4096}, "rsa-sha2-256-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "rsa-sha2-512": {"hostkey_size": 4096}, "rsa-sha2-512-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "sk-ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}, "sk-ssh-ed25519@openssh.com": {"hostkey_size": 256}, "ssh-ed25519": {"hostkey_size": 256}, "ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}}, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 3072}, 'server_policy': True},
# Generic OpenSSH Server policies
@@ -94,6 +95,8 @@ BUILTIN_POLICIES: Dict[str, Dict[str, Union[Optional[str], Optional[List[str]],
'Hardened OpenSSH Server v9.7 (version 1)': {'version': '1', 'changelog': 'Initial version.', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['sntrup761x25519-sha512@openssh.com', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256', 'ext-info-s', 'kex-strict-s-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {"rsa-sha2-256": {"hostkey_size": 4096}, "rsa-sha2-256-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "rsa-sha2-512": {"hostkey_size": 4096}, "rsa-sha2-512-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "sk-ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}, "sk-ssh-ed25519@openssh.com": {"hostkey_size": 256}, "ssh-ed25519": {"hostkey_size": 256}, "ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}}, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 3072}, 'server_policy': True},
'Hardened OpenSSH Server v9.8 (version 1)': {'version': '1', 'changelog': 'Initial version.', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['sntrup761x25519-sha512@openssh.com', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256', 'ext-info-s', 'kex-strict-s-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {"rsa-sha2-256": {"hostkey_size": 4096}, "rsa-sha2-256-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "rsa-sha2-512": {"hostkey_size": 4096}, "rsa-sha2-512-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "sk-ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}, "sk-ssh-ed25519@openssh.com": {"hostkey_size": 256}, "ssh-ed25519": {"hostkey_size": 256}, "ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}}, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 3072}, 'server_policy': True},
# Amazon Linux Policies
@@ -109,7 +112,6 @@ BUILTIN_POLICIES: Dict[str, Dict[str, Union[Optional[str], Optional[List[str]],
'Hardened Rocky Linux Client 9 (version 1)': {'version': '1', 'changelog': 'Initial version.', 'banner': None, 'compressions': None, 'host_keys': ['sk-ssh-ed25519-cert-v01@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'sk-ssh-ed25519@openssh.com', 'ssh-ed25519', 'rsa-sha2-512', 'rsa-sha2-256'], 'optional_host_keys': None, 'kex': ['sntrup761x25519-sha512@openssh.com', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256', 'ext-info-c', 'kex-strict-c-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'dh_modulus_sizes': None, 'server_policy': False},
# Ubuntu Client policies
'Hardened Ubuntu Client 16.04 LTS (version 2)': {'version': '2', 'changelog': 'No change log available.', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519', 'ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256', 'rsa-sha2-512'], 'optional_host_keys': None, 'kex': ['curve25519-sha256@libssh.org', 'diffie-hellman-group-exchange-sha256', 'ext-info-c'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'dh_modulus_sizes': None, 'server_policy': False},
@@ -120,5 +122,6 @@ BUILTIN_POLICIES: Dict[str, Dict[str, Union[Optional[str], Optional[List[str]],
'Hardened Ubuntu Client 22.04 LTS (version 4)': {'version': '4', 'changelog': 'Added kex-strict-c-v00@openssh.com to kex list.', 'banner': None, 'compressions': None, 'host_keys': ['sk-ssh-ed25519-cert-v01@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'sk-ssh-ed25519@openssh.com', 'ssh-ed25519', 'rsa-sha2-512', 'rsa-sha2-256'], 'optional_host_keys': None, 'kex': ['sntrup761x25519-sha512@openssh.com', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256', 'ext-info-c', 'kex-strict-c-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'dh_modulus_sizes': None, 'server_policy': False},
'Hardened Ubuntu Client 24.04 LTS (version 1)': {'version': '1', 'changelog': 'Initial version.', 'banner': None, 'compressions': None, 'host_keys': ['sk-ssh-ed25519-cert-v01@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'sk-ssh-ed25519@openssh.com', 'ssh-ed25519', 'rsa-sha2-512', 'rsa-sha2-256'], 'optional_host_keys': None, 'kex': ['sntrup761x25519-sha512@openssh.com', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256', 'diffie-hellman-group16-sha512', 'ext-info-c', 'kex-strict-c-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-gcm@openssh.com', 'aes128-ctr'], 'macs': ['hmac-sha2-512-etm@openssh.com', 'hmac-sha2-256-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'dh_modulus_sizes': None, 'server_policy': False},
}
+38 -10
View File
@@ -21,6 +21,7 @@
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
import errno
import multiprocessing
import os
import queue
@@ -160,6 +161,11 @@ class DHEat:
# The SSH2_Kex object that we recieved from the server in a prior connection. We'll use it as a template to craft our own kex.
self.kex = kex
# Resolve the target to an IP address depending on the user preferences (IPv4 or IPv6).
self.debug("Resolving target %s..." % self.target)
self.target_address_family, self.target_ip_address = DHEat._resolve_hostname(self.target, aconf.ip_version_preference)
self.debug("Resolved %s to %s (address family %u)" % (self.target, self.target_ip_address, self.target_address_family))
# The connection and read timeouts.
self.connect_timeout = aconf.timeout
self.read_timeout = aconf.timeout
@@ -324,6 +330,11 @@ class DHEat:
print("\n%sUnfortunately, this feature is not currently functional under Windows.%s This should get fixed in a future release. See: <https://github.com/jtesta/ssh-audit/issues/261>" % (DHEat.YELLOWB, DHEat.CLEAR))
return ""
# Resolve the target into an IP address
out.d("Resolving target %s..." % aconf.host)
target_address_family, target_ip_address = DHEat._resolve_hostname(aconf.host, aconf.ip_version_preference)
out.d("Resolved %s to %s (address family %u)" % (aconf.host, target_ip_address, target_address_family))
spinner = ["-", "\\", "|", "/"]
spinner_index = 0
@@ -349,7 +360,7 @@ class DHEat:
rate_str = " at a max rate of %s%u%s connections per second" % (DHEat.WHITEB, aconf.conn_rate_test_target_rate, DHEat.CLEAR)
print()
print("Performing non-disruptive rate test against %s[%s]:%u%s with %s%u%s concurrent sockets%s. No Diffie-Hellman requests will be sent." % (DHEat.WHITEB, aconf.host, aconf.port, DHEat.CLEAR, DHEat.WHITEB, concurrent_sockets, DHEat.CLEAR, rate_str))
print("Performing non-disruptive rate test against %s[%s]:%u%s with %s%u%s concurrent sockets%s. No Diffie-Hellman requests will be sent." % (DHEat.WHITEB, target_ip_address, aconf.port, DHEat.CLEAR, DHEat.WHITEB, concurrent_sockets, DHEat.CLEAR, rate_str))
print()
# Make room for the multi-line output.
@@ -426,16 +437,16 @@ class DHEat:
# Open new sockets until we've hit the number of concurrent sockets, or if we exceeded the number of maximum connections.
while (len(socket_dict) < concurrent_sockets) and (len(socket_dict) + num_opened_connections < max_connections):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s = socket.socket(target_address_family, socket.SOCK_STREAM)
s.setblocking(False)
# out.d("Creating socket (%u of %u already exist)..." % (len(socket_dict), concurrent_sockets), write_now=True)
ret = s.connect_ex((aconf.host, aconf.port))
ret = s.connect_ex((target_ip_address, aconf.port))
num_attempted_connections += 1
if ret in [0, 115]: # Check if connection is successful or EINPROGRESS.
if ret in [0, errno.EINPROGRESS]:
socket_dict[s] = now
else:
out.d("connect_ex() returned: %d" % ret, write_now=True)
out.d("connect_ex() returned: %s (%d)" % (os.strerror(ret), ret), write_now=True)
# out.d("Calling select() on %u sockets..." % len(socket_dict), write_now=True)
socket_list: List[socket.socket] = [*socket_dict] # Get a list of sockets from the dictionary.
@@ -743,6 +754,22 @@ class DHEat:
print()
@staticmethod
def _resolve_hostname(host: str, ip_version_preference: List[int]) -> Tuple[int, str]:
'''Resolves a hostname to its IPv4 or IPv6 address, depending on user preference.'''
family = socket.AF_UNSPEC
if len(ip_version_preference) == 1:
family = socket.AF_INET if ip_version_preference[0] == 4 else socket.AF_INET6
r = socket.getaddrinfo(host, 0, family, socket.SOCK_STREAM)
for address_family, socktype, _, _, addr in r:
if socktype == socket.SOCK_STREAM:
return address_family, addr[0]
return -1, ''
def _run(self) -> bool:
'''Where all the magic happens.'''
@@ -751,7 +778,7 @@ class DHEat:
if sys.platform == "win32":
self.output("%sWARNING:%s this feature has not been thoroughly tested on Windows. It may perform worse than on UNIX OSes." % (self.YELLOWB, self.CLEAR))
self.output("Running DHEat test against %s[%s]:%u%s with %s%u%s concurrent sockets..." % (self.WHITEB, self.target, self.port, self.CLEAR, self.WHITEB, self.concurrent_connections, self.CLEAR))
self.output("Running DHEat test against %s[%s]:%u%s with %s%u%s concurrent sockets..." % (self.WHITEB, self.target_ip_address, self.port, self.CLEAR, self.WHITEB, self.concurrent_connections, self.CLEAR))
# If the user didn't specify an exact kex algorithm to test, check our prioritized list against what the server supports. Larger p-values (such as group18: 8192-bits) cause the most strain on the server.
chosen_alg = ""
@@ -894,7 +921,8 @@ class DHEat:
# Copy variables from the object (which might exist in another process?). This might cut down on inter-process overhead.
connect_timeout = self.connect_timeout
target = self.target
target_ip_address = self.target_ip_address
target_address_family = self.target_address_family
port = self.port
# Determine if we are attacking with a GEX.
@@ -945,17 +973,17 @@ class DHEat:
num_socket_exceptions = 0
num_openssh_throttled_connections = 0
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s = socket.socket(target_address_family, socket.SOCK_STREAM)
s.settimeout(connect_timeout)
# Loop until a successful TCP connection is made.
connected = False
while not connected:
# self.debug("Connecting to %s:%d" % (self.target, self.port))
# self.debug("Connecting to %s:%d" % (self.target_ip_address, self.port))
try:
num_attempted_tcp_connections += 1
s.connect((target, port))
s.connect((target_ip_address, port))
connected = True
except OSError as e:
self.debug("Failed to connect: %s" % str(e))
+1 -1
View File
@@ -22,7 +22,7 @@
THE SOFTWARE.
"""
# The version to display.
VERSION = 'v3.2.0'
VERSION = 'v3.3.0-dev'
# SSH software to impersonate
SSH_HEADER = 'SSH-{0}-OpenSSH_8.2'
+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.
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 = {
'ssh-rsa': {'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-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'
@@ -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:
hostkey_modulus_size = 0
ca_modulus_size = 0
parsed_host_key_types = set()
# If the connection still exists, close it so we can test
# using a clean slate (otherwise it may exist in a non-testable
@@ -106,7 +110,7 @@ class HostKeyTest:
key_warn_comments = []
# 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
# 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_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("Raw hostkey bytes (%d): [%s]" % (len(raw_hostkey_bytes), raw_hostkey_bytes.hex()), write_now=True)
# 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)
@@ -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 host_key_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
# failure and/or warning comments.
@@ -228,7 +233,7 @@ class HostKeyTest:
db['key'][rsa_type][2].extend(key_warn_comments)
else:
host_key_types[host_key_type]['parsed'] = True
parsed_host_key_types.add(host_key_type)
db = SSH2_KexDB.get_db()
while len(db['key'][host_key_type]) < 3:
db['key'][host_key_type].append([])
+3
View File
@@ -134,6 +134,9 @@ class KexDH: # pragma: nocover
if self.__hostkey_type == 'ssh-ed25519':
self.out.d("%s has a fixed host key modulus of 32." % self.__hostkey_type)
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:
# 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)
+26
View File
@@ -605,3 +605,29 @@ macs = %s
dh_modulus_sizes_str = str(self._dh_modulus_sizes)
return "Name: %s\nVersion: %s\nAllow Algorithm Subset and/or Reordering: %r\nBanner: %s\nCompressions: %s\nHost Keys: %s\nOptional Host Keys: %s\nKey Exchanges: %s\nCiphers: %s\nMACs: %s\nHost Key Sizes: %s\nDH Modulus Sizes: %s\nServer Policy: %r" % (name, version, self._allow_algorithm_subset_and_reordering, banner, compressions_str, host_keys_str, optional_host_keys_str, kex_str, ciphers_str, macs_str, hostkey_sizes_str, dh_modulus_sizes_str, self._server_policy)
def __getstate__(self) -> Dict[str, Any]:
'''Called when pickling this object. The file descriptor isn't serializable, so we'll remove it from the state and include a string representation.'''
state = self.__dict__.copy()
if state['_warning_target'] == sys.stdout:
state['_warning_target_type'] = 'stdout'
else:
state['_warning_target_type'] = 'stderr'
del state['_warning_target']
return state
def __setstate__(self, state: Dict[str, Any]) -> None:
'''Called when unpickling this object. Based on the string representation of the file descriptor, we'll restore the right handle.'''
if state['_warning_target_type'] == 'stdout':
state['_warning_target'] = sys.stdout
else:
state['_warning_target'] = sys.stderr
del state['_warning_target_type']
self.__dict__.update(state)
+4
View File
@@ -224,4 +224,8 @@ class Software:
mx = re.match(r'^PuTTY_Release_(.*)', software)
if mx:
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
+14 -1
View File
@@ -1,7 +1,7 @@
"""
The MIT License (MIT)
Copyright (C) 2017-2020 Joe Testa (jtesta@positronsecurity.com)
Copyright (C) 2017-2024 Joe Testa (jtesta@positronsecurity.com)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -132,3 +132,16 @@ class SSH2_Kex:
srv = SSH2_KexParty(srv_enc, srv_mac, srv_compression, srv_languages)
kex = cls(outputbuffer, cookie, kex_algs, key_algs, cli, srv, follows, unused)
return kex
def __str__(self) -> str:
ret = "----\nSSH2_Kex object:"
ret += "\nHost keys: "
ret += ", ".join(self.__key_algs)
ret += "\nKey exchanges: "
ret += ", ".join(self.__kex_algs)
ret += "\nClient SSH2_KexParty:"
ret += "\n" + str(self.__client)
ret += "\nServer SSH2_KexParty:"
ret += "\n" + str(self.__server)
ret += "\n----"
return ret
+8
View File
@@ -1,6 +1,7 @@
"""
The MIT License (MIT)
Copyright (C) 2024 Joe Testa (jtesta@positronsecurity.com)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -48,3 +49,10 @@ class SSH2_KexParty:
@property
def languages(self) -> List[str]:
return self.__languages
def __str__(self) -> str:
ret = "Ciphers: " + ", ".join(self.__enc)
ret += "\nMACs: " + ", ".join(self.__mac)
ret += "\nCompressions: " + ", ".join(self.__compression)
ret += "\nLanguages: " + ", ".join(self.__languages)
return ret
+5 -2
View File
@@ -2,7 +2,7 @@
"""
The MIT License (MIT)
Copyright (C) 2017-2023 Joe Testa (jtesta@positronsecurity.com)
Copyright (C) 2017-2024 Joe Testa (jtesta@positronsecurity.com)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -421,6 +421,8 @@ def output_recommendations(out: OutputBuffer, algs: Algorithms, algorithm_recomm
fn = level_to_output[level]
an = '?'
sg = '?'
if action == 'del':
an, sg = 'remove', '-'
ret = False
@@ -733,7 +735,7 @@ 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, '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))
else:
spacing = ''
@@ -1315,6 +1317,7 @@ def audit(out: OutputBuffer, aconf: AuditConf, sshv: Optional[int] = None, print
elif sshv == 2:
try:
kex = SSH2_Kex.parse(out, payload)
out.d(str(kex))
except Exception:
out.fail("Failed to parse server's kex. Stack trace:\n%s" % str(traceback.format_exc()))
return exitcodes.CONNECTION_ERROR
@@ -2,5 +2,6 @@
"errors": [],
"host": "localhost",
"passed": true,
"policy": "Docker policy: test1 (version 1)"
"policy": "Docker policy: test1 (version 1)",
"port": 2222
}
@@ -27,5 +27,6 @@
],
"host": "localhost",
"passed": false,
"policy": "Docker poliicy: test10 (version 1)"
"policy": "Docker poliicy: test10 (version 1)",
"port": 2222
}
@@ -19,5 +19,6 @@
],
"host": "localhost",
"passed": false,
"policy": "Docker policy: test2 (version 1)"
"policy": "Docker policy: test2 (version 1)",
"port": 2222
}
@@ -18,5 +18,6 @@
],
"host": "localhost",
"passed": false,
"policy": "Docker policy: test3 (version 1)"
"policy": "Docker policy: test3 (version 1)",
"port": 2222
}
@@ -28,5 +28,6 @@
],
"host": "localhost",
"passed": false,
"policy": "Docker policy: test4 (version 1)"
"policy": "Docker policy: test4 (version 1)",
"port": 2222
}
@@ -27,5 +27,6 @@
],
"host": "localhost",
"passed": false,
"policy": "Docker policy: test5 (version 1)"
"policy": "Docker policy: test5 (version 1)",
"port": 2222
}
@@ -2,5 +2,6 @@
"errors": [],
"host": "localhost",
"passed": true,
"policy": "Docker poliicy: test7 (version 1)"
"policy": "Docker poliicy: test7 (version 1)",
"port": 2222
}
@@ -15,5 +15,6 @@
],
"host": "localhost",
"passed": false,
"policy": "Docker poliicy: test8 (version 1)"
"policy": "Docker poliicy: test8 (version 1)",
"port": 2222
}
@@ -15,5 +15,6 @@
],
"host": "localhost",
"passed": false,
"policy": "Docker poliicy: test9 (version 1)"
"policy": "Docker poliicy: test9 (version 1)",
"port": 2222
}
@@ -39,5 +39,6 @@
],
"host": "localhost",
"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",
"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": [],
"host": "localhost",
"passed": true,
"policy": "Docker policy: test11 (version 1)"
"policy": "Docker policy: test11 (version 1)",
"port": 2222
}
@@ -39,5 +39,6 @@
],
"host": "localhost",
"passed": false,
"policy": "Docker policy: test12 (version 1)"
"policy": "Docker policy: test12 (version 1)",
"port": 2222
}
@@ -2,5 +2,6 @@
"errors": [],
"host": "localhost",
"passed": true,
"policy": "Docker policy: test13 (version 1)"
"policy": "Docker policy: test13 (version 1)",
"port": 2222
}
@@ -15,5 +15,6 @@
],
"host": "localhost",
"passed": false,
"policy": "Docker policy: test14 (version 1)"
"policy": "Docker policy: test14 (version 1)",
"port": 2222
}
@@ -2,5 +2,6 @@
"errors": [],
"host": "localhost",
"passed": true,
"policy": "Docker policy: test15 (version 1)"
"policy": "Docker policy: test15 (version 1)",
"port": 2222
}
@@ -82,5 +82,6 @@
],
"host": "localhost",
"passed": false,
"policy": "Docker policy: test16 (version 1)"
"policy": "Docker policy: test16 (version 1)",
"port": 2222
}
@@ -2,5 +2,6 @@
"errors": [],
"host": "localhost",
"passed": true,
"policy": "Docker policy: test17 (version 1)"
"policy": "Docker policy: test17 (version 1)",
"port": 2222
}
@@ -2,5 +2,6 @@
"errors": [],
"host": "localhost",
"passed": true,
"policy": "Docker policy: test6 (version 1)"
"policy": "Docker policy: test6 (version 1)",
"port": 2222
}