mirror of
https://github.com/jtesta/ssh-audit.git
synced 2026-05-25 15:31:23 +02:00
Compare commits
8 Commits
v3.2.0
..
c095efe65a
| Author | SHA1 | Date | |
|---|---|---|---|
| c095efe65a | |||
| e42961fa9a | |||
| dcbc43acdf | |||
| 87e22ae26b | |||
| b2e621cafc | |||
| 46ec4e3edc | |||
| d19b154a46 | |||
| c5d90106e8 |
@@ -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,11 @@ 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.
|
||||
- Fixed crash when running with `-P` and `-T` options simultaneously.
|
||||
|
||||
### 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.
|
||||
|
||||
@@ -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},
|
||||
|
||||
}
|
||||
|
||||
+35
-8
@@ -160,6 +160,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 +329,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 +359,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,11 +436,11 @@ 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.
|
||||
socket_dict[s] = now
|
||||
@@ -743,6 +753,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 +777,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 +920,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 +972,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))
|
||||
|
||||
@@ -110,7 +110,7 @@ class GEXTest:
|
||||
# before continuing to issue reconnects.
|
||||
modulus_size_returned, reconnect_failed = GEXTest._send_init(out, s, kex_group, kex, gex_alg, bits_min, bits_pref, bits_max)
|
||||
if reconnect_failed:
|
||||
out.fail('Reconnect failed.')
|
||||
out.error('Reconnect failed.')
|
||||
return exitcodes.FAILURE
|
||||
|
||||
if modulus_size_returned > 0:
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -54,6 +54,14 @@ class OutputBuffer:
|
||||
self.__is_color_supported = ('colorama' in sys.modules) or (os.name == 'posix')
|
||||
self.line_ended = True
|
||||
|
||||
def error(self, msg, line_ended=True):
|
||||
"""
|
||||
Writes an error message to stderr.
|
||||
"""
|
||||
end = '' if line_ended else '\n'
|
||||
sys.stderr.write(f'{msg}{end}')
|
||||
sys.stderr.flush()
|
||||
|
||||
def _print(self, level: str, s: str = '', line_ended: bool = True) -> None:
|
||||
'''Saves output to buffer (if in buffered mode), or immediately prints to stdout otherwise.'''
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -88,7 +88,7 @@ def usage(uout: OutputBuffer, err: Optional[str] = None) -> None:
|
||||
p = os.path.basename(sys.argv[0])
|
||||
uout.head('# {} {}, https://github.com/jtesta/ssh-audit\n'.format(p, VERSION))
|
||||
if err is not None and len(err) > 0:
|
||||
uout.fail(err + '\n')
|
||||
uout.error(err + '\n')
|
||||
retval = exitcodes.UNKNOWN_ERROR
|
||||
uout.info('usage: {0} [options] <host>\n'.format(p))
|
||||
uout.info(' -h, --help print this help')
|
||||
@@ -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
|
||||
@@ -833,7 +835,7 @@ def list_policies(out: OutputBuffer, verbose: bool) -> None:
|
||||
|
||||
out.sep()
|
||||
if len(server_policy_names) == 0 and len(client_policy_names) == 0:
|
||||
out.fail("Error: no built-in policies found!")
|
||||
out.error("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")
|
||||
@@ -1049,19 +1051,19 @@ def process_commandline(out: OutputBuffer, args: List[str], usage_cb: Callable[.
|
||||
try:
|
||||
aconf.policy = Policy(policy_file=aconf.policy_file, json_output=aconf.json)
|
||||
except Exception as e:
|
||||
out.fail("Error while loading policy file: %s: %s" % (str(e), traceback.format_exc()))
|
||||
out.error("Error while loading policy file: %s: %s" % (str(e), traceback.format_exc()))
|
||||
out.write()
|
||||
sys.exit(exitcodes.UNKNOWN_ERROR)
|
||||
|
||||
# If the user wants to do a client audit, but provided a server policy, terminate.
|
||||
if aconf.client_audit and aconf.policy.is_server_policy():
|
||||
out.fail("Error: client audit selected, but server policy provided.")
|
||||
out.error("Error: client audit selected, but server policy provided.")
|
||||
out.write()
|
||||
sys.exit(exitcodes.UNKNOWN_ERROR)
|
||||
|
||||
# If the user wants to do a server audit, but provided a client policy, terminate.
|
||||
if aconf.client_audit is False and aconf.policy.is_server_policy() is False:
|
||||
out.fail("Error: server audit selected, but client policy provided.")
|
||||
out.error("Error: server audit selected, but client policy provided.")
|
||||
out.write()
|
||||
sys.exit(exitcodes.UNKNOWN_ERROR)
|
||||
|
||||
@@ -1260,7 +1262,7 @@ def audit(out: OutputBuffer, aconf: AuditConf, sshv: Optional[int] = None, print
|
||||
err = s.connect()
|
||||
|
||||
if err is not None:
|
||||
out.fail(err)
|
||||
out.error(err)
|
||||
|
||||
# If we're running against multiple targets, return a connection error to the calling worker thread. Otherwise, write the error message to the console and exit.
|
||||
if len(aconf.target_list) > 0:
|
||||
@@ -1308,7 +1310,7 @@ def audit(out: OutputBuffer, aconf: AuditConf, sshv: Optional[int] = None, print
|
||||
err = fmt.format(err_pair[0], err_pair[1], packet_type)
|
||||
if err is not None:
|
||||
output(out, aconf, banner, header)
|
||||
out.fail(err)
|
||||
out.error(err)
|
||||
return exitcodes.CONNECTION_ERROR
|
||||
if sshv == 1:
|
||||
program_retval = output(out, aconf, banner, header, pkm=SSH1_PublicKeyMessage.parse(payload))
|
||||
@@ -1316,7 +1318,7 @@ def audit(out: OutputBuffer, aconf: AuditConf, sshv: Optional[int] = None, print
|
||||
try:
|
||||
kex = SSH2_Kex.parse(out, payload)
|
||||
except Exception:
|
||||
out.fail("Failed to parse server's kex. Stack trace:\n%s" % str(traceback.format_exc()))
|
||||
out.error("Failed to parse server's kex. Stack trace:\n%s" % str(traceback.format_exc()))
|
||||
return exitcodes.CONNECTION_ERROR
|
||||
|
||||
if aconf.dheat is not None:
|
||||
|
||||
@@ -108,7 +108,8 @@ class SSH_Socket(ReadBuf, WriteBuf):
|
||||
s.listen()
|
||||
self.__sock_map[s.fileno()] = s
|
||||
except Exception as e:
|
||||
print("Warning: failed to listen on any IPv4 interfaces: %s" % str(e))
|
||||
self.__outputbuffer.error("Warning: failed to listen on any IPv4 interfaces: %s" % str(e))
|
||||
sys.exit(exitcodes.CONNECTION_ERROR)
|
||||
|
||||
try:
|
||||
# Socket to listen on all IPv6 addresses.
|
||||
@@ -119,11 +120,12 @@ class SSH_Socket(ReadBuf, WriteBuf):
|
||||
s.listen()
|
||||
self.__sock_map[s.fileno()] = s
|
||||
except Exception as e:
|
||||
print("Warning: failed to listen on any IPv6 interfaces: %s" % str(e))
|
||||
self.__outputbuffer.error("Warning: failed to listen on any IPv6 interfaces: %s" % str(e))
|
||||
sys.exit(exitcodes.CONNECTION_ERROR)
|
||||
|
||||
# If we failed to listen on any interfaces, terminate.
|
||||
if len(self.__sock_map.keys()) == 0:
|
||||
print("Error: failed to listen on any IPv4 and IPv6 interfaces!")
|
||||
self.__outputbuffer.error("Error: failed to listen on any IPv4 and IPv6 interfaces!")
|
||||
sys.exit(exitcodes.CONNECTION_ERROR)
|
||||
|
||||
# Wait for an incoming connection. If a timeout was explicitly
|
||||
@@ -141,7 +143,7 @@ class SSH_Socket(ReadBuf, WriteBuf):
|
||||
break
|
||||
|
||||
if self.__timeout_set and time_elapsed >= self.__timeout:
|
||||
print("Timeout elapsed. Terminating...")
|
||||
self.__outputbuffer.error("Timeout elapsed. Terminating...")
|
||||
sys.exit(exitcodes.CONNECTION_ERROR)
|
||||
|
||||
# Accept the connection.
|
||||
@@ -275,7 +277,7 @@ class SSH_Socket(ReadBuf, WriteBuf):
|
||||
payload_length = packet_length - padding_length - 1
|
||||
check_size = 4 + 1 + payload_length + padding_length
|
||||
if check_size % self.__block_size != 0:
|
||||
self.__outputbuffer.fail('[exception] invalid ssh packet (block size)').write()
|
||||
self.__outputbuffer.error('[exception] invalid ssh packet (block size)').write()
|
||||
sys.exit(exitcodes.CONNECTION_ERROR)
|
||||
self.ensure_read(payload_length)
|
||||
if sshv == 1:
|
||||
@@ -290,7 +292,7 @@ class SSH_Socket(ReadBuf, WriteBuf):
|
||||
if sshv == 1:
|
||||
rcrc = SSH1.crc32(padding + payload)
|
||||
if crc != rcrc:
|
||||
self.__outputbuffer.fail('[exception] packet checksum CRC32 mismatch.').write()
|
||||
self.__outputbuffer.error('[exception] packet checksum CRC32 mismatch.').write()
|
||||
sys.exit(exitcodes.CONNECTION_ERROR)
|
||||
else:
|
||||
self.ensure_read(padding_length)
|
||||
|
||||
Reference in New Issue
Block a user