1
0
mirror of https://github.com/jtesta/ssh-audit.git synced 2025-07-06 14:02:49 -05:00

84 Commits

Author SHA1 Message Date
68cf05d0ff Set version to 3.2.0 for release. 2024-04-22 16:32:57 -04:00
2d9ddabcad Updated DHEat rate connection warning message. 2024-04-22 16:26:03 -04:00
986f83653d Added multi-line real-time output for connection rate testing. 2024-04-22 13:56:50 -04:00
3c459f1428 Revised connection rate warning during standard audits. 2024-04-22 11:58:52 -04:00
46b89fff2e Sockets now time out after 30 seconds during connection rate testing. 2024-04-21 17:05:57 -04:00
81718d1948 Fixed non-interactive connection rate tests. Revised warning for lack of connection throttling. 2024-04-21 15:08:38 -04:00
8124c8e443 Added aes128-ocb@libassh.org cipher. 2024-04-18 21:09:02 -04:00
b9f569fdf8 Added warnings for Windows platform. 2024-04-18 20:51:14 -04:00
9126ae7d9c Improved DHEat statistics output. 2024-04-18 20:01:28 -04:00
d2f1a295a1 Removed vulture from Tox (it rarely made any findings, and when it did, pylint reported the same issues). 2024-04-18 19:36:13 -04:00
8190fe59d0 Added implementation for DHEat denial-of-service attack (CVE-2002-20001). (#211, #217) 2024-04-18 13:58:13 -04:00
d7f8bf3e6d Updated notes on OpenSSH default key exchanges. (#258) 2024-03-19 18:24:22 -04:00
3d403b1d70 Updated availability of algorithms in Dropbear. (#257) 2024-03-19 15:47:09 -04:00
9fae870260 Added allow_larger_keys flag to custom policies to control whether targets can have larger keys, and added Docker tests to complete work started in PR #242. 2024-03-19 14:45:19 -04:00
20873db596 use less-than instead of not-equal when comparing key sizes (#242)
When evaluating policy compliance, use less-than operator so keys bigger
than expected (and hence very often better) don't fail policy
evaulation. This change reduces the amount of false-positives and allows
for more flexibility when hardening SSH installations.

Signed-off-by: szubersk <szuberskidamian@gmail.com>
2024-03-19 14:38:27 -04:00
3c31934ac7 Added tests and other cleanups resulting from merging PR #252. 2024-03-18 17:48:50 -04:00
5bd925ffc6 [WIP] Adding allowed algorithms (#252)
* Added allowed policy fields

Added allowed fields for host keys kex ciphers and macs

* Adapted policy.py to newest dev version

* Added allow_algorithm_subset_and_reordering flag

* Removed allowed policy entries as they are redundant now

* Fixed call to append_error
2024-03-18 17:41:17 -04:00
7b3402b207 Added note that sntrup761x25519-sha512@openssh.com is the default OpenSSH kex since version 9.0. 2024-03-15 17:24:21 -04:00
b2f46eb71a Added extra GSS wildcard matching test. 2024-03-15 17:05:40 -04:00
ab41ca1023 Re-organized README. 2024-03-15 16:28:10 -04:00
b70fb0bc4c Added built-in policies for Amazon Linux 2023, Debian 12, and Rocky Linux 9. 2024-03-15 16:24:36 -04:00
db5104ecb8 Built-in policy change logs no longer printed within quotes. 2024-03-14 18:13:53 -04:00
15078aaea9 Built-in policies now include a change log. 2024-03-14 17:58:16 -04:00
f0874af4cd Split built-in policies from policy.py to builtin_policies.py. 2024-03-14 17:24:40 -04:00
064b55e0c2 Added 1 new key exchange algorithm: gss-nistp384-sha384-* 2024-03-14 16:01:48 -04:00
a4f508374a Updated README. 2024-03-12 21:13:10 -04:00
6f39407a8c use alpine, reduce layers (#249)
Signed-off-by: Daniel Thamdrup <dallemon@protonmail.com>
2024-03-12 21:02:26 -04:00
cb0f6b63d7 Fixed new pylint warnings. 2024-03-12 20:46:39 -04:00
3313046714 Added built-in policy for OpenSSH 9.7. 2024-03-12 20:23:55 -04:00
8ee0deade1 Properly upgrade packages and clean up apt cache in Dockerfile (#218)
Result:
```
REPOSITORY     TAG       IMAGE ID       CREATED              SIZE
ssh-audit      after     03e247aee0cc   About a minute ago   131MB
ssh-audit      before    609962ceafb1   About a minute ago   150MB
```
2024-02-18 10:25:14 -05:00
699739d42a Gracefully handle rare exceptions (i.e.: crashes) while performing GEX tests. 2024-02-17 13:44:06 -05:00
a958fd1fec Snap builds are now architecture-independent. (#232) 2024-02-17 12:54:28 -05:00
c33f419224 Updated '-m', '--manual' description in README. 2024-02-16 23:16:07 -05:00
6ee4899b4f Bumped copyright year. 2024-02-16 23:13:55 -05:00
20fbb706b0 The built-in man page (, ) is now available on Docker, PyPI, and Snap builds, in addition to the Windows build. (#231) 2024-02-16 22:40:53 -05:00
73b669b49d Fixed parsing of ecdsa-sha2-nistp* CA signatures on host keys. Additionally, they are now flagged as potentially back-doored, just as standard host keys are. (#239) 2024-02-16 21:58:51 -05:00
f326d58068 Disable color when the NO_COLOR environment variable is set. (#234) 2024-01-28 18:17:49 -05:00
b72f6a420f Added note regarding general OpenSSH policies failing against platforms with back-ported features. (#236) 2024-01-28 17:37:21 -05:00
fe65b5df8a Added missing dev tag to Change Log: v3.2.0 -> v3.2.0-dev 2023-12-21 15:34:38 -05:00
44393c56b3 Expanded filter of CBC ciphers to flag for the Terrapin vulnerability. 2023-12-21 15:30:43 -05:00
164356e776 Spelling fixes (#233) 2023-12-21 08:58:12 -05:00
c8e075ad13 Bumped version number to v3.2.0-dev. 2023-12-20 15:41:03 -05:00
eebeac99a0 Updated packaging instructions and Docker build steps. 2023-12-20 15:40:01 -05:00
dd91c2a41a Bumped version to 3.1.0 in preparation for stable release. Updated Change Log in README. 2023-12-20 13:12:13 -05:00
bef8c6c0f7 Updated notes on fixing Terrapin vulnerability. 2023-12-20 12:11:55 -05:00
75dbc03a77 Added 'additional_notes' field to JSON output. 2023-12-19 18:03:07 -05:00
c9412cbb88 Added built-in policies for OpenSSH 9.5 and 9.6. 2023-12-19 17:42:43 -05:00
a0f99942a2 Don't recommend enabling the chacha & CBC ciphers, nor ETM MACs in case the user disabled them to address the Terrapin vulnerability. (#229) 2023-12-19 17:16:58 -05:00
c259a83782 Added note that when a target is properly configured against the Terrapin vulnerability that unpatched peers may still create vulnerable connections. Updated Ubuntu Server & Client 20.04 & 22.04 policies to include new key exchange markers related to Terrapin counter-measures. 2023-12-19 14:03:28 -05:00
8e972c5e94 Added test for the Terrapin vulnerability (CVE-2023-48795) (#227). 2023-12-18 18:24:49 -05:00
46eb970376 Removed Python 3.7 from Github Actions testing. 2023-11-27 23:39:48 -05:00
965bcb6b18 Dropped support for Python 3.7. 2023-11-27 23:35:40 -05:00
ba8e8a7e68 Re-organized option host key types for OpenSSH 9.2 to correspond with updated Debian 12 hardening guide. 2023-11-27 21:33:13 -05:00
bad2c9cd8e In Ubuntu 22.04 client policy, moved host key types and to the end of all certificate types. 2023-11-27 20:07:36 -05:00
69e1e121fd In server policies, reduced expected DH modulus sizes from 4096 to 3072 to match online hardening guides. 2023-11-27 19:15:18 -05:00
848052df68 Add cleanup for apt cache files (#215)
* Add cleanup for apt cache files

Adding this command decreases the size of the image.

ssh-audit-new                                           latest                 0c391ba567ee   39 minutes ago   157MB
ssh-audit-old                                           latest                 a425e0043125   40 minutes ago   176MB

* Fix Dockerfile

Forgot to add logical "and" (&&)
2023-10-23 13:51:47 -04:00
d62e4cd80c Added Python 3.12 to Tox tests. 2023-10-22 16:43:04 -04:00
2809ff464a Added --rm to docker run commands so stopped containers are automatically removed. 2023-09-12 08:38:07 -04:00
02ab487232 Bumped version to v3.1.0-dev. 2023-09-07 08:57:59 -04:00
d62acd688e Updated Docker Makefile and packaging instructions. 2023-09-07 08:57:39 -04:00
f517e03d9f Bumped version to v3.0.0. 2023-09-07 07:45:07 -04:00
6c64257d91 Updated README. 2023-09-06 22:37:06 -04:00
982c0b4c72 Docker: Build multi-arch container images for amd64, arm64 and arm/v7 (#194)
* builds multi-arch container images for linux/{amd64,arm64,arm/v7}

* adds local-build build target for easier local testing
2023-09-06 22:32:18 -04:00
e26597a7aa Marked all NIST K-, B-, and T-curves as unproven since they are so rarely used. Added 12 new host keys: 'ecdsa-sha2-curve25519', 'ecdsa-sha2-nistb233', 'ecdsa-sha2-nistb409', 'ecdsa-sha2-nistk163', 'ecdsa-sha2-nistk233', 'ecdsa-sha2-nistk283', 'ecdsa-sha2-nistk409', 'ecdsa-sha2-nistp224', 'ecdsa-sha2-nistp192', 'ecdsa-sha2-nistt571', 'ssh-dsa', 'x509v3-sign-rsa-sha256'. Added 15 key exchanges: 'curve448-sha512@libssh.org', 'ecdh-nistp256-kyber-512r3-sha256-d00@openquantumsafe.org', 'ecdh-nistp384-kyber-768r3-sha384-d00@openquantumsafe.org', 'ecdh-nistp521-kyber-1024r3-sha512-d00@openquantumsafe.org', 'ecdh-sha2-brainpoolp256r1@genua.de', 'ecdh-sha2-brainpoolp384r1@genua.de', 'ecdh-sha2-brainpoolp521r1@genua.de', 'kexAlgoDH14SHA1', 'kexAlgoDH1SHA1', 'kexAlgoECDH256', 'kexAlgoECDH384', 'kexAlgoECDH521', 'sm2kep-sha2-nistp256', 'x25519-kyber-512r3-sha256-d00@amazon.com', 'x25519-kyber512-sha512@aws.amazon.com'. Added 8 new ciphers: 'aes192-gcm@openssh.com', 'cast128-12-cbc', 'cast128-12-cfb', 'cast128-12-ecb', 'cast128-12-ofb', 'des-cfb', 'des-ecb', 'des-ofb'. Added 14 new MACs: 'cbcmac-3des', 'cbcmac-aes', 'cbcmac-blowfish', 'cbcmac-des', 'cbcmac-rijndael', 'cbcmac-twofish', 'hmac-sha256-96', 'md5', 'md5-8', 'ripemd160', 'ripemd160-8', 'sha1', 'sha1-8', 'umac-128'. 2023-09-05 20:10:37 -04:00
f8e29674a3 Refined JSON notes output. Fixed Docker & Tox tests. 2023-09-05 16:36:54 -04:00
d3dd5a9cac Improved JSON output (#185) 2023-09-05 16:16:23 -04:00
79ca4b2d8b Updated README. 2023-09-05 14:22:35 -04:00
884ef645f8 Prioritized certificate host key types for Ubuntu 22.04 client policy. (#193) 2023-09-05 14:01:51 -04:00
953683a762 Fixed most warnings from Shellcheck scans. (#197) 2023-09-05 13:14:21 -04:00
38f9c21760 The color of all notes will be printed in green when the related algorithm is rated good. 2023-09-03 19:14:25 -04:00
4e6169d0cb Added built-in policy for OpenSSH 9.4. 2023-09-03 18:12:16 -04:00
2867c65819 Perform full Docker image update when building. 2023-09-03 18:07:30 -04:00
77cdb969b9 Fixed flake8 tests. 2023-09-03 16:25:26 -04:00
199e75f6cd Refined GEX testing against OpenSSH servers: when the fallback mechanism is suspected of being triggered, perform an additional test to obtain more accurate results. 2023-09-03 16:13:00 -04:00
3f2fdbaa3d Fixed crash during GEX tests. 2023-07-11 11:08:42 -04:00
83e90729e2 Updated README. 2023-06-20 16:12:30 -04:00
83f9e48271 Recommendation output now respects level (#196) 2023-06-20 16:09:37 -04:00
e2fc60cbb4 Updated README and test for resolve function. 2023-06-20 09:26:43 -04:00
a74c3abdde Removed sys.exit from _resolve in ssh_socket.py (#187)
Changed (and documented) _resolve so the application does not quit when a hostname cannot be resolved.

Adapted connect function to expect incoming exceptions from _resolve

This fixes issue #186
2023-06-20 09:21:06 -04:00
e99cb0b579 Now prints the reason why socket listening operations fail. 2023-06-20 08:43:11 -04:00
639f11a5e5 Results from concurrent scans against multiple hosts are no longer improperly combined (#190). 2023-06-19 14:13:32 -04:00
521a50a796 Added 'curve448-sha512@libssh.org' kex. (#195) 2023-06-19 10:35:13 -04:00
2d5a97841f Bumped version to 3.0.0-dev. 2023-04-29 14:46:07 -04:00
54b8c7da02 Updated PyPI and Snap build processes. 2023-04-29 14:42:54 -04:00
71 changed files with 5575 additions and 922 deletions

View File

@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
python-version: [3.7, 3.8, 3.9, "3.10", 3.11] python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@ -18,7 +18,7 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip
pip install -U codecov coveralls flake8 mypy pylint pytest tox vulture pip install -U codecov coveralls flake8 mypy pylint pytest tox
- name: Run Tox - name: Run Tox
run: | run: |
tox tox

View File

@ -1,13 +1,19 @@
FROM python:3-slim # syntax=docker/dockerfile:latest
FROM scratch AS files
WORKDIR / # Copy ssh-audit code to temporary container
COPY ssh-audit.py /
COPY src/ /
FROM python:3-alpine AS runtime
# Update the image to remediate any vulnerabilities.
RUN apk upgrade -U --no-cache -a -l && \
# Remove suid & sgid bits from all files. # Remove suid & sgid bits from all files.
RUN find / -xdev -perm /6000 -exec chmod ug-s {} \; 2> /dev/null || true find / -xdev -perm /6000 -exec chmod ug-s {} \; 2> /dev/null || true
# Copy the ssh-audit code. # Copy the ssh-audit code from files container.
COPY ssh-audit.py . COPY --from=files / /
COPY src/ .
# Allow listening on 2222/tcp for client auditing. # Allow listening on 2222/tcp for client auditing.
EXPOSE 2222 EXPOSE 2222

View File

@ -1,6 +1,6 @@
The MIT License (MIT) 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) Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)

View File

@ -4,10 +4,25 @@ ifeq ($(VERSION),)
endif endif
all: all:
docker build -t positronsecurity/ssh-audit:${VERSION} . ./add_builtin_man_page.sh
docker tag positronsecurity/ssh-audit:${VERSION} positronsecurity/ssh-audit:latest 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 push positronsecurity/ssh-audit:${VERSION} docker buildx build \
docker push positronsecurity/ssh-audit:latest --platform linux/amd64,linux/arm64,linux/arm/v7 \
--tag positronsecurity/ssh-audit:${VERSION} \
--tag positronsecurity/ssh-audit:latest \
--push \
.

View File

@ -1,12 +1,13 @@
all: all:
./add_builtin_man_page.sh
rm -rf /tmp/pypi_upload rm -rf /tmp/pypi_upload
virtualenv -p /usr/bin/python3 /tmp/pypi_upload/ virtualenv -p /usr/bin/python3 /tmp/pypi_upload/
cp -R src /tmp/pypi_upload/ cp -R src /tmp/pypi_upload/
cp setup.py setup.cfg README.md LICENSE /tmp/pypi_upload/ cp setup.py setup.cfg README.md LICENSE /tmp/pypi_upload/
/bin/bash -c "pushd /tmp/pypi_upload/; source bin/activate; pip3 install setuptools twine; python3 setup.py sdist bdist_wheel" /bin/bash -c "pushd /tmp/pypi_upload/; source bin/activate; pip3 install -U setuptools twine build; pip3 install -U requests_toolbelt; python3 -m build"
uploadtest: uploadtest:
/bin/bash -c "pushd /tmp/pypi_upload; source bin/activate; twine upload --repository-url https://test.pypi.org/legacy/ /tmp/pypi_upload/dist/*" /bin/bash -c "pushd /tmp/pypi_upload; source bin/activate; python3 -m twine upload --repository testpypi /tmp/pypi_upload/dist/*"
uploadprod: uploadprod:
/bin/bash -c "pushd /tmp/pypi_upload; source bin/activate; twine upload /tmp/pypi_upload/dist/*" /bin/bash -c "pushd /tmp/pypi_upload; source bin/activate; twine upload /tmp/pypi_upload/dist/*"

View File

@ -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,10 +15,10 @@ 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 $ sudo apt install python3-virtualenv python3.10-venv
$ make -f Makefile.pypi $ make -f Makefile.pypi
$ make -f Makefile.pypi uploadtest $ make -f Makefile.pypi uploadtest
``` ```
@ -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
@ -58,20 +58,25 @@ Create the snap package with:
Upload the snap with: Upload the snap with:
``` ```
$ snapcraft login $ snapcraft export-login ~/snap_creds.txt
$ snapcraft upload --release=stable ssh-audit_*.snap $ export SNAPCRAFT_STORE_CREDENTIALS=$(cat ~/snap_creds.txt)
$ snapcraft upload --release=beta 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
Build image with: 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:
``` ```
$ make -f Makefile.docker $ make -f Makefile.docker
``` ```
Then 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

View File

@ -32,7 +32,7 @@
- historical information from OpenSSH, Dropbear SSH and libssh; - historical information from OpenSSH, Dropbear SSH and libssh;
- policy scans to ensure adherence to a hardened/standard configuration; - policy scans to ensure adherence to a hardened/standard configuration;
- runs on Linux and Windows; - runs on Linux and Windows;
- supports Python 3.7 - 3.11; - supports Python 3.8 - 3.12;
- no dependencies - no dependencies
## Usage ## Usage
@ -48,16 +48,33 @@ usage: ssh-audit.py [options] <host>
-c, --client-audit starts a server on port 2222 to audit client -c, --client-audit starts a server on port 2222 to audit client
software config (use -p to change port; software config (use -p to change port;
use -t to change timeout) use -t to change timeout)
--conn-rate-test=N[:max_rate] perform a connection rate test (useful
for collecting metrics related to
susceptibility of the DHEat vuln).
Testing is conducted with N concurrent
sockets with an optional maximum rate
of connections per second.
-d, --debug Enable debug output. -d, --debug Enable debug output.
--dheat=N[:kex[:e_len]] continuously perform the DHEat DoS attack
(CVE-2002-20001) against the target using N
concurrent sockets. Optionally, a specific
key exchange algorithm can be specified
instead of allowing it to be automatically
chosen. Additionally, a small length of
the fake e value sent to the server can
be chosen for a more efficient attack (such
as 4).
-g, --gex-test=<x[,y,...]> dh gex modulus size test -g, --gex-test=<x[,y,...]> dh gex modulus size test
<min1:pref1:max1[,min2:pref2:max2,...]> <min1:pref1:max1[,min2:pref2:max2,...]>
<x-y[:step]> <x-y[:step]>
-j, --json JSON output (use -jj to enable indents) -j, --json JSON output (use -jj to enable indents)
-l, --level=<level> minimum output level (info|warn|fail) -l, --level=<level> minimum output level (info|warn|fail)
-L, --list-policies list all the official, built-in policies -L, --list-policies list all the official, built-in policies. Use with -v
to view policy change logs.
--lookup=<alg1,alg2,...> looks up an algorithm(s) without --lookup=<alg1,alg2,...> looks up an algorithm(s) without
connecting to a server connecting to a server
-m, --manual print the man page (Windows only) -m, --manual print the man page (Docker, PyPI, Snap, and Windows
builds only)
-M, --make-policy=<policy.txt> creates a policy based on the target server -M, --make-policy=<policy.txt> creates a policy based on the target server
(i.e.: the target server has the ideal (i.e.: the target server has the ideal
configuration that other servers should configuration that other servers should
@ -66,6 +83,9 @@ usage: ssh-audit.py [options] <host>
-p, --port=<port> port to connect -p, --port=<port> port to connect
-P, --policy=<"policy name" | policy.txt> run a policy test using the -P, --policy=<"policy name" | policy.txt> run a policy test using the
specified policy specified policy
--skip-rate-test skip the connection rate test during standard audits
(used to safely infer whether the DHEat attack
is viable)
-t, --timeout=<secs> timeout (in seconds) for connection and reading -t, --timeout=<secs> timeout (in seconds) for connection and reading
(default: 5) (default: 5)
-T, --targets=<hosts.txt> a file containing a list of target hosts (one -T, --targets=<hosts.txt> a file containing a list of target hosts (one
@ -130,6 +150,21 @@ To create a policy based on a target server (which can be manually edited):
ssh-audit -M new_policy.txt targetserver ssh-audit -M new_policy.txt targetserver
``` ```
To run the DHEat CPU exhaustion DoS attack ([CVE-2002-20001](https://nvd.nist.gov/vuln/detail/CVE-2002-20001)) against a target using 10 concurrent sockets:
```
ssh-audit --dheat=10 targetserver
```
To run the DHEat attack using the `diffie-hellman-group-exchange-sha256` key exchange algorithm:
```
ssh-audit --dheat=10:diffie-hellman-group-exchange-sha256 targetserver
```
To run the DHEat attack using the `diffie-hellman-group-exchange-sha256` key exchange algorithm along with very small but non-standard packet lengths (this may result in the same CPU exhaustion, but with many less bytes per second being sent):
```
ssh-audit --dheat=10:diffie-hellman-group-exchange-sha256:4 targetserver
```
## Screenshots ## Screenshots
### Server Standard Audit Example ### Server Standard Audit Example
@ -151,7 +186,7 @@ Below is a screen shot of the client-auditing output when an unhardened OpenSSH
Guides to harden server & client configuration can be found here: [https://www.ssh-audit.com/hardening_guides.html](https://www.ssh-audit.com/hardening_guides.html) Guides to harden server & client configuration can be found here: [https://www.ssh-audit.com/hardening_guides.html](https://www.ssh-audit.com/hardening_guides.html)
## Pre-Built Packages ## Pre-Built Packages
Pre-built packages are available for Windows (see the releases page), PyPI, Snap, and Docker: Pre-built packages are available for Windows (see the [Releases](https://github.com/jtesta/ssh-audit/releases) page), PyPI, Snap, and Docker:
To install from PyPI: To install from PyPI:
``` ```
@ -178,6 +213,50 @@ For convenience, a web front-end on top of the command-line tool is available at
## ChangeLog ## ChangeLog
### 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.
- Fixed parsing of `ecdsa-sha2-nistp*` CA signatures on host keys. Additionally, they are now flagged as potentially back-doored, just as standard host keys are.
- Gracefully handle rare exceptions (i.e.: crashes) while performing GEX tests.
- The built-in man page (`-m`, `--manual`) is now available on Docker, PyPI, and Snap builds, in addition to the Windows build.
- Snap builds are now architecture-independent.
- Changed Docker base image from `python:3-slim` to `python:3-alpine`, resulting in a 59% reduction in image size; credit [Daniel Thamdrup](https://github.com/dallemon).
- Added built-in policies for Amazon Linux 2023, Debian 12, OpenSSH 9.7, and Rocky Linux 9.
- Built-in policies now include a change log (use `-L -v` to view them).
- Custom policies now support the `allow_algorithm_subset_and_reordering` directive to allow targets to pass with a subset and/or re-ordered list of host keys, kex, ciphers, and MACs. This allows for the creation of a baseline policy where targets can optionally implement stricter controls; partial credit [yannik1015](https://github.com/yannik1015).
- Custom policies now support the `allow_larger_keys` directive to allow targets to pass with larger host keys, CA keys, and Diffie-Hellman keys. This allows for the creation of a baseline policy where targets can optionally implement stricter controls; partial credit [Damian Szuberski](https://github.com/szubersk).
- Color output is disabled if the `NO_COLOR` environment variable is set (see https://no-color.org/).
- Added 1 new key exchange algorithm: `gss-nistp384-sha384-*`.
- Added 1 new cipher: `aes128-ocb@libassh.org`.
### 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)).
- Dropped support for Python 3.7 (EOL was reached in June 2023).
- Added Python 3.12 support.
- In server policies, reduced expected DH modulus sizes from 4096 to 3072 to match the [online hardening guides](https://ssh-audit.com/hardening_guides.html) (note that 3072-bit moduli provide the equivalent of 128-bit symmetric security).
- In Ubuntu 22.04 client policy, moved host key types `sk-ssh-ed25519@openssh.com` and `ssh-ed25519` to the end of all certificate types.
- Updated Ubuntu Server & Client policies for 20.04 and 22.04 to account for key exchange list changes due to Terrapin vulnerability patches.
- Re-organized option host key types for OpenSSH 9.2 server policy to correspond with updated Debian 12 hardening guide.
- Added built-in policies for OpenSSH 9.5 and 9.6.
- Added an `additional_notes` field to the JSON output.
### v3.0.0 (2023-09-07)
- Results from concurrent scans against multiple hosts are no longer improperly combined; bug discovered by [Adam Russell](https://github.com/thecliguy).
- Hostname resolution failure no longer causes scans against multiple hosts to terminate unexpectedly; credit [Dani Cuesta](https://github.com/daniel-cues).
- Algorithm recommendations resulting from warnings are now printed in yellow instead of red; credit [Adam Russell](https://github.com/thecliguy).
- Added failure, warning, and info notes to JSON output (note that this results in a breaking change to the banner protocol, "enc", and "mac" fields); credit [Bareq Al-Azzawi](https://github.com/BareqAZ).
- Docker Makefile now creates multi-arch builds for amd64, arm64, and armv7; credit [Sebastian Cohnen](https://github.com/tisba).
- Fixed crash during GEX tests.
- Refined GEX testing against OpenSSH servers: when the fallback mechanism is suspected of being triggered, perform an additional test to obtain more accurate results.
- The color of all notes will be printed in green when the related algorithm is rated good.
- Prioritized host key certificate algorithms for Ubuntu 22.04 LTS client policy.
- Marked all NIST K-, B-, and T-curves as unproven since they are so rarely used.
- Added built-in policy for OpenSSH 9.4.
- Added 12 new host keys: `ecdsa-sha2-curve25519`, `ecdsa-sha2-nistb233`, `ecdsa-sha2-nistb409`, `ecdsa-sha2-nistk163`, `ecdsa-sha2-nistk233`, `ecdsa-sha2-nistk283`, `ecdsa-sha2-nistk409`, `ecdsa-sha2-nistp224`, `ecdsa-sha2-nistp192`, `ecdsa-sha2-nistt571`, `ssh-dsa`, `x509v3-sign-rsa-sha256`.
- Added 15 new key exchanges: `curve448-sha512@libssh.org`, `ecdh-nistp256-kyber-512r3-sha256-d00@openquantumsafe.org`, `ecdh-nistp384-kyber-768r3-sha384-d00@openquantumsafe.org`, `ecdh-nistp521-kyber-1024r3-sha512-d00@openquantumsafe.org`, `ecdh-sha2-brainpoolp256r1@genua.de`, `ecdh-sha2-brainpoolp384r1@genua.de`, `ecdh-sha2-brainpoolp521r1@genua.de`, `kexAlgoDH14SHA1`, `kexAlgoDH1SHA1`, `kexAlgoECDH256`, `kexAlgoECDH384`, `kexAlgoECDH521`, `sm2kep-sha2-nistp256`, `x25519-kyber-512r3-sha256-d00@amazon.com`, `x25519-kyber512-sha512@aws.amazon.com`.
- Added 8 new ciphers: `aes192-gcm@openssh.com`, `cast128-12-cbc`, `cast128-12-cfb`, `cast128-12-ecb`, `cast128-12-ofb`, `des-cfb`, `des-ecb`, `des-ofb`.
- Added 14 new MACs: `cbcmac-3des`, `cbcmac-aes`, `cbcmac-blowfish`, `cbcmac-des`, `cbcmac-rijndael`, `cbcmac-twofish`, `hmac-sha256-96`, `md5`, `md5-8`, `ripemd160`, `ripemd160-8`, `sha1`, `sha1-8`, `umac-128`.
### v2.9.0 (2023-04-29) ### v2.9.0 (2023-04-29)
- Dropped support for Python 3.6, as it reached EOL at the end of 2021. - Dropped support for Python 3.6, as it reached EOL at the end of 2021.
- Added Ubuntu Server & Client 22.04 hardening policies. - Added Ubuntu Server & Client 22.04 hardening policies.

View File

@ -3,7 +3,7 @@
# #
# The MIT License (MIT) # The MIT License (MIT)
# #
# Copyright (C) 2021 Joe Testa (jtesta@positronsecurity.com) # Copyright (C) 2021-2024 Joe Testa (jtesta@positronsecurity.com)
# Copyright (C) 2021 Adam Russell (<adam[at]thecliguy[dot]co[dot]uk>) # Copyright (C) 2021 Adam Russell (<adam[at]thecliguy[dot]co[dot]uk>)
# #
# Permission is hereby granted, free of charge, to any person obtaining a copy # Permission is hereby granted, free of charge, to any person obtaining a copy
@ -26,22 +26,21 @@
# #
################################################################################ ################################################################################
# update_windows_man_page.sh # add_builtin_man_page.sh
# #
# PURPOSE # PURPOSE
# Since Windows lacks a manual reader it's necessary to provide an alternative # Since some platforms lack a manual reader it's necessary to provide an
# means of reading the man page. # alternative means of reading the man page.
# #
# This script should be run as part of the ssh-audit packaging process for # This script should be run as part of the ssh-audit packaging process for
# Windows. It populates the 'WINDOWS_MAN_PAGE' variable in 'globals.py' with # Docker, PyPI, Snap, and Windows. It populates the 'BUILTIN_MAN_PAGE'
# the contents of the man page. Windows users can then print the content of # variable in 'globals.py' with the contents of the man page. Users can then
# 'WINDOWS_MAN_PAGE' by invoking ssh-audit with the manual parameters # see the man page with "ssh-audit [--manual|-m]".
# (--manual / -m).
# #
# Cygwin is required. # Linux or Cygwin is required to run this script.
# #
# USAGE # USAGE
# update_windows_man_page.sh [-m <path-to-man-page>] [-g <path-to-globals.py>] # add_builtin_man_page.sh [-m <path-to-man-page>] [-g <path-to-globals.py>]
# #
################################################################################ ################################################################################
@ -55,10 +54,10 @@ usage() {
PLATFORM="$(uname -s)" PLATFORM="$(uname -s)"
# This script is intended for use on Linux and Cygwin only. # This script is intended for use on Linux and Cygwin only.
case "$PLATFORM" in case "${PLATFORM}" in
Linux | CYGWIN*) ;; Linux | CYGWIN*) ;;
*) *)
echo "Platform not supported: $PLATFORM" echo "Platform not supported: ${PLATFORM}"
exit 1 exit 1
;; ;;
esac esac
@ -67,12 +66,12 @@ MAN_PAGE=./ssh-audit.1
GLOBALS_PY=./src/ssh_audit/globals.py GLOBALS_PY=./src/ssh_audit/globals.py
while getopts "m: g: h" OPTION; do while getopts "m: g: h" OPTION; do
case "$OPTION" in case "${OPTION}" in
m) m)
MAN_PAGE="$OPTARG" MAN_PAGE="${OPTARG}"
;; ;;
g) g)
GLOBALS_PY="$OPTARG" GLOBALS_PY="${OPTARG}"
;; ;;
h) h)
usage usage
@ -88,10 +87,10 @@ done
# Check that the specified files exist. # Check that the specified files exist.
[[ -f "$MAN_PAGE" ]] || { echo >&2 "man page file not found: $MAN_PAGE"; exit 1; } [[ -f "$MAN_PAGE" ]] || { echo >&2 "man page file not found: $MAN_PAGE"; exit 1; }
[[ -f "$GLOBALS_PY" ]] || { echo >&2 "globals.py file not found: $GLOBALS_PY"; exit 1; } [[ -f "${GLOBALS_PY}" ]] || { echo >&2 "globals.py file not found: ${GLOBALS_PY}"; exit 1; }
# Check that the 'ul' (do underlining) binary exists. # Check that the 'ul' (do underlining) binary exists.
if [[ "$PLATFORM" == "Linux" ]]; then if [[ "${PLATFORM}" == "Linux" ]]; then
command -v ul >/dev/null 2>&1 || { echo >&2 "ul not found."; exit 1; } command -v ul >/dev/null 2>&1 || { echo >&2 "ul not found."; exit 1; }
fi fi
@ -99,10 +98,10 @@ fi
command -v sed >/dev/null 2>&1 || { echo >&2 "sed not found."; exit 1; } command -v sed >/dev/null 2>&1 || { echo >&2 "sed not found."; exit 1; }
# Reset the globals.py file, in case it was modified from a prior run. # Reset the globals.py file, in case it was modified from a prior run.
git checkout $GLOBALS_PY > /dev/null 2>&1 git checkout "${GLOBALS_PY}" > /dev/null 2>&1
# Remove the Windows man page placeholder from 'globals.py'. # Remove the Windows man page placeholder from 'globals.py'.
sed -i '/^WINDOWS_MAN_PAGE/d' "$GLOBALS_PY" sed -i '/^BUILTIN_MAN_PAGE/d' "${GLOBALS_PY}"
echo "Processing man page at ${MAN_PAGE} and placing output into ${GLOBALS_PY}..." echo "Processing man page at ${MAN_PAGE} and placing output into ${GLOBALS_PY}..."
@ -116,15 +115,15 @@ echo "Processing man page at ${MAN_PAGE} and placing output into ${GLOBALS_PY}..
# escape sequence. Not required under Cygwin because man outputs ANSI escape # escape sequence. Not required under Cygwin because man outputs ANSI escape
# codes automatically. # codes automatically.
echo WINDOWS_MAN_PAGE = '"""' >> "$GLOBALS_PY" echo BUILTIN_MAN_PAGE = '"""' >> "${GLOBALS_PY}"
if [[ "$PLATFORM" == CYGWIN* ]]; then if [[ "${PLATFORM}" == CYGWIN* ]]; then
MANWIDTH=80 MAN_KEEP_FORMATTING=1 man "$MAN_PAGE" | sed $'s/\u2010/-/g' >> "$GLOBALS_PY" MANWIDTH=80 MAN_KEEP_FORMATTING=1 man "${MAN_PAGE}" | sed $'s/\u2010/-/g' >> "${GLOBALS_PY}"
else else
MANWIDTH=80 MAN_KEEP_FORMATTING=1 man "$MAN_PAGE" | ul | sed $'s/\u2010/-/g' >> "$GLOBALS_PY" MANWIDTH=80 MAN_KEEP_FORMATTING=1 man "${MAN_PAGE}" | ul | sed $'s/\u2010/-/g' >> "${GLOBALS_PY}"
fi fi
echo '"""' >> "$GLOBALS_PY" echo '"""' >> "${GLOBALS_PY}"
echo "Done." echo "Done."
exit 0 exit 0

View File

@ -3,7 +3,7 @@
# #
# The MIT License (MIT) # The MIT License (MIT)
# #
# Copyright (C) 2021 Joe Testa (jtesta@positronsecurity.com) # Copyright (C) 2021-2024 Joe Testa (jtesta@positronsecurity.com)
# #
# Permission is hereby granted, free of charge, to any person obtaining a copy # Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal # of this software and associated documentation files (the "Software"), to deal
@ -44,6 +44,9 @@ rm -rf parts/ prime/ snap/ stage/ build/ dist/ src/*.egg-info/ ssh-audit*.snap
git checkout snapcraft.yaml 2> /dev/null git checkout snapcraft.yaml 2> /dev/null
git checkout src/ssh_audit/globals.py 2> /dev/null git checkout src/ssh_audit/globals.py 2> /dev/null
# Add the built-in manual page.
./add_builtin_man_page.sh
# Get the version from the globals.py file. # Get the version from the globals.py file.
version=$(grep VERSION src/ssh_audit/globals.py | awk 'BEGIN {FS="="} ; {print $2}' | tr -d '[:space:]') version=$(grep VERSION src/ssh_audit/globals.py | awk 'BEGIN {FS="="} ; {print $2}' | tr -d '[:space:]')

View File

@ -3,7 +3,7 @@
# #
# The MIT License (MIT) # The MIT License (MIT)
# #
# Copyright (C) 2021 Joe Testa (jtesta@positronsecurity.com) # Copyright (C) 2021-2024 Joe Testa (jtesta@positronsecurity.com)
# #
# Permission is hereby granted, free of charge, to any person obtaining a copy # Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal # of this software and associated documentation files (the "Software"), to deal
@ -34,10 +34,10 @@
PLATFORM="$(uname -s)" PLATFORM="$(uname -s)"
# This script is intended for use on Cygwin only. # This script is intended for use on Cygwin only.
case "$PLATFORM" in case "${PLATFORM}" in
CYGWIN*) ;; CYGWIN*) ;;
*) *)
echo "Platform not supported ($PLATFORM). This must be run in Cygwin only." echo "Platform not supported (${PLATFORM}). This must be run in Cygwin only."
exit 1 exit 1
;; ;;
esac esac
@ -77,14 +77,15 @@ fi
git checkout src/ssh_audit/globals.py 2> /dev/null git checkout src/ssh_audit/globals.py 2> /dev/null
# Update the man page. # Update the man page.
./update_windows_man_page.sh ./add_builtin_man_page.sh
if [[ $? != 0 ]]; then retval=$?
if [[ ${retval} != 0 ]]; then
echo "Failed to run ./update_windows_man_page.sh" echo "Failed to run ./update_windows_man_page.sh"
exit 1 exit 1
fi fi
# Do all operations from this point from the main source directory. # Do all operations from this point from the main source directory.
pushd src/ssh_audit > /dev/null pushd src/ssh_audit || exit > /dev/null
# Delete the existing VERSION variable and add the value that the user entered, above. # Delete the existing VERSION variable and add the value that the user entered, above.
sed -i '/^VERSION/d' globals.py sed -i '/^VERSION/d' globals.py
@ -109,8 +110,9 @@ else
fi fi
# Ensure that the version string doesn't have '-dev' in it. # Ensure that the version string doesn't have '-dev' in it.
X=`dist/ssh-audit.exe | grep -E 'ssh-audit.exe v.+\-dev'` > /dev/null dist/ssh-audit.exe | grep -E 'ssh-audit.exe v.+\-dev' > /dev/null
if [[ $? == 0 ]]; then retval=$?
if [[ ${retval} == 0 ]]; then
echo -e "\nError: executable's version number includes '-dev'." echo -e "\nError: executable's version number includes '-dev'."
exit 1 exit 1
fi fi
@ -121,5 +123,5 @@ rm -rf build/ ssh-audit.spec ssh-audit.py
# Reset the changes we made to globals.py. # Reset the changes we made to globals.py.
git checkout globals.py 2> /dev/null git checkout globals.py 2> /dev/null
popd > /dev/null popd || exit > /dev/null
exit 0 exit 0

View File

@ -28,8 +28,8 @@ IMAGE_NAME=positronsecurity/ssh-audit-test-framework
# Terminal colors. # Terminal colors.
CLR="\033[0m" CLR="\033[0m"
RED="\033[0;31m" #RED="\033[0;31m"
YELLOW="\033[0;33m" #YELLOW="\033[0;33m"
GREEN="\033[0;32m" GREEN="\033[0;32m"
REDB="\033[1;31m" # Red + bold REDB="\033[1;31m" # Red + bold
YELLOWB="\033[1;33m" # Yellow + bold YELLOWB="\033[1;33m" # Yellow + bold
@ -38,7 +38,7 @@ GREENB="\033[1;32m" # Green + bold
# Program return values. # Program return values.
PROGRAM_RETVAL_FAILURE=3 PROGRAM_RETVAL_FAILURE=3
PROGRAM_RETVAL_WARNING=2 PROGRAM_RETVAL_WARNING=2
PROGRAM_RETVAL_CONNECTION_ERROR=1 #PROGRAM_RETVAL_CONNECTION_ERROR=1
PROGRAM_RETVAL_GOOD=0 PROGRAM_RETVAL_GOOD=0
@ -58,21 +58,21 @@ check_if_docker_image_exists() {
# Uncompresses and compiles the specified version of Dropbear. # Uncompresses and compiles the specified version of Dropbear.
compile_dropbear() { compile_dropbear() {
version=$1 version=$1
compile 'Dropbear' "$version" compile "Dropbear" "$version"
} }
# Uncompresses and compiles the specified version of OpenSSH. # Uncompresses and compiles the specified version of OpenSSH.
compile_openssh() { compile_openssh() {
version=$1 version=$1
compile 'OpenSSH' "$version" compile "OpenSSH" "$version"
} }
# Uncompresses and compiles the specified version of TinySSH. # Uncompresses and compiles the specified version of TinySSH.
compile_tinyssh() { compile_tinyssh() {
version=$1 version=$1
compile 'TinySSH' "$version" compile "TinySSH" "$version"
} }
@ -84,31 +84,31 @@ compile() {
uncompress_options= uncompress_options=
source_dir= source_dir=
server_executable= server_executable=
if [[ $project == 'OpenSSH' ]]; then if [[ $project == "OpenSSH" ]]; then
tarball="openssh-${version}.tar.gz" tarball="openssh-${version}.tar.gz"
uncompress_options="xzf" uncompress_options="xzf"
source_dir="openssh-${version}" source_dir="openssh-${version}"
server_executable=sshd server_executable=sshd
elif [[ $project == 'Dropbear' ]]; then elif [[ $project == "Dropbear" ]]; then
tarball="dropbear-${version}.tar.bz2" tarball="dropbear-${version}.tar.bz2"
uncompress_options="xjf" uncompress_options="xjf"
source_dir="dropbear-${version}" source_dir="dropbear-${version}"
server_executable=dropbear server_executable=dropbear
elif [[ $project == 'TinySSH' ]]; then elif [[ $project == "TinySSH" ]]; then
tarball="${version}.tar.gz" tarball="${version}.tar.gz"
uncompress_options="xzf" uncompress_options="xzf"
source_dir="tinyssh-${version}" source_dir="tinyssh-${version}"
server_executable='build/bin/tinysshd' server_executable="build/bin/tinysshd"
fi fi
echo "Uncompressing ${project} ${version}..." echo "Uncompressing ${project} ${version}..."
tar $uncompress_options "$tarball" tar $uncompress_options "$tarball"
echo "Compiling ${project} ${version}..." echo "Compiling ${project} ${version}..."
pushd "$source_dir" > /dev/null pushd "$source_dir" || exit > /dev/null
# TinySSH has no configure script... only a Makefile. # TinySSH has no configure script... only a Makefile.
if [[ $project == 'TinySSH' ]]; then if [[ $project == "TinySSH" ]]; then
make -j 10 make -j 10
else else
./configure && make -j 10 ./configure && make -j 10
@ -120,7 +120,7 @@ compile() {
fi fi
echo -e "\n${GREEN}Successfully built ${project} ${version}${CLR}\n" echo -e "\n${GREEN}Successfully built ${project} ${version}${CLR}\n"
popd > /dev/null popd || exit > /dev/null
} }
@ -130,11 +130,11 @@ create_docker_image() {
TMP_DIR=$(mktemp -d /tmp/sshaudit-docker-XXXXXXXXXX) TMP_DIR=$(mktemp -d /tmp/sshaudit-docker-XXXXXXXXXX)
# Copy the Dockerfile and all files in the test/docker/ dir to our new temp directory. # Copy the Dockerfile and all files in the test/docker/ dir to our new temp directory.
find test/docker/ -maxdepth 1 -type f -exec cp -t "$TMP_DIR" '{}' + find test/docker/ -maxdepth 1 -type f -exec cp -t "$TMP_DIR" "{}" +
# Make the temp directory our working directory for the duration of the build # Make the temp directory our working directory for the duration of the build
# process. # process.
pushd "$TMP_DIR" > /dev/null pushd "$TMP_DIR" || exit > /dev/null
# Get the release keys. # Get the release keys.
get_dropbear_release_key get_dropbear_release_key
@ -143,22 +143,22 @@ create_docker_image() {
# Aside from checking the GPG signatures, we also compare against this known-good # Aside from checking the GPG signatures, we also compare against this known-good
# SHA-256 hash just in case. # SHA-256 hash just in case.
get_openssh '4.0p1' '5adb9b2c2002650e15216bf94ed9db9541d9a17c96fcd876784861a8890bc92b' get_openssh "4.0p1" "5adb9b2c2002650e15216bf94ed9db9541d9a17c96fcd876784861a8890bc92b"
get_openssh '5.6p1' '538af53b2b8162c21a293bb004ae2bdb141abd250f61b4cea55244749f3c6c2b' get_openssh "5.6p1" "538af53b2b8162c21a293bb004ae2bdb141abd250f61b4cea55244749f3c6c2b"
get_openssh '8.0p1' 'bd943879e69498e8031eb6b7f44d08cdc37d59a7ab689aa0b437320c3481fd68' get_openssh "8.0p1" "bd943879e69498e8031eb6b7f44d08cdc37d59a7ab689aa0b437320c3481fd68"
get_dropbear '2019.78' '525965971272270995364a0eb01f35180d793182e63dd0b0c3eb0292291644a4' get_dropbear "2019.78" "525965971272270995364a0eb01f35180d793182e63dd0b0c3eb0292291644a4"
get_tinyssh '20190101' '554a9a94e53b370f0cd0c5fbbd322c34d1f695cbcea6a6a32dcb8c9f595b3fea' get_tinyssh "20190101" "554a9a94e53b370f0cd0c5fbbd322c34d1f695cbcea6a6a32dcb8c9f595b3fea"
# Compile the versions of OpenSSH. # Compile the versions of OpenSSH.
compile_openssh '4.0p1' compile_openssh "4.0p1"
compile_openssh '5.6p1' compile_openssh "5.6p1"
compile_openssh '8.0p1' compile_openssh "8.0p1"
# Compile the versions of Dropbear. # Compile the versions of Dropbear.
compile_dropbear '2019.78' compile_dropbear "2019.78"
# Compile the versions of TinySSH. # Compile the versions of TinySSH.
compile_tinyssh '20190101' compile_tinyssh "20190101"
# Rename the default config files so we know they are our originals. # Rename the default config files so we know they are our originals.
@ -175,7 +175,7 @@ create_docker_image() {
# #
# Test 1: Basic test. # Test 1: Basic test.
create_openssh_config '4.0p1' 'test1' "HostKey /etc/ssh/ssh1_host_key\nHostKey /etc/ssh/ssh_host_rsa_key_1024\nHostKey /etc/ssh/ssh_host_dsa_key" create_openssh_config "4.0p1" "test1" "HostKey /etc/ssh/ssh1_host_key\nHostKey /etc/ssh/ssh_host_rsa_key_1024\nHostKey /etc/ssh/ssh_host_dsa_key"
# #
@ -183,19 +183,19 @@ create_docker_image() {
# #
# Test 1: Basic test. # Test 1: Basic test.
create_openssh_config '5.6p1' 'test1' "HostKey /etc/ssh/ssh_host_rsa_key_1024\nHostKey /etc/ssh/ssh_host_dsa_key" create_openssh_config "5.6p1" "test1" "HostKey /etc/ssh/ssh_host_rsa_key_1024\nHostKey /etc/ssh/ssh_host_dsa_key"
# Test 2: RSA 1024 host key with RSA 1024 certificate. # Test 2: RSA 1024 host key with RSA 1024 certificate.
create_openssh_config '5.6p1' 'test2' "HostKey /etc/ssh/ssh_host_rsa_key_1024\nHostCertificate /etc/ssh/ssh_host_rsa_key_1024-cert_1024.pub" create_openssh_config "5.6p1" "test2" "HostKey /etc/ssh/ssh_host_rsa_key_1024\nHostCertificate /etc/ssh/ssh_host_rsa_key_1024-cert_1024.pub"
# Test 3: RSA 1024 host key with RSA 3072 certificate. # Test 3: RSA 1024 host key with RSA 3072 certificate.
create_openssh_config '5.6p1' 'test3' "HostKey /etc/ssh/ssh_host_rsa_key_1024\nHostCertificate /etc/ssh/ssh_host_rsa_key_1024-cert_3072.pub" create_openssh_config "5.6p1" "test3" "HostKey /etc/ssh/ssh_host_rsa_key_1024\nHostCertificate /etc/ssh/ssh_host_rsa_key_1024-cert_3072.pub"
# Test 4: RSA 3072 host key with RSA 1024 certificate. # Test 4: RSA 3072 host key with RSA 1024 certificate.
create_openssh_config '5.6p1' 'test4' "HostKey /etc/ssh/ssh_host_rsa_key_3072\nHostCertificate /etc/ssh/ssh_host_rsa_key_3072-cert_1024.pub" create_openssh_config "5.6p1" "test4" "HostKey /etc/ssh/ssh_host_rsa_key_3072\nHostCertificate /etc/ssh/ssh_host_rsa_key_3072-cert_1024.pub"
# Test 5: RSA 3072 host key with RSA 3072 certificate. # Test 5: RSA 3072 host key with RSA 3072 certificate.
create_openssh_config '5.6p1' 'test5' "HostKey /etc/ssh/ssh_host_rsa_key_3072\nHostCertificate /etc/ssh/ssh_host_rsa_key_3072-cert_3072.pub" create_openssh_config "5.6p1" "test5" "HostKey /etc/ssh/ssh_host_rsa_key_3072\nHostCertificate /etc/ssh/ssh_host_rsa_key_3072-cert_3072.pub"
# #
@ -203,19 +203,19 @@ create_docker_image() {
# #
# Test 1: Basic test. # Test 1: Basic test.
create_openssh_config '8.0p1' 'test1' "HostKey /etc/ssh/ssh_host_rsa_key_3072\nHostKey /etc/ssh/ssh_host_ecdsa_key\nHostKey /etc/ssh/ssh_host_ed25519_key" create_openssh_config "8.0p" "test1" "HostKey /etc/ssh/ssh_host_rsa_key_3072\nHostKey /etc/ssh/ssh_host_ecdsa_key\nHostKey /etc/ssh/ssh_host_ed25519_key"
# Test 2: ED25519 certificate test. # Test 2: ED25519 certificate test.
create_openssh_config '8.0p1' 'test2' "HostKey /etc/ssh/ssh_host_ed25519_key\nHostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub" create_openssh_config "8.0p1" "test2" "HostKey /etc/ssh/ssh_host_ed25519_key\nHostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub"
# Test 3: Hardened installation test. # Test 3: Hardened installation test.
create_openssh_config '8.0p1' 'test3' "HostKey /etc/ssh/ssh_host_ed25519_key\nKexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256\nCiphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr\nMACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com" create_openssh_config "8.0p1" "test3" "HostKey /etc/ssh/ssh_host_ed25519_key\nKexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256\nCiphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr\nMACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com"
# Now build the docker image! # Now build the docker image!
docker build --tag "$IMAGE_NAME:$IMAGE_VERSION" . docker build --tag "$IMAGE_NAME:$IMAGE_VERSION" .
popd > /dev/null popd || exit > /dev/null
rm -rf -- "$TMP_DIR" rm -rf -- "$TMP_DIR"
} }
@ -233,19 +233,19 @@ create_openssh_config() {
# Downloads the Dropbear release key and adds it to the local keyring. # Downloads the Dropbear release key and adds it to the local keyring.
get_dropbear_release_key() { get_dropbear_release_key() {
get_release_key 'Dropbear' 'https://matt.ucc.asn.au/dropbear/releases/dropbear-key-2015.asc' 'F29C6773' 'F734 7EF2 EE2E 07A2 6762 8CA9 4493 1494 F29C 6773' get_release_key "Dropbear" "https://matt.ucc.asn.au/dropbear/releases/dropbear-key-2015.asc" "F29C6773" "F734 7EF2 EE2E 07A2 6762 8CA9 4493 1494 F29C 6773"
} }
# Downloads the OpenSSH release key and adds it to the local keyring. # Downloads the OpenSSH release key and adds it to the local keyring.
get_openssh_release_key() { get_openssh_release_key() {
get_release_key 'OpenSSH' 'https://ftp.openbsd.org/pub/OpenBSD/OpenSSH/RELEASE_KEY.asc' '6D920D30' '59C2 118E D206 D927 E667 EBE3 D3E5 F56B 6D92 0D30' get_release_key "OpenSSH" "https://ftp.openbsd.org/pub/OpenBSD/OpenSSH/RELEASE_KEY.asc" "6D920D30" "59C2 118E D206 D927 E667 EBE3 D3E5 F56B 6D92 0D30"
} }
# Downloads the TinySSH release key and adds it to the local keyring. # Downloads the TinySSH release key and adds it to the local keyring.
get_tinyssh_release_key() { get_tinyssh_release_key() {
get_release_key 'TinySSH' '' '96939FF9' 'AADF 2EDF 5529 F170 2772 C8A2 DEC4 D246 931E F49B' get_release_key "TinySSH" "" "96939FF9" "AADF 2EDF 5529 F170 2772 C8A2 DEC4 D246 931E F49B"
} }
@ -256,7 +256,7 @@ get_release_key() {
release_key_fingerprint_expected=$4 release_key_fingerprint_expected=$4
# The TinySSH release key isn't on any website, apparently. # The TinySSH release key isn't on any website, apparently.
if [[ $project == 'TinySSH' ]]; then if [[ $project == "TinySSH" ]]; then
gpg --keyserver keys.gnupg.net --recv-key "$key_id" gpg --keyserver keys.gnupg.net --recv-key "$key_id"
else else
echo -e "\nGetting ${project} release key...\n" echo -e "\nGetting ${project} release key...\n"
@ -268,7 +268,8 @@ get_release_key() {
rm key.asc rm key.asc
fi fi
local release_key_fingerprint_actual=$(gpg --fingerprint "$key_id") local release_key_fingerprint_actual
release_key_fingerprint_actual=$(gpg --fingerprint "$key_id")
if [[ $release_key_fingerprint_actual != *"$release_key_fingerprint_expected"* ]]; then if [[ $release_key_fingerprint_actual != *"$release_key_fingerprint_expected"* ]]; then
echo -e "\n${REDB}Error: ${project} release key fingerprint does not match expected value!\n\tExpected: $release_key_fingerprint_expected\n\tActual: $release_key_fingerprint_actual\n\nTerminating.${CLR}" echo -e "\n${REDB}Error: ${project} release key fingerprint does not match expected value!\n\tExpected: $release_key_fingerprint_expected\n\tActual: $release_key_fingerprint_actual\n\nTerminating.${CLR}"
exit 1 exit 1
@ -281,7 +282,7 @@ get_release_key() {
get_dropbear() { get_dropbear() {
version=$1 version=$1
tarball_checksum_expected=$2 tarball_checksum_expected=$2
get_source 'Dropbear' "$version" "$tarball_checksum_expected" get_source "Dropbear" "$version" "$tarball_checksum_expected"
} }
@ -289,7 +290,7 @@ get_dropbear() {
get_openssh() { get_openssh() {
version=$1 version=$1
tarball_checksum_expected=$2 tarball_checksum_expected=$2
get_source 'OpenSSH' "$version" "$tarball_checksum_expected" get_source "OpenSSH" "$version" "$tarball_checksum_expected"
} }
@ -297,7 +298,7 @@ get_openssh() {
get_tinyssh() { get_tinyssh() {
version=$1 version=$1
tarball_checksum_expected=$2 tarball_checksum_expected=$2
get_source 'TinySSH' "$version" "$tarball_checksum_expected" get_source "TinySSH" "$version" "$tarball_checksum_expected"
} }
@ -311,20 +312,20 @@ get_source() {
tarball= tarball=
sig= sig=
signer= signer=
if [[ $project == 'OpenSSH' ]]; then if [[ $project == "OpenSSH" ]]; then
base_url_source='https://cdn.openbsd.org/pub/OpenBSD/OpenSSH/portable/' base_url_source="https://cdn.openbsd.org/pub/OpenBSD/OpenSSH/portable/"
base_url_sig=$base_url_source base_url_sig=$base_url_source
tarball="openssh-${version}.tar.gz" tarball="openssh-${version}.tar.gz"
sig="${tarball}.asc" sig="${tarball}.asc"
signer="Damien Miller " signer="Damien Miller "
elif [[ $project == 'Dropbear' ]]; then elif [[ $project == "Dropbear" ]]; then
base_url_source='https://matt.ucc.asn.au/dropbear/releases/' base_url_source="https://matt.ucc.asn.au/dropbear/releases/"
base_url_sig=$base_url_source base_url_sig=$base_url_source
tarball="dropbear-${version}.tar.bz2" tarball="dropbear-${version}.tar.bz2"
sig="${tarball}.asc" sig="${tarball}.asc"
signer="Dropbear SSH Release Signing <matt@ucc.asn.au>" signer="Dropbear SSH Release Signing <matt@ucc.asn.au>"
elif [[ $project == 'TinySSH' ]]; then elif [[ $project == "TinySSH" ]]; then
base_url_source='https://github.com/janmojzis/tinyssh/archive/' base_url_source="https://github.com/janmojzis/tinyssh/archive/"
base_url_sig="https://github.com/janmojzis/tinyssh/releases/download/${version}/" base_url_sig="https://github.com/janmojzis/tinyssh/releases/download/${version}/"
tarball="${version}.tar.gz" tarball="${version}.tar.gz"
sig="${tarball}.asc" sig="${tarball}.asc"
@ -339,12 +340,14 @@ get_source() {
# Older OpenSSH releases were .sigs. # Older OpenSSH releases were .sigs.
if [[ ($project == 'OpenSSH') && (! -f $sig) ]]; then if [[ ($project == "OpenSSH") && (! -f $sig) ]]; then
wget "${base_url_sig}openssh-${version}.tar.gz.sig" wget "${base_url_sig}openssh-${version}.tar.gz.sig"
sig=openssh-${version}.tar.gz.sig sig=openssh-${version}.tar.gz.sig
fi fi
local gpg_verify=$(gpg --verify "${sig}" "${tarball}" 2>&1) local gpg_verify
gpg_verify=$(gpg --verify "${sig}" "${tarball}" 2>&1)
retval=$?
if [[ $gpg_verify != *"Good signature from \"${signer}"* ]]; then if [[ $gpg_verify != *"Good signature from \"${signer}"* ]]; then
echo -e "\n\n${REDB}Error: ${project} signature invalid!\n$gpg_verify\n\nTerminating.${CLR}" echo -e "\n\n${REDB}Error: ${project} signature invalid!\n$gpg_verify\n\nTerminating.${CLR}"
exit 1 exit 1
@ -352,14 +355,15 @@ get_source() {
# Check GPG's return value. 0 denotes a valid signature, and 1 is returned # Check GPG's return value. 0 denotes a valid signature, and 1 is returned
# on invalid signatures. # on invalid signatures.
if [[ $? != 0 ]]; then if [[ ${retval} != 0 ]]; then
echo -e "\n\n${REDB}Error: ${project} signature invalid! Verification returned code: $?\n\nTerminating.${CLR}" echo -e "\n\n${REDB}Error: ${project} signature invalid! Verification returned code: $?\n\nTerminating.${CLR}"
exit 1 exit 1
fi fi
echo -e "${GREEN}Signature on ${project} sources verified.${CLR}\n" echo -e "${GREEN}Signature on ${project} sources verified.${CLR}\n"
local checksum_actual=$(sha256sum "${tarball}" | cut -f1 -d" ") local checksum_actual
checksum_actual=$(sha256sum "${tarball}" | cut -f1 -d" ")
if [[ $checksum_actual != "$tarball_checksum_expected" ]]; then if [[ $checksum_actual != "$tarball_checksum_expected" ]]; then
echo -e "${REDB}Error: ${project} checksum is invalid!\n Expected: ${tarball_checksum_expected}\n Actual: ${checksum_actual}\n\n Terminating.${CLR}" echo -e "${REDB}Error: ${project} checksum is invalid!\n Expected: ${tarball_checksum_expected}\n Actual: ${checksum_actual}\n\n Terminating.${CLR}"
exit 1 exit 1
@ -369,11 +373,12 @@ get_source() {
# Pulls the defined image from Dockerhub. # Pulls the defined image from Dockerhub.
pull_docker_image() { pull_docker_image() {
docker pull "$IMAGE_NAME:$IMAGE_VERSION" docker pull "${IMAGE_NAME}:${IMAGE_VERSION}"
if [[ $? == 0 ]]; then retval=$?
echo -e "${GREEN}Successfully downloaded image $IMAGE_NAME:$IMAGE_VERSION from Dockerhub.${CLR}\n" if [[ ${retval} == 0 ]]; then
echo -e "${GREEN}Successfully downloaded image ${IMAGE_NAME}:${IMAGE_VERSION} from Dockerhub.${CLR}\n"
else else
echo -e "${REDB}Failed to pull image $IMAGE_NAME:$IMAGE_VERSION from Dockerhub! Error code: $?${CLR}\n" echo -e "${REDB}Failed to pull image ${IMAGE_NAME}:${IMAGE_VERSION} from Dockerhub! Error code: ${retval}${CLR}\n"
exit 1 exit 1
fi fi
} }
@ -387,7 +392,7 @@ run_dropbear_test() {
options=$3 options=$3
expected_retval=$4 expected_retval=$4
run_test 'Dropbear' $dropbear_version $test_number "$options" $expected_retval run_test "Dropbear" "${dropbear_version}" "${test_number}" "${options}" "${expected_retval}"
} }
@ -398,7 +403,7 @@ run_openssh_test() {
test_number=$2 test_number=$2
expected_retval=$3 expected_retval=$3
run_test 'OpenSSH' $openssh_version $test_number '' $expected_retval run_test "OpenSSH" "${openssh_version}" "${test_number}" "" "${expected_retval}"
} }
@ -409,7 +414,7 @@ run_tinyssh_test() {
test_number=$2 test_number=$2
expected_retval=$3 expected_retval=$3
run_test 'TinySSH' $tinyssh_version $test_number '' $expected_retval run_test "TinySSH" "${tinyssh_version}" "${test_number}" "" "${expected_retval}"
} }
@ -427,7 +432,7 @@ run_test() {
expected_result_stdout= expected_result_stdout=
expected_result_json= expected_result_json=
test_name= test_name=
if [[ $server_type == 'OpenSSH' ]]; then if [[ $server_type == "OpenSSH" ]]; then
server_exec="/openssh/sshd-${version} -D -f /etc/ssh/sshd_config-${version}_${test_number}" server_exec="/openssh/sshd-${version} -D -f /etc/ssh/sshd_config-${version}_${test_number}"
test_result_stdout="${TEST_RESULT_DIR}/openssh_${version}_${test_number}.txt" test_result_stdout="${TEST_RESULT_DIR}/openssh_${version}_${test_number}.txt"
test_result_json="${TEST_RESULT_DIR}/openssh_${version}_${test_number}.json" test_result_json="${TEST_RESULT_DIR}/openssh_${version}_${test_number}.json"
@ -435,14 +440,14 @@ run_test() {
expected_result_json="test/docker/expected_results/openssh_${version}_${test_number}.json" expected_result_json="test/docker/expected_results/openssh_${version}_${test_number}.json"
test_name="OpenSSH ${version} ${test_number}" test_name="OpenSSH ${version} ${test_number}"
options= options=
elif [[ $server_type == 'Dropbear' ]]; then elif [[ $server_type == "Dropbear" ]]; then
server_exec="/dropbear/dropbear-${version} -F ${options}" server_exec="/dropbear/dropbear-${version} -F ${options}"
test_result_stdout="${TEST_RESULT_DIR}/dropbear_${version}_${test_number}.txt" test_result_stdout="${TEST_RESULT_DIR}/dropbear_${version}_${test_number}.txt"
test_result_json="${TEST_RESULT_DIR}/dropbear_${version}_${test_number}.json" test_result_json="${TEST_RESULT_DIR}/dropbear_${version}_${test_number}.json"
expected_result_stdout="test/docker/expected_results/dropbear_${version}_${test_number}.txt" expected_result_stdout="test/docker/expected_results/dropbear_${version}_${test_number}.txt"
expected_result_json="test/docker/expected_results/dropbear_${version}_${test_number}.json" expected_result_json="test/docker/expected_results/dropbear_${version}_${test_number}.json"
test_name="Dropbear ${version} ${test_number}" test_name="Dropbear ${version} ${test_number}"
elif [[ $server_type == 'TinySSH' ]]; then elif [[ $server_type == "TinySSH" ]]; then
server_exec="/usr/bin/tcpserver -HRDl0 0.0.0.0 22 /tinysshd/tinyssh-20190101 -v /etc/tinyssh/" server_exec="/usr/bin/tcpserver -HRDl0 0.0.0.0 22 /tinysshd/tinyssh-20190101 -v /etc/tinyssh/"
test_result_stdout="${TEST_RESULT_DIR}/tinyssh_${version}_${test_number}.txt" test_result_stdout="${TEST_RESULT_DIR}/tinyssh_${version}_${test_number}.txt"
test_result_json="${TEST_RESULT_DIR}/tinyssh_${version}_${test_number}.json" test_result_json="${TEST_RESULT_DIR}/tinyssh_${version}_${test_number}.json"
@ -451,14 +456,15 @@ run_test() {
test_name="TinySSH ${version} ${test_number}" test_name="TinySSH ${version} ${test_number}"
fi fi
cid=$(docker run -d -p 2222:22 "$IMAGE_NAME:$IMAGE_VERSION" ${server_exec}) #echo "Running: docker run --rm -d -p 2222:22 $IMAGE_NAME:$IMAGE_VERSION ${server_exec}"
#echo "Running: docker run -d -p 2222:22 $IMAGE_NAME:$IMAGE_VERSION ${server_exec}" cid=$(docker run --rm -d -p 2222:22 "${IMAGE_NAME}:${IMAGE_VERSION}" ${server_exec})
if [[ $? != 0 ]]; then retval=$?
echo -e "${REDB}Failed to run docker image! (exit code: $?)${CLR}" if [[ ${retval} != 0 ]]; then
echo -e "${REDB}Failed to run docker image! (exit code: ${retval})${CLR}"
exit 1 exit 1
fi fi
./ssh-audit.py localhost:2222 > "$test_result_stdout" ./ssh-audit.py --skip-rate-test localhost:2222 > "$test_result_stdout"
actual_retval=$? actual_retval=$?
if [[ $actual_retval != "$expected_retval" ]]; then if [[ $actual_retval != "$expected_retval" ]]; then
echo -e "${REDB}Unexpected return value. Expected: ${expected_retval}; Actual: ${actual_retval}${CLR}" echo -e "${REDB}Unexpected return value. Expected: ${expected_retval}; Actual: ${actual_retval}${CLR}"
@ -467,12 +473,12 @@ run_test() {
echo -e "\n${REDB}This failure cannot be automatically fixed; this script must be manually updated with the new expected return value.${CLR}" echo -e "\n${REDB}This failure cannot be automatically fixed; this script must be manually updated with the new expected return value.${CLR}"
fi fi
cat ${test_result_stdout} cat "${test_result_stdout}"
docker container stop -t 0 $cid > /dev/null docker container stop -t 0 "${cid}" > /dev/null
exit 1 exit 1
fi fi
./ssh-audit.py -jj localhost:2222 > "$test_result_json" ./ssh-audit.py --skip-rate-test -jj localhost:2222 > "$test_result_json"
actual_retval=$? actual_retval=$?
if [[ $actual_retval != "$expected_retval" ]]; then if [[ $actual_retval != "$expected_retval" ]]; then
echo -e "${REDB}Unexpected return value. Expected: ${expected_retval}; Actual: ${actual_retval}${CLR}" echo -e "${REDB}Unexpected return value. Expected: ${expected_retval}; Actual: ${actual_retval}${CLR}"
@ -481,21 +487,22 @@ run_test() {
echo -e "\n${REDB}This failure cannot be automatically fixed; this script must be manually updated with the new expected return value.${CLR}" echo -e "\n${REDB}This failure cannot be automatically fixed; this script must be manually updated with the new expected return value.${CLR}"
fi fi
cat ${test_result_json} cat "${test_result_json}"
docker container stop -t 0 $cid > /dev/null docker container stop -t 0 "${cid}" > /dev/null
exit 1 exit 1
fi fi
docker container stop -t 0 $cid > /dev/null docker container stop -t 0 "${cid}" > /dev/null
if [[ $? != 0 ]]; then retval=$?
echo -e "${REDB}Failed to stop docker container ${cid}! (exit code: $?)${CLR}" if [[ ${retval} != 0 ]]; then
echo -e "${REDB}Failed to stop docker container ${cid}! (exit code: ${retval})${CLR}"
exit 1 exit 1
fi fi
# TinySSH outputs a random string in each banner, which breaks our test. So # TinySSH outputs a random string in each banner, which breaks our test. So
# we need to filter out the banner part of the output so we get stable, repeatable # we need to filter out the banner part of the output so we get stable, repeatable
# results. # results.
if [[ $server_type == 'TinySSH' ]]; then if [[ $server_type == "TinySSH" ]]; then
grep -v "(gen) banner: " "${test_result_stdout}" > "${test_result_stdout}.tmp" grep -v "(gen) banner: " "${test_result_stdout}" > "${test_result_stdout}.tmp"
mv "${test_result_stdout}.tmp" "${test_result_stdout}" mv "${test_result_stdout}.tmp" "${test_result_stdout}"
cat "${test_result_json}" | perl -pe 's/"comments": ".*?"/"comments": ""/' | perl -pe 's/"raw": ".+?"/"raw": ""/' > "${test_result_json}.tmp" cat "${test_result_json}" | perl -pe 's/"comments": ".*?"/"comments": ""/' | perl -pe 's/"raw": ".+?"/"raw": ""/' > "${test_result_json}.tmp"
@ -503,7 +510,8 @@ run_test() {
fi fi
diff=$(diff -u "${expected_result_stdout}" "${test_result_stdout}") diff=$(diff -u "${expected_result_stdout}" "${test_result_stdout}")
if [[ $? != 0 ]]; then retval=$?
if [[ ${retval} != 0 ]]; then
# If the user wants to update the tests, then overwrite the expected results with the actual results. # If the user wants to update the tests, then overwrite the expected results with the actual results.
if [[ $accept == 1 ]]; then if [[ $accept == 1 ]]; then
@ -518,7 +526,8 @@ run_test() {
fi fi
diff=$(diff -u "${expected_result_json}" "${test_result_json}") diff=$(diff -u "${expected_result_json}" "${test_result_json}")
if [[ $? != 0 ]]; then retval=$?
if [[ ${retval} != 0 ]]; then
# If the user wants to update the tests, then overwrite the expected results with the actual results. # If the user wants to update the tests, then overwrite the expected results with the actual results.
if [[ $accept == 1 ]]; then if [[ $accept == 1 ]]; then
@ -562,15 +571,15 @@ run_custom_policy_test() {
version= version=
config= config=
if [[ ${config_number} == 'config1' ]]; then if [[ ${config_number} == "config1" ]]; then
version='5.6p1' version="5.6p1"
config='sshd_config-5.6p1_test1' config="sshd_config-5.6p1_test1"
elif [[ ${config_number} == 'config2' ]]; then elif [[ ${config_number} == "config2" ]]; then
version='8.0p1' version="8.0p1"
config='sshd_config-8.0p1_test1' config="sshd_config-8.0p1_test1"
elif [[ ${config_number} == 'config3' ]]; then elif [[ ${config_number} == "config3" ]]; then
version='5.6p1' version="5.6p1"
config='sshd_config-5.6p1_test4' config="sshd_config-5.6p1_test4"
fi fi
server_exec="/openssh/sshd-${version} -D -f /etc/ssh/${config}" server_exec="/openssh/sshd-${version} -D -f /etc/ssh/${config}"
@ -594,10 +603,11 @@ run_policy_test() {
expected_exit_code=$6 expected_exit_code=$6
#echo "Running: docker run -d -p 2222:22 $IMAGE_NAME:$IMAGE_VERSION ${server_exec}" #echo "Running: docker run --rm -d -p 2222:22 $IMAGE_NAME:$IMAGE_VERSION ${server_exec}"
cid=$(docker run -d -p 2222:22 "$IMAGE_NAME:$IMAGE_VERSION" ${server_exec}) cid=$(docker run --rm -d -p 2222:22 "${IMAGE_NAME}:${IMAGE_VERSION}" ${server_exec})
if [[ $? != 0 ]]; then retval=$?
echo -e "${REDB}Failed to run docker image! (exit code: $?)${CLR}" if [[ ${retval} != 0 ]]; then
echo -e "${REDB}Failed to run docker image! (exit code: ${retval})${CLR}"
exit 1 exit 1
fi fi
@ -612,7 +622,7 @@ run_policy_test() {
fi fi
cat "${test_result_stdout}" cat "${test_result_stdout}"
docker container stop -t 0 $cid > /dev/null docker container stop -t 0 "${cid}" > /dev/null
exit 1 exit 1
fi fi
@ -627,18 +637,20 @@ run_policy_test() {
fi fi
cat "${test_result_json}" cat "${test_result_json}"
docker container stop -t 0 $cid > /dev/null docker container stop -t 0 "${cid}" > /dev/null
exit 1 exit 1
fi fi
docker container stop -t 0 $cid > /dev/null docker container stop -t 0 "${cid}" > /dev/null
if [[ $? != 0 ]]; then retval=$?
echo -e "${REDB}Failed to stop docker container ${cid}! (exit code: $?)${CLR}" if [[ ${retval} != 0 ]]; then
echo -e "${REDB}Failed to stop docker container ${cid}! (exit code: ${retval})${CLR}"
exit 1 exit 1
fi fi
diff=$(diff -u "${expected_result_stdout}" "${test_result_stdout}") diff=$(diff -u "${expected_result_stdout}" "${test_result_stdout}")
if [[ $? != 0 ]]; then retval=$?
if [[ ${retval} != 0 ]]; then
# If the user wants to update the tests, then overwrite the expected results with the actual results. # If the user wants to update the tests, then overwrite the expected results with the actual results.
if [[ $accept == 1 ]]; then if [[ $accept == 1 ]]; then
@ -652,7 +664,8 @@ run_policy_test() {
fi fi
diff=$(diff -u "${expected_result_json}" "${test_result_json}") diff=$(diff -u "${expected_result_json}" "${test_result_json}")
if [[ $? != 0 ]]; then retval=$?
if [[ ${retval} != 0 ]]; then
# If the user wants to update the tests, then overwrite the expected results with the actual results. # If the user wants to update the tests, then overwrite the expected results with the actual results.
if [[ $accept == 1 ]]; then if [[ $accept == 1 ]]; then
@ -671,8 +684,9 @@ run_policy_test() {
# First check if docker is functional. # First check if docker is functional.
docker version > /dev/null docker version > /dev/null
if [[ $? != 0 ]]; then retval=$?
echo -e "${REDB}Error: 'docker version' command failed (error code: $?). Is docker installed and functioning?${CLR}" if [[ ${retval} != 0 ]]; then
echo -e "${REDB}Error: 'docker version' command failed (error code: ${retval}). Is docker installed and functioning?${CLR}"
exit 1 exit 1
fi fi
@ -680,7 +694,8 @@ fi
# Check if the docker image is the most up-to-date version. # Check if the docker image is the most up-to-date version.
docker_image_exists=0 docker_image_exists=0
check_if_docker_image_exists check_if_docker_image_exists
if [[ $? == 0 ]]; then retval=$?
if [[ ${retval} == 0 ]]; then
docker_image_exists=1 docker_image_exists=1
fi fi
@ -688,11 +703,11 @@ fi
# Check if the user specified --create to build a new image. # Check if the user specified --create to build a new image.
if [[ ($# == 1) && ($1 == "--create") ]]; then if [[ ($# == 1) && ($1 == "--create") ]]; then
# Ensure that the image name doesn't already exist before building. # Ensure that the image name doesn't already exist before building.
if [[ $docker_image_exists == 1 ]]; then if [[ ${docker_image_exists} == 1 ]]; then
echo -e "${REDB}Error: --create specified, but $IMAGE_NAME:$IMAGE_VERSION already exists!${CLR}" echo -e "${REDB}Error: --create specified, but ${IMAGE_NAME}:${IMAGE_VERSION} already exists!${CLR}"
exit 1 exit 1
else else
echo -e "\nCreating docker image $IMAGE_NAME:$IMAGE_VERSION..." echo -e "\nCreating docker image ${IMAGE_NAME}:${IMAGE_VERSION}..."
create_docker_image create_docker_image
echo -e "\n${GREEN}Done creating docker image!${CLR}" echo -e "\n${GREEN}Done creating docker image!${CLR}"
exit 0 exit 0
@ -708,8 +723,8 @@ fi
# If we weren't explicitly told to create a new image, and it doesn't exist, then pull it from Dockerhub. # If we weren't explicitly told to create a new image, and it doesn't exist, then pull it from Dockerhub.
if [[ $docker_image_exists == 0 ]]; then if [[ ${docker_image_exists} == 0 ]]; then
echo -e "\nPulling docker image $IMAGE_NAME:$IMAGE_VERSION..." echo -e "\nPulling docker image ${IMAGE_NAME}:${IMAGE_VERSION}..."
pull_docker_image pull_docker_image
fi fi
@ -721,64 +736,73 @@ TEST_RESULT_DIR=$(mktemp -d /tmp/ssh-audit_test-results_XXXXXXXXXX)
# Now run all the tests. # Now run all the tests.
echo -e "\nRunning tests..." echo -e "\nRunning tests..."
run_openssh_test '4.0p1' 'test1' $PROGRAM_RETVAL_FAILURE run_openssh_test "4.0p1" "test1" "${PROGRAM_RETVAL_FAILURE}"
echo echo
run_openssh_test '5.6p1' 'test1' $PROGRAM_RETVAL_FAILURE run_openssh_test "5.6p1" "test1" "${PROGRAM_RETVAL_FAILURE}"
run_openssh_test '5.6p1' 'test2' $PROGRAM_RETVAL_FAILURE run_openssh_test "5.6p1" "test2" "${PROGRAM_RETVAL_FAILURE}"
run_openssh_test '5.6p1' 'test3' $PROGRAM_RETVAL_FAILURE run_openssh_test "5.6p1" "test3" "${PROGRAM_RETVAL_FAILURE}"
run_openssh_test '5.6p1' 'test4' $PROGRAM_RETVAL_FAILURE run_openssh_test "5.6p1" "test4" "${PROGRAM_RETVAL_FAILURE}"
run_openssh_test '5.6p1' 'test5' $PROGRAM_RETVAL_FAILURE run_openssh_test "5.6p1" "test5" "${PROGRAM_RETVAL_FAILURE}"
echo echo
run_openssh_test '8.0p1' 'test1' $PROGRAM_RETVAL_FAILURE run_openssh_test "8.0p1" "test1" "${PROGRAM_RETVAL_FAILURE}"
run_openssh_test '8.0p1' 'test2' $PROGRAM_RETVAL_FAILURE run_openssh_test "8.0p1" "test2" "${PROGRAM_RETVAL_FAILURE}"
run_openssh_test '8.0p1' 'test3' $PROGRAM_RETVAL_WARNING run_openssh_test "8.0p1" "test3" "${PROGRAM_RETVAL_WARNING}"
echo echo
run_dropbear_test '2019.78' 'test1' '-r /etc/dropbear/dropbear_rsa_host_key_1024 -r /etc/dropbear/dropbear_dss_host_key -r /etc/dropbear/dropbear_ecdsa_host_key' 3 run_dropbear_test "2019.78" "test1" "-r /etc/dropbear/dropbear_rsa_host_key_1024 -r /etc/dropbear/dropbear_dss_host_key -r /etc/dropbear/dropbear_ecdsa_host_key" 3
echo echo
run_tinyssh_test '20190101' 'test1' $PROGRAM_RETVAL_WARNING run_tinyssh_test "20190101" "test1" "${PROGRAM_RETVAL_WARNING}"
echo echo
echo echo
run_custom_policy_test 'config1' 'test1' $PROGRAM_RETVAL_GOOD run_custom_policy_test "config1" "test1" "${PROGRAM_RETVAL_GOOD}"
run_custom_policy_test 'config1' 'test2' $PROGRAM_RETVAL_FAILURE run_custom_policy_test "config1" "test2" "${PROGRAM_RETVAL_FAILURE}"
run_custom_policy_test 'config1' 'test3' $PROGRAM_RETVAL_FAILURE run_custom_policy_test "config1" "test3" "${PROGRAM_RETVAL_FAILURE}"
run_custom_policy_test 'config1' 'test4' $PROGRAM_RETVAL_FAILURE run_custom_policy_test "config1" "test4" "${PROGRAM_RETVAL_FAILURE}"
run_custom_policy_test 'config1' 'test5' $PROGRAM_RETVAL_FAILURE run_custom_policy_test "config1" "test5" "${PROGRAM_RETVAL_FAILURE}"
run_custom_policy_test 'config2' 'test6' $PROGRAM_RETVAL_GOOD run_custom_policy_test "config2" "test6" "${PROGRAM_RETVAL_GOOD}"
# Passing test with host key certificate and CA key certificates. # Passing test with host key certificate and CA key certificates.
run_custom_policy_test 'config3' 'test7' $PROGRAM_RETVAL_GOOD run_custom_policy_test "config3" "test7" "${PROGRAM_RETVAL_GOOD}"
# Failing test with host key certificate and non-compliant CA key length. # Failing test with host key certificate and non-compliant CA key length.
run_custom_policy_test 'config3' 'test8' $PROGRAM_RETVAL_FAILURE run_custom_policy_test "config3" "test8" "${PROGRAM_RETVAL_FAILURE}"
# Failing test with non-compliant host key certificate and CA key certificate. # Failing test with non-compliant host key certificate and CA key certificate.
run_custom_policy_test 'config3' 'test9' $PROGRAM_RETVAL_FAILURE run_custom_policy_test "config3" "test9" "${PROGRAM_RETVAL_FAILURE}"
# Failing test with non-compliant host key certificate and non-compliant CA key certificate. # Failing test with non-compliant host key certificate and non-compliant CA key certificate.
run_custom_policy_test 'config3' 'test10' $PROGRAM_RETVAL_FAILURE run_custom_policy_test "config3" "test10" "${PROGRAM_RETVAL_FAILURE}"
# Passing test with host key size check. # Passing test with host key size check.
run_custom_policy_test 'config2' 'test11' $PROGRAM_RETVAL_GOOD run_custom_policy_test "config2" "test11" "${PROGRAM_RETVAL_GOOD}"
# Failing test with non-compliant host key size check. # Failing test with non-compliant host key size check.
run_custom_policy_test 'config2' 'test12' $PROGRAM_RETVAL_FAILURE run_custom_policy_test "config2" "test12" "${PROGRAM_RETVAL_FAILURE}"
# Passing test with DH modulus test. # Passing test with DH modulus test.
run_custom_policy_test 'config2' 'test13' $PROGRAM_RETVAL_GOOD run_custom_policy_test "config2" "test13" "${PROGRAM_RETVAL_GOOD}"
# Failing test with DH modulus test. # Failing test with DH modulus test.
run_custom_policy_test 'config2' 'test14' $PROGRAM_RETVAL_FAILURE run_custom_policy_test "config2" "test14" "${PROGRAM_RETVAL_FAILURE}"
# Passing test with algorithm subset matching.
run_custom_policy_test "config2" "test15" "${PROGRAM_RETVAL_GOOD}"
# Failing test with algorithm subset matching.
run_custom_policy_test "config2" "test16" "${PROGRAM_RETVAL_FAILURE}"
# Passing test with larger key matching.
run_custom_policy_test "config2" "test17" "${PROGRAM_RETVAL_GOOD}"
# Failing test for built-in OpenSSH 8.0p1 server policy (RSA host key size is 3072 instead of 4096). # Failing test for built-in OpenSSH 8.0p1 server policy (RSA host key size is 3072 instead of 4096).
run_builtin_policy_test "Hardened OpenSSH Server v8.0 (version 2)" "8.0p1" "test1" "-o HostKeyAlgorithms=rsa-sha2-512,rsa-sha2-256,ssh-ed25519 -o KexAlgorithms=curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256 -o Ciphers=chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr -o MACs=hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com" $PROGRAM_RETVAL_FAILURE run_builtin_policy_test "Hardened OpenSSH Server v8.0 (version 4)" "8.0p1" "test1" "-o HostKeyAlgorithms=rsa-sha2-512,rsa-sha2-256,ssh-ed25519 -o KexAlgorithms=curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256 -o Ciphers=chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr -o MACs=hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com" "${PROGRAM_RETVAL_FAILURE}"
# Failing test for built-in OpenSSH 8.0p1 server policy (MACs not hardened). # Failing test for built-in OpenSSH 8.0p1 server policy (MACs not hardened).
run_builtin_policy_test "Hardened OpenSSH Server v8.0 (version 2)" "8.0p1" "test2" "-o HostKeyAlgorithms=rsa-sha2-512,rsa-sha2-256,ssh-ed25519 -o KexAlgorithms=curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256 -o Ciphers=chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr" $PROGRAM_RETVAL_FAILURE run_builtin_policy_test "Hardened OpenSSH Server v8.0 (version 4)" "8.0p1" "test2" "-o HostKeyAlgorithms=rsa-sha2-512,rsa-sha2-256,ssh-ed25519 -o KexAlgorithms=curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256 -o Ciphers=chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr" "${PROGRAM_RETVAL_FAILURE}"
if [[ $num_failures == 0 ]]; then if [[ $num_failures == 0 ]]; then
echo -e "\n${GREENB}ALL TESTS PASS!${CLR}\n" echo -e "\n${GREENB}ALL TESTS PASS!${CLR}\n"
rm -rf -- "$TEST_RESULT_DIR" rm -rf -- "${TEST_RESULT_DIR}"
else else
echo -e "\n${REDB}${num_failures} TESTS FAILED!${CLR}\n" echo -e "\n${REDB}${num_failures} TESTS FAILED!${CLR}\n"
fi fi

View File

@ -19,11 +19,11 @@ classifiers =
License :: OSI Approved :: MIT License License :: OSI Approved :: MIT License
Operating System :: OS Independent Operating System :: OS Independent
Programming Language :: Python :: 3 Programming Language :: Python :: 3
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.11
Programming Language :: Python :: 3.12
Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: Implementation :: CPython
Programming Language :: Python :: Implementation :: PyPy Programming Language :: Python :: Implementation :: PyPy
Topic :: Security Topic :: Security
@ -33,7 +33,7 @@ classifiers =
packages = find: packages = find:
package_dir = package_dir =
= src = src
python_requires = >=3.7,<4 python_requires = >=3.8,<4
[options.packages.find] [options.packages.find]
where = src where = src

View File

@ -8,6 +8,9 @@ description: |
base: core22 base: core22
grade: stable grade: stable
confinement: strict confinement: strict
architectures:
- build-on: [amd64]
build-for: [all]
apps: apps:
ssh-audit: ssh-audit:

View File

@ -54,7 +54,7 @@ class Algorithms:
def ssh1(self) -> Optional['Algorithms.Item']: def ssh1(self) -> Optional['Algorithms.Item']:
if self.ssh1kex is None: if self.ssh1kex is None:
return None return None
item = Algorithms.Item(1, SSH1_KexDB.ALGORITHMS) item = Algorithms.Item(1, SSH1_KexDB.get_db())
item.add('key', ['ssh-rsa1']) item.add('key', ['ssh-rsa1'])
item.add('enc', self.ssh1kex.supported_ciphers) item.add('enc', self.ssh1kex.supported_ciphers)
item.add('aut', self.ssh1kex.supported_authentications) item.add('aut', self.ssh1kex.supported_authentications)
@ -64,7 +64,7 @@ class Algorithms:
def ssh2(self) -> Optional['Algorithms.Item']: def ssh2(self) -> Optional['Algorithms.Item']:
if self.ssh2kex is None: if self.ssh2kex is None:
return None return None
item = Algorithms.Item(2, SSH2_KexDB.ALGORITHMS) item = Algorithms.Item(2, SSH2_KexDB.get_db())
item.add('kex', self.ssh2kex.kex_algorithms) item.add('kex', self.ssh2kex.kex_algorithms)
item.add('key', self.ssh2kex.key_algorithms) item.add('key', self.ssh2kex.key_algorithms)
item.add('enc', self.ssh2kex.server.encryption) item.add('enc', self.ssh2kex.server.encryption)

View File

@ -1,7 +1,7 @@
""" """
The MIT License (MIT) The MIT License (MIT)
Copyright (C) 2017-2021 Joe Testa (jtesta@positronsecurity.com) Copyright (C) 2017-2024 Joe Testa (jtesta@positronsecurity.com)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu) Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
@ -60,10 +60,20 @@ class AuditConf:
self.manual = False self.manual = False
self.debug = False self.debug = False
self.gex_test = '' self.gex_test = ''
self.dheat: Optional[str] = None
self.dheat_concurrent_connections: int = 0
self.dheat_e_length: int = 0
self.dheat_target_alg: str = ""
self.skip_rate_test = False
self.conn_rate_test: str = "1:1"
self.conn_rate_test_enabled = False
self.conn_rate_test_threads = 0
self.conn_rate_test_target_rate = 0
def __setattr__(self, name: str, value: Union[str, int, float, bool, Sequence[int]]) -> None: def __setattr__(self, name: str, value: Union[str, int, float, bool, Sequence[int]]) -> None:
valid = False valid = False
if name in ['batch', 'client_audit', 'colors', 'json', 'json_print_indent', 'list_policies', 'manual', 'make_policy', 'ssh1', 'ssh2', 'timeout_set', 'verbose', 'debug']: if name in ['batch', 'client_audit', 'colors', 'json', 'json_print_indent', 'list_policies', 'manual', 'make_policy', 'ssh1', 'ssh2', 'timeout_set', 'verbose', 'debug', 'skip_rate_test']:
valid, value = True, bool(value) valid, value = True, bool(value)
elif name in ['ipv4', 'ipv6']: elif name in ['ipv4', 'ipv6']:
valid, value = True, bool(value) valid, value = True, bool(value)
@ -94,6 +104,89 @@ class AuditConf:
if num_threads < 1: if num_threads < 1:
raise ValueError('invalid number of threads: {}'.format(value)) raise ValueError('invalid number of threads: {}'.format(value))
value = num_threads value = num_threads
elif name == "dheat":
# Valid values:
# * None
# * "10" (concurrent-connections)
# * "10:diffie-hellman-group18-sha512" (concurrent-connections:target-alg)
# * "10:diffie-hellman-group18-sha512:100" (concurrent-connections:target-alg:e-length)
valid = True
if value is not None:
def _parse_concurrent_connections(s: str) -> int:
if Utils.parse_int(s) < 1:
raise ValueError("number of concurrent connections must be 1 or greater: {}".format(s))
return int(s)
def _parse_e_length(s: str) -> int:
s_int = Utils.parse_int(s)
if s_int < 2:
raise ValueError("length of e must not be less than 2: {}".format(s))
return s_int
def _parse_target_alg(s: str) -> str:
if len(s) == 0:
raise ValueError("target algorithm must not be the empty string.")
return s
value = str(value)
fields = value.split(':')
self.dheat_concurrent_connections = _parse_concurrent_connections(fields[0])
# Parse the target algorithm if present.
if len(fields) >= 2:
self.dheat_target_alg = _parse_target_alg(fields[1])
# Parse the length of e, if present.
if len(fields) == 3:
self.dheat_e_length = _parse_e_length(fields[2])
if len(fields) > 3:
raise ValueError("only three fields are expected instead of {}: {}".format(len(fields), value))
elif name in ["dheat_concurrent_connections", "dheat_e_length"]:
valid = True
if not isinstance(value, int):
valid = False
elif name == "dheat_target_alg":
valid = True
if not isinstance(value, str):
valid = False
elif name == "conn_rate_test":
# Valid values:
# * "4" (run rate test with 4 threads)
# * "4:100" (run rate test with 4 threads, targeting 100 connections/second)
error_msg = "valid format for {:s} is \"N\" or \"N:N\", where N is an integer.".format(name)
self.conn_rate_test_enabled = True
fields = str(value).split(":")
if len(fields) > 2 or len(fields) == 0:
raise ValueError(error_msg)
else:
self.conn_rate_test_threads = int(fields[0])
if self.conn_rate_test_threads < 1:
raise ValueError("number of threads must be 1 or greater.")
self.conn_rate_test_target_rate = 0
if len(fields) == 2:
self.conn_rate_test_target_rate = int(fields[1])
if self.conn_rate_test_target_rate < 1:
raise ValueError("rate target must be 1 or greater.")
elif name == "conn_rate_test_enabled":
valid = True
if not isinstance(value, bool):
valid = False
elif name in ["conn_rate_test_threads", "conn_rate_test_target_rate"]:
valid = True
if not isinstance(value, int):
valid = False
if valid: if valid:
object.__setattr__(self, name, value) object.__setattr__(self, name, value)

View File

@ -0,0 +1,124 @@
"""
The MIT License (MIT)
Copyright (C) 2020-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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
from typing import Any, Dict, List, Optional, Union
# Each field maps directly to a private member variable of the Policy class.
BUILTIN_POLICIES: Dict[str, Dict[str, Union[Optional[str], Optional[List[str]], bool, Dict[str, Any]]]] = {
# Amazon Linux 2023
'Hardened Amazon Linux 2023 (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', '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},
# Debian Server 12
'Hardened Debian 12 (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', '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},
# Rocky Linux 9
'Hardened Rocky Linux 9 (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', '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},
# Ubuntu Server policies
'Hardened Ubuntu Server 16.04 LTS (version 4)': {'version': '4', 'changelog': 'No change log available.', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519'], 'optional_host_keys': ['ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256@libssh.org', 'diffie-hellman-group-exchange-sha256'], '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-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "rsa-sha2-512-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "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 18.04 LTS (version 4)': {'version': '4', 'changelog': 'No change log available.', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519'], 'optional_host_keys': ['ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], '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-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "rsa-sha2-512-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "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 20.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': ['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 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},
# Generic OpenSSH Server policies
'Hardened OpenSSH Server v7.7 (version 4)': {'version': '4', 'changelog': 'No change log available.', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], '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}, "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 v7.8 (version 4)': {'version': '4', 'changelog': 'No change log available.', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], '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}, "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 v7.9 (version 4)': {'version': '4', 'changelog': 'No change log available.', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], '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}, "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 v8.0 (version 4)': {'version': '4', 'changelog': 'No change log available.', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], '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}, "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 v8.1 (version 4)': {'version': '4', 'changelog': 'No change log available.', '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': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], '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 v8.2 (version 4)': {'version': '4', 'changelog': 'No change log available.', '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': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], '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 v8.3 (version 4)': {'version': '4', 'changelog': 'No change log available.', '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': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], '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 v8.4 (version 4)': {'version': '4', 'changelog': 'No change log available.', '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': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], '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 v8.5 (version 4)': {'version': '4', 'changelog': 'No change log available.', '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': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], '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 v8.6 (version 4)': {'version': '4', 'changelog': 'No change log available.', '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': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], '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 v8.7 (version 4)': {'version': '4', 'changelog': 'No change log available.', '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': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], '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 v8.8 (version 3)': {'version': '3', 'changelog': 'No change log available.', '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'], '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 v8.9 (version 3)': {'version': '3', 'changelog': 'No change log available.', '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'], '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.0 (version 3)': {'version': '3', 'changelog': 'No change log available.', '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'], '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.1 (version 3)': {'version': '3', 'changelog': 'No change log available.', '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'], '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.2 (version 3)': {'version': '3', 'changelog': 'No change log available.', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_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'], 'kex': ['sntrup761x25519-sha512@openssh.com', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], '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.3 (version 3)': {'version': '3', 'changelog': 'No change log available.', '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'], '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.4 (version 2)': {'version': '2', 'changelog': 'No change log available.', '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'], '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.5 (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'], '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.6 (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.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},
# Amazon Linux Policies
'Hardened Amazon Linux Client 2023 (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},
# Debian Client Policies
'Hardened Debian Client 12 (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},
# Rocky Linux Policies
'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},
'Hardened Ubuntu Client 18.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', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', '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},
'Hardened Ubuntu Client 20.04 LTS (version 3)': {'version': '3', 'changelog': 'Added kex-strict-c-v00@openssh.com to kex list.', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512', 'rsa-sha2-512-cert-v01@openssh.com'], 'optional_host_keys': None, 'kex': ['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 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},
}

1064
src/ssh_audit/dheat.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -21,13 +21,15 @@
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
""" """
import struct
import traceback import traceback
# pylint: disable=unused-import # pylint: disable=unused-import
from typing import Dict, List, Set, Sequence, Tuple, Iterable # noqa: F401 from typing import Dict, List, Set, Sequence, Tuple, Iterable # noqa: F401
from typing import Callable, Optional, Union, Any # noqa: F401 from typing import Callable, Optional, Union, Any # noqa: F401
from ssh_audit.kexdh import KexDHException, KexGroupExchange_SHA1, KexGroupExchange_SHA256 from ssh_audit.banner import Banner
from ssh_audit.kexdh import KexDHException, KexGroupExchange, KexGroupExchange_SHA1, KexGroupExchange_SHA256
from ssh_audit.ssh2_kexdb import SSH2_KexDB from ssh_audit.ssh2_kexdb import SSH2_KexDB
from ssh_audit.ssh2_kex import SSH2_Kex from ssh_audit.ssh2_kex import SSH2_Kex
from ssh_audit.ssh_socket import SSH_Socket from ssh_audit.ssh_socket import SSH_Socket
@ -64,7 +66,7 @@ class GEXTest:
# Parse the server's KEX. # Parse the server's KEX.
_, payload = s.read_packet(2) _, payload = s.read_packet(2)
SSH2_Kex.parse(out, payload) SSH2_Kex.parse(out, payload)
except KexDHException: except (KexDHException, struct.error):
out.v("Failed to parse server's kex. Stack trace:\n%s" % str(traceback.format_exc()), write_now=True) out.v("Failed to parse server's kex. Stack trace:\n%s" % str(traceback.format_exc()), write_now=True)
return False return False
@ -106,24 +108,12 @@ class GEXTest:
# in a "connection reset by peer" error. It may be possible # in a "connection reset by peer" error. It may be possible
# to recover from such an error by sleeping for some time # to recover from such an error by sleeping for some time
# before continuing to issue reconnects. # before continuing to issue reconnects.
if GEXTest.reconnect(out, s, kex, gex_alg) is False: 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.fail('Reconnect failed.')
return exitcodes.FAILURE return exitcodes.FAILURE
try:
modulus_size_returned = None
kex_group.send_init_gex(s, bits_min, bits_pref, bits_max)
kex_group.recv_reply(s, False)
modulus_size_returned = kex_group.get_dh_modulus_size()
out.d('Modulus size returned by server: ' + str(modulus_size_returned) + ' bits', write_now=True)
except KexDHException:
out.d('[exception] ' + str(traceback.format_exc()), write_now=True)
finally:
# The server is in a state that is not re-testable,
# so there's nothing else to do with this open
# connection.
s.close()
if modulus_size_returned is not None: if modulus_size_returned > 0:
if gex_alg in modulus_dict: if gex_alg in modulus_dict:
if modulus_size_returned not in modulus_dict[gex_alg]: if modulus_size_returned not in modulus_dict[gex_alg]:
modulus_dict[gex_alg].append(modulus_size_returned) modulus_dict[gex_alg].append(modulus_size_returned)
@ -134,7 +124,7 @@ class GEXTest:
# Runs the DH moduli test against the specified target. # Runs the DH moduli test against the specified target.
@staticmethod @staticmethod
def run(out: 'OutputBuffer', s: 'SSH_Socket', kex: 'SSH2_Kex') -> None: def run(out: 'OutputBuffer', s: 'SSH_Socket', banner: Optional['Banner'], kex: 'SSH2_Kex') -> None:
GEX_ALGS = { GEX_ALGS = {
'diffie-hellman-group-exchange-sha1': KexGroupExchange_SHA1, 'diffie-hellman-group-exchange-sha1': KexGroupExchange_SHA1,
'diffie-hellman-group-exchange-sha256': KexGroupExchange_SHA256, 'diffie-hellman-group-exchange-sha256': KexGroupExchange_SHA256,
@ -148,31 +138,14 @@ class GEXTest:
# Check if the server supports any of the group-exchange # Check if the server supports any of the group-exchange
# algorithms. If so, test each one. # algorithms. If so, test each one.
for gex_alg, kex_group_class in GEX_ALGS.items(): for gex_alg, kex_group_class in GEX_ALGS.items(): # pylint: disable=too-many-nested-blocks
if gex_alg in kex.kex_algorithms: if gex_alg in kex.kex_algorithms:
out.d('Preparing to perform DH group exchange using ' + gex_alg + ' with min, pref and max modulus sizes of 512 bits, 1024 bits and 1536 bits...', write_now=True) out.d('Preparing to perform DH group exchange using ' + gex_alg + ' with min, pref and max modulus sizes of 512 bits, 1024 bits and 1536 bits...', write_now=True)
if GEXTest.reconnect(out, s, kex, gex_alg) is False:
break
kex_group = kex_group_class(out) kex_group = kex_group_class(out)
smallest_modulus = -1 smallest_modulus, reconnect_failed = GEXTest._send_init(out, s, kex_group, kex, gex_alg, 512, 1024, 1536)
if reconnect_failed:
# First try a range of weak sizes. break
try:
kex_group.send_init_gex(s, 512, 1024, 1536)
kex_group.recv_reply(s, False)
# Its been observed that servers will return a group
# larger than the requested max. So just because we
# got here, doesn't mean the server is vulnerable...
smallest_modulus = kex_group.get_dh_modulus_size()
out.d('Modulus size returned by server: ' + str(smallest_modulus) + ' bits', write_now=True)
except KexDHException:
out.d('[exception] ' + str(traceback.format_exc()), write_now=True)
finally:
s.close()
# Try an array of specific modulus sizes... one at a time. # Try an array of specific modulus sizes... one at a time.
reconnect_failed = False reconnect_failed = False
@ -183,32 +156,25 @@ class GEXTest:
if bits >= smallest_modulus > 0: if bits >= smallest_modulus > 0:
break break
out.d('Preparing to perform DH group exchange using ' + gex_alg + ' with min, pref and max modulus sizes of ' + str(bits) + ' bits...', write_now=True) smallest_modulus, reconnect_failed = GEXTest._send_init(out, s, kex_group, kex, gex_alg, bits, bits, bits)
if GEXTest.reconnect(out, s, kex, gex_alg) is False: # If the smallest modulus is 2048 and the server is OpenSSH, then we may have triggered the fallback mechanism, which tends to happen in testing scenarios such as this but not in most real-world conditions (see X). To better test this condition, we will do an additional check to see if the server supports sizes between 2048 and 4096, and consider this the definitive result.
reconnect_failed = True openssh_test_updated = False
break if (smallest_modulus == 2048) and (banner is not None) and (banner.software is not None) and (banner.software.find('OpenSSH') != -1):
out.d('First pass found a minimum GEX modulus of 2048 against OpenSSH server. Performing a second pass to get a more accurate result...')
try: smallest_modulus, _ = GEXTest._send_init(out, s, kex_group, kex, gex_alg, 2048, 3072, 4096)
kex_group.send_init_gex(s, bits, bits, bits) out.d('Modulus size returned by server during second pass: %d bits' % smallest_modulus, write_now=True)
kex_group.recv_reply(s, False) openssh_test_updated = bool((smallest_modulus > 0) and (smallest_modulus != 2048))
smallest_modulus = kex_group.get_dh_modulus_size()
out.d('Modulus size returned by server: ' + str(smallest_modulus) + ' bits', write_now=True)
except KexDHException as e:
out.d('Exception when testing DH group exchange ' + gex_alg + ' with modulus size ' + str(bits) + '. (Hint: this is probably normal since the server does not support this modulus size.): ' + str(e), write_now=True)
finally:
# The server is in a state that is not re-testable,
# so there's nothing else to do with this open
# connection.
s.close()
if smallest_modulus > 0: if smallest_modulus > 0:
kex.set_dh_modulus_size(gex_alg, smallest_modulus) kex.set_dh_modulus_size(gex_alg, smallest_modulus)
lst = SSH2_KexDB.get_db()['kex'][gex_alg]
# We flag moduli smaller than 2048 as a failure. # We flag moduli smaller than 2048 as a failure.
if smallest_modulus < 2048: if smallest_modulus < 2048:
text = 'using small %d-bit modulus' % smallest_modulus text = 'using small %d-bit modulus' % smallest_modulus
lst = SSH2_KexDB.ALGORITHMS['kex'][gex_alg]
# For 'diffie-hellman-group-exchange-sha256', add # For 'diffie-hellman-group-exchange-sha256', add
# a failure reason. # a failure reason.
if len(lst) == 1: if len(lst) == 1:
@ -222,7 +188,6 @@ class GEXTest:
# Moduli smaller than 3072 get flagged as a warning. # Moduli smaller than 3072 get flagged as a warning.
elif smallest_modulus < 3072: elif smallest_modulus < 3072:
lst = SSH2_KexDB.ALGORITHMS['kex'][gex_alg]
# Ensure that a warning list exists for us to append to, below. # Ensure that a warning list exists for us to append to, below.
while len(lst) < 3: while len(lst) < 3:
@ -233,5 +198,39 @@ class GEXTest:
if text not in lst[2]: if text not in lst[2]:
lst[2].append(text) lst[2].append(text)
# If we retested against OpenSSH (because its fallback mechanism was triggered), add a special note for the user.
if openssh_test_updated:
text = "OpenSSH's GEX fallback mechanism was triggered during testing. Very old SSH clients will still be able to create connections using a 2048-bit modulus, though modern clients will use %u. This can only be disabled by recompiling the code (see https://github.com/openssh/openssh-portable/blob/V_9_4/dh.c#L477)." % smallest_modulus
# Ensure that an info list exists for us to append to, below.
while len(lst) < 4:
lst.append([])
# Ensure this is only added once.
if text not in lst[3]:
lst[3].append(text)
if reconnect_failed: if reconnect_failed:
break break
@staticmethod
def _send_init(out: 'OutputBuffer', s: 'SSH_Socket', kex_group: 'KexGroupExchange', kex: 'SSH2_Kex', gex_alg: str, min_bits: int, pref_bits: int, max_bits: int) -> Tuple[int, bool]:
'''Internal function for sending the GEX initialization to the server with the minimum, preferred, and maximum modulus bits. Returns a Tuple of the modulus size received from the server (or -1 on error) and boolean signifying that the connection to the server failed.'''
smallest_modulus = -1
reconnect_failed = False
try:
if GEXTest.reconnect(out, s, kex, gex_alg) is False:
reconnect_failed = True
out.d('GEXTest._send_init(%s, %u, %u, %u): reconnection failed.' % (gex_alg, min_bits, pref_bits, max_bits), write_now=True)
else:
kex_group.send_init_gex(s, min_bits, pref_bits, max_bits)
kex_group.recv_reply(s, False)
smallest_modulus = kex_group.get_dh_modulus_size()
out.d('GEXTest._send_init(%s, %u, %u, %u): received modulus size: %d' % (gex_alg, min_bits, pref_bits, max_bits, smallest_modulus), write_now=True)
except KexDHException as e:
out.d('GEXTest._send_init(%s, %u, %u, %u): exception when performing DH group exchange init: %s' % (gex_alg, min_bits, pref_bits, max_bits, str(e)), write_now=True)
finally:
s.close()
return smallest_modulus, reconnect_failed

View File

@ -1,7 +1,7 @@
""" """
The MIT License (MIT) The MIT License (MIT)
Copyright (C) 2017-2021 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@ -22,7 +22,7 @@
THE SOFTWARE. THE SOFTWARE.
""" """
# The version to display. # The version to display.
VERSION = 'v2.9.0' VERSION = 'v3.2.0'
# SSH software to impersonate # SSH software to impersonate
SSH_HEADER = 'SSH-{0}-OpenSSH_8.2' SSH_HEADER = 'SSH-{0}-OpenSSH_8.2'
@ -30,8 +30,8 @@ SSH_HEADER = 'SSH-{0}-OpenSSH_8.2'
# The URL to the Github issues tracker. # The URL to the Github issues tracker.
GITHUB_ISSUES_URL = 'https://github.com/jtesta/ssh-audit/issues' GITHUB_ISSUES_URL = 'https://github.com/jtesta/ssh-audit/issues'
# The man page. Only filled in on Windows systems. # The man page. Only filled in on Docker, PyPI, Snap, and Windows builds.
WINDOWS_MAN_PAGE = '' BUILTIN_MAN_PAGE = ''
# True when installed from a Snap package, otherwise False. # True when installed from a Snap package, otherwise False.
SNAP_PACKAGE = False SNAP_PACKAGE = False

View File

@ -180,7 +180,7 @@ class HostKeyTest:
hostkey_min_good = 256 hostkey_min_good = 256
hostkey_min_warn = 224 hostkey_min_warn = 224
hostkey_warn_str = HostKeyTest.SMALL_ECC_MODULUS_WARNING hostkey_warn_str = HostKeyTest.SMALL_ECC_MODULUS_WARNING
if ca_key_type.startswith('ssh-ed25519') or host_key_type.startswith('ecdsa-sha2-nistp'): if ca_key_type.startswith('ssh-ed25519') or ca_key_type.startswith('ecdsa-sha2-nistp'):
cakey_min_good = 256 cakey_min_good = 256
cakey_min_warn = 224 cakey_min_warn = 224
cakey_warn_str = HostKeyTest.SMALL_ECC_MODULUS_WARNING cakey_warn_str = HostKeyTest.SMALL_ECC_MODULUS_WARNING
@ -209,6 +209,10 @@ class HostKeyTest:
elif (0 < ca_modulus_size < cakey_min_good) and (cakey_warn_str not in key_warn_comments): elif (0 < ca_modulus_size < cakey_min_good) and (cakey_warn_str not in key_warn_comments):
key_warn_comments.append(cakey_warn_str) key_warn_comments.append(cakey_warn_str)
# If the CA key type uses ECDSA with a NIST P-curve, fail it for possibly being back-doored.
if ca_key_type.startswith('ecdsa-sha2-nistp'):
key_fail_comments.append('CA key uses elliptic curves that are suspected as being backdoored by the U.S. National Security Agency')
# 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 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: if host_key_type in HostKeyTest.RSA_FAMILY:
for rsa_type in HostKeyTest.RSA_FAMILY: for rsa_type in HostKeyTest.RSA_FAMILY:
@ -216,16 +220,18 @@ class HostKeyTest:
# If the current key is a member of the RSA family, then populate all RSA family members with the same # 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. # failure and/or warning comments.
while len(SSH2_KexDB.ALGORITHMS['key'][rsa_type]) < 3: db = SSH2_KexDB.get_db()
SSH2_KexDB.ALGORITHMS['key'][rsa_type].append([]) while len(db['key'][rsa_type]) < 3:
db['key'][rsa_type].append([])
SSH2_KexDB.ALGORITHMS['key'][rsa_type][1].extend(key_fail_comments) db['key'][rsa_type][1].extend(key_fail_comments)
SSH2_KexDB.ALGORITHMS['key'][rsa_type][2].extend(key_warn_comments) db['key'][rsa_type][2].extend(key_warn_comments)
else: else:
host_key_types[host_key_type]['parsed'] = True host_key_types[host_key_type]['parsed'] = True
while len(SSH2_KexDB.ALGORITHMS['key'][host_key_type]) < 3: db = SSH2_KexDB.get_db()
SSH2_KexDB.ALGORITHMS['key'][host_key_type].append([]) while len(db['key'][host_key_type]) < 3:
db['key'][host_key_type].append([])
SSH2_KexDB.ALGORITHMS['key'][host_key_type][1].extend(key_fail_comments) db['key'][host_key_type][1].extend(key_fail_comments)
SSH2_KexDB.ALGORITHMS['key'][host_key_type][2].extend(key_warn_comments) db['key'][host_key_type][2].extend(key_warn_comments)

View File

@ -26,6 +26,7 @@ import binascii
import os import os
import random import random
import struct import struct
import traceback
# pylint: disable=unused-import # pylint: disable=unused-import
from typing import Dict, List, Set, Sequence, Tuple, Iterable # noqa: F401 from typing import Dict, List, Set, Sequence, Tuple, Iterable # noqa: F401
@ -79,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
@ -99,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.
@ -211,6 +212,15 @@ class KexDH: # pragma: nocover
# CA's modulus. Bingo. # CA's modulus. Bingo.
ca_key_n, ca_key_n_len, ptr = KexDH.__get_bytes(ca_key, ptr) # pylint: disable=unused-variable ca_key_n, ca_key_n_len, ptr = KexDH.__get_bytes(ca_key, ptr) # pylint: disable=unused-variable
if ca_key_type.startswith("ecdsa-sha2-nistp") and ca_key_n_len > 0:
self.out.d("Found ecdsa-sha2-nistp* CA key type.")
# 0x04 signifies that this is an uncompressed public key (meaning that full X and Y values are provided in ca_key_n.
if ca_key_n[0] == 4:
ca_key_n_len = ca_key_n_len - 1 # Subtract the 0x04 byte.
ca_key_n_len = int(ca_key_n_len / 2) # Divide by 2 since the modulus is the size of either the X or Y value.
else: else:
self.out.d("Certificate type %u found; this is not usually valid in the context of a host key! Skipping it..." % cert_type) self.out.d("Certificate type %u found; this is not usually valid in the context of a host key! Skipping it..." % cert_type)
@ -375,6 +385,7 @@ class KexGroupExchange(KexDH):
while packet_type == Protocol.MSG_DEBUG: while packet_type == Protocol.MSG_DEBUG:
packet_type, payload = s.read_packet(2) packet_type, payload = s.read_packet(2)
try:
# Parse the modulus (p) and generator (g) values from the server. # Parse the modulus (p) and generator (g) values from the server.
ptr = 0 ptr = 0
p_len = struct.unpack('>I', payload[ptr:ptr + 4])[0] p_len = struct.unpack('>I', payload[ptr:ptr + 4])[0]
@ -388,6 +399,8 @@ class KexGroupExchange(KexDH):
g = int(binascii.hexlify(payload[ptr:ptr + g_len]), 16) g = int(binascii.hexlify(payload[ptr:ptr + g_len]), 16)
ptr += g_len ptr += g_len
except struct.error:
raise KexDHException("Error while parsing modulus and generator during GEX init: %s" % str(traceback.format_exc())) from None
# Now that we got the generator and modulus, perform the DH exchange # Now that we got the generator and modulus, perform the DH exchange
# like usual. # like usual.

View File

@ -1,7 +1,7 @@
""" """
The MIT License (MIT) The MIT License (MIT)
Copyright (C) 2020-2023 Joe Testa (jtesta@positronsecurity.com) Copyright (C) 2020-2024 Joe Testa (jtesta@positronsecurity.com)
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@ -31,6 +31,7 @@ from datetime import date
from ssh_audit import exitcodes from ssh_audit import exitcodes
from ssh_audit.banner import Banner from ssh_audit.banner import Banner
from ssh_audit.builtin_policies import BUILTIN_POLICIES
from ssh_audit.globals import SNAP_PACKAGE, SNAP_PERMISSIONS_ERROR from ssh_audit.globals import SNAP_PACKAGE, SNAP_PERMISSIONS_ERROR
from ssh_audit.ssh2_kex import SSH2_Kex from ssh_audit.ssh2_kex import SSH2_Kex
@ -38,69 +39,6 @@ from ssh_audit.ssh2_kex import SSH2_Kex
# Validates policy files and performs policy testing # Validates policy files and performs policy testing
class Policy: class Policy:
# Each field maps directly to a private member variable of the Policy class.
BUILTIN_POLICIES: Dict[str, Dict[str, Union[Optional[str], Optional[List[str]], bool, Dict[str, Any]]]] = {
# Ubuntu Server policies
'Hardened Ubuntu Server 16.04 LTS (version 2)': {'version': '2', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519'], 'optional_host_keys': ['ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256@libssh.org', 'diffie-hellman-group-exchange-sha256'], '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-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "rsa-sha2-512-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "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': 2048}, 'server_policy': True},
'Hardened Ubuntu Server 18.04 LTS (version 2)': {'version': '2', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519'], 'optional_host_keys': ['ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], '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-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "rsa-sha2-512-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "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': 2048}, 'server_policy': True},
'Hardened Ubuntu Server 20.04 LTS (version 2)': {'version': '2', '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': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], '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': 2048}, 'server_policy': True},
'Hardened Ubuntu Server 22.04 LTS (version 2)': {'version': '2', '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'], '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': 2048}, 'server_policy': True},
# Generic OpenSSH Server policies
'Hardened OpenSSH Server v7.7 (version 2)': {'version': '2', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], '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}, "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': 2048}, 'server_policy': True},
'Hardened OpenSSH Server v7.8 (version 2)': {'version': '2', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], '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}, "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': 2048}, 'server_policy': True},
'Hardened OpenSSH Server v7.9 (version 2)': {'version': '2', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], '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}, "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': 2048}, 'server_policy': True},
'Hardened OpenSSH Server v8.0 (version 2)': {'version': '2', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], '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}, "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': 2048}, 'server_policy': True},
'Hardened OpenSSH Server v8.1 (version 2)': {'version': '2', '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': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], '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': 2048}, 'server_policy': True},
'Hardened OpenSSH Server v8.2 (version 2)': {'version': '2', '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': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], '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': 2048}, 'server_policy': True},
'Hardened OpenSSH Server v8.3 (version 2)': {'version': '2', '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': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], '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': 2048}, 'server_policy': True},
'Hardened OpenSSH Server v8.4 (version 2)': {'version': '2', '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': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], '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': 2048}, 'server_policy': True},
'Hardened OpenSSH Server v8.5 (version 2)': {'version': '2', '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': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], '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': 2048}, 'server_policy': True},
'Hardened OpenSSH Server v8.6 (version 2)': {'version': '2', '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': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], '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': 2048}, 'server_policy': True},
'Hardened OpenSSH Server v8.7 (version 2)': {'version': '2', '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': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], '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': 2048}, 'server_policy': True},
'Hardened OpenSSH Server v8.8 (version 1)': {'version': '1', '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'], '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': 2048}, 'server_policy': True},
'Hardened OpenSSH Server v8.9 (version 1)': {'version': '1', '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'], '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': 2048}, 'server_policy': True},
'Hardened OpenSSH Server v9.0 (version 1)': {'version': '1', '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'], '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': 2048}, 'server_policy': True},
'Hardened OpenSSH Server v9.1 (version 1)': {'version': '1', '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'], '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': 2048}, 'server_policy': True},
'Hardened OpenSSH Server v9.2 (version 1)': {'version': '1', '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'], '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': 2048}, 'server_policy': True},
'Hardened OpenSSH Server v9.3 (version 1)': {'version': '1', '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'], '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': 2048}, 'server_policy': True},
# Ubuntu Client policies
'Hardened Ubuntu Client 16.04 LTS (version 2)': {'version': '2', '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},
'Hardened Ubuntu Client 18.04 LTS (version 2)': {'version': '2', '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', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', '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},
'Hardened Ubuntu Client 20.04 LTS (version 2)': {'version': '2', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512', 'rsa-sha2-512-cert-v01@openssh.com'], 'optional_host_keys': None, 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', '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},
'Hardened Ubuntu Client 22.04 LTS (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512', 'rsa-sha2-512-cert-v01@openssh.com'], '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'], '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},
}
WARNING_DEPRECATED_DIRECTIVES = "\nWARNING: this policy is using deprecated features. Future versions of ssh-audit may remove support for them. Re-generating the policy file is perhaps the most straight-forward way of resolving this issue. Manually converting the 'hostkey_size_*', 'cakey_size_*', and 'dh_modulus_size_*' directives into the new format is another option.\n" WARNING_DEPRECATED_DIRECTIVES = "\nWARNING: this policy is using deprecated features. Future versions of ssh-audit may remove support for them. Re-generating the policy file is perhaps the most straight-forward way of resolving this issue. Manually converting the 'hostkey_size_*', 'cakey_size_*', and 'dh_modulus_size_*' directives into the new format is another option.\n"
def __init__(self, policy_file: Optional[str] = None, policy_data: Optional[str] = None, manual_load: bool = False, json_output: bool = False) -> None: def __init__(self, policy_file: Optional[str] = None, policy_data: Optional[str] = None, manual_load: bool = False, json_output: bool = False) -> None:
@ -116,6 +54,9 @@ class Policy:
self._hostkey_sizes: Optional[Dict[str, Dict[str, Union[int, str, bytes]]]] = None self._hostkey_sizes: Optional[Dict[str, Dict[str, Union[int, str, bytes]]]] = None
self._dh_modulus_sizes: Optional[Dict[str, int]] = None self._dh_modulus_sizes: Optional[Dict[str, int]] = None
self._server_policy = True self._server_policy = True
self._allow_algorithm_subset_and_reordering = False
self._allow_larger_keys = False
self._errors: List[Any] = []
self._name_and_version: str = '' self._name_and_version: str = ''
@ -174,7 +115,7 @@ class Policy:
key = key.strip() key = key.strip()
val = val.strip() val = val.strip()
if key not in ['name', 'version', 'banner', 'compressions', 'host keys', 'optional host keys', 'key exchanges', 'ciphers', 'macs', 'client policy', 'host_key_sizes', 'dh_modulus_sizes'] and not key.startswith('hostkey_size_') and not key.startswith('cakey_size_') and not key.startswith('dh_modulus_size_'): if key not in ['name', 'version', 'banner', 'compressions', 'host keys', 'optional host keys', 'key exchanges', 'ciphers', 'macs', 'client policy', 'host_key_sizes', 'dh_modulus_sizes', 'allow_algorithm_subset_and_reordering', 'allow_larger_keys'] and not key.startswith('hostkey_size_') and not key.startswith('cakey_size_') and not key.startswith('dh_modulus_size_'):
raise ValueError("invalid field found in policy: %s" % line) raise ValueError("invalid field found in policy: %s" % line)
if key in ['name', 'banner']: if key in ['name', 'banner']:
@ -267,7 +208,10 @@ class Policy:
elif key.startswith('client policy') and val.lower() == 'true': elif key.startswith('client policy') and val.lower() == 'true':
self._server_policy = False self._server_policy = False
elif key == 'allow_algorithm_subset_and_reordering' and val.lower() == 'true':
self._allow_algorithm_subset_and_reordering = True
elif key == 'allow_larger_keys' and val.lower() == 'true':
self._allow_larger_keys = True
if self._name is None: if self._name is None:
raise ValueError('The policy does not have a name field.') raise ValueError('The policy does not have a name field.')
@ -277,13 +221,12 @@ class Policy:
self._name_and_version = "%s (version %s)" % (self._name, self._version) self._name_and_version = "%s (version %s)" % (self._name, self._version)
@staticmethod def _append_error(self, mismatched_field: str, expected_required: Optional[List[str]], expected_optional: Optional[List[str]], actual: List[str]) -> None:
def _append_error(errors: List[Any], mismatched_field: str, expected_required: Optional[List[str]], expected_optional: Optional[List[str]], actual: List[str]) -> None:
if expected_required is None: if expected_required is None:
expected_required = [''] expected_required = ['']
if expected_optional is None: if expected_optional is None:
expected_optional = [''] expected_optional = ['']
errors.append({'mismatched_field': mismatched_field, 'expected_required': expected_required, 'expected_optional': expected_optional, 'actual': actual}) self._errors.append({'mismatched_field': mismatched_field, 'expected_required': expected_required, 'expected_optional': expected_optional, 'actual': actual})
def _normalize_hostkey_sizes(self) -> None: def _normalize_hostkey_sizes(self) -> None:
@ -356,6 +299,12 @@ name = "Custom Policy (based on %s on %s)"
# The version of this policy (displayed in the output during scans). Not parsed, and may be any value, including strings. # The version of this policy (displayed in the output during scans). Not parsed, and may be any value, including strings.
version = 1 version = 1
# When false, host keys, kex, ciphers, and MAC lists must match exactly. When true, the target host may support a subset of the specified algorithms and/or algorithms may appear in a different order; this feature is useful for specifying a baseline and allowing some hosts the option to implement stricter controls.
allow_algorithm_subset_and_reordering = false
# When false, host keys, CA keys, and Diffie-Hellman key sizes must exactly match what's specified in this policy. When true, target systems are allowed to have larger keys; this feature is useful for specifying a baseline and allowing some hosts the option to implement stricter controls.
allow_larger_keys = false
# The banner that must match exactly. Commented out to ignore banners, since minor variability in the banner is sometimes normal. # The banner that must match exactly. Commented out to ignore banners, since minor variability in the banner is sometimes normal.
# banner = "%s" # banner = "%s"
@ -385,41 +334,53 @@ macs = %s
'''Evaluates a server configuration against this policy. Returns a tuple of a boolean (True if server adheres to policy) and an array of strings that holds error messages.''' '''Evaluates a server configuration against this policy. Returns a tuple of a boolean (True if server adheres to policy) and an array of strings that holds error messages.'''
ret = True ret = True
errors: List[Any] = []
banner_str = str(banner) banner_str = str(banner)
if (self._banner is not None) and (banner_str != self._banner): if (self._banner is not None) and (banner_str != self._banner):
ret = False ret = False
self._append_error(errors, 'Banner', [self._banner], None, [banner_str]) self._append_error('Banner', [self._banner], None, [banner_str])
# All subsequent tests require a valid kex, so end here if we don't have one. # All subsequent tests require a valid kex, so end here if we don't have one.
if kex is None: if kex is None:
return ret, errors, self._get_error_str(errors) error_list, error_str = self._get_errors()
return ret, error_list, error_str
if (self._compressions is not None) and (kex.server.compression != self._compressions): if (self._compressions is not None) and (kex.server.compression != self._compressions):
ret = False ret = False
self._append_error(errors, 'Compression', self._compressions, None, kex.server.compression) self._append_error('Compression', self._compressions, None, kex.server.compression)
# If a list of optional host keys was given in the policy, remove any of its entries from the list retrieved from the server. This allows us to do an exact comparison with the expected list below. # If a list of optional host keys was given in the policy, remove any of its entries from the list retrieved from the server. This allows us to do an exact comparison with the expected list below.
pruned_host_keys = kex.key_algorithms pruned_host_keys = kex.key_algorithms
if self._optional_host_keys is not None: if self._optional_host_keys is not None:
pruned_host_keys = [x for x in kex.key_algorithms if x not in self._optional_host_keys] pruned_host_keys = [x for x in kex.key_algorithms if x not in self._optional_host_keys]
if (self._host_keys is not None) and (pruned_host_keys != self._host_keys): # Check host keys.
if self._host_keys is not None:
# If the policy allows subsets and re-ordered algorithms...
if self._allow_algorithm_subset_and_reordering:
for hostkey_t in kex.key_algorithms:
if hostkey_t not in self._host_keys:
ret = False ret = False
self._append_error(errors, 'Host keys', self._host_keys, self._optional_host_keys, kex.key_algorithms) self._append_error('Host keys', self._host_keys, self._optional_host_keys, kex.key_algorithms)
break
# The policy requires exact matching of algorithms.
elif pruned_host_keys != self._host_keys:
ret = False
self._append_error('Host keys', self._host_keys, self._optional_host_keys, kex.key_algorithms)
# Check host key sizes.
if self._hostkey_sizes is not None: if self._hostkey_sizes is not None:
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 (self._allow_larger_keys and actual_hostkey_size < expected_hostkey_size) or \
(not self._allow_larger_keys and 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('Host key (%s) sizes' % hostkey_type, [str(expected_hostkey_size)], None, [str(actual_hostkey_size)])
# If we have expected CA signatures set, check them against what the server returned. # If we have expected CA signatures set, check them against what the server returned.
if self._hostkey_sizes is not None and len(cast(str, self._hostkey_sizes[hostkey_type]['ca_key_type'])) > 0 and cast(int, self._hostkey_sizes[hostkey_type]['ca_key_size']) > 0: if self._hostkey_sizes is not None and len(cast(str, self._hostkey_sizes[hostkey_type]['ca_key_type'])) > 0 and cast(int, self._hostkey_sizes[hostkey_type]['ca_key_size']) > 0:
@ -431,23 +392,60 @@ macs = %s
# Ensure that the CA signature type is what's expected (i.e.: the server doesn't have an RSA sig when we're expecting an ED25519 sig). # Ensure that the CA signature type is what's expected (i.e.: the server doesn't have an RSA sig when we're expecting an ED25519 sig).
if actual_ca_key_type != expected_ca_key_type: if actual_ca_key_type != expected_ca_key_type:
ret = False ret = False
self._append_error(errors, 'CA signature type', [expected_ca_key_type], None, [actual_ca_key_type]) self._append_error('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 (self._allow_larger_keys and actual_ca_key_size < expected_ca_key_size) or \
(not self._allow_larger_keys and 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('CA signature size (%s)' % actual_ca_key_type, [str(expected_ca_key_size)], None, [str(actual_ca_key_size)])
if kex.kex_algorithms != self._kex: # Check key exchanges.
if self._kex is not None:
# If the policy allows subsets and re-ordered algorithms...
if self._allow_algorithm_subset_and_reordering:
for kex_t in kex.kex_algorithms:
if kex_t not in self._kex:
ret = False ret = False
self._append_error(errors, 'Key exchanges', self._kex, None, kex.kex_algorithms) self._append_error('Key exchanges', self._kex, None, kex.kex_algorithms)
break
# If kex-strict-?-v00@openssh.com is in the policy (i.e. the Terrapin vulnerability countermeasure), then it must appear in the server's list, regardless of the "allow_algorithm_subset_and_reordering" flag.
if ('kex-strict-s-v00@openssh.com' in self._kex and 'kex-strict-s-v00@openssh.com' not in kex.kex_algorithms) or \
('kex-strict-c-v00@openssh.com' in self._kex and 'kex-strict-c-v00@openssh.com' not in kex.kex_algorithms):
ret = False
self._append_error('Key exchanges', self._kex, None, kex.kex_algorithms)
if (self._ciphers is not None) and (kex.server.encryption != self._ciphers): # The policy requires exact matching of algorithms.
elif kex.kex_algorithms != self._kex:
ret = False ret = False
self._append_error(errors, 'Ciphers', self._ciphers, None, kex.server.encryption) self._append_error('Key exchanges', self._kex, None, kex.kex_algorithms)
if (self._macs is not None) and (kex.server.mac != self._macs): # Checking Ciphers
if self._ciphers is not None:
# If the policy allows subsets and re-ordered algorithms...
if self._allow_algorithm_subset_and_reordering:
for cipher_t in kex.server.encryption:
if cipher_t not in self._ciphers:
ret = False ret = False
self._append_error(errors, 'MACs', self._macs, None, kex.server.mac) self._append_error('Ciphers', self._ciphers, None, kex.server.encryption)
break
# The policy requires exact matching of algorithms.
elif kex.server.encryption != self._ciphers:
ret = False
self._append_error('Ciphers', self._ciphers, None, kex.server.encryption)
# Checking MACs
if self._macs is not None:
# If the policy allows subsets and re-ordered algorithms...
if self._allow_algorithm_subset_and_reordering:
for mac_t in kex.server.mac:
if mac_t not in self._macs:
ret = False
self._append_error('MACs', self._macs, None, kex.server.mac)
break
# The policy requires exact matching of algorithms.
elif kex.server.mac != self._macs:
ret = False
self._append_error('MACs', self._macs, None, kex.server.mac)
if self._dh_modulus_sizes is not None: if self._dh_modulus_sizes is not None:
dh_modulus_types = list(self._dh_modulus_sizes.keys()) dh_modulus_types = list(self._dh_modulus_sizes.keys())
@ -456,26 +454,30 @@ 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 (self._allow_larger_keys and actual_dh_modulus_size < expected_dh_modulus_size) or \
(not self._allow_larger_keys and actual_dh_modulus_size != expected_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('Group exchange (%s) modulus sizes' % dh_modulus_type, [str(expected_dh_modulus_size)], None, [str(actual_dh_modulus_size)])
return ret, errors, self._get_error_str(errors) error_list, error_str = self._get_errors()
return ret, error_list, error_str
@staticmethod def _get_errors(self) -> Tuple[List[Any], str]:
def _get_error_str(errors: List[Any]) -> str: '''Returns the list of errors, along with the string representation of those errors.'''
'''Transforms an error struct to a flat string of error messages.'''
subset_and_reordering_semicolon = "; subset and/or reordering allowed" if self._allow_algorithm_subset_and_reordering else "; exact match"
subset_and_reordering_parens = " (subset and/or reordering allowed)" if self._allow_algorithm_subset_and_reordering else ""
error_list = [] error_list = []
spacer = '' spacer = ''
for e in errors: for e in self._errors:
e_str = " * %s did not match.\n" % e['mismatched_field'] e_str = " * %s did not match.\n" % e['mismatched_field']
if ('expected_optional' in e) and (e['expected_optional'] != ['']): if ('expected_optional' in e) and (e['expected_optional'] != ['']):
e_str += " - Expected (required): %s\n - Expected (optional): %s\n" % (Policy._normalize_error_field(e['expected_required']), Policy._normalize_error_field(e['expected_optional'])) e_str += " - Expected (required%s): %s\n - Expected (optional): %s\n" % (subset_and_reordering_semicolon, Policy._normalize_error_field(e['expected_required']), Policy._normalize_error_field(e['expected_optional']))
spacer = ' ' spacer = ' '
else: else:
e_str += " - Expected: %s\n" % Policy._normalize_error_field(e['expected_required']) e_str += " - Expected%s: %s\n" % (subset_and_reordering_parens, Policy._normalize_error_field(e['expected_required']))
spacer = ' ' spacer = ' '
e_str += " - Actual:%s%s\n" % (spacer, Policy._normalize_error_field(e['actual'])) e_str += " - Actual:%s%s\n" % (spacer, Policy._normalize_error_field(e['actual']))
error_list.append(e_str) error_list.append(e_str)
@ -486,7 +488,7 @@ macs = %s
if len(error_list) > 0: if len(error_list) > 0:
error_str = "\n".join(error_list) error_str = "\n".join(error_list)
return error_str return self._errors, error_str
def get_name_and_version(self) -> str: def get_name_and_version(self) -> str:
@ -500,28 +502,34 @@ macs = %s
@staticmethod @staticmethod
def list_builtin_policies() -> Tuple[List[str], List[str]]: def list_builtin_policies(verbose: bool) -> Tuple[List[str], List[str]]:
'''Returns two lists: a list of names of built-in server policies, and a list of names of built-in client policies, respectively.''' '''Returns two lists: a list of names of built-in server policies, and a list of names of built-in client policies, respectively.'''
server_policy_names = [] server_policy_descriptions = []
client_policy_names = [] client_policy_descriptions = []
for policy_name, policy in Policy.BUILTIN_POLICIES.items(): for policy_name, policy in BUILTIN_POLICIES.items():
if policy['server_policy']: policy_description = ""
server_policy_names.append(policy_name) if verbose:
policy_description = "\"{:s}\": {:s}".format(policy_name, policy['changelog'])
else: else:
client_policy_names.append(policy_name) policy_description = "\"{:s}\"".format(policy_name)
server_policy_names.sort() if policy['server_policy']:
client_policy_names.sort() server_policy_descriptions.append(policy_description)
return server_policy_names, client_policy_names else:
client_policy_descriptions.append(policy_description)
server_policy_descriptions.sort()
client_policy_descriptions.sort()
return server_policy_descriptions, client_policy_descriptions
@staticmethod @staticmethod
def load_builtin_policy(policy_name: str, json_output: bool = False) -> Optional['Policy']: def load_builtin_policy(policy_name: str, json_output: bool = False) -> Optional['Policy']:
'''Returns a Policy with the specified built-in policy name loaded, or None if no policy of that name exists.''' '''Returns a Policy with the specified built-in policy name loaded, or None if no policy of that name exists.'''
p = None p = None
if policy_name in Policy.BUILTIN_POLICIES: if policy_name in BUILTIN_POLICIES:
policy_struct = Policy.BUILTIN_POLICIES[policy_name] policy_struct = BUILTIN_POLICIES[policy_name]
p = Policy(manual_load=True, json_output=json_output) p = Policy(manual_load=True, json_output=json_output)
policy_name_without_version = policy_name[0:policy_name.rfind(' (')] policy_name_without_version = policy_name[0:policy_name.rfind(' (')]
p._name = policy_name_without_version # pylint: disable=protected-access p._name = policy_name_without_version # pylint: disable=protected-access
@ -596,4 +604,4 @@ macs = %s
if self._dh_modulus_sizes is not None: if self._dh_modulus_sizes is not None:
dh_modulus_sizes_str = str(self._dh_modulus_sizes) dh_modulus_sizes_str = str(self._dh_modulus_sizes)
return "Name: %s\nVersion: %s\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, 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) 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)

View File

@ -22,6 +22,9 @@
THE SOFTWARE. THE SOFTWARE.
""" """
# pylint: disable=unused-import # pylint: disable=unused-import
import copy
import threading
from typing import Dict, List, Set, Sequence, Tuple, Iterable # noqa: F401 from typing import Dict, List, Set, Sequence, Tuple, Iterable # noqa: F401
from typing import Callable, Optional, Union, Any # noqa: F401 from typing import Callable, Optional, Union, Any # noqa: F401
@ -34,7 +37,9 @@ class SSH1_KexDB: # pylint: disable=too-few-public-methods
FAIL_NA_UNSAFE = 'not implemented in OpenSSH (server), unsafe algorithm' FAIL_NA_UNSAFE = 'not implemented in OpenSSH (server), unsafe algorithm'
TEXT_CIPHER_IDEA = 'cipher used by commercial SSH' TEXT_CIPHER_IDEA = 'cipher used by commercial SSH'
ALGORITHMS: Dict[str, Dict[str, List[List[Optional[str]]]]] = { DB_PER_THREAD: Dict[int, Dict[str, Dict[str, List[List[Optional[str]]]]]] = {}
MASTER_DB: Dict[str, Dict[str, List[List[Optional[str]]]]] = {
'key': { 'key': {
'ssh-rsa1': [['1.2.2']], 'ssh-rsa1': [['1.2.2']],
}, },
@ -56,3 +61,24 @@ class SSH1_KexDB: # pylint: disable=too-few-public-methods
'kerberos': [['1.2.2', '3.6'], [FAIL_OPENSSH37_REMOVE]], 'kerberos': [['1.2.2', '3.6'], [FAIL_OPENSSH37_REMOVE]],
} }
} }
@staticmethod
def get_db() -> Dict[str, Dict[str, List[List[Optional[str]]]]]:
'''Returns a copy of the MASTER_DB that is private to the calling thread. This prevents multiple threads from polluting the results of other threads.'''
calling_thread_id = threading.get_ident()
if calling_thread_id not in SSH1_KexDB.DB_PER_THREAD:
SSH1_KexDB.DB_PER_THREAD[calling_thread_id] = copy.deepcopy(SSH1_KexDB.MASTER_DB)
return SSH1_KexDB.DB_PER_THREAD[calling_thread_id]
@staticmethod
def thread_exit() -> None:
'''Deletes the calling thread's copy of the MASTER_DB. This is needed because, in rare circumstances, a terminated thread's ID can be re-used by new threads.'''
calling_thread_id = threading.get_ident()
if calling_thread_id in SSH1_KexDB.DB_PER_THREAD:
del SSH1_KexDB.DB_PER_THREAD[calling_thread_id]

View File

@ -1,7 +1,7 @@
""" """
The MIT License (MIT) 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) Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
@ -23,6 +23,9 @@
THE SOFTWARE. THE SOFTWARE.
""" """
# pylint: disable=unused-import # pylint: disable=unused-import
import copy
import threading
from typing import Dict, List, Set, Sequence, Tuple, Iterable # noqa: F401 from typing import Dict, List, Set, Sequence, Tuple, Iterable # noqa: F401
from typing import Callable, Optional, Union, Any # noqa: F401 from typing import Callable, Optional, Union, Any # noqa: F401
@ -59,7 +62,9 @@ class SSH2_KexDB: # pylint: disable=too-few-public-methods
WARN_TAG_SIZE_96 = 'using small 96-bit tag size' WARN_TAG_SIZE_96 = 'using small 96-bit tag size'
INFO_DEFAULT_OPENSSH_CIPHER = 'default cipher since OpenSSH 6.9' INFO_DEFAULT_OPENSSH_CIPHER = 'default cipher since OpenSSH 6.9'
INFO_DEFAULT_OPENSSH_KEX = 'default key exchange since OpenSSH 6.4' INFO_DEFAULT_OPENSSH_KEX_65_TO_73 = 'default key exchange from OpenSSH 6.5 to 7.3'
INFO_DEFAULT_OPENSSH_KEX_74_TO_89 = 'default key exchange from OpenSSH 7.4 to 8.9'
INFO_DEFAULT_OPENSSH_KEX_90 = 'default key exchange since OpenSSH 9.0'
INFO_DEPRECATED_IN_OPENSSH88 = 'deprecated in OpenSSH 8.8: https://www.openssh.com/txt/release-8.8' INFO_DEPRECATED_IN_OPENSSH88 = 'deprecated in OpenSSH 8.8: https://www.openssh.com/txt/release-8.8'
INFO_DISABLED_IN_DBEAR67 = 'disabled in Dropbear SSH 2015.67' INFO_DISABLED_IN_DBEAR67 = 'disabled in Dropbear SSH 2015.67'
INFO_DISABLED_IN_OPENSSH70 = 'disabled in OpenSSH 7.0: https://www.openssh.com/txt/release-7.0' INFO_DISABLED_IN_OPENSSH70 = 'disabled in OpenSSH 7.0: https://www.openssh.com/txt/release-7.0'
@ -68,15 +73,20 @@ class SSH2_KexDB: # pylint: disable=too-few-public-methods
INFO_REMOVED_IN_OPENSSH69 = 'removed in OpenSSH 6.9: https://www.openssh.com/txt/release-6.9' INFO_REMOVED_IN_OPENSSH69 = 'removed in OpenSSH 6.9: https://www.openssh.com/txt/release-6.9'
INFO_REMOVED_IN_OPENSSH70 = 'removed in OpenSSH 7.0: https://www.openssh.com/txt/release-7.0' INFO_REMOVED_IN_OPENSSH70 = 'removed in OpenSSH 7.0: https://www.openssh.com/txt/release-7.0'
INFO_WITHDRAWN_PQ_ALG = 'the sntrup4591761 algorithm was withdrawn, as it may not provide strong post-quantum security' INFO_WITHDRAWN_PQ_ALG = 'the sntrup4591761 algorithm was withdrawn, as it may not provide strong post-quantum security'
INFO_EXTENSION_NEGOTIATION = 'pseudo-algorithm that denotes the peer supports RFC8308 extensions'
INFO_STRICT_KEX = 'pseudo-algorithm that denotes the peer supports a stricter key exchange method as a counter-measure to the Terrapin attack (CVE-2023-48795)'
# Maintains a dictionary per calling thread that yields its own copy of MASTER_DB. This prevents results from one thread polluting the results of another thread.
DB_PER_THREAD: Dict[int, Dict[str, Dict[str, List[List[Optional[str]]]]]] = {}
ALGORITHMS: Dict[str, Dict[str, List[List[Optional[str]]]]] = { MASTER_DB: Dict[str, Dict[str, List[List[Optional[str]]]]] = {
# Format: 'algorithm_name': [['version_first_appeared_in'], [reason_for_failure1, reason_for_failure2, ...], [warning1, warning2, ...], [info1, info2, ...]] # Format: 'algorithm_name': [['version_first_appeared_in'], [reason_for_failure1, reason_for_failure2, ...], [warning1, warning2, ...], [info1, info2, ...]]
'kex': { 'kex': {
'Curve25519SHA256': [[]], 'Curve25519SHA256': [[]],
'curve25519-sha256': [['7.4,d2018.76'], [], [], [INFO_DEFAULT_OPENSSH_KEX]], 'curve25519-sha256': [['7.4,d2018.76'], [], [], [INFO_DEFAULT_OPENSSH_KEX_74_TO_89]],
'curve25519-sha256@libssh.org': [['6.4,d2013.62,l10.6.0'], [], [], [INFO_DEFAULT_OPENSSH_KEX]], 'curve25519-sha256@libssh.org': [['6.4,d2013.62,l10.6.0'], [], [], [INFO_DEFAULT_OPENSSH_KEX_65_TO_73]],
'curve448-sha512': [[]], 'curve448-sha512': [[]],
'curve448-sha512@libssh.org': [[]],
'diffie-hellman-group14-sha1': [['3.9,d0.53,l10.6.0'], [FAIL_SHA1], [WARN_2048BIT_MODULUS]], 'diffie-hellman-group14-sha1': [['3.9,d0.53,l10.6.0'], [FAIL_SHA1], [WARN_2048BIT_MODULUS]],
'diffie-hellman-group14-sha224@ssh.com': [[]], 'diffie-hellman-group14-sha224@ssh.com': [[]],
'diffie-hellman-group14-sha256': [['7.3,d2016.73'], [], [WARN_2048BIT_MODULUS]], 'diffie-hellman-group14-sha256': [['7.3,d2016.73'], [], [WARN_2048BIT_MODULUS]],
@ -101,50 +111,57 @@ class SSH2_KexDB: # pylint: disable=too-few-public-methods
'diffie-hellman-group-exchange-sha256@ssh.com': [[]], 'diffie-hellman-group-exchange-sha256@ssh.com': [[]],
'diffie-hellman-group-exchange-sha384@ssh.com': [[]], 'diffie-hellman-group-exchange-sha384@ssh.com': [[]],
'diffie-hellman-group-exchange-sha512@ssh.com': [[]], 'diffie-hellman-group-exchange-sha512@ssh.com': [[]],
'ecdh-nistp256-kyber-512r3-sha256-d00@openquantumsafe.org': [[], [FAIL_NSA_BACKDOORED_CURVE]],
'ecdh-nistp384-kyber-768r3-sha384-d00@openquantumsafe.org': [[], [FAIL_NSA_BACKDOORED_CURVE]],
'ecdh-nistp521-kyber-1024r3-sha512-d00@openquantumsafe.org': [[], [FAIL_NSA_BACKDOORED_CURVE]],
'ecdh-sha2-1.2.840.10045.3.1.1': [[], [FAIL_SMALL_ECC_MODULUS, FAIL_NSA_BACKDOORED_CURVE]], # NIST P-192 / secp192r1 'ecdh-sha2-1.2.840.10045.3.1.1': [[], [FAIL_SMALL_ECC_MODULUS, FAIL_NSA_BACKDOORED_CURVE]], # NIST P-192 / secp192r1
'ecdh-sha2-1.2.840.10045.3.1.7': [[], [FAIL_NSA_BACKDOORED_CURVE]], # NIST P-256 / secp256r1 'ecdh-sha2-1.2.840.10045.3.1.7': [[], [FAIL_NSA_BACKDOORED_CURVE]], # NIST P-256 / secp256r1
'ecdh-sha2-1.3.132.0.10': [[]], # ECDH over secp256k1 (i.e.: the Bitcoin curve) 'ecdh-sha2-1.3.132.0.10': [[]], # ECDH over secp256k1 (i.e.: the Bitcoin curve)
'ecdh-sha2-1.3.132.0.16': [[]], # sect283k1 'ecdh-sha2-1.3.132.0.16': [[], [FAIL_UNPROVEN]], # sect283k1
'ecdh-sha2-1.3.132.0.1': [[], [FAIL_SMALL_ECC_MODULUS]], # sect163k1 'ecdh-sha2-1.3.132.0.1': [[], [FAIL_UNPROVEN, FAIL_SMALL_ECC_MODULUS]], # sect163k1
'ecdh-sha2-1.3.132.0.26': [[], [FAIL_SMALL_ECC_MODULUS]], # sect233k1 'ecdh-sha2-1.3.132.0.26': [[], [FAIL_UNPROVEN, FAIL_SMALL_ECC_MODULUS]], # sect233k1
'ecdh-sha2-1.3.132.0.27': [[], [FAIL_SMALL_ECC_MODULUS, FAIL_NSA_BACKDOORED_CURVE]], # sect233r1 'ecdh-sha2-1.3.132.0.27': [[], [FAIL_SMALL_ECC_MODULUS, FAIL_NSA_BACKDOORED_CURVE]], # sect233r1
'ecdh-sha2-1.3.132.0.33': [[], [FAIL_SMALL_ECC_MODULUS, FAIL_NSA_BACKDOORED_CURVE]], # NIST P-224 / secp224r1 'ecdh-sha2-1.3.132.0.33': [[], [FAIL_SMALL_ECC_MODULUS, FAIL_NSA_BACKDOORED_CURVE]], # NIST P-224 / secp224r1
'ecdh-sha2-1.3.132.0.34': [[], [FAIL_NSA_BACKDOORED_CURVE]], # NIST P-384 / secp384r1 'ecdh-sha2-1.3.132.0.34': [[], [FAIL_NSA_BACKDOORED_CURVE]], # NIST P-384 / secp384r1
'ecdh-sha2-1.3.132.0.35': [[], [FAIL_NSA_BACKDOORED_CURVE]], # NIST P-521 / secp521r1 'ecdh-sha2-1.3.132.0.35': [[], [FAIL_NSA_BACKDOORED_CURVE]], # NIST P-521 / secp521r1
'ecdh-sha2-1.3.132.0.36': [[]], # sect409k1 'ecdh-sha2-1.3.132.0.36': [[], [FAIL_UNPROVEN]], # sect409k1
'ecdh-sha2-1.3.132.0.37': [[], [FAIL_NSA_BACKDOORED_CURVE]], # sect409r1 'ecdh-sha2-1.3.132.0.37': [[], [FAIL_NSA_BACKDOORED_CURVE]], # sect409r1
'ecdh-sha2-1.3.132.0.38': [[]], # sect571k1 'ecdh-sha2-1.3.132.0.38': [[], [FAIL_UNPROVEN]], # sect571k1
# Note: the base64 strings, according to draft 6 of RFC5656, is Base64(MD5(DER(OID))). The final RFC5656 dropped the base64 strings in favor of plain OID concatenation, but apparently some SSH servers implement them anyway. See: https://datatracker.ietf.org/doc/html/draft-green-secsh-ecc-06#section-9.2 # Note: the base64 strings, according to draft 6 of RFC5656, is Base64(MD5(DER(OID))). The final RFC5656 dropped the base64 strings in favor of plain OID concatenation, but apparently some SSH servers implement them anyway. See: https://datatracker.ietf.org/doc/html/draft-green-secsh-ecc-06#section-9.2
'ecdh-sha2-4MHB+NBt3AlaSRQ7MnB4cg==': [[], [FAIL_SMALL_ECC_MODULUS]], # sect163k1 'ecdh-sha2-4MHB+NBt3AlaSRQ7MnB4cg==': [[], [FAIL_UNPROVEN, FAIL_SMALL_ECC_MODULUS]], # sect163k1
'ecdh-sha2-5pPrSUQtIaTjUSt5VZNBjg==': [[], [FAIL_SMALL_ECC_MODULUS, FAIL_NSA_BACKDOORED_CURVE]], # NIST P-192 / secp192r1 'ecdh-sha2-5pPrSUQtIaTjUSt5VZNBjg==': [[], [FAIL_SMALL_ECC_MODULUS, FAIL_NSA_BACKDOORED_CURVE]], # NIST P-192 / secp192r1
'ecdh-sha2-9UzNcgwTlEnSCECZa7V1mw==': [[], [FAIL_NSA_BACKDOORED_CURVE]], # NIST P-256 / secp256r1 'ecdh-sha2-9UzNcgwTlEnSCECZa7V1mw==': [[], [FAIL_NSA_BACKDOORED_CURVE]], # NIST P-256 / secp256r1
'ecdh-sha2-brainpoolp256r1@genua.de': [[], [FAIL_UNPROVEN]],
'ecdh-sha2-brainpoolp384r1@genua.de': [[], [FAIL_UNPROVEN]],
'ecdh-sha2-brainpoolp521r1@genua.de': [[], [FAIL_UNPROVEN]],
'ecdh-sha2-curve25519': [[], []], 'ecdh-sha2-curve25519': [[], []],
'ecdh-sha2-D3FefCjYoJ/kfXgAyLddYA==': [[], [FAIL_NSA_BACKDOORED_CURVE]], # sect409r1 'ecdh-sha2-D3FefCjYoJ/kfXgAyLddYA==': [[], [FAIL_NSA_BACKDOORED_CURVE]], # sect409r1
'ecdh-sha2-h/SsxnLCtRBh7I9ATyeB3A==': [[], [FAIL_NSA_BACKDOORED_CURVE]], # NIST P-521 / secp521r1 'ecdh-sha2-h/SsxnLCtRBh7I9ATyeB3A==': [[], [FAIL_NSA_BACKDOORED_CURVE]], # NIST P-521 / secp521r1
'ecdh-sha2-m/FtSAmrV4j/Wy6RVUaK7A==': [[]], # sect409k1 'ecdh-sha2-m/FtSAmrV4j/Wy6RVUaK7A==': [[], [FAIL_UNPROVEN]], # sect409k1
'ecdh-sha2-mNVwCXAoS1HGmHpLvBC94w==': [[]], # sect571k1 'ecdh-sha2-mNVwCXAoS1HGmHpLvBC94w==': [[], [FAIL_UNPROVEN]], # sect571k1
'ecdh-sha2-nistb233': [[], [FAIL_UNPROVEN, FAIL_SMALL_ECC_MODULUS]],
'ecdh-sha2-nistb233': [[]], # The NIST P-curves are suspected as being backdoored; this isn't a P-curve. 'ecdh-sha2-nistb409': [[], [FAIL_UNPROVEN]],
'ecdh-sha2-nistb409': [[]], # Not a NIST P-curve. 'ecdh-sha2-nistk163': [[], [FAIL_UNPROVEN, FAIL_SMALL_ECC_MODULUS]],
'ecdh-sha2-nistk163': [[], [FAIL_SMALL_ECC_MODULUS]], # Not a NIST P-curve. 'ecdh-sha2-nistk233': [[], [FAIL_UNPROVEN, FAIL_SMALL_ECC_MODULUS]],
'ecdh-sha2-nistk233': [[]], # Not a NIST P-curve. 'ecdh-sha2-nistk283': [[], [FAIL_UNPROVEN]],
'ecdh-sha2-nistk283': [[]], # Not a NIST P-curve. 'ecdh-sha2-nistk409': [[], [FAIL_UNPROVEN]],
'ecdh-sha2-nistk409': [[]], # Not a NIST P-curve.
'ecdh-sha2-nistp192': [[], [FAIL_NSA_BACKDOORED_CURVE]], 'ecdh-sha2-nistp192': [[], [FAIL_NSA_BACKDOORED_CURVE]],
'ecdh-sha2-nistp224': [[], [FAIL_NSA_BACKDOORED_CURVE]], 'ecdh-sha2-nistp224': [[], [FAIL_NSA_BACKDOORED_CURVE]],
'ecdh-sha2-nistp256': [['5.7,d2013.62,l10.6.0'], [FAIL_NSA_BACKDOORED_CURVE]], 'ecdh-sha2-nistp256': [['5.7,d2013.62,l10.6.0'], [FAIL_NSA_BACKDOORED_CURVE]],
'ecdh-sha2-nistp384': [['5.7,d2013.62'], [FAIL_NSA_BACKDOORED_CURVE]], 'ecdh-sha2-nistp384': [['5.7,d2013.62'], [FAIL_NSA_BACKDOORED_CURVE]],
'ecdh-sha2-nistp521': [['5.7,d2013.62'], [FAIL_NSA_BACKDOORED_CURVE]], 'ecdh-sha2-nistp521': [['5.7,d2013.62'], [FAIL_NSA_BACKDOORED_CURVE]],
'ecdh-sha2-nistt571': [[]], # Not a NIST P-curve. 'ecdh-sha2-nistt571': [[], [FAIL_UNPROVEN]],
'ecdh-sha2-qCbG5Cn/jjsZ7nBeR7EnOA==': [[FAIL_SMALL_ECC_MODULUS, FAIL_NSA_BACKDOORED_CURVE]], # sect233r1 'ecdh-sha2-qCbG5Cn/jjsZ7nBeR7EnOA==': [[FAIL_SMALL_ECC_MODULUS, FAIL_NSA_BACKDOORED_CURVE]], # sect233r1
'ecdh-sha2-qcFQaMAMGhTziMT0z+Tuzw==': [[], [FAIL_NSA_BACKDOORED_CURVE]], # NIST P-384 / secp384r1 'ecdh-sha2-qcFQaMAMGhTziMT0z+Tuzw==': [[], [FAIL_NSA_BACKDOORED_CURVE]], # NIST P-384 / secp384r1
'ecdh-sha2-VqBg4QRPjxx1EXZdV0GdWQ==': [[], [FAIL_SMALL_ECC_MODULUS, FAIL_NSA_BACKDOORED_CURVE]], # NIST P-224 / secp224r1 'ecdh-sha2-VqBg4QRPjxx1EXZdV0GdWQ==': [[], [FAIL_NSA_BACKDOORED_CURVE, FAIL_SMALL_ECC_MODULUS]], # NIST P-224 / secp224r1
'ecdh-sha2-wiRIU8TKjMZ418sMqlqtvQ==': [[]], # sect283k1 'ecdh-sha2-wiRIU8TKjMZ418sMqlqtvQ==': [[], [FAIL_UNPROVEN]], # sect283k1
'ecdh-sha2-zD/b3hu/71952ArpUG4OjQ==': [[], [FAIL_SMALL_ECC_MODULUS]], # sect233k1 'ecdh-sha2-zD/b3hu/71952ArpUG4OjQ==': [[], [FAIL_UNPROVEN, FAIL_SMALL_ECC_MODULUS]], # sect233k1
'ecmqv-sha2': [[], [FAIL_UNPROVEN]], 'ecmqv-sha2': [[], [FAIL_UNPROVEN]],
'ext-info-c': [[]], # Extension negotiation (RFC 8308) 'ext-info-c': [[], [], [], [INFO_EXTENSION_NEGOTIATION]], # Extension negotiation (RFC 8308)
'ext-info-s': [[]], # Extension negotiation (RFC 8308) 'ext-info-s': [[], [], [], [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).
# The GSS kex algorithms get special wildcard handling, since they include variable base64 data after their standard prefixes. # The GSS kex algorithms get special wildcard handling, since they include variable base64 data after their standard prefixes.
'gss-13.3.132.0.10-sha256-*': [[], [FAIL_UNKNOWN]], 'gss-13.3.132.0.10-sha256-*': [[], [FAIL_UNKNOWN]],
@ -161,16 +178,24 @@ class SSH2_KexDB: # pylint: disable=too-few-public-methods
'gss-group1-sha1-*': [[], [FAIL_1024BIT_MODULUS, FAIL_LOGJAM_ATTACK, FAIL_SHA1]], 'gss-group1-sha1-*': [[], [FAIL_1024BIT_MODULUS, FAIL_LOGJAM_ATTACK, FAIL_SHA1]],
'gss-nistp256-sha256-*': [[], [FAIL_NSA_BACKDOORED_CURVE]], 'gss-nistp256-sha256-*': [[], [FAIL_NSA_BACKDOORED_CURVE]],
'gss-nistp384-sha256-*': [[], [FAIL_NSA_BACKDOORED_CURVE]], 'gss-nistp384-sha256-*': [[], [FAIL_NSA_BACKDOORED_CURVE]],
'gss-nistp384-sha384-*': [[], [FAIL_NSA_BACKDOORED_CURVE]],
'gss-nistp521-sha512-*': [[], [FAIL_NSA_BACKDOORED_CURVE]], 'gss-nistp521-sha512-*': [[], [FAIL_NSA_BACKDOORED_CURVE]],
'kexAlgoCurve25519SHA256': [[]], 'kexAlgoCurve25519SHA256': [[]],
'kexAlgoDH14SHA1': [[], [FAIL_SHA1], [WARN_2048BIT_MODULUS]],
'kexAlgoDH1SHA1': [[], [FAIL_1024BIT_MODULUS, FAIL_LOGJAM_ATTACK, FAIL_SHA1]],
'kexAlgoECDH256': [[], [FAIL_NSA_BACKDOORED_CURVE]],
'kexAlgoECDH384': [[], [FAIL_NSA_BACKDOORED_CURVE]],
'kexAlgoECDH521': [[], [FAIL_NSA_BACKDOORED_CURVE]],
'kexguess2@matt.ucc.asn.au': [['d2013.57']], 'kexguess2@matt.ucc.asn.au': [['d2013.57']],
'm383-sha384@libassh.org': [[], [FAIL_UNPROVEN]], 'm383-sha384@libassh.org': [[], [FAIL_UNPROVEN]],
'm511-sha512@libassh.org': [[], [FAIL_UNPROVEN]], 'm511-sha512@libassh.org': [[], [FAIL_UNPROVEN]],
'rsa1024-sha1': [[], [FAIL_1024BIT_MODULUS, FAIL_SHA1]], 'rsa1024-sha1': [[], [FAIL_1024BIT_MODULUS, FAIL_SHA1]],
'rsa2048-sha256': [[], [], [WARN_2048BIT_MODULUS]], 'rsa2048-sha256': [[], [], [WARN_2048BIT_MODULUS]],
'sm2kep-sha2-nistp256': [[], [FAIL_NSA_BACKDOORED_CURVE, FAIL_UNTRUSTED]],
'sntrup4591761x25519-sha512@tinyssh.org': [['8.0', '8.4'], [], [WARN_EXPERIMENTAL], [INFO_WITHDRAWN_PQ_ALG]], 'sntrup4591761x25519-sha512@tinyssh.org': [['8.0', '8.4'], [], [WARN_EXPERIMENTAL], [INFO_WITHDRAWN_PQ_ALG]],
'sntrup761x25519-sha512@openssh.com': [['8.5'], [], []], 'sntrup761x25519-sha512@openssh.com': [['8.5'], [], [], [INFO_DEFAULT_OPENSSH_KEX_90]],
'x25519-kyber-512r3-sha256-d00@amazon.com': [[]],
'x25519-kyber512-sha512@aws.amazon.com': [[]],
}, },
'key': { 'key': {
'dsa2048-sha224@libassh.org': [[], [FAIL_UNPROVEN], [WARN_2048BIT_MODULUS]], 'dsa2048-sha224@libassh.org': [[], [FAIL_UNPROVEN], [WARN_2048BIT_MODULUS]],
@ -178,18 +203,28 @@ class SSH2_KexDB: # pylint: disable=too-few-public-methods
'dsa3072-sha256@libassh.org': [[], [FAIL_UNPROVEN]], 'dsa3072-sha256@libassh.org': [[], [FAIL_UNPROVEN]],
'ecdsa-sha2-1.3.132.0.10-cert-v01@openssh.com': [[], [FAIL_UNKNOWN]], 'ecdsa-sha2-1.3.132.0.10-cert-v01@openssh.com': [[], [FAIL_UNKNOWN]],
'ecdsa-sha2-1.3.132.0.10': [[], [], [WARN_RNDSIG_KEY]], # ECDSA over secp256k1 (i.e.: the Bitcoin curve) 'ecdsa-sha2-1.3.132.0.10': [[], [], [WARN_RNDSIG_KEY]], # ECDSA over secp256k1 (i.e.: the Bitcoin curve)
'ecdsa-sha2-curve25519': [[], [], [WARN_RNDSIG_KEY]], # ECDSA with Curve25519? Bizarre...
'ecdsa-sha2-nistb233': [[], [FAIL_UNPROVEN, FAIL_SMALL_ECC_MODULUS], [WARN_RNDSIG_KEY]],
'ecdsa-sha2-nistb409': [[], [FAIL_UNPROVEN], [WARN_RNDSIG_KEY]],
'ecdsa-sha2-nistk163': [[], [FAIL_UNPROVEN, FAIL_SMALL_ECC_MODULUS], [WARN_RNDSIG_KEY]],
'ecdsa-sha2-nistk233': [[], [FAIL_UNPROVEN, FAIL_SMALL_ECC_MODULUS], [WARN_RNDSIG_KEY]],
'ecdsa-sha2-nistk283': [[], [FAIL_UNPROVEN], [WARN_RNDSIG_KEY]],
'ecdsa-sha2-nistk409': [[], [FAIL_UNPROVEN], [WARN_RNDSIG_KEY]],
'ecdsa-sha2-nistp224': [[], [FAIL_NSA_BACKDOORED_CURVE, FAIL_SMALL_ECC_MODULUS], [WARN_RNDSIG_KEY]],
'ecdsa-sha2-nistp192': [[], [FAIL_NSA_BACKDOORED_CURVE, FAIL_SMALL_ECC_MODULUS], [WARN_RNDSIG_KEY]],
'ecdsa-sha2-nistp256': [['5.7,d2013.62,l10.6.4'], [FAIL_NSA_BACKDOORED_CURVE], [WARN_RNDSIG_KEY]], 'ecdsa-sha2-nistp256': [['5.7,d2013.62,l10.6.4'], [FAIL_NSA_BACKDOORED_CURVE], [WARN_RNDSIG_KEY]],
'ecdsa-sha2-nistp256-cert-v01@openssh.com': [['5.7'], [FAIL_NSA_BACKDOORED_CURVE], [WARN_RNDSIG_KEY]], 'ecdsa-sha2-nistp256-cert-v01@openssh.com': [['5.7'], [FAIL_NSA_BACKDOORED_CURVE], [WARN_RNDSIG_KEY]],
'ecdsa-sha2-nistp384': [['5.7,d2013.62,l10.6.4'], [FAIL_NSA_BACKDOORED_CURVE], [WARN_RNDSIG_KEY]], 'ecdsa-sha2-nistp384': [['5.7,d2013.62,l10.6.4'], [FAIL_NSA_BACKDOORED_CURVE], [WARN_RNDSIG_KEY]],
'ecdsa-sha2-nistp384-cert-v01@openssh.com': [['5.7'], [FAIL_NSA_BACKDOORED_CURVE], [WARN_RNDSIG_KEY]], 'ecdsa-sha2-nistp384-cert-v01@openssh.com': [['5.7'], [FAIL_NSA_BACKDOORED_CURVE], [WARN_RNDSIG_KEY]],
'ecdsa-sha2-nistp521': [['5.7,d2013.62,l10.6.4'], [FAIL_NSA_BACKDOORED_CURVE], [WARN_RNDSIG_KEY]], 'ecdsa-sha2-nistp521': [['5.7,d2013.62,l10.6.4'], [FAIL_NSA_BACKDOORED_CURVE], [WARN_RNDSIG_KEY]],
'ecdsa-sha2-nistp521-cert-v01@openssh.com': [['5.7'], [FAIL_NSA_BACKDOORED_CURVE], [WARN_RNDSIG_KEY]], 'ecdsa-sha2-nistp521-cert-v01@openssh.com': [['5.7'], [FAIL_NSA_BACKDOORED_CURVE], [WARN_RNDSIG_KEY]],
'ecdsa-sha2-nistt571': [[], [FAIL_UNPROVEN], [WARN_RNDSIG_KEY]],
'eddsa-e382-shake256@libassh.org': [[], [FAIL_UNPROVEN]], 'eddsa-e382-shake256@libassh.org': [[], [FAIL_UNPROVEN]],
'eddsa-e521-shake256@libassh.org': [[], [FAIL_UNPROVEN]], 'eddsa-e521-shake256@libassh.org': [[], [FAIL_UNPROVEN]],
'null': [[], [FAIL_PLAINTEXT]], 'null': [[], [FAIL_PLAINTEXT]],
'pgp-sign-dss': [[], [FAIL_1024BIT_MODULUS]], 'pgp-sign-dss': [[], [FAIL_1024BIT_MODULUS]],
'pgp-sign-rsa': [[], [FAIL_1024BIT_MODULUS]], 'pgp-sign-rsa': [[], [FAIL_1024BIT_MODULUS]],
'rsa-sha2-256': [['7.2']], 'rsa-sha2-256': [['7.2,d2020.79']],
'rsa-sha2-256-cert-v01@openssh.com': [['7.8']], 'rsa-sha2-256-cert-v01@openssh.com': [['7.8']],
'rsa-sha2-512': [['7.2']], 'rsa-sha2-512': [['7.2']],
'rsa-sha2-512-cert-v01@openssh.com': [['7.8']], 'rsa-sha2-512-cert-v01@openssh.com': [['7.8']],
@ -200,6 +235,7 @@ class SSH2_KexDB: # pylint: disable=too-few-public-methods
'spi-sign-rsa': [[]], 'spi-sign-rsa': [[]],
'spki-sign-dss': [[], [FAIL_1024BIT_MODULUS]], 'spki-sign-dss': [[], [FAIL_1024BIT_MODULUS]],
'spki-sign-rsa': [[], [FAIL_1024BIT_MODULUS]], 'spki-sign-rsa': [[], [FAIL_1024BIT_MODULUS]],
'ssh-dsa': [[], [FAIL_1024BIT_MODULUS], [WARN_RNDSIG_KEY]],
'ssh-dss': [['2.1.0,d0.28,l10.2', '6.9'], [FAIL_1024BIT_MODULUS], [WARN_RNDSIG_KEY], [INFO_DISABLED_IN_OPENSSH70]], 'ssh-dss': [['2.1.0,d0.28,l10.2', '6.9'], [FAIL_1024BIT_MODULUS], [WARN_RNDSIG_KEY], [INFO_DISABLED_IN_OPENSSH70]],
'ssh-dss-cert-v00@openssh.com': [['5.4', '6.9'], [FAIL_1024BIT_MODULUS], [WARN_RNDSIG_KEY], [INFO_DISABLED_IN_OPENSSH70]], 'ssh-dss-cert-v00@openssh.com': [['5.4', '6.9'], [FAIL_1024BIT_MODULUS], [WARN_RNDSIG_KEY], [INFO_DISABLED_IN_OPENSSH70]],
'ssh-dss-cert-v01@openssh.com': [['5.6', '6.9'], [FAIL_1024BIT_MODULUS], [WARN_RNDSIG_KEY]], 'ssh-dss-cert-v01@openssh.com': [['5.6', '6.9'], [FAIL_1024BIT_MODULUS], [WARN_RNDSIG_KEY]],
@ -207,7 +243,7 @@ class SSH2_KexDB: # pylint: disable=too-few-public-methods
'ssh-dss-sha256@ssh.com': [[], [FAIL_1024BIT_MODULUS]], 'ssh-dss-sha256@ssh.com': [[], [FAIL_1024BIT_MODULUS]],
'ssh-dss-sha384@ssh.com': [[], [FAIL_1024BIT_MODULUS]], 'ssh-dss-sha384@ssh.com': [[], [FAIL_1024BIT_MODULUS]],
'ssh-dss-sha512@ssh.com': [[], [FAIL_1024BIT_MODULUS]], 'ssh-dss-sha512@ssh.com': [[], [FAIL_1024BIT_MODULUS]],
'ssh-ed25519': [['6.5,l10.7.0']], 'ssh-ed25519': [['6.5,d2020.79,l10.7.0']],
'ssh-ed25519-cert-v01@openssh.com': [['6.5']], 'ssh-ed25519-cert-v01@openssh.com': [['6.5']],
'ssh-ed448': [[]], 'ssh-ed448': [[]],
'ssh-ed448-cert-v01@openssh.com': [[], [], [], [INFO_NEVER_IMPLEMENTED_IN_OPENSSH]], 'ssh-ed448-cert-v01@openssh.com': [[], [], [], [INFO_NEVER_IMPLEMENTED_IN_OPENSSH]],
@ -241,6 +277,7 @@ class SSH2_KexDB: # pylint: disable=too-few-public-methods
'x509v3-sign-rsa': [[], [FAIL_SHA1]], 'x509v3-sign-rsa': [[], [FAIL_SHA1]],
'x509v3-sign-rsa-sha1': [[], [FAIL_SHA1]], 'x509v3-sign-rsa-sha1': [[], [FAIL_SHA1]],
'x509v3-sign-rsa-sha224@ssh.com': [[]], 'x509v3-sign-rsa-sha224@ssh.com': [[]],
'x509v3-sign-rsa-sha256': [[]],
'x509v3-sign-rsa-sha256@ssh.com': [[]], 'x509v3-sign-rsa-sha256@ssh.com': [[]],
'x509v3-sign-rsa-sha384@ssh.com': [[]], 'x509v3-sign-rsa-sha384@ssh.com': [[]],
'x509v3-sign-rsa-sha512@ssh.com': [[]], 'x509v3-sign-rsa-sha512@ssh.com': [[]],
@ -260,8 +297,10 @@ class SSH2_KexDB: # pylint: disable=too-few-public-methods
'aes128-ctr': [['3.7,d0.52,l10.4.1']], 'aes128-ctr': [['3.7,d0.52,l10.4.1']],
'aes128-gcm': [[]], 'aes128-gcm': [[]],
'aes128-gcm@openssh.com': [['6.2']], 'aes128-gcm@openssh.com': [['6.2']],
'aes128-ocb@libassh.org': [[], [], [WARN_CIPHER_MODE]],
'aes192-cbc': [['2.3.0,l10.2', '6.6', None], [], [WARN_CIPHER_MODE]], 'aes192-cbc': [['2.3.0,l10.2', '6.6', None], [], [WARN_CIPHER_MODE]],
'aes192-ctr': [['3.7,l10.4.1']], 'aes192-ctr': [['3.7,l10.4.1']],
'aes192-gcm@openssh.com': [[], [], [], [INFO_NEVER_IMPLEMENTED_IN_OPENSSH]],
'aes256-cbc': [['2.3.0,d0.47,l10.2', '6.6', None], [], [WARN_CIPHER_MODE]], 'aes256-cbc': [['2.3.0,d0.47,l10.2', '6.6', None], [], [WARN_CIPHER_MODE]],
'aes256-ctr': [['3.7,d0.52,l10.4.1']], 'aes256-ctr': [['3.7,d0.52,l10.4.1']],
'aes256-gcm': [[]], 'aes256-gcm': [[]],
@ -289,14 +328,21 @@ class SSH2_KexDB: # pylint: disable=too-few-public-methods
'camellia256-ctr@openssh.org': [[]], 'camellia256-ctr@openssh.org': [[]],
'cast128-12-cbc@ssh.com': [[], [FAIL_CAST], [WARN_CIPHER_MODE]], 'cast128-12-cbc@ssh.com': [[], [FAIL_CAST], [WARN_CIPHER_MODE]],
'cast128-cbc': [['2.1.0', '6.6', '7.1'], [FAIL_CAST], [WARN_CIPHER_MODE, WARN_BLOCK_SIZE]], 'cast128-cbc': [['2.1.0', '6.6', '7.1'], [FAIL_CAST], [WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
'cast128-12-cbc': [[], [FAIL_CAST], [WARN_CIPHER_MODE]],
'cast128-12-cfb': [[], [FAIL_CAST], [WARN_CIPHER_MODE]],
'cast128-12-ecb': [[], [FAIL_CAST], [WARN_CIPHER_MODE]],
'cast128-12-ofb': [[], [FAIL_CAST], [WARN_CIPHER_MODE]],
'cast128-cfb': [[], [FAIL_CAST], [WARN_CIPHER_MODE]], 'cast128-cfb': [[], [FAIL_CAST], [WARN_CIPHER_MODE]],
'cast128-ctr': [[], [FAIL_CAST]], 'cast128-ctr': [[], [FAIL_CAST]],
'cast128-ecb': [[], [FAIL_CAST], [WARN_CIPHER_MODE]], 'cast128-ecb': [[], [FAIL_CAST], [WARN_CIPHER_MODE]],
'cast128-ofb': [[], [FAIL_CAST], [WARN_CIPHER_MODE]], 'cast128-ofb': [[], [FAIL_CAST], [WARN_CIPHER_MODE]],
'chacha20-poly1305': [[], [], [], [INFO_DEFAULT_OPENSSH_CIPHER]], 'chacha20-poly1305': [[], [], [], [INFO_DEFAULT_OPENSSH_CIPHER]],
'chacha20-poly1305@openssh.com': [['6.5'], [], [], [INFO_DEFAULT_OPENSSH_CIPHER]], 'chacha20-poly1305@openssh.com': [['6.5,d2020.79'], [], [], [INFO_DEFAULT_OPENSSH_CIPHER]],
'crypticore128@ssh.com': [[], [FAIL_UNPROVEN]], 'crypticore128@ssh.com': [[], [FAIL_UNPROVEN]],
'des-cbc': [[], [FAIL_DES], [WARN_CIPHER_MODE, WARN_BLOCK_SIZE]], 'des-cbc': [[], [FAIL_DES], [WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
'des-cfb': [[], [FAIL_DES], [WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
'des-ecb': [[], [FAIL_DES], [WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
'des-ofb': [[], [FAIL_DES], [WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
'des-cbc-ssh1': [[], [FAIL_DES], [WARN_CIPHER_MODE, WARN_BLOCK_SIZE]], 'des-cbc-ssh1': [[], [FAIL_DES], [WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
'des-cbc@ssh.com': [[], [FAIL_DES], [WARN_CIPHER_MODE, WARN_BLOCK_SIZE]], 'des-cbc@ssh.com': [[], [FAIL_DES], [WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
'des': [[], [FAIL_DES], [WARN_CIPHER_MODE, WARN_BLOCK_SIZE]], 'des': [[], [FAIL_DES], [WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
@ -340,6 +386,12 @@ class SSH2_KexDB: # pylint: disable=too-few-public-methods
'AEAD_AES_256_GCM': [[]], 'AEAD_AES_256_GCM': [[]],
'aes128-gcm': [[]], 'aes128-gcm': [[]],
'aes256-gcm': [[]], 'aes256-gcm': [[]],
'cbcmac-3des': [[], [FAIL_UNPROVEN, FAIL_3DES]],
'cbcmac-aes': [[], [FAIL_UNPROVEN]],
'cbcmac-blowfish': [[], [FAIL_UNPROVEN, FAIL_BLOWFISH]],
'cbcmac-des': [[], [FAIL_UNPROVEN, FAIL_DES]],
'cbcmac-rijndael': [[], [FAIL_UNPROVEN, FAIL_RIJNDAEL]],
'cbcmac-twofish': [[], [FAIL_UNPROVEN]],
'chacha20-poly1305@openssh.com': [[], [], [], [INFO_NEVER_IMPLEMENTED_IN_OPENSSH]], # Despite the @openssh.com tag, this was never shipped as a MAC in OpenSSH (only as a cipher); it is only implemented as a MAC in Syncplify. 'chacha20-poly1305@openssh.com': [[], [], [], [INFO_NEVER_IMPLEMENTED_IN_OPENSSH]], # Despite the @openssh.com tag, this was never shipped as a MAC in OpenSSH (only as a cipher); it is only implemented as a MAC in Syncplify.
'crypticore-mac@ssh.com': [[], [FAIL_UNPROVEN]], 'crypticore-mac@ssh.com': [[], [FAIL_UNPROVEN]],
'hmac-md5': [['2.1.0,d0.28', '6.6', '7.1'], [FAIL_MD5], [WARN_ENCRYPT_AND_MAC]], 'hmac-md5': [['2.1.0,d0.28', '6.6', '7.1'], [FAIL_MD5], [WARN_ENCRYPT_AND_MAC]],
@ -369,6 +421,7 @@ class SSH2_KexDB: # pylint: disable=too-few-public-methods
'hmac-sha2-512-etm@openssh.com': [['6.2']], 'hmac-sha2-512-etm@openssh.com': [['6.2']],
'hmac-sha256-2@ssh.com': [[], [], [WARN_ENCRYPT_AND_MAC]], 'hmac-sha256-2@ssh.com': [[], [], [WARN_ENCRYPT_AND_MAC]],
'hmac-sha256-96@ssh.com': [[], [], [WARN_ENCRYPT_AND_MAC, WARN_TAG_SIZE]], 'hmac-sha256-96@ssh.com': [[], [], [WARN_ENCRYPT_AND_MAC, WARN_TAG_SIZE]],
'hmac-sha256-96': [[], [], [WARN_ENCRYPT_AND_MAC, WARN_TAG_SIZE]],
'hmac-sha256@ssh.com': [[], [], [WARN_ENCRYPT_AND_MAC]], 'hmac-sha256@ssh.com': [[], [], [WARN_ENCRYPT_AND_MAC]],
'hmac-sha256': [[], [], [WARN_ENCRYPT_AND_MAC]], 'hmac-sha256': [[], [], [WARN_ENCRYPT_AND_MAC]],
'hmac-sha2-56': [[], [], [WARN_TAG_SIZE, WARN_ENCRYPT_AND_MAC]], 'hmac-sha2-56': [[], [], [WARN_TAG_SIZE, WARN_ENCRYPT_AND_MAC]],
@ -380,7 +433,14 @@ class SSH2_KexDB: # pylint: disable=too-few-public-methods
'hmac-sha512@ssh.com': [[], [], [WARN_ENCRYPT_AND_MAC]], 'hmac-sha512@ssh.com': [[], [], [WARN_ENCRYPT_AND_MAC]],
'hmac-sha512': [[], [], [WARN_ENCRYPT_AND_MAC]], 'hmac-sha512': [[], [], [WARN_ENCRYPT_AND_MAC]],
'hmac-whirlpool': [[], [], [WARN_ENCRYPT_AND_MAC]], 'hmac-whirlpool': [[], [], [WARN_ENCRYPT_AND_MAC]],
'md5': [[], [FAIL_PLAINTEXT]],
'md5-8': [[], [FAIL_PLAINTEXT]],
'none': [['d2013.56'], [FAIL_PLAINTEXT]], 'none': [['d2013.56'], [FAIL_PLAINTEXT]],
'ripemd160': [[], [FAIL_PLAINTEXT]],
'ripemd160-8': [[], [FAIL_PLAINTEXT]],
'sha1': [[], [FAIL_PLAINTEXT]],
'sha1-8': [[], [FAIL_PLAINTEXT]],
'umac-128': [[], [], [WARN_ENCRYPT_AND_MAC]],
'umac-128-etm@openssh.com': [['6.2']], 'umac-128-etm@openssh.com': [['6.2']],
'umac-128@openssh.com': [['6.2'], [], [WARN_ENCRYPT_AND_MAC]], 'umac-128@openssh.com': [['6.2'], [], [WARN_ENCRYPT_AND_MAC]],
'umac-32@openssh.com': [[], [], [WARN_ENCRYPT_AND_MAC, WARN_TAG_SIZE], [INFO_NEVER_IMPLEMENTED_IN_OPENSSH]], 'umac-32@openssh.com': [[], [], [WARN_ENCRYPT_AND_MAC, WARN_TAG_SIZE], [INFO_NEVER_IMPLEMENTED_IN_OPENSSH]],
@ -389,3 +449,24 @@ class SSH2_KexDB: # pylint: disable=too-few-public-methods
'umac-96@openssh.com': [[], [], [WARN_ENCRYPT_AND_MAC], [INFO_NEVER_IMPLEMENTED_IN_OPENSSH]], 'umac-96@openssh.com': [[], [], [WARN_ENCRYPT_AND_MAC], [INFO_NEVER_IMPLEMENTED_IN_OPENSSH]],
} }
} }
@staticmethod
def get_db() -> Dict[str, Dict[str, List[List[Optional[str]]]]]:
'''Returns a copy of the MASTER_DB that is private to the calling thread. This prevents multiple threads from polluting the results of other threads.'''
calling_thread_id = threading.get_ident()
if calling_thread_id not in SSH2_KexDB.DB_PER_THREAD:
SSH2_KexDB.DB_PER_THREAD[calling_thread_id] = copy.deepcopy(SSH2_KexDB.MASTER_DB)
return SSH2_KexDB.DB_PER_THREAD[calling_thread_id]
@staticmethod
def thread_exit() -> None:
'''Deletes the calling thread's copy of the MASTER_DB. This is needed because, in rare circumstances, a terminated thread's ID can be re-used by new threads.'''
calling_thread_id = threading.get_ident()
if calling_thread_id in SSH2_KexDB.DB_PER_THREAD:
del SSH2_KexDB.DB_PER_THREAD[calling_thread_id]

View File

@ -27,6 +27,7 @@ import concurrent.futures
import copy import copy
import getopt import getopt
import json import json
import multiprocessing
import os import os
import re import re
import sys import sys
@ -39,11 +40,12 @@ from typing import cast, Callable, Optional, Union, Any # noqa: F401
from ssh_audit.globals import SNAP_PACKAGE from ssh_audit.globals import SNAP_PACKAGE
from ssh_audit.globals import SNAP_PERMISSIONS_ERROR from ssh_audit.globals import SNAP_PERMISSIONS_ERROR
from ssh_audit.globals import VERSION from ssh_audit.globals import VERSION
from ssh_audit.globals import WINDOWS_MAN_PAGE from ssh_audit.globals import BUILTIN_MAN_PAGE
from ssh_audit.algorithm import Algorithm from ssh_audit.algorithm import Algorithm
from ssh_audit.algorithms import Algorithms from ssh_audit.algorithms import Algorithms
from ssh_audit.auditconf import AuditConf from ssh_audit.auditconf import AuditConf
from ssh_audit.banner import Banner from ssh_audit.banner import Banner
from ssh_audit.dheat import DHEat
from ssh_audit import exitcodes from ssh_audit import exitcodes
from ssh_audit.fingerprint import Fingerprint from ssh_audit.fingerprint import Fingerprint
from ssh_audit.gextest import GEXTest from ssh_audit.gextest import GEXTest
@ -96,19 +98,37 @@ def usage(uout: OutputBuffer, err: Optional[str] = None) -> None:
uout.info(' -6, --ipv6 enable IPv6 (order of precedence)') uout.info(' -6, --ipv6 enable IPv6 (order of precedence)')
uout.info(' -b, --batch batch output') uout.info(' -b, --batch batch output')
uout.info(' -c, --client-audit starts a server on port 2222 to audit client\n software config (use -p to change port;\n use -t to change timeout)') uout.info(' -c, --client-audit starts a server on port 2222 to audit client\n software config (use -p to change port;\n use -t to change timeout)')
uout.info(' --conn-rate-test=N[:max_rate] perform a connection rate test (useful')
uout.info(' for collecting metrics related to')
uout.info(' susceptibility of the DHEat vuln).')
uout.info(' Testing is conducted with N concurrent')
uout.info(' sockets with an optional maximum rate')
uout.info(' of connections per second.')
uout.info(' -d, --debug debug output') uout.info(' -d, --debug debug output')
uout.info(' --dheat=N[:kex[:e_len]] continuously perform the DHEat DoS attack')
uout.info(' (CVE-2002-20001) against the target using N')
uout.info(' concurrent sockets. Optionally, a specific')
uout.info(' key exchange algorithm can be specified')
uout.info(' instead of allowing it to be automatically')
uout.info(' chosen. Additionally, a small length of')
uout.info(' the fake e value sent to the server can')
uout.info(' be chosen for a more efficient attack (such')
uout.info(' as 4).')
uout.info(' -g, --gex-test=<x[,y,...]> dh gex modulus size test') uout.info(' -g, --gex-test=<x[,y,...]> dh gex modulus size test')
uout.info(' <min1:pref1:max1[,min2:pref2:max2,...]>') uout.info(' <min1:pref1:max1[,min2:pref2:max2,...]>')
uout.info(' <x-y[:step]>') uout.info(' <x-y[:step]>')
uout.info(' -j, --json JSON output (use -jj to enable indents)') uout.info(' -j, --json JSON output (use -jj to enable indents)')
uout.info(' -l, --level=<level> minimum output level (info|warn|fail)') uout.info(' -l, --level=<level> minimum output level (info|warn|fail)')
uout.info(' -L, --list-policies list all the official, built-in policies') uout.info(' -L, --list-policies list all the official, built-in policies. Use with -v')
uout.info(' to view policy change logs.')
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(' --skip-rate-test skip the connection rate test during standard audits\n (used to safely infer whether the DHEat attack\n is viable)')
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)')
uout.info(' -T, --targets=<hosts.txt> a file containing a list of target hosts (one\n per line, format HOST[:PORT]). Use --threads\n to control concurrent scans.') uout.info(' -T, --targets=<hosts.txt> a file containing a list of target hosts (one\n per line, format HOST[:PORT]). Use --threads\n to control concurrent scans.')
uout.info(' --threads=<threads> number of threads to use when scanning multiple\n targets (-T/--targets) (default: 32)') uout.info(' --threads=<threads> number of threads to use when scanning multiple\n targets (-T/--targets) (default: 32)')
@ -195,6 +215,7 @@ def output_algorithm(out: OutputBuffer, alg_db: Dict[str, Dict[str, List[List[Op
alg_name = alg_name_with_size if alg_name_with_size is not None else alg_name alg_name = alg_name_with_size if alg_name_with_size is not None else alg_name
first = True first = True
use_good_for_all = False
for level, text in texts: for level, text in texts:
if level == 'fail': if level == 'fail':
program_retval = exitcodes.FAILURE program_retval = exitcodes.FAILURE
@ -203,9 +224,13 @@ def output_algorithm(out: OutputBuffer, alg_db: Dict[str, Dict[str, List[List[Op
f = getattr(out, level) f = getattr(out, level)
comment = (padding + ' -- [' + level + '] ' + text) if text != '' else '' comment = (padding + ' -- [' + level + '] ' + text) if text != '' else ''
if first:
if first and level == 'info': # If the first algorithm's comment is an 'info', this implies that it is rated good. Hence, the out.good() function should be used to write all subsequent notes for this algorithm as well.
if (first and level == 'info') or use_good_for_all:
f = out.good f = out.good
use_good_for_all = True
if first:
f(prefix + alg_name + comment) f(prefix + alg_name + comment)
first = False first = False
else: # pylint: disable=else-if-used else: # pylint: disable=else-if-used
@ -359,11 +384,8 @@ def output_recommendations(out: OutputBuffer, algs: Algorithms, algorithm_recomm
for cve_list in VersionVulnerabilityDB.CVE['PuTTY']: for cve_list in VersionVulnerabilityDB.CVE['PuTTY']:
vuln_version = float(cve_list[1]) vuln_version = float(cve_list[1])
cvssv2_severity = cve_list[4] cvssv2_severity = cve_list[4]
max_vuln_version = max(vuln_version, max_vuln_version)
if vuln_version > max_vuln_version: max_cvssv2_severity = max(cvssv2_severity, max_cvssv2_severity)
max_vuln_version = vuln_version
if cvssv2_severity > max_cvssv2_severity:
max_cvssv2_severity = cvssv2_severity
fn = out.warn fn = out.warn
if max_cvssv2_severity > 8.0: if max_cvssv2_severity > 8.0:
@ -379,6 +401,12 @@ def output_recommendations(out: OutputBuffer, algs: Algorithms, algorithm_recomm
ret = False ret = False
return ret return ret
level_to_output = {
"informational": out.good,
"warning": out.warn,
"critical": out.fail
}
with out: with out:
recommendations = get_algorithm_recommendations(algs, algorithm_recommendation_suppress_list, software, for_server=True) recommendations = get_algorithm_recommendations(algs, algorithm_recommendation_suppress_list, software, for_server=True)
@ -391,15 +419,15 @@ def output_recommendations(out: OutputBuffer, algs: Algorithms, algorithm_recomm
p = '' if out.batch else ' ' * (padlen - len(name)) p = '' if out.batch else ' ' * (padlen - len(name))
fn = level_to_output[level]
if action == 'del': if action == 'del':
an, sg, fn = 'remove', '-', out.warn an, sg = 'remove', '-'
ret = False ret = False
if level == 'critical':
fn = out.fail
elif action == 'add': elif action == 'add':
an, sg, fn = 'append', '+', out.good an, sg = 'append', '+'
elif action == 'chg': elif action == 'chg':
an, sg, fn = 'change', '!', out.fail an, sg = 'change', '!'
ret = False ret = False
if notes != '': if notes != '':
@ -420,7 +448,7 @@ def output_recommendations(out: OutputBuffer, algs: Algorithms, algorithm_recomm
# Output additional information & notes. # Output additional information & notes.
def output_info(out: OutputBuffer, software: Optional['Software'], client_audit: bool, any_problems: bool, is_json_output: bool) -> None: def output_info(out: OutputBuffer, software: Optional['Software'], client_audit: bool, any_problems: bool, is_json_output: bool, additional_notes: List[str]) -> None:
with out: with out:
# Tell user that PuTTY cannot be hardened at the protocol-level. # Tell user that PuTTY cannot be hardened at the protocol-level.
if client_audit and (software is not None) and (software.product == Product.PuTTY): if client_audit and (software is not None) and (software.product == Product.PuTTY):
@ -430,35 +458,175 @@ def output_info(out: OutputBuffer, software: Optional['Software'], client_audit:
if any_problems: if any_problems:
out.warn('(nfo) For hardening guides on common OSes, please see: <https://www.ssh-audit.com/hardening_guides.html>') out.warn('(nfo) For hardening guides on common OSes, please see: <https://www.ssh-audit.com/hardening_guides.html>')
# Add any additional notes.
for additional_note in additional_notes:
if len(additional_note) > 0:
out.warn("(nfo) %s" % additional_note)
if not out.is_section_empty() and not is_json_output: if not out.is_section_empty() and not is_json_output:
out.head('# additional info') out.head('# additional info')
out.flush_section() out.flush_section()
out.sep() out.sep()
def post_process_findings(banner: Optional[Banner], algs: Algorithms) -> List[str]: def post_process_findings(banner: Optional[Banner], algs: Algorithms, client_audit: bool, dh_rate_test_notes: str) -> Tuple[List[str], List[str]]:
'''Perform post-processing on scan results before reporting them to the user. Returns a list of algorithms that should not be recommended''' '''Perform post-processing on scan results before reporting them to the user. Returns a list of algorithms that should not be recommended and a list of notes.'''
def _add_terrapin_warning(db: Dict[str, Dict[str, List[List[Optional[str]]]]], category: str, algorithm_name: str) -> None:
'''Adds a warning regarding the Terrapin vulnerability for the specified algorithm.'''
# Ensure that a slot for warnings exists for this algorithm.
while len(db[category][algorithm_name]) < 3:
db[category][algorithm_name].append([])
db[category][algorithm_name][2].append("vulnerable to the Terrapin attack (CVE-2023-48795), allowing message prefix truncation")
def _get_chacha_ciphers_enabled(algs: Algorithms) -> List[str]:
'''Returns a list of chacha20-poly1305 ciphers that the peer supports.'''
ret = []
if algs.ssh2kex is not None:
ciphers_supported = algs.ssh2kex.client.encryption if client_audit else algs.ssh2kex.server.encryption
for cipher in ciphers_supported:
if cipher.startswith("chacha20-poly1305"):
ret.append(cipher)
return ret
def _get_chacha_ciphers_not_enabled(db: Dict[str, Dict[str, List[List[Optional[str]]]]], algs: Algorithms) -> List[str]:
'''Returns a list of all chacha20-poly1305 in our algorithm database.'''
ret = []
for cipher in db["enc"]:
if cipher.startswith("chacha20-poly1305") and cipher not in _get_chacha_ciphers_enabled(algs):
ret.append(cipher)
return ret
def _get_cbc_ciphers_enabled(algs: Algorithms) -> List[str]:
'''Returns a list of CBC ciphers that the peer supports.'''
ret = []
if algs.ssh2kex is not None:
ciphers_supported = algs.ssh2kex.client.encryption if client_audit else algs.ssh2kex.server.encryption
for cipher in ciphers_supported:
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)
return ret
def _get_cbc_ciphers_not_enabled(db: Dict[str, Dict[str, List[List[Optional[str]]]]], algs: Algorithms) -> List[str]:
'''Returns a list of all CBC ciphers in our algorithm database.'''
ret = []
for cipher in db["enc"]:
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)
return ret
def _get_etm_macs_enabled(algs: Algorithms) -> List[str]:
'''Returns a list of ETM MACs that the peer supports.'''
ret = []
if algs.ssh2kex is not None:
macs_supported = algs.ssh2kex.client.mac if client_audit else algs.ssh2kex.server.mac
for mac in macs_supported:
if mac.endswith("-etm@openssh.com"):
ret.append(mac)
return ret
def _get_etm_macs_not_enabled(db: Dict[str, Dict[str, List[List[Optional[str]]]]], algs: Algorithms) -> List[str]:
'''Returns a list of ETM MACs in our algorithm database.'''
ret = []
for mac in db["mac"]:
if mac.endswith("-etm@openssh.com") and mac not in _get_etm_macs_enabled(algs):
ret.append(mac)
return ret
algorithm_recommendation_suppress_list = [] algorithm_recommendation_suppress_list = []
algs_to_note = []
#
# Post-processing of the OpenSSH diffie-hellman-group-exchange-sha256 fallback mechanism bug/feature.
#
# If the server is OpenSSH, and the diffie-hellman-group-exchange-sha256 key exchange was found with modulus size 2048, add a note regarding the bug that causes the server to support 2048-bit moduli no matter the configuration. # If the server is OpenSSH, and the diffie-hellman-group-exchange-sha256 key exchange was found with modulus size 2048, add a note regarding the bug that causes the server to support 2048-bit moduli no matter the configuration.
if (algs.ssh2kex is not None and 'diffie-hellman-group-exchange-sha256' in algs.ssh2kex.kex_algorithms and 'diffie-hellman-group-exchange-sha256' in algs.ssh2kex.dh_modulus_sizes() and algs.ssh2kex.dh_modulus_sizes()['diffie-hellman-group-exchange-sha256'] == 2048) and (banner is not None and banner.software is not None and banner.software.find('OpenSSH') != -1): if (algs.ssh2kex is not None and 'diffie-hellman-group-exchange-sha256' in algs.ssh2kex.kex_algorithms and 'diffie-hellman-group-exchange-sha256' in algs.ssh2kex.dh_modulus_sizes() and algs.ssh2kex.dh_modulus_sizes()['diffie-hellman-group-exchange-sha256'] == 2048) and (banner is not None and banner.software is not None and banner.software.find('OpenSSH') != -1):
# Ensure a list for notes exists. # Ensure a list for notes exists.
while len(SSH2_KexDB.ALGORITHMS['kex']['diffie-hellman-group-exchange-sha256']) < 4: db = SSH2_KexDB.get_db()
SSH2_KexDB.ALGORITHMS['kex']['diffie-hellman-group-exchange-sha256'].append([]) while len(db['kex']['diffie-hellman-group-exchange-sha256']) < 4:
db['kex']['diffie-hellman-group-exchange-sha256'].append([])
SSH2_KexDB.ALGORITHMS['kex']['diffie-hellman-group-exchange-sha256'][3].append("A bug in OpenSSH causes it to fall back to a 2048-bit modulus regardless of server configuration (https://bugzilla.mindrot.org/show_bug.cgi?id=2793)") db['kex']['diffie-hellman-group-exchange-sha256'][3].append("A bug in OpenSSH causes it to fall back to a 2048-bit modulus regardless of server configuration (https://bugzilla.mindrot.org/show_bug.cgi?id=2793)")
# Ensure that this algorithm doesn't appear in the recommendations section since the user cannot control this OpenSSH bug. # Ensure that this algorithm doesn't appear in the recommendations section since the user cannot control this OpenSSH bug.
algorithm_recommendation_suppress_list.append('diffie-hellman-group-exchange-sha256') algorithm_recommendation_suppress_list.append('diffie-hellman-group-exchange-sha256')
return algorithm_recommendation_suppress_list # Check for the Terrapin vulnerability (CVE-2023-48795), and mark the vulnerable algorithms.
kex_strict_marker = False
if algs.ssh2kex is not None and \
((client_audit and 'kex-strict-c-v00@openssh.com' in algs.ssh2kex.kex_algorithms) or (not client_audit and 'kex-strict-s-v00@openssh.com' in algs.ssh2kex.kex_algorithms)): # Strict KEX marker is present.
kex_strict_marker = True
db = SSH2_KexDB.get_db()
#
# Post-processing of algorithms related to the Terrapin vulnerability (CVE-2023-48795).
#
# Without the strict KEX marker, the chacha20-poly1305 ciphers are always vulnerable.
for chacha_cipher in _get_chacha_ciphers_enabled(algs):
if kex_strict_marker:
# Inform the user that the target is correctly configured, but another peer may still choose this algorithm without using strict KEX negotiation, which would still result in vulnerability.
algs_to_note.append(chacha_cipher)
else:
_add_terrapin_warning(db, "enc", chacha_cipher)
cbc_ciphers_enabled = _get_cbc_ciphers_enabled(algs)
etm_macs_enabled = _get_etm_macs_enabled(algs)
# Without the strict KEX marker, if at least one CBC cipher and at least one ETM MAC is supported, mark them all as vulnerable.
if len(cbc_ciphers_enabled) > 0 and len(etm_macs_enabled) > 0:
for cipher in cbc_ciphers_enabled:
if kex_strict_marker:
# Inform the user that the target is correctly configured, but another peer may still choose this algorithm without using strict KEX negotiation, which would still result in vulnerability.
algs_to_note.append(cipher)
else:
_add_terrapin_warning(db, "enc", cipher)
for mac in etm_macs_enabled:
if kex_strict_marker:
# Inform the user that the target is correctly configured, but another peer may still choose this algorithm without using strict KEX negotiation, which would still result in vulnerability.
algs_to_note.append(mac)
else:
_add_terrapin_warning(db, "mac", mac)
# Return a note telling the user that, while this target is properly configured, if connected to a vulnerable peer, then a vulnerable connection is still possible.
additional_notes = []
if len(algs_to_note) > 0:
additional_notes.append("Be aware that, while this target properly supports the strict key exchange method (via the kex-strict-?-v00@openssh.com marker) needed to protect against the Terrapin vulnerability (CVE-2023-48795), all peers must also support this feature as well, otherwise the vulnerability will still be present. The following algorithms would allow an unpatched peer to create vulnerable SSH channels with this target: %s. If any CBC ciphers are in this list, you may remove them while leaving the *-etm@openssh.com MACs in place; these MACs are fine while paired with non-CBC cipher types." % ", ".join(algs_to_note))
# Add the chacha ciphers, CBC ciphers, and ETM MACs to the recommendation suppression list if they are not enabled on the server. That way they are not recommended to the user to enable if they were explicitly disabled to handle the Terrapin vulnerability. However, they can still be recommended for disabling.
algorithm_recommendation_suppress_list += _get_chacha_ciphers_not_enabled(db, algs)
algorithm_recommendation_suppress_list += _get_cbc_ciphers_not_enabled(db, algs)
algorithm_recommendation_suppress_list += _get_etm_macs_not_enabled(db, algs)
# Append any notes related to the DH rate test.
if len(dh_rate_test_notes) > 0:
additional_notes.append(dh_rate_test_notes)
return algorithm_recommendation_suppress_list, additional_notes
# Returns a exitcodes.* flag to denote if any failures or warnings were encountered. # Returns a exitcodes.* flag to denote if any failures or warnings were encountered.
def output(out: OutputBuffer, aconf: AuditConf, banner: Optional[Banner], header: List[str], client_host: Optional[str] = None, kex: Optional[SSH2_Kex] = None, pkm: Optional[SSH1_PublicKeyMessage] = None, print_target: bool = False) -> int: def output(out: OutputBuffer, aconf: AuditConf, banner: Optional[Banner], header: List[str], client_host: Optional[str] = None, kex: Optional[SSH2_Kex] = None, pkm: Optional[SSH1_PublicKeyMessage] = None, print_target: bool = False, dh_rate_test_notes: str = "") -> int:
program_retval = exitcodes.GOOD program_retval = exitcodes.GOOD
client_audit = client_host is not None # If set, this is a client audit. client_audit = client_host is not None # If set, this is a client audit.
@ -466,7 +634,7 @@ def output(out: OutputBuffer, aconf: AuditConf, banner: Optional[Banner], header
algs = Algorithms(pkm, kex) algs = Algorithms(pkm, kex)
# Perform post-processing on the findings to make final adjustments before outputting the results. # Perform post-processing on the findings to make final adjustments before outputting the results.
algorithm_recommendation_suppress_list = post_process_findings(banner, algs) algorithm_recommendation_suppress_list, additional_notes = post_process_findings(banner, algs, client_audit, dh_rate_test_notes)
with out: with out:
if print_target: if print_target:
@ -522,7 +690,7 @@ def output(out: OutputBuffer, aconf: AuditConf, banner: Optional[Banner], header
# SSHv1 # SSHv1
if pkm is not None: if pkm is not None:
adb = SSH1_KexDB.ALGORITHMS adb = SSH1_KexDB.get_db()
ciphers = pkm.supported_ciphers ciphers = pkm.supported_ciphers
auths = pkm.supported_authentications auths = pkm.supported_authentications
title, atype = 'SSH1 host-key algorithms', 'key' title, atype = 'SSH1 host-key algorithms', 'key'
@ -534,7 +702,7 @@ def output(out: OutputBuffer, aconf: AuditConf, banner: Optional[Banner], header
# SSHv2 # SSHv2
if kex is not None: if kex is not None:
adb = SSH2_KexDB.ALGORITHMS adb = SSH2_KexDB.get_db()
title, atype = 'key exchange algorithms', 'kex' title, atype = 'key exchange algorithms', 'kex'
program_retval = output_algorithms(out, title, adb, atype, kex.kex_algorithms, unknown_algorithms, aconf.json, program_retval, maxlen, dh_modulus_sizes=kex.dh_modulus_sizes()) program_retval = output_algorithms(out, title, adb, atype, kex.kex_algorithms, unknown_algorithms, aconf.json, program_retval, maxlen, dh_modulus_sizes=kex.dh_modulus_sizes())
title, atype = 'host-key algorithms', 'key' title, atype = 'host-key algorithms', 'key'
@ -546,12 +714,12 @@ def output(out: OutputBuffer, aconf: AuditConf, banner: Optional[Banner], header
output_fingerprints(out, algs, aconf.json) output_fingerprints(out, algs, aconf.json)
perfect_config = output_recommendations(out, algs, algorithm_recommendation_suppress_list, software, aconf.json, maxlen) perfect_config = output_recommendations(out, algs, algorithm_recommendation_suppress_list, software, aconf.json, maxlen)
output_info(out, software, client_audit, not perfect_config, aconf.json) output_info(out, software, client_audit, not perfect_config, aconf.json, additional_notes)
if aconf.json: if aconf.json:
out.reset() out.reset()
# Build & write the JSON struct. # Build & write the JSON struct.
out.info(json.dumps(build_struct(aconf.host + ":" + str(aconf.port), banner, cves, kex=kex, client_host=client_host, software=software, algorithms=algs, algorithm_recommendation_suppress_list=algorithm_recommendation_suppress_list), indent=4 if aconf.json_print_indent else None, sort_keys=True)) out.info(json.dumps(build_struct(aconf.host + ":" + str(aconf.port), banner, cves, kex=kex, client_host=client_host, software=software, algorithms=algs, algorithm_recommendation_suppress_list=algorithm_recommendation_suppress_list, additional_notes=additional_notes), indent=4 if aconf.json_print_indent else None, sort_keys=True))
elif len(unknown_algorithms) > 0: # If we encountered any unknown algorithms, ask the user to report them. elif len(unknown_algorithms) > 0: # If we encountered any unknown algorithms, ask the user to report them.
out.warn("\n\n!!! WARNING: unknown algorithm(s) found!: %s. Please email the full output above to the maintainer (jtesta@positronsecurity.com), or create a Github issue at <https://github.com/jtesta/ssh-audit/issues>.\n" % ','.join(unknown_algorithms)) out.warn("\n\n!!! WARNING: unknown algorithm(s) found!: %s. Please email the full output above to the maintainer (jtesta@positronsecurity.com), or create a Github issue at <https://github.com/jtesta/ssh-audit/issues>.\n" % ','.join(unknown_algorithms))
@ -650,24 +818,26 @@ def get_algorithm_recommendations(algs: Optional[Algorithms], algorithm_recommen
return ret return ret
def list_policies(out: OutputBuffer) -> None: def list_policies(out: OutputBuffer, verbose: bool) -> None:
'''Prints a list of server & client policies.''' '''Prints a list of server & client policies.'''
server_policy_names, client_policy_names = Policy.list_builtin_policies() server_policy_names, client_policy_names = Policy.list_builtin_policies(verbose)
if len(server_policy_names) > 0: if len(server_policy_names) > 0:
out.head('\nServer policies:\n') out.head('\nServer policies:\n')
out.info(" * \"%s\"" % "\"\n * \"".join(server_policy_names)) out.info(" * %s" % "\n * ".join(server_policy_names))
if len(client_policy_names) > 0: if len(client_policy_names) > 0:
out.head('\nClient policies:\n') out.head('\nClient policies:\n')
out.info(" * \"%s\"" % "\"\n * \"".join(client_policy_names)) out.info(" * %s" % "\n * ".join(client_policy_names))
out.sep() out.sep()
if len(server_policy_names) == 0 and len(client_policy_names) == 0: if len(server_policy_names) == 0 and len(client_policy_names) == 0:
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("Hint: Use -L -v to also see the change log for each policy.\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()
@ -711,12 +881,17 @@ 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
try: try:
sopts = 'h1246M:p:P:jbcnvl:t:T:Lmdg:' sopts = 'h1246M:p:P:jbcnvl:t:T:Lmdg:'
lopts = ['help', 'ssh1', 'ssh2', 'ipv4', 'ipv6', 'make-policy=', 'port=', 'policy=', 'json', 'batch', 'client-audit', 'no-colors', 'verbose', 'level=', 'timeout=', 'targets=', 'list-policies', 'lookup=', 'threads=', 'manual', 'debug', 'gex-test='] lopts = ['help', 'ssh1', 'ssh2', 'ipv4', 'ipv6', 'make-policy=', 'port=', 'policy=', 'json', 'batch', 'client-audit', 'no-colors', 'verbose', 'level=', 'timeout=', 'targets=', 'list-policies', 'lookup=', 'threads=', 'manual', 'debug', 'gex-test=', 'dheat=', 'skip-rate-test', 'conn-rate-test=']
opts, args = getopt.gnu_getopt(args, sopts, lopts) opts, args = getopt.gnu_getopt(args, sopts, lopts)
except getopt.GetoptError as err: except getopt.GetoptError as err:
usage_cb(out, str(err)) usage_cb(out, str(err))
@ -804,6 +979,12 @@ def process_commandline(out: OutputBuffer, args: List[str], usage_cb: Callable[.
usage_cb(out, '{} {} {} is not valid'.format(o, bits_left_bound, bits_right_bound)) usage_cb(out, '{} {} {} is not valid'.format(o, bits_left_bound, bits_right_bound))
aconf.gex_test = a aconf.gex_test = a
elif o == '--dheat':
aconf.dheat = a
elif o == '--skip-rate-test':
aconf.skip_rate_test = True
elif o == '--conn-rate-test':
aconf.conn_rate_test = a
if len(args) == 0 and aconf.client_audit is False and aconf.target_file is None and aconf.list_policies is False and aconf.lookup == '' and aconf.manual is False: if len(args) == 0 and aconf.client_audit is False and aconf.target_file is None and aconf.list_policies is False and aconf.lookup == '' and aconf.manual is False:
@ -816,7 +997,7 @@ def process_commandline(out: OutputBuffer, args: List[str], usage_cb: Callable[.
return aconf return aconf
if aconf.list_policies: if aconf.list_policies:
list_policies(out) list_policies(out, aconf.verbose)
sys.exit(exitcodes.GOOD) sys.exit(exitcodes.GOOD)
if aconf.client_audit is False and aconf.target_file is None: if aconf.client_audit is False and aconf.target_file is None:
@ -887,7 +1068,35 @@ def process_commandline(out: OutputBuffer, args: List[str], usage_cb: Callable[.
return aconf return aconf
def build_struct(target_host: str, banner: Optional['Banner'], cves: List[Dict[str, Union[str, float]]], kex: Optional['SSH2_Kex'] = None, pkm: Optional['SSH1_PublicKeyMessage'] = None, client_host: Optional[str] = None, software: Optional[Software] = None, algorithms: Optional[Algorithms] = None, algorithm_recommendation_suppress_list: Optional[List[str]] = None) -> Any: # pylint: disable=too-many-arguments def build_struct(target_host: str, banner: Optional['Banner'], cves: List[Dict[str, Union[str, float]]], kex: Optional['SSH2_Kex'] = None, pkm: Optional['SSH1_PublicKeyMessage'] = None, client_host: Optional[str] = None, software: Optional[Software] = None, algorithms: Optional[Algorithms] = None, algorithm_recommendation_suppress_list: Optional[List[str]] = None, additional_notes: List[str] = []) -> Any: # pylint: disable=dangerous-default-value
def fetch_notes(algorithm: str, alg_type: str) -> Dict[str, List[Optional[str]]]:
'''Returns a dictionary containing the messages in the "fail", "warn", and "info" levels for this algorithm.'''
alg_db = SSH2_KexDB.get_db()
alg_info = {}
if algorithm in alg_db[alg_type]:
alg_desc = alg_db[alg_type][algorithm]
alg_desc_len = len(alg_desc)
# If a list for the failure notes exists, add it to the return value. Similarly, add the related lists for the warnings and informational notes.
if (alg_desc_len >= 2) and (len(alg_desc[1]) > 0):
alg_info["fail"] = alg_desc[1]
if (alg_desc_len >= 3) and (len(alg_desc[2]) > 0):
alg_info["warn"] = alg_desc[2]
if (alg_desc_len >= 4) and (len(alg_desc[3]) > 0):
alg_info["info"] = alg_desc[3]
# Add information about when this algorithm was implemented in OpenSSH/Dropbear.
since_text = Algorithm.get_since_text(alg_desc[0])
if (since_text is not None) and (len(since_text) > 0):
# Add the "info" key with an empty list if the if-block above didn't create it already.
if "info" not in alg_info:
alg_info["info"] = []
alg_info["info"].append(since_text)
else:
alg_info["fail"] = [SSH2_KexDB.FAIL_UNKNOWN]
return alg_info
banner_str = '' banner_str = ''
banner_protocol = None banner_protocol = None
@ -895,7 +1104,7 @@ def build_struct(target_host: str, banner: Optional['Banner'], cves: List[Dict[s
banner_comments = None banner_comments = None
if banner is not None: if banner is not None:
banner_str = str(banner) banner_str = str(banner)
banner_protocol = banner.protocol banner_protocol = '.'.join(str(x) for x in banner.protocol)
banner_software = banner.software banner_software = banner.software
banner_comments = banner.comments banner_comments = banner.comments
@ -920,19 +1129,22 @@ def build_struct(target_host: str, banner: Optional['Banner'], cves: List[Dict[s
res['kex'] = [] res['kex'] = []
dh_alg_sizes = kex.dh_modulus_sizes() dh_alg_sizes = kex.dh_modulus_sizes()
for algorithm in kex.kex_algorithms: for algorithm in kex.kex_algorithms:
alg_notes = fetch_notes(algorithm, 'kex')
entry: Any = { entry: Any = {
'algorithm': algorithm, 'algorithm': algorithm,
'notes': alg_notes,
} }
if algorithm in dh_alg_sizes: if algorithm in dh_alg_sizes:
hostkey_size = dh_alg_sizes[algorithm] hostkey_size = dh_alg_sizes[algorithm]
entry['keysize'] = hostkey_size entry['keysize'] = hostkey_size
res['kex'].append(entry) res['kex'].append(entry)
res['key'] = [] res['key'] = []
host_keys = kex.host_keys() host_keys = kex.host_keys()
for algorithm in kex.key_algorithms: for algorithm in kex.key_algorithms:
alg_notes = fetch_notes(algorithm, 'key')
entry = { entry = {
'algorithm': algorithm, 'algorithm': algorithm,
'notes': alg_notes,
} }
if algorithm in host_keys: if algorithm in host_keys:
hostkey_info = host_keys[algorithm] hostkey_info = host_keys[algorithm]
@ -952,8 +1164,24 @@ def build_struct(target_host: str, banner: Optional['Banner'], cves: List[Dict[s
entry['casize'] = ca_size entry['casize'] = ca_size
res['key'].append(entry) res['key'].append(entry)
res['enc'] = kex.server.encryption res['enc'] = []
res['mac'] = kex.server.mac for algorithm in kex.server.encryption:
alg_notes = fetch_notes(algorithm, 'enc')
entry = {
'algorithm': algorithm,
'notes': alg_notes,
}
res['enc'].append(entry)
res['mac'] = []
for algorithm in kex.server.mac:
alg_notes = fetch_notes(algorithm, 'mac')
entry = {
'algorithm': algorithm,
'notes': alg_notes,
}
res['mac'].append(entry)
res['fingerprints'] = [] res['fingerprints'] = []
host_keys = kex.host_keys() host_keys = kex.host_keys()
@ -1008,6 +1236,9 @@ def build_struct(target_host: str, banner: Optional['Banner'], cves: List[Dict[s
# Add in the recommendations. # Add in the recommendations.
res['recommendations'] = get_algorithm_recommendations(algorithms, algorithm_recommendation_suppress_list, software, for_server=True) res['recommendations'] = get_algorithm_recommendations(algorithms, algorithm_recommendation_suppress_list, software, for_server=True)
# Add in the additional notes.
res['additional_notes'] = additional_notes
return res return res
@ -1088,16 +1319,31 @@ def audit(out: OutputBuffer, aconf: AuditConf, sshv: Optional[int] = None, print
out.fail("Failed to parse server's kex. Stack trace:\n%s" % str(traceback.format_exc())) out.fail("Failed to parse server's kex. Stack trace:\n%s" % str(traceback.format_exc()))
return exitcodes.CONNECTION_ERROR return exitcodes.CONNECTION_ERROR
if aconf.dheat is not None:
DHEat(out, aconf, banner, kex).run()
return exitcodes.GOOD
elif aconf.conn_rate_test_enabled:
DHEat.dh_rate_test(out, aconf, kex, 0, 0, 0)
return exitcodes.GOOD
dh_rate_test_notes = ""
if aconf.client_audit is False: if aconf.client_audit is False:
HostKeyTest.run(out, s, kex) HostKeyTest.run(out, s, kex)
if aconf.gex_test != '': if aconf.gex_test != '':
return run_gex_granular_modulus_size_test(out, s, kex, aconf) return run_gex_granular_modulus_size_test(out, s, kex, aconf)
else: else:
GEXTest.run(out, s, kex) GEXTest.run(out, s, banner, kex)
# Skip the rate test if the user specified "--skip-rate-test".
if aconf.skip_rate_test:
out.d("Skipping rate test due to --skip-rate-test option.")
else:
# Try to open many TCP connections against the server if any Diffie-Hellman key exchanges are present; this tests potential vulnerability to the DHEat DOS attack. Use 3 concurrent sockets over at most 1.5 seconds to open at most 38 connections (stops if 1.5 seconds elapse, or 38 connections are opened--whichever comes first). If more than 25 connections per second were observed, flag the DH algorithms with a warning about the DHEat DOS vuln.
dh_rate_test_notes = DHEat.dh_rate_test(out, aconf, kex, 1.5, 38, 3)
# This is a standard audit scan. # This is a standard audit scan.
if (aconf.policy is None) and (aconf.make_policy is False): if (aconf.policy is None) and (aconf.make_policy is False):
program_retval = output(out, aconf, banner, header, client_host=s.client_host, kex=kex, print_target=print_target) program_retval = output(out, aconf, banner, header, client_host=s.client_host, kex=kex, print_target=print_target, dh_rate_test_notes=dh_rate_test_notes)
# This is a policy test. # This is a policy test.
elif (aconf.policy is not None) and (aconf.make_policy is False): elif (aconf.policy is not None) and (aconf.make_policy is False):
@ -1124,7 +1370,7 @@ def algorithm_lookup(out: OutputBuffer, alg_names: str) -> int:
} }
algorithm_names = alg_names.split(",") algorithm_names = alg_names.split(",")
adb = SSH2_KexDB.ALGORITHMS adb = SSH2_KexDB.get_db()
# Use nested dictionary comprehension to iterate an outer dictionary where # Use nested dictionary comprehension to iterate an outer dictionary where
# each key is an alg type that consists of a value (which is itself a # each key is an alg type that consists of a value (which is itself a
@ -1213,23 +1459,21 @@ def target_worker_thread(host: str, port: int, shared_aconf: AuditConf) -> Tuple
return ret, string_output return ret, string_output
def windows_manual(out: OutputBuffer) -> int: def builtin_manual(out: OutputBuffer) -> int:
'''Prints the man page on Windows. Returns an exitcodes.* flag.''' '''Prints the man page (Docker, PyPI, Snap, and Windows builds only). Returns an exitcodes.* flag.'''
retval = exitcodes.GOOD
if sys.platform != 'win32': builtin_man_page = BUILTIN_MAN_PAGE
out.fail("The '-m' and '--manual' parameters are reserved for use on Windows only.\nUsers of other operating systems should read the man page.") if builtin_man_page == "":
retval = exitcodes.FAILURE out.fail("The '-m' and '--manual' parameters are reserved for use in Docker, PyPI, Snap,\nand Windows builds only. Users of other platforms should read the system man\npage.")
return retval return exitcodes.FAILURE
# If colors are disabled, strip the ANSI color codes from the man page. # If colors are disabled, strip the ANSI color codes from the man page.
windows_man_page = WINDOWS_MAN_PAGE
if not out.use_colors: if not out.use_colors:
windows_man_page = re.sub(r'\x1b\[\d+?m', '', windows_man_page) builtin_man_page = re.sub(r'\x1b\[\d+?m', '', builtin_man_page)
out.info(windows_man_page) out.info(builtin_man_page)
return retval return exitcodes.GOOD
def get_permitted_syntax_for_gex_test() -> Dict[str, str]: def get_permitted_syntax_for_gex_test() -> Dict[str, str]:
@ -1323,7 +1567,7 @@ def main() -> int:
# to output a plain text version of the man page. # to output a plain text version of the man page.
if (sys.platform == 'win32') and ('colorama' not in sys.modules): if (sys.platform == 'win32') and ('colorama' not in sys.modules):
out.use_colors = False out.use_colors = False
retval = windows_manual(out) retval = builtin_manual(out)
out.write() out.write()
sys.exit(retval) sys.exit(retval)
@ -1376,6 +1620,10 @@ def main() -> int:
if aconf.json: if aconf.json:
print(']') print(']')
# Send notification that this thread is exiting. This deletes the thread's local copy of the algorithm databases.
SSH1_KexDB.thread_exit()
SSH2_KexDB.thread_exit()
else: # Just a scan against a single target. else: # Just a scan against a single target.
ret = audit(out, aconf) ret = audit(out, aconf)
out.write() out.write()
@ -1384,8 +1632,9 @@ def main() -> int:
if __name__ == '__main__': # pragma: nocover if __name__ == '__main__': # pragma: nocover
exit_code = exitcodes.GOOD multiprocessing.freeze_support() # Needed for PyInstaller (Windows) builds.
exit_code = exitcodes.GOOD
try: try:
exit_code = main() exit_code = main()
except Exception: except Exception:

View File

@ -75,12 +75,17 @@ class SSH_Socket(ReadBuf, WriteBuf):
self.client_port = None self.client_port = None
def _resolve(self) -> Iterable[Tuple[int, Tuple[Any, ...]]]: def _resolve(self) -> Iterable[Tuple[int, Tuple[Any, ...]]]:
"""Resolves a hostname into a list of IPs
Raises
------
socket.gaierror [Errno -2]
If the hostname cannot be resolved.
"""
# If __ip_version_preference has only one entry, then it means that ONLY that IP version should be used. # If __ip_version_preference has only one entry, then it means that ONLY that IP version should be used.
if len(self.__ip_version_preference) == 1: if len(self.__ip_version_preference) == 1:
family = socket.AF_INET if self.__ip_version_preference[0] == 4 else socket.AF_INET6 family = socket.AF_INET if self.__ip_version_preference[0] == 4 else socket.AF_INET6
else: else:
family = socket.AF_UNSPEC family = socket.AF_UNSPEC
try:
stype = socket.SOCK_STREAM stype = socket.SOCK_STREAM
r = socket.getaddrinfo(self.__host, self.__port, family, stype) r = socket.getaddrinfo(self.__host, self.__port, family, stype)
@ -90,9 +95,6 @@ class SSH_Socket(ReadBuf, WriteBuf):
for af, socktype, _proto, _canonname, addr in r: for af, socktype, _proto, _canonname, addr in r:
if socktype == socket.SOCK_STREAM: if socktype == socket.SOCK_STREAM:
yield af, addr yield af, addr
except socket.error as e:
self.__outputbuffer.fail('[exception] {}'.format(e)).write()
sys.exit(exitcodes.CONNECTION_ERROR)
# Listens on a server socket and accepts one connection (used for # Listens on a server socket and accepts one connection (used for
# auditing client connections). # auditing client connections).
@ -105,8 +107,8 @@ class SSH_Socket(ReadBuf, WriteBuf):
s.bind(('0.0.0.0', self.__port)) s.bind(('0.0.0.0', self.__port))
s.listen() s.listen()
self.__sock_map[s.fileno()] = s self.__sock_map[s.fileno()] = s
except Exception: except Exception as e:
print("Warning: failed to listen on any IPv4 interfaces.") print("Warning: failed to listen on any IPv4 interfaces: %s" % str(e))
try: try:
# Socket to listen on all IPv6 addresses. # Socket to listen on all IPv6 addresses.
@ -116,8 +118,8 @@ class SSH_Socket(ReadBuf, WriteBuf):
s.bind(('::', self.__port)) s.bind(('::', self.__port))
s.listen() s.listen()
self.__sock_map[s.fileno()] = s self.__sock_map[s.fileno()] = s
except Exception: except Exception as e:
print("Warning: failed to listen on any IPv6 interfaces.") print("Warning: failed to listen on any IPv6 interfaces: %s" % str(e))
# If we failed to listen on any interfaces, terminate. # If we failed to listen on any interfaces, terminate.
if len(self.__sock_map.keys()) == 0: if len(self.__sock_map.keys()) == 0:
@ -152,9 +154,9 @@ class SSH_Socket(ReadBuf, WriteBuf):
def connect(self) -> Optional[str]: def connect(self) -> Optional[str]:
'''Returns None on success, or an error string.''' '''Returns None on success, or an error string.'''
err = None err = None
for af, addr in self._resolve():
s = None s = None
try: try:
for af, addr in self._resolve():
s = socket.socket(af, socket.SOCK_STREAM) s = socket.socket(af, socket.SOCK_STREAM)
s.settimeout(self.__timeout) s.settimeout(self.__timeout)
self.__outputbuffer.d(("Connecting to %s:%d..." % ('[%s]' % addr[0] if Utils.is_ipv6_address(addr[0]) else addr[0], addr[1])), write_now=True) self.__outputbuffer.d(("Connecting to %s:%d..." % ('[%s]' % addr[0] if Utils.is_ipv6_address(addr[0]) else addr[0], addr[1])), write_now=True)
@ -244,8 +246,7 @@ class SSH_Socket(ReadBuf, WriteBuf):
def send_banner(self, banner: str) -> None: def send_banner(self, banner: str) -> None:
self.send(banner.encode() + b'\r\n') self.send(banner.encode() + b'\r\n')
if self.__state < self.SM_BANNER_SENT: self.__state = max(self.__state, self.SM_BANNER_SENT)
self.__state = self.SM_BANNER_SENT
def ensure_read(self, size: int) -> None: def ensure_read(self, size: int) -> None:
while self.unread_len < size: while self.unread_len < size:

View File

@ -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'],

View File

@ -1,4 +1,4 @@
.TH SSH-AUDIT 1 "March 13, 2022" .TH SSH-AUDIT 1 "April 18, 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
@ -46,11 +46,21 @@ Enables grepable output.
.br .br
Starts a server on port 2222 to audit client software configuration. Use -p/--port=<port> to change port and -t/--timeout=<secs> to change listen timeout. Starts a server on port 2222 to audit client software configuration. Use -p/--port=<port> to change port and -t/--timeout=<secs> to change listen timeout.
.TP
.B \-\-conn\-rate\-test=N[:max_rate]
.br
Performs a connection rate test (useful for collecting metrics related to susceptibility of the DHEat vulnerability [CVE-2002-20001]). A successful connection is counted when the server returns a valid SSH banner. Testing is conducted with N concurrent sockets with an optional maximum rate of connections per second.
.TP .TP
.B -d, \-\-debug .B -d, \-\-debug
.br .br
Enable debug output. Enable debug output.
.TP
.B \-\-dheat=N[:kex[:e_len]]
.br
Run the DHEat DoS attack (CVE-2002-20001) against the target server (which will consume all available CPU resources). The number of concurrent sockets, N, needed to achieve this effect will be highly dependent on the CPU resources available on the target, as well as the latency between the source and target machines. The key exchange is automatically chosen based on which would cause maximum effect, unless explicitly chosen in the second field. Lastly, an (experimental) option allows the length in bytes of the fake e value sent to the server to be specified in the third field. Normally, the length of e is roughly the length of the modulus of the Diffie-Hellman exchange (hence, an 8192-bit / 1024-byte value of e is sent in each connection when targeting the diffie-hellman-group18-sha512 algorithm). Instead, it was observed that many SSH implementations accept small values, such as 4 bytes; this results in a much more network-efficient attack.
.TP .TP
.B -g, \-\-gex-test=<x[,y,...] | min1:pref1:max1[,min2:pref2:max2,...] | x-y[:step]> .B -g, \-\-gex-test=<x[,y,...] | min1:pref1:max1[,min2:pref2:max2,...] | x-y[:step]>
.br .br
@ -94,7 +104,7 @@ Specify the minimum output level. Default is info.
.TP .TP
.B -L, \-\-list-policies .B -L, \-\-list-policies
.br .br
List all official, built-in policies for common systems. Their full names can then be passed to -P/--policy. List all official, built-in policies for common systems. Their full names can then be passed to -P/--policy. Add \-v to \-L to view policy change logs.
.TP .TP
.B \-\-lookup=<alg1,alg2,...> .B \-\-lookup=<alg1,alg2,...>
@ -104,7 +114,7 @@ Look up the security information of an algorithm(s) in the internal database. D
.TP .TP
.B -m, \-\-manual .B -m, \-\-manual
.br .br
Print the man page (Windows only). Print the man page (Docker, PyPI, Snap, and Windows builds only).
.TP .TP
.B -M, \-\-make-policy=<custom_policy.txt> .B -M, \-\-make-policy=<custom_policy.txt>
@ -114,7 +124,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>
@ -126,6 +136,11 @@ The TCP port to connect to when auditing a server, or the port to listen on when
.br .br
Runs a policy audit against a target using the specified policy (see \fBPOLICY AUDIT\fP section for detailed description of this mode of operation). Combine with -c/--client-audit to audit a client configuration instead of a server. Use -L/--list-policies to list all official, built-in policies for common systems. Runs a policy audit against a target using the specified policy (see \fBPOLICY AUDIT\fP section for detailed description of this mode of operation). Combine with -c/--client-audit to audit a client configuration instead of a server. Use -L/--list-policies to list all official, built-in policies for common systems.
.TP
.B \-\-skip\-rate\-test
.br
Skips the connection rate test during standard audits. By default, a few dozen TCP connections are created with the target host to see if connection throttling is implemented (this can safely infer whether the target is vulnerable to the DHEat attack; see CVE-2002-20001).
.TP .TP
.B -t, \-\-timeout=<secs> .B -t, \-\-timeout=<secs>
.br .br
@ -273,6 +288,46 @@ ssh-audit targetserver --gex-test=0-5120:1024
.fi .fi
.RE .RE
.LP
To run the DHEat DoS attack (monitor the target server's CPU usage to determine the optimal number of concurrent sockets):
.RS
.nf
ssh-audit targetserver --dheat=10
.fi
.RE
.LP
To run the DHEat attack and manually target the diffie-hellman-group-exchange-sha256 algorithm:
.RS
.nf
ssh-audit targetserver --dheat=10:diffie-hellman-group-exchange-sha256
.fi
.RE
.LP
To run the DHEat attack and manually target the diffie-hellman-group-exchange-sha256 algorithm with a very small length of e (resulting in the same effect but without having to send large packets):
.RS
.nf
ssh-audit targetserver --dheat=10:diffie-hellman-group-exchange-sha256:4
.fi
.RE
.LP
To test the number of successful connections per second that can be created with the target using 8 parallel threads (useful for detecting whether connection throttling is implemented by the target):
.RS
.nf
ssh-audit targetserver --conn-rate-test=8
.fi
.RE
.LP
To use 8 parallel threads to create up to 100 connections per second with the target (useful for understanding how much CPU load is caused on the target simply from handling new connections vs excess modular exponentiation when performing the DHEat attack):
.RS
.nf
ssh-audit targetserver --conn-rate-test=8:100
.fi
.RE
.SH RETURN VALUES .SH RETURN VALUES
When a successful connection is made and all algorithms are rated as "good", \fBssh-audit\fP returns 0. Other possible return values are: When a successful connection is made and all algorithms are rated as "good", \fBssh-audit\fP returns 0. Other possible return values are:

View File

@ -1,6 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""src/ssh_audit/ssh_audit.py wrapper for backwards compatibility""" """src/ssh_audit/ssh_audit.py wrapper for backwards compatibility"""
import multiprocessing
import sys import sys
import traceback import traceback
from pathlib import Path from pathlib import Path
@ -10,8 +11,10 @@ sys.path.insert(0, str(Path(__file__).resolve().parent / "src"))
from ssh_audit.ssh_audit import main # noqa: E402 from ssh_audit.ssh_audit import main # noqa: E402
from ssh_audit import exitcodes # noqa: E402 from ssh_audit import exitcodes # noqa: E402
exit_code = exitcodes.GOOD if __name__ == "__main__":
multiprocessing.freeze_support() # Needed for PyInstaller (Windows) builds.
exit_code = exitcodes.GOOD
try: try:
exit_code = main() exit_code = main()
except Exception: except Exception:

View File

@ -73,6 +73,7 @@ class _VirtualSocket:
self.rdata = [] self.rdata = []
self.sdata = [] self.sdata = []
self.errors = {} self.errors = {}
self.blocking = False
self.gsock = _VirtualGlobalSocket(self) self.gsock = _VirtualGlobalSocket(self)
def _check_err(self, method): def _check_err(self, method):
@ -83,12 +84,18 @@ class _VirtualSocket:
def connect(self, address): def connect(self, address):
return self._connect(address, False) return self._connect(address, False)
def connect_ex(self, address):
return self.connect(address)
def _connect(self, address, ret=True): def _connect(self, address, ret=True):
self.peer_address = address self.peer_address = address
self._connected = True self._connected = True
self._check_err('connect') self._check_err('connect')
return self if ret else None return self if ret else None
def setblocking(self, r: bool):
self.blocking = r
def settimeout(self, timeout): def settimeout(self, timeout):
self.timeout = timeout self.timeout = timeout

View File

@ -1,10 +1,8 @@
{ {
"additional_notes": [],
"banner": { "banner": {
"comments": null, "comments": null,
"protocol": [ "protocol": "2.0",
2,
0
],
"raw": "SSH-2.0-dropbear_2019.78", "raw": "SSH-2.0-dropbear_2019.78",
"software": "dropbear_2019.78" "software": "dropbear_2019.78"
}, },
@ -14,12 +12,70 @@
], ],
"cves": [], "cves": [],
"enc": [ "enc": [
"aes128-ctr", {
"aes256-ctr", "algorithm": "aes128-ctr",
"aes128-cbc", "notes": {
"aes256-cbc", "info": [
"3des-ctr", "available since OpenSSH 3.7, Dropbear SSH 0.52"
"3des-cbc" ]
}
},
{
"algorithm": "aes256-ctr",
"notes": {
"info": [
"available since OpenSSH 3.7, Dropbear SSH 0.52"
]
}
},
{
"algorithm": "aes128-cbc",
"notes": {
"info": [
"available since OpenSSH 2.3.0, Dropbear SSH 0.28"
],
"warn": [
"using weak cipher mode"
]
}
},
{
"algorithm": "aes256-cbc",
"notes": {
"info": [
"available since OpenSSH 2.3.0, Dropbear SSH 0.47"
],
"warn": [
"using weak cipher mode"
]
}
},
{
"algorithm": "3des-ctr",
"notes": {
"fail": [
"using broken & deprecated 3DES cipher"
],
"info": [
"available since Dropbear SSH 0.52"
]
}
},
{
"algorithm": "3des-cbc",
"notes": {
"fail": [
"using broken & deprecated 3DES cipher"
],
"info": [
"available since OpenSSH 1.2.2, Dropbear SSH 0.28"
],
"warn": [
"using weak cipher mode",
"using small 64-bit block size"
]
}
}
], ],
"fingerprints": [ "fingerprints": [
{ {
@ -35,46 +91,175 @@
], ],
"kex": [ "kex": [
{ {
"algorithm": "curve25519-sha256" "algorithm": "curve25519-sha256",
"notes": {
"info": [
"default key exchange from OpenSSH 7.4 to 8.9",
"available since OpenSSH 7.4, Dropbear SSH 2018.76"
]
}
}, },
{ {
"algorithm": "curve25519-sha256@libssh.org" "algorithm": "curve25519-sha256@libssh.org",
"notes": {
"info": [
"default key exchange from OpenSSH 6.5 to 7.3",
"available since OpenSSH 6.4, Dropbear SSH 2013.62"
]
}
}, },
{ {
"algorithm": "ecdh-sha2-nistp521" "algorithm": "ecdh-sha2-nistp521",
"notes": {
"fail": [
"using elliptic curves that are suspected as being backdoored by the U.S. National Security Agency"
],
"info": [
"available since OpenSSH 5.7, Dropbear SSH 2013.62"
]
}
}, },
{ {
"algorithm": "ecdh-sha2-nistp384" "algorithm": "ecdh-sha2-nistp384",
"notes": {
"fail": [
"using elliptic curves that are suspected as being backdoored by the U.S. National Security Agency"
],
"info": [
"available since OpenSSH 5.7, Dropbear SSH 2013.62"
]
}
}, },
{ {
"algorithm": "ecdh-sha2-nistp256" "algorithm": "ecdh-sha2-nistp256",
"notes": {
"fail": [
"using elliptic curves that are suspected as being backdoored by the U.S. National Security Agency"
],
"info": [
"available since OpenSSH 5.7, Dropbear SSH 2013.62"
]
}
}, },
{ {
"algorithm": "diffie-hellman-group14-sha256" "algorithm": "diffie-hellman-group14-sha256",
"notes": {
"info": [
"available since OpenSSH 7.3, Dropbear SSH 2016.73"
],
"warn": [
"2048-bit modulus only provides 112-bits of symmetric strength"
]
}
}, },
{ {
"algorithm": "diffie-hellman-group14-sha1" "algorithm": "diffie-hellman-group14-sha1",
"notes": {
"fail": [
"using broken SHA-1 hash algorithm"
],
"info": [
"available since OpenSSH 3.9, Dropbear SSH 0.53"
],
"warn": [
"2048-bit modulus only provides 112-bits of symmetric strength"
]
}
}, },
{ {
"algorithm": "kexguess2@matt.ucc.asn.au" "algorithm": "kexguess2@matt.ucc.asn.au",
"notes": {
"info": [
"available since Dropbear SSH 2013.57"
]
}
} }
], ],
"key": [ "key": [
{ {
"algorithm": "ecdsa-sha2-nistp256" "algorithm": "ecdsa-sha2-nistp256",
"notes": {
"fail": [
"using elliptic curves that are suspected as being backdoored by the U.S. National Security Agency"
],
"info": [
"available since OpenSSH 5.7, Dropbear SSH 2013.62"
],
"warn": [
"using weak random number generator could reveal the key"
]
}
}, },
{ {
"algorithm": "ssh-rsa", "algorithm": "ssh-rsa",
"keysize": 1024 "keysize": 1024,
"notes": {
"fail": [
"using broken SHA-1 hash algorithm",
"using small 1024-bit modulus"
],
"info": [
"deprecated in OpenSSH 8.8: https://www.openssh.com/txt/release-8.8",
"available since OpenSSH 2.5.0, Dropbear SSH 0.28"
]
}
}, },
{ {
"algorithm": "ssh-dss" "algorithm": "ssh-dss",
"notes": {
"fail": [
"using small 1024-bit modulus"
],
"info": [
"disabled in OpenSSH 7.0: https://www.openssh.com/txt/release-7.0",
"available since OpenSSH 2.1.0, Dropbear SSH 0.28"
],
"warn": [
"using weak random number generator could reveal the key"
]
}
} }
], ],
"mac": [ "mac": [
"hmac-sha1-96", {
"hmac-sha1", "algorithm": "hmac-sha1-96",
"hmac-sha2-256" "notes": {
"fail": [
"using broken SHA-1 hash algorithm"
],
"info": [
"available since OpenSSH 2.5.0, Dropbear SSH 0.47"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
},
{
"algorithm": "hmac-sha1",
"notes": {
"fail": [
"using broken SHA-1 hash algorithm"
],
"info": [
"available since OpenSSH 2.1.0, Dropbear SSH 0.28"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
},
{
"algorithm": "hmac-sha2-256",
"notes": {
"info": [
"available since OpenSSH 5.9, Dropbear SSH 2013.56"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
}
], ],
"recommendations": { "recommendations": {
"critical": { "critical": {

View File

@ -6,9 +6,9 @@
# key exchange algorithms # key exchange algorithms
(kex) curve25519-sha256 -- [info] available since OpenSSH 7.4, Dropbear SSH 2018.76 (kex) curve25519-sha256 -- [info] available since OpenSSH 7.4, Dropbear SSH 2018.76
`- [info] default key exchange since OpenSSH 6.4  `- [info] default key exchange from OpenSSH 7.4 to 8.9
(kex) curve25519-sha256@libssh.org -- [info] available since OpenSSH 6.4, Dropbear SSH 2013.62 (kex) curve25519-sha256@libssh.org -- [info] available since OpenSSH 6.4, Dropbear SSH 2013.62
`- [info] default key exchange since OpenSSH 6.4  `- [info] default key exchange from OpenSSH 6.5 to 7.3
(kex) ecdh-sha2-nistp521 -- [fail] using elliptic curves that are suspected as being backdoored by the U.S. National Security Agency (kex) ecdh-sha2-nistp521 -- [fail] using elliptic curves that are suspected as being backdoored by the U.S. National Security Agency
`- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62 `- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62
(kex) ecdh-sha2-nistp384 -- [fail] using elliptic curves that are suspected as being backdoored by the U.S. National Security Agency (kex) ecdh-sha2-nistp384 -- [fail] using elliptic curves that are suspected as being backdoored by the U.S. National Security Agency

View File

@ -1,10 +1,8 @@
{ {
"additional_notes": [],
"banner": { "banner": {
"comments": null, "comments": null,
"protocol": [ "protocol": "1.99",
1,
99
],
"raw": "SSH-1.99-OpenSSH_4.0", "raw": "SSH-1.99-OpenSSH_4.0",
"software": "OpenSSH_4.0" "software": "OpenSSH_4.0"
}, },
@ -125,17 +123,134 @@
} }
], ],
"enc": [ "enc": [
"aes128-cbc", {
"3des-cbc", "algorithm": "aes128-cbc",
"blowfish-cbc", "notes": {
"cast128-cbc", "info": [
"arcfour", "available since OpenSSH 2.3.0, Dropbear SSH 0.28"
"aes192-cbc", ],
"aes256-cbc", "warn": [
"rijndael-cbc@lysator.liu.se", "using weak cipher mode"
"aes128-ctr", ]
"aes192-ctr", }
"aes256-ctr" },
{
"algorithm": "3des-cbc",
"notes": {
"fail": [
"using broken & deprecated 3DES cipher"
],
"info": [
"available since OpenSSH 1.2.2, Dropbear SSH 0.28"
],
"warn": [
"using weak cipher mode",
"using small 64-bit block size"
]
}
},
{
"algorithm": "blowfish-cbc",
"notes": {
"fail": [
"using weak & deprecated Blowfish cipher"
],
"info": [
"available since OpenSSH 1.2.2, Dropbear SSH 0.28"
],
"warn": [
"using weak cipher mode",
"using small 64-bit block size"
]
}
},
{
"algorithm": "cast128-cbc",
"notes": {
"fail": [
"using weak & deprecated CAST cipher"
],
"info": [
"available since OpenSSH 2.1.0"
],
"warn": [
"using weak cipher mode",
"using small 64-bit block size"
]
}
},
{
"algorithm": "arcfour",
"notes": {
"fail": [
"using broken RC4 cipher"
],
"info": [
"available since OpenSSH 2.1.0"
]
}
},
{
"algorithm": "aes192-cbc",
"notes": {
"info": [
"available since OpenSSH 2.3.0"
],
"warn": [
"using weak cipher mode"
]
}
},
{
"algorithm": "aes256-cbc",
"notes": {
"info": [
"available since OpenSSH 2.3.0, Dropbear SSH 0.47"
],
"warn": [
"using weak cipher mode"
]
}
},
{
"algorithm": "rijndael-cbc@lysator.liu.se",
"notes": {
"fail": [
"using deprecated & non-standardized Rijndael cipher"
],
"info": [
"disabled in OpenSSH 7.0: https://www.openssh.com/txt/release-7.0",
"available since OpenSSH 2.3.0"
],
"warn": [
"using weak cipher mode"
]
}
},
{
"algorithm": "aes128-ctr",
"notes": {
"info": [
"available since OpenSSH 3.7, Dropbear SSH 0.52"
]
}
},
{
"algorithm": "aes192-ctr",
"notes": {
"info": [
"available since OpenSSH 3.7"
]
}
},
{
"algorithm": "aes256-ctr",
"notes": {
"info": [
"available since OpenSSH 3.7, Dropbear SSH 0.52"
]
}
}
], ],
"fingerprints": [ "fingerprints": [
{ {
@ -152,31 +267,161 @@
"kex": [ "kex": [
{ {
"algorithm": "diffie-hellman-group-exchange-sha1", "algorithm": "diffie-hellman-group-exchange-sha1",
"keysize": 1024 "keysize": 1024,
"notes": {
"fail": [
"using small 1024-bit modulus"
],
"info": [
"available since OpenSSH 2.3.0"
]
}
}, },
{ {
"algorithm": "diffie-hellman-group14-sha1" "algorithm": "diffie-hellman-group14-sha1",
"notes": {
"fail": [
"using broken SHA-1 hash algorithm"
],
"info": [
"available since OpenSSH 3.9, Dropbear SSH 0.53"
],
"warn": [
"2048-bit modulus only provides 112-bits of symmetric strength"
]
}
}, },
{ {
"algorithm": "diffie-hellman-group1-sha1" "algorithm": "diffie-hellman-group1-sha1",
"notes": {
"fail": [
"using small 1024-bit modulus",
"vulnerable to the Logjam attack: https://en.wikipedia.org/wiki/Logjam_(computer_security)",
"using broken SHA-1 hash algorithm"
],
"info": [
"removed in OpenSSH 6.9: https://www.openssh.com/txt/release-6.9",
"available since OpenSSH 2.3.0, Dropbear SSH 0.28"
]
}
} }
], ],
"key": [ "key": [
{ {
"algorithm": "ssh-rsa", "algorithm": "ssh-rsa",
"keysize": 1024 "keysize": 1024,
"notes": {
"fail": [
"using broken SHA-1 hash algorithm",
"using small 1024-bit modulus"
],
"info": [
"deprecated in OpenSSH 8.8: https://www.openssh.com/txt/release-8.8",
"available since OpenSSH 2.5.0, Dropbear SSH 0.28"
]
}
}, },
{ {
"algorithm": "ssh-dss" "algorithm": "ssh-dss",
"notes": {
"fail": [
"using small 1024-bit modulus"
],
"info": [
"disabled in OpenSSH 7.0: https://www.openssh.com/txt/release-7.0",
"available since OpenSSH 2.1.0, Dropbear SSH 0.28"
],
"warn": [
"using weak random number generator could reveal the key"
]
}
} }
], ],
"mac": [ "mac": [
"hmac-md5", {
"hmac-sha1", "algorithm": "hmac-md5",
"hmac-ripemd160", "notes": {
"hmac-ripemd160@openssh.com", "fail": [
"hmac-sha1-96", "using broken MD5 hash algorithm"
"hmac-md5-96" ],
"info": [
"available since OpenSSH 2.1.0, Dropbear SSH 0.28"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
},
{
"algorithm": "hmac-sha1",
"notes": {
"fail": [
"using broken SHA-1 hash algorithm"
],
"info": [
"available since OpenSSH 2.1.0, Dropbear SSH 0.28"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
},
{
"algorithm": "hmac-ripemd160",
"notes": {
"fail": [
"using deprecated RIPEMD hash algorithm"
],
"info": [
"available since OpenSSH 2.5.0"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
},
{
"algorithm": "hmac-ripemd160@openssh.com",
"notes": {
"fail": [
"using deprecated RIPEMD hash algorithm"
],
"info": [
"available since OpenSSH 2.1.0"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
},
{
"algorithm": "hmac-sha1-96",
"notes": {
"fail": [
"using broken SHA-1 hash algorithm"
],
"info": [
"available since OpenSSH 2.5.0, Dropbear SSH 0.47"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
},
{
"algorithm": "hmac-md5-96",
"notes": {
"fail": [
"using broken MD5 hash algorithm"
],
"info": [
"available since OpenSSH 2.5.0"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
}
], ],
"recommendations": { "recommendations": {
"critical": { "critical": {

View File

@ -1,10 +1,8 @@
{ {
"additional_notes": [],
"banner": { "banner": {
"comments": null, "comments": null,
"protocol": [ "protocol": "2.0",
2,
0
],
"raw": "SSH-2.0-OpenSSH_5.6", "raw": "SSH-2.0-OpenSSH_5.6",
"software": "OpenSSH_5.6" "software": "OpenSSH_5.6"
}, },
@ -90,19 +88,156 @@
} }
], ],
"enc": [ "enc": [
"aes128-ctr", {
"aes192-ctr", "algorithm": "aes128-ctr",
"aes256-ctr", "notes": {
"arcfour256", "info": [
"arcfour128", "available since OpenSSH 3.7, Dropbear SSH 0.52"
"aes128-cbc", ]
"3des-cbc", }
"blowfish-cbc", },
"cast128-cbc", {
"aes192-cbc", "algorithm": "aes192-ctr",
"aes256-cbc", "notes": {
"arcfour", "info": [
"rijndael-cbc@lysator.liu.se" "available since OpenSSH 3.7"
]
}
},
{
"algorithm": "aes256-ctr",
"notes": {
"info": [
"available since OpenSSH 3.7, Dropbear SSH 0.52"
]
}
},
{
"algorithm": "arcfour256",
"notes": {
"fail": [
"using broken RC4 cipher"
],
"info": [
"available since OpenSSH 4.2"
]
}
},
{
"algorithm": "arcfour128",
"notes": {
"fail": [
"using broken RC4 cipher"
],
"info": [
"available since OpenSSH 4.2"
]
}
},
{
"algorithm": "aes128-cbc",
"notes": {
"info": [
"available since OpenSSH 2.3.0, Dropbear SSH 0.28"
],
"warn": [
"using weak cipher mode"
]
}
},
{
"algorithm": "3des-cbc",
"notes": {
"fail": [
"using broken & deprecated 3DES cipher"
],
"info": [
"available since OpenSSH 1.2.2, Dropbear SSH 0.28"
],
"warn": [
"using weak cipher mode",
"using small 64-bit block size"
]
}
},
{
"algorithm": "blowfish-cbc",
"notes": {
"fail": [
"using weak & deprecated Blowfish cipher"
],
"info": [
"available since OpenSSH 1.2.2, Dropbear SSH 0.28"
],
"warn": [
"using weak cipher mode",
"using small 64-bit block size"
]
}
},
{
"algorithm": "cast128-cbc",
"notes": {
"fail": [
"using weak & deprecated CAST cipher"
],
"info": [
"available since OpenSSH 2.1.0"
],
"warn": [
"using weak cipher mode",
"using small 64-bit block size"
]
}
},
{
"algorithm": "aes192-cbc",
"notes": {
"info": [
"available since OpenSSH 2.3.0"
],
"warn": [
"using weak cipher mode"
]
}
},
{
"algorithm": "aes256-cbc",
"notes": {
"info": [
"available since OpenSSH 2.3.0, Dropbear SSH 0.47"
],
"warn": [
"using weak cipher mode"
]
}
},
{
"algorithm": "arcfour",
"notes": {
"fail": [
"using broken RC4 cipher"
],
"info": [
"available since OpenSSH 2.1.0"
]
}
},
{
"algorithm": "rijndael-cbc@lysator.liu.se",
"notes": {
"fail": [
"using deprecated & non-standardized Rijndael cipher"
],
"info": [
"disabled in OpenSSH 7.0: https://www.openssh.com/txt/release-7.0",
"available since OpenSSH 2.3.0"
],
"warn": [
"using weak cipher mode"
]
}
}
], ],
"fingerprints": [ "fingerprints": [
{ {
@ -119,36 +254,185 @@
"kex": [ "kex": [
{ {
"algorithm": "diffie-hellman-group-exchange-sha256", "algorithm": "diffie-hellman-group-exchange-sha256",
"keysize": 1024 "keysize": 1024,
"notes": {
"fail": [
"using small 1024-bit modulus"
],
"info": [
"available since OpenSSH 4.4"
]
}
}, },
{ {
"algorithm": "diffie-hellman-group-exchange-sha1", "algorithm": "diffie-hellman-group-exchange-sha1",
"keysize": 1024 "keysize": 1024,
"notes": {
"fail": [
"using small 1024-bit modulus"
],
"info": [
"available since OpenSSH 2.3.0"
]
}
}, },
{ {
"algorithm": "diffie-hellman-group14-sha1" "algorithm": "diffie-hellman-group14-sha1",
"notes": {
"fail": [
"using broken SHA-1 hash algorithm"
],
"info": [
"available since OpenSSH 3.9, Dropbear SSH 0.53"
],
"warn": [
"2048-bit modulus only provides 112-bits of symmetric strength"
]
}
}, },
{ {
"algorithm": "diffie-hellman-group1-sha1" "algorithm": "diffie-hellman-group1-sha1",
"notes": {
"fail": [
"using small 1024-bit modulus",
"vulnerable to the Logjam attack: https://en.wikipedia.org/wiki/Logjam_(computer_security)",
"using broken SHA-1 hash algorithm"
],
"info": [
"removed in OpenSSH 6.9: https://www.openssh.com/txt/release-6.9",
"available since OpenSSH 2.3.0, Dropbear SSH 0.28"
]
}
} }
], ],
"key": [ "key": [
{ {
"algorithm": "ssh-rsa", "algorithm": "ssh-rsa",
"keysize": 1024 "keysize": 1024,
"notes": {
"fail": [
"using broken SHA-1 hash algorithm",
"using small 1024-bit modulus"
],
"info": [
"deprecated in OpenSSH 8.8: https://www.openssh.com/txt/release-8.8",
"available since OpenSSH 2.5.0, Dropbear SSH 0.28"
]
}
}, },
{ {
"algorithm": "ssh-dss" "algorithm": "ssh-dss",
"notes": {
"fail": [
"using small 1024-bit modulus"
],
"info": [
"disabled in OpenSSH 7.0: https://www.openssh.com/txt/release-7.0",
"available since OpenSSH 2.1.0, Dropbear SSH 0.28"
],
"warn": [
"using weak random number generator could reveal the key"
]
}
} }
], ],
"mac": [ "mac": [
"hmac-md5", {
"hmac-sha1", "algorithm": "hmac-md5",
"umac-64@openssh.com", "notes": {
"hmac-ripemd160", "fail": [
"hmac-ripemd160@openssh.com", "using broken MD5 hash algorithm"
"hmac-sha1-96", ],
"hmac-md5-96" "info": [
"available since OpenSSH 2.1.0, Dropbear SSH 0.28"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
},
{
"algorithm": "hmac-sha1",
"notes": {
"fail": [
"using broken SHA-1 hash algorithm"
],
"info": [
"available since OpenSSH 2.1.0, Dropbear SSH 0.28"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
},
{
"algorithm": "umac-64@openssh.com",
"notes": {
"info": [
"available since OpenSSH 4.7"
],
"warn": [
"using encrypt-and-MAC mode",
"using small 64-bit tag size"
]
}
},
{
"algorithm": "hmac-ripemd160",
"notes": {
"fail": [
"using deprecated RIPEMD hash algorithm"
],
"info": [
"available since OpenSSH 2.5.0"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
},
{
"algorithm": "hmac-ripemd160@openssh.com",
"notes": {
"fail": [
"using deprecated RIPEMD hash algorithm"
],
"info": [
"available since OpenSSH 2.1.0"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
},
{
"algorithm": "hmac-sha1-96",
"notes": {
"fail": [
"using broken SHA-1 hash algorithm"
],
"info": [
"available since OpenSSH 2.5.0, Dropbear SSH 0.47"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
},
{
"algorithm": "hmac-md5-96",
"notes": {
"fail": [
"using broken MD5 hash algorithm"
],
"info": [
"available since OpenSSH 2.5.0"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
}
], ],
"recommendations": { "recommendations": {
"critical": { "critical": {

View File

@ -1,10 +1,8 @@
{ {
"additional_notes": [],
"banner": { "banner": {
"comments": null, "comments": null,
"protocol": [ "protocol": "2.0",
2,
0
],
"raw": "SSH-2.0-OpenSSH_5.6", "raw": "SSH-2.0-OpenSSH_5.6",
"software": "OpenSSH_5.6" "software": "OpenSSH_5.6"
}, },
@ -90,19 +88,156 @@
} }
], ],
"enc": [ "enc": [
"aes128-ctr", {
"aes192-ctr", "algorithm": "aes128-ctr",
"aes256-ctr", "notes": {
"arcfour256", "info": [
"arcfour128", "available since OpenSSH 3.7, Dropbear SSH 0.52"
"aes128-cbc", ]
"3des-cbc", }
"blowfish-cbc", },
"cast128-cbc", {
"aes192-cbc", "algorithm": "aes192-ctr",
"aes256-cbc", "notes": {
"arcfour", "info": [
"rijndael-cbc@lysator.liu.se" "available since OpenSSH 3.7"
]
}
},
{
"algorithm": "aes256-ctr",
"notes": {
"info": [
"available since OpenSSH 3.7, Dropbear SSH 0.52"
]
}
},
{
"algorithm": "arcfour256",
"notes": {
"fail": [
"using broken RC4 cipher"
],
"info": [
"available since OpenSSH 4.2"
]
}
},
{
"algorithm": "arcfour128",
"notes": {
"fail": [
"using broken RC4 cipher"
],
"info": [
"available since OpenSSH 4.2"
]
}
},
{
"algorithm": "aes128-cbc",
"notes": {
"info": [
"available since OpenSSH 2.3.0, Dropbear SSH 0.28"
],
"warn": [
"using weak cipher mode"
]
}
},
{
"algorithm": "3des-cbc",
"notes": {
"fail": [
"using broken & deprecated 3DES cipher"
],
"info": [
"available since OpenSSH 1.2.2, Dropbear SSH 0.28"
],
"warn": [
"using weak cipher mode",
"using small 64-bit block size"
]
}
},
{
"algorithm": "blowfish-cbc",
"notes": {
"fail": [
"using weak & deprecated Blowfish cipher"
],
"info": [
"available since OpenSSH 1.2.2, Dropbear SSH 0.28"
],
"warn": [
"using weak cipher mode",
"using small 64-bit block size"
]
}
},
{
"algorithm": "cast128-cbc",
"notes": {
"fail": [
"using weak & deprecated CAST cipher"
],
"info": [
"available since OpenSSH 2.1.0"
],
"warn": [
"using weak cipher mode",
"using small 64-bit block size"
]
}
},
{
"algorithm": "aes192-cbc",
"notes": {
"info": [
"available since OpenSSH 2.3.0"
],
"warn": [
"using weak cipher mode"
]
}
},
{
"algorithm": "aes256-cbc",
"notes": {
"info": [
"available since OpenSSH 2.3.0, Dropbear SSH 0.47"
],
"warn": [
"using weak cipher mode"
]
}
},
{
"algorithm": "arcfour",
"notes": {
"fail": [
"using broken RC4 cipher"
],
"info": [
"available since OpenSSH 2.1.0"
]
}
},
{
"algorithm": "rijndael-cbc@lysator.liu.se",
"notes": {
"fail": [
"using deprecated & non-standardized Rijndael cipher"
],
"info": [
"disabled in OpenSSH 7.0: https://www.openssh.com/txt/release-7.0",
"available since OpenSSH 2.3.0"
],
"warn": [
"using weak cipher mode"
]
}
}
], ],
"fingerprints": [ "fingerprints": [
{ {
@ -119,39 +254,187 @@
"kex": [ "kex": [
{ {
"algorithm": "diffie-hellman-group-exchange-sha256", "algorithm": "diffie-hellman-group-exchange-sha256",
"keysize": 1024 "keysize": 1024,
"notes": {
"fail": [
"using small 1024-bit modulus"
],
"info": [
"available since OpenSSH 4.4"
]
}
}, },
{ {
"algorithm": "diffie-hellman-group-exchange-sha1", "algorithm": "diffie-hellman-group-exchange-sha1",
"keysize": 1024 "keysize": 1024,
"notes": {
"fail": [
"using small 1024-bit modulus"
],
"info": [
"available since OpenSSH 2.3.0"
]
}
}, },
{ {
"algorithm": "diffie-hellman-group14-sha1" "algorithm": "diffie-hellman-group14-sha1",
"notes": {
"fail": [
"using broken SHA-1 hash algorithm"
],
"info": [
"available since OpenSSH 3.9, Dropbear SSH 0.53"
],
"warn": [
"2048-bit modulus only provides 112-bits of symmetric strength"
]
}
}, },
{ {
"algorithm": "diffie-hellman-group1-sha1" "algorithm": "diffie-hellman-group1-sha1",
"notes": {
"fail": [
"using small 1024-bit modulus",
"vulnerable to the Logjam attack: https://en.wikipedia.org/wiki/Logjam_(computer_security)",
"using broken SHA-1 hash algorithm"
],
"info": [
"removed in OpenSSH 6.9: https://www.openssh.com/txt/release-6.9",
"available since OpenSSH 2.3.0, Dropbear SSH 0.28"
]
}
} }
], ],
"key": [ "key": [
{ {
"algorithm": "ssh-rsa", "algorithm": "ssh-rsa",
"keysize": 1024 "keysize": 1024,
"notes": {
"fail": [
"using broken SHA-1 hash algorithm",
"using small 1024-bit modulus"
],
"info": [
"deprecated in OpenSSH 8.8: https://www.openssh.com/txt/release-8.8",
"available since OpenSSH 2.5.0, Dropbear SSH 0.28"
]
}
}, },
{ {
"algorithm": "ssh-rsa-cert-v01@openssh.com", "algorithm": "ssh-rsa-cert-v01@openssh.com",
"ca_algorithm": "ssh-rsa", "ca_algorithm": "ssh-rsa",
"casize": 1024, "casize": 1024,
"keysize": 1024 "keysize": 1024,
"notes": {
"fail": [
"using broken SHA-1 hash algorithm",
"using small 1024-bit hostkey modulus",
"using small 1024-bit CA key modulus"
],
"info": [
"deprecated in OpenSSH 8.8: https://www.openssh.com/txt/release-8.8",
"available since OpenSSH 5.6"
]
}
} }
], ],
"mac": [ "mac": [
"hmac-md5", {
"hmac-sha1", "algorithm": "hmac-md5",
"umac-64@openssh.com", "notes": {
"hmac-ripemd160", "fail": [
"hmac-ripemd160@openssh.com", "using broken MD5 hash algorithm"
"hmac-sha1-96", ],
"hmac-md5-96" "info": [
"available since OpenSSH 2.1.0, Dropbear SSH 0.28"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
},
{
"algorithm": "hmac-sha1",
"notes": {
"fail": [
"using broken SHA-1 hash algorithm"
],
"info": [
"available since OpenSSH 2.1.0, Dropbear SSH 0.28"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
},
{
"algorithm": "umac-64@openssh.com",
"notes": {
"info": [
"available since OpenSSH 4.7"
],
"warn": [
"using encrypt-and-MAC mode",
"using small 64-bit tag size"
]
}
},
{
"algorithm": "hmac-ripemd160",
"notes": {
"fail": [
"using deprecated RIPEMD hash algorithm"
],
"info": [
"available since OpenSSH 2.5.0"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
},
{
"algorithm": "hmac-ripemd160@openssh.com",
"notes": {
"fail": [
"using deprecated RIPEMD hash algorithm"
],
"info": [
"available since OpenSSH 2.1.0"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
},
{
"algorithm": "hmac-sha1-96",
"notes": {
"fail": [
"using broken SHA-1 hash algorithm"
],
"info": [
"available since OpenSSH 2.5.0, Dropbear SSH 0.47"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
},
{
"algorithm": "hmac-md5-96",
"notes": {
"fail": [
"using broken MD5 hash algorithm"
],
"info": [
"available since OpenSSH 2.5.0"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
}
], ],
"recommendations": { "recommendations": {
"critical": { "critical": {

View File

@ -1,10 +1,8 @@
{ {
"additional_notes": [],
"banner": { "banner": {
"comments": null, "comments": null,
"protocol": [ "protocol": "2.0",
2,
0
],
"raw": "SSH-2.0-OpenSSH_5.6", "raw": "SSH-2.0-OpenSSH_5.6",
"software": "OpenSSH_5.6" "software": "OpenSSH_5.6"
}, },
@ -90,19 +88,156 @@
} }
], ],
"enc": [ "enc": [
"aes128-ctr", {
"aes192-ctr", "algorithm": "aes128-ctr",
"aes256-ctr", "notes": {
"arcfour256", "info": [
"arcfour128", "available since OpenSSH 3.7, Dropbear SSH 0.52"
"aes128-cbc", ]
"3des-cbc", }
"blowfish-cbc", },
"cast128-cbc", {
"aes192-cbc", "algorithm": "aes192-ctr",
"aes256-cbc", "notes": {
"arcfour", "info": [
"rijndael-cbc@lysator.liu.se" "available since OpenSSH 3.7"
]
}
},
{
"algorithm": "aes256-ctr",
"notes": {
"info": [
"available since OpenSSH 3.7, Dropbear SSH 0.52"
]
}
},
{
"algorithm": "arcfour256",
"notes": {
"fail": [
"using broken RC4 cipher"
],
"info": [
"available since OpenSSH 4.2"
]
}
},
{
"algorithm": "arcfour128",
"notes": {
"fail": [
"using broken RC4 cipher"
],
"info": [
"available since OpenSSH 4.2"
]
}
},
{
"algorithm": "aes128-cbc",
"notes": {
"info": [
"available since OpenSSH 2.3.0, Dropbear SSH 0.28"
],
"warn": [
"using weak cipher mode"
]
}
},
{
"algorithm": "3des-cbc",
"notes": {
"fail": [
"using broken & deprecated 3DES cipher"
],
"info": [
"available since OpenSSH 1.2.2, Dropbear SSH 0.28"
],
"warn": [
"using weak cipher mode",
"using small 64-bit block size"
]
}
},
{
"algorithm": "blowfish-cbc",
"notes": {
"fail": [
"using weak & deprecated Blowfish cipher"
],
"info": [
"available since OpenSSH 1.2.2, Dropbear SSH 0.28"
],
"warn": [
"using weak cipher mode",
"using small 64-bit block size"
]
}
},
{
"algorithm": "cast128-cbc",
"notes": {
"fail": [
"using weak & deprecated CAST cipher"
],
"info": [
"available since OpenSSH 2.1.0"
],
"warn": [
"using weak cipher mode",
"using small 64-bit block size"
]
}
},
{
"algorithm": "aes192-cbc",
"notes": {
"info": [
"available since OpenSSH 2.3.0"
],
"warn": [
"using weak cipher mode"
]
}
},
{
"algorithm": "aes256-cbc",
"notes": {
"info": [
"available since OpenSSH 2.3.0, Dropbear SSH 0.47"
],
"warn": [
"using weak cipher mode"
]
}
},
{
"algorithm": "arcfour",
"notes": {
"fail": [
"using broken RC4 cipher"
],
"info": [
"available since OpenSSH 2.1.0"
]
}
},
{
"algorithm": "rijndael-cbc@lysator.liu.se",
"notes": {
"fail": [
"using deprecated & non-standardized Rijndael cipher"
],
"info": [
"disabled in OpenSSH 7.0: https://www.openssh.com/txt/release-7.0",
"available since OpenSSH 2.3.0"
],
"warn": [
"using weak cipher mode"
]
}
}
], ],
"fingerprints": [ "fingerprints": [
{ {
@ -119,39 +254,186 @@
"kex": [ "kex": [
{ {
"algorithm": "diffie-hellman-group-exchange-sha256", "algorithm": "diffie-hellman-group-exchange-sha256",
"keysize": 1024 "keysize": 1024,
"notes": {
"fail": [
"using small 1024-bit modulus"
],
"info": [
"available since OpenSSH 4.4"
]
}
}, },
{ {
"algorithm": "diffie-hellman-group-exchange-sha1", "algorithm": "diffie-hellman-group-exchange-sha1",
"keysize": 1024 "keysize": 1024,
"notes": {
"fail": [
"using small 1024-bit modulus"
],
"info": [
"available since OpenSSH 2.3.0"
]
}
}, },
{ {
"algorithm": "diffie-hellman-group14-sha1" "algorithm": "diffie-hellman-group14-sha1",
"notes": {
"fail": [
"using broken SHA-1 hash algorithm"
],
"info": [
"available since OpenSSH 3.9, Dropbear SSH 0.53"
],
"warn": [
"2048-bit modulus only provides 112-bits of symmetric strength"
]
}
}, },
{ {
"algorithm": "diffie-hellman-group1-sha1" "algorithm": "diffie-hellman-group1-sha1",
"notes": {
"fail": [
"using small 1024-bit modulus",
"vulnerable to the Logjam attack: https://en.wikipedia.org/wiki/Logjam_(computer_security)",
"using broken SHA-1 hash algorithm"
],
"info": [
"removed in OpenSSH 6.9: https://www.openssh.com/txt/release-6.9",
"available since OpenSSH 2.3.0, Dropbear SSH 0.28"
]
}
} }
], ],
"key": [ "key": [
{ {
"algorithm": "ssh-rsa", "algorithm": "ssh-rsa",
"keysize": 1024 "keysize": 1024,
"notes": {
"fail": [
"using broken SHA-1 hash algorithm",
"using small 1024-bit modulus"
],
"info": [
"deprecated in OpenSSH 8.8: https://www.openssh.com/txt/release-8.8",
"available since OpenSSH 2.5.0, Dropbear SSH 0.28"
]
}
}, },
{ {
"algorithm": "ssh-rsa-cert-v01@openssh.com", "algorithm": "ssh-rsa-cert-v01@openssh.com",
"ca_algorithm": "ssh-rsa", "ca_algorithm": "ssh-rsa",
"casize": 3072, "casize": 3072,
"keysize": 1024 "keysize": 1024,
"notes": {
"fail": [
"using broken SHA-1 hash algorithm",
"using small 1024-bit hostkey modulus"
],
"info": [
"deprecated in OpenSSH 8.8: https://www.openssh.com/txt/release-8.8",
"available since OpenSSH 5.6"
]
}
} }
], ],
"mac": [ "mac": [
"hmac-md5", {
"hmac-sha1", "algorithm": "hmac-md5",
"umac-64@openssh.com", "notes": {
"hmac-ripemd160", "fail": [
"hmac-ripemd160@openssh.com", "using broken MD5 hash algorithm"
"hmac-sha1-96", ],
"hmac-md5-96" "info": [
"available since OpenSSH 2.1.0, Dropbear SSH 0.28"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
},
{
"algorithm": "hmac-sha1",
"notes": {
"fail": [
"using broken SHA-1 hash algorithm"
],
"info": [
"available since OpenSSH 2.1.0, Dropbear SSH 0.28"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
},
{
"algorithm": "umac-64@openssh.com",
"notes": {
"info": [
"available since OpenSSH 4.7"
],
"warn": [
"using encrypt-and-MAC mode",
"using small 64-bit tag size"
]
}
},
{
"algorithm": "hmac-ripemd160",
"notes": {
"fail": [
"using deprecated RIPEMD hash algorithm"
],
"info": [
"available since OpenSSH 2.5.0"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
},
{
"algorithm": "hmac-ripemd160@openssh.com",
"notes": {
"fail": [
"using deprecated RIPEMD hash algorithm"
],
"info": [
"available since OpenSSH 2.1.0"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
},
{
"algorithm": "hmac-sha1-96",
"notes": {
"fail": [
"using broken SHA-1 hash algorithm"
],
"info": [
"available since OpenSSH 2.5.0, Dropbear SSH 0.47"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
},
{
"algorithm": "hmac-md5-96",
"notes": {
"fail": [
"using broken MD5 hash algorithm"
],
"info": [
"available since OpenSSH 2.5.0"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
}
], ],
"recommendations": { "recommendations": {
"critical": { "critical": {

View File

@ -1,10 +1,8 @@
{ {
"additional_notes": [],
"banner": { "banner": {
"comments": null, "comments": null,
"protocol": [ "protocol": "2.0",
2,
0
],
"raw": "SSH-2.0-OpenSSH_5.6", "raw": "SSH-2.0-OpenSSH_5.6",
"software": "OpenSSH_5.6" "software": "OpenSSH_5.6"
}, },
@ -90,19 +88,156 @@
} }
], ],
"enc": [ "enc": [
"aes128-ctr", {
"aes192-ctr", "algorithm": "aes128-ctr",
"aes256-ctr", "notes": {
"arcfour256", "info": [
"arcfour128", "available since OpenSSH 3.7, Dropbear SSH 0.52"
"aes128-cbc", ]
"3des-cbc", }
"blowfish-cbc", },
"cast128-cbc", {
"aes192-cbc", "algorithm": "aes192-ctr",
"aes256-cbc", "notes": {
"arcfour", "info": [
"rijndael-cbc@lysator.liu.se" "available since OpenSSH 3.7"
]
}
},
{
"algorithm": "aes256-ctr",
"notes": {
"info": [
"available since OpenSSH 3.7, Dropbear SSH 0.52"
]
}
},
{
"algorithm": "arcfour256",
"notes": {
"fail": [
"using broken RC4 cipher"
],
"info": [
"available since OpenSSH 4.2"
]
}
},
{
"algorithm": "arcfour128",
"notes": {
"fail": [
"using broken RC4 cipher"
],
"info": [
"available since OpenSSH 4.2"
]
}
},
{
"algorithm": "aes128-cbc",
"notes": {
"info": [
"available since OpenSSH 2.3.0, Dropbear SSH 0.28"
],
"warn": [
"using weak cipher mode"
]
}
},
{
"algorithm": "3des-cbc",
"notes": {
"fail": [
"using broken & deprecated 3DES cipher"
],
"info": [
"available since OpenSSH 1.2.2, Dropbear SSH 0.28"
],
"warn": [
"using weak cipher mode",
"using small 64-bit block size"
]
}
},
{
"algorithm": "blowfish-cbc",
"notes": {
"fail": [
"using weak & deprecated Blowfish cipher"
],
"info": [
"available since OpenSSH 1.2.2, Dropbear SSH 0.28"
],
"warn": [
"using weak cipher mode",
"using small 64-bit block size"
]
}
},
{
"algorithm": "cast128-cbc",
"notes": {
"fail": [
"using weak & deprecated CAST cipher"
],
"info": [
"available since OpenSSH 2.1.0"
],
"warn": [
"using weak cipher mode",
"using small 64-bit block size"
]
}
},
{
"algorithm": "aes192-cbc",
"notes": {
"info": [
"available since OpenSSH 2.3.0"
],
"warn": [
"using weak cipher mode"
]
}
},
{
"algorithm": "aes256-cbc",
"notes": {
"info": [
"available since OpenSSH 2.3.0, Dropbear SSH 0.47"
],
"warn": [
"using weak cipher mode"
]
}
},
{
"algorithm": "arcfour",
"notes": {
"fail": [
"using broken RC4 cipher"
],
"info": [
"available since OpenSSH 2.1.0"
]
}
},
{
"algorithm": "rijndael-cbc@lysator.liu.se",
"notes": {
"fail": [
"using deprecated & non-standardized Rijndael cipher"
],
"info": [
"disabled in OpenSSH 7.0: https://www.openssh.com/txt/release-7.0",
"available since OpenSSH 2.3.0"
],
"warn": [
"using weak cipher mode"
]
}
}
], ],
"fingerprints": [ "fingerprints": [
{ {
@ -119,39 +254,185 @@
"kex": [ "kex": [
{ {
"algorithm": "diffie-hellman-group-exchange-sha256", "algorithm": "diffie-hellman-group-exchange-sha256",
"keysize": 1024 "keysize": 1024,
"notes": {
"fail": [
"using small 1024-bit modulus"
],
"info": [
"available since OpenSSH 4.4"
]
}
}, },
{ {
"algorithm": "diffie-hellman-group-exchange-sha1", "algorithm": "diffie-hellman-group-exchange-sha1",
"keysize": 1024 "keysize": 1024,
"notes": {
"fail": [
"using small 1024-bit modulus"
],
"info": [
"available since OpenSSH 2.3.0"
]
}
}, },
{ {
"algorithm": "diffie-hellman-group14-sha1" "algorithm": "diffie-hellman-group14-sha1",
"notes": {
"fail": [
"using broken SHA-1 hash algorithm"
],
"info": [
"available since OpenSSH 3.9, Dropbear SSH 0.53"
],
"warn": [
"2048-bit modulus only provides 112-bits of symmetric strength"
]
}
}, },
{ {
"algorithm": "diffie-hellman-group1-sha1" "algorithm": "diffie-hellman-group1-sha1",
"notes": {
"fail": [
"using small 1024-bit modulus",
"vulnerable to the Logjam attack: https://en.wikipedia.org/wiki/Logjam_(computer_security)",
"using broken SHA-1 hash algorithm"
],
"info": [
"removed in OpenSSH 6.9: https://www.openssh.com/txt/release-6.9",
"available since OpenSSH 2.3.0, Dropbear SSH 0.28"
]
}
} }
], ],
"key": [ "key": [
{ {
"algorithm": "ssh-rsa", "algorithm": "ssh-rsa",
"keysize": 3072 "keysize": 3072,
"notes": {
"fail": [
"using broken SHA-1 hash algorithm"
],
"info": [
"deprecated in OpenSSH 8.8: https://www.openssh.com/txt/release-8.8",
"available since OpenSSH 2.5.0, Dropbear SSH 0.28"
]
}
}, },
{ {
"algorithm": "ssh-rsa-cert-v01@openssh.com", "algorithm": "ssh-rsa-cert-v01@openssh.com",
"ca_algorithm": "ssh-rsa", "ca_algorithm": "ssh-rsa",
"casize": 1024, "casize": 1024,
"keysize": 3072 "keysize": 3072,
"notes": {
"fail": [
"using broken SHA-1 hash algorithm",
"using small 1024-bit CA key modulus"
],
"info": [
"deprecated in OpenSSH 8.8: https://www.openssh.com/txt/release-8.8",
"available since OpenSSH 5.6"
]
}
} }
], ],
"mac": [ "mac": [
"hmac-md5", {
"hmac-sha1", "algorithm": "hmac-md5",
"umac-64@openssh.com", "notes": {
"hmac-ripemd160", "fail": [
"hmac-ripemd160@openssh.com", "using broken MD5 hash algorithm"
"hmac-sha1-96", ],
"hmac-md5-96" "info": [
"available since OpenSSH 2.1.0, Dropbear SSH 0.28"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
},
{
"algorithm": "hmac-sha1",
"notes": {
"fail": [
"using broken SHA-1 hash algorithm"
],
"info": [
"available since OpenSSH 2.1.0, Dropbear SSH 0.28"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
},
{
"algorithm": "umac-64@openssh.com",
"notes": {
"info": [
"available since OpenSSH 4.7"
],
"warn": [
"using encrypt-and-MAC mode",
"using small 64-bit tag size"
]
}
},
{
"algorithm": "hmac-ripemd160",
"notes": {
"fail": [
"using deprecated RIPEMD hash algorithm"
],
"info": [
"available since OpenSSH 2.5.0"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
},
{
"algorithm": "hmac-ripemd160@openssh.com",
"notes": {
"fail": [
"using deprecated RIPEMD hash algorithm"
],
"info": [
"available since OpenSSH 2.1.0"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
},
{
"algorithm": "hmac-sha1-96",
"notes": {
"fail": [
"using broken SHA-1 hash algorithm"
],
"info": [
"available since OpenSSH 2.5.0, Dropbear SSH 0.47"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
},
{
"algorithm": "hmac-md5-96",
"notes": {
"fail": [
"using broken MD5 hash algorithm"
],
"info": [
"available since OpenSSH 2.5.0"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
}
], ],
"recommendations": { "recommendations": {
"critical": { "critical": {

View File

@ -1,10 +1,8 @@
{ {
"additional_notes": [],
"banner": { "banner": {
"comments": null, "comments": null,
"protocol": [ "protocol": "2.0",
2,
0
],
"raw": "SSH-2.0-OpenSSH_5.6", "raw": "SSH-2.0-OpenSSH_5.6",
"software": "OpenSSH_5.6" "software": "OpenSSH_5.6"
}, },
@ -90,19 +88,156 @@
} }
], ],
"enc": [ "enc": [
"aes128-ctr", {
"aes192-ctr", "algorithm": "aes128-ctr",
"aes256-ctr", "notes": {
"arcfour256", "info": [
"arcfour128", "available since OpenSSH 3.7, Dropbear SSH 0.52"
"aes128-cbc", ]
"3des-cbc", }
"blowfish-cbc", },
"cast128-cbc", {
"aes192-cbc", "algorithm": "aes192-ctr",
"aes256-cbc", "notes": {
"arcfour", "info": [
"rijndael-cbc@lysator.liu.se" "available since OpenSSH 3.7"
]
}
},
{
"algorithm": "aes256-ctr",
"notes": {
"info": [
"available since OpenSSH 3.7, Dropbear SSH 0.52"
]
}
},
{
"algorithm": "arcfour256",
"notes": {
"fail": [
"using broken RC4 cipher"
],
"info": [
"available since OpenSSH 4.2"
]
}
},
{
"algorithm": "arcfour128",
"notes": {
"fail": [
"using broken RC4 cipher"
],
"info": [
"available since OpenSSH 4.2"
]
}
},
{
"algorithm": "aes128-cbc",
"notes": {
"info": [
"available since OpenSSH 2.3.0, Dropbear SSH 0.28"
],
"warn": [
"using weak cipher mode"
]
}
},
{
"algorithm": "3des-cbc",
"notes": {
"fail": [
"using broken & deprecated 3DES cipher"
],
"info": [
"available since OpenSSH 1.2.2, Dropbear SSH 0.28"
],
"warn": [
"using weak cipher mode",
"using small 64-bit block size"
]
}
},
{
"algorithm": "blowfish-cbc",
"notes": {
"fail": [
"using weak & deprecated Blowfish cipher"
],
"info": [
"available since OpenSSH 1.2.2, Dropbear SSH 0.28"
],
"warn": [
"using weak cipher mode",
"using small 64-bit block size"
]
}
},
{
"algorithm": "cast128-cbc",
"notes": {
"fail": [
"using weak & deprecated CAST cipher"
],
"info": [
"available since OpenSSH 2.1.0"
],
"warn": [
"using weak cipher mode",
"using small 64-bit block size"
]
}
},
{
"algorithm": "aes192-cbc",
"notes": {
"info": [
"available since OpenSSH 2.3.0"
],
"warn": [
"using weak cipher mode"
]
}
},
{
"algorithm": "aes256-cbc",
"notes": {
"info": [
"available since OpenSSH 2.3.0, Dropbear SSH 0.47"
],
"warn": [
"using weak cipher mode"
]
}
},
{
"algorithm": "arcfour",
"notes": {
"fail": [
"using broken RC4 cipher"
],
"info": [
"available since OpenSSH 2.1.0"
]
}
},
{
"algorithm": "rijndael-cbc@lysator.liu.se",
"notes": {
"fail": [
"using deprecated & non-standardized Rijndael cipher"
],
"info": [
"disabled in OpenSSH 7.0: https://www.openssh.com/txt/release-7.0",
"available since OpenSSH 2.3.0"
],
"warn": [
"using weak cipher mode"
]
}
}
], ],
"fingerprints": [ "fingerprints": [
{ {
@ -119,39 +254,184 @@
"kex": [ "kex": [
{ {
"algorithm": "diffie-hellman-group-exchange-sha256", "algorithm": "diffie-hellman-group-exchange-sha256",
"keysize": 1024 "keysize": 1024,
"notes": {
"fail": [
"using small 1024-bit modulus"
],
"info": [
"available since OpenSSH 4.4"
]
}
}, },
{ {
"algorithm": "diffie-hellman-group-exchange-sha1", "algorithm": "diffie-hellman-group-exchange-sha1",
"keysize": 1024 "keysize": 1024,
"notes": {
"fail": [
"using small 1024-bit modulus"
],
"info": [
"available since OpenSSH 2.3.0"
]
}
}, },
{ {
"algorithm": "diffie-hellman-group14-sha1" "algorithm": "diffie-hellman-group14-sha1",
"notes": {
"fail": [
"using broken SHA-1 hash algorithm"
],
"info": [
"available since OpenSSH 3.9, Dropbear SSH 0.53"
],
"warn": [
"2048-bit modulus only provides 112-bits of symmetric strength"
]
}
}, },
{ {
"algorithm": "diffie-hellman-group1-sha1" "algorithm": "diffie-hellman-group1-sha1",
"notes": {
"fail": [
"using small 1024-bit modulus",
"vulnerable to the Logjam attack: https://en.wikipedia.org/wiki/Logjam_(computer_security)",
"using broken SHA-1 hash algorithm"
],
"info": [
"removed in OpenSSH 6.9: https://www.openssh.com/txt/release-6.9",
"available since OpenSSH 2.3.0, Dropbear SSH 0.28"
]
}
} }
], ],
"key": [ "key": [
{ {
"algorithm": "ssh-rsa", "algorithm": "ssh-rsa",
"keysize": 3072 "keysize": 3072,
"notes": {
"fail": [
"using broken SHA-1 hash algorithm"
],
"info": [
"deprecated in OpenSSH 8.8: https://www.openssh.com/txt/release-8.8",
"available since OpenSSH 2.5.0, Dropbear SSH 0.28"
]
}
}, },
{ {
"algorithm": "ssh-rsa-cert-v01@openssh.com", "algorithm": "ssh-rsa-cert-v01@openssh.com",
"ca_algorithm": "ssh-rsa", "ca_algorithm": "ssh-rsa",
"casize": 3072, "casize": 3072,
"keysize": 3072 "keysize": 3072,
"notes": {
"fail": [
"using broken SHA-1 hash algorithm"
],
"info": [
"deprecated in OpenSSH 8.8: https://www.openssh.com/txt/release-8.8",
"available since OpenSSH 5.6"
]
}
} }
], ],
"mac": [ "mac": [
"hmac-md5", {
"hmac-sha1", "algorithm": "hmac-md5",
"umac-64@openssh.com", "notes": {
"hmac-ripemd160", "fail": [
"hmac-ripemd160@openssh.com", "using broken MD5 hash algorithm"
"hmac-sha1-96", ],
"hmac-md5-96" "info": [
"available since OpenSSH 2.1.0, Dropbear SSH 0.28"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
},
{
"algorithm": "hmac-sha1",
"notes": {
"fail": [
"using broken SHA-1 hash algorithm"
],
"info": [
"available since OpenSSH 2.1.0, Dropbear SSH 0.28"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
},
{
"algorithm": "umac-64@openssh.com",
"notes": {
"info": [
"available since OpenSSH 4.7"
],
"warn": [
"using encrypt-and-MAC mode",
"using small 64-bit tag size"
]
}
},
{
"algorithm": "hmac-ripemd160",
"notes": {
"fail": [
"using deprecated RIPEMD hash algorithm"
],
"info": [
"available since OpenSSH 2.5.0"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
},
{
"algorithm": "hmac-ripemd160@openssh.com",
"notes": {
"fail": [
"using deprecated RIPEMD hash algorithm"
],
"info": [
"available since OpenSSH 2.1.0"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
},
{
"algorithm": "hmac-sha1-96",
"notes": {
"fail": [
"using broken SHA-1 hash algorithm"
],
"info": [
"available since OpenSSH 2.5.0, Dropbear SSH 0.47"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
},
{
"algorithm": "hmac-md5-96",
"notes": {
"fail": [
"using broken MD5 hash algorithm"
],
"info": [
"available since OpenSSH 2.5.0"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
}
], ],
"recommendations": { "recommendations": {
"critical": { "critical": {

View File

@ -23,9 +23,21 @@
"4096" "4096"
], ],
"mismatched_field": "Host key (rsa-sha2-512) sizes" "mismatched_field": "Host key (rsa-sha2-512) sizes"
},
{
"actual": [
"4096"
],
"expected_optional": [
""
],
"expected_required": [
"3072"
],
"mismatched_field": "Group exchange (diffie-hellman-group-exchange-sha256) modulus sizes"
} }
], ],
"host": "localhost", "host": "localhost",
"passed": false, "passed": false,
"policy": "Hardened OpenSSH Server v8.0 (version 2)" "policy": "Hardened OpenSSH Server v8.0 (version 4)"
} }

View File

@ -1,8 +1,12 @@
Host: localhost:2222 Host: localhost:2222
Policy: Hardened OpenSSH Server v8.0 (version 2) Policy: Hardened OpenSSH Server v8.0 (version 4)
Result: ❌ Failed! Result: ❌ Failed!
 
Errors: Errors:
* Group exchange (diffie-hellman-group-exchange-sha256) modulus sizes did not match.
- Expected: 3072
- Actual: 4096
* Host key (rsa-sha2-256) sizes did not match. * Host key (rsa-sha2-256) sizes did not match.
- Expected: 4096 - Expected: 4096
- Actual: 3072 - Actual: 3072

View File

@ -46,9 +46,21 @@
"umac-128-etm@openssh.com" "umac-128-etm@openssh.com"
], ],
"mismatched_field": "MACs" "mismatched_field": "MACs"
},
{
"actual": [
"4096"
],
"expected_optional": [
""
],
"expected_required": [
"3072"
],
"mismatched_field": "Group exchange (diffie-hellman-group-exchange-sha256) modulus sizes"
} }
], ],
"host": "localhost", "host": "localhost",
"passed": false, "passed": false,
"policy": "Hardened OpenSSH Server v8.0 (version 2)" "policy": "Hardened OpenSSH Server v8.0 (version 4)"
} }

View File

@ -1,8 +1,12 @@
Host: localhost:2222 Host: localhost:2222
Policy: Hardened OpenSSH Server v8.0 (version 2) Policy: Hardened OpenSSH Server v8.0 (version 4)
Result: ❌ Failed! Result: ❌ Failed!
 
Errors: Errors:
* Group exchange (diffie-hellman-group-exchange-sha256) modulus sizes did not match.
- Expected: 3072
- Actual: 4096
* Host key (rsa-sha2-256) sizes did not match. * Host key (rsa-sha2-256) sizes did not match.
- Expected: 4096 - Expected: 4096
- Actual: 3072 - Actual: 3072

View File

@ -2,13 +2,13 @@
"errors": [ "errors": [
{ {
"actual": [ "actual": [
"2048" "4096"
], ],
"expected_optional": [ "expected_optional": [
"" ""
], ],
"expected_required": [ "expected_required": [
"4096" "8192"
], ],
"mismatched_field": "Group exchange (diffie-hellman-group-exchange-sha256) modulus sizes" "mismatched_field": "Group exchange (diffie-hellman-group-exchange-sha256) modulus sizes"
} }

View File

@ -16,6 +16,6 @@ Result: ❌ Failed!
 
Errors: Errors:
* Group exchange (diffie-hellman-group-exchange-sha256) modulus sizes did not match. * Group exchange (diffie-hellman-group-exchange-sha256) modulus sizes did not match.
- Expected: 4096 - Expected: 8192
- Actual: 2048 - Actual: 4096
 

View File

@ -0,0 +1,6 @@
{
"errors": [],
"host": "localhost",
"passed": true,
"policy": "Docker policy: test15 (version 1)"
}

View File

@ -0,0 +1,3 @@
Host: localhost:2222
Policy: Docker policy: test15 (version 1)
Result: ✔ Passed

View File

@ -0,0 +1,86 @@
{
"errors": [
{
"actual": [
"rsa-sha2-512",
"rsa-sha2-256",
"ssh-rsa",
"ecdsa-sha2-nistp256",
"ssh-ed25519"
],
"expected_optional": [
""
],
"expected_required": [
"rsa-sha2-512",
"extra_hostkey_alg"
],
"mismatched_field": "Host keys"
},
{
"actual": [
"curve25519-sha256",
"curve25519-sha256@libssh.org",
"ecdh-sha2-nistp256",
"ecdh-sha2-nistp384",
"ecdh-sha2-nistp521",
"diffie-hellman-group-exchange-sha256",
"diffie-hellman-group16-sha512",
"diffie-hellman-group18-sha512",
"diffie-hellman-group14-sha256",
"diffie-hellman-group14-sha1"
],
"expected_optional": [
""
],
"expected_required": [
"curve25519-sha256",
"extra_kex_alg"
],
"mismatched_field": "Key exchanges"
},
{
"actual": [
"chacha20-poly1305@openssh.com",
"aes128-ctr",
"aes192-ctr",
"aes256-ctr",
"aes128-gcm@openssh.com",
"aes256-gcm@openssh.com"
],
"expected_optional": [
""
],
"expected_required": [
"chacha20-poly1305@openssh.com",
"extra_cipher_alg"
],
"mismatched_field": "Ciphers"
},
{
"actual": [
"umac-64-etm@openssh.com",
"umac-128-etm@openssh.com",
"hmac-sha2-256-etm@openssh.com",
"hmac-sha2-512-etm@openssh.com",
"hmac-sha1-etm@openssh.com",
"umac-64@openssh.com",
"umac-128@openssh.com",
"hmac-sha2-256",
"hmac-sha2-512",
"hmac-sha1"
],
"expected_optional": [
""
],
"expected_required": [
"umac-64-etm@openssh.com",
"extra_mac_alg"
],
"mismatched_field": "MACs"
}
],
"host": "localhost",
"passed": false,
"policy": "Docker policy: test16 (version 1)"
}

View File

@ -0,0 +1,21 @@
Host: localhost:2222
Policy: Docker policy: test16 (version 1)
Result: ❌ Failed!

Errors:
* Ciphers did not match.
- Expected (subset and/or reordering allowed): chacha20-poly1305@openssh.com, extra_cipher_alg
- Actual: chacha20-poly1305@openssh.com, aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, aes256-gcm@openssh.com
* Host keys did not match.
- Expected (subset and/or reordering allowed): rsa-sha2-512, extra_hostkey_alg
- Actual: rsa-sha2-512, rsa-sha2-256, ssh-rsa, ecdsa-sha2-nistp256, ssh-ed25519
* Key exchanges did not match.
- Expected (subset and/or reordering allowed): curve25519-sha256, extra_kex_alg
- Actual: curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group-exchange-sha256, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1
* MACs did not match.
- Expected (subset and/or reordering allowed): umac-64-etm@openssh.com, extra_mac_alg
- Actual: umac-64-etm@openssh.com, umac-128-etm@openssh.com, hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, hmac-sha1-etm@openssh.com, umac-64@openssh.com, umac-128@openssh.com, hmac-sha2-256, hmac-sha2-512, hmac-sha1


View File

@ -0,0 +1,6 @@
{
"errors": [],
"host": "localhost",
"passed": true,
"policy": "Docker policy: test17 (version 1)"
}

View File

@ -0,0 +1,3 @@
Host: localhost:2222
Policy: Docker policy: test17 (version 1)
Result: ✔ Passed

View File

@ -1,10 +1,8 @@
{ {
"additional_notes": [],
"banner": { "banner": {
"comments": null, "comments": null,
"protocol": [ "protocol": "2.0",
2,
0
],
"raw": "SSH-2.0-OpenSSH_8.0", "raw": "SSH-2.0-OpenSSH_8.0",
"software": "OpenSSH_8.0" "software": "OpenSSH_8.0"
}, },
@ -35,12 +33,58 @@
} }
], ],
"enc": [ "enc": [
"chacha20-poly1305@openssh.com", {
"aes128-ctr", "algorithm": "chacha20-poly1305@openssh.com",
"aes192-ctr", "notes": {
"aes256-ctr", "info": [
"aes128-gcm@openssh.com", "default cipher since OpenSSH 6.9",
"aes256-gcm@openssh.com" "available since OpenSSH 6.5, Dropbear SSH 2020.79"
],
"warn": [
"vulnerable to the Terrapin attack (CVE-2023-48795), allowing message prefix truncation"
]
}
},
{
"algorithm": "aes128-ctr",
"notes": {
"info": [
"available since OpenSSH 3.7, Dropbear SSH 0.52"
]
}
},
{
"algorithm": "aes192-ctr",
"notes": {
"info": [
"available since OpenSSH 3.7"
]
}
},
{
"algorithm": "aes256-ctr",
"notes": {
"info": [
"available since OpenSSH 3.7, Dropbear SSH 0.52"
]
}
},
{
"algorithm": "aes128-gcm@openssh.com",
"notes": {
"info": [
"available since OpenSSH 6.2"
]
}
},
{
"algorithm": "aes256-gcm@openssh.com",
"notes": {
"info": [
"available since OpenSSH 6.2"
]
}
}
], ],
"fingerprints": [ "fingerprints": [
{ {
@ -66,68 +110,269 @@
], ],
"kex": [ "kex": [
{ {
"algorithm": "curve25519-sha256" "algorithm": "curve25519-sha256",
"notes": {
"info": [
"default key exchange from OpenSSH 7.4 to 8.9",
"available since OpenSSH 7.4, Dropbear SSH 2018.76"
]
}
}, },
{ {
"algorithm": "curve25519-sha256@libssh.org" "algorithm": "curve25519-sha256@libssh.org",
"notes": {
"info": [
"default key exchange from OpenSSH 6.5 to 7.3",
"available since OpenSSH 6.4, Dropbear SSH 2013.62"
]
}
}, },
{ {
"algorithm": "ecdh-sha2-nistp256" "algorithm": "ecdh-sha2-nistp256",
"notes": {
"fail": [
"using elliptic curves that are suspected as being backdoored by the U.S. National Security Agency"
],
"info": [
"available since OpenSSH 5.7, Dropbear SSH 2013.62"
]
}
}, },
{ {
"algorithm": "ecdh-sha2-nistp384" "algorithm": "ecdh-sha2-nistp384",
"notes": {
"fail": [
"using elliptic curves that are suspected as being backdoored by the U.S. National Security Agency"
],
"info": [
"available since OpenSSH 5.7, Dropbear SSH 2013.62"
]
}
}, },
{ {
"algorithm": "ecdh-sha2-nistp521" "algorithm": "ecdh-sha2-nistp521",
"notes": {
"fail": [
"using elliptic curves that are suspected as being backdoored by the U.S. National Security Agency"
],
"info": [
"available since OpenSSH 5.7, Dropbear SSH 2013.62"
]
}
}, },
{ {
"algorithm": "diffie-hellman-group-exchange-sha256", "algorithm": "diffie-hellman-group-exchange-sha256",
"keysize": 2048 "keysize": 4096,
"notes": {
"info": [
"OpenSSH's GEX fallback mechanism was triggered during testing. Very old SSH clients will still be able to create connections using a 2048-bit modulus, though modern clients will use 4096. This can only be disabled by recompiling the code (see https://github.com/openssh/openssh-portable/blob/V_9_4/dh.c#L477).",
"available since OpenSSH 4.4"
]
}
}, },
{ {
"algorithm": "diffie-hellman-group16-sha512" "algorithm": "diffie-hellman-group16-sha512",
"notes": {
"info": [
"available since OpenSSH 7.3, Dropbear SSH 2016.73"
]
}
}, },
{ {
"algorithm": "diffie-hellman-group18-sha512" "algorithm": "diffie-hellman-group18-sha512",
"notes": {
"info": [
"available since OpenSSH 7.3"
]
}
}, },
{ {
"algorithm": "diffie-hellman-group14-sha256" "algorithm": "diffie-hellman-group14-sha256",
"notes": {
"info": [
"available since OpenSSH 7.3, Dropbear SSH 2016.73"
],
"warn": [
"2048-bit modulus only provides 112-bits of symmetric strength"
]
}
}, },
{ {
"algorithm": "diffie-hellman-group14-sha1" "algorithm": "diffie-hellman-group14-sha1",
"notes": {
"fail": [
"using broken SHA-1 hash algorithm"
],
"info": [
"available since OpenSSH 3.9, Dropbear SSH 0.53"
],
"warn": [
"2048-bit modulus only provides 112-bits of symmetric strength"
]
}
} }
], ],
"key": [ "key": [
{ {
"algorithm": "rsa-sha2-512", "algorithm": "rsa-sha2-512",
"keysize": 3072 "keysize": 3072,
"notes": {
"info": [
"available since OpenSSH 7.2"
]
}
}, },
{ {
"algorithm": "rsa-sha2-256", "algorithm": "rsa-sha2-256",
"keysize": 3072 "keysize": 3072,
"notes": {
"info": [
"available since OpenSSH 7.2, Dropbear SSH 2020.79"
]
}
}, },
{ {
"algorithm": "ssh-rsa", "algorithm": "ssh-rsa",
"keysize": 3072 "keysize": 3072,
"notes": {
"fail": [
"using broken SHA-1 hash algorithm"
],
"info": [
"deprecated in OpenSSH 8.8: https://www.openssh.com/txt/release-8.8",
"available since OpenSSH 2.5.0, Dropbear SSH 0.28"
]
}
}, },
{ {
"algorithm": "ecdsa-sha2-nistp256" "algorithm": "ecdsa-sha2-nistp256",
"notes": {
"fail": [
"using elliptic curves that are suspected as being backdoored by the U.S. National Security Agency"
],
"info": [
"available since OpenSSH 5.7, Dropbear SSH 2013.62"
],
"warn": [
"using weak random number generator could reveal the key"
]
}
}, },
{ {
"algorithm": "ssh-ed25519" "algorithm": "ssh-ed25519",
"notes": {
"info": [
"available since OpenSSH 6.5, Dropbear SSH 2020.79"
]
}
} }
], ],
"mac": [ "mac": [
"umac-64-etm@openssh.com", {
"umac-128-etm@openssh.com", "algorithm": "umac-64-etm@openssh.com",
"hmac-sha2-256-etm@openssh.com", "notes": {
"hmac-sha2-512-etm@openssh.com", "info": [
"hmac-sha1-etm@openssh.com", "available since OpenSSH 6.2"
"umac-64@openssh.com", ],
"umac-128@openssh.com", "warn": [
"hmac-sha2-256", "using small 64-bit tag size"
"hmac-sha2-512", ]
"hmac-sha1" }
},
{
"algorithm": "umac-128-etm@openssh.com",
"notes": {
"info": [
"available since OpenSSH 6.2"
]
}
},
{
"algorithm": "hmac-sha2-256-etm@openssh.com",
"notes": {
"info": [
"available since OpenSSH 6.2"
]
}
},
{
"algorithm": "hmac-sha2-512-etm@openssh.com",
"notes": {
"info": [
"available since OpenSSH 6.2"
]
}
},
{
"algorithm": "hmac-sha1-etm@openssh.com",
"notes": {
"fail": [
"using broken SHA-1 hash algorithm"
],
"info": [
"available since OpenSSH 6.2"
]
}
},
{
"algorithm": "umac-64@openssh.com",
"notes": {
"info": [
"available since OpenSSH 4.7"
],
"warn": [
"using encrypt-and-MAC mode",
"using small 64-bit tag size"
]
}
},
{
"algorithm": "umac-128@openssh.com",
"notes": {
"info": [
"available since OpenSSH 6.2"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
},
{
"algorithm": "hmac-sha2-256",
"notes": {
"info": [
"available since OpenSSH 5.9, Dropbear SSH 2013.56"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
},
{
"algorithm": "hmac-sha2-512",
"notes": {
"info": [
"available since OpenSSH 5.9, Dropbear SSH 2013.56"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
},
{
"algorithm": "hmac-sha1",
"notes": {
"fail": [
"using broken SHA-1 hash algorithm"
],
"info": [
"available since OpenSSH 2.1.0, Dropbear SSH 0.28"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
}
], ],
"recommendations": { "recommendations": {
"critical": { "critical": {
@ -174,6 +419,12 @@
}, },
"warning": { "warning": {
"del": { "del": {
"enc": [
{
"name": "chacha20-poly1305@openssh.com",
"notes": ""
}
],
"kex": [ "kex": [
{ {
"name": "diffie-hellman-group14-sha256", "name": "diffie-hellman-group14-sha256",

View File

@ -1,7 +1,7 @@
# general # general
(gen) banner: SSH-2.0-OpenSSH_8.0 (gen) banner: SSH-2.0-OpenSSH_8.0
(gen) software: OpenSSH 8.0 (gen) software: OpenSSH 8.0
(gen) compatibility: OpenSSH 7.4+, Dropbear SSH 2018.76+ (gen) compatibility: OpenSSH 7.4+, Dropbear SSH 2020.79+
(gen) compression: enabled (zlib@openssh.com) (gen) compression: enabled (zlib@openssh.com)
# security # security
@ -12,18 +12,17 @@
# key exchange algorithms # key exchange algorithms
(kex) curve25519-sha256 -- [info] available since OpenSSH 7.4, Dropbear SSH 2018.76 (kex) curve25519-sha256 -- [info] available since OpenSSH 7.4, Dropbear SSH 2018.76
`- [info] default key exchange since OpenSSH 6.4  `- [info] default key exchange from OpenSSH 7.4 to 8.9
(kex) curve25519-sha256@libssh.org -- [info] available since OpenSSH 6.4, Dropbear SSH 2013.62 (kex) curve25519-sha256@libssh.org -- [info] available since OpenSSH 6.4, Dropbear SSH 2013.62
`- [info] default key exchange since OpenSSH 6.4  `- [info] default key exchange from OpenSSH 6.5 to 7.3
(kex) ecdh-sha2-nistp256 -- [fail] using elliptic curves that are suspected as being backdoored by the U.S. National Security Agency (kex) ecdh-sha2-nistp256 -- [fail] using elliptic curves that are suspected as being backdoored by the U.S. National Security Agency
`- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62 `- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62
(kex) ecdh-sha2-nistp384 -- [fail] using elliptic curves that are suspected as being backdoored by the U.S. National Security Agency (kex) ecdh-sha2-nistp384 -- [fail] using elliptic curves that are suspected as being backdoored by the U.S. National Security Agency
`- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62 `- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62
(kex) ecdh-sha2-nistp521 -- [fail] using elliptic curves that are suspected as being backdoored by the U.S. National Security Agency (kex) ecdh-sha2-nistp521 -- [fail] using elliptic curves that are suspected as being backdoored by the U.S. National Security Agency
`- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62 `- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62
(kex) diffie-hellman-group-exchange-sha256 (2048-bit) -- [warn] 2048-bit modulus only provides 112-bits of symmetric strength (kex) diffie-hellman-group-exchange-sha256 (4096-bit) -- [info] available since OpenSSH 4.4
`- [info] available since OpenSSH 4.4  `- [info] OpenSSH's GEX fallback mechanism was triggered during testing. Very old SSH clients will still be able to create connections using a 2048-bit modulus, though modern clients will use 4096. This can only be disabled by recompiling the code (see https://github.com/openssh/openssh-portable/blob/V_9_4/dh.c#L477).
`- [info] A bug in OpenSSH causes it to fall back to a 2048-bit modulus regardless of server configuration (https://bugzilla.mindrot.org/show_bug.cgi?id=2793)
(kex) diffie-hellman-group16-sha512 -- [info] available since OpenSSH 7.3, Dropbear SSH 2016.73 (kex) diffie-hellman-group16-sha512 -- [info] available since OpenSSH 7.3, Dropbear SSH 2016.73
(kex) diffie-hellman-group18-sha512 -- [info] available since OpenSSH 7.3 (kex) diffie-hellman-group18-sha512 -- [info] available since OpenSSH 7.3
(kex) diffie-hellman-group14-sha256 -- [warn] 2048-bit modulus only provides 112-bits of symmetric strength (kex) diffie-hellman-group14-sha256 -- [warn] 2048-bit modulus only provides 112-bits of symmetric strength
@ -34,17 +33,18 @@
# host-key algorithms # host-key algorithms
(key) rsa-sha2-512 (3072-bit) -- [info] available since OpenSSH 7.2 (key) rsa-sha2-512 (3072-bit) -- [info] available since OpenSSH 7.2
(key) rsa-sha2-256 (3072-bit) -- [info] available since OpenSSH 7.2 (key) rsa-sha2-256 (3072-bit) -- [info] available since OpenSSH 7.2, Dropbear SSH 2020.79
(key) ssh-rsa (3072-bit) -- [fail] using broken SHA-1 hash algorithm (key) ssh-rsa (3072-bit) -- [fail] using broken SHA-1 hash algorithm
`- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.28 `- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.28
`- [info] deprecated in OpenSSH 8.8: https://www.openssh.com/txt/release-8.8 `- [info] deprecated in OpenSSH 8.8: https://www.openssh.com/txt/release-8.8
(key) ecdsa-sha2-nistp256 -- [fail] using elliptic curves that are suspected as being backdoored by the U.S. National Security Agency (key) ecdsa-sha2-nistp256 -- [fail] using elliptic curves that are suspected as being backdoored by the U.S. National Security Agency
 `- [warn] using weak random number generator could reveal the key  `- [warn] using weak random number generator could reveal the key
`- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62 `- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62
(key) ssh-ed25519 -- [info] available since OpenSSH 6.5 (key) ssh-ed25519 -- [info] available since OpenSSH 6.5, Dropbear SSH 2020.79
# encryption algorithms (ciphers) # encryption algorithms (ciphers)
(enc) chacha20-poly1305@openssh.com -- [info] available since OpenSSH 6.5 (enc) chacha20-poly1305@openssh.com -- [warn] vulnerable to the Terrapin attack (CVE-2023-48795), allowing message prefix truncation
`- [info] available since OpenSSH 6.5, Dropbear SSH 2020.79
`- [info] default cipher since OpenSSH 6.9 `- [info] default cipher since OpenSSH 6.9
(enc) aes128-ctr -- [info] available since OpenSSH 3.7, Dropbear SSH 0.52 (enc) aes128-ctr -- [info] available since OpenSSH 3.7, Dropbear SSH 0.52
(enc) aes192-ctr -- [info] available since OpenSSH 3.7 (enc) aes192-ctr -- [info] available since OpenSSH 3.7
@ -86,6 +86,7 @@
(rec) -hmac-sha1 -- mac algorithm to remove  (rec) -hmac-sha1 -- mac algorithm to remove 
(rec) -hmac-sha1-etm@openssh.com -- mac algorithm to remove  (rec) -hmac-sha1-etm@openssh.com -- mac algorithm to remove 
(rec) -ssh-rsa -- key algorithm to remove  (rec) -ssh-rsa -- key algorithm to remove 
(rec) -chacha20-poly1305@openssh.com -- enc algorithm to remove 
(rec) -diffie-hellman-group14-sha256 -- kex algorithm to remove  (rec) -diffie-hellman-group14-sha256 -- kex algorithm to remove 
(rec) -hmac-sha2-256 -- mac algorithm to remove  (rec) -hmac-sha2-256 -- mac algorithm to remove 
(rec) -hmac-sha2-512 -- mac algorithm to remove  (rec) -hmac-sha2-512 -- mac algorithm to remove 

View File

@ -1,10 +1,8 @@
{ {
"additional_notes": [],
"banner": { "banner": {
"comments": null, "comments": null,
"protocol": [ "protocol": "2.0",
2,
0
],
"raw": "SSH-2.0-OpenSSH_8.0", "raw": "SSH-2.0-OpenSSH_8.0",
"software": "OpenSSH_8.0" "software": "OpenSSH_8.0"
}, },
@ -35,12 +33,58 @@
} }
], ],
"enc": [ "enc": [
"chacha20-poly1305@openssh.com", {
"aes128-ctr", "algorithm": "chacha20-poly1305@openssh.com",
"aes192-ctr", "notes": {
"aes256-ctr", "info": [
"aes128-gcm@openssh.com", "default cipher since OpenSSH 6.9",
"aes256-gcm@openssh.com" "available since OpenSSH 6.5, Dropbear SSH 2020.79"
],
"warn": [
"vulnerable to the Terrapin attack (CVE-2023-48795), allowing message prefix truncation"
]
}
},
{
"algorithm": "aes128-ctr",
"notes": {
"info": [
"available since OpenSSH 3.7, Dropbear SSH 0.52"
]
}
},
{
"algorithm": "aes192-ctr",
"notes": {
"info": [
"available since OpenSSH 3.7"
]
}
},
{
"algorithm": "aes256-ctr",
"notes": {
"info": [
"available since OpenSSH 3.7, Dropbear SSH 0.52"
]
}
},
{
"algorithm": "aes128-gcm@openssh.com",
"notes": {
"info": [
"available since OpenSSH 6.2"
]
}
},
{
"algorithm": "aes256-gcm@openssh.com",
"notes": {
"info": [
"available since OpenSSH 6.2"
]
}
}
], ],
"fingerprints": [ "fingerprints": [
{ {
@ -56,58 +100,234 @@
], ],
"kex": [ "kex": [
{ {
"algorithm": "curve25519-sha256" "algorithm": "curve25519-sha256",
"notes": {
"info": [
"default key exchange from OpenSSH 7.4 to 8.9",
"available since OpenSSH 7.4, Dropbear SSH 2018.76"
]
}
}, },
{ {
"algorithm": "curve25519-sha256@libssh.org" "algorithm": "curve25519-sha256@libssh.org",
"notes": {
"info": [
"default key exchange from OpenSSH 6.5 to 7.3",
"available since OpenSSH 6.4, Dropbear SSH 2013.62"
]
}
}, },
{ {
"algorithm": "ecdh-sha2-nistp256" "algorithm": "ecdh-sha2-nistp256",
"notes": {
"fail": [
"using elliptic curves that are suspected as being backdoored by the U.S. National Security Agency"
],
"info": [
"available since OpenSSH 5.7, Dropbear SSH 2013.62"
]
}
}, },
{ {
"algorithm": "ecdh-sha2-nistp384" "algorithm": "ecdh-sha2-nistp384",
"notes": {
"fail": [
"using elliptic curves that are suspected as being backdoored by the U.S. National Security Agency"
],
"info": [
"available since OpenSSH 5.7, Dropbear SSH 2013.62"
]
}
}, },
{ {
"algorithm": "ecdh-sha2-nistp521" "algorithm": "ecdh-sha2-nistp521",
"notes": {
"fail": [
"using elliptic curves that are suspected as being backdoored by the U.S. National Security Agency"
],
"info": [
"available since OpenSSH 5.7, Dropbear SSH 2013.62"
]
}
}, },
{ {
"algorithm": "diffie-hellman-group-exchange-sha256", "algorithm": "diffie-hellman-group-exchange-sha256",
"keysize": 2048 "keysize": 4096,
"notes": {
"info": [
"OpenSSH's GEX fallback mechanism was triggered during testing. Very old SSH clients will still be able to create connections using a 2048-bit modulus, though modern clients will use 4096. This can only be disabled by recompiling the code (see https://github.com/openssh/openssh-portable/blob/V_9_4/dh.c#L477).",
"available since OpenSSH 4.4"
]
}
}, },
{ {
"algorithm": "diffie-hellman-group16-sha512" "algorithm": "diffie-hellman-group16-sha512",
"notes": {
"info": [
"available since OpenSSH 7.3, Dropbear SSH 2016.73"
]
}
}, },
{ {
"algorithm": "diffie-hellman-group18-sha512" "algorithm": "diffie-hellman-group18-sha512",
"notes": {
"info": [
"available since OpenSSH 7.3"
]
}
}, },
{ {
"algorithm": "diffie-hellman-group14-sha256" "algorithm": "diffie-hellman-group14-sha256",
"notes": {
"info": [
"available since OpenSSH 7.3, Dropbear SSH 2016.73"
],
"warn": [
"2048-bit modulus only provides 112-bits of symmetric strength"
]
}
}, },
{ {
"algorithm": "diffie-hellman-group14-sha1" "algorithm": "diffie-hellman-group14-sha1",
"notes": {
"fail": [
"using broken SHA-1 hash algorithm"
],
"info": [
"available since OpenSSH 3.9, Dropbear SSH 0.53"
],
"warn": [
"2048-bit modulus only provides 112-bits of symmetric strength"
]
}
} }
], ],
"key": [ "key": [
{ {
"algorithm": "ssh-ed25519" "algorithm": "ssh-ed25519",
"notes": {
"info": [
"available since OpenSSH 6.5, Dropbear SSH 2020.79"
]
}
}, },
{ {
"algorithm": "ssh-ed25519-cert-v01@openssh.com", "algorithm": "ssh-ed25519-cert-v01@openssh.com",
"ca_algorithm": "ssh-ed25519", "ca_algorithm": "ssh-ed25519",
"casize": 256 "casize": 256,
"notes": {
"info": [
"available since OpenSSH 6.5"
]
}
} }
], ],
"mac": [ "mac": [
"umac-64-etm@openssh.com", {
"umac-128-etm@openssh.com", "algorithm": "umac-64-etm@openssh.com",
"hmac-sha2-256-etm@openssh.com", "notes": {
"hmac-sha2-512-etm@openssh.com", "info": [
"hmac-sha1-etm@openssh.com", "available since OpenSSH 6.2"
"umac-64@openssh.com", ],
"umac-128@openssh.com", "warn": [
"hmac-sha2-256", "using small 64-bit tag size"
"hmac-sha2-512", ]
"hmac-sha1" }
},
{
"algorithm": "umac-128-etm@openssh.com",
"notes": {
"info": [
"available since OpenSSH 6.2"
]
}
},
{
"algorithm": "hmac-sha2-256-etm@openssh.com",
"notes": {
"info": [
"available since OpenSSH 6.2"
]
}
},
{
"algorithm": "hmac-sha2-512-etm@openssh.com",
"notes": {
"info": [
"available since OpenSSH 6.2"
]
}
},
{
"algorithm": "hmac-sha1-etm@openssh.com",
"notes": {
"fail": [
"using broken SHA-1 hash algorithm"
],
"info": [
"available since OpenSSH 6.2"
]
}
},
{
"algorithm": "umac-64@openssh.com",
"notes": {
"info": [
"available since OpenSSH 4.7"
],
"warn": [
"using encrypt-and-MAC mode",
"using small 64-bit tag size"
]
}
},
{
"algorithm": "umac-128@openssh.com",
"notes": {
"info": [
"available since OpenSSH 6.2"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
},
{
"algorithm": "hmac-sha2-256",
"notes": {
"info": [
"available since OpenSSH 5.9, Dropbear SSH 2013.56"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
},
{
"algorithm": "hmac-sha2-512",
"notes": {
"info": [
"available since OpenSSH 5.9, Dropbear SSH 2013.56"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
},
{
"algorithm": "hmac-sha1",
"notes": {
"fail": [
"using broken SHA-1 hash algorithm"
],
"info": [
"available since OpenSSH 2.1.0, Dropbear SSH 0.28"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
}
], ],
"recommendations": { "recommendations": {
"critical": { "critical": {
@ -158,6 +378,12 @@
}, },
"warning": { "warning": {
"del": { "del": {
"enc": [
{
"name": "chacha20-poly1305@openssh.com",
"notes": ""
}
],
"kex": [ "kex": [
{ {
"name": "diffie-hellman-group14-sha256", "name": "diffie-hellman-group14-sha256",

View File

@ -1,7 +1,7 @@
# general # general
(gen) banner: SSH-2.0-OpenSSH_8.0 (gen) banner: SSH-2.0-OpenSSH_8.0
(gen) software: OpenSSH 8.0 (gen) software: OpenSSH 8.0
(gen) compatibility: OpenSSH 7.4+, Dropbear SSH 2018.76+ (gen) compatibility: OpenSSH 7.4+, Dropbear SSH 2020.79+
(gen) compression: enabled (zlib@openssh.com) (gen) compression: enabled (zlib@openssh.com)
# security # security
@ -12,18 +12,17 @@
# key exchange algorithms # key exchange algorithms
(kex) curve25519-sha256 -- [info] available since OpenSSH 7.4, Dropbear SSH 2018.76 (kex) curve25519-sha256 -- [info] available since OpenSSH 7.4, Dropbear SSH 2018.76
`- [info] default key exchange since OpenSSH 6.4  `- [info] default key exchange from OpenSSH 7.4 to 8.9
(kex) curve25519-sha256@libssh.org -- [info] available since OpenSSH 6.4, Dropbear SSH 2013.62 (kex) curve25519-sha256@libssh.org -- [info] available since OpenSSH 6.4, Dropbear SSH 2013.62
`- [info] default key exchange since OpenSSH 6.4  `- [info] default key exchange from OpenSSH 6.5 to 7.3
(kex) ecdh-sha2-nistp256 -- [fail] using elliptic curves that are suspected as being backdoored by the U.S. National Security Agency (kex) ecdh-sha2-nistp256 -- [fail] using elliptic curves that are suspected as being backdoored by the U.S. National Security Agency
`- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62 `- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62
(kex) ecdh-sha2-nistp384 -- [fail] using elliptic curves that are suspected as being backdoored by the U.S. National Security Agency (kex) ecdh-sha2-nistp384 -- [fail] using elliptic curves that are suspected as being backdoored by the U.S. National Security Agency
`- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62 `- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62
(kex) ecdh-sha2-nistp521 -- [fail] using elliptic curves that are suspected as being backdoored by the U.S. National Security Agency (kex) ecdh-sha2-nistp521 -- [fail] using elliptic curves that are suspected as being backdoored by the U.S. National Security Agency
`- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62 `- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62
(kex) diffie-hellman-group-exchange-sha256 (2048-bit) -- [warn] 2048-bit modulus only provides 112-bits of symmetric strength (kex) diffie-hellman-group-exchange-sha256 (4096-bit) -- [info] available since OpenSSH 4.4
`- [info] available since OpenSSH 4.4  `- [info] OpenSSH's GEX fallback mechanism was triggered during testing. Very old SSH clients will still be able to create connections using a 2048-bit modulus, though modern clients will use 4096. This can only be disabled by recompiling the code (see https://github.com/openssh/openssh-portable/blob/V_9_4/dh.c#L477).
`- [info] A bug in OpenSSH causes it to fall back to a 2048-bit modulus regardless of server configuration (https://bugzilla.mindrot.org/show_bug.cgi?id=2793)
(kex) diffie-hellman-group16-sha512 -- [info] available since OpenSSH 7.3, Dropbear SSH 2016.73 (kex) diffie-hellman-group16-sha512 -- [info] available since OpenSSH 7.3, Dropbear SSH 2016.73
(kex) diffie-hellman-group18-sha512 -- [info] available since OpenSSH 7.3 (kex) diffie-hellman-group18-sha512 -- [info] available since OpenSSH 7.3
(kex) diffie-hellman-group14-sha256 -- [warn] 2048-bit modulus only provides 112-bits of symmetric strength (kex) diffie-hellman-group14-sha256 -- [warn] 2048-bit modulus only provides 112-bits of symmetric strength
@ -33,11 +32,12 @@
`- [info] available since OpenSSH 3.9, Dropbear SSH 0.53 `- [info] available since OpenSSH 3.9, Dropbear SSH 0.53
# host-key algorithms # host-key algorithms
(key) ssh-ed25519 -- [info] available since OpenSSH 6.5 (key) ssh-ed25519 -- [info] available since OpenSSH 6.5, Dropbear SSH 2020.79
(key) ssh-ed25519-cert-v01@openssh.com (256-bit cert/256-bit ssh-ed25519 CA) -- [info] available since OpenSSH 6.5 (key) ssh-ed25519-cert-v01@openssh.com (256-bit cert/256-bit ssh-ed25519 CA) -- [info] available since OpenSSH 6.5
# encryption algorithms (ciphers) # encryption algorithms (ciphers)
(enc) chacha20-poly1305@openssh.com -- [info] available since OpenSSH 6.5 (enc) chacha20-poly1305@openssh.com -- [warn] vulnerable to the Terrapin attack (CVE-2023-48795), allowing message prefix truncation
`- [info] available since OpenSSH 6.5, Dropbear SSH 2020.79
`- [info] default cipher since OpenSSH 6.9 `- [info] default cipher since OpenSSH 6.9
(enc) aes128-ctr -- [info] available since OpenSSH 3.7, Dropbear SSH 0.52 (enc) aes128-ctr -- [info] available since OpenSSH 3.7, Dropbear SSH 0.52
(enc) aes192-ctr -- [info] available since OpenSSH 3.7 (enc) aes192-ctr -- [info] available since OpenSSH 3.7
@ -78,6 +78,7 @@
(rec) -hmac-sha1-etm@openssh.com -- mac algorithm to remove  (rec) -hmac-sha1-etm@openssh.com -- mac algorithm to remove 
(rec) +rsa-sha2-256 -- key algorithm to append  (rec) +rsa-sha2-256 -- key algorithm to append 
(rec) +rsa-sha2-512 -- key algorithm to append  (rec) +rsa-sha2-512 -- key algorithm to append 
(rec) -chacha20-poly1305@openssh.com -- enc algorithm to remove 
(rec) -diffie-hellman-group14-sha256 -- kex algorithm to remove  (rec) -diffie-hellman-group14-sha256 -- kex algorithm to remove 
(rec) -hmac-sha2-256 -- mac algorithm to remove  (rec) -hmac-sha2-256 -- mac algorithm to remove 
(rec) -hmac-sha2-512 -- mac algorithm to remove  (rec) -hmac-sha2-512 -- mac algorithm to remove 

View File

@ -1,10 +1,8 @@
{ {
"additional_notes": [],
"banner": { "banner": {
"comments": null, "comments": null,
"protocol": [ "protocol": "2.0",
2,
0
],
"raw": "SSH-2.0-OpenSSH_8.0", "raw": "SSH-2.0-OpenSSH_8.0",
"software": "OpenSSH_8.0" "software": "OpenSSH_8.0"
}, },
@ -35,12 +33,58 @@
} }
], ],
"enc": [ "enc": [
"chacha20-poly1305@openssh.com", {
"aes256-gcm@openssh.com", "algorithm": "chacha20-poly1305@openssh.com",
"aes128-gcm@openssh.com", "notes": {
"aes256-ctr", "info": [
"aes192-ctr", "default cipher since OpenSSH 6.9",
"aes128-ctr" "available since OpenSSH 6.5, Dropbear SSH 2020.79"
],
"warn": [
"vulnerable to the Terrapin attack (CVE-2023-48795), allowing message prefix truncation"
]
}
},
{
"algorithm": "aes256-gcm@openssh.com",
"notes": {
"info": [
"available since OpenSSH 6.2"
]
}
},
{
"algorithm": "aes128-gcm@openssh.com",
"notes": {
"info": [
"available since OpenSSH 6.2"
]
}
},
{
"algorithm": "aes256-ctr",
"notes": {
"info": [
"available since OpenSSH 3.7, Dropbear SSH 0.52"
]
}
},
{
"algorithm": "aes192-ctr",
"notes": {
"info": [
"available since OpenSSH 3.7"
]
}
},
{
"algorithm": "aes128-ctr",
"notes": {
"info": [
"available since OpenSSH 3.7, Dropbear SSH 0.52"
]
}
}
], ],
"fingerprints": [ "fingerprints": [
{ {
@ -56,25 +100,69 @@
], ],
"kex": [ "kex": [
{ {
"algorithm": "curve25519-sha256" "algorithm": "curve25519-sha256",
"notes": {
"info": [
"default key exchange from OpenSSH 7.4 to 8.9",
"available since OpenSSH 7.4, Dropbear SSH 2018.76"
]
}
}, },
{ {
"algorithm": "curve25519-sha256@libssh.org" "algorithm": "curve25519-sha256@libssh.org",
"notes": {
"info": [
"default key exchange from OpenSSH 6.5 to 7.3",
"available since OpenSSH 6.4, Dropbear SSH 2013.62"
]
}
}, },
{ {
"algorithm": "diffie-hellman-group-exchange-sha256", "algorithm": "diffie-hellman-group-exchange-sha256",
"keysize": 2048 "keysize": 4096,
"notes": {
"info": [
"OpenSSH's GEX fallback mechanism was triggered during testing. Very old SSH clients will still be able to create connections using a 2048-bit modulus, though modern clients will use 4096. This can only be disabled by recompiling the code (see https://github.com/openssh/openssh-portable/blob/V_9_4/dh.c#L477).",
"available since OpenSSH 4.4"
]
}
} }
], ],
"key": [ "key": [
{ {
"algorithm": "ssh-ed25519" "algorithm": "ssh-ed25519",
"notes": {
"info": [
"available since OpenSSH 6.5, Dropbear SSH 2020.79"
]
}
} }
], ],
"mac": [ "mac": [
"hmac-sha2-256-etm@openssh.com", {
"hmac-sha2-512-etm@openssh.com", "algorithm": "hmac-sha2-256-etm@openssh.com",
"umac-128-etm@openssh.com" "notes": {
"info": [
"available since OpenSSH 6.2"
]
}
},
{
"algorithm": "hmac-sha2-512-etm@openssh.com",
"notes": {
"info": [
"available since OpenSSH 6.2"
]
}
},
{
"algorithm": "umac-128-etm@openssh.com",
"notes": {
"info": [
"available since OpenSSH 6.2"
]
}
}
], ],
"recommendations": { "recommendations": {
"informational": { "informational": {
@ -100,6 +188,16 @@
} }
] ]
} }
},
"warning": {
"del": {
"enc": [
{
"name": "chacha20-poly1305@openssh.com",
"notes": ""
}
]
}
} }
}, },
"target": "localhost:2222" "target": "localhost:2222"

View File

@ -1,7 +1,7 @@
# general # general
(gen) banner: SSH-2.0-OpenSSH_8.0 (gen) banner: SSH-2.0-OpenSSH_8.0
(gen) software: OpenSSH 8.0 (gen) software: OpenSSH 8.0
(gen) compatibility: OpenSSH 7.4+, Dropbear SSH 2018.76+ (gen) compatibility: OpenSSH 7.4+, Dropbear SSH 2020.79+
(gen) compression: enabled (zlib@openssh.com) (gen) compression: enabled (zlib@openssh.com)
# security # security
@ -12,18 +12,18 @@
# key exchange algorithms # key exchange algorithms
(kex) curve25519-sha256 -- [info] available since OpenSSH 7.4, Dropbear SSH 2018.76 (kex) curve25519-sha256 -- [info] available since OpenSSH 7.4, Dropbear SSH 2018.76
`- [info] default key exchange since OpenSSH 6.4  `- [info] default key exchange from OpenSSH 7.4 to 8.9
(kex) curve25519-sha256@libssh.org -- [info] available since OpenSSH 6.4, Dropbear SSH 2013.62 (kex) curve25519-sha256@libssh.org -- [info] available since OpenSSH 6.4, Dropbear SSH 2013.62
`- [info] default key exchange since OpenSSH 6.4  `- [info] default key exchange from OpenSSH 6.5 to 7.3
(kex) diffie-hellman-group-exchange-sha256 (2048-bit) -- [warn] 2048-bit modulus only provides 112-bits of symmetric strength (kex) diffie-hellman-group-exchange-sha256 (4096-bit) -- [info] available since OpenSSH 4.4
`- [info] available since OpenSSH 4.4  `- [info] OpenSSH's GEX fallback mechanism was triggered during testing. Very old SSH clients will still be able to create connections using a 2048-bit modulus, though modern clients will use 4096. This can only be disabled by recompiling the code (see https://github.com/openssh/openssh-portable/blob/V_9_4/dh.c#L477).
`- [info] A bug in OpenSSH causes it to fall back to a 2048-bit modulus regardless of server configuration (https://bugzilla.mindrot.org/show_bug.cgi?id=2793)
# host-key algorithms # host-key algorithms
(key) ssh-ed25519 -- [info] available since OpenSSH 6.5 (key) ssh-ed25519 -- [info] available since OpenSSH 6.5, Dropbear SSH 2020.79
# encryption algorithms (ciphers) # encryption algorithms (ciphers)
(enc) chacha20-poly1305@openssh.com -- [info] available since OpenSSH 6.5 (enc) chacha20-poly1305@openssh.com -- [warn] vulnerable to the Terrapin attack (CVE-2023-48795), allowing message prefix truncation
`- [info] available since OpenSSH 6.5, Dropbear SSH 2020.79
`- [info] default cipher since OpenSSH 6.9 `- [info] default cipher since OpenSSH 6.9
(enc) aes256-gcm@openssh.com -- [info] available since OpenSSH 6.2 (enc) aes256-gcm@openssh.com -- [info] available since OpenSSH 6.2
(enc) aes128-gcm@openssh.com -- [info] available since OpenSSH 6.2 (enc) aes128-gcm@openssh.com -- [info] available since OpenSSH 6.2
@ -44,4 +44,8 @@
(rec) +diffie-hellman-group18-sha512 -- kex algorithm to append  (rec) +diffie-hellman-group18-sha512 -- kex algorithm to append 
(rec) +rsa-sha2-256 -- key algorithm to append  (rec) +rsa-sha2-256 -- key algorithm to append 
(rec) +rsa-sha2-512 -- key algorithm to append  (rec) +rsa-sha2-512 -- key algorithm to append 
(rec) -chacha20-poly1305@openssh.com -- enc algorithm to remove 
# additional info
(nfo) For hardening guides on common OSes, please see: <https://www.ssh-audit.com/hardening_guides.html>

View File

@ -1,10 +1,8 @@
{ {
"additional_notes": [],
"banner": { "banner": {
"comments": "", "comments": "",
"protocol": [ "protocol": "2.0",
2,
0
],
"raw": "", "raw": "",
"software": "tinyssh_noversion" "software": "tinyssh_noversion"
}, },
@ -13,7 +11,18 @@
], ],
"cves": [], "cves": [],
"enc": [ "enc": [
"chacha20-poly1305@openssh.com" {
"algorithm": "chacha20-poly1305@openssh.com",
"notes": {
"info": [
"default cipher since OpenSSH 6.9",
"available since OpenSSH 6.5, Dropbear SSH 2020.79"
],
"warn": [
"vulnerable to the Terrapin attack (CVE-2023-48795), allowing message prefix truncation"
]
}
}
], ],
"fingerprints": [ "fingerprints": [
{ {
@ -29,22 +38,58 @@
], ],
"kex": [ "kex": [
{ {
"algorithm": "curve25519-sha256" "algorithm": "curve25519-sha256",
"notes": {
"info": [
"default key exchange from OpenSSH 7.4 to 8.9",
"available since OpenSSH 7.4, Dropbear SSH 2018.76"
]
}
}, },
{ {
"algorithm": "curve25519-sha256@libssh.org" "algorithm": "curve25519-sha256@libssh.org",
"notes": {
"info": [
"default key exchange from OpenSSH 6.5 to 7.3",
"available since OpenSSH 6.4, Dropbear SSH 2013.62"
]
}
}, },
{ {
"algorithm": "sntrup4591761x25519-sha512@tinyssh.org" "algorithm": "sntrup4591761x25519-sha512@tinyssh.org",
"notes": {
"info": [
"the sntrup4591761 algorithm was withdrawn, as it may not provide strong post-quantum security",
"available since OpenSSH 8.0"
],
"warn": [
"using experimental algorithm"
]
}
} }
], ],
"key": [ "key": [
{ {
"algorithm": "ssh-ed25519" "algorithm": "ssh-ed25519",
"notes": {
"info": [
"available since OpenSSH 6.5, Dropbear SSH 2020.79"
]
}
} }
], ],
"mac": [ "mac": [
"hmac-sha2-256" {
"algorithm": "hmac-sha2-256",
"notes": {
"info": [
"available since OpenSSH 5.9, Dropbear SSH 2013.56"
],
"warn": [
"using encrypt-and-MAC mode"
]
}
}
], ],
"recommendations": {}, "recommendations": {},
"target": "localhost:2222" "target": "localhost:2222"

View File

@ -1,22 +1,23 @@
# general # general
(gen) software: TinySSH noversion (gen) software: TinySSH noversion
(gen) compatibility: OpenSSH 8.0-8.4, Dropbear SSH 2018.76+ (gen) compatibility: OpenSSH 8.0-8.4, Dropbear SSH 2020.79+
(gen) compression: disabled (gen) compression: disabled
# key exchange algorithms # key exchange algorithms
(kex) curve25519-sha256 -- [info] available since OpenSSH 7.4, Dropbear SSH 2018.76 (kex) curve25519-sha256 -- [info] available since OpenSSH 7.4, Dropbear SSH 2018.76
`- [info] default key exchange since OpenSSH 6.4  `- [info] default key exchange from OpenSSH 7.4 to 8.9
(kex) curve25519-sha256@libssh.org -- [info] available since OpenSSH 6.4, Dropbear SSH 2013.62 (kex) curve25519-sha256@libssh.org -- [info] available since OpenSSH 6.4, Dropbear SSH 2013.62
`- [info] default key exchange since OpenSSH 6.4  `- [info] default key exchange from OpenSSH 6.5 to 7.3
(kex) sntrup4591761x25519-sha512@tinyssh.org -- [warn] using experimental algorithm (kex) sntrup4591761x25519-sha512@tinyssh.org -- [warn] using experimental algorithm
`- [info] available since OpenSSH 8.0 `- [info] available since OpenSSH 8.0
`- [info] the sntrup4591761 algorithm was withdrawn, as it may not provide strong post-quantum security `- [info] the sntrup4591761 algorithm was withdrawn, as it may not provide strong post-quantum security
# host-key algorithms # host-key algorithms
(key) ssh-ed25519 -- [info] available since OpenSSH 6.5 (key) ssh-ed25519 -- [info] available since OpenSSH 6.5, Dropbear SSH 2020.79
# encryption algorithms (ciphers) # encryption algorithms (ciphers)
(enc) chacha20-poly1305@openssh.com -- [info] available since OpenSSH 6.5 (enc) chacha20-poly1305@openssh.com -- [warn] vulnerable to the Terrapin attack (CVE-2023-48795), allowing message prefix truncation
`- [info] available since OpenSSH 6.5, Dropbear SSH 2020.79
`- [info] default cipher since OpenSSH 6.9 `- [info] default cipher since OpenSSH 6.9
# message authentication code algorithms # message authentication code algorithms

View File

@ -23,7 +23,7 @@ hostkey_size_rsa-sha2-512 = 3072
hostkey_size_ssh-rsa = 3072 hostkey_size_ssh-rsa = 3072
# Group exchange DH modulus sizes. # Group exchange DH modulus sizes.
dh_modulus_size_diffie-hellman-group-exchange-sha256 = 2048 dh_modulus_size_diffie-hellman-group-exchange-sha256 = 4096
# The host key types that must match exactly (order matters). # The host key types that must match exactly (order matters).
host keys = rsa-sha2-512, rsa-sha2-256, ssh-rsa, ecdsa-sha2-nistp256, ssh-ed25519 host keys = rsa-sha2-512, rsa-sha2-256, ssh-rsa, ecdsa-sha2-nistp256, ssh-ed25519

View File

@ -23,7 +23,7 @@ hostkey_size_rsa-sha2-512 = 3072
hostkey_size_ssh-rsa = 3072 hostkey_size_ssh-rsa = 3072
# Group exchange DH modulus sizes. # Group exchange DH modulus sizes.
dh_modulus_size_diffie-hellman-group-exchange-sha256 = 4096 dh_modulus_size_diffie-hellman-group-exchange-sha256 = 8192
# The host key types that must match exactly (order matters). # The host key types that must match exactly (order matters).
host keys = rsa-sha2-512, rsa-sha2-256, ssh-rsa, ecdsa-sha2-nistp256, ssh-ed25519 host keys = rsa-sha2-512, rsa-sha2-256, ssh-rsa, ecdsa-sha2-nistp256, ssh-ed25519

View File

@ -0,0 +1,13 @@
#
# Docker policy: test15
#
name = "Docker policy: test15"
version = 1
allow_algorithm_subset_and_reordering = true
banner = "SSH-2.0-OpenSSH_8.0"
compressions = none, zlib@openssh.com
host keys = rsa-sha2-512, rsa-sha2-256, ssh-rsa, ecdsa-sha2-nistp256, ssh-ed25519, extra_hostkey_alg
key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group-exchange-sha256, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1, extra_kex_alg
ciphers = chacha20-poly1305@openssh.com, aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, aes256-gcm@openssh.com, extra_cipher_alg
macs = umac-64-etm@openssh.com, umac-128-etm@openssh.com, hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, hmac-sha1-etm@openssh.com, umac-64@openssh.com, umac-128@openssh.com, hmac-sha2-256, hmac-sha2-512, hmac-sha1, extra_mac_alg

View File

@ -0,0 +1,13 @@
#
# Docker policy: test16
#
name = "Docker policy: test16"
version = 1
allow_algorithm_subset_and_reordering = true
banner = "SSH-2.0-OpenSSH_8.0"
compressions = none, zlib@openssh.com
host keys = rsa-sha2-512, extra_hostkey_alg
key exchanges = curve25519-sha256, extra_kex_alg
ciphers = chacha20-poly1305@openssh.com, extra_cipher_alg
macs = umac-64-etm@openssh.com, extra_mac_alg

View File

@ -0,0 +1,15 @@
#
# Docker policy: test17
#
name = "Docker policy: test17"
version = 1
allow_larger_keys = true
banner = "SSH-2.0-OpenSSH_8.0"
compressions = none, zlib@openssh.com
host keys = rsa-sha2-512, rsa-sha2-256, ssh-rsa, ecdsa-sha2-nistp256, ssh-ed25519
key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group-exchange-sha256, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1
ciphers = chacha20-poly1305@openssh.com, aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, aes256-gcm@openssh.com
macs = umac-64-etm@openssh.com, umac-128-etm@openssh.com, hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, hmac-sha1-etm@openssh.com, umac-64@openssh.com, umac-128@openssh.com, hmac-sha2-256, hmac-sha2-512, hmac-sha1
host_key_sizes = {"ssh-rsa": {"hostkey_size": 2048}, "rsa-sha2-256": {"hostkey_size": 2048}, "rsa-sha2-512": {"hostkey_size": 2048}, "ssh-ed25519": {"hostkey_size": 256}}
dh_modulus_sizes = {"diffie-hellman-group-exchange-sha256": 2048}

29
test/test_dheater.py Normal file
View File

@ -0,0 +1,29 @@
import pytest
from ssh_audit.ssh2_kexdb import SSH2_KexDB
from ssh_audit.dheat import DHEat
class TestDHEat:
@pytest.fixture(autouse=True)
def init(self):
self.SSH2_KexDB = SSH2_KexDB
self.DHEat = DHEat
def test_kex_definition_completeness(self):
alg_db = self.SSH2_KexDB.get_db()
kex_db = alg_db['kex']
# Get all Diffie-Hellman algorithms defined in our database.
dh_algs = []
for kex in kex_db:
if kex.startswith('diffie-hellman-'):
dh_algs.append(kex)
# Ensure that each DH algorithm in our database is in either DHEat's alg_priority or gex_algs list. Also ensure that all non-group exchange algorithms are accounted for in the alg_modulus_sizes dictionary.
for dh_alg in dh_algs:
assert (dh_alg in self.DHEat.alg_priority) or (dh_alg in self.DHEat.gex_algs)
if dh_alg.find("group-exchange") == -1:
assert dh_alg in self.DHEat.alg_modulus_sizes

View File

@ -17,6 +17,7 @@ class TestErrors:
conf = self.AuditConf('localhost', 22) conf = self.AuditConf('localhost', 22)
conf.colors = False conf.colors = False
conf.batch = True conf.batch = True
conf.skip_rate_test = True
return conf return conf
def _audit(self, spy, conf=None, exit_expected=False): def _audit(self, spy, conf=None, exit_expected=False):

View File

@ -2,6 +2,7 @@ import hashlib
import pytest import pytest
from datetime import date from datetime import date
from ssh_audit.builtin_policies import BUILTIN_POLICIES
from ssh_audit.outputbuffer import OutputBuffer from ssh_audit.outputbuffer import OutputBuffer
from ssh_audit.policy import Policy from ssh_audit.policy import Policy
from ssh_audit.ssh2_kex import SSH2_Kex from ssh_audit.ssh2_kex import SSH2_Kex
@ -40,47 +41,52 @@ class TestPolicy:
def test_builtin_policy_consistency(self): def test_builtin_policy_consistency(self):
'''Ensure that the BUILTIN_POLICIES struct is consistent.''' '''Ensure that the BUILTIN_POLICIES struct is consistent.'''
for policy_name in Policy.BUILTIN_POLICIES: for policy_name in BUILTIN_POLICIES:
# Ensure that the policy name ends with " (version X)", where X is the 'version' field.
version_str = " (version %s)" % Policy.BUILTIN_POLICIES[policy_name]['version']
assert policy_name.endswith(version_str)
# Ensure that all required fields are present. # Ensure that all required fields are present.
required_fields = ['version', 'banner', 'compressions', 'host_keys', 'optional_host_keys', 'kex', 'ciphers', 'macs', 'hostkey_sizes', 'dh_modulus_sizes', 'server_policy'] required_fields = ['version', 'changelog', 'banner', 'compressions', 'host_keys', 'optional_host_keys', 'kex', 'ciphers', 'macs', 'hostkey_sizes', 'dh_modulus_sizes', 'server_policy']
for field in required_fields: for field in required_fields:
assert field in Policy.BUILTIN_POLICIES[policy_name] assert field in BUILTIN_POLICIES[policy_name]
# Ensure that the policy name ends with " (version X)", where X is the 'version' field.
version_str = " (version %s)" % BUILTIN_POLICIES[policy_name]['version']
assert policy_name.endswith(version_str)
# Ensure no extra fields are present. # Ensure no extra fields are present.
assert len(required_fields) == len(Policy.BUILTIN_POLICIES[policy_name]) assert len(required_fields) == len(BUILTIN_POLICIES[policy_name])
# Ensure that the changelog field is a string and non-empty.
assert type(BUILTIN_POLICIES[policy_name]['changelog']) is str
assert len(BUILTIN_POLICIES[policy_name]['changelog']) > 0
# Ensure that at least one host key is defined. # Ensure that at least one host key is defined.
assert type(Policy.BUILTIN_POLICIES[policy_name]['host_keys']) == list assert type(BUILTIN_POLICIES[policy_name]['host_keys']) is list
assert len(Policy.BUILTIN_POLICIES[policy_name]['host_keys']) > 0 assert len(BUILTIN_POLICIES[policy_name]['host_keys']) > 0
# Ensure that at least one key exchange is defined. # Ensure that at least one key exchange is defined.
assert type(Policy.BUILTIN_POLICIES[policy_name]['kex']) == list assert type(BUILTIN_POLICIES[policy_name]['kex']) is list
assert len(Policy.BUILTIN_POLICIES[policy_name]['kex']) > 0 assert len(BUILTIN_POLICIES[policy_name]['kex']) > 0
# Ensure that at least one cipher is defined. # Ensure that at least one cipher is defined.
assert type(Policy.BUILTIN_POLICIES[policy_name]['ciphers']) == list assert type(BUILTIN_POLICIES[policy_name]['ciphers']) is list
assert len(Policy.BUILTIN_POLICIES[policy_name]['ciphers']) > 0 assert len(BUILTIN_POLICIES[policy_name]['ciphers']) > 0
# Ensure that at least one MAC is defined # Ensure that at least one MAC is defined
assert type(Policy.BUILTIN_POLICIES[policy_name]['macs']) == list assert type(BUILTIN_POLICIES[policy_name]['macs']) is list
assert len(Policy.BUILTIN_POLICIES[policy_name]['macs']) > 0 assert len(BUILTIN_POLICIES[policy_name]['macs']) > 0
# These tests apply to server policies only. # These tests apply to server policies only.
if Policy.BUILTIN_POLICIES[policy_name]['server_policy']: if BUILTIN_POLICIES[policy_name]['server_policy']:
assert type(Policy.BUILTIN_POLICIES[policy_name]['hostkey_sizes']) == dict assert type(BUILTIN_POLICIES[policy_name]['hostkey_sizes']) is dict
assert len(Policy.BUILTIN_POLICIES[policy_name]['hostkey_sizes']) > 0 assert len(BUILTIN_POLICIES[policy_name]['hostkey_sizes']) > 0
# Examine all the hostkey_sizes entries... # Examine all the hostkey_sizes entries...
for hostkey_type in Policy.BUILTIN_POLICIES[policy_name]['hostkey_sizes']: for hostkey_type in BUILTIN_POLICIES[policy_name]['hostkey_sizes']:
hostkey_data = Policy.BUILTIN_POLICIES[policy_name]['hostkey_sizes'][hostkey_type] hostkey_data = BUILTIN_POLICIES[policy_name]['hostkey_sizes'][hostkey_type]
# Ensure that 'hostkey_size' is always included and that it is an integer. # Ensure that 'hostkey_size' is always included and that it is an integer.
assert 'hostkey_size' in hostkey_data assert 'hostkey_size' in hostkey_data
assert type(hostkey_data['hostkey_size']) == int assert type(hostkey_data['hostkey_size']) is int
# If this is an ed25519 host key, ensure its size is fixed at 256. If its an RSA host key, ensure the size is 4096. # If this is an ed25519 host key, ensure its size is fixed at 256. If its an RSA host key, ensure the size is 4096.
if hostkey_type.find('ed25519') != -1: if hostkey_type.find('ed25519') != -1:
@ -105,27 +111,27 @@ class TestPolicy:
assert hostkey_data['ca_key_size'] == 256 assert hostkey_data['ca_key_size'] == 256
# Ensure that the 'dh_modulus_size' field is a dict. # Ensure that the 'dh_modulus_size' field is a dict.
assert type(Policy.BUILTIN_POLICIES[policy_name]['dh_modulus_sizes']) == dict assert type(BUILTIN_POLICIES[policy_name]['dh_modulus_sizes']) is dict
# The 'dh_modulus_size' field should have either one entry, or be empty. # The 'dh_modulus_size' field should have either one entry, or be empty.
assert len(Policy.BUILTIN_POLICIES[policy_name]['dh_modulus_sizes']) in range(0, 2) # The endpoint in range() is not inclusive assert len(BUILTIN_POLICIES[policy_name]['dh_modulus_sizes']) in range(0, 2) # The endpoint in range() is not inclusive
# If 'diffie-hellman-group-exchange-sha256' is in the kex list, ensure that it exists in the 'dh_modulus_sizes' entry. That entry must be defined for 2048 bits or larger. # If 'diffie-hellman-group-exchange-sha256' is in the kex list, ensure that it exists in the 'dh_modulus_sizes' entry. That entry must be defined for 2048 bits or larger.
if 'diffie-hellman-group-exchange-sha256' in Policy.BUILTIN_POLICIES[policy_name]['kex']: if 'diffie-hellman-group-exchange-sha256' in BUILTIN_POLICIES[policy_name]['kex']:
assert 'diffie-hellman-group-exchange-sha256' in Policy.BUILTIN_POLICIES[policy_name]['dh_modulus_sizes'] assert 'diffie-hellman-group-exchange-sha256' in BUILTIN_POLICIES[policy_name]['dh_modulus_sizes']
assert int(Policy.BUILTIN_POLICIES[policy_name]['dh_modulus_sizes']['diffie-hellman-group-exchange-sha256']) >= 2048 assert int(BUILTIN_POLICIES[policy_name]['dh_modulus_sizes']['diffie-hellman-group-exchange-sha256']) >= 2048
else: # Client-specific tests. else: # Client-specific tests.
# These must be set to None for client policies, since they have no meaning otherwise. # These must be set to None for client policies, since they have no meaning otherwise.
assert Policy.BUILTIN_POLICIES[policy_name]['hostkey_sizes'] is None assert BUILTIN_POLICIES[policy_name]['hostkey_sizes'] is None
assert Policy.BUILTIN_POLICIES[policy_name]['dh_modulus_sizes'] is None assert BUILTIN_POLICIES[policy_name]['dh_modulus_sizes'] is None
# Ensure that each built-in policy can be loaded with Policy.load_builtin_policy(). # Ensure that each built-in policy can be loaded with Policy.load_builtin_policy().
assert Policy.load_builtin_policy(policy_name) is not None assert Policy.load_builtin_policy(policy_name) is not None
# Ensure that both server and client policy names are returned. # Ensure that both server and client policy names are returned.
server_policy_names, client_policy_names = Policy.list_builtin_policies() server_policy_names, client_policy_names = Policy.list_builtin_policies(False)
assert len(server_policy_names) > 0 assert len(server_policy_names) > 0
assert len(client_policy_names) > 0 assert len(client_policy_names) > 0
@ -144,7 +150,7 @@ ciphers = cipher_alg1, cipher_alg2, cipher_alg3
macs = mac_alg1, mac_alg2, mac_alg3''' macs = mac_alg1, mac_alg2, mac_alg3'''
policy = self.Policy(policy_data=policy_data) policy = self.Policy(policy_data=policy_data)
assert str(policy) == "Name: [Test Policy]\nVersion: [1]\nBanner: {undefined}\nCompressions: comp_alg1\nHost Keys: key_alg1\nOptional Host Keys: {undefined}\nKey Exchanges: kex_alg1, kex_alg2\nCiphers: cipher_alg1, cipher_alg2, cipher_alg3\nMACs: mac_alg1, mac_alg2, mac_alg3\nHost Key Sizes: {undefined}\nDH Modulus Sizes: {undefined}\nServer Policy: True" assert str(policy) == "Name: [Test Policy]\nVersion: [1]\nAllow Algorithm Subset and/or Reordering: False\nBanner: {undefined}\nCompressions: comp_alg1\nHost Keys: key_alg1\nOptional Host Keys: {undefined}\nKey Exchanges: kex_alg1, kex_alg2\nCiphers: cipher_alg1, cipher_alg2, cipher_alg3\nMACs: mac_alg1, mac_alg2, mac_alg3\nHost Key Sizes: {undefined}\nDH Modulus Sizes: {undefined}\nServer Policy: True"
def test_policy_invalid_1(self): def test_policy_invalid_1(self):
@ -291,7 +297,7 @@ macs = mac_alg1, mac_alg2, mac_alg3'''
pol_data = pol_data.replace(date.today().strftime('%Y/%m/%d'), '[todays date]') pol_data = pol_data.replace(date.today().strftime('%Y/%m/%d'), '[todays date]')
# Instead of writing out the entire expected policy--line by line--just check that it has the expected hash. # Instead of writing out the entire expected policy--line by line--just check that it has the expected hash.
assert hashlib.sha256(pol_data.encode('ascii')).hexdigest() == '4af7777fb57a1dad0cf438c899a11d4f625fd9276ea3bb5ef5c9fe8806cb47dc' assert hashlib.sha256(pol_data.encode('ascii')).hexdigest() == 'fb84bce442cff2bce9bf653d6373a8a938e3bfcfbd1e876f51a08c1842df3cff'
def test_policy_evaluate_passing_1(self): def test_policy_evaluate_passing_1(self):
@ -434,3 +440,96 @@ macs = mac_alg1, mac_alg2, XXXmismatchedXXX, mac_alg3'''
assert len(errors) == 2 assert len(errors) == 2
assert error_str.find('Host keys did not match.') != -1 assert error_str.find('Host keys did not match.') != -1
assert error_str.find('MACs did not match.') != -1 assert error_str.find('MACs did not match.') != -1
def test_policy_evaluate_subset_passing_1(self):
'''Ensure that exact algorithm matches work even when subset parsing is enabled.'''
policy_data = '''name = "Test Policy"
version = 1
allow_algorithm_subset_and_reordering = true
compressions = comp_alg1, comp_alg2
host keys = key_alg1, key_alg2
key exchanges = kex_alg1, kex_alg2
ciphers = cipher_alg1, cipher_alg2, cipher_alg3
macs = mac_alg1, mac_alg2, mac_alg3'''
policy = self.Policy(policy_data=policy_data)
ret, errors, error_str = policy.evaluate('SSH Server 1.0', self._get_kex())
assert ret is True
assert len(errors) == 0
assert error_str == ""
def test_policy_evaluate_subset_passing_2(self):
'''Ensure that subset parsing works.'''
policy_data = '''name = "Test Policy"
version = 1
allow_algorithm_subset_and_reordering = true
compressions = comp_alg1, comp_alg2
host keys = key_alg2, key_alg1, key_alg0
key exchanges = kex_alg3, kex_alg1, kex_alg2
ciphers = cipher_alg0, cipher_alg3, cipher_alg2, cipher_alg1
macs = mac_alg2, mac_alg1, mac_alg3, mac_alg0'''
policy = self.Policy(policy_data=policy_data)
ret, errors, error_str = policy.evaluate('SSH Server 1.0', self._get_kex())
assert ret is True
assert len(errors) == 0
assert error_str == ""
def test_policy_evaluate_subset_failing_1(self):
'''Ensure that subset parsing returns a failure.'''
policy_data = '''name = "Test Policy"
version = 1
allow_algorithm_subset_and_reordering = true
compressions = comp_alg1, comp_alg2
host keys = key_alg7, key_alg8, key_alg9
key exchanges = kex_alg7, kex_alg8, kex_alg9
ciphers = cipher_alg7, cipher_alg8, cipher_alg9, cipher_alg10
macs = mac_alg7, mac_alg8, mac_alg9, mac_alg10'''
policy = self.Policy(policy_data=policy_data)
ret, errors, error_str = policy.evaluate('SSH Server 1.0', self._get_kex())
assert ret is False
assert len(errors) == 4
assert error_str.find("Ciphers did not match.") != -1
assert error_str.find("Host keys did not match.") != -1
assert error_str.find("MACs did not match") != -1
assert error_str.find("Key exchanges did not match.") != -1
def test_policy_evaluate_subset_failing_2(self):
'''Ensure that subset parsing returns a failure when policy includes kex-strict-s-v00@openssh.com, but target does not.'''
policy_data = '''name = "Test Policy"
version = 1
allow_algorithm_subset_and_reordering = true
compressions = comp_alg1, comp_alg2
host keys = key_alg2, key_alg1, key_alg0
key exchanges = kex_alg3, kex_alg1, kex_alg2, kex-strict-s-v00@openssh.com
ciphers = cipher_alg0, cipher_alg3, cipher_alg2, cipher_alg1
macs = mac_alg2, mac_alg1, mac_alg3, mac_alg0'''
policy = self.Policy(policy_data=policy_data)
ret, errors, error_str = policy.evaluate('SSH Server 1.0', self._get_kex())
assert ret is False
assert len(errors) == 1
assert error_str.find("Key exchanges did not match.") != -1
def test_policy_evaluate_subset_failing_3(self):
'''Ensure that subset parsing returns a failure when policy includes kex-strict-c-v00@openssh.com, but target does not.'''
policy_data = '''name = "Test Policy"
version = 1
allow_algorithm_subset_and_reordering = true
compressions = comp_alg1, comp_alg2
host keys = key_alg2, key_alg1, key_alg0
key exchanges = kex_alg3, kex_alg1, kex_alg2, kex-strict-c-v00@openssh.com
ciphers = cipher_alg0, cipher_alg3, cipher_alg2, cipher_alg1
macs = mac_alg2, mac_alg1, mac_alg3, mac_alg0'''
policy = self.Policy(policy_data=policy_data)
ret, errors, error_str = policy.evaluate('SSH Server 1.0', self._get_kex())
assert ret is False
assert len(errors) == 1
assert error_str.find("Key exchanges did not match.") != -1

View File

@ -22,12 +22,12 @@ class TestResolve:
vsocket.gsock.addrinfodata['localhost#22'] = socket.gaierror(8, 'hostname nor servname provided, or not known') vsocket.gsock.addrinfodata['localhost#22'] = socket.gaierror(8, 'hostname nor servname provided, or not known')
conf = self._conf() conf = self._conf()
s = self.ssh_socket(self.OutputBuffer(), 'localhost', 22, conf.ip_version_preference) s = self.ssh_socket(self.OutputBuffer(), 'localhost', 22, conf.ip_version_preference)
output_spy.begin() # output_spy.begin()
with pytest.raises(SystemExit): with pytest.raises(socket.gaierror):
list(s._resolve()) list(s._resolve())
lines = output_spy.flush() # lines = output_spy.flush()
assert len(lines) == 1 # assert len(lines) == 1
assert 'hostname nor servname provided' in lines[-1] # assert 'hostname nor servname provided' in lines[-1]
def test_resolve_hostname_without_records(self, output_spy, virtual_socket): def test_resolve_hostname_without_records(self, output_spy, virtual_socket):
vsocket = virtual_socket vsocket = virtual_socket

View File

@ -33,6 +33,7 @@ class TestSSH1:
conf.verbose = True conf.verbose = True
conf.ssh1 = True conf.ssh1 = True
conf.ssh2 = False conf.ssh2 = False
conf.skip_rate_test = True
return conf return conf
def _create_ssh1_packet(self, payload, valid_crc=True): def _create_ssh1_packet(self, payload, valid_crc=True):

View File

@ -32,6 +32,7 @@ class TestSSH2:
conf.verbose = True conf.verbose = True
conf.ssh1 = False conf.ssh1 = False
conf.ssh2 = True conf.ssh2 = True
conf.skip_rate_test = True
return conf return conf
@classmethod @classmethod
@ -64,7 +65,7 @@ class TestSSH2:
def _kex_payload_with_gss(self): def _kex_payload_with_gss(self):
w = self.wbuf() w = self.wbuf()
w.write(b'\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff') w.write(b'\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff')
w.write_list(['gss-gex-sha1-dZuIebMjgUqaxvbF7hDbAw==', 'gss-gex-sha1-vz8J1E9PzLr8b1K+0remTg==', 'gss-group14-sha1-dZuIebMjgUqaxvbF7hDbAw==', 'gss-group14-sha1-vz8J1E9PzLr8b1K+0remTg==', 'gss-group14-sha256-dZuIebMjgUqaxvbF7hDbAw==', 'gss-group14-sha256-vz8J1E9PzLr8b1K+0remTg==', 'gss-group16-sha512-dZuIebMjgUqaxvbF7hDbAw==', 'gss-group16-sha512-vz8J1E9PzLr8b1K+0remTg==', 'gss-group18-sha512-dZuIebMjgUqaxvbF7hDbAw==', 'gss-group18-sha512-vz8J1E9PzLr8b1K+0remTg==', 'gss-group1-sha1-dZuIebMjgUqaxvbF7hDbAw==', 'gss-group1-sha1-vz8J1E9PzLr8b1K+0remTg==', 'gss-curve448-sha512-XXX']) w.write_list(['gss-gex-sha1-dZuIebMjgUqaxvbF7hDbAw==', 'gss-gex-sha1-vz8J1E9PzLr8b1K+0remTg==', 'gss-group14-sha1-dZuIebMjgUqaxvbF7hDbAw==', 'gss-group14-sha1-vz8J1E9PzLr8b1K+0remTg==', 'gss-group14-sha256-dZuIebMjgUqaxvbF7hDbAw==', 'gss-group14-sha256-vz8J1E9PzLr8b1K+0remTg==', 'gss-group16-sha512-dZuIebMjgUqaxvbF7hDbAw==', 'gss-group16-sha512-vz8J1E9PzLr8b1K+0remTg==', 'gss-group18-sha512-dZuIebMjgUqaxvbF7hDbAw==', 'gss-group18-sha512-vz8J1E9PzLr8b1K+0remTg==', 'gss-group1-sha1-dZuIebMjgUqaxvbF7hDbAw==', 'gss-group1-sha1-vz8J1E9PzLr8b1K+0remTg==', 'gss-curve448-sha512-XXX', 'gss-nistp256-sha256-RANDOMCHARSTOTESTWILDCARDMATCHING'])
w.write_list(['ssh-ed25519']) w.write_list(['ssh-ed25519'])
w.write_list(['chacha20-poly1305@openssh.com']) w.write_list(['chacha20-poly1305@openssh.com'])
w.write_list(['chacha20-poly1305@openssh.com']) w.write_list(['chacha20-poly1305@openssh.com'])
@ -164,7 +165,7 @@ class TestSSH2:
self.audit(out, self._conf()) self.audit(out, self._conf())
out.write() out.write()
lines = output_spy.flush() lines = output_spy.flush()
assert len(lines) == 70 assert len(lines) == 83
def test_ssh2_server_invalid_first_packet(self, output_spy, virtual_socket): def test_ssh2_server_invalid_first_packet(self, output_spy, virtual_socket):
vsocket = virtual_socket vsocket = virtual_socket

View File

@ -7,7 +7,7 @@ class Test_SSH2_KexDB:
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def init(self): def init(self):
self.db = SSH2_KexDB.ALGORITHMS self.db = SSH2_KexDB.get_db()
def test_ssh2_kexdb(self): def test_ssh2_kexdb(self):
'''Ensures that the SSH2_KexDB.ALGORITHMS dictionary is in the right format.''' '''Ensures that the SSH2_KexDB.ALGORITHMS dictionary is in the right format.'''

35
tox.ini
View File

@ -1,7 +1,7 @@
[tox] [tox]
envlist = envlist =
py{py3}-{test,pylint,flake8,vulture} py{py3}-{test,pylint,flake8}
py{37,38,39,310,311}-{test,mypy,pylint,flake8,vulture} py{38,39,310,311,312}-{test,mypy,pylint,flake8}
cov cov
skip_missing_interpreters = true skip_missing_interpreters = true
@ -9,11 +9,10 @@ skip_missing_interpreters = true
deps = deps =
test: pytest test: pytest
test,cov: {[testenv:cov]deps} test,cov: {[testenv:cov]deps}
test,py{37,38,39,310,311}-{type,mypy}: colorama test,py{38,39,310,311,312}-{type,mypy}: colorama
py{37,38,39,310,311}-{type,mypy}: {[testenv:mypy]deps} py{38,39,310,311,312}-{type,mypy}: {[testenv:mypy]deps}
py{py3,37,38,39,310,311}-{lint,pylint},lint: {[testenv:pylint]deps} py{py3,38,39,310,311,312}-{lint,pylint},lint: {[testenv:pylint]deps}
py{py3,37,38,39,310,311}-{lint,flake8},lint: {[testenv:flake8]deps} py{py3,38,39,310,311,312}-{lint,flake8},lint: {[testenv:flake8]deps}
py{py3,37,38,39,310,311}-{lint,vulture},lint: {[testenv:vulture]deps}
setenv = setenv =
SSHAUDIT = {toxinidir}/src SSHAUDIT = {toxinidir}/src
test: COVERAGE_FILE = {toxinidir}/.coverage.{envname} test: COVERAGE_FILE = {toxinidir}/.coverage.{envname}
@ -25,10 +24,10 @@ commands =
test: coverage combine test: coverage combine
test: coverage report --show-missing test: coverage report --show-missing
test: coverage html -d {toxinidir}/reports/html/coverage.{envname} test: coverage html -d {toxinidir}/reports/html/coverage.{envname}
py{37,38,39,310,311}-{type,mypy}: {[testenv:mypy]commands} py{38,39,310,311,312}-{type,mypy}: {[testenv:mypy]commands}
py{py3,37,38,39,310,311}-{lint,pylint},lint: {[testenv:pylint]commands} py{py3,38,39,310,311,312}-{lint,pylint},lint: {[testenv:pylint]commands}
py{py3,37,38,39,310,311}-{lint,flake8},lint: {[testenv:flake8]commands} py{py3,38,39,310,311,312}-{lint,flake8},lint: {[testenv:flake8]commands}
py{py3,37,38,39,310,311}-{lint,vulture},lint: {[testenv:vulture]commands}
#ignore_outcome = #ignore_outcome =
# type: true # type: true
# lint: true # lint: true
@ -75,17 +74,6 @@ deps =
commands = commands =
flake8 {posargs:{env:SSHAUDIT} {toxinidir}/setup.py {toxinidir}/test {toxinidir}/ssh-audit.py} --statistics flake8 {posargs:{env:SSHAUDIT} {toxinidir}/setup.py {toxinidir}/test {toxinidir}/ssh-audit.py} --statistics
[testenv:vulture]
deps =
vulture
commands =
python -c "import sys; from subprocess import Popen, PIPE; \
a = ['vulture', '--min-confidence', '100'] + r'{posargs:{env:SSHAUDIT}}'.split(' '); \
o = Popen(a, shell=False, stdout=PIPE).communicate()[0]; \
l = [x for x in o.split(b'\n') if x and b'Unused import' not in x]; \
print(b'\n'.join(l).decode('utf-8')); \
sys.exit(1 if len(l) > 0 else 0)"
[pylint] [pylint]
reports = no reports = no
#output-format = colorized #output-format = colorized
@ -101,11 +89,14 @@ disable =
no-else-return, no-else-return,
super-with-arguments, # Can be re-factored, at some point. super-with-arguments, # Can be re-factored, at some point.
too-complex, too-complex,
too-many-arguments,
too-many-boolean-expressions, too-many-boolean-expressions,
too-many-branches, too-many-branches,
too-many-instance-attributes, too-many-instance-attributes,
too-many-lines, too-many-lines,
too-many-locals, too-many-locals,
too-many-nested-blocks,
too-many-return-statements,
too-many-statements, too-many-statements,
consider-using-f-string consider-using-f-string
max-complexity = 15 max-complexity = 15