mirror of
https://github.com/jtesta/ssh-audit.git
synced 2026-05-25 23:41:22 +02:00
Compare commits
9 Commits
v3.1.0
...
0d0003cca0
| Author | SHA1 | Date | |
|---|---|---|---|
| 0d0003cca0 | |||
| f326d58068 | |||
| b72f6a420f | |||
| aab105c398 | |||
| fe65b5df8a | |||
| 44393c56b3 | |||
| 164356e776 | |||
| c8e075ad13 | |||
| eebeac99a0 |
+13
-2
@@ -4,10 +4,21 @@ ifeq ($(VERSION),)
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
all:
|
all:
|
||||||
docker build -t positronsecurity/ssh-audit:${VERSION} .
|
docker buildx create --name multiarch --use || exit 0
|
||||||
|
docker buildx build \
|
||||||
|
--platform linux/amd64,linux/arm64,linux/arm/v7 \
|
||||||
|
--tag positronsecurity/ssh-audit:${VERSION} \
|
||||||
|
--tag positronsecurity/ssh-audit:latest \
|
||||||
|
.
|
||||||
|
docker buildx build \
|
||||||
|
--tag positronsecurity/ssh-audit:${VERSION} \
|
||||||
|
--tag positronsecurity/ssh-audit:latest \
|
||||||
|
--load \
|
||||||
|
--builder=multiarch \
|
||||||
|
.
|
||||||
|
|
||||||
upload:
|
upload:
|
||||||
docker login
|
docker login -u positronsecurity
|
||||||
docker buildx build \
|
docker buildx build \
|
||||||
--platform linux/amd64,linux/arm64,linux/arm/v7 \
|
--platform linux/amd64,linux/arm64,linux/arm/v7 \
|
||||||
--tag positronsecurity/ssh-audit:${VERSION} \
|
--tag positronsecurity/ssh-audit:${VERSION} \
|
||||||
|
|||||||
+8
-5
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
An executable can only be made on a Windows host because the PyInstaller tool (https://www.pyinstaller.org/) does not support cross-compilation.
|
An executable can only be made on a Windows host because the PyInstaller tool (https://www.pyinstaller.org/) does not support cross-compilation.
|
||||||
|
|
||||||
1.) Install Python v3.11.x from https://www.python.org/. To make life easier, check the option to add Python to the PATH environment variable.
|
1.) Install Python v3.x from https://www.python.org/. To make life easier, check the option to add Python to the PATH environment variable.
|
||||||
|
|
||||||
2.) Install Cygwin (https://www.cygwin.com/).
|
2.) Install Cygwin (https://www.cygwin.com/).
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ An executable can only be made on a Windows host because the PyInstaller tool (h
|
|||||||
|
|
||||||
# PyPI
|
# PyPI
|
||||||
|
|
||||||
To create package and upload to test server:
|
To create package and upload to test server (hint: use username '\_\_token\_\_' and API token for test.pypi.org):
|
||||||
|
|
||||||
```
|
```
|
||||||
$ sudo apt install python3-virtualenv python3.10-venv
|
$ sudo apt install python3-virtualenv python3.10-venv
|
||||||
@@ -31,7 +31,7 @@ To download from test server and verify:
|
|||||||
$ pip3 install --index-url https://test.pypi.org/simple ssh-audit
|
$ pip3 install --index-url https://test.pypi.org/simple ssh-audit
|
||||||
```
|
```
|
||||||
|
|
||||||
To upload to production server (hint: use username '\_\_token\_\_' and API token):
|
To upload to production server (hint: use username '\_\_token\_\_' and API token for production pypi.org):
|
||||||
|
|
||||||
```
|
```
|
||||||
$ make -f Makefile.pypi uploadprod
|
$ make -f Makefile.pypi uploadprod
|
||||||
@@ -61,19 +61,22 @@ Upload the snap with:
|
|||||||
$ snapcraft export-login ~/snap_creds.txt
|
$ snapcraft export-login ~/snap_creds.txt
|
||||||
$ export SNAPCRAFT_STORE_CREDENTIALS=$(cat ~/snap_creds.txt)
|
$ export SNAPCRAFT_STORE_CREDENTIALS=$(cat ~/snap_creds.txt)
|
||||||
$ snapcraft upload --release=beta ssh-audit_*.snap
|
$ snapcraft upload --release=beta ssh-audit_*.snap
|
||||||
$ snapcraft upload --release=stable ssh-audit_*.snap
|
$ snapcraft status ssh-audit # Note the revision number of the beta channel.
|
||||||
|
$ snapcraft release ssh-audit X stable # Fill in with the revision number.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
# Docker
|
# Docker
|
||||||
|
|
||||||
|
Ensure that the buildx plugin is available by following the installation instructions available at: https://docs.docker.com/engine/install/ubuntu/
|
||||||
|
|
||||||
Build a local image with:
|
Build a local image with:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ make -f Makefile.docker
|
$ make -f Makefile.docker
|
||||||
```
|
```
|
||||||
|
|
||||||
Create a multi-architecture build and upload it to Dockerhub with:
|
Create a multi-architecture build and upload it to Dockerhub with (hint: use the API token as the password):
|
||||||
|
|
||||||
```
|
```
|
||||||
$ make -f Makefile.docker upload
|
$ make -f Makefile.docker upload
|
||||||
|
|||||||
@@ -178,6 +178,10 @@ For convenience, a web front-end on top of the command-line tool is available at
|
|||||||
|
|
||||||
## ChangeLog
|
## ChangeLog
|
||||||
|
|
||||||
|
### v3.2.0-dev (???)
|
||||||
|
- Expanded filter of CBC ciphers to flag for the Terrapin vulnerability. It now includes more rarely found ciphers.
|
||||||
|
- Color output is disabled if the `NO_COLOR` environment variable is set (see https://no-color.org/).
|
||||||
|
|
||||||
### v3.1.0 (2023-12-20)
|
### v3.1.0 (2023-12-20)
|
||||||
- Added test for the Terrapin message prefix truncation vulnerability ([CVE-2023-48795](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-48795)).
|
- Added test for the Terrapin message prefix truncation vulnerability ([CVE-2023-48795](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-48795)).
|
||||||
- Dropped support for Python 3.7 (EOL was reached in June 2023).
|
- Dropped support for Python 3.7 (EOL was reached in June 2023).
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
"""
|
"""
|
||||||
# The version to display.
|
# The version to display.
|
||||||
VERSION = 'v3.1.0'
|
VERSION = 'v3.2.0-dev'
|
||||||
|
|
||||||
# SSH software to impersonate
|
# SSH software to impersonate
|
||||||
SSH_HEADER = 'SSH-{0}-OpenSSH_8.2'
|
SSH_HEADER = 'SSH-{0}-OpenSSH_8.2'
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ class KexDH: # pragma: nocover
|
|||||||
# contains the host key, among other things. Function returns the host
|
# contains the host key, among other things. Function returns the host
|
||||||
# key blob (from which the fingerprint can be calculated).
|
# key blob (from which the fingerprint can be calculated).
|
||||||
def recv_reply(self, s: 'SSH_Socket', parse_host_key_size: bool = True) -> Optional[bytes]:
|
def recv_reply(self, s: 'SSH_Socket', parse_host_key_size: bool = True) -> Optional[bytes]:
|
||||||
# Reset the CA info, in case it was set from a prior invokation.
|
# Reset the CA info, in case it was set from a prior invocation.
|
||||||
self.__hostkey_type = ''
|
self.__hostkey_type = ''
|
||||||
self.__hostkey_e = 0 # pylint: disable=unused-private-member
|
self.__hostkey_e = 0 # pylint: disable=unused-private-member
|
||||||
self.__hostkey_n = 0 # pylint: disable=unused-private-member
|
self.__hostkey_n = 0 # pylint: disable=unused-private-member
|
||||||
@@ -100,7 +100,7 @@ class KexDH: # pragma: nocover
|
|||||||
# A connection error occurred. We can't parse anything, so just
|
# A connection error occurred. We can't parse anything, so just
|
||||||
# return. The host key modulus (and perhaps certificate modulus)
|
# return. The host key modulus (and perhaps certificate modulus)
|
||||||
# will remain at length 0.
|
# will remain at length 0.
|
||||||
self.out.d("KexDH.recv_reply(): received packge_type == -1.")
|
self.out.d("KexDH.recv_reply(): received package_type == -1.")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Get the host key blob, F, and signature.
|
# Get the host key blob, F, and signature.
|
||||||
|
|||||||
@@ -419,11 +419,11 @@ macs = %s
|
|||||||
hostkey_types = list(self._hostkey_sizes.keys())
|
hostkey_types = list(self._hostkey_sizes.keys())
|
||||||
hostkey_types.sort() # Sorted to make testing output repeatable.
|
hostkey_types.sort() # Sorted to make testing output repeatable.
|
||||||
for hostkey_type in hostkey_types:
|
for hostkey_type in hostkey_types:
|
||||||
expected_hostkey_size = self._hostkey_sizes[hostkey_type]['hostkey_size']
|
expected_hostkey_size = cast(int, self._hostkey_sizes[hostkey_type]['hostkey_size'])
|
||||||
server_host_keys = kex.host_keys()
|
server_host_keys = kex.host_keys()
|
||||||
if hostkey_type in server_host_keys:
|
if hostkey_type in server_host_keys:
|
||||||
actual_hostkey_size = server_host_keys[hostkey_type]['hostkey_size']
|
actual_hostkey_size = cast(int, server_host_keys[hostkey_type]['hostkey_size'])
|
||||||
if actual_hostkey_size != expected_hostkey_size:
|
if actual_hostkey_size < expected_hostkey_size:
|
||||||
ret = False
|
ret = False
|
||||||
self._append_error(errors, 'Host key (%s) sizes' % hostkey_type, [str(expected_hostkey_size)], None, [str(actual_hostkey_size)])
|
self._append_error(errors, 'Host key (%s) sizes' % hostkey_type, [str(expected_hostkey_size)], None, [str(actual_hostkey_size)])
|
||||||
|
|
||||||
@@ -439,7 +439,7 @@ macs = %s
|
|||||||
ret = False
|
ret = False
|
||||||
self._append_error(errors, 'CA signature type', [expected_ca_key_type], None, [actual_ca_key_type])
|
self._append_error(errors, 'CA signature type', [expected_ca_key_type], None, [actual_ca_key_type])
|
||||||
# Ensure that the actual and expected signature sizes match.
|
# Ensure that the actual and expected signature sizes match.
|
||||||
elif actual_ca_key_size != expected_ca_key_size:
|
elif actual_ca_key_size < expected_ca_key_size:
|
||||||
ret = False
|
ret = False
|
||||||
self._append_error(errors, 'CA signature size (%s)' % actual_ca_key_type, [str(expected_ca_key_size)], None, [str(actual_ca_key_size)])
|
self._append_error(errors, 'CA signature size (%s)' % actual_ca_key_type, [str(expected_ca_key_size)], None, [str(actual_ca_key_size)])
|
||||||
|
|
||||||
@@ -462,7 +462,7 @@ macs = %s
|
|||||||
expected_dh_modulus_size = self._dh_modulus_sizes[dh_modulus_type]
|
expected_dh_modulus_size = self._dh_modulus_sizes[dh_modulus_type]
|
||||||
if dh_modulus_type in kex.dh_modulus_sizes():
|
if dh_modulus_type in kex.dh_modulus_sizes():
|
||||||
actual_dh_modulus_size = kex.dh_modulus_sizes()[dh_modulus_type]
|
actual_dh_modulus_size = kex.dh_modulus_sizes()[dh_modulus_type]
|
||||||
if expected_dh_modulus_size != actual_dh_modulus_size:
|
if expected_dh_modulus_size > actual_dh_modulus_size:
|
||||||
ret = False
|
ret = False
|
||||||
self._append_error(errors, 'Group exchange (%s) modulus sizes' % dh_modulus_type, [str(expected_dh_modulus_size)], None, [str(actual_dh_modulus_size)])
|
self._append_error(errors, 'Group exchange (%s) modulus sizes' % dh_modulus_type, [str(expected_dh_modulus_size)], None, [str(actual_dh_modulus_size)])
|
||||||
|
|
||||||
|
|||||||
@@ -106,7 +106,8 @@ def usage(uout: OutputBuffer, err: Optional[str] = None) -> None:
|
|||||||
uout.info(' --lookup=<alg1,alg2,...> looks up an algorithm(s) without\n connecting to a server')
|
uout.info(' --lookup=<alg1,alg2,...> looks up an algorithm(s) without\n connecting to a server')
|
||||||
uout.info(' -M, --make-policy=<policy.txt> creates a policy based on the target server\n (i.e.: the target server has the ideal\n configuration that other servers should\n adhere to)')
|
uout.info(' -M, --make-policy=<policy.txt> creates a policy based on the target server\n (i.e.: the target server has the ideal\n configuration that other servers should\n adhere to)')
|
||||||
uout.info(' -m, --manual print the man page (Windows only)')
|
uout.info(' -m, --manual print the man page (Windows only)')
|
||||||
uout.info(' -n, --no-colors disable colors')
|
uout.info(' -n, --no-colors disable colors (automatic when the NO_COLOR')
|
||||||
|
uout.info(' environment variable is set)')
|
||||||
uout.info(' -p, --port=<port> port to connect')
|
uout.info(' -p, --port=<port> port to connect')
|
||||||
uout.info(' -P, --policy=<policy.txt> run a policy test using the specified policy')
|
uout.info(' -P, --policy=<policy.txt> run a policy test using the specified policy')
|
||||||
uout.info(' -t, --timeout=<secs> timeout (in seconds) for connection and reading\n (default: 5)')
|
uout.info(' -t, --timeout=<secs> timeout (in seconds) for connection and reading\n (default: 5)')
|
||||||
@@ -491,7 +492,7 @@ def post_process_findings(banner: Optional[Banner], algs: Algorithms, client_aud
|
|||||||
if algs.ssh2kex is not None:
|
if algs.ssh2kex is not None:
|
||||||
ciphers_supported = algs.ssh2kex.client.encryption if client_audit else algs.ssh2kex.server.encryption
|
ciphers_supported = algs.ssh2kex.client.encryption if client_audit else algs.ssh2kex.server.encryption
|
||||||
for cipher in ciphers_supported:
|
for cipher in ciphers_supported:
|
||||||
if cipher.endswith("-cbc"):
|
if cipher.endswith("-cbc") or cipher.endswith("-cbc@openssh.org") or cipher.endswith("-cbc@ssh.com") or cipher == "rijndael-cbc@lysator.liu.se":
|
||||||
ret.append(cipher)
|
ret.append(cipher)
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
@@ -501,7 +502,7 @@ def post_process_findings(banner: Optional[Banner], algs: Algorithms, client_aud
|
|||||||
ret = []
|
ret = []
|
||||||
|
|
||||||
for cipher in db["enc"]:
|
for cipher in db["enc"]:
|
||||||
if cipher.endswith("-cbc") and cipher not in _get_cbc_ciphers_enabled(algs):
|
if (cipher.endswith("-cbc") or cipher.endswith("-cbc@openssh.org") or cipher.endswith("-cbc@ssh.com") or cipher == "rijndael-cbc@lysator.liu.se") and cipher not in _get_cbc_ciphers_enabled(algs):
|
||||||
ret.append(cipher)
|
ret.append(cipher)
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
@@ -814,6 +815,7 @@ def list_policies(out: OutputBuffer) -> None:
|
|||||||
out.fail("Error: no built-in policies found!")
|
out.fail("Error: no built-in policies found!")
|
||||||
else:
|
else:
|
||||||
out.info("\nHint: Use -P and provide the full name of a policy to run a policy scan with.\n")
|
out.info("\nHint: Use -P and provide the full name of a policy to run a policy scan with.\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.write()
|
out.write()
|
||||||
|
|
||||||
|
|
||||||
@@ -857,6 +859,11 @@ def process_commandline(out: OutputBuffer, args: List[str], usage_cb: Callable[.
|
|||||||
aconf = AuditConf()
|
aconf = AuditConf()
|
||||||
|
|
||||||
enable_colors = not any(i in args for i in ['--no-colors', '-n'])
|
enable_colors = not any(i in args for i in ['--no-colors', '-n'])
|
||||||
|
|
||||||
|
# Disable colors if the NO_COLOR environment variable is set.
|
||||||
|
if "NO_COLOR" in os.environ:
|
||||||
|
enable_colors = False
|
||||||
|
|
||||||
aconf.colors = enable_colors
|
aconf.colors = enable_colors
|
||||||
out.use_colors = enable_colors
|
out.use_colors = enable_colors
|
||||||
|
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ class VersionVulnerabilityDB: # pylint: disable=too-few-public-methods
|
|||||||
['2.1', '4.1p1', 1, 'CVE-2005-2798', 5.0, 'leak data about authentication credentials'],
|
['2.1', '4.1p1', 1, 'CVE-2005-2798', 5.0, 'leak data about authentication credentials'],
|
||||||
['3.5', '3.5p1', 1, 'CVE-2004-2760', 6.8, 'leak data through different connection states'],
|
['3.5', '3.5p1', 1, 'CVE-2004-2760', 6.8, 'leak data through different connection states'],
|
||||||
['2.3', '3.7.1p2', 1, 'CVE-2004-2069', 5.0, 'cause DoS via large number of connections (slot exhaustion)'],
|
['2.3', '3.7.1p2', 1, 'CVE-2004-2069', 5.0, 'cause DoS via large number of connections (slot exhaustion)'],
|
||||||
['3.0', '3.4p1', 1, 'CVE-2004-0175', 4.3, 'leak data through directoy traversal'],
|
['3.0', '3.4p1', 1, 'CVE-2004-0175', 4.3, 'leak data through directory traversal'],
|
||||||
['1.2', '3.9p1', 1, 'CVE-2003-1562', 7.6, 'leak data about authentication credentials'],
|
['1.2', '3.9p1', 1, 'CVE-2003-1562', 7.6, 'leak data about authentication credentials'],
|
||||||
['3.1p1', '3.7.1p1', 1, 'CVE-2003-0787', 7.5, 'privilege escalation via modifying stack'],
|
['3.1p1', '3.7.1p1', 1, 'CVE-2003-0787', 7.5, 'privilege escalation via modifying stack'],
|
||||||
['3.1p1', '3.7.1p1', 1, 'CVE-2003-0786', 10.0, 'privilege escalation via bypassing authentication'],
|
['3.1p1', '3.7.1p1', 1, 'CVE-2003-0786', 10.0, 'privilege escalation via bypassing authentication'],
|
||||||
|
|||||||
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
.TH SSH-AUDIT 1 "March 13, 2022"
|
.TH SSH-AUDIT 1 "January 28, 2024"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
\fBssh-audit\fP \- SSH server & client configuration auditor
|
\fBssh-audit\fP \- SSH server & client configuration auditor
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
@@ -114,7 +114,7 @@ Creates a policy based on the target server. Useful when other servers should b
|
|||||||
.TP
|
.TP
|
||||||
.B -n, \-\-no-colors
|
.B -n, \-\-no-colors
|
||||||
.br
|
.br
|
||||||
Disable color output.
|
Disable color output. Automatically set when the NO_COLOR environment variable is set.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B -p, \-\-port=<port>
|
.B -p, \-\-port=<port>
|
||||||
|
|||||||
Reference in New Issue
Block a user