74 Commits

Author SHA1 Message Date
Joe Testa
4f9a630de4 Added Debian 13 policies and hardening guides. 2025-09-01 18:22:46 -04:00
Joe Testa
f821565ff9 Renamed hardeningguides.py. 2025-09-01 17:39:07 -04:00
Joe Testa
062a1f3cb4 Updated changelog message for version 2 of Ubuntu Server 24 policy. 2025-09-01 16:43:59 -04:00
Joe Testa
c900874406 Added policy option to allow host key subsets and/or reorderings. 2025-09-01 16:22:40 -04:00
Joe Testa
0382cf9b2d Aside from linking to online hardening guides, mention that built-in guides are also available. 2025-08-30 16:26:57 -04:00
Joe Testa
d8d90a3a89 Dropped support for Python 3.8, as it reached its end-of-life in October 2024. 2025-08-24 15:50:58 -04:00
Joe Testa
aaa7d24565 Updated GEX fallback detection for OpenSSH 10.0 and later, as version 9.9 was the last to include it. (#310) 2025-08-24 15:41:14 -04:00
Joe Testa
d3b1551520 Added OpenSSH 10.0 policy. 2025-08-24 12:52:22 -04:00
Joe Testa
970d747dcb Smoothed out some rough edges from PR #307. 2025-08-17 16:34:32 -04:00
oam7575
1c0d3d5df1 print config v2 Issue #191 (#307)
* print config v2

 - printconfig script
 - test_printconfig for tox testing
 - update globals for GUIDES_UPDATED date value
 - update ssh_audit for print_config argument and checks

* pr307 update 1

* pr307 update 2

* pr307 - attempt 2

* Update ssh_audit.py

Missed a TAB
2025-08-17 16:05:14 -04:00
Joe Testa
4845a8fdee Updated README. 2025-08-06 08:40:36 -04:00
Joe Testa
11a902cb14 Removed SSHv1 support (#298). 2025-07-26 19:57:11 -04:00
Joe Testa
b456bb31b9 Added note on mlkem768x25519-sha256 that it is the default key exchange since OpenSSH 10.0. 2025-06-16 18:59:36 -04:00
Joe Testa
32085b2fa5 Added two new ciphers: AEAD_CAMELLIA_128_GCM, AEAD_CAMELLIA_256_GCM. 2025-05-18 18:46:40 -04:00
Joe Testa
5ddd8cca5b Added 2 new key exchanges: mlkem768nistp256-sha256, mlkem1024nistp384-sha384. 2025-04-18 18:29:18 -04:00
Joe Testa
b90db2c1af Fixed mypy failure. 2025-04-18 17:06:29 -04:00
playoutsideplay
68c827c239 Update LICENSE (#319)
Updated year
2025-04-18 16:27:44 -04:00
Joe Testa
e318787a5c Batch mode no longer automatically enables verbose mode. 2024-12-05 10:06:58 -05:00
Joe Testa
d9c703c777 When running against multiple hosts, now prints each target host regardless of output level. (#309) 2024-12-05 09:41:26 -05:00
Joe Testa
28a1e23986 Added warnings to all key exchanges that do not provide protection against quantum attacks. 2024-11-25 15:56:51 -05:00
Joe Testa
a01baadfa8 Additional cleanups after merging #304. 2024-11-22 12:28:02 -05:00
oam7575
45abc3aaf4 Argparse v3 - RC1 (#304)
* Argparse v3 - RC1

* Argparse v3 - RC1

Argparse v3 RC1 - post feedback

Argparse v3 - RC2
2024-11-22 12:26:20 -05:00
Joe Testa
99c64787d9 Updated description of -m option. 2024-10-16 16:39:11 -04:00
Joe Testa
3fa62c3ac5 Fixed man page parsing error. (#301) 2024-10-16 16:23:20 -04:00
Joe Testa
d7fff591fa Bumped version to v3.4.0-dev. 2024-10-15 18:30:08 -04:00
Joe Testa
84647ecb32 Updated packaging notes. 2024-10-15 18:29:25 -04:00
Joe Testa
772204ce8b Bumped version to v3.3.0. 2024-10-15 13:28:38 -04:00
Joe Testa
c0133a8d5f Listing built-in policies will now hide older versions, unless -v is used. 2024-10-11 15:43:09 -04:00
Joe Testa
3220043aaf Added note regarding hardening instructions. 2024-10-10 16:10:52 -04:00
Joe Testa
40ed92bbe6 Run tests against stable version of Python 3.13. 2024-10-10 16:06:18 -04:00
Joe Testa
720150b471 Issue a warning if an out-dated policy is used. 2024-10-10 15:57:29 -04:00
Joe Testa
d0628f6eb4 Updated ext-info-c and ext-info-s key exchanges to include versions of OpenSSH they were first included in. (#291) 2024-10-07 17:41:39 -04:00
Joe Testa
1e060a94c0 Updated built-in server and client policies for Amazon Linux 2023. 2024-10-01 18:15:02 -04:00
Joe Testa
8563c2925b Updated built-in client policy for Debian 12. 2024-10-01 17:48:49 -04:00
Joe Testa
556306be5e Updated built-in client policy for Rocky Linux 9. 2024-10-01 17:39:42 -04:00
Joe Testa
7ab6d20454 Updated built-in client policy for Ubuntu 22.04. 2024-10-01 17:32:49 -04:00
Joe Testa
1f1a51d591 Updated Ubuntu 22.04 built-in policy. 2024-10-01 17:06:03 -04:00
Joe Testa
77a63de133 Updated Rocky Linux 9 built-in policy. 2024-10-01 16:21:23 -04:00
Joe Testa
cffa126277 Updated Debian 12 built-in policy. (#283) 2024-10-01 15:01:44 -04:00
Joe Testa
dc615cef7f Fixed DH rate testing on Windows. (#261) 2024-09-28 18:39:55 -04:00
Joe Testa
cb6142c609 Ignore mypy errors on colorama import. 2024-09-28 17:43:32 -04:00
Joe Testa
629008e55e Updated test commands. 2024-09-26 18:34:40 -04:00
Joe Testa
016a5d89f7 Updated Github Actions workflow to use Tox through pip instead of the platform version. 2024-09-26 18:31:21 -04:00
Joe Testa
93b30b4258 Removed version-based CVE information. (#240) 2024-09-26 13:15:58 -04:00
Joe Testa
3b8a75e407 Server kex/host key parsing failures no longer output a stack trace unless in debug mode. 2024-09-25 17:34:18 -04:00
Joe Testa
67e11f82b3 Updated --targets description. 2024-09-25 17:12:16 -04:00
Joe Testa
2cd96f1785 Ensure ECDSA and DSS fingerprints are only output in verbose mode. Clean up Docker tests from merge of #286. 2024-09-25 17:05:17 -04:00
Daniel Lenski
a4b78b752e Enable HostKeyTest to extract ECDSA and DSA keys (#286)
Their certificate-embedded counterparts are enabled as well.

As with RSA, it *is* possible for DSA keys to be of variable length (not
just 1024 bits), so I've added `{'variable_key_len': True}` to the relevant
`HOST_KEY_TYPES` entries, although this key-value pair is otherwise unused.
2024-09-25 16:57:03 -04:00
Joe Testa
ac540c8b5f Created FUNDING.yml. 2024-09-25 16:20:45 -04:00
Joe Testa
e11492b7a3 Updated shields. 2024-09-25 16:07:01 -04:00
Joe Testa
02bc48c574 Bumped supported Python range. 2024-09-25 14:18:41 -04:00
Joe Testa
24d7d46c42 Updated PyPI downloads shield. 2024-09-25 10:05:35 -04:00
Joe Testa
e97bbd9782 Added Python 3.13 support. 2024-09-24 18:20:07 -04:00
Joe Testa
6d57c7c0f7 The -p/--port option will now set the default port for multi-host scans (specified with -T/--targets). (#294) 2024-09-24 16:42:53 -04:00
Joe Testa
ea3258151e Fixed invalid JSON output when a socket error occurs while performing a client audit. (#295) 2024-09-24 15:48:14 -04:00
Joe Testa
f9032c8277 Added built-in policy for OpenSSH 9.9. 2024-09-24 15:05:05 -04:00
Joe Testa
d7398baad7 Added two new key exchanges: mlkem768x25519-sha256, sntrup761x25519-sha512. 2024-09-19 17:40:49 -04:00
Joe Testa
4621d52223 Updated unknown algorithm message. 2024-09-19 17:01:37 -04:00
Joe Testa
2a7cb13895 Added grasshopper-ctr128 cipher. 2024-09-18 17:59:45 -04:00
Joe Testa
06ebdbd0fe Updated README. 2024-08-26 16:46:34 -04:00
Drew Noel
7752023dc2 Switch connect_ex result checks to use errno lookups (#289)
* Switch connect_ex result checks to errno lookups

* Return errno strings, clean up comment
2024-08-26 16:38:44 -04:00
Joe Testa
a6f02ae8e8 Added debugging output for key exchanges. 2024-08-26 16:25:32 -04:00
Joe Testa
9049c8476a Updated README. 2024-07-06 21:01:19 -04:00
Daniel Lenski
bbbdf71e50 Recognize LANcom LCOS software and support ed448 key extraction (#277)
* Include raw hostkey bytes in debug output

* Recognize LANcom LCOS software and support extraction of ssh-ed448 key type

LANcom router devices appear to be primarily used in Germany (see [1]
for examples on the public Internet), and they appear to support the
`ssh-ed448` key type which is documented in [2], but which has never
been supported by any as-yet-released version of OpenSSH.

[1] https://www.shodan.io/search?query=ssh+%22ed448%22
[2] https://datatracker.ietf.org/doc/html/rfc8709#name-public-key-format
2024-07-06 20:56:24 -04:00
Joe Testa
92db5f0138 Updated docker tests and README due to merge of PR #281. 2024-07-05 10:53:00 -04:00
dreizehnutters
bc2a89eb11 fix for https://github.com/jtesta/ssh-audit/issues/280 (#281)
* fix for https://github.com/jtesta/ssh-audit/issues/280

* changed json format to min. the damage for a change
2024-07-05 10:49:16 -04:00
Joe Testa
ea117b203b Updated README. 2024-07-05 10:16:06 -04:00
Daniel Lenski
d8f8b7c57c Make HostKeyTest class reusable (#278)
Because the `HostKeyTest` class was mutating its static/global
`HOST_KEY_TYPES` dict, this class could not actually be used more than once
in a single thread!

Rather than mutate this dict after parsing each key type
(`HOST_KEY_TYPES[host_key_type]['parsed'] = True`), the `perform_test`
method should simple add the parsed key types to a local `set()`.
2024-07-05 10:11:18 -04:00
Joe Testa
e42961fa9a Added built-in policy for OpenSSH 9.8. 2024-07-02 21:31:36 -04:00
Joe Testa
dcbc43acdf Fixed crash when running with '-P' and '-T' options simultaneously. (#273) 2024-07-02 20:56:11 -04:00
Joe Testa
87e22ae26b Added IPv6 support for DHEat and connection rate tests. (#269) 2024-06-29 19:05:20 -04:00
Joe Testa
46ec4e3edc Added built-in policies for Ubuntu 24.04 LTS server and client. 2024-04-29 19:11:47 -04:00
Joe Testa
d19b154a46 Bumped version to v3.3.0-dev. 2024-04-22 17:57:26 -04:00
Joe Testa
c5d90106e8 Updated docker run command. 2024-04-22 17:54:37 -04:00
86 changed files with 2128 additions and 2154 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
github: jtesta

View File

@@ -7,18 +7,18 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2 uses: actions/setup-python@v5
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
- name: Install dependencies - name: Install dependencies
run: | run: |
python -m pip install --upgrade pip python3 -m pip install --upgrade pip
pip install -U codecov coveralls flake8 mypy pylint pytest tox python3 -m pip install -U codecov coveralls flake8 mypy pylint pytest tox
- name: Run Tox - name: Run Tox
run: | run: |
tox python3 -m tox

View File

@@ -11,7 +11,7 @@ However, if you can submit patches that pass all of our automated tests, then yo
[Tox](https://tox.wiki/) is used to automate testing. Linting is done with [pylint](http://pylint.pycqa.org/en/latest/) & [flake8](https://flake8.pycqa.org/en/latest/), and static type-checking is done with [mypy](https://mypy.readthedocs.io/en/stable/). [Tox](https://tox.wiki/) is used to automate testing. Linting is done with [pylint](http://pylint.pycqa.org/en/latest/) & [flake8](https://flake8.pycqa.org/en/latest/), and static type-checking is done with [mypy](https://mypy.readthedocs.io/en/stable/).
For Ubuntu systems, install tox with `apt install tox`, then simply run `tox` in the top-level directory. Look for any error messages in the (verbose) output. Install the required packages with `python3 -m pip install -U codecov coveralls flake8 mypy pylint pytest tox`, then run the tests with `python3 -m tox`. Look for any error messages in the (verbose) output.
## Docker Tests ## Docker Tests

View File

@@ -1,6 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (C) 2017-2024 Joe Testa (jtesta@positronsecurity.com) Copyright (C) 2017-2025 Joe Testa (jtesta@positronsecurity.com)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu) Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)

View File

@@ -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 (hint: use username '\_\_token\_\_' and API token for test.pypi.org): To create package and upload to test server (hint: use API token for test.pypi.org):
``` ```
$ sudo apt install python3-virtualenv python3.10-venv $ sudo apt install python3-virtualenv python3.12-venv
$ make -f Makefile.pypi $ make -f Makefile.pypi
$ make -f Makefile.pypi uploadtest $ make -f Makefile.pypi uploadtest
``` ```
@@ -26,12 +26,12 @@ To create package and upload to test server (hint: use username '\_\_token\_\_'
To download from test server and verify: To download from test server and verify:
``` ```
$ virtualenv -p /usr/bin/python3 /tmp/pypi_test $ virtualenv /tmp/pypi_test
$ cd /tmp/pypi_test; source bin/activate $ cd /tmp/pypi_test; source bin/activate
$ 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 for production pypi.org): To upload to production server (hint: use API token for production pypi.org):
``` ```
$ make -f Makefile.pypi uploadprod $ make -f Makefile.pypi uploadprod
@@ -40,7 +40,7 @@ To upload to production server (hint: use username '\_\_token\_\_' and API token
To download from production server and verify: To download from production server and verify:
``` ```
$ virtualenv -p /usr/bin/python3 /tmp/pypi_prod $ virtualenv /tmp/pypi_prod
$ cd /tmp/pypi_prod; source bin/activate $ cd /tmp/pypi_prod; source bin/activate
$ pip3 install ssh-audit $ pip3 install ssh-audit
``` ```
@@ -48,14 +48,14 @@ To download from production server and verify:
# Snap # Snap
To create the snap package, run a fully-updated Ubuntu Server 22.04 VM. To create the Snap package, run a fully-updated Ubuntu Server 24.04 VM.
Create the snap package with: Create the Snap package with:
``` ```
$ ./build_snap.sh $ ./build_snap.sh
``` ```
Upload the snap with: Upload the Snap with:
``` ```
$ snapcraft export-login ~/snap_creds.txt $ snapcraft export-login ~/snap_creds.txt
@@ -68,7 +68,7 @@ Upload the snap with:
# Docker # Docker
Ensure that the buildx plugin is available by following the installation instructions available at: https://docs.docker.com/engine/install/ubuntu/ Ensure that the `buildx` plugin is available by following the installation instructions available at: https://docs.docker.com/engine/install/ubuntu/
Build a local image with: Build a local image with:

197
README.md
View File

@@ -1,10 +1,15 @@
# ssh-audit # ssh-audit
[![License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](https://github.com/jtesta/ssh-audit/blob/master/LICENSE) [![License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](https://github.com/jtesta/ssh-audit/blob/master/LICENSE)
[![PyPI Downloads](https://img.shields.io/pypi/dm/ssh-audit)](https://pypi.org/project/ssh-audit/)
[![Docker Pulls](https://img.shields.io/docker/pulls/positronsecurity/ssh-audit)](https://hub.docker.com/r/positronsecurity/ssh-audit)
[![Build Status](https://github.com/jtesta/ssh-audit/actions/workflows/tox.yaml/badge.svg)](https://github.com/jtesta/ssh-audit/actions) [![Build Status](https://github.com/jtesta/ssh-audit/actions/workflows/tox.yaml/badge.svg)](https://github.com/jtesta/ssh-audit/actions)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/jtesta/ssh-audit/blob/master/CONTRIBUTING.md) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/jtesta/ssh-audit/blob/master/CONTRIBUTING.md)
[![PyPI Downloads](https://img.shields.io/pypi/dm/ssh-audit?label=pypi%20downloads&color=purple)](https://pypi.org/project/ssh-audit/)
[![Homebrew Downloads](https://img.shields.io/homebrew/installs/dy/ssh-audit?label=homebrew%20downloads&color=teal)](https://formulae.brew.sh/formula/ssh-audit)
[![Docker Pulls](https://img.shields.io/docker/pulls/positronsecurity/ssh-audit)](https://hub.docker.com/r/positronsecurity/ssh-audit)
[![Snap Downloads](https://img.shields.io/badge/snap%20downloads-no%20idea-yellow.svg)](https://snapcraft.io/ssh-audit)
[![Github Sponsors](https://img.shields.io/github/sponsors/jtesta?color=red)](https://github.com/sponsors/jtesta)
**ssh-audit** is a tool for ssh server & client configuration auditing. **ssh-audit** is a tool for ssh server & client configuration auditing.
[jtesta/ssh-audit](https://github.com/jtesta/ssh-audit/) (v2.0+) is the updated and maintained version of ssh-audit forked from [arthepsy/ssh-audit](https://github.com/arthepsy/ssh-audit) (v1.x) due to inactivity. [jtesta/ssh-audit](https://github.com/jtesta/ssh-audit/) (v2.0+) is the updated and maintained version of ssh-audit forked from [arthepsy/ssh-audit](https://github.com/arthepsy/ssh-audit) (v1.x) due to inactivity.
@@ -21,78 +26,109 @@
- [ChangeLog](#changelog) - [ChangeLog](#changelog)
## Features ## Features
- SSH1 and SSH2 protocol server support; - analyze SSH both server and client configuration;
- analyze SSH client configuration;
- grab banner, recognize device or software and operating system, detect compression; - grab banner, recognize device or software and operating system, detect compression;
- gather key-exchange, host-key, encryption and message authentication code algorithms; - gather key-exchange, host-key, encryption and message authentication code algorithms;
- output algorithm information (available since, removed/disabled, unsafe/weak/legacy, etc); - output algorithm security information (available since, removed/disabled, unsafe/weak/legacy, etc);
- output algorithm recommendations (append or remove based on recognized software version); - output algorithm recommendations (append or remove based on recognized software version);
- output security information (related issues, assigned CVE list, etc);
- analyze SSH version compatibility based on algorithm information; - analyze SSH version compatibility based on algorithm information;
- 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.8 - 3.12; - supports Python 3.9 - 3.13;
- no dependencies - no dependencies
## Usage ## Usage
``` ```
usage: ssh-audit.py [options] <host> usage: ssh-audit.py [-h] [-4] [-6] [-b] [-c] [-d]
[-g <min1:pref1:max1[,min2:pref2:max2,...]> / <x-y[:step]>]
[-j] [-l {info,warn,fail}] [-L] [-M custom_policy.txt]
[-m] [-n] [-P "Built-In Policy Name" / custom_policy.txt]
[-p N] [-T targets.txt] [-t N] [-v]
[--conn-rate-test N[:max_rate]] [--dheat N[:kex[:e_len]]]
[--get-hardening-guide platform] [--list-hardening-guides]
[--lookup alg1[,alg2,...]] [--skip-rate-test]
[--threads N]
[host]
-h, --help print this help # ssh-audit.py v3.4.0-dev, https://github.com/jtesta/ssh-audit
-1, --ssh1 force ssh version 1 only
-2, --ssh2 force ssh version 2 only positional arguments:
-4, --ipv4 enable IPv4 (order of precedence) host target hostname or IPv4/IPv6 address
-6, --ipv6 enable IPv6 (order of precedence)
-b, --batch batch output optional arguments:
-c, --client-audit starts a server on port 2222 to audit client -h, --help show this help message and exit
software config (use -p to change port; -4, --ipv4 enable IPv4 (order of precedence)
use -t to change timeout) -6, --ipv6 enable IPv6 (order of precedence)
--conn-rate-test=N[:max_rate] perform a connection rate test (useful -b, --batch batch output
for collecting metrics related to -c, --client-audit starts a server on port 2222 to audit client software
susceptibility of the DHEat vuln). config (use -p to change port; use -t to change
Testing is conducted with N concurrent timeout)
sockets with an optional maximum rate -d, --debug enable debugging output
of connections per second. -g <min1:pref1:max1[,min2:pref2:max2,...]> / <x-y[:step]>, --gex-test <min1:pref1:max1[,min2:pref2:max2,...]> / <x-y[:step]>
-d, --debug Enable debug output. conducts a very customized Diffie-Hellman GEX modulus
--dheat=N[:kex[:e_len]] continuously perform the DHEat DoS attack size test. Tests an array of minimum, preferred, and
(CVE-2002-20001) against the target using N maximum values, or a range of values with an optional
concurrent sockets. Optionally, a specific incremental step amount
key exchange algorithm can be specified -j, --json enable JSON output (use -jj to enable indentation for
instead of allowing it to be automatically better readability)
chosen. Additionally, a small length of -l {info,warn,fail}, --level {info,warn,fail}
the fake e value sent to the server can minimum output level (default: info)
be chosen for a more efficient attack (such -L, --list-policies list all the official, built-in policies. Combine with
as 4). -v to view policy change logs
-g, --gex-test=<x[,y,...]> dh gex modulus size test -M custom_policy.txt, --make-policy custom_policy.txt
<min1:pref1:max1[,min2:pref2:max2,...]> creates a policy based on the target server (i.e.: the
<x-y[:step]> target server has the ideal configuration that other
-j, --json JSON output (use -jj to enable indents) servers should adhere to), and stores it in the file
-l, --level=<level> minimum output level (info|warn|fail) path specified
-L, --list-policies list all the official, built-in policies. Use with -v -m, --manual print the man page (Docker, PyPI, Snap, and Windows
to view policy change logs. builds only)
--lookup=<alg1,alg2,...> looks up an algorithm(s) without -n, --no-colors disable colors (automatic when the NO_COLOR
connecting to a server environment variable is set)
-m, --manual print the man page (Docker, PyPI, Snap, and Windows -P "Built-In Policy Name" / custom_policy.txt, --policy "Built-In Policy Name" / custom_policy.txt
builds only) run a policy test using the specified policy (use -L
-M, --make-policy=<policy.txt> creates a policy based on the target server to see built-in policies, or specify filesystem path
(i.e.: the target server has the ideal to custom policy created by -M)
configuration that other servers should -p N, --port N the TCP port to connect to (or to listen on when -c is
adhere to) used)
-n, --no-colors disable colors -T targets.txt, --targets targets.txt
-p, --port=<port> port to connect a file containing a list of target hosts (one per
-P, --policy=<"policy name" | policy.txt> run a policy test using the line, format HOST[:PORT]). Use -p/--port to set the
specified policy default port for all hosts. Use --threads to control
--skip-rate-test skip the connection rate test during standard audits concurrent scans
(used to safely infer whether the DHEat attack -t N, --timeout N timeout (in seconds) for connection and reading
is viable) (default: 5)
-t, --timeout=<secs> timeout (in seconds) for connection and reading -v, --verbose enable verbose output
(default: 5) --conn-rate-test N[:max_rate]
-T, --targets=<hosts.txt> a file containing a list of target hosts (one perform a connection rate test (useful for collecting
per line, format HOST[:PORT]) metrics related to susceptibility of the DHEat vuln).
--threads=<threads> number of threads to use when scanning multiple Testing is conducted with N concurrent sockets with an
targets (-T/--targets) (default: 32) optional maximum rate of connections per second
-v, --verbose verbose 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).
--get-hardening-guide platform
retrieves the hardening guide for the specified
platform name (use --list-hardening-guides to see list
of available guides).
--list-hardening-guides
list all official, built-in hardening guides for
common systems. Their full names can then be passed to
--get-hardening-guide. Add -v to this option to view
hardening guide change logs and prior versions.
--lookup alg1[,alg2,...]
looks up an algorithm(s) without connecting to a
server.
--skip-rate-test skip the connection rate test during standard audits
(used to safely infer whether the DHEat attack is
viable)
--threads N number of threads to use when scanning multiple
targets (-T/--targets) (default: 32)
``` ```
* if both IPv4 and IPv6 are used, order of precedence can be set by using either `-46` or `-64`. * if both IPv4 and IPv6 are used, order of precedence can be set by using either `-46` or `-64`.
* batch flag `-b` will output sections without header and without empty lines (implies verbose flag). * batch flag `-b` will output sections without header and without empty lines (implies verbose flag).
@@ -183,7 +219,7 @@ Below is a screen shot of the client-auditing output when an unhardened OpenSSH
![client_screenshot](https://user-images.githubusercontent.com/2982011/68867998-b946c100-06c4-11ea-975f-1f47e4178a74.png) ![client_screenshot](https://user-images.githubusercontent.com/2982011/68867998-b946c100-06c4-11ea-975f-1f47e4178a74.png)
## Hardening Guides ## Hardening Guides
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 are built into the tool (see `--list-hardening-guides` and `--get-hardening-guide` options). Additionally, they are also available online at: [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](https://github.com/jtesta/ssh-audit/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:
@@ -202,7 +238,7 @@ To install from Dockerhub:
``` ```
$ docker pull positronsecurity/ssh-audit $ docker pull positronsecurity/ssh-audit
``` ```
(Then run with: `docker run -it -p 2222:2222 positronsecurity/ssh-audit 10.1.1.1`) (Then run with: `docker run -it --rm -p 2222:2222 positronsecurity/ssh-audit 10.1.1.1`)
The status of various other platform packages can be found below (via Repology): The status of various other platform packages can be found below (via Repology):
@@ -213,6 +249,39 @@ For convenience, a web front-end on top of the command-line tool is available at
## ChangeLog ## ChangeLog
### v3.4.0-dev
- BIG THANKS to [realmiwi](https://github.com/realmiwi) for being the project's *very first sponsor!!*
- Dropped support for Python 3.8, as it reached end-of-life in October 2024.
- Added warning to all key exchanges that do not include protections against quantum attacks due to the Harvest Now, Decrypt Later strategy (see https://en.wikipedia.org/wiki/Harvest_now,_decrypt_later).
- Removed SSHv1 support (rationale is documented in: https://github.com/jtesta/ssh-audit/issues/298).
- Added hardening guides (see `--list-hardening-guides` and `--get-hardening-guide`). Previously, they were only available at <https://ssh-audit.com/hardening_guides.html>, but now they are built-in for convenience; partial credit [oam7575](https://github.com/oam7575).
- Added `allow_hostkey_subset_and_reordering` policy option to allow targets to have a more stringent list of host keys and/or a different ordering of them.
- Migrated from deprecated `getopt` module to `argparse`; partial credit [oam7575](https://github.com/oam7575).
- When running against multiple hosts, now prints each target host regardless of output level.
- Batch mode (`-b`) no longer automatically enables verbose mode, due to sometimes confusing results; users can still explicitly enable verbose mode using the `-v` flag.
- Added built-in policy for OpenSSH 10.0.
- Added hardening guides and policies for Debian 13.
- Added 2 new key exchanges: `mlkem768nistp256-sha256`, `mlkem1024nistp384-sha384`.
- Added 2 new ciphers: `AEAD_CAMELLIA_128_GCM`, `AEAD_CAMELLIA_256_GCM`.
### v3.3.0 (2024-10-15)
- Added Python 3.13 support.
- Added built-in policies for Ubuntu 24.04 LTS server & client, OpenSSH 9.8, and OpenSSH 9.9.
- Added IPv6 support for DHEat and connection rate tests.
- Added TCP port information to JSON policy scan results; credit [Fabian Malte Kopp](https://github.com/dreizehnutters).
- Added LANcom LCOS server recognition and Ed448 key extraction; credit [Daniel Lenski](https://github.com/dlenskiSB).
- Now reports ECDSA and DSS fingerprints when in verbose mode; partial credit [Daniel Lenski](https://github.com/dlenskiSB).
- Removed CVE information based on server/client version numbers, as this was wildly inaccurate (see [this thread](https://github.com/jtesta/ssh-audit/issues/240) for the full discussion, as well as the results of the community vote on this matter).
- Fixed crash when running with `-P` and `-T` options simultaneously.
- Fixed host key tests from only reporting a key type at most once despite multiple hosts supporting it; credit [Daniel Lenski](https://github.com/dlenskiSB).
- Fixed DHEat connection rate testing on MacOS X and BSD platforms; credit [Drew Noel](https://github.com/drewmnoel) and [Michael Osipov](https://github.com/michael-o).
- Fixed invalid JSON output when a socket error occurs while performing a client audit.
- Fixed `--conn-rate-test` feature on Windows.
- When scanning multiple targets (using `-T`/`--targets`), the `-p`/`--port` option will now be used as the default port (set to 22 if `-p`/`--port` is not given). Hosts specified in the file can override this default with an explicit port number (i.e.: "host1:1234"). For example, when using `-T targets.txt -p 222`, all hosts in `targets.txt` that do not explicitly include a port number will default to 222; when using `-T targets.txt` (without `-p`), all hosts will use a default of 22.
- Updated built-in server & client policies for Amazon Linux 2023, Debian 12, Rocky Linux 9, and Ubuntu 22.04 to improve host key efficiency and cipher resistance to quantum attacks.
- Added 1 new cipher: `grasshopper-ctr128`.
- Added 2 new key exchanges: `mlkem768x25519-sha256`, `sntrup761x25519-sha512`.
### v3.2.0 (2024-04-22) ### 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)). - 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. - Expanded filter of CBC ciphers to flag for the Terrapin vulnerability. It now includes more rarely found ciphers.

View File

@@ -111,18 +111,9 @@ echo "Processing man page at ${MAN_PAGE} and placing output into ${GLOBALS_PY}..
# * 'MAN_KEEP_FORMATTING' preserves the backspace-overwrite sequence when # * 'MAN_KEEP_FORMATTING' preserves the backspace-overwrite sequence when
# redirected to a file or a pipe. # redirected to a file or a pipe.
# * sed converts unicode hyphens into an ASCI equivalent. # * sed converts unicode hyphens into an ASCI equivalent.
# * The 'ul' command converts the backspace-overwrite sequence to an ANSI
# escape sequence. Not required under Cygwin because man outputs ANSI escape
# codes automatically.
echo BUILTIN_MAN_PAGE = '"""' >> "${GLOBALS_PY}" echo BUILTIN_MAN_PAGE = '"""' >> "${GLOBALS_PY}"
MANWIDTH=80 MAN_KEEP_FORMATTING=1 man "${MAN_PAGE}" | sed $'s/\u2010/-/g' >> "${GLOBALS_PY}"
if [[ "${PLATFORM}" == CYGWIN* ]]; then
MANWIDTH=80 MAN_KEEP_FORMATTING=1 man "${MAN_PAGE}" | sed $'s/\u2010/-/g' >> "${GLOBALS_PY}"
else
MANWIDTH=80 MAN_KEEP_FORMATTING=1 man "${MAN_PAGE}" | ul | sed $'s/\u2010/-/g' >> "${GLOBALS_PY}"
fi
echo '"""' >> "${GLOBALS_PY}" echo '"""' >> "${GLOBALS_PY}"
echo "Done." echo "Done."

View File

@@ -793,6 +793,9 @@ run_custom_policy_test "config2" "test16" "${PROGRAM_RETVAL_FAILURE}"
# Passing test with larger key matching. # Passing test with larger key matching.
run_custom_policy_test "config2" "test17" "${PROGRAM_RETVAL_GOOD}" run_custom_policy_test "config2" "test17" "${PROGRAM_RETVAL_GOOD}"
# Passing test with host key subset matching.
run_custom_policy_test "config2" "test18" "${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 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}" 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}"

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.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 :: 3.12
Programming Language :: Python :: 3.13
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.8,<4 python_requires = >=3.9,<4
[options.packages.find] [options.packages.find]
where = src where = src

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-2025 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
@@ -29,8 +29,6 @@ from typing import Callable, Optional, Union, Any # noqa: F401
from ssh_audit.algorithm import Algorithm from ssh_audit.algorithm import Algorithm
from ssh_audit.product import Product from ssh_audit.product import Product
from ssh_audit.software import Software from ssh_audit.software import Software
from ssh_audit.ssh1_kexdb import SSH1_KexDB
from ssh_audit.ssh1_publickeymessage import SSH1_PublicKeyMessage
from ssh_audit.ssh2_kex import SSH2_Kex from ssh_audit.ssh2_kex import SSH2_Kex
from ssh_audit.ssh2_kexdb import SSH2_KexDB from ssh_audit.ssh2_kexdb import SSH2_KexDB
from ssh_audit.timeframe import Timeframe from ssh_audit.timeframe import Timeframe
@@ -38,28 +36,13 @@ from ssh_audit.utils import Utils
class Algorithms: class Algorithms:
def __init__(self, pkm: Optional[SSH1_PublicKeyMessage], kex: Optional[SSH2_Kex]) -> None: def __init__(self, kex: Optional[SSH2_Kex]) -> None:
self.__ssh1kex = pkm
self.__ssh2kex = kex self.__ssh2kex = kex
@property
def ssh1kex(self) -> Optional[SSH1_PublicKeyMessage]:
return self.__ssh1kex
@property @property
def ssh2kex(self) -> Optional[SSH2_Kex]: def ssh2kex(self) -> Optional[SSH2_Kex]:
return self.__ssh2kex return self.__ssh2kex
@property
def ssh1(self) -> Optional['Algorithms.Item']:
if self.ssh1kex is None:
return None
item = Algorithms.Item(1, SSH1_KexDB.get_db())
item.add('key', ['ssh-rsa1'])
item.add('enc', self.ssh1kex.supported_ciphers)
item.add('aut', self.ssh1kex.supported_authentications)
return item
@property @property
def ssh2(self) -> Optional['Algorithms.Item']: def ssh2(self) -> Optional['Algorithms.Item']:
if self.ssh2kex is None: if self.ssh2kex is None:
@@ -73,7 +56,7 @@ class Algorithms:
@property @property
def values(self) -> Iterable['Algorithms.Item']: def values(self) -> Iterable['Algorithms.Item']:
for item in [self.ssh1, self.ssh2]: for item in [self.ssh2]:
if item is not None: if item is not None:
yield item yield item
@@ -82,10 +65,6 @@ class Algorithms:
def _ml(items: Sequence[str]) -> int: def _ml(items: Sequence[str]) -> int:
return max(len(i) for i in items) return max(len(i) for i in items)
maxlen = 0 maxlen = 0
if self.ssh1kex is not None:
maxlen = max(_ml(self.ssh1kex.supported_ciphers),
_ml(self.ssh1kex.supported_authentications),
maxlen)
if self.ssh2kex is not None: if self.ssh2kex is not None:
maxlen = max(_ml(self.ssh2kex.kex_algorithms), maxlen = max(_ml(self.ssh2kex.kex_algorithms),
_ml(self.ssh2kex.key_algorithms), _ml(self.ssh2kex.key_algorithms),
@@ -172,8 +151,11 @@ class Algorithms:
if fc > 0: if fc > 0:
faults += pow(10, 2 - i) * fc faults += pow(10, 2 - i) * fc
if n not in alg_list: if n not in alg_list:
# Don't recommend certificate or token types; these will only appear in the server's list if they are fully configured & functional on the server. # Don't recommend certificate or token types; these will only appear in the server's list if they are fully configured & functional on the server. Also don't recommend 'ext-info-[cs]' nor 'kex-strict-[cs]-v00@openssh.com' key exchanges.
if faults > 0 or (alg_type == 'key' and (('-cert-' in n) or (n.startswith('sk-')))) or empty_version: if faults > 0 or \
(alg_type == 'key' and (('-cert-' in n) or (n.startswith('sk-')))) or \
(alg_type == 'kex' and (n.startswith('ext-info-') or n.startswith('kex-strict-'))) or \
empty_version:
continue continue
rec[sshv][alg_type]['add'][n] = 0 rec[sshv][alg_type]['add'][n] = 0
else: else:

View File

@@ -1,7 +1,7 @@
""" """
The MIT License (MIT) The MIT License (MIT)
Copyright (C) 2017-2024 Joe Testa (jtesta@positronsecurity.com) Copyright (C) 2017-2025 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
@@ -35,8 +35,6 @@ class AuditConf:
def __init__(self, host: str = '', port: int = 22) -> None: def __init__(self, host: str = '', port: int = 22) -> None:
self.host = host self.host = host
self.port = port self.port = port
self.ssh1 = True
self.ssh2 = True
self.batch = False self.batch = False
self.client_audit = False self.client_audit = False
self.colors = True self.colors = True
@@ -73,7 +71,7 @@ class AuditConf:
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', 'skip_rate_test']: if name in ['batch', 'client_audit', 'colors', 'json', 'json_print_indent', 'list_policies', 'manual', 'make_policy', '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)

View File

@@ -30,14 +30,22 @@ BUILTIN_POLICIES: Dict[str, Dict[str, Union[Optional[str], Optional[List[str]],
# Amazon Linux 2023 # 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}, '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},
'Hardened Amazon Linux 2023 (version 2)': {'version': '2', 'changelog': 'Re-ordered host keys to prioritize ED25519 due to efficiency. Re-ordered cipher list to prioritize larger key sizes as a countermeasure to quantum attacks.', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519', 'rsa-sha2-512', 'rsa-sha2-256'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['sntrup761x25519-sha512@openssh.com', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-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', 'aes256-ctr', 'aes192-ctr', 'aes128-gcm@openssh.com', '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 # 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}, '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},
'Hardened Debian 12 (version 2)': {'version': '2', 'changelog': 'Re-ordered host keys to prioritize ED25519 due to efficiency. Re-ordered cipher list to prioritize larger key sizes as a countermeasure to quantum attacks.', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519', 'rsa-sha2-512', 'rsa-sha2-256'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['sntrup761x25519-sha512@openssh.com', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-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', 'aes256-ctr', 'aes192-ctr', 'aes128-gcm@openssh.com', '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 Debian 13 (version 1)': {'version': '1', 'changelog': 'Initial revision. As compared to the Debian 12 guide, the following changes were made: 1.) all non-post-quantum key exchanges were removed, and mlkem768x25519-sha256 and sntrup761x25519-sha512 were added, 2.) editing the /etc/ssh/moduli file is no longer done, as we no longer use any group-exchange algorithms (none of them currently protect against quantum attacks), 3.) editing the sshd_config file is no longer done, in order to make system upgrades easier for users, 4.) network-level connection throttling is no longer done, as OpenSSH prevents the DHEat attack by default now.', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519', 'rsa-sha2-512', 'rsa-sha2-256'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['mlkem768x25519-sha256', 'sntrup761x25519-sha512', 'sntrup761x25519-sha512@openssh.com', 'ext-info-s', 'kex-strict-s-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-gcm@openssh.com', 'aes128-ctr'], 'macs': ['hmac-sha2-512-etm@openssh.com', 'hmac-sha2-256-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {"rsa-sha2-256": {"hostkey_size": 4096}, "rsa-sha2-256-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "rsa-sha2-512": {"hostkey_size": 4096}, "rsa-sha2-512-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "sk-ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}, "sk-ssh-ed25519@openssh.com": {"hostkey_size": 256}, "ssh-ed25519": {"hostkey_size": 256}, "ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}}, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 3072}, "allow_hostkey_subset_and_reordering": True, 'server_policy': True},
# Rocky Linux 9 # 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}, '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},
'Hardened Rocky Linux 9 (version 2)': {'version': '2', 'changelog': 'Re-ordered host keys to prioritize ED25519 due to efficiency. Re-ordered cipher list to prioritize larger key sizes as a countermeasure to quantum attacks.', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519', 'rsa-sha2-512', 'rsa-sha2-256'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['sntrup761x25519-sha512@openssh.com', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-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', 'aes256-ctr', 'aes192-ctr', 'aes128-gcm@openssh.com', '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 # Ubuntu Server policies
@@ -49,6 +57,11 @@ BUILTIN_POLICIES: Dict[str, Dict[str, Union[Optional[str], Optional[List[str]],
'Hardened Ubuntu Server 22.04 LTS (version 5)': {'version': '5', 'changelog': 'Added kex-strict-s-v00@openssh.com to kex list.', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['sntrup761x25519-sha512@openssh.com', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256', 'kex-strict-s-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {"rsa-sha2-256": {"hostkey_size": 4096}, "rsa-sha2-256-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "rsa-sha2-512": {"hostkey_size": 4096}, "rsa-sha2-512-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "sk-ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}, "sk-ssh-ed25519@openssh.com": {"hostkey_size": 256}, "ssh-ed25519": {"hostkey_size": 256}, "ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}}, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 3072}, 'server_policy': True}, 'Hardened Ubuntu Server 22.04 LTS (version 5)': {'version': '5', 'changelog': 'Added kex-strict-s-v00@openssh.com to kex list.', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['sntrup761x25519-sha512@openssh.com', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256', 'kex-strict-s-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {"rsa-sha2-256": {"hostkey_size": 4096}, "rsa-sha2-256-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "rsa-sha2-512": {"hostkey_size": 4096}, "rsa-sha2-512-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "sk-ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}, "sk-ssh-ed25519@openssh.com": {"hostkey_size": 256}, "ssh-ed25519": {"hostkey_size": 256}, "ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}}, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 3072}, 'server_policy': True},
'Hardened Ubuntu Server 22.04 LTS (version 6)': {'version': '6', 'changelog': 'Re-ordered host keys to prioritize ED25519 due to efficiency. Re-ordered cipher list to prioritize larger key sizes as a countermeasure to quantum attacks.', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519', 'rsa-sha2-512', 'rsa-sha2-256'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['sntrup761x25519-sha512@openssh.com', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-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', 'aes256-ctr', 'aes192-ctr', 'aes128-gcm@openssh.com', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {"rsa-sha2-256": {"hostkey_size": 4096}, "rsa-sha2-256-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "rsa-sha2-512": {"hostkey_size": 4096}, "rsa-sha2-512-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "sk-ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}, "sk-ssh-ed25519@openssh.com": {"hostkey_size": 256}, "ssh-ed25519": {"hostkey_size": 256}, "ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}}, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 3072}, 'server_policy': True},
'Hardened Ubuntu Server 24.04 LTS (version 2)': {'version': '2', 'changelog': 'Removed commands directly editing sshd_config to make system updates easier for users.', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519', 'rsa-sha2-512', 'rsa-sha2-256'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['sntrup761x25519-sha512@openssh.com', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256', 'diffie-hellman-group16-sha512', 'ext-info-s', 'kex-strict-s-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-gcm@openssh.com', 'aes128-ctr'], 'macs': ['hmac-sha2-512-etm@openssh.com', 'hmac-sha2-256-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {"rsa-sha2-256": {"hostkey_size": 4096}, "rsa-sha2-256-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "rsa-sha2-512": {"hostkey_size": 4096}, "rsa-sha2-512-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "sk-ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}, "sk-ssh-ed25519@openssh.com": {"hostkey_size": 256}, "ssh-ed25519": {"hostkey_size": 256}, "ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}}, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 3072}, "allow_hostkey_subset_and_reordering": True, "server_policy": True},
'Hardened Ubuntu Server 24.04 LTS (version 1)': {'version': '1', 'changelog': 'Initial version.', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519', 'rsa-sha2-512', 'rsa-sha2-256'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['sntrup761x25519-sha512@openssh.com', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256', 'diffie-hellman-group16-sha512', 'ext-info-s', 'kex-strict-s-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-gcm@openssh.com', 'aes128-ctr'], 'macs': ['hmac-sha2-512-etm@openssh.com', 'hmac-sha2-256-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {"rsa-sha2-256": {"hostkey_size": 4096}, "rsa-sha2-256-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "rsa-sha2-512": {"hostkey_size": 4096}, "rsa-sha2-512-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "sk-ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}, "sk-ssh-ed25519@openssh.com": {"hostkey_size": 256}, "ssh-ed25519": {"hostkey_size": 256}, "ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}}, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 3072}, 'server_policy': True},
# Generic OpenSSH Server policies # Generic OpenSSH Server policies
@@ -94,21 +107,33 @@ BUILTIN_POLICIES: Dict[str, Dict[str, Union[Optional[str], Optional[List[str]],
'Hardened OpenSSH Server v9.7 (version 1)': {'version': '1', 'changelog': 'Initial version.', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['sntrup761x25519-sha512@openssh.com', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256', 'ext-info-s', 'kex-strict-s-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {"rsa-sha2-256": {"hostkey_size": 4096}, "rsa-sha2-256-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "rsa-sha2-512": {"hostkey_size": 4096}, "rsa-sha2-512-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "sk-ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}, "sk-ssh-ed25519@openssh.com": {"hostkey_size": 256}, "ssh-ed25519": {"hostkey_size": 256}, "ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}}, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 3072}, 'server_policy': True}, 'Hardened OpenSSH Server v9.7 (version 1)': {'version': '1', 'changelog': 'Initial version.', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['sntrup761x25519-sha512@openssh.com', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256', 'ext-info-s', 'kex-strict-s-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {"rsa-sha2-256": {"hostkey_size": 4096}, "rsa-sha2-256-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "rsa-sha2-512": {"hostkey_size": 4096}, "rsa-sha2-512-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "sk-ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}, "sk-ssh-ed25519@openssh.com": {"hostkey_size": 256}, "ssh-ed25519": {"hostkey_size": 256}, "ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}}, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 3072}, 'server_policy': True},
'Hardened OpenSSH Server v9.8 (version 1)': {'version': '1', 'changelog': 'Initial version.', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['sntrup761x25519-sha512@openssh.com', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256', 'ext-info-s', 'kex-strict-s-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {"rsa-sha2-256": {"hostkey_size": 4096}, "rsa-sha2-256-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "rsa-sha2-512": {"hostkey_size": 4096}, "rsa-sha2-512-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "sk-ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}, "sk-ssh-ed25519@openssh.com": {"hostkey_size": 256}, "ssh-ed25519": {"hostkey_size": 256}, "ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}}, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 3072}, 'server_policy': True},
'Hardened OpenSSH Server v9.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', 'sntrup761x25519-sha512@openssh.com', 'mlkem768x25519-sha256', '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 v10.0 (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': ['mlkem768x25519-sha256', 'sntrup761x25519-sha512', 'sntrup761x25519-sha512@openssh.com', 'ext-info-s', 'kex-strict-s-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-gcm@openssh.com', 'aes128-ctr'], 'macs': ['hmac-sha2-512-etm@openssh.com', 'hmac-sha2-256-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {"rsa-sha2-256": {"hostkey_size": 4096}, "rsa-sha2-256-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "rsa-sha2-512": {"hostkey_size": 4096}, "rsa-sha2-512-cert-v01@openssh.com": {"ca_key_size": 4096, "ca_key_type": "ssh-rsa", "hostkey_size": 4096}, "sk-ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}, "sk-ssh-ed25519@openssh.com": {"hostkey_size": 256}, "ssh-ed25519": {"hostkey_size": 256}, "ssh-ed25519-cert-v01@openssh.com": {"ca_key_size": 256, "ca_key_type": "ssh-ed25519", "hostkey_size": 256}}, 'dh_modulus_sizes': {}, 'server_policy': True},
# Amazon Linux Policies # 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}, '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},
'Hardened Amazon Linux Client 2023 (version 2)': {'version': '2', 'changelog': 'Re-ordered cipher list to prioritize larger key sizes as a countermeasure to quantum attacks.', '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', 'aes256-ctr', 'aes192-ctr', 'aes128-gcm@openssh.com', '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 # 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}, '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},
'Hardened Debian Client 12 (version 2)': {'version': '2', 'changelog': 'Re-ordered cipher list to prioritize larger key sizes as a countermeasure to quantum attacks.', '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', 'aes256-ctr', 'aes192-ctr', 'aes128-gcm@openssh.com', '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 Debian Client 13 (version 1)': {'version': '1', 'changelog': 'Initial revision. As compared to the Debian 12 guide, the following changes were made: all non-post-quantum key exchanges were removed, and mlkem768x25519-sha256 and sntrup761x25519-sha512 were added.', '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': ['mlkem768x25519-sha256', 'sntrup761x25519-sha512', 'sntrup761x25519-sha512@openssh.com', 'ext-info-c', 'kex-strict-c-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-gcm@openssh.com', 'aes128-ctr'], 'macs': ['hmac-sha2-512-etm@openssh.com', 'hmac-sha2-256-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'dh_modulus_sizes': None, 'server_policy': False},
# Rocky Linux Policies # 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}, '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},
'Hardened Rocky Linux Client 9 (version 2)': {'version': '2', 'changelog': 'Re-ordered cipher list to prioritize larger key sizes as a countermeasure to quantum attacks.', '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', 'aes256-ctr', 'aes192-ctr', 'aes128-gcm@openssh.com', '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 # Ubuntu Client policies
@@ -120,5 +145,8 @@ BUILTIN_POLICIES: Dict[str, Dict[str, Union[Optional[str], Optional[List[str]],
'Hardened Ubuntu Client 22.04 LTS (version 4)': {'version': '4', 'changelog': 'Added kex-strict-c-v00@openssh.com to kex list.', 'banner': None, 'compressions': None, 'host_keys': ['sk-ssh-ed25519-cert-v01@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'sk-ssh-ed25519@openssh.com', 'ssh-ed25519', 'rsa-sha2-512', 'rsa-sha2-256'], 'optional_host_keys': None, 'kex': ['sntrup761x25519-sha512@openssh.com', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256', 'ext-info-c', 'kex-strict-c-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'dh_modulus_sizes': None, 'server_policy': False}, 'Hardened Ubuntu Client 22.04 LTS (version 4)': {'version': '4', 'changelog': 'Added kex-strict-c-v00@openssh.com to kex list.', 'banner': None, 'compressions': None, 'host_keys': ['sk-ssh-ed25519-cert-v01@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'sk-ssh-ed25519@openssh.com', 'ssh-ed25519', 'rsa-sha2-512', 'rsa-sha2-256'], 'optional_host_keys': None, 'kex': ['sntrup761x25519-sha512@openssh.com', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256', 'ext-info-c', 'kex-strict-c-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'dh_modulus_sizes': None, 'server_policy': False},
'Hardened Ubuntu Client 22.04 LTS (version 5)': {'version': '5', 'changelog': 'Re-ordered cipher list to prioritize larger key sizes as a countermeasure to quantum attacks.', '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', 'aes256-ctr', 'aes192-ctr', 'aes128-gcm@openssh.com', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'dh_modulus_sizes': None, 'server_policy': False},
'Hardened Ubuntu Client 24.04 LTS (version 1)': {'version': '1', 'changelog': 'Initial version.', 'banner': None, 'compressions': None, 'host_keys': ['sk-ssh-ed25519-cert-v01@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'sk-ssh-ed25519@openssh.com', 'ssh-ed25519', 'rsa-sha2-512', 'rsa-sha2-256'], 'optional_host_keys': None, 'kex': ['sntrup761x25519-sha512@openssh.com', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256', 'diffie-hellman-group16-sha512', 'ext-info-c', 'kex-strict-c-v00@openssh.com'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-gcm@openssh.com', 'aes128-ctr'], 'macs': ['hmac-sha2-512-etm@openssh.com', 'hmac-sha2-256-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'dh_modulus_sizes': None, 'server_policy': False},
} }

View File

@@ -21,6 +21,7 @@
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 errno
import multiprocessing import multiprocessing
import os import os
import queue import queue
@@ -160,6 +161,11 @@ class DHEat:
# The SSH2_Kex object that we recieved from the server in a prior connection. We'll use it as a template to craft our own kex. # The SSH2_Kex object that we recieved from the server in a prior connection. We'll use it as a template to craft our own kex.
self.kex = kex self.kex = kex
# Resolve the target to an IP address depending on the user preferences (IPv4 or IPv6).
self.debug("Resolving target %s..." % self.target)
self.target_address_family, self.target_ip_address = DHEat._resolve_hostname(self.target, aconf.ip_version_preference)
self.debug("Resolved %s to %s (address family %u)" % (self.target, self.target_ip_address, self.target_address_family))
# The connection and read timeouts. # The connection and read timeouts.
self.connect_timeout = aconf.timeout self.connect_timeout = aconf.timeout
self.read_timeout = aconf.timeout self.read_timeout = aconf.timeout
@@ -318,11 +324,10 @@ class DHEat:
del socket_dict[s] del socket_dict[s]
if sys.platform == "win32": # Resolve the target into an IP address
DHEat.YELLOWB = "\033[1;93m" out.d("Resolving target %s..." % aconf.host)
DHEat.CLEAR = "\033[0m" target_address_family, target_ip_address = DHEat._resolve_hostname(aconf.host, aconf.ip_version_preference)
print("\n%sUnfortunately, this feature is not currently functional under Windows.%s This should get fixed in a future release. See: <https://github.com/jtesta/ssh-audit/issues/261>" % (DHEat.YELLOWB, DHEat.CLEAR)) out.d("Resolved %s to %s (address family %u)" % (aconf.host, target_ip_address, target_address_family))
return ""
spinner = ["-", "\\", "|", "/"] spinner = ["-", "\\", "|", "/"]
spinner_index = 0 spinner_index = 0
@@ -349,7 +354,7 @@ class DHEat:
rate_str = " at a max rate of %s%u%s connections per second" % (DHEat.WHITEB, aconf.conn_rate_test_target_rate, DHEat.CLEAR) rate_str = " at a max rate of %s%u%s connections per second" % (DHEat.WHITEB, aconf.conn_rate_test_target_rate, DHEat.CLEAR)
print() print()
print("Performing non-disruptive rate test against %s[%s]:%u%s with %s%u%s concurrent sockets%s. No Diffie-Hellman requests will be sent." % (DHEat.WHITEB, aconf.host, aconf.port, DHEat.CLEAR, DHEat.WHITEB, concurrent_sockets, DHEat.CLEAR, rate_str)) print("Performing non-disruptive rate test against %s[%s]:%u%s with %s%u%s concurrent sockets%s. No Diffie-Hellman requests will be sent." % (DHEat.WHITEB, target_ip_address, aconf.port, DHEat.CLEAR, DHEat.WHITEB, concurrent_sockets, DHEat.CLEAR, rate_str))
print() print()
# Make room for the multi-line output. # Make room for the multi-line output.
@@ -426,16 +431,16 @@ class DHEat:
# Open new sockets until we've hit the number of concurrent sockets, or if we exceeded the number of maximum connections. # Open new sockets until we've hit the number of concurrent sockets, or if we exceeded the number of maximum connections.
while (len(socket_dict) < concurrent_sockets) and (len(socket_dict) + num_opened_connections < max_connections): while (len(socket_dict) < concurrent_sockets) and (len(socket_dict) + num_opened_connections < max_connections):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s = socket.socket(target_address_family, socket.SOCK_STREAM)
s.setblocking(False) s.setblocking(False)
# out.d("Creating socket (%u of %u already exist)..." % (len(socket_dict), concurrent_sockets), write_now=True) # out.d("Creating socket (%u of %u already exist)..." % (len(socket_dict), concurrent_sockets), write_now=True)
ret = s.connect_ex((aconf.host, aconf.port)) ret = s.connect_ex((target_ip_address, aconf.port))
num_attempted_connections += 1 num_attempted_connections += 1
if ret in [0, 115]: # Check if connection is successful or EINPROGRESS. if ret in [0, errno.EINPROGRESS, errno.EWOULDBLOCK]:
socket_dict[s] = now socket_dict[s] = now
else: else:
out.d("connect_ex() returned: %d" % ret, write_now=True) out.d("connect_ex() returned: %s (%d)" % (os.strerror(ret), ret), write_now=True)
# out.d("Calling select() on %u sockets..." % len(socket_dict), write_now=True) # out.d("Calling select() on %u sockets..." % len(socket_dict), write_now=True)
socket_list: List[socket.socket] = [*socket_dict] # Get a list of sockets from the dictionary. socket_list: List[socket.socket] = [*socket_dict] # Get a list of sockets from the dictionary.
@@ -743,6 +748,22 @@ class DHEat:
print() print()
@staticmethod
def _resolve_hostname(host: str, ip_version_preference: List[int]) -> Tuple[int, str]:
'''Resolves a hostname to its IPv4 or IPv6 address, depending on user preference.'''
family = socket.AF_UNSPEC
if len(ip_version_preference) == 1:
family = socket.AF_INET if ip_version_preference[0] == 4 else socket.AF_INET6
r = socket.getaddrinfo(host, 0, family, socket.SOCK_STREAM)
for address_family, socktype, _, _, addr in r:
if socktype == socket.SOCK_STREAM:
return int(address_family), str(addr[0])
return int(socket.AF_UNSPEC), ''
def _run(self) -> bool: def _run(self) -> bool:
'''Where all the magic happens.''' '''Where all the magic happens.'''
@@ -751,7 +772,7 @@ class DHEat:
if sys.platform == "win32": if sys.platform == "win32":
self.output("%sWARNING:%s this feature has not been thoroughly tested on Windows. It may perform worse than on UNIX OSes." % (self.YELLOWB, self.CLEAR)) self.output("%sWARNING:%s this feature has not been thoroughly tested on Windows. It may perform worse than on UNIX OSes." % (self.YELLOWB, self.CLEAR))
self.output("Running DHEat test against %s[%s]:%u%s with %s%u%s concurrent sockets..." % (self.WHITEB, self.target, self.port, self.CLEAR, self.WHITEB, self.concurrent_connections, self.CLEAR)) self.output("Running DHEat test against %s[%s]:%u%s with %s%u%s concurrent sockets..." % (self.WHITEB, self.target_ip_address, self.port, self.CLEAR, self.WHITEB, self.concurrent_connections, self.CLEAR))
# If the user didn't specify an exact kex algorithm to test, check our prioritized list against what the server supports. Larger p-values (such as group18: 8192-bits) cause the most strain on the server. # If the user didn't specify an exact kex algorithm to test, check our prioritized list against what the server supports. Larger p-values (such as group18: 8192-bits) cause the most strain on the server.
chosen_alg = "" chosen_alg = ""
@@ -894,7 +915,8 @@ class DHEat:
# Copy variables from the object (which might exist in another process?). This might cut down on inter-process overhead. # Copy variables from the object (which might exist in another process?). This might cut down on inter-process overhead.
connect_timeout = self.connect_timeout connect_timeout = self.connect_timeout
target = self.target target_ip_address = self.target_ip_address
target_address_family = self.target_address_family
port = self.port port = self.port
# Determine if we are attacking with a GEX. # Determine if we are attacking with a GEX.
@@ -945,17 +967,17 @@ class DHEat:
num_socket_exceptions = 0 num_socket_exceptions = 0
num_openssh_throttled_connections = 0 num_openssh_throttled_connections = 0
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s = socket.socket(target_address_family, socket.SOCK_STREAM)
s.settimeout(connect_timeout) s.settimeout(connect_timeout)
# Loop until a successful TCP connection is made. # Loop until a successful TCP connection is made.
connected = False connected = False
while not connected: while not connected:
# self.debug("Connecting to %s:%d" % (self.target, self.port)) # self.debug("Connecting to %s:%d" % (self.target_ip_address, self.port))
try: try:
num_attempted_tcp_connections += 1 num_attempted_tcp_connections += 1
s.connect((target, port)) s.connect((target_ip_address, port))
connected = True connected = True
except OSError as e: except OSError as e:
self.debug("Failed to connect: %s" % str(e)) self.debug("Failed to connect: %s" % str(e))

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-2025 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
@@ -30,6 +30,7 @@ from typing import Callable, Optional, Union, Any # noqa: F401
from ssh_audit.banner import Banner from ssh_audit.banner import Banner
from ssh_audit.kexdh import KexDHException, KexGroupExchange, KexGroupExchange_SHA1, KexGroupExchange_SHA256 from ssh_audit.kexdh import KexDHException, KexGroupExchange, KexGroupExchange_SHA1, KexGroupExchange_SHA256
from ssh_audit.software import Software
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 +65,7 @@ class GEXTest:
try: try:
# Parse the server's KEX. # Parse the server's KEX.
_, payload = s.read_packet(2) _, payload = s.read_packet()
SSH2_Kex.parse(out, payload) SSH2_Kex.parse(out, payload)
except (KexDHException, struct.error): 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)
@@ -158,9 +159,22 @@ class GEXTest:
smallest_modulus, reconnect_failed = GEXTest._send_init(out, s, kex_group, kex, gex_alg, bits, bits, bits) smallest_modulus, reconnect_failed = GEXTest._send_init(out, s, kex_group, kex, gex_alg, bits, bits, bits)
# 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. # If the banner exists, then parse it into a Software object. Next, if the target host is OpenSSH, check if its version is 9.9 or less; these versions are known to have a GEX fallback mechanism.
software = None if banner is None else Software.parse(banner)
is_openssh = False
has_fallback_mechanism = False
if (software is not None) and (software.product == "OpenSSH"):
is_openssh = True
try:
if float(software.version) <= 9.9:
has_fallback_mechanism = True
out.d(f"Found version of OpenSSH that includes GEX fallback mechanism: {software}")
except ValueError:
pass
# If the smallest modulus is 2048 and the server is OpenSSH v9.9 or less, 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. 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.
openssh_test_updated = False openssh_test_updated = False
if (smallest_modulus == 2048) and (banner is not None) and (banner.software is not None) and (banner.software.find('OpenSSH') != -1): if (smallest_modulus == 2048) and is_openssh and has_fallback_mechanism:
out.d('First pass found a minimum GEX modulus of 2048 against OpenSSH server. Performing a second pass to get a more accurate result...') out.d('First pass found a minimum GEX modulus of 2048 against OpenSSH server. Performing a second pass to get a more accurate result...')
smallest_modulus, _ = GEXTest._send_init(out, s, kex_group, kex, gex_alg, 2048, 3072, 4096) smallest_modulus, _ = GEXTest._send_init(out, s, kex_group, kex, gex_alg, 2048, 3072, 4096)
out.d('Modulus size returned by server during second pass: %d bits' % smallest_modulus, write_now=True) out.d('Modulus size returned by server during second pass: %d bits' % smallest_modulus, write_now=True)

View File

@@ -22,7 +22,7 @@
THE SOFTWARE. THE SOFTWARE.
""" """
# The version to display. # The version to display.
VERSION = 'v3.2.0' VERSION = 'v3.4.0-dev'
# SSH software to impersonate # SSH software to impersonate
SSH_HEADER = 'SSH-{0}-OpenSSH_8.2' SSH_HEADER = 'SSH-{0}-OpenSSH_8.2'

View File

@@ -0,0 +1,687 @@
"""
The MIT License (MIT)
Copyright (C) 2025 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
from ssh_audit.outputbuffer import OutputBuffer
class Hardening_Guides:
HARDENING_GUIDES: Dict[str, Any] = {
"Amazon Linux 2023": [
{
"server_guide": True,
"version": 3,
"version_date": "2024-10-01",
"change_log": "Re-ordered host keys to prioritize ED25519 due to efficiency. Re-ordered cipher list to prioritize larger key sizes as a countermeasure to quantum attacks.",
"notes": "all commands below are to be executed as the root user.",
"commands": [
{
"heading": "Re-generate the RSA and ED25519 keys",
"comment": "",
"command": "rm -f /etc/ssh/ssh_host_*\nssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N \"\"\nssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N \"\""
},
{
"heading": "Enable the ED25519 and RSA keys",
"comment": "Enable the ED25519 and RSA HostKey directives in the /etc/ssh/sshd_config file:",
"command": "echo -e \"\\nHostKey /etc/ssh/ssh_host_ed25519_key\\nHostKey /etc/ssh/ssh_host_rsa_key\" >> /etc/ssh/sshd_config"
},
{
"heading": "Remove small Diffie-Hellman moduli",
"comment": "",
"command": "awk '$5 >= 3071' /etc/ssh/moduli > /etc/ssh/moduli.safe\nmv -f /etc/ssh/moduli.safe /etc/ssh/moduli"
},
{
"heading": "Restrict supported key exchange, cipher, and MAC algorithms",
"comment": "",
"command": "echo -e \"# Restrict key exchange, cipher, and MAC algorithms, as per sshaudit.com hardening guide.\\nKexAlgorithms sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org,gss-curve25519-sha256-,diffie-hellman-group16-sha512,gss-group16-sha512-,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256\\n\\nCiphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\\n\\nMACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com\\n\\nHostKeyAlgorithms 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\\n\\nCASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\\n\\nGSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\\n\\nHostbasedAcceptedAlgorithms sk-ssh-ed25519-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-256\\n\\nPubkeyAcceptedAlgorithms 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\\n\\n\" > /etc/crypto-policies/back-ends/opensshserver.config"
},
{
"heading": "Restart OpenSSH server",
"comment": "",
"command": "systemctl restart sshd"
},
{
"heading": "Implement connection rate throttling",
"comment": "Connection rate throttling is needed in order to protect against the DHEat denial-of-service attack. A complete and flexible solution is to use iptables to allow up to 10 connections every 10 seconds from any one source address. An alternate solution is to set OpenSSH's PerSourceMaxStartups directive to 1 (note, however, that this can cause incomplete results during ssh-audit scans, as well as other client failures when bursts of connections are made).",
"command": "dnf install -y iptables\niptables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --set\niptables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 10 --hitcount 10 -j DROP\nip6tables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --set\nip6tables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 10 --hitcount 10 -j DROP\ndnf install -y iptables-services\niptables-save > /etc/sysconfig/iptables\nip6tables-save > /etc/sysconfig/ip6tables\nsystemctl enable iptables\nsystemctl enable ip6tables\nsystemctl start iptables\nsystemctl start ip6tables"
},
]
},
{
"server_guide": True,
"version": 2,
"version_date": "2024-04-22",
"change_log": "Added connection throttling instructions to counteract the DHEat denial-of-service attack.",
"notes": "",
"commands": [] # Commands for this older version are not tracked here.
},
{
"server_guide": True,
"version": 1,
"version_date": "2024-03-15",
"change_log": "Initial revision.",
"notes": "",
"commands": [] # Commands for this older version are not tracked here.
},
{
"server_guide": False,
"version": 3,
"version_date": "2024-10-01",
"change_log": "Re-ordered cipher list to prioritize larger key sizes as a countermeasure to quantum attacks.",
"notes": "",
"commands": [
{
"heading": "Run the following in a terminal to harden the SSH client for the local user:",
"comment": "",
"command": "mkdir -p -m 0700 ~/.ssh; echo -e \"\\nHost *\\n Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\\n\\n KexAlgorithms sntrup761x25519-sha512@openssh.com,gss-curve25519-sha256-,curve25519-sha256,curve25519-sha256@libssh.org,gss-group16-sha512-,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256\\n\\n MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com\\n\\n HostKeyAlgorithms 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\\n\\n CASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\\n\\n GSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\\n\\n HostbasedAcceptedAlgorithms sk-ssh-ed25519-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-256\\n\\n PubkeyAcceptedAlgorithms sk-ssh-ed25519-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-256\\n\\n\" >> ~/.ssh/config"
},
]
},
{
"server_guide": False,
"version": 2,
"version_date": "2024-04-22",
"change_log": "added connection throttling instructions to counteract the DHEat denial-of-service attack.",
"notes": "",
"commands": [] # Commands for this older version are not tracked here.
},
{
"server_guide": False,
"version": 1,
"version_date": "2024-03-15",
"change_log": "Initial revision.",
"notes": "",
"commands": [] # Commands for this older version are not tracked here.
},
],
"Debian 11": [
{
"server_guide": True,
"version": 1,
"version_date": "2021-09-17",
"change_log": "Latest version.",
"notes": "all commands below are to be executed as the root user.",
"commands": [
{
"heading": "Re-generate the RSA and ED25519 keys",
"comment": "",
"command": "rm -f /etc/ssh/ssh_host_*\nssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N \"\"\nssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N \"\""
},
{
"heading": "Enable the RSA and ED25519 keys",
"comment": "Enable the RSA and ED25519 HostKey directives in the /etc/ssh/sshd_config file:",
"command": "sed -i 's/^\\#HostKey \\/etc\\/ssh\\/ssh_host_\\(rsa\\|ed25519\\)_key$/HostKey \\/etc\\/ssh\\/ssh_host_\\1_key/g' /etc/ssh/sshd_config"
},
{
"heading": "Remove small Diffie-Hellman moduli",
"comment": "",
"command": "awk '$5 >= 3071' /etc/ssh/moduli > /etc/ssh/moduli.safe\nmv -f /etc/ssh/moduli.safe /etc/ssh/moduli"
},
{
"heading": "Restrict supported key exchange, cipher, and MAC algorithms",
"comment": "",
"command": "echo -e \"\\n# Restrict key exchange, cipher, and MAC algorithms, as per sshaudit.com\\n# hardening guide.\\nKexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,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\\nHostKeyAlgorithms 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-512,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-512-cert-v01@openssh.com\" > /etc/ssh/sshd_config.d/ssh-audit_hardening.conf"
},
{
"heading": "Restart OpenSSH server",
"comment": "",
"command": "service ssh restart"
},
]
},
],
"Debian 12": [
{
"server_guide": True,
"version": 3,
"version_date": "2025-04-18",
"change_log": "Added sntrup761x25519-sha512 to KexAlgorithms.",
"notes": "all commands below are to be executed as the root user.",
"commands": [
{
"heading": "Re-generate the RSA and ED25519 keys",
"comment": "",
"command": "rm /etc/ssh/ssh_host_*\nssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N \"\"\nssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N \"\""
},
{
"heading": "Enable the ED25519 and RSA keys",
"comment": "Enable the ED25519 and RSA HostKey directives in the /etc/ssh/sshd_config file:",
"command": "echo -e \"\\nHostKey /etc/ssh/ssh_host_ed25519_key\\nHostKey /etc/ssh/ssh_host_rsa_key\" >> /etc/ssh/sshd_config"
},
{
"heading": "Remove small Diffie-Hellman moduli",
"comment": "",
"command": "awk '$5 >= 3071' /etc/ssh/moduli > /etc/ssh/moduli.safe\nmv /etc/ssh/moduli.safe /etc/ssh/moduli"
},
{
"heading": "Restrict supported key exchange, cipher, and MAC algorithms",
"comment": "",
"command": "echo -e \"# Restrict key exchange, cipher, and MAC algorithms, as per sshaudit.com\\n# hardening guide.\\n KexAlgorithms sntrup761x25519-sha512,sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org,gss-curve25519-sha256-,diffie-hellman-group16-sha512,gss-group16-sha512-,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256\\n\\nCiphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\\n\\nMACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com\\n\\nHostKeyAlgorithms 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\\n\\nRequiredRSASize 3072\\n\\nCASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\\n\\nGSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\\n\\nHostbasedAcceptedAlgorithms sk-ssh-ed25519-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-256\\n\\nPubkeyAcceptedAlgorithms sk-ssh-ed25519-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-256\\n\\n\" > /etc/ssh/sshd_config.d/ssh-audit_hardening.conf"
},
{
"heading": "Restart OpenSSH server",
"comment": "",
"command": "service ssh restart"
},
{
"heading": "Implement connection rate throttling",
"comment": "Connection rate throttling is needed in order to protect against the DHEat denial-of-service attack. A complete and flexible solution is to use iptables to allow up to 10 connections every 10 seconds from any one source address. An alternate solution is to set OpenSSH's PerSourceMaxStartups directive to 1 (note, however, that this can cause incomplete results during ssh-audit scans, as well as other client failures when bursts of connections are made).",
"command": "apt update\nDEBIAN_FRONTEND=noninteractive apt install -q -y iptables netfilter-persistent iptables-persistent\niptables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --set\niptables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 10 --hitcount 10 -j DROP\nip6tables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --set\nip6tables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 10 --hitcount 10 -j DROP\nservice netfilter-persistent save"
},
]
},
{
"server_guide": True,
"version": 2,
"version_date": "2024-10-01",
"change_log": "Re-ordered host keys to prioritize ED25519 due to efficiency. Re-ordered cipher list to prioritize larger key sizes as a countermeasure to quantum attacks.",
"notes": "",
"commands": [] # Commands for this older version are not tracked here.
},
{
"server_guide": True,
"version": 1,
"version_date": "2024-04-24",
"change_log": "Added connection throttling instructions to counteract the DHEat denial-of-service attack.",
"notes": "",
"commands": [] # Commands for this older version are not tracked here.
},
{
"server_guide": False,
"version": 2,
"version_date": "2024-10-01",
"change_log": "Added RequiredRSASize directive to enforce a minimum of 3072-bit user and host-based authentication keys. Re-ordered cipher list to prioritize larger key sizes as a countermeasure to quantum attacks.",
"notes": "",
"commands": [
{
"heading": "Run the following in a terminal to harden the SSH client for the local user:",
"comment": "",
"command": "mkdir -p -m 0700 ~/.ssh; echo -e \"\\nHost *\\n Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\\n\\n KexAlgorithms sntrup761x25519-sha512@openssh.com,gss-curve25519-sha256-,curve25519-sha256,curve25519-sha256@libssh.org,gss-group16-sha512-,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256\\n\\n MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com\\n\\n RequiredRSASize 3072\\n\\n HostKeyAlgorithms 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\\n\\n CASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\\n\\n GSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\\n\\n HostbasedAcceptedAlgorithms sk-ssh-ed25519-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-256\\n\\n PubkeyAcceptedAlgorithms sk-ssh-ed25519-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-256\\n\\n\" >> ~/.ssh/config"
},
]
},
{
"server_guide": False,
"version": 1,
"version_date": "2024-03-15",
"change_log": "Initial revision.",
"notes": "",
"commands": [] # Commands for this older version are not tracked here.
},
],
"Debian 13": [
{
"server_guide": True,
"version": 1,
"version_date": "2025-09-01",
"change_log": "Initial revision. As compared to the Debian 12 guide, the following changes were made: 1.) all non-post-quantum key exchanges were removed, and mlkem768x25519-sha256 and sntrup761x25519-sha512 were added, 2.) editing the /etc/ssh/moduli file is no longer done, as we no longer use any group-exchange algorithms (none of them currently protect against quantum attacks), 3.) editing the sshd_config file is no longer done, in order to make system upgrades easier for users, 4.) network-level connection throttling is no longer done, as OpenSSH prevents the DHEat attack by default now.",
"notes": "all commands below are to be executed as the root user.",
"commands": [
{
"heading": "Re-generate the ED25519 and RSA keys",
"comment": "",
"command": "rm /etc/ssh/ssh_host_*\nssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N \"\"\nssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N \"\""
},
{
"heading": "Restrict supported key exchange, cipher, and MAC algorithms",
"comment": "",
"command": "echo -e \"# Restrict key exchange, cipher, and MAC algorithms, as per sshaudit.com\\n# hardening guide.\\nKexAlgorithms mlkem768x25519-sha256,sntrup761x25519-sha512,sntrup761x25519-sha512@openssh.com\\n\\nCiphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\\n\\nMACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com\\n\\nRequiredRSASize 3072\\n\\nHostKeyAlgorithms 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\\n\\nCASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\\n\\nGSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\\n\\nHostbasedAcceptedAlgorithms 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\\n\\nPubkeyAcceptedAlgorithms 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\" > /etc/ssh/sshd_config.d/debian13_ssh-audit_hardening.conf"
},
{
"heading": "Restart OpenSSH server",
"comment": "",
"command": "service ssh restart"
},
]
},
{
"server_guide": False,
"version": 1,
"version_date": "2025-09-01",
"change_log": "Initial revision. As compared to the Debian 12 guide, the following changes were made: all non-post-quantum key exchanges were removed.",
"notes": "",
"commands": [
{
"heading": "Run the following in a terminal to harden the SSH client for the local user:",
"comment": "",
"command": "mkdir -p -m 0700 ~/.ssh; echo -e \"\\nHost *\\n Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\\n\\n KexAlgorithms mlkem768x25519-sha256,sntrup761x25519-sha512,sntrup761x25519-sha512@openssh.com\\n\\n MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com\\n\\n RequiredRSASize 3072\\n\\n HostKeyAlgorithms 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\\n\\n CASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\\n\\n GSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\\n\\n HostbasedAcceptedAlgorithms sk-ssh-ed25519-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-256\\n\\n PubkeyAcceptedAlgorithms sk-ssh-ed25519-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-256\\n\\n\" >> ~/.ssh/config"
},
]
},
],
"Rocky Linux 9": [
{
"server_guide": True,
"version": 2,
"version_date": "2024-10-01",
"change_log": "Re-ordered host keys to prioritize ED25519 due to efficiency. Re-ordered cipher list to prioritize larger key sizes as a countermeasure to quantum attacks.",
"notes": "all commands below are to be executed as the root user.",
"commands": [
{
"heading": "Re-generate the RSA and ED25519 keys",
"comment": "",
"command": "rm -f /etc/ssh/ssh_host_*\nssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N \"\"\nssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N \"\""
},
{
"heading": "Enable the ED25519 and RSA keys",
"comment": "Enable the ED25519 and RSA HostKey directives in the /etc/ssh/sshd_config file:",
"command": "echo -e \"\\nHostKey /etc/ssh/ssh_host_ed25519_key\\nHostKey /etc/ssh/ssh_host_rsa_key\" >> /etc/ssh/sshd_config"
},
{
"heading": "Remove small Diffie-Hellman moduli",
"comment": "",
"command": "awk '$5 >= 3071' /etc/ssh/moduli > /etc/ssh/moduli.safe\nmv -f /etc/ssh/moduli.safe /etc/ssh/moduli"
},
{
"heading": "Restrict supported key exchange, cipher, and MAC algorithms",
"comment": "",
"command": "echo -e \"# Restrict key exchange, cipher, and MAC algorithms, as per sshaudit.com\\n# hardening guide.\\nKexAlgorithms sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org,gss-curve25519-sha256-,diffie-hellman-group16-sha512,gss-group16-sha512-,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256\\n\\nCiphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\\n\\nMACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com\\n\\nHostKeyAlgorithms 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\\n\\nRequiredRSASize 3072\\n\\nCASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\\n\\nGSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\\n\\nHostbasedAcceptedAlgorithms sk-ssh-ed25519-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-256\\n\\nPubkeyAcceptedAlgorithms 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\\n\\n\" > /etc/crypto-policies/back-ends/opensshserver.config"
},
{
"heading": "Restart OpenSSH server",
"comment": "",
"command": "systemctl restart sshd"
},
{
"heading": "Implement connection rate throttling",
"comment": "Connection rate throttling is needed in order to protect against the DHEat denial-of-service attack. A complete and flexible solution is to use iptables/firewalld to allow up to 10 connections every 10 seconds from any one source address. An alternate solution is to set OpenSSH's PerSourceMaxStartups directive to 1 (note, however, that this can cause incomplete results during ssh-audit scans, as well as other client failures when bursts of connections are made).",
"command": "firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 0 -p tcp --dport 22 -m state --state NEW -m recent --set\nfirewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 1 -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 10 --hitcount 10 -j DROP\nfirewall-cmd --permanent --direct --add-rule ipv6 filter INPUT 0 -p tcp --dport 22 -m state --state NEW -m recent --set\nfirewall-cmd --permanent --direct --add-rule ipv6 filter INPUT 1 -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 10 --hitcount 10 -j DROP\nsystemctl reload firewalld"
},
]
},
{
"server_guide": True,
"version": 1,
"version_date": "2024-04-24",
"change_log": "Added connection throttling instructions to counteract the DHEat denial-of-service attack.",
"notes": "",
"commands": [] # Commands for this older version are not tracked here.
},
{
"server_guide": False,
"version": 2,
"version_date": "2024-10-01",
"change_log": "Added RequiredRSASize directive to enforce a minimum of 3072-bit user and host-based authentication keys. Re-ordered cipher list to prioritize larger key sizes as a countermeasure to quantum attacks.",
"notes": "",
"commands": [
{
"heading": "Run the following in a terminal to harden the SSH client for the local user:",
"comment": "",
"command": "mkdir -p -m 0700 ~/.ssh; echo -e \"\\nHost *\\n Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\\n\\n KexAlgorithms sntrup761x25519-sha512@openssh.com,gss-curve25519-sha256-,curve25519-sha256,curve25519-sha256@libssh.org,gss-group16-sha512-,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256\\n\\n MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com\\n\\n RequiredRSASize 3072\\n\\n HostKeyAlgorithms 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\\n\\n CASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\\n\\n GSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\\n\\n HostbasedAcceptedAlgorithms sk-ssh-ed25519-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-256\\n\\n PubkeyAcceptedAlgorithms sk-ssh-ed25519-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-256\\n\\n\" >> ~/.ssh/config"
},
]
},
{
"server_guide": False,
"version": 1,
"version_date": "2024-03-15",
"change_log": "Initial revision.",
"notes": "",
"commands": [] # Commands for this older version are not tracked here.
},
],
"Ubuntu 22.04": [
{
"server_guide": True,
"version": 2,
"version_date": "2024-10-01",
"change_log": "Re-ordered host keys to prioritize ED25519 due to efficiency. Re-ordered cipher list to prioritize larger key sizes as a countermeasure to quantum attacks.",
"notes": "all commands below are to be executed as the root user.",
"commands": [
{
"heading": "Re-generate the RSA and ED25519 keys",
"comment": "",
"command": "rm /etc/ssh/ssh_host_*\nssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N \"\"\nssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N \"\""
},
{
"heading": "Enable the ED25519 and RSA keys",
"comment": "Enable the ED25519 and RSA HostKey directives in the /etc/ssh/sshd_config file:",
"command": "echo -e \"\\nHostKey /etc/ssh/ssh_host_ed25519_key\\nHostKey /etc/ssh/ssh_host_rsa_key\" >> /etc/ssh/sshd_config"
},
{
"heading": "Remove small Diffie-Hellman moduli",
"comment": "",
"command": "awk '$5 >= 3071' /etc/ssh/moduli > /etc/ssh/moduli.safe\nmv /etc/ssh/moduli.safe /etc/ssh/moduli"
},
{
"heading": "Restrict supported key exchange, cipher, and MAC algorithms",
"comment": "",
"command": "echo -e \"# Restrict key exchange, cipher, and MAC algorithms, as per sshaudit.com\\n# hardening guide.\\nKexAlgorithms sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org,gss-curve25519-sha256-,diffie-hellman-group16-sha512,gss-group16-sha512-,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256\\n\\nCiphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\\n\\nMACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com\\n\\nHostKeyAlgorithms 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\\n\\nCASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\\n\\nGSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\\n\\nHostbasedAcceptedAlgorithms sk-ssh-ed25519-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-256\\n\\nPubkeyAcceptedAlgorithms sk-ssh-ed25519-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-256\" > /etc/ssh/sshd_config.d/ssh-audit_hardening.conf"
},
{
"heading": "Implement connection rate throttling",
"comment": "Connection rate throttling is needed in order to protect against the DHEat denial-of-service attack. A complete and flexible solution is to use iptables to allow up to 10 connections every 10 seconds from any one source address. An alternate solution is to set OpenSSH's PerSourceMaxStartups directive to 1 (note, however, that this can cause incomplete results during ssh-audit scans, as well as other client failures when bursts of connections are made).",
"command": "iptables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --set\niptables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 10 --hitcount 10 -j DROP\nip6tables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --set\nip6tables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 10 --hitcount 10 -j DROP\nDEBIAN_FRONTEND=noninteractive apt install -q -y netfilter-persistent iptables-persistent\nservice netfilter-persistent save"
},
{
"heading": "Restart OpenSSH server",
"comment": "",
"command": "service ssh restart"
},
]
},
{
"server_guide": True,
"version": 1,
"version_date": "2024-04-22",
"change_log": "Added connection throttling instructions to counteract the DHEat denial-of-service attack.",
"notes": "",
"commands": [] # Commands for this older version are not tracked here.
},
{
"server_guide": False,
"version": 1,
"version_date": "2024-10-01",
"change_log": "Re-ordered cipher list to prioritize larger key sizes as a countermeasure to quantum attacks.",
"notes": "",
"commands": [
{
"heading": "Run the following in a terminal to harden the SSH client for the local user:",
"comment": "",
"command": "mkdir -p -m 0700 ~/.ssh; echo -e \"\\nHost *\\n Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\\n\\n KexAlgorithms sntrup761x25519-sha512@openssh.com,gss-curve25519-sha256-,curve25519-sha256,curve25519-sha256@libssh.org,gss-group16-sha512-,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256\\n\\n MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com\\n\\n HostKeyAlgorithms 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\\n\\n CASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\\n\\n GSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\\n\\n HostbasedAcceptedAlgorithms sk-ssh-ed25519-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-256\\n\\n PubkeyAcceptedAlgorithms sk-ssh-ed25519-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-256\\n\\n\" >> ~/.ssh/config"
},
]
},
],
"Ubuntu 24.04": [
{
"server_guide": True,
"version": 3,
"version_date": "2025-09-01",
"change_log": "Removed commands directly editing sshd_config to make system updates easier for users.",
"notes": "all commands below are to be executed as the root user.",
"commands": [
{
"heading": "Re-generate the ED25519 and RSA keys",
"comment": "",
"command": "rm /etc/ssh/ssh_host_*\nssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N \"\"\nssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N \"\""
},
{
"heading": "Remove small Diffie-Hellman moduli",
"comment": "",
"command": "awk '$5 >= 3071' /etc/ssh/moduli > /etc/ssh/moduli.safe\nmv /etc/ssh/moduli.safe /etc/ssh/moduli"
},
{
"heading": "Restrict supported key exchange, cipher, and MAC algorithms",
"comment": "",
"command": "echo -e \"# Restrict key exchange, cipher, and MAC algorithms, as per sshaudit.com\\n# hardening guide.\\nKexAlgorithms sntrup761x25519-sha512@openssh.com,gss-curve25519-sha256-,curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256,gss-group16-sha512-,diffie-hellman-group16-sha512\\n\\nCiphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\\n\\nMACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com\\n\\nRequiredRSASize 3072\\n\\nHostKeyAlgorithms 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\\n\\nCASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\\n\\nGSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\\n\\nHostbasedAcceptedAlgorithms 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\\n\\nPubkeyAcceptedAlgorithms 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\" > /etc/ssh/sshd_config.d/ssh-audit_hardening.conf"
},
{
"heading": "Restart OpenSSH server",
"comment": "",
"command": "service ssh restart"
},
{
"heading": "Implement connection rate throttling",
"comment": "Connection rate throttling is needed in order to protect against the DHEat denial-of-service attack. A complete and flexible solution is to use iptables to allow up to 10 connections every 10 seconds from any one source address. An alternate solution is to set OpenSSH's PerSourceMaxStartups directive to 1 (note, however, that this can cause incomplete results during ssh-audit scans, as well as other client failures when bursts of connections are made).",
"command": "iptables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --set\niptables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 10 --hitcount 10 -j DROP\nip6tables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --set\nip6tables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 10 --hitcount 10 -j DROP\nDEBIAN_FRONTEND=noninteractive apt install -q -y netfilter-persistent iptables-persistent\nservice netfilter-persistent save"
},
]
},
{
"server_guide": True,
"version": 2,
"version_date": "2024-10-01",
"change_log": "Added RequiredRSASize directive to enforce a minimum of 3072-bit user and host-based authentication keys.",
"notes": "all commands below are to be executed as the root user.",
"commands": [
{
"heading": "Re-generate the ED25519 and RSA keys",
"comment": "",
"command": "rm /etc/ssh/ssh_host_*\nssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N \"\"\nssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N \"\""
},
{
"heading": "Remove small Diffie-Hellman moduli",
"comment": "",
"command": "awk '$5 >= 3071' /etc/ssh/moduli > /etc/ssh/moduli.safe\nmv /etc/ssh/moduli.safe /etc/ssh/moduli"
},
{
"heading": "Enable the ED25519 and RSA keys",
"comment": "Enable the ED25519 and RSA HostKey directives in the /etc/ssh/sshd_config file:",
"command": "echo -e \"\\nHostKey /etc/ssh/ssh_host_ed25519_key\\nHostKey /etc/ssh/ssh_host_rsa_key\" >> /etc/ssh/sshd_config"
},
{
"heading": "Restrict supported key exchange, cipher, and MAC algorithms",
"comment": "",
"command": "echo -e \"# Restrict key exchange, cipher, and MAC algorithms, as per sshaudit.com\\n# hardening guide.\\nKexAlgorithms sntrup761x25519-sha512@openssh.com,gss-curve25519-sha256-,curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256,gss-group16-sha512-,diffie-hellman-group16-sha512\\n\\nCiphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\\n\\nMACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com\\n\\nRequiredRSASize 3072\\n\\nHostKeyAlgorithms 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\\n\\nCASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\\n\\nGSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\\n\\nHostbasedAcceptedAlgorithms 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\\n\\nPubkeyAcceptedAlgorithms 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\" > /etc/ssh/sshd_config.d/ssh-audit_hardening.conf"
},
{
"heading": "Restart OpenSSH server",
"comment": "",
"command": "service ssh restart"
},
{
"heading": "Implement connection rate throttling",
"comment": "Connection rate throttling is needed in order to protect against the DHEat denial-of-service attack. A complete and flexible solution is to use iptables to allow up to 10 connections every 10 seconds from any one source address. An alternate solution is to set OpenSSH's PerSourceMaxStartups directive to 1 (note, however, that this can cause incomplete results during ssh-audit scans, as well as other client failures when bursts of connections are made).",
"command": "iptables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --set\niptables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 10 --hitcount 10 -j DROP\nip6tables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --set\nip6tables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 10 --hitcount 10 -j DROP\nDEBIAN_FRONTEND=noninteractive apt install -q -y netfilter-persistent iptables-persistent\nservice netfilter-persistent save"
},
]
},
{
"server_guide": True,
"version": 1,
"version_date": "2024-04-29",
"change_log": "Initial revision. In comparison to Ubuntu 22.04 LTS guide, the following changes were made: 1.) For key exchanges, diffie-hellman-group18-sha512 and diffie-hellman-group-exchange-sha256 were prioritized over diffie-hellman-group16-sha512 due to greater security strength; GSS algorithms were prioritized over their non-GSS equivalents in order to match the client guide, 2.) For ciphers, 256-bit AES ciphers were prioritized over 192 and 128-bit AES ciphers due to their increased resistence against quantum computing attacks (previously, weaker GCM ciphers had priority over CTR ciphers), 3.) The HostbasedAcceptedAlgorithms and PubkeyAcceptedAlgorithms settings are now the same as HostKeyAlgorithms setting, 4.) The hmac-sha2-512-etm@openssh.com MAC was increased in priority due to its increased resistence against quantum computing attacks, and 5.) The ED25519 host keys were given priority over RSA host keys due to their greater efficiency.",
"notes": "",
"commands": [] # Commands for this older version are not tracked here.
},
{
"server_guide": False,
"version": 2,
"version_date": "2024-10-01",
"change_log": "Added RequiredRSASize directive to enforce a minimum of 3072-bit user and host-based authentication keys.",
"notes": "",
"commands": [
{
"heading": "Run the following in a terminal to harden the SSH client for the local user:",
"comment": "",
"command": "mkdir -p -m 0700 ~/.ssh; echo -e \"\\nHost *\\n Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\\n\\n KexAlgorithms sntrup761x25519-sha512@openssh.com,gss-curve25519-sha256-,curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256,gss-group16-sha512-,diffie-hellman-group16-sha512\\n\\n MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com\\n\\n RequiredRSASize 3072\\n\\n HostKeyAlgorithms 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\\n\\n CASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\\n\\n GSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\\n\\n HostbasedAcceptedAlgorithms 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\\n\\n PubkeyAcceptedAlgorithms 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\\n\\n\" >> ~/.ssh/config"
},
]
},
{
"server_guide": False,
"version": 1,
"version_date": "2024-04-29",
"change_log": "Initial revision. In comparison to Ubuntu 22.04 LTS Client guide, the following changes were made: 1.) For key exchanges, diffie-hellman-group18-sha512 and diffie-hellman-group-exchange-sha256 were prioritized over diffie-hellman-group16-sha512 due to greater security strength, 2.) For ciphers, 256-bit AES ciphers were prioritized over 192 and 128-bit AES ciphers due to their increased resistence against quantum computing attacks (previously, weaker GCM ciphers had priority over CTR ciphers), 3.) The HostbasedAcceptedAlgorithms and PubkeyAcceptedAlgorithms settings are now the same as HostKeyAlgorithms setting, and 4.) The hmac-sha2-512-etm@openssh.com MAC was increased in priority due to its increased resistence against quantum computing attacks.",
"notes": "",
"commands": [] # Commands for this older version are not tracked here.
},
],
"Linux Mint 21": [
{
"server_guide": False,
"version": 1,
"version_date": "2024-10-01",
"change_log": "Re-ordered cipher list to prioritize larger key sizes as a countermeasure to quantum attacks.",
"notes": "",
"commands": [
{
"heading": "Run the following in a terminal to harden the SSH client for the local user:",
"comment": "",
"command": "mkdir -p -m 0700 ~/.ssh; echo -e \"\\nHost *\\n Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\\n\\n KexAlgorithms sntrup761x25519-sha512@openssh.com,gss-curve25519-sha256-,curve25519-sha256,curve25519-sha256@libssh.org,gss-group16-sha512-,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256\\n\\n MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com\\n\\n HostKeyAlgorithms 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\\n\\n CASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\\n\\n GSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\\n\\n HostbasedAcceptedAlgorithms sk-ssh-ed25519-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-256\\n\\n PubkeyAcceptedAlgorithms sk-ssh-ed25519-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-256\\n\\n\" >> ~/.ssh/config"
},
]
},
],
"Linux Mint 22": [
{
"server_guide": False,
"version": 2,
"version_date": "2024-10-01",
"change_log": "Added RequiredRSASize directive to enforce a minimum of 3072-bit user and host-based authentication keys.",
"notes": "",
"commands": [
{
"heading": "Run the following in a terminal to harden the SSH client for the local user:",
"comment": "",
"command": "mkdir -p -m 0700 ~/.ssh; echo -e \"\\nHost *\\n Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\\n\\n KexAlgorithms sntrup761x25519-sha512@openssh.com,gss-curve25519-sha256-,curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256,gss-group16-sha512-,diffie-hellman-group16-sha512\\n\\n MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com\\n\\n RequiredRSASize 3072\\n\\n HostKeyAlgorithms 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\\n\\n CASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\\n\\n GSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\\n\\n HostbasedAcceptedAlgorithms 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\\n\\n PubkeyAcceptedAlgorithms 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\\n\\n\" >> ~/.ssh/config"
},
]
},
{
"server_guide": False,
"version": 1,
"version_date": "2024-04-29",
"change_log": "Initial revision. In comparison to Ubuntu 22.04 LTS Client guide, the following changes were made: 1.) For key exchanges, diffie-hellman-group18-sha512 and diffie-hellman-group-exchange-sha256 were prioritized over diffie-hellman-group16-sha512 due to greater security strength, 2.) For ciphers, 256-bit AES ciphers were prioritized over 192 and 128-bit AES ciphers due to their increased resistence against quantum computing attacks (previously, weaker GCM ciphers had priority over CTR ciphers), 3.) The HostbasedAcceptedAlgorithms and PubkeyAcceptedAlgorithms settings are now the same as HostKeyAlgorithms setting, and 4.) The hmac-sha2-512-etm@openssh.com MAC was increased in priority due to its increased resistence against quantum computing attacks.",
"notes": "",
"commands": [] # Commands for this older version are not tracked here.
},
],
}
@staticmethod
def list_guides(out: OutputBuffer, verbose: bool) -> None:
'''Print all the server and client hardening guides.'''
server_guide_names = []
client_guide_names = []
# Iterate through the guides, and record a list of server guide names, along with a separate list for client guide names.
for name, guides in Hardening_Guides.HARDENING_GUIDES.items():
for guide in guides:
version = guide["version"]
version_date = guide["version_date"]
change_log = guide["change_log"]
if guide["server_guide"]:
full_name = f"{name} Server" if not verbose else f"{name} Server (version {version}): {version_date}: {change_log}"
if full_name not in server_guide_names:
server_guide_names.append(full_name)
else:
full_name = f"{name} Client" if not verbose else f"{name} Client (version {version}): {version_date}: {change_log}"
if full_name not in client_guide_names:
client_guide_names.append(full_name)
# Sort the names.
server_guide_names.sort()
client_guide_names.sort()
# Print the lists.
out.head("\nServer hardening guides:\n")
out.info(" * %s" % "\n * ".join(server_guide_names))
out.head("\nClient hardening guides:\n")
out.info(" * %s" % "\n * ".join(client_guide_names))
out.info("\n")
if not verbose:
out.info("Hint: add -v to --list-hardening-guides in order to see change log messages and prior versions. Prior versions of hardening guides can be retrieved as well with --get-hardening-guide (i.e.: --get-hardening-guide \"Ubuntu 24.04 Server (version 1)\").\n")
out.write()
@staticmethod
def print_hardening_guide(out: OutputBuffer, platform: str) -> None:
'''Prints a hardening guide for the specified platform.'''
platform_orig = platform
invalid_guide_name_error = "Invalid guide name. Run --list-hardening-guides to see list of valid guide names."
# If the user provided a version with the platform name, parse the version number they're interested in.
use_latest_version = True
use_version = 0
pos = platform.find(" (version ")
if pos != -1:
use_latest_version = False
end_pos = platform.find(")", pos)
try:
use_version = int(platform[pos + 10:end_pos])
except ValueError:
out.fail(invalid_guide_name_error, write_now=True)
return
platform = platform[0:pos]
last_space_pos = platform.rfind(" ")
if last_space_pos == -1:
out.fail(invalid_guide_name_error, write_now=True)
return
# From input such as "Ubuntu 24.04 Server", parse the OS name ("Ubuntu 24.04") and last word ("Server").
os_name = platform[0:last_space_pos]
last_word = platform[last_space_pos + 1:]
# Determine if this is a server or client guide.
is_server = False
if last_word == "Server":
is_server = True
elif last_word != "Client":
out.fail(invalid_guide_name_error, write_now=True)
return
# Ensure that this OS exists in the database.
if os_name not in Hardening_Guides.HARDENING_GUIDES:
out.fail(invalid_guide_name_error, write_now=True)
return
# Pull all guides for this OS name.
guides = Hardening_Guides.HARDENING_GUIDES[os_name]
# Iterate over guides until we find the type (server/client) we need, as well as the version of the guide we need.
selected_guide = None
latest_version = 0
for guide in guides:
if guide["server_guide"] == is_server:
version = guide["version"]
if use_latest_version and (version > latest_version):
selected_guide = guide
latest_version = version
elif use_latest_version is False and use_version == version:
selected_guide = guide
# Ensure we found a guide from above.
if selected_guide is None:
out.fail(invalid_guide_name_error, write_now=True)
return
# Now print the guide.
version_header = f"\n#\n# Hardening guide for {platform_orig}\n#\n" if not use_latest_version else f"\n#\n# Hardening guide for {platform_orig} (version {latest_version})\n#\n"
out.info(version_header)
commands = selected_guide["commands"]
for command_dict in commands:
heading = command_dict["heading"]
comment = command_dict["comment"]
command = command_dict["command"]
if heading != "":
out.info(f"# {heading}")
if comment != "":
out.info(f"# {comment}")
out.info(f"{command}")
out.info(s="")
out.write()

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)
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
@@ -40,7 +40,7 @@ class HostKeyTest:
# Tracks the RSA host key types. As of this writing, testing one in this family yields valid results for the rest. # Tracks the RSA host key types. As of this writing, testing one in this family yields valid results for the rest.
RSA_FAMILY = ['ssh-rsa', 'rsa-sha2-256', 'rsa-sha2-512'] RSA_FAMILY = ['ssh-rsa', 'rsa-sha2-256', 'rsa-sha2-512']
# Dict holding the host key types we should extract & parse. 'cert' is True to denote that a host key type handles certificates (thus requires additional parsing). 'variable_key_len' is True for host key types that can have variable sizes (True only for RSA types, as the rest are of fixed-size). After the host key type is fully parsed, the key 'parsed' is added with a value of True. # Dict holding the host key types we should extract & parse. 'cert' is True to denote that a host key type handles certificates (thus requires additional parsing). 'variable_key_len' is True for host key types that can have variable sizes (True only for RSA types, as the rest are of fixed-size).
HOST_KEY_TYPES = { HOST_KEY_TYPES = {
'ssh-rsa': {'cert': False, 'variable_key_len': True}, 'ssh-rsa': {'cert': False, 'variable_key_len': True},
'rsa-sha2-256': {'cert': False, 'variable_key_len': True}, 'rsa-sha2-256': {'cert': False, 'variable_key_len': True},
@@ -52,6 +52,20 @@ class HostKeyTest:
'ssh-ed25519': {'cert': False, 'variable_key_len': False}, 'ssh-ed25519': {'cert': False, 'variable_key_len': False},
'ssh-ed25519-cert-v01@openssh.com': {'cert': True, 'variable_key_len': False}, 'ssh-ed25519-cert-v01@openssh.com': {'cert': True, 'variable_key_len': False},
'ssh-ed448': {'cert': False, 'variable_key_len': False},
# 'ssh-ed448-cert-v01@openssh.com': {'cert': True, 'variable_key_len': False},
'ecdsa-sha2-nistp256': {'cert': False, 'variable_key_len': False},
'ecdsa-sha2-nistp384': {'cert': False, 'variable_key_len': False},
'ecdsa-sha2-nistp521': {'cert': False, 'variable_key_len': False},
'ecdsa-sha2-nistp256-cert-v01@openssh.com': {'cert': True, 'variable_key_len': False},
'ecdsa-sha2-nistp384-cert-v01@openssh.com': {'cert': True, 'variable_key_len': False},
'ecdsa-sha2-nistp521-cert-v01@openssh.com': {'cert': True, 'variable_key_len': False},
'ssh-dss': {'cert': False, 'variable_key_len': True},
'ssh-dss-cert-v01@openssh.com': {'cert': True, 'variable_key_len': True},
} }
TWO2K_MODULUS_WARNING = '2048-bit modulus only provides 112-bits of symmetric strength' TWO2K_MODULUS_WARNING = '2048-bit modulus only provides 112-bits of symmetric strength'
@@ -93,6 +107,7 @@ class HostKeyTest:
def perform_test(out: 'OutputBuffer', s: 'SSH_Socket', server_kex: 'SSH2_Kex', kex_str: str, kex_group: 'KexDH', host_key_types: Dict[str, Dict[str, bool]]) -> None: def perform_test(out: 'OutputBuffer', s: 'SSH_Socket', server_kex: 'SSH2_Kex', kex_str: str, kex_group: 'KexDH', host_key_types: Dict[str, Dict[str, bool]]) -> None:
hostkey_modulus_size = 0 hostkey_modulus_size = 0
ca_modulus_size = 0 ca_modulus_size = 0
parsed_host_key_types = set()
# If the connection still exists, close it so we can test # If the connection still exists, close it so we can test
# using a clean slate (otherwise it may exist in a non-testable # using a clean slate (otherwise it may exist in a non-testable
@@ -106,7 +121,7 @@ class HostKeyTest:
key_warn_comments = [] key_warn_comments = []
# Skip those already handled (i.e.: those in the RSA family, as testing one tests them all). # Skip those already handled (i.e.: those in the RSA family, as testing one tests them all).
if 'parsed' in host_key_types[host_key_type] and host_key_types[host_key_type]['parsed']: if host_key_type in parsed_host_key_types:
continue continue
# If this host key type is supported by the server, we test it. # If this host key type is supported by the server, we test it.
@@ -136,7 +151,12 @@ class HostKeyTest:
_, payload = s.read_packet() _, payload = s.read_packet()
SSH2_Kex.parse(out, payload) SSH2_Kex.parse(out, payload)
except Exception: except Exception:
out.v("Failed to parse server's kex. Stack trace:\n%s" % str(traceback.format_exc()), write_now=True) msg = "Failed to parse server's kex."
if not out.debug:
msg += " Re-run in debug mode to see stack trace."
out.v(msg, write_now=True)
out.d("Stack trace:\n%s" % str(traceback.format_exc()), write_now=True)
return return
# Do the initial DH exchange. The server responds back # Do the initial DH exchange. The server responds back
@@ -147,7 +167,12 @@ class HostKeyTest:
kex_reply = kex_group.recv_reply(s) kex_reply = kex_group.recv_reply(s)
raw_hostkey_bytes = kex_reply if kex_reply is not None else b'' raw_hostkey_bytes = kex_reply if kex_reply is not None else b''
except KexDHException: except KexDHException:
out.v("Failed to parse server's host key. Stack trace:\n%s" % str(traceback.format_exc()), write_now=True) msg = "Failed to parse server's host key."
if not out.debug:
msg += " Re-run in debug mode to see stack trace."
out.v(msg, write_now=True)
out.d("Stack trace:\n%s" % str(traceback.format_exc()), write_now=True)
# Since parsing this host key failed, there's nothing more to do but close the socket and move on to the next host key type. # Since parsing this host key failed, there's nothing more to do but close the socket and move on to the next host key type.
s.close() s.close()
@@ -157,6 +182,7 @@ class HostKeyTest:
ca_key_type = kex_group.get_ca_type() ca_key_type = kex_group.get_ca_type()
ca_modulus_size = kex_group.get_ca_size() ca_modulus_size = kex_group.get_ca_size()
out.d("Hostkey type: [%s]; hostkey size: %u; CA type: [%s]; CA modulus size: %u" % (host_key_type, hostkey_modulus_size, ca_key_type, ca_modulus_size), write_now=True) out.d("Hostkey type: [%s]; hostkey size: %u; CA type: [%s]; CA modulus size: %u" % (host_key_type, hostkey_modulus_size, ca_key_type, ca_modulus_size), write_now=True)
out.d("Raw hostkey bytes (%d): [%s]" % (len(raw_hostkey_bytes), raw_hostkey_bytes.hex()), write_now=True)
# Record all the host key info. # Record all the host key info.
server_kex.set_host_key(host_key_type, raw_hostkey_bytes, hostkey_modulus_size, ca_key_type, ca_modulus_size) server_kex.set_host_key(host_key_type, raw_hostkey_bytes, hostkey_modulus_size, ca_key_type, ca_modulus_size)
@@ -186,7 +212,7 @@ class HostKeyTest:
cakey_warn_str = HostKeyTest.SMALL_ECC_MODULUS_WARNING cakey_warn_str = HostKeyTest.SMALL_ECC_MODULUS_WARNING
# Keys smaller than 2048 result in a failure. Keys smaller 3072 result in a warning. Update the database accordingly. # Keys smaller than 2048 result in a failure. Keys smaller 3072 result in a warning. Update the database accordingly.
if (cert is False) and (hostkey_modulus_size < hostkey_min_good): if (cert is False) and (hostkey_modulus_size < hostkey_min_good) and (host_key_type != 'ssh-dss'): # Skip ssh-dss, otherwise we get duplicate failure messages (SSH2_KexDB will always flag it).
# If the key is under 2048, add to the failure list. # If the key is under 2048, add to the failure list.
if hostkey_modulus_size < hostkey_min_warn: if hostkey_modulus_size < hostkey_min_warn:
@@ -216,7 +242,7 @@ class HostKeyTest:
# If this host key type is in the RSA family, then mark them all as parsed (since results in one are valid for them all). # If 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:
host_key_types[rsa_type]['parsed'] = True parsed_host_key_types.add(rsa_type)
# If the current key is a member of the RSA family, then populate all RSA family members with the same # 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.
@@ -228,7 +254,7 @@ class HostKeyTest:
db['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 parsed_host_key_types.add(host_key_type)
db = SSH2_KexDB.get_db() db = SSH2_KexDB.get_db()
while len(db['key'][host_key_type]) < 3: while len(db['key'][host_key_type]) < 3:
db['key'][host_key_type].append([]) db['key'][host_key_type].append([])

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-2025 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
@@ -88,11 +88,11 @@ class KexDH: # pragma: nocover
self.__ca_key_type = '' self.__ca_key_type = ''
self.__ca_n_len = 0 self.__ca_n_len = 0
packet_type, payload = s.read_packet(2) packet_type, payload = s.read_packet()
# Skip any & all MSG_DEBUG messages. # Skip any & all MSG_DEBUG messages.
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()
if packet_type != -1 and packet_type not in [Protocol.MSG_KEXDH_REPLY, Protocol.MSG_KEXDH_GEX_REPLY]: # pylint: disable=no-else-raise if packet_type != -1 and packet_type not in [Protocol.MSG_KEXDH_REPLY, Protocol.MSG_KEXDH_GEX_REPLY]: # pylint: disable=no-else-raise
raise KexDHException('Expected MSG_KEXDH_REPLY (%d) or MSG_KEXDH_GEX_REPLY (%d), but got %d instead.' % (Protocol.MSG_KEXDH_REPLY, Protocol.MSG_KEXDH_GEX_REPLY, packet_type)) raise KexDHException('Expected MSG_KEXDH_REPLY (%d) or MSG_KEXDH_GEX_REPLY (%d), but got %d instead.' % (Protocol.MSG_KEXDH_REPLY, Protocol.MSG_KEXDH_GEX_REPLY, packet_type))
@@ -134,6 +134,9 @@ class KexDH: # pragma: nocover
if self.__hostkey_type == 'ssh-ed25519': if self.__hostkey_type == 'ssh-ed25519':
self.out.d("%s has a fixed host key modulus of 32." % self.__hostkey_type) self.out.d("%s has a fixed host key modulus of 32." % self.__hostkey_type)
self.__hostkey_n_len = 32 self.__hostkey_n_len = 32
elif self.__hostkey_type == 'ssh-ed448':
self.out.d("%s has a fixed host key modulus of 57." % self.__hostkey_type)
self.__hostkey_n_len = 57
else: else:
# Here is the modulus size & actual modulus of the host key public key. # Here is the modulus size & actual modulus of the host key public key.
hostkey_n, self.__hostkey_n_len, ptr = KexDH.__get_bytes(hostkey, ptr) hostkey_n, self.__hostkey_n_len, ptr = KexDH.__get_bytes(hostkey, ptr)
@@ -377,13 +380,13 @@ class KexGroupExchange(KexDH):
s.write_int(maxbits) s.write_int(maxbits)
s.send_packet() s.send_packet()
packet_type, payload = s.read_packet(2) packet_type, payload = s.read_packet()
if packet_type not in [Protocol.MSG_KEXDH_GEX_GROUP, Protocol.MSG_DEBUG]: if packet_type not in [Protocol.MSG_KEXDH_GEX_GROUP, Protocol.MSG_DEBUG]:
raise KexDHException('Expected MSG_KEXDH_GEX_REPLY (%d), but got %d instead.' % (Protocol.MSG_KEXDH_GEX_REPLY, packet_type)) raise KexDHException('Expected MSG_KEXDH_GEX_REPLY (%d), but got %d instead.' % (Protocol.MSG_KEXDH_GEX_REPLY, packet_type))
# Skip any & all MSG_DEBUG messages. # Skip any & all MSG_DEBUG messages.
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()
try: try:
# Parse the modulus (p) and generator (g) values from the server. # Parse the modulus (p) and generator (g) values from the server.

View File

@@ -54,11 +54,11 @@ class OutputBuffer:
self.__is_color_supported = ('colorama' in sys.modules) or (os.name == 'posix') self.__is_color_supported = ('colorama' in sys.modules) or (os.name == 'posix')
self.line_ended = True self.line_ended = True
def _print(self, level: str, s: str = '', line_ended: bool = True) -> None: def _print(self, level: str, s: str = '', line_ended: bool = True, always_print: bool = False) -> None:
'''Saves output to buffer (if in buffered mode), or immediately prints to stdout otherwise.''' '''Saves output to buffer (if in buffered mode), or immediately prints to stdout otherwise.'''
# If we're logging only 'warn' or above, and this is an 'info', ignore message. # If we're logging only 'warn' or above, and this is an 'info', ignore message, unless always_print is True (useful for printing informational lines regardless of the level setting).
if self.get_level(level) < self.__level: if (always_print is False) and (self.get_level(level) < self.__level):
return return
if self.use_colors and self.colors_supported and len(s) > 0 and level != 'info': if self.use_colors and self.colors_supported and len(s) > 0 and level != 'info':
@@ -145,20 +145,22 @@ class OutputBuffer:
self._print('head', s, line_ended) self._print('head', s, line_ended)
return self return self
def fail(self, s: str, line_ended: bool = True) -> 'OutputBuffer': def fail(self, s: str, line_ended: bool = True, write_now: bool = False, always_print: bool = False) -> 'OutputBuffer':
self._print('fail', s, line_ended) self._print('fail', s, line_ended, always_print=always_print)
if write_now:
self.write()
return self return self
def warn(self, s: str, line_ended: bool = True) -> 'OutputBuffer': def warn(self, s: str, line_ended: bool = True, always_print: bool = False) -> 'OutputBuffer':
self._print('warn', s, line_ended) self._print('warn', s, line_ended, always_print=always_print)
return self return self
def info(self, s: str, line_ended: bool = True) -> 'OutputBuffer': def info(self, s: str, line_ended: bool = True, always_print: bool = False) -> 'OutputBuffer':
self._print('info', s, line_ended) self._print('info', s, line_ended, always_print=always_print)
return self return self
def good(self, s: str, line_ended: bool = True) -> 'OutputBuffer': def good(self, s: str, line_ended: bool = True, always_print: bool = False) -> 'OutputBuffer':
self._print('good', s, line_ended) self._print('good', s, line_ended, always_print=always_print)
return self return self
def sep(self) -> 'OutputBuffer': def sep(self) -> 'OutputBuffer':

View File

@@ -1,7 +1,7 @@
""" """
The MIT License (MIT) The MIT License (MIT)
Copyright (C) 2020-2024 Joe Testa (jtesta@positronsecurity.com) Copyright (C) 2020-2025 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
@@ -55,8 +55,10 @@ class Policy:
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_algorithm_subset_and_reordering = False
self._allow_hostkey_subset_and_reordering = False
self._allow_larger_keys = False self._allow_larger_keys = False
self._errors: List[Any] = [] self._errors: List[Any] = []
self._updated_builtin_policy_available = False # If True, an outdated built-in policy was loaded.
self._name_and_version: str = '' self._name_and_version: str = ''
@@ -115,7 +117,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', '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_'): 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_hostkey_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']:
@@ -210,6 +212,9 @@ class Policy:
self._server_policy = False self._server_policy = False
elif key == 'allow_algorithm_subset_and_reordering' and val.lower() == 'true': elif key == 'allow_algorithm_subset_and_reordering' and val.lower() == 'true':
self._allow_algorithm_subset_and_reordering = True self._allow_algorithm_subset_and_reordering = True
self._allow_hostkey_subset_and_reordering = True # We force subsets/reorderings of the host keys as well in this case.
elif key == 'allow_hostkey_subset_and_reordering' and val.lower() == 'true':
self._allow_hostkey_subset_and_reordering = True
elif key == 'allow_larger_keys' and val.lower() == 'true': elif key == 'allow_larger_keys' and val.lower() == 'true':
self._allow_larger_keys = True self._allow_larger_keys = True
@@ -299,9 +304,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. # 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. A value of true automatically sets "allow_hostkey_subset_and_reordering" to true as well.
allow_algorithm_subset_and_reordering = false allow_algorithm_subset_and_reordering = false
# When false, the host key list must match exactly. When true, the target host may support a subset of the specified host keys and/or the keys may appear in a different order.
allow_hostkey_subset_and_reordering = true
# 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. # 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 allow_larger_keys = false
@@ -357,7 +365,7 @@ macs = %s
# Check host keys. # Check host keys.
if self._host_keys is not None: if self._host_keys is not None:
# If the policy allows subsets and re-ordered algorithms... # If the policy allows subsets and re-ordered algorithms...
if self._allow_algorithm_subset_and_reordering: if self._allow_hostkey_subset_and_reordering:
for hostkey_t in kex.key_algorithms: for hostkey_t in kex.key_algorithms:
if hostkey_t not in self._host_keys: if hostkey_t not in self._host_keys:
ret = False ret = False
@@ -466,11 +474,18 @@ macs = %s
def _get_errors(self) -> Tuple[List[Any], str]: def _get_errors(self) -> Tuple[List[Any], str]:
'''Returns the list of errors, along with the string representation of those errors.''' '''Returns the list of errors, along with the string representation of those errors.'''
subset_and_reordering_semicolon = "; subset and/or reordering allowed" if self._allow_algorithm_subset_and_reordering else "; exact match" all_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 "" all_subset_and_reordering_parens = " (subset and/or reordering allowed)" if self._allow_algorithm_subset_and_reordering else ""
hostkey_subset_and_reordering_semicolon = "; subset and/or reordering allowed" if self._allow_hostkey_subset_and_reordering else "; exact match"
hostkey_subset_and_reordering_parens = " (subset and/or reordering allowed)" if self._allow_hostkey_subset_and_reordering else ""
error_list = [] error_list = []
spacer = '' spacer = ''
for e in self._errors: for e in self._errors:
subset_and_reordering_semicolon = hostkey_subset_and_reordering_semicolon if e["mismatched_field"] == "Host keys" else all_subset_and_reordering_semicolon
subset_and_reordering_parens = hostkey_subset_and_reordering_parens if e["mismatched_field"] == "Host keys" else all_subset_and_reordering_parens
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'] != ['']):
@@ -496,6 +511,11 @@ macs = %s
return self._name_and_version return self._name_and_version
def is_outdated_builtin_policy(self) -> bool:
'''Returns True if this is a built-in policy that has a more recent version available than currently selected.'''
return self._updated_builtin_policy_available
def is_server_policy(self) -> bool: def is_server_policy(self) -> bool:
'''Returns True if this is a server policy, or False if this is a client policy.''' '''Returns True if this is a server policy, or False if this is a client policy.'''
return self._server_policy return self._server_policy
@@ -507,18 +527,46 @@ macs = %s
server_policy_descriptions = [] server_policy_descriptions = []
client_policy_descriptions = [] client_policy_descriptions = []
latest_server_policies: Dict[str, Dict[str, Union[int, str]]] = {}
latest_client_policies: Dict[str, Dict[str, Union[int, str]]] = {}
for policy_name, policy in BUILTIN_POLICIES.items(): for policy_name, policy in BUILTIN_POLICIES.items():
policy_description = ""
if verbose: # If not in verbose mode, only store the latest version of each policy.
policy_description = "\"{:s}\": {:s}".format(policy_name, policy['changelog']) if not verbose:
else:
policy_description = "\"{:s}\"".format(policy_name) policy_description = "\"{:s}\"".format(policy_name)
if policy['server_policy']: # Truncate the version off the policy name and obtain the version as an integer. (i.e.: "Platform X (version 3)" -> "Platform X", 3
server_policy_descriptions.append(policy_description) policy_name_no_version = ""
else: version = 0
client_policy_descriptions.append(policy_description) version_pos = policy_name.find(" (version ")
if version_pos != -1:
policy_name_no_version = policy_name[0:version_pos]
version = int(cast(str, policy['version'])) # Unit tests guarantee this to be parseable as an int.
d = latest_server_policies if policy['server_policy'] else latest_client_policies
if policy_name_no_version not in d:
d[policy_name_no_version] = {}
d[policy_name_no_version]['latest_version'] = version
d[policy_name_no_version]['description'] = policy_description
elif version > cast(int, d[policy_name_no_version]['latest_version']): # If an updated version of the policy was found, replace the old one.
d[policy_name_no_version]['latest_version'] = version
d[policy_name_no_version]['description'] = policy_description
else: # In verbose mode, return all policy versions.
policy_description = "\"{:s}\": {:s}".format(policy_name, policy['changelog'])
if policy['server_policy']:
server_policy_descriptions.append(policy_description)
else:
client_policy_descriptions.append(policy_description)
# Now that we have references to the latest policies only, add their full descriptions to the lists for returning.
if not verbose:
for _, dd in latest_server_policies.items():
server_policy_descriptions.append(cast(str, dd['description']))
for _, dd in latest_client_policies.items():
client_policy_descriptions.append(cast(str, dd['description']))
# Sort the lists for better readability.
server_policy_descriptions.sort() server_policy_descriptions.sort()
client_policy_descriptions.sort() client_policy_descriptions.sort()
return server_policy_descriptions, client_policy_descriptions return server_policy_descriptions, client_policy_descriptions
@@ -545,10 +593,19 @@ macs = %s
p._dh_modulus_sizes = cast(Optional[Dict[str, int]], policy_struct['dh_modulus_sizes']) # pylint: disable=protected-access p._dh_modulus_sizes = cast(Optional[Dict[str, int]], policy_struct['dh_modulus_sizes']) # pylint: disable=protected-access
p._server_policy = cast(bool, policy_struct['server_policy']) # pylint: disable=protected-access p._server_policy = cast(bool, policy_struct['server_policy']) # pylint: disable=protected-access
p._name_and_version = "%s (version %s)" % (p._name, p._version) # pylint: disable=protected-access p._name_and_version = "%s (version %s)" % (p._name, p._version) # pylint: disable=protected-access
p._allow_hostkey_subset_and_reordering = cast(bool, policy_struct['allow_hostkey_subset_and_reordering']) if 'allow_hostkey_subset_and_reordering' in policy_struct else False # pylint: disable=protected-access
# Ensure this struct has all the necessary fields. # Ensure this struct has all the necessary fields.
p._normalize_hostkey_sizes() # pylint: disable=protected-access p._normalize_hostkey_sizes() # pylint: disable=protected-access
# Now check if an updated version of the requested policy exists. If so, set a warning for the user.
if p is not None and p._version is not None: # pylint: disable=protected-access
next_version = str(int(p._version) + 1) # pylint: disable=protected-access
name_version_pos = policy_name.find("(version ")
next_version_name = policy_name[0:name_version_pos] + "(version %s)" % next_version
if next_version_name in BUILTIN_POLICIES:
p._updated_builtin_policy_available = True # pylint: disable=protected-access
return p return p
@@ -604,4 +661,30 @@ 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\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) return "Name: %s\nVersion: %s\nAllow Algorithm Subset and/or Reordering: %r\nAllow Host Key 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, self._allow_hostkey_subset_and_reordering, banner, compressions_str, host_keys_str, optional_host_keys_str, kex_str, ciphers_str, macs_str, hostkey_sizes_str, dh_modulus_sizes_str, self._server_policy)
def __getstate__(self) -> Dict[str, Any]:
'''Called when pickling this object. The file descriptor isn't serializable, so we'll remove it from the state and include a string representation.'''
state = self.__dict__.copy()
if state['_warning_target'] == sys.stdout:
state['_warning_target_type'] = 'stdout'
else:
state['_warning_target_type'] = 'stderr'
del state['_warning_target']
return state
def __setstate__(self, state: Dict[str, Any]) -> None:
'''Called when unpickling this object. Based on the string representation of the file descriptor, we'll restore the right handle.'''
if state['_warning_target_type'] == 'stdout':
state['_warning_target'] = sys.stdout
else:
state['_warning_target'] = sys.stderr
del state['_warning_target_type']
self.__dict__.update(state)

View File

@@ -224,4 +224,8 @@ class Software:
mx = re.match(r'^PuTTY_Release_(.*)', software) mx = re.match(r'^PuTTY_Release_(.*)', software)
if mx: if mx:
return cls(None, Product.PuTTY, mx.group(1), None, None) return cls(None, Product.PuTTY, mx.group(1), None, None)
mx = re.match(r'^lancom(.*)', software)
if mx:
v, p = 'LANcom', 'LCOS sshd'
return cls(v, p, mx.group(1), None, None)
return None return None

View File

@@ -1,40 +0,0 @@
"""
The MIT License (MIT)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
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.
"""
# pylint: disable=unused-import
from typing import Dict, List, Set, Sequence, Tuple, Iterable # noqa: F401
from typing import Callable, Optional, Union, Any # noqa: F401
from ssh_audit.ssh1_crc32 import SSH1_CRC32
class SSH1:
_crc32: Optional[SSH1_CRC32] = None
CIPHERS = ['none', 'idea', 'des', '3des', 'tss', 'rc4', 'blowfish']
AUTHS = ['none', 'rhosts', 'rsa', 'password', 'rhosts_rsa', 'tis', 'kerberos']
@classmethod
def crc32(cls, v: bytes) -> int:
if cls._crc32 is None:
cls._crc32 = SSH1_CRC32()
return cls._crc32.calc(v)

View File

@@ -1,47 +0,0 @@
"""
The MIT License (MIT)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
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.
"""
# pylint: disable=unused-import
from typing import Dict, List, Set, Sequence, Tuple, Iterable # noqa: F401
from typing import Callable, Optional, Union, Any # noqa: F401
class SSH1_CRC32:
def __init__(self) -> None:
self._table = [0] * 256
for i in range(256):
crc = 0
n = i
for _ in range(8):
x = (crc ^ n) & 1
crc = (crc >> 1) ^ (x * 0xedb88320)
n = n >> 1
self._table[i] = crc
def calc(self, v: bytes) -> int:
crc, length = 0, len(v)
for i in range(length):
n = ord(v[i:i + 1])
n = n ^ (crc & 0xff)
crc = (crc >> 8) ^ self._table[n]
return crc

View File

@@ -1,84 +0,0 @@
"""
The MIT License (MIT)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
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.
"""
# pylint: disable=unused-import
import copy
import threading
from typing import Dict, List, Set, Sequence, Tuple, Iterable # noqa: F401
from typing import Callable, Optional, Union, Any # noqa: F401
class SSH1_KexDB: # pylint: disable=too-few-public-methods
FAIL_PLAINTEXT = 'no encryption/integrity'
FAIL_OPENSSH37_REMOVE = 'removed since OpenSSH 3.7'
FAIL_NA_BROKEN = 'not implemented in OpenSSH, broken algorithm'
FAIL_NA_UNSAFE = 'not implemented in OpenSSH (server), unsafe algorithm'
TEXT_CIPHER_IDEA = 'cipher used by commercial SSH'
DB_PER_THREAD: Dict[int, Dict[str, Dict[str, List[List[Optional[str]]]]]] = {}
MASTER_DB: Dict[str, Dict[str, List[List[Optional[str]]]]] = {
'key': {
'ssh-rsa1': [['1.2.2']],
},
'enc': {
'none': [['1.2.2'], [FAIL_PLAINTEXT]],
'idea': [[None], [], [], [TEXT_CIPHER_IDEA]],
'des': [['2.3.0C'], [FAIL_NA_UNSAFE]],
'3des': [['1.2.2']],
'tss': [[''], [FAIL_NA_BROKEN]],
'rc4': [[], [FAIL_NA_BROKEN]],
'blowfish': [['1.2.2']],
},
'aut': {
'rhosts': [['1.2.2', '3.6'], [FAIL_OPENSSH37_REMOVE]],
'rsa': [['1.2.2']],
'password': [['1.2.2']],
'rhosts_rsa': [['1.2.2']],
'tis': [['1.2.2']],
'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,144 +0,0 @@
"""
The MIT License (MIT)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
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.
"""
# pylint: disable=unused-import
from typing import Dict, List, Set, Sequence, Tuple, Iterable # noqa: F401
from typing import Callable, Optional, Union, Any # noqa: F401
from ssh_audit.ssh1 import SSH1
from ssh_audit.readbuf import ReadBuf
from ssh_audit.utils import Utils
from ssh_audit.writebuf import WriteBuf
class SSH1_PublicKeyMessage:
def __init__(self, cookie: bytes, skey: Tuple[int, int, int], hkey: Tuple[int, int, int], pflags: int, cmask: int, amask: int) -> None:
if len(skey) != 3:
raise ValueError('invalid server key pair: {}'.format(skey))
if len(hkey) != 3:
raise ValueError('invalid host key pair: {}'.format(hkey))
self.__cookie = cookie
self.__server_key = skey
self.__host_key = hkey
self.__protocol_flags = pflags
self.__supported_ciphers_mask = cmask
self.__supported_authentications_mask = amask
@property
def cookie(self) -> bytes:
return self.__cookie
@property
def server_key_bits(self) -> int:
return self.__server_key[0]
@property
def server_key_public_exponent(self) -> int:
return self.__server_key[1]
@property
def server_key_public_modulus(self) -> int:
return self.__server_key[2]
@property
def host_key_bits(self) -> int:
return self.__host_key[0]
@property
def host_key_public_exponent(self) -> int:
return self.__host_key[1]
@property
def host_key_public_modulus(self) -> int:
return self.__host_key[2]
@property
def host_key_fingerprint_data(self) -> bytes:
# pylint: disable=protected-access
mod = WriteBuf._create_mpint(self.host_key_public_modulus, False)
e = WriteBuf._create_mpint(self.host_key_public_exponent, False)
return mod + e
@property
def protocol_flags(self) -> int:
return self.__protocol_flags
@property
def supported_ciphers_mask(self) -> int:
return self.__supported_ciphers_mask
@property
def supported_ciphers(self) -> List[str]:
ciphers = []
for i in range(len(SSH1.CIPHERS)): # pylint: disable=consider-using-enumerate
if self.__supported_ciphers_mask & (1 << i) != 0:
ciphers.append(Utils.to_text(SSH1.CIPHERS[i]))
return ciphers
@property
def supported_authentications_mask(self) -> int:
return self.__supported_authentications_mask
@property
def supported_authentications(self) -> List[str]:
auths = []
for i in range(1, len(SSH1.AUTHS)):
if self.__supported_authentications_mask & (1 << i) != 0:
auths.append(Utils.to_text(SSH1.AUTHS[i]))
return auths
def write(self, wbuf: 'WriteBuf') -> None:
wbuf.write(self.cookie)
wbuf.write_int(self.server_key_bits)
wbuf.write_mpint1(self.server_key_public_exponent)
wbuf.write_mpint1(self.server_key_public_modulus)
wbuf.write_int(self.host_key_bits)
wbuf.write_mpint1(self.host_key_public_exponent)
wbuf.write_mpint1(self.host_key_public_modulus)
wbuf.write_int(self.protocol_flags)
wbuf.write_int(self.supported_ciphers_mask)
wbuf.write_int(self.supported_authentications_mask)
@property
def payload(self) -> bytes:
wbuf = WriteBuf()
self.write(wbuf)
return wbuf.write_flush()
@classmethod
def parse(cls, payload: bytes) -> 'SSH1_PublicKeyMessage':
buf = ReadBuf(payload)
cookie = buf.read(8)
server_key_bits = buf.read_int()
server_key_exponent = buf.read_mpint1()
server_key_modulus = buf.read_mpint1()
skey = (server_key_bits, server_key_exponent, server_key_modulus)
host_key_bits = buf.read_int()
host_key_exponent = buf.read_mpint1()
host_key_modulus = buf.read_mpint1()
hkey = (host_key_bits, host_key_exponent, host_key_modulus)
pflags = buf.read_int()
cmask = buf.read_int()
amask = buf.read_int()
pkm = cls(cookie, skey, hkey, pflags, cmask, amask)
return pkm

View File

@@ -1,7 +1,7 @@
""" """
The MIT License (MIT) The MIT License (MIT)
Copyright (C) 2017-2020 Joe Testa (jtesta@positronsecurity.com) Copyright (C) 2017-2024 Joe Testa (jtesta@positronsecurity.com)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu) 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
@@ -132,3 +132,16 @@ class SSH2_Kex:
srv = SSH2_KexParty(srv_enc, srv_mac, srv_compression, srv_languages) srv = SSH2_KexParty(srv_enc, srv_mac, srv_compression, srv_languages)
kex = cls(outputbuffer, cookie, kex_algs, key_algs, cli, srv, follows, unused) kex = cls(outputbuffer, cookie, kex_algs, key_algs, cli, srv, follows, unused)
return kex return kex
def __str__(self) -> str:
ret = "----\nSSH2_Kex object:"
ret += "\nHost keys: "
ret += ", ".join(self.__key_algs)
ret += "\nKey exchanges: "
ret += ", ".join(self.__kex_algs)
ret += "\nClient SSH2_KexParty:"
ret += "\n" + str(self.__client)
ret += "\nServer SSH2_KexParty:"
ret += "\n" + str(self.__server)
ret += "\n----"
return ret

View File

@@ -1,7 +1,7 @@
""" """
The MIT License (MIT) The MIT License (MIT)
Copyright (C) 2017-2024 Joe Testa (jtesta@positronsecurity.com) Copyright (C) 2017-2025 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
@@ -57,6 +57,7 @@ class SSH2_KexDB: # pylint: disable=too-few-public-methods
WARN_CIPHER_MODE = 'using weak cipher mode' WARN_CIPHER_MODE = 'using weak cipher mode'
WARN_ENCRYPT_AND_MAC = 'using encrypt-and-MAC mode' WARN_ENCRYPT_AND_MAC = 'using encrypt-and-MAC mode'
WARN_EXPERIMENTAL = 'using experimental algorithm' WARN_EXPERIMENTAL = 'using experimental algorithm'
WARN_NOT_PQ_SAFE = 'does not provide protection against post-quantum attacks'
WARN_RNDSIG_KEY = 'using weak random number generator could reveal the key' WARN_RNDSIG_KEY = 'using weak random number generator could reveal the key'
WARN_TAG_SIZE = 'using small 64-bit tag size' WARN_TAG_SIZE = 'using small 64-bit tag size'
WARN_TAG_SIZE_96 = 'using small 96-bit tag size' WARN_TAG_SIZE_96 = 'using small 96-bit tag size'
@@ -64,11 +65,15 @@ class SSH2_KexDB: # pylint: disable=too-few-public-methods
INFO_DEFAULT_OPENSSH_CIPHER = 'default cipher since OpenSSH 6.9' INFO_DEFAULT_OPENSSH_CIPHER = 'default cipher since OpenSSH 6.9'
INFO_DEFAULT_OPENSSH_KEX_65_TO_73 = 'default key exchange from OpenSSH 6.5 to 7.3' 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_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_DEFAULT_OPENSSH_KEX_90_TO_98 = 'default key exchange from OpenSSH 9.0 to 9.8'
INFO_DEFAULT_OPENSSH_KEX_99 = 'default key exchange in OpenSSH 9.9'
INFO_DEFAULT_OPENSSH_KEX_100 = 'default key exchange since OpenSSH 10.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'
INFO_NEVER_IMPLEMENTED_IN_OPENSSH = 'despite the @openssh.com tag, this was never implemented in OpenSSH' INFO_NEVER_IMPLEMENTED_IN_OPENSSH = 'despite the @openssh.com tag, this was never implemented in OpenSSH'
INFO_HYBRID_PQ_X25519_KEX = 'hybrid key exchange based on post-quantum resistant algorithm and proven conventional X25519 algorithm'
INFO_HYBRID_PQ_NISTP_KEX = 'hybrid key exchange based on post-quantum resistant algorithm and a suspected back-doored NIST P-curve'
INFO_REMOVED_IN_OPENSSH61 = 'removed since OpenSSH 6.1, removed from specification' INFO_REMOVED_IN_OPENSSH61 = 'removed since OpenSSH 6.1, removed from specification'
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'
@@ -82,118 +87,122 @@ class SSH2_KexDB: # pylint: disable=too-few-public-methods
MASTER_DB: 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': [[], [], [WARN_NOT_PQ_SAFE]],
'curve25519-sha256': [['7.4,d2018.76'], [], [], [INFO_DEFAULT_OPENSSH_KEX_74_TO_89]], 'curve25519-sha256': [['7.4,d2018.76'], [], [WARN_NOT_PQ_SAFE], [INFO_DEFAULT_OPENSSH_KEX_74_TO_89]],
'curve25519-sha256@libssh.org': [['6.4,d2013.62,l10.6.0'], [], [], [INFO_DEFAULT_OPENSSH_KEX_65_TO_73]], 'curve25519-sha256@libssh.org': [['6.4,d2013.62,l10.6.0'], [], [WARN_NOT_PQ_SAFE], [INFO_DEFAULT_OPENSSH_KEX_65_TO_73]],
'curve448-sha512': [[]], 'curve448-sha512': [[], [], [WARN_NOT_PQ_SAFE]],
'curve448-sha512@libssh.org': [[]], 'curve448-sha512@libssh.org': [[], [], [WARN_NOT_PQ_SAFE]],
'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, WARN_NOT_PQ_SAFE]],
'diffie-hellman-group14-sha224@ssh.com': [[]], 'diffie-hellman-group14-sha224@ssh.com': [[], [], [WARN_2048BIT_MODULUS, WARN_NOT_PQ_SAFE]],
'diffie-hellman-group14-sha256': [['7.3,d2016.73'], [], [WARN_2048BIT_MODULUS]], 'diffie-hellman-group14-sha256': [['7.3,d2016.73'], [], [WARN_2048BIT_MODULUS, WARN_NOT_PQ_SAFE]],
'diffie-hellman-group14-sha256@ssh.com': [[], [], [WARN_2048BIT_MODULUS]], 'diffie-hellman-group14-sha256@ssh.com': [[], [], [WARN_2048BIT_MODULUS, WARN_NOT_PQ_SAFE]],
'diffie-hellman-group15-sha256': [[]], 'diffie-hellman-group15-sha256': [[], [], [WARN_NOT_PQ_SAFE]],
'diffie-hellman-group15-sha256@ssh.com': [[]], 'diffie-hellman-group15-sha256@ssh.com': [[], [], [WARN_NOT_PQ_SAFE]],
'diffie-hellman-group15-sha384@ssh.com': [[]], 'diffie-hellman-group15-sha384@ssh.com': [[], [], [WARN_NOT_PQ_SAFE]],
'diffie-hellman-group15-sha512': [[]], 'diffie-hellman-group15-sha512': [[], [], [WARN_NOT_PQ_SAFE]],
'diffie-hellman-group16-sha256': [[]], 'diffie-hellman-group16-sha256': [[], [], [WARN_NOT_PQ_SAFE]],
'diffie-hellman-group16-sha384@ssh.com': [[]], 'diffie-hellman-group16-sha384@ssh.com': [[], [], [WARN_NOT_PQ_SAFE]],
'diffie-hellman-group16-sha512': [['7.3,d2016.73']], 'diffie-hellman-group16-sha512': [['7.3,d2016.73'], [], [WARN_NOT_PQ_SAFE]],
'diffie-hellman-group16-sha512@ssh.com': [[]], 'diffie-hellman-group16-sha512@ssh.com': [[], [], [WARN_NOT_PQ_SAFE]],
'diffie-hellman-group17-sha512': [[]], 'diffie-hellman-group17-sha512': [[], [], [WARN_NOT_PQ_SAFE]],
'diffie-hellman_group17-sha512': [[]], 'diffie-hellman_group17-sha512': [[], [], [WARN_NOT_PQ_SAFE]],
'diffie-hellman-group18-sha512': [['7.3']], 'diffie-hellman-group18-sha512': [['7.3'], [], [WARN_NOT_PQ_SAFE]],
'diffie-hellman-group18-sha512@ssh.com': [[]], 'diffie-hellman-group18-sha512@ssh.com': [[], [], [WARN_NOT_PQ_SAFE]],
'diffie-hellman-group1-sha1': [['2.3.0,d0.28,l10.2', '6.6', '6.9'], [FAIL_1024BIT_MODULUS, FAIL_LOGJAM_ATTACK, FAIL_SHA1], [], [INFO_REMOVED_IN_OPENSSH69]], 'diffie-hellman-group1-sha1': [['2.3.0,d0.28,l10.2', '6.6', '6.9'], [FAIL_1024BIT_MODULUS, FAIL_LOGJAM_ATTACK, FAIL_SHA1], [WARN_NOT_PQ_SAFE], [INFO_REMOVED_IN_OPENSSH69]],
'diffie-hellman-group1-sha256': [[], [FAIL_1024BIT_MODULUS]], 'diffie-hellman-group1-sha256': [[], [FAIL_1024BIT_MODULUS], [WARN_NOT_PQ_SAFE]],
'diffie-hellman-group-exchange-sha1': [['2.3.0', '6.6', None], [FAIL_SHA1]], 'diffie-hellman-group-exchange-sha1': [['2.3.0', '6.6', None], [FAIL_SHA1], [WARN_NOT_PQ_SAFE]],
'diffie-hellman-group-exchange-sha224@ssh.com': [[]], 'diffie-hellman-group-exchange-sha224@ssh.com': [[], [], [WARN_NOT_PQ_SAFE]],
'diffie-hellman-group-exchange-sha256': [['4.4']], 'diffie-hellman-group-exchange-sha256': [['4.4'], [], [WARN_NOT_PQ_SAFE]],
'diffie-hellman-group-exchange-sha256@ssh.com': [[]], 'diffie-hellman-group-exchange-sha256@ssh.com': [[], [], [WARN_NOT_PQ_SAFE]],
'diffie-hellman-group-exchange-sha384@ssh.com': [[]], 'diffie-hellman-group-exchange-sha384@ssh.com': [[], [], [WARN_NOT_PQ_SAFE]],
'diffie-hellman-group-exchange-sha512@ssh.com': [[]], 'diffie-hellman-group-exchange-sha512@ssh.com': [[], [], [WARN_NOT_PQ_SAFE]],
'ecdh-nistp256-kyber-512r3-sha256-d00@openquantumsafe.org': [[], [FAIL_NSA_BACKDOORED_CURVE]], '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-nistp384-kyber-768r3-sha384-d00@openquantumsafe.org': [[], [FAIL_NSA_BACKDOORED_CURVE]],
'ecdh-nistp521-kyber-1024r3-sha512-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], [WARN_NOT_PQ_SAFE]], # 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], [WARN_NOT_PQ_SAFE]], # 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': [[], [], [WARN_NOT_PQ_SAFE]], # ECDH over secp256k1 (i.e.: the Bitcoin curve)
'ecdh-sha2-1.3.132.0.16': [[], [FAIL_UNPROVEN]], # sect283k1 'ecdh-sha2-1.3.132.0.16': [[], [FAIL_UNPROVEN], [WARN_NOT_PQ_SAFE]], # sect283k1
'ecdh-sha2-1.3.132.0.1': [[], [FAIL_UNPROVEN, FAIL_SMALL_ECC_MODULUS]], # sect163k1 'ecdh-sha2-1.3.132.0.1': [[], [FAIL_UNPROVEN, FAIL_SMALL_ECC_MODULUS], [WARN_NOT_PQ_SAFE]], # sect163k1
'ecdh-sha2-1.3.132.0.26': [[], [FAIL_UNPROVEN, FAIL_SMALL_ECC_MODULUS]], # sect233k1 'ecdh-sha2-1.3.132.0.26': [[], [FAIL_UNPROVEN, FAIL_SMALL_ECC_MODULUS], [WARN_NOT_PQ_SAFE]], # 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], [WARN_NOT_PQ_SAFE]], # 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], [WARN_NOT_PQ_SAFE]], # 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], [WARN_NOT_PQ_SAFE]], # 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], [WARN_NOT_PQ_SAFE]], # NIST P-521 / secp521r1
'ecdh-sha2-1.3.132.0.36': [[], [FAIL_UNPROVEN]], # sect409k1 'ecdh-sha2-1.3.132.0.36': [[], [FAIL_UNPROVEN], [WARN_NOT_PQ_SAFE]], # sect409k1
'ecdh-sha2-1.3.132.0.37': [[], [FAIL_NSA_BACKDOORED_CURVE]], # sect409r1 'ecdh-sha2-1.3.132.0.37': [[], [FAIL_NSA_BACKDOORED_CURVE], [WARN_NOT_PQ_SAFE]], # sect409r1
'ecdh-sha2-1.3.132.0.38': [[], [FAIL_UNPROVEN]], # sect571k1 'ecdh-sha2-1.3.132.0.38': [[], [FAIL_UNPROVEN], [WARN_NOT_PQ_SAFE]], # 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_UNPROVEN, FAIL_SMALL_ECC_MODULUS]], # sect163k1 'ecdh-sha2-4MHB+NBt3AlaSRQ7MnB4cg==': [[], [FAIL_UNPROVEN, FAIL_SMALL_ECC_MODULUS], [WARN_NOT_PQ_SAFE]], # 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], [WARN_NOT_PQ_SAFE]], # NIST P-192 / secp192r1
'ecdh-sha2-9UzNcgwTlEnSCECZa7V1mw==': [[], [FAIL_NSA_BACKDOORED_CURVE]], # NIST P-256 / secp256r1 'ecdh-sha2-9UzNcgwTlEnSCECZa7V1mw==': [[], [FAIL_NSA_BACKDOORED_CURVE], [WARN_NOT_PQ_SAFE]], # NIST P-256 / secp256r1
'ecdh-sha2-brainpoolp256r1@genua.de': [[], [FAIL_UNPROVEN]], 'ecdh-sha2-brainpoolp256r1@genua.de': [[], [FAIL_UNPROVEN], [WARN_NOT_PQ_SAFE]],
'ecdh-sha2-brainpoolp384r1@genua.de': [[], [FAIL_UNPROVEN]], 'ecdh-sha2-brainpoolp384r1@genua.de': [[], [FAIL_UNPROVEN], [WARN_NOT_PQ_SAFE]],
'ecdh-sha2-brainpoolp521r1@genua.de': [[], [FAIL_UNPROVEN]], 'ecdh-sha2-brainpoolp521r1@genua.de': [[], [FAIL_UNPROVEN], [WARN_NOT_PQ_SAFE]],
'ecdh-sha2-curve25519': [[], []], 'ecdh-sha2-curve25519': [[], [], [WARN_NOT_PQ_SAFE]],
'ecdh-sha2-D3FefCjYoJ/kfXgAyLddYA==': [[], [FAIL_NSA_BACKDOORED_CURVE]], # sect409r1 'ecdh-sha2-D3FefCjYoJ/kfXgAyLddYA==': [[], [FAIL_NSA_BACKDOORED_CURVE], [WARN_NOT_PQ_SAFE]], # sect409r1
'ecdh-sha2-h/SsxnLCtRBh7I9ATyeB3A==': [[], [FAIL_NSA_BACKDOORED_CURVE]], # NIST P-521 / secp521r1 'ecdh-sha2-h/SsxnLCtRBh7I9ATyeB3A==': [[], [FAIL_NSA_BACKDOORED_CURVE], [WARN_NOT_PQ_SAFE]], # NIST P-521 / secp521r1
'ecdh-sha2-m/FtSAmrV4j/Wy6RVUaK7A==': [[], [FAIL_UNPROVEN]], # sect409k1 'ecdh-sha2-m/FtSAmrV4j/Wy6RVUaK7A==': [[], [FAIL_UNPROVEN], [WARN_NOT_PQ_SAFE]], # sect409k1
'ecdh-sha2-mNVwCXAoS1HGmHpLvBC94w==': [[], [FAIL_UNPROVEN]], # sect571k1 'ecdh-sha2-mNVwCXAoS1HGmHpLvBC94w==': [[], [FAIL_UNPROVEN], [WARN_NOT_PQ_SAFE]], # sect571k1
'ecdh-sha2-nistb233': [[], [FAIL_UNPROVEN, FAIL_SMALL_ECC_MODULUS]], 'ecdh-sha2-nistb233': [[], [FAIL_UNPROVEN, FAIL_SMALL_ECC_MODULUS], [WARN_NOT_PQ_SAFE]],
'ecdh-sha2-nistb409': [[], [FAIL_UNPROVEN]], 'ecdh-sha2-nistb409': [[], [FAIL_UNPROVEN], [WARN_NOT_PQ_SAFE]],
'ecdh-sha2-nistk163': [[], [FAIL_UNPROVEN, FAIL_SMALL_ECC_MODULUS]], 'ecdh-sha2-nistk163': [[], [FAIL_UNPROVEN, FAIL_SMALL_ECC_MODULUS], [WARN_NOT_PQ_SAFE]],
'ecdh-sha2-nistk233': [[], [FAIL_UNPROVEN, FAIL_SMALL_ECC_MODULUS]], 'ecdh-sha2-nistk233': [[], [FAIL_UNPROVEN, FAIL_SMALL_ECC_MODULUS], [WARN_NOT_PQ_SAFE]],
'ecdh-sha2-nistk283': [[], [FAIL_UNPROVEN]], 'ecdh-sha2-nistk283': [[], [FAIL_UNPROVEN], [WARN_NOT_PQ_SAFE]],
'ecdh-sha2-nistk409': [[], [FAIL_UNPROVEN]], 'ecdh-sha2-nistk409': [[], [FAIL_UNPROVEN], [WARN_NOT_PQ_SAFE]],
'ecdh-sha2-nistp192': [[], [FAIL_NSA_BACKDOORED_CURVE]], 'ecdh-sha2-nistp192': [[], [FAIL_NSA_BACKDOORED_CURVE], [WARN_NOT_PQ_SAFE]],
'ecdh-sha2-nistp224': [[], [FAIL_NSA_BACKDOORED_CURVE]], 'ecdh-sha2-nistp224': [[], [FAIL_NSA_BACKDOORED_CURVE], [WARN_NOT_PQ_SAFE]],
'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], [WARN_NOT_PQ_SAFE]],
'ecdh-sha2-nistp384': [['5.7,d2013.62'], [FAIL_NSA_BACKDOORED_CURVE]], 'ecdh-sha2-nistp384': [['5.7,d2013.62'], [FAIL_NSA_BACKDOORED_CURVE], [WARN_NOT_PQ_SAFE]],
'ecdh-sha2-nistp521': [['5.7,d2013.62'], [FAIL_NSA_BACKDOORED_CURVE]], 'ecdh-sha2-nistp521': [['5.7,d2013.62'], [FAIL_NSA_BACKDOORED_CURVE], [WARN_NOT_PQ_SAFE]],
'ecdh-sha2-nistt571': [[], [FAIL_UNPROVEN]], 'ecdh-sha2-nistt571': [[], [FAIL_UNPROVEN], [WARN_NOT_PQ_SAFE]],
'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], [WARN_NOT_PQ_SAFE]], # sect233r1
'ecdh-sha2-qcFQaMAMGhTziMT0z+Tuzw==': [[], [FAIL_NSA_BACKDOORED_CURVE]], # NIST P-384 / secp384r1 'ecdh-sha2-qcFQaMAMGhTziMT0z+Tuzw==': [[], [FAIL_NSA_BACKDOORED_CURVE], [WARN_NOT_PQ_SAFE]], # NIST P-384 / secp384r1
'ecdh-sha2-VqBg4QRPjxx1EXZdV0GdWQ==': [[], [FAIL_NSA_BACKDOORED_CURVE, FAIL_SMALL_ECC_MODULUS]], # NIST P-224 / secp224r1 'ecdh-sha2-VqBg4QRPjxx1EXZdV0GdWQ==': [[], [FAIL_NSA_BACKDOORED_CURVE, FAIL_SMALL_ECC_MODULUS], [WARN_NOT_PQ_SAFE]], # NIST P-224 / secp224r1
'ecdh-sha2-wiRIU8TKjMZ418sMqlqtvQ==': [[], [FAIL_UNPROVEN]], # sect283k1 'ecdh-sha2-wiRIU8TKjMZ418sMqlqtvQ==': [[], [FAIL_UNPROVEN], [WARN_NOT_PQ_SAFE]], # sect283k1
'ecdh-sha2-zD/b3hu/71952ArpUG4OjQ==': [[], [FAIL_UNPROVEN, FAIL_SMALL_ECC_MODULUS]], # sect233k1 'ecdh-sha2-zD/b3hu/71952ArpUG4OjQ==': [[], [FAIL_UNPROVEN, FAIL_SMALL_ECC_MODULUS], [WARN_NOT_PQ_SAFE]], # sect233k1
'ecmqv-sha2': [[], [FAIL_UNPROVEN]], 'ecmqv-sha2': [[], [FAIL_UNPROVEN], [WARN_NOT_PQ_SAFE]],
'ext-info-c': [[], [], [], [INFO_EXTENSION_NEGOTIATION]], # Extension negotiation (RFC 8308) 'ext-info-c': [['7.2'], [], [], [INFO_EXTENSION_NEGOTIATION]], # Extension negotiation (RFC 8308)
'ext-info-s': [[], [], [], [INFO_EXTENSION_NEGOTIATION]], # Extension negotiation (RFC 8308) 'ext-info-s': [['9.6'], [], [], [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-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). '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], [WARN_NOT_PQ_SAFE]],
'gss-curve25519-sha256-*': [[]], 'gss-curve25519-sha256-*': [[], [], [WARN_NOT_PQ_SAFE]],
'gss-curve448-sha512-*': [[]], 'gss-curve448-sha512-*': [[], [], [WARN_NOT_PQ_SAFE]],
'gss-gex-sha1-*': [[], [FAIL_SHA1]], 'gss-gex-sha1-*': [[], [FAIL_SHA1], [WARN_NOT_PQ_SAFE]],
'gss-gex-sha256-*': [[]], 'gss-gex-sha256-*': [[], [], [WARN_NOT_PQ_SAFE]],
'gss-group14-sha1-*': [[], [FAIL_SHA1], [WARN_2048BIT_MODULUS]], 'gss-group14-sha1-*': [[], [FAIL_SHA1], [WARN_2048BIT_MODULUS, WARN_NOT_PQ_SAFE]],
'gss-group14-sha256-*': [[], [], [WARN_2048BIT_MODULUS]], 'gss-group14-sha256-*': [[], [], [WARN_2048BIT_MODULUS, WARN_NOT_PQ_SAFE]],
'gss-group15-sha512-*': [[]], 'gss-group15-sha512-*': [[], [], [WARN_NOT_PQ_SAFE]],
'gss-group16-sha512-*': [[]], 'gss-group16-sha512-*': [[], [], [WARN_NOT_PQ_SAFE]],
'gss-group17-sha512-*': [[]], 'gss-group17-sha512-*': [[], [], [WARN_NOT_PQ_SAFE]],
'gss-group18-sha512-*': [[]], 'gss-group18-sha512-*': [[], [], [WARN_NOT_PQ_SAFE]],
'gss-group1-sha1-*': [[], [FAIL_1024BIT_MODULUS, FAIL_LOGJAM_ATTACK, FAIL_SHA1]], 'gss-group1-sha1-*': [[], [FAIL_1024BIT_MODULUS, FAIL_LOGJAM_ATTACK, FAIL_SHA1], [WARN_NOT_PQ_SAFE]],
'gss-nistp256-sha256-*': [[], [FAIL_NSA_BACKDOORED_CURVE]], 'gss-nistp256-sha256-*': [[], [FAIL_NSA_BACKDOORED_CURVE], [WARN_NOT_PQ_SAFE]],
'gss-nistp384-sha256-*': [[], [FAIL_NSA_BACKDOORED_CURVE]], 'gss-nistp384-sha256-*': [[], [FAIL_NSA_BACKDOORED_CURVE], [WARN_NOT_PQ_SAFE]],
'gss-nistp384-sha384-*': [[], [FAIL_NSA_BACKDOORED_CURVE]], 'gss-nistp384-sha384-*': [[], [FAIL_NSA_BACKDOORED_CURVE], [WARN_NOT_PQ_SAFE]],
'gss-nistp521-sha512-*': [[], [FAIL_NSA_BACKDOORED_CURVE]], 'gss-nistp521-sha512-*': [[], [FAIL_NSA_BACKDOORED_CURVE], [WARN_NOT_PQ_SAFE]],
'kexAlgoCurve25519SHA256': [[]], 'kexAlgoCurve25519SHA256': [[], [], [WARN_NOT_PQ_SAFE]],
'kexAlgoDH14SHA1': [[], [FAIL_SHA1], [WARN_2048BIT_MODULUS]], 'kexAlgoDH14SHA1': [[], [FAIL_SHA1], [WARN_2048BIT_MODULUS, WARN_NOT_PQ_SAFE]],
'kexAlgoDH1SHA1': [[], [FAIL_1024BIT_MODULUS, FAIL_LOGJAM_ATTACK, FAIL_SHA1]], 'kexAlgoDH1SHA1': [[], [FAIL_1024BIT_MODULUS, FAIL_LOGJAM_ATTACK, FAIL_SHA1], [WARN_NOT_PQ_SAFE]],
'kexAlgoECDH256': [[], [FAIL_NSA_BACKDOORED_CURVE]], 'kexAlgoECDH256': [[], [FAIL_NSA_BACKDOORED_CURVE], [WARN_NOT_PQ_SAFE]],
'kexAlgoECDH384': [[], [FAIL_NSA_BACKDOORED_CURVE]], 'kexAlgoECDH384': [[], [FAIL_NSA_BACKDOORED_CURVE], [WARN_NOT_PQ_SAFE]],
'kexAlgoECDH521': [[], [FAIL_NSA_BACKDOORED_CURVE]], 'kexAlgoECDH521': [[], [FAIL_NSA_BACKDOORED_CURVE], [WARN_NOT_PQ_SAFE]],
'kexguess2@matt.ucc.asn.au': [['d2013.57']], 'kexguess2@matt.ucc.asn.au': [['d2013.57'], [], [WARN_NOT_PQ_SAFE]],
'm383-sha384@libassh.org': [[], [FAIL_UNPROVEN]], 'm383-sha384@libassh.org': [[], [FAIL_UNPROVEN], [WARN_NOT_PQ_SAFE]],
'm511-sha512@libassh.org': [[], [FAIL_UNPROVEN]], 'm511-sha512@libassh.org': [[], [FAIL_UNPROVEN], [WARN_NOT_PQ_SAFE]],
'rsa1024-sha1': [[], [FAIL_1024BIT_MODULUS, FAIL_SHA1]], 'mlkem768x25519-sha256': [['9.9'], [], [], [INFO_DEFAULT_OPENSSH_KEX_100, INFO_HYBRID_PQ_X25519_KEX]],
'rsa2048-sha256': [[], [], [WARN_2048BIT_MODULUS]], 'mlkem768nistp256-sha256': [[], [FAIL_NSA_BACKDOORED_CURVE], [], [INFO_HYBRID_PQ_NISTP_KEX]],
'sm2kep-sha2-nistp256': [[], [FAIL_NSA_BACKDOORED_CURVE, FAIL_UNTRUSTED]], 'mlkem1024nistp384-sha384': [[], [FAIL_NSA_BACKDOORED_CURVE], [], [INFO_HYBRID_PQ_NISTP_KEX]],
'rsa1024-sha1': [[], [FAIL_1024BIT_MODULUS, FAIL_SHA1], [WARN_NOT_PQ_SAFE]],
'rsa2048-sha256': [[], [], [WARN_2048BIT_MODULUS, WARN_NOT_PQ_SAFE]],
'sm2kep-sha2-nistp256': [[], [FAIL_NSA_BACKDOORED_CURVE, FAIL_UNTRUSTED], [WARN_NOT_PQ_SAFE]],
'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'], [], [], [INFO_DEFAULT_OPENSSH_KEX_90]], 'sntrup761x25519-sha512': [['9.9'], [], [], [INFO_DEFAULT_OPENSSH_KEX_99, INFO_HYBRID_PQ_X25519_KEX]],
'sntrup761x25519-sha512@openssh.com': [['8.5'], [], [], [INFO_DEFAULT_OPENSSH_KEX_90_TO_98, INFO_HYBRID_PQ_X25519_KEX]],
'x25519-kyber-512r3-sha256-d00@amazon.com': [[]], 'x25519-kyber-512r3-sha256-d00@amazon.com': [[]],
'x25519-kyber512-sha512@aws.amazon.com': [[]], 'x25519-kyber512-sha512@aws.amazon.com': [[]],
}, },
@@ -293,6 +302,8 @@ class SSH2_KexDB: # pylint: disable=too-few-public-methods
'3des-ofb': [[], [FAIL_3DES], [WARN_CIPHER_MODE]], '3des-ofb': [[], [FAIL_3DES], [WARN_CIPHER_MODE]],
'AEAD_AES_128_GCM': [[]], 'AEAD_AES_128_GCM': [[]],
'AEAD_AES_256_GCM': [[]], 'AEAD_AES_256_GCM': [[]],
'AEAD_CAMELLIA_128_GCM': [[]],
'AEAD_CAMELLIA_256_GCM': [[]],
'aes128-cbc': [['2.3.0,d0.28,l10.2', '6.6', None], [], [WARN_CIPHER_MODE]], 'aes128-cbc': [['2.3.0,d0.28,l10.2', '6.6', None], [], [WARN_CIPHER_MODE]],
'aes128-ctr': [['3.7,d0.52,l10.4.1']], 'aes128-ctr': [['3.7,d0.52,l10.4.1']],
'aes128-gcm': [[]], 'aes128-gcm': [[]],
@@ -346,6 +357,7 @@ class SSH2_KexDB: # pylint: disable=too-few-public-methods
'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]],
'grasshopper-ctr128': [[], [FAIL_UNTRUSTED]],
'idea-cbc': [[], [FAIL_IDEA], [WARN_CIPHER_MODE]], 'idea-cbc': [[], [FAIL_IDEA], [WARN_CIPHER_MODE]],
'idea-cfb': [[], [FAIL_IDEA], [WARN_CIPHER_MODE]], 'idea-cfb': [[], [FAIL_IDEA], [WARN_CIPHER_MODE]],
'idea-ctr': [[], [FAIL_IDEA]], 'idea-ctr': [[], [FAIL_IDEA]],

View File

@@ -1,6 +1,7 @@
""" """
The MIT License (MIT) The MIT License (MIT)
Copyright (C) 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
@@ -48,3 +49,10 @@ class SSH2_KexParty:
@property @property
def languages(self) -> List[str]: def languages(self) -> List[str]:
return self.__languages return self.__languages
def __str__(self) -> str:
ret = "Ciphers: " + ", ".join(self.__enc)
ret += "\nMACs: " + ", ".join(self.__mac)
ret += "\nCompressions: " + ", ".join(self.__compression)
ret += "\nLanguages: " + ", ".join(self.__languages)
return ret

View File

@@ -2,7 +2,7 @@
""" """
The MIT License (MIT) The MIT License (MIT)
Copyright (C) 2017-2023 Joe Testa (jtesta@positronsecurity.com) Copyright (C) 2017-2025 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,9 +23,9 @@
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 argparse
import concurrent.futures import concurrent.futures
import copy import copy
import getopt
import json import json
import multiprocessing import multiprocessing
import os import os
@@ -33,6 +33,7 @@ import re
import sys import sys
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 cast, Callable, Optional, Union, Any # noqa: F401 from typing import cast, Callable, Optional, Union, Any # noqa: F401
@@ -52,16 +53,14 @@ from ssh_audit.gextest import GEXTest
from ssh_audit.hostkeytest import HostKeyTest from ssh_audit.hostkeytest import HostKeyTest
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.hardening_guides import Hardening_Guides
from ssh_audit.product import Product from ssh_audit.product import Product
from ssh_audit.protocol import Protocol from ssh_audit.protocol import Protocol
from ssh_audit.software import Software from ssh_audit.software import Software
from ssh_audit.ssh1_kexdb import SSH1_KexDB
from ssh_audit.ssh1_publickeymessage import SSH1_PublicKeyMessage
from ssh_audit.ssh2_kex import SSH2_Kex from ssh_audit.ssh2_kex import SSH2_Kex
from ssh_audit.ssh2_kexdb import SSH2_KexDB from ssh_audit.ssh2_kexdb import SSH2_KexDB
from ssh_audit.ssh_socket import SSH_Socket from ssh_audit.ssh_socket import SSH_Socket
from ssh_audit.utils import Utils from ssh_audit.utils import Utils
from ssh_audit.versionvulnerabilitydb import VersionVulnerabilityDB
# no_idna_workaround = False # no_idna_workaround = False
@@ -69,7 +68,7 @@ from ssh_audit.versionvulnerabilitydb import VersionVulnerabilityDB
# Only import colorama under Windows. Other OSes can natively handle terminal colors. # Only import colorama under Windows. Other OSes can natively handle terminal colors.
if sys.platform == 'win32': if sys.platform == 'win32':
try: try:
from colorama import just_fix_windows_console from colorama import just_fix_windows_console # type: ignore
just_fix_windows_console() just_fix_windows_console()
except ImportError: except ImportError:
pass pass
@@ -83,61 +82,6 @@ if sys.platform == 'win32':
# no_idna_workaround = True # no_idna_workaround = True
def usage(uout: OutputBuffer, err: Optional[str] = None) -> None:
retval = exitcodes.GOOD
p = os.path.basename(sys.argv[0])
uout.head('# {} {}, https://github.com/jtesta/ssh-audit\n'.format(p, VERSION))
if err is not None and len(err) > 0:
uout.fail(err + '\n')
retval = exitcodes.UNKNOWN_ERROR
uout.info('usage: {0} [options] <host>\n'.format(p))
uout.info(' -h, --help print this help')
uout.info(' -1, --ssh1 force ssh version 1 only')
uout.info(' -2, --ssh2 force ssh version 2 only')
uout.info(' -4, --ipv4 enable IPv4 (order of precedence)')
uout.info(' -6, --ipv6 enable IPv6 (order of precedence)')
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(' --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(' --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(' <min1:pref1:max1[,min2:pref2:max2,...]>')
uout.info(' <x-y[:step]>')
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, --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(' -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(' -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, --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, --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(' -v, --verbose verbose output')
uout.sep()
uout.write()
sys.exit(retval)
def output_algorithms(out: OutputBuffer, title: str, alg_db: Dict[str, Dict[str, List[List[Optional[str]]]]], alg_type: str, algorithms: List[str], unknown_algs: List[str], is_json_output: bool, program_retval: int, maxlen: int = 0, host_keys: Optional[Dict[str, Dict[str, Union[bytes, str, int]]]] = None, dh_modulus_sizes: Optional[Dict[str, int]] = None) -> int: # pylint: disable=too-many-arguments def output_algorithms(out: OutputBuffer, title: str, alg_db: Dict[str, Dict[str, List[List[Optional[str]]]]], alg_type: str, algorithms: List[str], unknown_algs: List[str], is_json_output: bool, program_retval: int, maxlen: int = 0, host_keys: Optional[Dict[str, Dict[str, Union[bytes, str, int]]]] = None, dh_modulus_sizes: Optional[Dict[str, int]] = None) -> int: # pylint: disable=too-many-arguments
with out: with out:
for algorithm in algorithms: for algorithm in algorithms:
@@ -273,74 +217,22 @@ def output_compatibility(out: OutputBuffer, algs: Algorithms, client_audit: bool
out.good('(gen) compatibility: ' + ', '.join(comp_text)) out.good('(gen) compatibility: ' + ', '.join(comp_text))
def output_security_sub(out: OutputBuffer, sub: str, software: Optional[Software], client_audit: bool, padlen: int) -> List[Dict[str, Union[str, float]]]: def output_security(out: OutputBuffer, banner: Optional[Banner], padlen: int, is_json_output: bool) -> None:
ret: List[Dict[str, Union[str, float]]] = []
secdb = VersionVulnerabilityDB.CVE if sub == 'cve' else VersionVulnerabilityDB.TXT
if software is None or software.product not in secdb:
return ret
for line in secdb[software.product]:
vfrom: str = ''
vtill: str = ''
vfrom, vtill = line[0:2]
if not software.between_versions(vfrom, vtill):
continue
target: int = 0
name: str = ''
target, name = line[2:4]
is_server = target & 1 == 1
is_client = target & 2 == 2
# is_local = target & 4 == 4
# If this security entry applies only to servers, but we're testing a client, then skip it. Similarly, skip entries that apply only to clients, but we're testing a server.
if (is_server and not is_client and client_audit) or (is_client and not is_server and not client_audit):
continue
p = '' if out.batch else ' ' * (padlen - len(name))
if sub == 'cve':
cvss: float = 0.0
descr: str = ''
cvss, descr = line[4:6]
# Critical CVSS scores (>= 8.0) are printed as a fail, otherwise they are printed as a warning.
out_func = out.warn
if cvss >= 8.0:
out_func = out.fail
out_func('(cve) {}{} -- (CVSSv2: {}) {}'.format(name, p, cvss, descr))
ret.append({'name': name, 'cvssv2': cvss, 'description': descr})
else:
descr = line[4]
out.fail('(sec) {}{} -- {}'.format(name, p, descr))
return ret
def output_security(out: OutputBuffer, banner: Optional[Banner], client_audit: bool, padlen: int, is_json_output: bool) -> List[Dict[str, Union[str, float]]]:
cves = []
with out: with out:
if banner is not None: if (banner is not None) and (banner.protocol[0] == 1):
software = Software.parse(banner) p = '' if out.batch else ' ' * (padlen - 14)
cves = output_security_sub(out, 'cve', software, client_audit, padlen) out.fail('(sec) SSH v1 enabled{} -- SSH v1 can be exploited to recover plaintext passwords'.format(p))
_ = output_security_sub(out, 'txt', software, client_audit, padlen)
if banner.protocol[0] == 1:
p = '' if out.batch else ' ' * (padlen - 14)
out.fail('(sec) SSH v1 enabled{} -- SSH v1 can be exploited to recover plaintext passwords'.format(p))
if not out.is_section_empty() and not is_json_output: if not out.is_section_empty() and not is_json_output:
out.head('# security') out.head('# security')
out.flush_section() out.flush_section()
out.sep() out.sep()
return cves
def output_fingerprints(out: OutputBuffer, algs: Algorithms, is_json_output: bool) -> None: def output_fingerprints(out: OutputBuffer, algs: Algorithms, is_json_output: bool) -> None:
with out: with out:
fps = {} fps = {}
if algs.ssh1kex is not None:
name = 'ssh-rsa1'
fp = Fingerprint(algs.ssh1kex.host_key_fingerprint_data)
# bits = algs.ssh1kex.host_key_bits
fps[name] = fp
if algs.ssh2kex is not None: if algs.ssh2kex is not None:
host_keys = algs.ssh2kex.host_keys() host_keys = algs.ssh2kex.host_keys()
for host_key_type in algs.ssh2kex.host_keys(): for host_key_type in algs.ssh2kex.host_keys():
@@ -360,11 +252,19 @@ def output_fingerprints(out: OutputBuffer, algs: Algorithms, is_json_output: boo
fp_types = sorted(fps.keys()) fp_types = sorted(fps.keys())
for fp_type in fp_types: for fp_type in fp_types:
fp = fps[fp_type] fp = fps[fp_type]
out.good('(fin) {}: {}'.format(fp_type, fp.sha256))
# Don't output any ECDSA or DSS fingerprints unless verbose mode is enabled.
if fp_type.startswith("ecdsa-") or (fp_type == "ssh-dss"):
if out.verbose:
out.warn('(fin) {}: {} -- [info] this fingerprint type is insecure and should not be relied upon'.format(fp_type, fp.sha256))
else:
continue # If verbose mode is not enabled, skip this type entirely.
else:
out.good('(fin) {}: {}'.format(fp_type, fp.sha256))
# Output the MD5 hash too if verbose mode is enabled. # Output the MD5 hash too if verbose mode is enabled.
if out.verbose: if out.verbose:
out.info('(fin) {}: {} -- [info] do not rely on MD5 fingerprints for server identification; it is insecure for this use case'.format(fp_type, fp.md5)) out.warn('(fin) {}: {} -- [info] do not rely on MD5 fingerprints for server identification; it is insecure for this use case'.format(fp_type, fp.md5))
if not out.is_section_empty() and not is_json_output: if not out.is_section_empty() and not is_json_output:
out.head('# fingerprints') out.head('# fingerprints')
@@ -376,31 +276,6 @@ def output_fingerprints(out: OutputBuffer, algs: Algorithms, is_json_output: boo
def output_recommendations(out: OutputBuffer, algs: Algorithms, algorithm_recommendation_suppress_list: List[str], software: Optional[Software], is_json_output: bool, padlen: int = 0) -> bool: def output_recommendations(out: OutputBuffer, algs: Algorithms, algorithm_recommendation_suppress_list: List[str], software: Optional[Software], is_json_output: bool, padlen: int = 0) -> bool:
ret = True ret = True
# PuTTY's algorithms cannot be modified, so there's no point in issuing recommendations.
if (software is not None) and (software.product == Product.PuTTY):
max_vuln_version = 0.0
max_cvssv2_severity = 0.0
# Search the CVE database for the most recent vulnerable version and the max CVSSv2 score.
for cve_list in VersionVulnerabilityDB.CVE['PuTTY']:
vuln_version = float(cve_list[1])
cvssv2_severity = cve_list[4]
max_vuln_version = max(vuln_version, max_vuln_version)
max_cvssv2_severity = max(cvssv2_severity, max_cvssv2_severity)
fn = out.warn
if max_cvssv2_severity > 8.0:
fn = out.fail
# Assuming that PuTTY versions will always increment by 0.01, we can calculate the first safe version by adding 0.01 to the latest vulnerable version.
current_version = float(software.version)
upgrade_to_version = max_vuln_version + 0.01
if current_version < upgrade_to_version:
out.head('# recommendations')
fn('(rec) Upgrade to PuTTY v%.2f' % upgrade_to_version)
out.sep()
ret = False
return ret
level_to_output = { level_to_output = {
"informational": out.good, "informational": out.good,
"warning": out.warn, "warning": out.warn,
@@ -421,6 +296,8 @@ def output_recommendations(out: OutputBuffer, algs: Algorithms, algorithm_recomm
fn = level_to_output[level] fn = level_to_output[level]
an = '?'
sg = '?'
if action == 'del': if action == 'del':
an, sg = 'remove', '-' an, sg = 'remove', '-'
ret = False ret = False
@@ -434,7 +311,7 @@ def output_recommendations(out: OutputBuffer, algs: Algorithms, algorithm_recomm
notes = " (%s)" % notes notes = " (%s)" % notes
fm = '(rec) {0}{1}{2}-- {3} algorithm to {4}{5} ' fm = '(rec) {0}{1}{2}-- {3} algorithm to {4}{5} '
fn(fm.format(sg, name, p, alg_type, an, notes)) fn(fm.format(sg, name, p, alg_type, an, notes)) # type: ignore[operator]
if not out.is_section_empty() and not is_json_output: if not out.is_section_empty() and not is_json_output:
if software is not None: if software is not None:
@@ -456,7 +333,7 @@ def output_info(out: OutputBuffer, software: Optional['Software'], client_audit:
# If any warnings or failures were given, print a link to the hardening guides. # If any warnings or failures were given, print a link to the hardening guides.
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, a built-in list can be viewed with --list-hardening-guides, or an online list can be found at: <https://www.ssh-audit.com/hardening_guides.html>')
# Add any additional notes. # Add any additional notes.
for additional_note in additional_notes: for additional_note in additional_notes:
@@ -626,12 +503,11 @@ def post_process_findings(banner: Optional[Banner], algs: Algorithms, client_aud
# 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, dh_rate_test_notes: str = "") -> int: def output(out: OutputBuffer, aconf: AuditConf, banner: Optional[Banner], header: List[str], client_host: Optional[str] = None, kex: Optional[SSH2_Kex] = 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.
sshv = 1 if pkm is not None else 2 algs = Algorithms(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, additional_notes = post_process_findings(banner, algs, client_audit, dh_rate_test_notes) algorithm_recommendation_suppress_list, additional_notes = post_process_findings(banner, algs, client_audit, dh_rate_test_notes)
@@ -649,14 +525,14 @@ def output(out: OutputBuffer, aconf: AuditConf, banner: Optional[Banner], header
else: else:
host = '%s:%d' % (aconf.host, aconf.port) host = '%s:%d' % (aconf.host, aconf.port)
out.good('(gen) target: {}'. format(host)) out.good('(gen) target: {}'. format(host), always_print=True)
if client_audit: if client_audit:
out.good('(gen) client IP: {}'.format(client_host)) out.good('(gen) client IP: {}'.format(client_host), always_print=True)
if len(header) > 0: if len(header) > 0:
out.info('(gen) header: ' + '\n'.join(header)) out.info('(gen) header: ' + '\n'.join(header))
if banner is not None: if banner is not None:
banner_line = '(gen) banner: {}'.format(banner) banner_line = '(gen) banner: {}'.format(banner)
if sshv == 1 or banner.protocol[0] == 1: if banner.protocol[0] == 1:
out.fail(banner_line) out.fail(banner_line)
out.fail('(gen) protocol SSH1 enabled') out.fail('(gen) protocol SSH1 enabled')
else: else:
@@ -684,22 +560,10 @@ def output(out: OutputBuffer, aconf: AuditConf, banner: Optional[Banner], header
out.flush_section() out.flush_section()
out.sep() out.sep()
maxlen = algs.maxlen + 1 maxlen = algs.maxlen + 1
cves = output_security(out, banner, client_audit, maxlen, aconf.json) output_security(out, banner, maxlen, aconf.json)
# Filled in by output_algorithms() with unidentified algs. # Filled in by output_algorithms() with unidentified algs.
unknown_algorithms: List[str] = [] unknown_algorithms: List[str] = []
# SSHv1
if pkm is not None:
adb = SSH1_KexDB.get_db()
ciphers = pkm.supported_ciphers
auths = pkm.supported_authentications
title, atype = 'SSH1 host-key algorithms', 'key'
program_retval = output_algorithms(out, title, adb, atype, ['ssh-rsa1'], unknown_algorithms, aconf.json, program_retval, maxlen)
title, atype = 'SSH1 encryption algorithms (ciphers)', 'enc'
program_retval = output_algorithms(out, title, adb, atype, ciphers, unknown_algorithms, aconf.json, program_retval, maxlen)
title, atype = 'SSH1 authentication types', 'aut'
program_retval = output_algorithms(out, title, adb, atype, auths, unknown_algorithms, aconf.json, program_retval, maxlen)
# SSHv2 # SSHv2
if kex is not None: if kex is not None:
adb = SSH2_KexDB.get_db() adb = SSH2_KexDB.get_db()
@@ -719,9 +583,9 @@ def output(out: OutputBuffer, aconf: AuditConf, banner: Optional[Banner], header
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, additional_notes=additional_notes), indent=4 if aconf.json_print_indent else None, sort_keys=True)) out.info(json.dumps(build_struct(aconf.host + ":" + str(aconf.port), banner, 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. If this is the latest version of ssh-audit (see <https://github.com/jtesta/ssh-audit/releases>), please create a new Github issue at <https://github.com/jtesta/ssh-audit/issues> with the full output above.\n" % ','.join(unknown_algorithms))
return program_retval return program_retval
@@ -733,7 +597,12 @@ def evaluate_policy(out: OutputBuffer, aconf: AuditConf, banner: Optional['Banne
passed, error_struct, error_str = aconf.policy.evaluate(banner, kex) passed, error_struct, error_str = aconf.policy.evaluate(banner, kex)
if aconf.json: if aconf.json:
json_struct = {'host': aconf.host, 'policy': aconf.policy.get_name_and_version(), 'passed': passed, 'errors': error_struct} warnings: List[str] = []
if aconf.policy.is_outdated_builtin_policy():
warnings.append("A newer version of this built-in policy is available.")
json_struct = {'host': aconf.host, 'port': aconf.port, 'policy': aconf.policy.get_name_and_version(), 'passed': passed, 'errors': error_struct, 'warnings': warnings}
out.info(json.dumps(json_struct, indent=4 if aconf.json_print_indent else None, sort_keys=True)) out.info(json.dumps(json_struct, indent=4 if aconf.json_print_indent else None, sort_keys=True))
else: else:
spacing = '' spacing = ''
@@ -766,6 +635,10 @@ def evaluate_policy(out: OutputBuffer, aconf: AuditConf, banner: Optional['Banne
out.fail("%sFailed!" % icon_fail) out.fail("%sFailed!" % icon_fail)
out.warn("\nErrors:\n%s" % error_str) out.warn("\nErrors:\n%s" % error_str)
# If the user selected an out-dated built-in policy then issue a warning.
if aconf.policy.is_outdated_builtin_policy():
out.warn("Note: A newer version of this built-in policy is available. Use the -L option to view all available versions.")
return passed return passed
@@ -836,8 +709,9 @@ def list_policies(out: OutputBuffer, verbose: bool) -> None:
out.fail("Error: no built-in policies found!") out.fail("Error: no built-in policies found!")
else: else:
out.info("\nHint: Use -P and provide the full name of a policy to run a policy scan with.\n") out.info("\nHint: Use -P and provide the full name of a policy to run a policy scan with.\n")
out.info("Hint: Use -L -v to also see the change log for each policy.\n") out.info("Hint: Use -L -v to see the change log for each policy, as well as previous versions.\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.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.info("Note: instructions for hardening targets, which correspond to the above policies, can be found at: <https://ssh-audit.com/hardening_guides.html>\n")
out.write() out.write()
@@ -876,7 +750,7 @@ def make_policy(aconf: AuditConf, banner: Optional['Banner'], kex: Optional['SSH
print(err) print(err)
def process_commandline(out: OutputBuffer, args: List[str], usage_cb: Callable[..., None]) -> 'AuditConf': # pylint: disable=too-many-statements def process_commandline(out: OutputBuffer, args: List[str]) -> 'AuditConf': # pylint: disable=too-many-statements
# pylint: disable=too-many-branches # pylint: disable=too-many-branches
aconf = AuditConf() aconf = AuditConf()
@@ -889,82 +763,90 @@ def process_commandline(out: OutputBuffer, args: List[str], usage_cb: Callable[.
aconf.colors = enable_colors aconf.colors = enable_colors
out.use_colors = enable_colors out.use_colors = enable_colors
try:
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=', 'dheat=', 'skip-rate-test', 'conn-rate-test=']
opts, args = getopt.gnu_getopt(args, sopts, lopts)
except getopt.GetoptError as err:
usage_cb(out, str(err))
aconf.ssh1, aconf.ssh2 = False, False
host: str = '' host: str = ''
oport: Optional[str] = None port: int = 22
port: int = 0
for o, a in opts:
if o in ('-h', '--help'):
usage_cb(out)
elif o in ('-1', '--ssh1'):
aconf.ssh1 = True
elif o in ('-2', '--ssh2'):
aconf.ssh2 = True
elif o in ('-4', '--ipv4'):
aconf.ipv4 = True
elif o in ('-6', '--ipv6'):
aconf.ipv6 = True
elif o in ('-p', '--port'):
oport = a
elif o in ('-b', '--batch'):
aconf.batch = True
aconf.verbose = True
elif o in ('-c', '--client-audit'):
aconf.client_audit = True
elif o in ('-j', '--json'):
if aconf.json: # If specified twice, enable indent printing.
aconf.json_print_indent = True
else:
aconf.json = True
elif o in ('-v', '--verbose'):
aconf.verbose = True
out.verbose = True
elif o in ('-l', '--level'):
if a not in ('info', 'warn', 'fail'):
usage_cb(out, 'level {} is not valid'.format(a))
aconf.level = a
elif o in ('-t', '--timeout'):
aconf.timeout = float(a)
aconf.timeout_set = True
elif o in ('-M', '--make-policy'):
aconf.make_policy = True
aconf.policy_file = a
elif o in ('-P', '--policy'):
aconf.policy_file = a
elif o in ('-T', '--targets'):
aconf.target_file = a
# If we're on Windows, and we can't use the idna workaround, force only one thread to be used (otherwise a crash would occur). parser = argparse.ArgumentParser(description="# {} {}, https://github.com/jtesta/ssh-audit".format(os.path.basename(sys.argv[0]), VERSION), allow_abbrev=False)
# if no_idna_workaround:
# print("\nWARNING: the idna module was not found on this system, thus only single-threaded scanning will be done (this is a workaround for this Windows-specific crash: https://github.com/python/cpython/issues/73474). Multi-threaded scanning can be enabled by installing the idna module (pip install idna).\n") # Add short options to the parser
# aconf.threads = 1 parser.add_argument("-4", "--ipv4", action="store_true", dest="ipv4", default=False, help="enable IPv4 (order of precedence)")
elif o == '--threads': parser.add_argument("-6", "--ipv6", action="store_true", dest="ipv6", default=False, help="enable IPv6 (order of precedence)")
aconf.threads = int(a) parser.add_argument("-b", "--batch", action="store_true", dest="batch", default=False, help="batch output")
# if no_idna_workaround: parser.add_argument("-c", "--client-audit", action="store_true", dest="client_audit", default=False, help="starts a server on port 2222 to audit client software config (use -p to change port; use -t to change timeout)")
# aconf.threads = 1 parser.add_argument("-d", "--debug", action="store_true", dest="debug", default=False, help="enable debugging output")
elif o in ('-L', '--list-policies'): parser.add_argument("-g", "--gex-test", action="store", dest="gex_test", metavar="<min1:pref1:max1[,min2:pref2:max2,...]> / <x-y[:step]>", type=str, default=None, help="conducts a very customized Diffie-Hellman GEX modulus size test. Tests an array of minimum, preferred, and maximum values, or a range of values with an optional incremental step amount")
aconf.list_policies = True parser.add_argument("-j", "--json", action="count", dest="json", default=0, help="enable JSON output (use -jj to enable indentation for better readability)")
elif o == '--lookup': parser.add_argument("-l", "--level", action="store", dest="level", type=str, choices=["info", "warn", "fail"], default="info", help="minimum output level (default: %(default)s)")
aconf.lookup = a parser.add_argument("-L", "--list-policies", action="store_true", dest="list_policies", default=False, help="list all the official, built-in policies. Combine with -v to view policy change logs")
elif o in ('-m', '--manual'): parser.add_argument("-M", "--make-policy", action="store", dest="make_policy", metavar="custom_policy.txt", type=str, default=None, help="creates a policy based on the target server (i.e.: the target server has the ideal configuration that other servers should adhere to), and stores it in the file path specified")
aconf.manual = True parser.add_argument("-m", "--manual", action="store_true", dest="manual", default=False, help="print the man page (Docker, PyPI, Snap, and Windows builds only)")
elif o in ('-d', '--debug'): parser.add_argument("-n", "--no-colors", action="store_true", dest="no_colors", default=False, help="disable colors (automatic when the NO_COLOR environment variable is set)")
parser.add_argument("-P", "--policy", action="store", dest="policy", metavar="\"Built-In Policy Name\" / custom_policy.txt", type=str, default=None, help="run a policy test using the specified policy (use -L to see built-in policies, or specify filesystem path to custom policy created by -M)")
parser.add_argument("-p", "--port", action="store", dest="oport", metavar="N", type=int, default=None, help="the TCP port to connect to (or to listen on when -c is used)")
parser.add_argument("-T", "--targets", action="store", dest="targets", metavar="targets.txt", type=str, default=None, help="a file containing a list of target hosts (one per line, format HOST[:PORT]). Use -p/--port to set the default port for all hosts. Use --threads to control concurrent scans")
parser.add_argument("-t", "--timeout", action="store", dest="timeout", metavar="N", type=int, default=5, help="timeout (in seconds) for connection and reading (default: %(default)s)")
parser.add_argument("-v", "--verbose", action="store_true", dest="verbose", default=False, help="enable verbose output")
# Add long options to the parser
parser.add_argument("--conn-rate-test", action="store", dest="conn_rate_test", metavar="N[:max_rate]", type=str, default=None, help="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")
parser.add_argument("--dheat", action="store", dest="dheat", metavar="N[:kex[:e_len]]", type=str, default=None, help="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).")
parser.add_argument("--get-hardening-guide", action="store", metavar="platform", dest="get_hardening_guide", type=str, default=None, help="retrieves the hardening guide for the specified platform name (use --list-hardening-guides to see list of available guides).")
parser.add_argument("--list-hardening-guides", action="store_true", dest="list_hardening_guides", default=False, help="list all official, built-in hardening guides for common systems. Their full names can then be passed to --get-hardening-guide. Add -v to this option to view hardening guide change logs and prior versions.")
parser.add_argument("--lookup", action="store", dest="lookup", metavar="alg1[,alg2,...]", type=str, default=None, help="looks up an algorithm(s) without connecting to a server.")
parser.add_argument("--skip-rate-test", action="store_true", dest="skip_rate_test", default=False, help="skip the connection rate test during standard audits (used to safely infer whether the DHEat attack is viable)")
parser.add_argument("--threads", action="store", dest="threads", metavar="N", type=int, default=32, help="number of threads to use when scanning multiple targets (-T/--targets) (default: %(default)s)")
# The mandatory target option. Or rather, mandatory when -L, -T, --lookup or --print-config are not used.
parser.add_argument("host", nargs="?", action="store", type=str, default="", help="target hostname or IPv4/IPv6 address")
# If no arguments were given, print the help and exit.
if len(args) < 1:
parser.print_help()
sys.exit(exitcodes.UNKNOWN_ERROR)
oport: Optional[int] = None
try:
argument = parser.parse_args(args=args)
# Set simple flags.
aconf.client_audit = argument.client_audit
aconf.ipv4 = argument.ipv4
aconf.ipv6 = argument.ipv6
aconf.level = argument.level
aconf.list_policies = argument.list_policies
aconf.manual = argument.manual
aconf.skip_rate_test = argument.skip_rate_test
oport = argument.oport
if argument.batch is True:
aconf.batch = True
# If one -j was given, turn on JSON output. If -jj was given, enable indentation.
aconf.json = argument.json > 0
if argument.json > 1:
aconf.json_print_indent = True
if argument.conn_rate_test is not None:
aconf.conn_rate_test = argument.conn_rate_test
if argument.debug is True:
aconf.debug = True aconf.debug = True
out.debug = True out.debug = True
elif o in ('-g', '--gex-test'):
if argument.dheat is not None:
aconf.dheat = argument.dheat
if argument.gex_test is not None:
dh_gex = argument.gex_test
permitted_syntax = get_permitted_syntax_for_gex_test() permitted_syntax = get_permitted_syntax_for_gex_test()
if not any(re.search(regex_str, a) for regex_str in permitted_syntax.values()): if not any(re.search(regex_str, dh_gex) for regex_str in permitted_syntax.values()):
usage_cb(out, '{} {} is not valid'.format(o, a)) out.fail('{} is not valid'.format(dh_gex), write_now=True)
sys.exit(exitcodes.UNKNOWN_ERROR)
if re.search(permitted_syntax['RANGE'], a): if re.search(permitted_syntax['RANGE'], dh_gex):
extracted_digits = re.findall(r'\d+', a) extracted_digits = re.findall(r'\d+', dh_gex)
bits_left_bound = int(extracted_digits[0]) bits_left_bound = int(extracted_digits[0])
bits_right_bound = int(extracted_digits[1]) bits_right_bound = int(extracted_digits[1])
@@ -973,56 +855,87 @@ def process_commandline(out: OutputBuffer, args: List[str], usage_cb: Callable[.
bits_step = int(extracted_digits[2]) bits_step = int(extracted_digits[2])
if bits_step <= 0: if bits_step <= 0:
usage_cb(out, '{} {} is not valid'.format(o, bits_step)) out.fail('the step field cannot be 0 or less: {}'.format(bits_step), write_now=True)
sys.exit(exitcodes.UNKNOWN_ERROR)
if all(x < 0 for x in (bits_left_bound, bits_right_bound)): if all(x < 0 for x in (bits_left_bound, bits_right_bound)):
usage_cb(out, '{} {} {} is not valid'.format(o, bits_left_bound, bits_right_bound)) out.fail('{} {} {} is not valid'.format(dh_gex, bits_left_bound, bits_right_bound), write_now=True)
sys.exit(exitcodes.UNKNOWN_ERROR)
aconf.gex_test = a aconf.gex_test = dh_gex
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 argument.lookup is not None:
aconf.lookup = argument.lookup
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 argument.make_policy is not None:
usage_cb(out) aconf.make_policy = True
aconf.policy_file = argument.make_policy
if argument.policy is not None:
aconf.policy_file = argument.policy
if argument.targets is not None:
aconf.target_file = argument.targets
if argument.threads is not None:
aconf.threads = argument.threads
if argument.timeout is not None:
aconf.timeout = float(argument.timeout)
aconf.timeout_set = True
if argument.verbose is True:
aconf.verbose = True
out.verbose = True
except argparse.ArgumentError as err:
out.fail(str(err), write_now=True)
parser.print_help()
sys.exit(exitcodes.UNKNOWN_ERROR)
if argument.host == "" and argument.client_audit is False and argument.targets is None and argument.list_policies is False and argument.lookup is None and argument.manual is False and argument.list_hardening_guides is False and argument.get_hardening_guide is None:
out.fail("target host must be specified, unless -c, -m, -L, -T, --lookup or --list-hardening-guides are used", write_now=True)
sys.exit(exitcodes.UNKNOWN_ERROR)
if aconf.manual: if aconf.manual:
return aconf return aconf
if aconf.lookup != '': if aconf.lookup != "":
return aconf return aconf
if aconf.list_policies: if aconf.list_policies:
list_policies(out, aconf.verbose) list_policies(out, aconf.verbose)
sys.exit(exitcodes.GOOD) sys.exit(exitcodes.GOOD)
# Print a list of the hardening guides, or the specific guide requested by the user.
if argument.list_hardening_guides is True:
Hardening_Guides.list_guides(out, aconf.verbose)
sys.exit(exitcodes.GOOD)
elif argument.get_hardening_guide is not None:
Hardening_Guides.print_hardening_guide(out, argument.get_hardening_guide)
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:
if oport is not None: if oport is not None:
host = args[0] host = argument.host
else: else:
host, port = Utils.parse_host_and_port(args[0]) host, port = Utils.parse_host_and_port(argument.host)
if not host and aconf.target_file is None:
usage_cb(out, 'host is empty')
if port == 0 and oport is None: if not host and aconf.target_file is None:
if aconf.client_audit: # The default port to listen on during a client audit is 2222. out.fail("target host is not specified", write_now=True)
port = 2222 sys.exit(exitcodes.UNKNOWN_ERROR)
else:
port = 22 if oport is None and aconf.client_audit: # The default port to listen on during a client audit is 2222.
port = 2222
if oport is not None: if oport is not None:
port = Utils.parse_int(oport) port = Utils.parse_int(oport)
if port <= 0 or port > 65535: if port < 1 or port > 65535:
usage_cb(out, 'port {} is not valid'.format(oport)) out.fail("port must be greater than 0 and less than 65535: {}".format(oport), write_now=True)
sys.exit(exitcodes.UNKNOWN_ERROR)
aconf.host = host aconf.host = host
aconf.port = port aconf.port = port
if not (aconf.ssh1 or aconf.ssh2):
aconf.ssh1, aconf.ssh2 = True, True
# If a file containing a list of targets was given, read it. # If a file containing a list of targets was given, read it.
if aconf.target_file is not None: if aconf.target_file is not None:
@@ -1049,26 +962,23 @@ def process_commandline(out: OutputBuffer, args: List[str], usage_cb: Callable[.
try: try:
aconf.policy = Policy(policy_file=aconf.policy_file, json_output=aconf.json) aconf.policy = Policy(policy_file=aconf.policy_file, json_output=aconf.json)
except Exception as e: except Exception as e:
out.fail("Error while loading policy file: %s: %s" % (str(e), traceback.format_exc())) out.fail("Error while loading policy file: %s: %s" % (str(e), traceback.format_exc()), write_now=True)
out.write()
sys.exit(exitcodes.UNKNOWN_ERROR) sys.exit(exitcodes.UNKNOWN_ERROR)
# If the user wants to do a client audit, but provided a server policy, terminate. # If the user wants to do a client audit, but provided a server policy, terminate.
if aconf.client_audit and aconf.policy.is_server_policy(): if aconf.client_audit and aconf.policy.is_server_policy():
out.fail("Error: client audit selected, but server policy provided.") out.fail("Error: client audit selected, but server policy provided.", write_now=True)
out.write()
sys.exit(exitcodes.UNKNOWN_ERROR) sys.exit(exitcodes.UNKNOWN_ERROR)
# If the user wants to do a server audit, but provided a client policy, terminate. # If the user wants to do a server audit, but provided a client policy, terminate.
if aconf.client_audit is False and aconf.policy.is_server_policy() is False: if aconf.client_audit is False and aconf.policy.is_server_policy() is False:
out.fail("Error: server audit selected, but client policy provided.") out.fail("Error: server audit selected, but client policy provided.", write_now=True)
out.write()
sys.exit(exitcodes.UNKNOWN_ERROR) sys.exit(exitcodes.UNKNOWN_ERROR)
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, additional_notes: List[str] = []) -> Any: # pylint: disable=dangerous-default-value def build_struct(target_host: str, banner: Optional['Banner'], kex: Optional['SSH2_Kex'] = 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]]]: 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.''' '''Returns a dictionary containing the messages in the "fail", "warn", and "info" levels for this algorithm.'''
@@ -1213,25 +1123,9 @@ def build_struct(target_host: str, banner: Optional['Banner'], cves: List[Dict[s
'hash_alg': 'MD5', 'hash_alg': 'MD5',
'hash': fp.md5[4:] 'hash': fp.md5[4:]
}) })
else:
pkm_supported_ciphers = None
pkm_supported_authentications = None
pkm_fp = None
if pkm is not None:
pkm_supported_ciphers = pkm.supported_ciphers
pkm_supported_authentications = pkm.supported_authentications
pkm_fp = Fingerprint(pkm.host_key_fingerprint_data).sha256
res['key'] = ['ssh-rsa1'] # Historically, CVE information was returned. Now we'll just return an empty dictionary so as to not break any legacy clients.
res['enc'] = pkm_supported_ciphers res['cves'] = []
res['aut'] = pkm_supported_authentications
res['fingerprints'] = [{
'type': 'ssh-rsa1',
'fp': pkm_fp,
}]
# Add in the CVE information.
res['cves'] = cves
# 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)
@@ -1243,7 +1137,7 @@ def build_struct(target_host: str, banner: Optional['Banner'], cves: List[Dict[s
# Returns one of the exitcodes.* flags. # Returns one of the exitcodes.* flags.
def audit(out: OutputBuffer, aconf: AuditConf, sshv: Optional[int] = None, print_target: bool = False) -> int: def audit(out: OutputBuffer, aconf: AuditConf, print_target: bool = False) -> int:
program_retval = exitcodes.GOOD program_retval = exitcodes.GOOD
out.batch = aconf.batch out.batch = aconf.batch
out.verbose = aconf.verbose out.verbose = aconf.verbose
@@ -1269,10 +1163,8 @@ def audit(out: OutputBuffer, aconf: AuditConf, sshv: Optional[int] = None, print
out.write() out.write()
sys.exit(exitcodes.CONNECTION_ERROR) sys.exit(exitcodes.CONNECTION_ERROR)
if sshv is None:
sshv = 2 if aconf.ssh2 else 1
err = None err = None
banner, header, err = s.get_banner(sshv) banner, header, err = s.get_banner()
if banner is None: if banner is None:
if err is None: if err is None:
err = '[exception] did not receive banner.' err = '[exception] did not receive banner.'
@@ -1281,7 +1173,7 @@ def audit(out: OutputBuffer, aconf: AuditConf, sshv: Optional[int] = None, print
if err is None: if err is None:
s.send_kexinit() # Send the algorithms we support (except we don't since this isn't a real SSH connection). s.send_kexinit() # Send the algorithms we support (except we don't since this isn't a real SSH connection).
packet_type, payload = s.read_packet(sshv) packet_type, payload = s.read_packet()
if packet_type < 0: if packet_type < 0:
try: try:
if len(payload) > 0: if len(payload) > 0:
@@ -1290,17 +1182,10 @@ def audit(out: OutputBuffer, aconf: AuditConf, sshv: Optional[int] = None, print
payload_txt = 'empty' payload_txt = 'empty'
except UnicodeDecodeError: except UnicodeDecodeError:
payload_txt = '"{}"'.format(repr(payload).lstrip('b')[1:-1]) payload_txt = '"{}"'.format(repr(payload).lstrip('b')[1:-1])
if payload_txt == 'Protocol major versions differ.':
if sshv == 2 and aconf.ssh1:
ret = audit(out, aconf, 1)
out.write()
return ret
err = '[exception] error reading packet ({})'.format(payload_txt) err = '[exception] error reading packet ({})'.format(payload_txt)
else: else:
err_pair = None err_pair = None
if sshv == 1 and packet_type != Protocol.SMSG_PUBLIC_KEY: if packet_type != Protocol.MSG_KEXINIT:
err_pair = ('SMSG_PUBLIC_KEY', Protocol.SMSG_PUBLIC_KEY)
elif sshv == 2 and packet_type != Protocol.MSG_KEXINIT:
err_pair = ('MSG_KEXINIT', Protocol.MSG_KEXINIT) err_pair = ('MSG_KEXINIT', Protocol.MSG_KEXINIT)
if err_pair is not None: if err_pair is not None:
fmt = '[exception] did not receive {0} ({1}), ' + \ fmt = '[exception] did not receive {0} ({1}), ' + \
@@ -1310,51 +1195,50 @@ def audit(out: OutputBuffer, aconf: AuditConf, sshv: Optional[int] = None, print
output(out, aconf, banner, header) output(out, aconf, banner, header)
out.fail(err) out.fail(err)
return exitcodes.CONNECTION_ERROR return exitcodes.CONNECTION_ERROR
if sshv == 1:
program_retval = output(out, aconf, banner, header, pkm=SSH1_PublicKeyMessage.parse(payload))
elif sshv == 2:
try:
kex = SSH2_Kex.parse(out, payload)
except Exception:
out.fail("Failed to parse server's kex. Stack trace:\n%s" % str(traceback.format_exc()))
return exitcodes.CONNECTION_ERROR
if aconf.dheat is not None: try:
DHEat(out, aconf, banner, kex).run() kex = SSH2_Kex.parse(out, payload)
return exitcodes.GOOD out.d(str(kex))
elif aconf.conn_rate_test_enabled: except Exception:
DHEat.dh_rate_test(out, aconf, kex, 0, 0, 0) out.fail("Failed to parse server's kex. Stack trace:\n%s" % str(traceback.format_exc()))
return exitcodes.GOOD return exitcodes.CONNECTION_ERROR
dh_rate_test_notes = "" if aconf.dheat is not None:
if aconf.client_audit is False: DHEat(out, aconf, banner, kex).run()
HostKeyTest.run(out, s, kex) return exitcodes.GOOD
if aconf.gex_test != '': elif aconf.conn_rate_test_enabled:
return run_gex_granular_modulus_size_test(out, s, kex, aconf) DHEat.dh_rate_test(out, aconf, kex, 0, 0, 0)
else: return exitcodes.GOOD
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.
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, dh_rate_test_notes=dh_rate_test_notes)
# This is a policy test.
elif (aconf.policy is not None) and (aconf.make_policy is False):
program_retval = exitcodes.GOOD if evaluate_policy(out, aconf, banner, s.client_host, kex=kex) else exitcodes.FAILURE
# A new policy should be made from this scan.
elif (aconf.policy is None) and (aconf.make_policy is True):
make_policy(aconf, banner, kex, s.client_host)
dh_rate_test_notes = ""
if aconf.client_audit is False:
HostKeyTest.run(out, s, kex)
if aconf.gex_test != '':
return run_gex_granular_modulus_size_test(out, s, kex, aconf)
else: else:
raise RuntimeError('Internal error while handling output: %r %r' % (aconf.policy is None, aconf.make_policy)) 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.
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, dh_rate_test_notes=dh_rate_test_notes)
# This is a policy test.
elif (aconf.policy is not None) and (aconf.make_policy is False):
program_retval = exitcodes.GOOD if evaluate_policy(out, aconf, banner, s.client_host, kex=kex) else exitcodes.FAILURE
# A new policy should be made from this scan.
elif (aconf.policy is None) and (aconf.make_policy is True):
make_policy(aconf, banner, kex, s.client_host)
else:
raise RuntimeError('Internal error while handling output: %r %r' % (aconf.policy is None, aconf.make_policy))
return program_retval return program_retval
@@ -1551,7 +1435,7 @@ def run_gex_granular_modulus_size_test(out: OutputBuffer, s: 'SSH_Socket', kex:
def main() -> int: def main() -> int:
out = OutputBuffer() out = OutputBuffer()
aconf = process_commandline(out, sys.argv[1:], usage) aconf = process_commandline(out, sys.argv[1:])
# If we're on Windows, but the colorama module could not be imported, print a warning if we're in verbose mode. # If we're on Windows, but the colorama module could not be imported, print a warning if we're in verbose mode.
if (sys.platform == 'win32') and ('colorama' not in sys.modules): if (sys.platform == 'win32') and ('colorama' not in sys.modules):
@@ -1584,10 +1468,10 @@ def main() -> int:
if aconf.json: if aconf.json:
print('[', end='') print('[', end='')
# Loop through each target in the list. # Loop through each target in the list. Entries can specify a port number to use, otherwise the value provided on the command line (--port=N) will be used by default (set to 22 if --port is not used).
target_servers = [] target_servers = []
for _, target in enumerate(aconf.target_list): for _, target in enumerate(aconf.target_list):
host, port = Utils.parse_host_and_port(target, default_port=22) host, port = Utils.parse_host_and_port(target, default_port=aconf.port)
target_servers.append((host, port)) target_servers.append((host, port))
# A ranked list of return codes. Those with higher indices will take precedence over lower ones. For example, if three servers are scanned, yielding WARNING, GOOD, and UNKNOWN_ERROR, the overall result will be UNKNOWN_ERROR, since its index is the highest. Errors have highest priority, followed by failures, then warnings. # A ranked list of return codes. Those with higher indices will take precedence over lower ones. For example, if three servers are scanned, yielding WARNING, GOOD, and UNKNOWN_ERROR, the overall result will be UNKNOWN_ERROR, since its index is the highest. Errors have highest priority, followed by failures, then warnings.
@@ -1621,7 +1505,6 @@ def main() -> int:
print(']') print(']')
# Send notification that this thread is exiting. This deletes the thread's local copy of the algorithm databases. # 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() SSH2_KexDB.thread_exit()
else: # Just a scan against a single target. else: # Just a scan against a single target.

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-2025 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
@@ -39,7 +39,6 @@ from ssh_audit.globals import SSH_HEADER
from ssh_audit.outputbuffer import OutputBuffer from ssh_audit.outputbuffer import OutputBuffer
from ssh_audit.protocol import Protocol from ssh_audit.protocol import Protocol
from ssh_audit.readbuf import ReadBuf from ssh_audit.readbuf import ReadBuf
from ssh_audit.ssh1 import SSH1
from ssh_audit.ssh2_kex import SSH2_Kex from ssh_audit.ssh2_kex import SSH2_Kex
from ssh_audit.ssh2_kexparty import SSH2_KexParty from ssh_audit.ssh2_kexparty import SSH2_KexParty
from ssh_audit.utils import Utils from ssh_audit.utils import Utils
@@ -108,7 +107,7 @@ class SSH_Socket(ReadBuf, WriteBuf):
s.listen() s.listen()
self.__sock_map[s.fileno()] = s self.__sock_map[s.fileno()] = s
except Exception as e: except Exception as e:
print("Warning: failed to listen on any IPv4 interfaces: %s" % str(e)) print("Warning: failed to listen on any IPv4 interfaces: %s" % str(e), file=sys.stderr)
try: try:
# Socket to listen on all IPv6 addresses. # Socket to listen on all IPv6 addresses.
@@ -119,11 +118,11 @@ class SSH_Socket(ReadBuf, WriteBuf):
s.listen() s.listen()
self.__sock_map[s.fileno()] = s self.__sock_map[s.fileno()] = s
except Exception as e: except Exception as e:
print("Warning: failed to listen on any IPv6 interfaces: %s" % str(e)) print("Warning: failed to listen on any IPv6 interfaces: %s" % str(e), file=sys.stderr)
# 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:
print("Error: failed to listen on any IPv4 and IPv6 interfaces!") print("Error: failed to listen on any IPv4 and IPv6 interfaces!", file=sys.stderr)
sys.exit(exitcodes.CONNECTION_ERROR) sys.exit(exitcodes.CONNECTION_ERROR)
# Wait for an incoming connection. If a timeout was explicitly # Wait for an incoming connection. If a timeout was explicitly
@@ -173,7 +172,7 @@ class SSH_Socket(ReadBuf, WriteBuf):
errm = 'cannot connect to {} port {}: {}'.format(*errt) errm = 'cannot connect to {} port {}: {}'.format(*errt)
return '[exception] {}'.format(errm) return '[exception] {}'.format(errm)
def get_banner(self, sshv: int = 2) -> Tuple[Optional['Banner'], List[str], Optional[str]]: def get_banner(self) -> Tuple[Optional['Banner'], List[str], Optional[str]]:
self.__outputbuffer.d('Getting banner...', write_now=True) self.__outputbuffer.d('Getting banner...', write_now=True)
if self.__sock is None: if self.__sock is None:
@@ -181,7 +180,7 @@ class SSH_Socket(ReadBuf, WriteBuf):
if self.__banner is not None: if self.__banner is not None:
return self.__banner, self.__header, None return self.__banner, self.__header, None
banner = SSH_HEADER.format('1.5' if sshv == 1 else '2.0') banner = SSH_HEADER.format('2.0')
if self.__state < self.SM_BANNER_SENT: if self.__state < self.SM_BANNER_SENT:
self.send_banner(banner) self.send_banner(banner)
@@ -254,47 +253,27 @@ class SSH_Socket(ReadBuf, WriteBuf):
if s < 0: if s < 0:
raise SSH_Socket.InsufficientReadException(e) raise SSH_Socket.InsufficientReadException(e)
def read_packet(self, sshv: int = 2) -> Tuple[int, bytes]: def read_packet(self) -> Tuple[int, bytes]:
try: try:
header = WriteBuf() header = WriteBuf()
self.ensure_read(4) self.ensure_read(4)
packet_length = self.read_int() packet_length = self.read_int()
header.write_int(packet_length) header.write_int(packet_length)
# XXX: validate length # XXX: validate length
if sshv == 1: self.ensure_read(1)
padding_length = 8 - packet_length % 8 padding_length = self.read_byte()
self.ensure_read(padding_length) header.write_byte(padding_length)
padding = self.read(padding_length) payload_length = packet_length - padding_length - 1
header.write(padding) check_size = 4 + 1 + payload_length + padding_length
payload_length = packet_length
check_size = padding_length + payload_length
else:
self.ensure_read(1)
padding_length = self.read_byte()
header.write_byte(padding_length)
payload_length = packet_length - padding_length - 1
check_size = 4 + 1 + payload_length + padding_length
if check_size % self.__block_size != 0: if check_size % self.__block_size != 0:
self.__outputbuffer.fail('[exception] invalid ssh packet (block size)').write() self.__outputbuffer.fail('[exception] invalid ssh packet (block size)').write()
sys.exit(exitcodes.CONNECTION_ERROR) sys.exit(exitcodes.CONNECTION_ERROR)
self.ensure_read(payload_length) self.ensure_read(payload_length)
if sshv == 1: payload = self.read(payload_length)
payload = self.read(payload_length - 4) header.write(payload)
header.write(payload)
crc = self.read_int()
header.write_int(crc)
else:
payload = self.read(payload_length)
header.write(payload)
packet_type = ord(payload[0:1]) packet_type = ord(payload[0:1])
if sshv == 1: self.ensure_read(padding_length)
rcrc = SSH1.crc32(padding + payload) _ = self.read(padding_length)
if crc != rcrc:
self.__outputbuffer.fail('[exception] packet checksum CRC32 mismatch.').write()
sys.exit(exitcodes.CONNECTION_ERROR)
else:
self.ensure_read(padding_length)
padding = self.read(padding_length)
payload = payload[1:] payload = payload[1:]
return packet_type, payload return packet_type, payload
except SSH_Socket.InsufficientReadException as ex: except SSH_Socket.InsufficientReadException as ex:

View File

@@ -129,7 +129,7 @@ class Utils:
return -1.0 return -1.0
@staticmethod @staticmethod
def parse_host_and_port(host_and_port: str, default_port: int = 0) -> Tuple[str, int]: def parse_host_and_port(host_and_port: str, default_port: int = 22) -> Tuple[str, int]:
'''Parses a string into a tuple of its host and port. The port is 0 if not specified.''' '''Parses a string into a tuple of its host and port. The port is 0 if not specified.'''
host = host_and_port host = host_and_port
port = default_port port = default_port

View File

@@ -1,170 +0,0 @@
"""
The MIT License (MIT)
Copyright (C) 2017-2020 Joe Testa (jtesta@positronsecurity.com)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
Permission is hereby granted, free of charge, to any person obtaining a copy
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.
"""
# pylint: disable=unused-import
from typing import Dict, List, Set, Sequence, Tuple, Iterable # noqa: F401
from typing import Callable, Optional, Union, Any # noqa: F401
class VersionVulnerabilityDB: # pylint: disable=too-few-public-methods
# Format: [starting_vuln_version, last_vuln_version, affected, CVE_ID, CVSSv2, description]
# affected: 1 = server, 2 = client, 4 = local
# Example: if it affects servers, both remote & local, then affected
# = 1. If it affects servers, but is a local issue only,
# then affected = 1 + 4 = 5.
CVE: Dict[str, List[List[Any]]] = {
'Dropbear SSH': [
['0.0', '2020.81', 2, 'CVE-2021-36369', 7.5, 'trivial authentication attack to bypass FIDO tokens and SSH-ASKPASS'],
['0.0', '2018.76', 1, 'CVE-2018-15599', 5.0, 'remote users may enumerate users on the system'],
['0.0', '2017.74', 5, 'CVE-2017-9079', 4.7, 'local users can read certain files as root'],
['0.0', '2017.74', 5, 'CVE-2017-9078', 9.3, 'local users may elevate privileges to root under certain conditions'],
['0.0', '2016.73', 5, 'CVE-2016-7409', 2.1, 'local users can read process memory under limited conditions'],
['0.0', '2016.73', 1, 'CVE-2016-7408', 6.5, 'remote users can execute arbitrary code'],
['0.0', '2016.73', 5, 'CVE-2016-7407', 10.0, 'local users can execute arbitrary code'],
['0.0', '2016.73', 1, 'CVE-2016-7406', 10.0, 'remote users can execute arbitrary code'],
['0.44', '2015.71', 1, 'CVE-2016-3116', 5.5, 'bypass command restrictions via xauth command injection'],
['0.28', '2013.58', 1, 'CVE-2013-4434', 5.0, 'discover valid usernames through different time delays'],
['0.28', '2013.58', 1, 'CVE-2013-4421', 5.0, 'cause DoS via a compressed packet (memory consumption)'],
['0.52', '2011.54', 1, 'CVE-2012-0920', 7.1, 'execute arbitrary code or bypass command restrictions'],
['0.40', '0.48.1', 1, 'CVE-2007-1099', 7.5, 'conduct a MitM attack (no warning for hostkey mismatch)'],
['0.28', '0.47', 1, 'CVE-2006-1206', 7.5, 'cause DoS via large number of connections (slot exhaustion)'],
['0.39', '0.47', 1, 'CVE-2006-0225', 4.6, 'execute arbitrary commands via scp with crafted filenames'],
['0.28', '0.46', 1, 'CVE-2005-4178', 6.5, 'execute arbitrary code via buffer overflow vulnerability'],
['0.28', '0.42', 1, 'CVE-2004-2486', 7.5, 'execute arbitrary code via DSS verification code']],
'libssh': [
['0.6.4', '0.6.4', 1, 'CVE-2018-10933', 6.4, 'authentication bypass'],
['0.7.0', '0.7.5', 1, 'CVE-2018-10933', 6.4, 'authentication bypass'],
['0.8.0', '0.8.3', 1, 'CVE-2018-10933', 6.4, 'authentication bypass'],
['0.1', '0.7.2', 1, 'CVE-2016-0739', 4.3, 'conduct a MitM attack (weakness in DH key generation)'],
['0.5.1', '0.6.4', 1, 'CVE-2015-3146', 5.0, 'cause DoS via kex packets (null pointer dereference)'],
['0.5.1', '0.6.3', 1, 'CVE-2014-8132', 5.0, 'cause DoS via kex init packet (dangling pointer)'],
['0.4.7', '0.6.2', 1, 'CVE-2014-0017', 1.9, 'leak data via PRNG state reuse on forking servers'],
['0.4.7', '0.5.3', 1, 'CVE-2013-0176', 4.3, 'cause DoS via kex packet (null pointer dereference)'],
['0.4.7', '0.5.2', 1, 'CVE-2012-6063', 7.5, 'cause DoS or execute arbitrary code via sftp (double free)'],
['0.4.7', '0.5.2', 1, 'CVE-2012-4562', 7.5, 'cause DoS or execute arbitrary code (overflow check)'],
['0.4.7', '0.5.2', 1, 'CVE-2012-4561', 5.0, 'cause DoS via unspecified vectors (invalid pointer)'],
['0.4.7', '0.5.2', 1, 'CVE-2012-4560', 7.5, 'cause DoS or execute arbitrary code (buffer overflow)'],
['0.4.7', '0.5.2', 1, 'CVE-2012-4559', 6.8, 'cause DoS or execute arbitrary code (double free)']],
'OpenSSH': [
['6.2', '8.7', 5, 'CVE-2021-41617', 7.0, 'privilege escalation via supplemental groups'],
['1.0', '8.8', 2, 'CVE-2021-36368', 3.7, 'trivial authentication attack to bypass FIDO tokens and SSH-ASKPASS'],
['8.2', '8.4', 2, 'CVE-2021-28041', 7.1, 'double free via ssh-agent'],
['1.0', '8.3', 5, 'CVE-2020-15778', 7.8, 'command injection via anomalous argument transfers'],
['5.7', '8.3', 2, 'CVE-2020-14145', 5.9, 'information leak via algorithm negotiation'],
['8.2', '8.2', 2, 'CVE-2020-12062', 7.5, 'arbitrary files overwrite via scp'],
['7.7', '8.0', 7, 'CVE-2019-16905', 7.8, 'memory corruption and local code execution via pre-authentication integer overflow'],
['1.0', '7.9', 2, 'CVE-2019-6111', 5.9, 'arbitrary files overwrite via scp'],
['1.0', '7.9', 2, 'CVE-2019-6110', 6.8, 'output manipulation'],
['1.0', '7.9', 2, 'CVE-2019-6109', 6.8, 'output manipulation'],
['1.0', '7.9', 2, 'CVE-2018-20685', 5.3, 'directory permissions modification via scp'],
['5.9', '7.8', 1, 'CVE-2018-15919', 5.3, 'username enumeration via GS2'],
['1.0', '7.7', 1, 'CVE-2018-15473', 5.3, 'enumerate usernames due to timing discrepancies'],
['1.2', '6.292', 1, 'CVE-2017-15906', 5.3, 'readonly bypass via sftp'],
['1.0', '8.7', 1, 'CVE-2016-20012', 5.3, 'enumerate usernames via challenge response'],
['7.2', '7.2p2', 1, 'CVE-2016-6515', 7.8, 'cause DoS via long password string (crypt CPU consumption)'],
['1.2.2', '7.2', 1, 'CVE-2016-3115', 5.5, 'bypass command restrictions via crafted X11 forwarding data'],
['5.4', '7.1', 1, 'CVE-2016-1907', 5.0, 'cause DoS via crafted network traffic (out of bounds read)'],
['5.4', '7.1p1', 2, 'CVE-2016-0778', 4.6, 'cause DoS via requesting many forwardings (heap based buffer overflow)'],
['5.0', '7.1p1', 2, 'CVE-2016-0777', 4.0, 'leak data via allowing transfer of entire buffer'],
['6.0', '7.2p2', 5, 'CVE-2015-8325', 7.2, 'privilege escalation via triggering crafted environment'],
['6.8', '6.9', 5, 'CVE-2015-6565', 7.2, 'cause DoS via writing to a device (terminal disruption)'],
['5.0', '6.9', 5, 'CVE-2015-6564', 6.9, 'privilege escalation via leveraging sshd uid'],
['5.0', '6.9', 5, 'CVE-2015-6563', 1.9, 'conduct impersonation attack'],
['6.9p1', '6.9p1', 1, 'CVE-2015-5600', 8.5, 'cause Dos or aid in conduct brute force attack (CPU consumption)'],
['6.0', '6.6', 1, 'CVE-2015-5352', 4.3, 'bypass access restrictions via a specific connection'],
['6.0', '6.6', 2, 'CVE-2014-2653', 5.8, 'bypass SSHFP DNS RR check via unacceptable host certificate'],
['5.0', '6.5', 1, 'CVE-2014-2532', 5.8, 'bypass environment restrictions via specific string before wildcard'],
['1.2', '6.4', 1, 'CVE-2014-1692', 7.5, 'cause DoS via triggering error condition (memory corruption)'],
['6.2', '6.3', 1, 'CVE-2013-4548', 6.0, 'bypass command restrictions via crafted packet data'],
['1.2', '5.6', 1, 'CVE-2012-0814', 3.5, 'leak data via debug messages'],
['1.2', '5.8', 1, 'CVE-2011-5000', 3.5, 'cause DoS via large value in certain length field (memory consumption)'],
['5.6', '5.7', 2, 'CVE-2011-0539', 5.0, 'leak data or conduct hash collision attack'],
['1.2', '6.1', 1, 'CVE-2010-5107', 5.0, 'cause DoS via large number of connections (slot exhaustion)'],
['1.2', '5.8', 1, 'CVE-2010-4755', 4.0, 'cause DoS via crafted glob expression (CPU and memory consumption)'],
['1.2', '5.6', 1, 'CVE-2010-4478', 7.5, 'bypass authentication check via crafted values'],
['4.3', '4.8', 1, 'CVE-2009-2904', 6.9, 'privilege escalation via hard links to setuid programs'],
['4.0', '5.1', 1, 'CVE-2008-5161', 2.6, 'recover plaintext data from ciphertext'],
['1.2', '4.6', 1, 'CVE-2008-4109', 5.0, 'cause DoS via multiple login attempts (slot exhaustion)'],
['1.2', '4.8', 1, 'CVE-2008-1657', 6.5, 'bypass command restrictions via modifying session file'],
['1.2.2', '4.9', 1, 'CVE-2008-1483', 6.9, 'hijack forwarded X11 connections'],
['4.0', '4.6', 1, 'CVE-2007-4752', 7.5, 'privilege escalation via causing an X client to be trusted'],
['4.3p2', '4.3p2', 1, 'CVE-2007-3102', 4.3, 'allow attacker to write random data to audit log'],
['1.2', '4.6', 1, 'CVE-2007-2243', 5.0, 'discover valid usernames through different responses'],
['4.4', '4.4', 1, 'CVE-2006-5794', 7.5, 'bypass authentication'],
['4.1', '4.1p1', 1, 'CVE-2006-5229', 2.6, 'discover valid usernames through different time delays'],
['1.2', '4.3p2', 1, 'CVE-2006-5052', 5.0, 'discover valid usernames through different responses'],
['1.2', '4.3p2', 1, 'CVE-2006-5051', 9.3, 'cause DoS or execute arbitrary code (double free)'],
['4.5', '4.5', 1, 'CVE-2006-4925', 5.0, 'cause DoS via invalid protocol sequence (crash)'],
['1.2', '4.3p2', 1, 'CVE-2006-4924', 7.8, 'cause DoS via crafted packet (CPU consumption)'],
['3.8.1p1', '3.8.1p1', 1, 'CVE-2006-0883', 5.0, 'cause DoS via connecting multiple times (client connection refusal)'],
['3.0', '4.2p1', 1, 'CVE-2006-0225', 4.6, 'execute arbitrary code'],
['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'],
['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 directory traversal'],
['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-0786', 10.0, 'privilege escalation via bypassing authentication'],
['1.0', '3.7.1', 1, 'CVE-2003-0695', 7.5, 'cause DoS or execute arbitrary code'],
['1.0', '3.7', 1, 'CVE-2003-0693', 10.0, 'execute arbitrary code'],
['3.0', '3.6.1p2', 1, 'CVE-2003-0386', 7.5, 'bypass address restrictions for connection'],
['3.1p1', '3.6.1p1', 1, 'CVE-2003-0190', 5.0, 'discover valid usernames through different time delays'],
['3.2.2', '3.2.2', 1, 'CVE-2002-0765', 7.5, 'bypass authentication'],
['1.2.2', '3.3p1', 1, 'CVE-2002-0640', 10.0, 'execute arbitrary code'],
['1.2.2', '3.3p1', 1, 'CVE-2002-0639', 10.0, 'execute arbitrary code'],
['2.1', '3.2', 1, 'CVE-2002-0575', 7.5, 'privilege escalation'],
['2.1', '3.0.2p1', 2, 'CVE-2002-0083', 10.0, 'privilege escalation'],
['3.0', '3.0p1', 1, 'CVE-2001-1507', 7.5, 'bypass authentication'],
['1.2.3', '3.0.1p1', 5, 'CVE-2001-0872', 7.2, 'privilege escalation via crafted environment variables'],
['1.2.3', '2.1.1', 1, 'CVE-2001-0361', 4.0, 'recover plaintext from ciphertext'],
['1.2', '2.1', 1, 'CVE-2000-0525', 10.0, 'execute arbitrary code (improper privileges)']],
'PuTTY': [
# info for CVE-2021-36367 - only PuTTY up to 0.71 is affected - see https://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/reject-trivial-auth.html
['0.0', '0.71', 2, 'CVE-2021-36367', 8.1, 'trivial authentication attack to bypass FIDO tokens and SSH-ASKPASS'],
['0.0', '0.74', 2, 'CVE-2021-33500', 5.0, 'denial of service of the complete windows desktop'],
['0.68', '0.73', 2, 'CVE-2020-14002', 4.3, 'Observable Discrepancy which allows man-in-the-middle attackers to target initial connection attempts'],
['0.54', '0.73', 2, 'CVE-2020-XXXX', 5.0, 'out of bounds memory read'],
['0.0', '0.72', 2, 'CVE-2019-17069', 5.0, 'potential DOS by remote SSHv1 server'],
['0.71', '0.72', 2, 'CVE-2019-17068', 5.0, 'xterm bracketed paste mode command injection'],
['0.52', '0.72', 2, 'CVE-2019-17067', 7.5, 'port rebinding weakness in port forward tunnel handling'],
['0.0', '0.71', 2, 'CVE-2019-XXXX', 5.0, 'undefined vulnerability in obsolete SSHv1 protocol handling'],
['0.0', '0.71', 6, 'CVE-2019-XXXX', 5.0, 'local privilege escalation in Pageant'],
['0.0', '0.70', 2, 'CVE-2019-9898', 7.5, 'potential recycling of random numbers'],
['0.0', '0.70', 2, 'CVE-2019-9897', 5.0, 'multiple denial-of-service issues from writing to the terminal'],
['0.0', '0.70', 6, 'CVE-2019-9896', 4.6, 'local application hijacking through malicious Windows help file'],
['0.0', '0.70', 2, 'CVE-2019-9894', 6.4, 'buffer overflow in RSA key exchange'],
['0.0', '0.69', 6, 'CVE-2016-6167', 4.4, 'local application hijacking through untrusted DLL loading'],
['0.0', '0.67', 2, 'CVE-2017-6542', 7.5, 'buffer overflow in UNIX client that can result in privilege escalation or denial-of-service'],
['0.0', '0.66', 2, 'CVE-2016-2563', 7.5, 'buffer overflow in SCP command-line utility'],
['0.0', '0.65', 2, 'CVE-2015-5309', 4.3, 'integer overflow in terminal-handling code'],
]
}
TXT: Dict[str, List[List[Any]]] = {
'Dropbear SSH': [
['0.28', '0.34', 1, 'remote root exploit', 'remote format string buffer overflow exploit (exploit-db#387)']],
'libssh': [
['0.3.3', '0.3.3', 1, 'null pointer check', 'missing null pointer check in "crypt_set_algorithms_server"'],
['0.3.3', '0.3.3', 1, 'integer overflow', 'integer overflow in "buffer_get_data"'],
['0.3.3', '0.3.3', 3, 'heap overflow', 'heap overflow in "packet_decrypt"']]
}

View File

@@ -1,4 +1,4 @@
.TH SSH-AUDIT 1 "April 18, 2024" .TH SSH-AUDIT 1 "August 17, 2025"
.SH NAME .SH NAME
\fBssh-audit\fP \- SSH server & client configuration auditor \fBssh-audit\fP \- SSH server & client configuration auditor
.SH SYNOPSIS .SH SYNOPSIS
@@ -16,16 +16,6 @@ See <https://www.ssh\-audit.com/> for official hardening guides for common platf
.br .br
Print short summary of options. Print short summary of options.
.TP
.B -1, \-\-ssh1
.br
Only perform an audit using SSH protocol version 1.
.TP
.B -2, \-\-ssh2
.br
Only perform an audit using SSH protocol version 2.
.TP .TP
.B -4, \-\-ipv4 .B -4, \-\-ipv4
.br .br
@@ -61,6 +51,11 @@ Enable debug output.
.br .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. 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
.B \-\-get\-hardening\-guide=<platform_name>
.br
Retrieves the hardening guide for the specified platform name (use \-\-list\-hardening\-guides to see list of available guides).
.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
@@ -101,10 +96,15 @@ Output results in JSON format. Specify twice (-jj) to enable indent printing (u
.br .br
Specify the minimum output level. Default is info. Specify the minimum output level. Default is info.
.TP
.B \-\-list-hardening-guides
.br
List all official, built-in hardening guides for common systems. Their full names can then be passed to \-\-get\-hardening\-guide. Add \-v to this option to view hardening guide change logs and prior versions.
.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. Add \-v to \-L to view policy change logs. 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,...>
@@ -149,7 +149,7 @@ The timeout, in seconds, for creating connections and reading data from the sock
.TP .TP
.B -T, \-\-targets=<hosts.txt> .B -T, \-\-targets=<hosts.txt>
.br .br
A file containing a list of target hosts. Each line must have one host, in the format of HOST[:PORT]. Use --threads to control concurrent scans. A file containing a list of target hosts. Each line must have one host, in the format of HOST[:PORT]. Use -p/--port to set the default port for all hosts. Use --threads to control concurrent scans.
.TP .TP
.B \-\-threads=<threads> .B \-\-threads=<threads>

View File

@@ -78,6 +78,26 @@
} }
], ],
"fingerprints": [ "fingerprints": [
{
"hash": "jdUfqoGCDOY1drQcoqIJm/pEix2r09hqwOs9E9GimZQ",
"hash_alg": "SHA256",
"hostkey": "ecdsa-sha2-nistp256"
},
{
"hash": "98:27:f3:12:20:f6:23:6d:1a:00:2a:6c:71:7c:1e:6b",
"hash_alg": "MD5",
"hostkey": "ecdsa-sha2-nistp256"
},
{
"hash": "NBzry0uMAX8BRsn4mv9CHpeivMOdwzGFEKrf6Hg7tIQ",
"hash_alg": "SHA256",
"hostkey": "ssh-dss"
},
{
"hash": "16:60:9e:54:d7:1e:b3:0d:97:60:12:ad:fe:83:a2:40",
"hash_alg": "MD5",
"hostkey": "ssh-dss"
},
{ {
"hash": "CDfAU12pjQS7/91kg7gYacza0U/6PDbE04Ic3IpYxkM", "hash": "CDfAU12pjQS7/91kg7gYacza0U/6PDbE04Ic3IpYxkM",
"hash_alg": "SHA256", "hash_alg": "SHA256",
@@ -96,6 +116,9 @@
"info": [ "info": [
"default key exchange from OpenSSH 7.4 to 8.9", "default key exchange from OpenSSH 7.4 to 8.9",
"available since OpenSSH 7.4, Dropbear SSH 2018.76" "available since OpenSSH 7.4, Dropbear SSH 2018.76"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -105,6 +128,9 @@
"info": [ "info": [
"default key exchange from OpenSSH 6.5 to 7.3", "default key exchange from OpenSSH 6.5 to 7.3",
"available since OpenSSH 6.4, Dropbear SSH 2013.62" "available since OpenSSH 6.4, Dropbear SSH 2013.62"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -116,6 +142,9 @@
], ],
"info": [ "info": [
"available since OpenSSH 5.7, Dropbear SSH 2013.62" "available since OpenSSH 5.7, Dropbear SSH 2013.62"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -127,6 +156,9 @@
], ],
"info": [ "info": [
"available since OpenSSH 5.7, Dropbear SSH 2013.62" "available since OpenSSH 5.7, Dropbear SSH 2013.62"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -138,6 +170,9 @@
], ],
"info": [ "info": [
"available since OpenSSH 5.7, Dropbear SSH 2013.62" "available since OpenSSH 5.7, Dropbear SSH 2013.62"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -148,7 +183,8 @@
"available since OpenSSH 7.3, Dropbear SSH 2016.73" "available since OpenSSH 7.3, Dropbear SSH 2016.73"
], ],
"warn": [ "warn": [
"2048-bit modulus only provides 112-bits of symmetric strength" "2048-bit modulus only provides 112-bits of symmetric strength",
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -162,7 +198,8 @@
"available since OpenSSH 3.9, Dropbear SSH 0.53" "available since OpenSSH 3.9, Dropbear SSH 0.53"
], ],
"warn": [ "warn": [
"2048-bit modulus only provides 112-bits of symmetric strength" "2048-bit modulus only provides 112-bits of symmetric strength",
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -171,6 +208,9 @@
"notes": { "notes": {
"info": [ "info": [
"available since Dropbear SSH 2013.57" "available since Dropbear SSH 2013.57"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
} }
@@ -329,12 +369,6 @@
"name": "twofish256-ctr", "name": "twofish256-ctr",
"notes": "" "notes": ""
} }
],
"kex": [
{
"name": "diffie-hellman-group16-sha512",
"notes": ""
}
] ]
} }
}, },
@@ -351,9 +385,21 @@
} }
], ],
"kex": [ "kex": [
{
"name": "curve25519-sha256",
"notes": ""
},
{
"name": "curve25519-sha256@libssh.org",
"notes": ""
},
{ {
"name": "diffie-hellman-group14-sha256", "name": "diffie-hellman-group14-sha256",
"notes": "" "notes": ""
},
{
"name": "kexguess2@matt.ucc.asn.au",
"notes": ""
} }
], ],
"mac": [ "mac": [

View File

@@ -5,22 +5,30 @@
(gen) compression: enabled (zlib@openssh.com) (gen) compression: enabled (zlib@openssh.com)
# key exchange algorithms # key exchange algorithms
(kex) curve25519-sha256 -- [info] available since OpenSSH 7.4, Dropbear SSH 2018.76 (kex) curve25519-sha256 -- [warn] does not provide protection against post-quantum attacks
 `- [info] default key exchange from OpenSSH 7.4 to 8.9 `- [info] available since OpenSSH 7.4, Dropbear SSH 2018.76
(kex) curve25519-sha256@libssh.org -- [info] available since OpenSSH 6.4, Dropbear SSH 2013.62 `- [info] default key exchange from OpenSSH 7.4 to 8.9
 `- [info] default key exchange from OpenSSH 6.5 to 7.3 (kex) curve25519-sha256@libssh.org -- [warn] does not provide protection against post-quantum attacks
`- [info] available since OpenSSH 6.4, Dropbear SSH 2013.62
`- [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
 `- [warn] does not provide protection against post-quantum attacks
`- [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
 `- [warn] does not provide protection against post-quantum attacks
`- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62 `- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62
(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
 `- [warn] does not provide protection against post-quantum attacks
`- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62 `- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62
(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
 `- [warn] does not provide protection against post-quantum attacks
`- [info] available since OpenSSH 7.3, Dropbear SSH 2016.73 `- [info] available since OpenSSH 7.3, Dropbear SSH 2016.73
(kex) diffie-hellman-group14-sha1 -- [fail] using broken SHA-1 hash algorithm (kex) diffie-hellman-group14-sha1 -- [fail] using broken SHA-1 hash algorithm
 `- [warn] 2048-bit modulus only provides 112-bits of symmetric strength  `- [warn] 2048-bit modulus only provides 112-bits of symmetric strength
 `- [warn] does not provide protection against post-quantum attacks
`- [info] available since OpenSSH 3.9, Dropbear SSH 0.53 `- [info] available since OpenSSH 3.9, Dropbear SSH 0.53
(kex) kexguess2@matt.ucc.asn.au -- [info] available since Dropbear SSH 2013.57 (kex) kexguess2@matt.ucc.asn.au -- [warn] does not provide protection against post-quantum attacks
`- [info] available since Dropbear SSH 2013.57
# host-key algorithms # host-key algorithms
(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
@@ -74,14 +82,16 @@
(rec) -hmac-sha1-96 -- mac algorithm to remove  (rec) -hmac-sha1-96 -- mac algorithm to remove 
(rec) -ssh-dss -- key algorithm to remove  (rec) -ssh-dss -- key algorithm to remove 
(rec) -ssh-rsa -- key algorithm to remove  (rec) -ssh-rsa -- key algorithm to remove 
(rec) +diffie-hellman-group16-sha512 -- kex algorithm to append 
(rec) +twofish128-ctr -- enc algorithm to append  (rec) +twofish128-ctr -- enc algorithm to append 
(rec) +twofish256-ctr -- enc algorithm to append  (rec) +twofish256-ctr -- enc algorithm to append 
(rec) -aes128-cbc -- enc algorithm to remove  (rec) -aes128-cbc -- enc algorithm to remove 
(rec) -aes256-cbc -- enc algorithm to remove  (rec) -aes256-cbc -- enc algorithm to remove 
(rec) -curve25519-sha256 -- kex algorithm to remove 
(rec) -curve25519-sha256@libssh.org -- kex 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) -kexguess2@matt.ucc.asn.au -- kex algorithm to remove 
# additional info # additional info
(nfo) For hardening guides on common OSes, please see: <https://www.ssh-audit.com/hardening_guides.html> (nfo) For hardening guides on common OSes, a built-in list can be viewed with --list-hardening-guides, or an online list can be found at: <https://www.ssh-audit.com/hardening_guides.html>

View File

@@ -10,118 +10,7 @@
"none", "none",
"zlib" "zlib"
], ],
"cves": [ "cves": [],
{
"cvssv2": 7.8,
"description": "command injection via anomalous argument transfers",
"name": "CVE-2020-15778"
},
{
"cvssv2": 5.3,
"description": "enumerate usernames due to timing discrepancies",
"name": "CVE-2018-15473"
},
{
"cvssv2": 5.3,
"description": "readonly bypass via sftp",
"name": "CVE-2017-15906"
},
{
"cvssv2": 5.3,
"description": "enumerate usernames via challenge response",
"name": "CVE-2016-20012"
},
{
"cvssv2": 5.5,
"description": "bypass command restrictions via crafted X11 forwarding data",
"name": "CVE-2016-3115"
},
{
"cvssv2": 7.5,
"description": "cause DoS via triggering error condition (memory corruption)",
"name": "CVE-2014-1692"
},
{
"cvssv2": 3.5,
"description": "leak data via debug messages",
"name": "CVE-2012-0814"
},
{
"cvssv2": 3.5,
"description": "cause DoS via large value in certain length field (memory consumption)",
"name": "CVE-2011-5000"
},
{
"cvssv2": 5.0,
"description": "cause DoS via large number of connections (slot exhaustion)",
"name": "CVE-2010-5107"
},
{
"cvssv2": 4.0,
"description": "cause DoS via crafted glob expression (CPU and memory consumption)",
"name": "CVE-2010-4755"
},
{
"cvssv2": 7.5,
"description": "bypass authentication check via crafted values",
"name": "CVE-2010-4478"
},
{
"cvssv2": 2.6,
"description": "recover plaintext data from ciphertext",
"name": "CVE-2008-5161"
},
{
"cvssv2": 5.0,
"description": "cause DoS via multiple login attempts (slot exhaustion)",
"name": "CVE-2008-4109"
},
{
"cvssv2": 6.5,
"description": "bypass command restrictions via modifying session file",
"name": "CVE-2008-1657"
},
{
"cvssv2": 6.9,
"description": "hijack forwarded X11 connections",
"name": "CVE-2008-1483"
},
{
"cvssv2": 7.5,
"description": "privilege escalation via causing an X client to be trusted",
"name": "CVE-2007-4752"
},
{
"cvssv2": 5.0,
"description": "discover valid usernames through different responses",
"name": "CVE-2007-2243"
},
{
"cvssv2": 5.0,
"description": "discover valid usernames through different responses",
"name": "CVE-2006-5052"
},
{
"cvssv2": 9.3,
"description": "cause DoS or execute arbitrary code (double free)",
"name": "CVE-2006-5051"
},
{
"cvssv2": 7.8,
"description": "cause DoS via crafted packet (CPU consumption)",
"name": "CVE-2006-4924"
},
{
"cvssv2": 4.6,
"description": "execute arbitrary code",
"name": "CVE-2006-0225"
},
{
"cvssv2": 5.0,
"description": "leak data about authentication credentials",
"name": "CVE-2005-2798"
}
],
"enc": [ "enc": [
{ {
"algorithm": "aes128-cbc", "algorithm": "aes128-cbc",
@@ -253,6 +142,16 @@
} }
], ],
"fingerprints": [ "fingerprints": [
{
"hash": "sqDDYhzYz7YIQeFDc0WF8SeXtrEz+iwsV7d/FdIgztM",
"hash_alg": "SHA256",
"hostkey": "ssh-dss"
},
{
"hash": "5c:de:62:f0:60:c8:93:13:87:71:78:95:56:3f:61:51",
"hash_alg": "MD5",
"hostkey": "ssh-dss"
},
{ {
"hash": "YZ457EBcJTSxRKI3yXRgtAj3PBf5B9/F36b1SVooml4", "hash": "YZ457EBcJTSxRKI3yXRgtAj3PBf5B9/F36b1SVooml4",
"hash_alg": "SHA256", "hash_alg": "SHA256",
@@ -274,6 +173,9 @@
], ],
"info": [ "info": [
"available since OpenSSH 2.3.0" "available since OpenSSH 2.3.0"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -287,7 +189,8 @@
"available since OpenSSH 3.9, Dropbear SSH 0.53" "available since OpenSSH 3.9, Dropbear SSH 0.53"
], ],
"warn": [ "warn": [
"2048-bit modulus only provides 112-bits of symmetric strength" "2048-bit modulus only provides 112-bits of symmetric strength",
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -302,6 +205,9 @@
"info": [ "info": [
"removed in OpenSSH 6.9: https://www.openssh.com/txt/release-6.9", "removed in OpenSSH 6.9: https://www.openssh.com/txt/release-6.9",
"available since OpenSSH 2.3.0, Dropbear SSH 0.28" "available since OpenSSH 2.3.0, Dropbear SSH 0.28"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
} }

View File

@@ -6,39 +6,20 @@
(gen) compression: enabled (zlib) (gen) compression: enabled (zlib)
# security # security
(cve) CVE-2020-15778 -- (CVSSv2: 7.8) command injection via anomalous argument transfers
(cve) CVE-2018-15473 -- (CVSSv2: 5.3) enumerate usernames due to timing discrepancies
(cve) CVE-2017-15906 -- (CVSSv2: 5.3) readonly bypass via sftp
(cve) CVE-2016-20012 -- (CVSSv2: 5.3) enumerate usernames via challenge response
(cve) CVE-2016-3115 -- (CVSSv2: 5.5) bypass command restrictions via crafted X11 forwarding data
(cve) CVE-2014-1692 -- (CVSSv2: 7.5) cause DoS via triggering error condition (memory corruption)
(cve) CVE-2012-0814 -- (CVSSv2: 3.5) leak data via debug messages
(cve) CVE-2011-5000 -- (CVSSv2: 3.5) cause DoS via large value in certain length field (memory consumption)
(cve) CVE-2010-5107 -- (CVSSv2: 5.0) cause DoS via large number of connections (slot exhaustion)
(cve) CVE-2010-4755 -- (CVSSv2: 4.0) cause DoS via crafted glob expression (CPU and memory consumption)
(cve) CVE-2010-4478 -- (CVSSv2: 7.5) bypass authentication check via crafted values
(cve) CVE-2008-5161 -- (CVSSv2: 2.6) recover plaintext data from ciphertext
(cve) CVE-2008-4109 -- (CVSSv2: 5.0) cause DoS via multiple login attempts (slot exhaustion)
(cve) CVE-2008-1657 -- (CVSSv2: 6.5) bypass command restrictions via modifying session file
(cve) CVE-2008-1483 -- (CVSSv2: 6.9) hijack forwarded X11 connections
(cve) CVE-2007-4752 -- (CVSSv2: 7.5) privilege escalation via causing an X client to be trusted
(cve) CVE-2007-2243 -- (CVSSv2: 5.0) discover valid usernames through different responses
(cve) CVE-2006-5052 -- (CVSSv2: 5.0) discover valid usernames through different responses
(cve) CVE-2006-5051 -- (CVSSv2: 9.3) cause DoS or execute arbitrary code (double free)
(cve) CVE-2006-4924 -- (CVSSv2: 7.8) cause DoS via crafted packet (CPU consumption)
(cve) CVE-2006-0225 -- (CVSSv2: 4.6) execute arbitrary code
(cve) CVE-2005-2798 -- (CVSSv2: 5.0) leak data about authentication credentials
(sec) SSH v1 enabled -- SSH v1 can be exploited to recover plaintext passwords (sec) SSH v1 enabled -- SSH v1 can be exploited to recover plaintext passwords
# key exchange algorithms # key exchange algorithms
(kex) diffie-hellman-group-exchange-sha1 (1024-bit) -- [fail] using small 1024-bit modulus (kex) diffie-hellman-group-exchange-sha1 (1024-bit) -- [fail] using small 1024-bit modulus
 `- [warn] does not provide protection against post-quantum attacks
`- [info] available since OpenSSH 2.3.0 `- [info] available since OpenSSH 2.3.0
(kex) diffie-hellman-group14-sha1 -- [fail] using broken SHA-1 hash algorithm (kex) diffie-hellman-group14-sha1 -- [fail] using broken SHA-1 hash algorithm
 `- [warn] 2048-bit modulus only provides 112-bits of symmetric strength  `- [warn] 2048-bit modulus only provides 112-bits of symmetric strength
 `- [warn] does not provide protection against post-quantum attacks
`- [info] available since OpenSSH 3.9, Dropbear SSH 0.53 `- [info] available since OpenSSH 3.9, Dropbear SSH 0.53
(kex) diffie-hellman-group1-sha1 -- [fail] using small 1024-bit modulus (kex) diffie-hellman-group1-sha1 -- [fail] using small 1024-bit modulus
 `- [fail] vulnerable to the Logjam attack: https://en.wikipedia.org/wiki/Logjam_(computer_security)  `- [fail] vulnerable to the Logjam attack: https://en.wikipedia.org/wiki/Logjam_(computer_security)
 `- [fail] using broken SHA-1 hash algorithm  `- [fail] using broken SHA-1 hash algorithm
 `- [warn] does not provide protection against post-quantum attacks
`- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28 `- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28
`- [info] removed in OpenSSH 6.9: https://www.openssh.com/txt/release-6.9 `- [info] removed in OpenSSH 6.9: https://www.openssh.com/txt/release-6.9
@@ -126,5 +107,5 @@
(rec) -aes256-cbc -- enc algorithm to remove  (rec) -aes256-cbc -- enc algorithm to remove 
# additional info # additional info
(nfo) For hardening guides on common OSes, please see: <https://www.ssh-audit.com/hardening_guides.html> (nfo) For hardening guides on common OSes, a built-in list can be viewed with --list-hardening-guides, or an online list can be found at: <https://www.ssh-audit.com/hardening_guides.html>

View File

@@ -2,5 +2,7 @@
"errors": [], "errors": [],
"host": "localhost", "host": "localhost",
"passed": true, "passed": true,
"policy": "Docker policy: test1 (version 1)" "policy": "Docker policy: test1 (version 1)",
"port": 2222,
"warnings": []
} }

View File

@@ -27,5 +27,7 @@
], ],
"host": "localhost", "host": "localhost",
"passed": false, "passed": false,
"policy": "Docker poliicy: test10 (version 1)" "policy": "Docker poliicy: test10 (version 1)",
"port": 2222,
"warnings": []
} }

View File

@@ -19,5 +19,7 @@
], ],
"host": "localhost", "host": "localhost",
"passed": false, "passed": false,
"policy": "Docker policy: test2 (version 1)" "policy": "Docker policy: test2 (version 1)",
"port": 2222,
"warnings": []
} }

View File

@@ -18,5 +18,7 @@
], ],
"host": "localhost", "host": "localhost",
"passed": false, "passed": false,
"policy": "Docker policy: test3 (version 1)" "policy": "Docker policy: test3 (version 1)",
"port": 2222,
"warnings": []
} }

View File

@@ -28,5 +28,7 @@
], ],
"host": "localhost", "host": "localhost",
"passed": false, "passed": false,
"policy": "Docker policy: test4 (version 1)" "policy": "Docker policy: test4 (version 1)",
"port": 2222,
"warnings": []
} }

View File

@@ -27,5 +27,7 @@
], ],
"host": "localhost", "host": "localhost",
"passed": false, "passed": false,
"policy": "Docker policy: test5 (version 1)" "policy": "Docker policy: test5 (version 1)",
"port": 2222,
"warnings": []
} }

View File

@@ -2,5 +2,7 @@
"errors": [], "errors": [],
"host": "localhost", "host": "localhost",
"passed": true, "passed": true,
"policy": "Docker poliicy: test7 (version 1)" "policy": "Docker poliicy: test7 (version 1)",
"port": 2222,
"warnings": []
} }

View File

@@ -15,5 +15,7 @@
], ],
"host": "localhost", "host": "localhost",
"passed": false, "passed": false,
"policy": "Docker poliicy: test8 (version 1)" "policy": "Docker poliicy: test8 (version 1)",
"port": 2222,
"warnings": []
} }

View File

@@ -15,5 +15,7 @@
], ],
"host": "localhost", "host": "localhost",
"passed": false, "passed": false,
"policy": "Docker poliicy: test9 (version 1)" "policy": "Docker poliicy: test9 (version 1)",
"port": 2222,
"warnings": []
} }

View File

@@ -10,83 +10,7 @@
"none", "none",
"zlib@openssh.com" "zlib@openssh.com"
], ],
"cves": [ "cves": [],
{
"cvssv2": 7.8,
"description": "command injection via anomalous argument transfers",
"name": "CVE-2020-15778"
},
{
"cvssv2": 5.3,
"description": "enumerate usernames due to timing discrepancies",
"name": "CVE-2018-15473"
},
{
"cvssv2": 5.3,
"description": "readonly bypass via sftp",
"name": "CVE-2017-15906"
},
{
"cvssv2": 5.3,
"description": "enumerate usernames via challenge response",
"name": "CVE-2016-20012"
},
{
"cvssv2": 5.5,
"description": "bypass command restrictions via crafted X11 forwarding data",
"name": "CVE-2016-3115"
},
{
"cvssv2": 5.0,
"description": "cause DoS via crafted network traffic (out of bounds read)",
"name": "CVE-2016-1907"
},
{
"cvssv2": 6.9,
"description": "privilege escalation via leveraging sshd uid",
"name": "CVE-2015-6564"
},
{
"cvssv2": 1.9,
"description": "conduct impersonation attack",
"name": "CVE-2015-6563"
},
{
"cvssv2": 5.8,
"description": "bypass environment restrictions via specific string before wildcard",
"name": "CVE-2014-2532"
},
{
"cvssv2": 7.5,
"description": "cause DoS via triggering error condition (memory corruption)",
"name": "CVE-2014-1692"
},
{
"cvssv2": 3.5,
"description": "leak data via debug messages",
"name": "CVE-2012-0814"
},
{
"cvssv2": 3.5,
"description": "cause DoS via large value in certain length field (memory consumption)",
"name": "CVE-2011-5000"
},
{
"cvssv2": 5.0,
"description": "cause DoS via large number of connections (slot exhaustion)",
"name": "CVE-2010-5107"
},
{
"cvssv2": 4.0,
"description": "cause DoS via crafted glob expression (CPU and memory consumption)",
"name": "CVE-2010-4755"
},
{
"cvssv2": 7.5,
"description": "bypass authentication check via crafted values",
"name": "CVE-2010-4478"
}
],
"enc": [ "enc": [
{ {
"algorithm": "aes128-ctr", "algorithm": "aes128-ctr",
@@ -240,6 +164,16 @@
} }
], ],
"fingerprints": [ "fingerprints": [
{
"hash": "sqDDYhzYz7YIQeFDc0WF8SeXtrEz+iwsV7d/FdIgztM",
"hash_alg": "SHA256",
"hostkey": "ssh-dss"
},
{
"hash": "5c:de:62:f0:60:c8:93:13:87:71:78:95:56:3f:61:51",
"hash_alg": "MD5",
"hostkey": "ssh-dss"
},
{ {
"hash": "YZ457EBcJTSxRKI3yXRgtAj3PBf5B9/F36b1SVooml4", "hash": "YZ457EBcJTSxRKI3yXRgtAj3PBf5B9/F36b1SVooml4",
"hash_alg": "SHA256", "hash_alg": "SHA256",
@@ -261,6 +195,9 @@
], ],
"info": [ "info": [
"available since OpenSSH 4.4" "available since OpenSSH 4.4"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -273,6 +210,9 @@
], ],
"info": [ "info": [
"available since OpenSSH 2.3.0" "available since OpenSSH 2.3.0"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -286,7 +226,8 @@
"available since OpenSSH 3.9, Dropbear SSH 0.53" "available since OpenSSH 3.9, Dropbear SSH 0.53"
], ],
"warn": [ "warn": [
"2048-bit modulus only provides 112-bits of symmetric strength" "2048-bit modulus only provides 112-bits of symmetric strength",
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -301,6 +242,9 @@
"info": [ "info": [
"removed in OpenSSH 6.9: https://www.openssh.com/txt/release-6.9", "removed in OpenSSH 6.9: https://www.openssh.com/txt/release-6.9",
"available since OpenSSH 2.3.0, Dropbear SSH 0.28" "available since OpenSSH 2.3.0, Dropbear SSH 0.28"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
} }

View File

@@ -4,34 +4,21 @@
(gen) compatibility: OpenSSH 4.7-6.6, Dropbear SSH 0.53+ (some functionality from 0.52) (gen) compatibility: OpenSSH 4.7-6.6, Dropbear SSH 0.53+ (some functionality from 0.52)
(gen) compression: enabled (zlib@openssh.com) (gen) compression: enabled (zlib@openssh.com)
# security
(cve) CVE-2020-15778 -- (CVSSv2: 7.8) command injection via anomalous argument transfers
(cve) CVE-2018-15473 -- (CVSSv2: 5.3) enumerate usernames due to timing discrepancies
(cve) CVE-2017-15906 -- (CVSSv2: 5.3) readonly bypass via sftp
(cve) CVE-2016-20012 -- (CVSSv2: 5.3) enumerate usernames via challenge response
(cve) CVE-2016-3115 -- (CVSSv2: 5.5) bypass command restrictions via crafted X11 forwarding data
(cve) CVE-2016-1907 -- (CVSSv2: 5.0) cause DoS via crafted network traffic (out of bounds read)
(cve) CVE-2015-6564 -- (CVSSv2: 6.9) privilege escalation via leveraging sshd uid
(cve) CVE-2015-6563 -- (CVSSv2: 1.9) conduct impersonation attack
(cve) CVE-2014-2532 -- (CVSSv2: 5.8) bypass environment restrictions via specific string before wildcard
(cve) CVE-2014-1692 -- (CVSSv2: 7.5) cause DoS via triggering error condition (memory corruption)
(cve) CVE-2012-0814 -- (CVSSv2: 3.5) leak data via debug messages
(cve) CVE-2011-5000 -- (CVSSv2: 3.5) cause DoS via large value in certain length field (memory consumption)
(cve) CVE-2010-5107 -- (CVSSv2: 5.0) cause DoS via large number of connections (slot exhaustion)
(cve) CVE-2010-4755 -- (CVSSv2: 4.0) cause DoS via crafted glob expression (CPU and memory consumption)
(cve) CVE-2010-4478 -- (CVSSv2: 7.5) bypass authentication check via crafted values
# key exchange algorithms # key exchange algorithms
(kex) diffie-hellman-group-exchange-sha256 (1024-bit) -- [fail] using small 1024-bit modulus (kex) diffie-hellman-group-exchange-sha256 (1024-bit) -- [fail] using small 1024-bit modulus
 `- [warn] does not provide protection against post-quantum attacks
`- [info] available since OpenSSH 4.4 `- [info] available since OpenSSH 4.4
(kex) diffie-hellman-group-exchange-sha1 (1024-bit) -- [fail] using small 1024-bit modulus (kex) diffie-hellman-group-exchange-sha1 (1024-bit) -- [fail] using small 1024-bit modulus
 `- [warn] does not provide protection against post-quantum attacks
`- [info] available since OpenSSH 2.3.0 `- [info] available since OpenSSH 2.3.0
(kex) diffie-hellman-group14-sha1 -- [fail] using broken SHA-1 hash algorithm (kex) diffie-hellman-group14-sha1 -- [fail] using broken SHA-1 hash algorithm
 `- [warn] 2048-bit modulus only provides 112-bits of symmetric strength  `- [warn] 2048-bit modulus only provides 112-bits of symmetric strength
 `- [warn] does not provide protection against post-quantum attacks
`- [info] available since OpenSSH 3.9, Dropbear SSH 0.53 `- [info] available since OpenSSH 3.9, Dropbear SSH 0.53
(kex) diffie-hellman-group1-sha1 -- [fail] using small 1024-bit modulus (kex) diffie-hellman-group1-sha1 -- [fail] using small 1024-bit modulus
 `- [fail] vulnerable to the Logjam attack: https://en.wikipedia.org/wiki/Logjam_(computer_security)  `- [fail] vulnerable to the Logjam attack: https://en.wikipedia.org/wiki/Logjam_(computer_security)
 `- [fail] using broken SHA-1 hash algorithm  `- [fail] using broken SHA-1 hash algorithm
 `- [warn] does not provide protection against post-quantum attacks
`- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28 `- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28
`- [info] removed in OpenSSH 6.9: https://www.openssh.com/txt/release-6.9 `- [info] removed in OpenSSH 6.9: https://www.openssh.com/txt/release-6.9
@@ -130,5 +117,5 @@
(rec) -umac-64@openssh.com -- mac algorithm to remove  (rec) -umac-64@openssh.com -- mac algorithm to remove 
# additional info # additional info
(nfo) For hardening guides on common OSes, please see: <https://www.ssh-audit.com/hardening_guides.html> (nfo) For hardening guides on common OSes, a built-in list can be viewed with --list-hardening-guides, or an online list can be found at: <https://www.ssh-audit.com/hardening_guides.html>

View File

@@ -10,83 +10,7 @@
"none", "none",
"zlib@openssh.com" "zlib@openssh.com"
], ],
"cves": [ "cves": [],
{
"cvssv2": 7.8,
"description": "command injection via anomalous argument transfers",
"name": "CVE-2020-15778"
},
{
"cvssv2": 5.3,
"description": "enumerate usernames due to timing discrepancies",
"name": "CVE-2018-15473"
},
{
"cvssv2": 5.3,
"description": "readonly bypass via sftp",
"name": "CVE-2017-15906"
},
{
"cvssv2": 5.3,
"description": "enumerate usernames via challenge response",
"name": "CVE-2016-20012"
},
{
"cvssv2": 5.5,
"description": "bypass command restrictions via crafted X11 forwarding data",
"name": "CVE-2016-3115"
},
{
"cvssv2": 5.0,
"description": "cause DoS via crafted network traffic (out of bounds read)",
"name": "CVE-2016-1907"
},
{
"cvssv2": 6.9,
"description": "privilege escalation via leveraging sshd uid",
"name": "CVE-2015-6564"
},
{
"cvssv2": 1.9,
"description": "conduct impersonation attack",
"name": "CVE-2015-6563"
},
{
"cvssv2": 5.8,
"description": "bypass environment restrictions via specific string before wildcard",
"name": "CVE-2014-2532"
},
{
"cvssv2": 7.5,
"description": "cause DoS via triggering error condition (memory corruption)",
"name": "CVE-2014-1692"
},
{
"cvssv2": 3.5,
"description": "leak data via debug messages",
"name": "CVE-2012-0814"
},
{
"cvssv2": 3.5,
"description": "cause DoS via large value in certain length field (memory consumption)",
"name": "CVE-2011-5000"
},
{
"cvssv2": 5.0,
"description": "cause DoS via large number of connections (slot exhaustion)",
"name": "CVE-2010-5107"
},
{
"cvssv2": 4.0,
"description": "cause DoS via crafted glob expression (CPU and memory consumption)",
"name": "CVE-2010-4755"
},
{
"cvssv2": 7.5,
"description": "bypass authentication check via crafted values",
"name": "CVE-2010-4478"
}
],
"enc": [ "enc": [
{ {
"algorithm": "aes128-ctr", "algorithm": "aes128-ctr",
@@ -261,6 +185,9 @@
], ],
"info": [ "info": [
"available since OpenSSH 4.4" "available since OpenSSH 4.4"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -273,6 +200,9 @@
], ],
"info": [ "info": [
"available since OpenSSH 2.3.0" "available since OpenSSH 2.3.0"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -286,7 +216,8 @@
"available since OpenSSH 3.9, Dropbear SSH 0.53" "available since OpenSSH 3.9, Dropbear SSH 0.53"
], ],
"warn": [ "warn": [
"2048-bit modulus only provides 112-bits of symmetric strength" "2048-bit modulus only provides 112-bits of symmetric strength",
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -301,6 +232,9 @@
"info": [ "info": [
"removed in OpenSSH 6.9: https://www.openssh.com/txt/release-6.9", "removed in OpenSSH 6.9: https://www.openssh.com/txt/release-6.9",
"available since OpenSSH 2.3.0, Dropbear SSH 0.28" "available since OpenSSH 2.3.0, Dropbear SSH 0.28"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
} }

View File

@@ -4,34 +4,21 @@
(gen) compatibility: OpenSSH 5.6-6.6, Dropbear SSH 0.53+ (some functionality from 0.52) (gen) compatibility: OpenSSH 5.6-6.6, Dropbear SSH 0.53+ (some functionality from 0.52)
(gen) compression: enabled (zlib@openssh.com) (gen) compression: enabled (zlib@openssh.com)
# security
(cve) CVE-2020-15778 -- (CVSSv2: 7.8) command injection via anomalous argument transfers
(cve) CVE-2018-15473 -- (CVSSv2: 5.3) enumerate usernames due to timing discrepancies
(cve) CVE-2017-15906 -- (CVSSv2: 5.3) readonly bypass via sftp
(cve) CVE-2016-20012 -- (CVSSv2: 5.3) enumerate usernames via challenge response
(cve) CVE-2016-3115 -- (CVSSv2: 5.5) bypass command restrictions via crafted X11 forwarding data
(cve) CVE-2016-1907 -- (CVSSv2: 5.0) cause DoS via crafted network traffic (out of bounds read)
(cve) CVE-2015-6564 -- (CVSSv2: 6.9) privilege escalation via leveraging sshd uid
(cve) CVE-2015-6563 -- (CVSSv2: 1.9) conduct impersonation attack
(cve) CVE-2014-2532 -- (CVSSv2: 5.8) bypass environment restrictions via specific string before wildcard
(cve) CVE-2014-1692 -- (CVSSv2: 7.5) cause DoS via triggering error condition (memory corruption)
(cve) CVE-2012-0814 -- (CVSSv2: 3.5) leak data via debug messages
(cve) CVE-2011-5000 -- (CVSSv2: 3.5) cause DoS via large value in certain length field (memory consumption)
(cve) CVE-2010-5107 -- (CVSSv2: 5.0) cause DoS via large number of connections (slot exhaustion)
(cve) CVE-2010-4755 -- (CVSSv2: 4.0) cause DoS via crafted glob expression (CPU and memory consumption)
(cve) CVE-2010-4478 -- (CVSSv2: 7.5) bypass authentication check via crafted values
# key exchange algorithms # key exchange algorithms
(kex) diffie-hellman-group-exchange-sha256 (1024-bit) -- [fail] using small 1024-bit modulus (kex) diffie-hellman-group-exchange-sha256 (1024-bit) -- [fail] using small 1024-bit modulus
 `- [warn] does not provide protection against post-quantum attacks
`- [info] available since OpenSSH 4.4 `- [info] available since OpenSSH 4.4
(kex) diffie-hellman-group-exchange-sha1 (1024-bit) -- [fail] using small 1024-bit modulus (kex) diffie-hellman-group-exchange-sha1 (1024-bit) -- [fail] using small 1024-bit modulus
 `- [warn] does not provide protection against post-quantum attacks
`- [info] available since OpenSSH 2.3.0 `- [info] available since OpenSSH 2.3.0
(kex) diffie-hellman-group14-sha1 -- [fail] using broken SHA-1 hash algorithm (kex) diffie-hellman-group14-sha1 -- [fail] using broken SHA-1 hash algorithm
 `- [warn] 2048-bit modulus only provides 112-bits of symmetric strength  `- [warn] 2048-bit modulus only provides 112-bits of symmetric strength
 `- [warn] does not provide protection against post-quantum attacks
`- [info] available since OpenSSH 3.9, Dropbear SSH 0.53 `- [info] available since OpenSSH 3.9, Dropbear SSH 0.53
(kex) diffie-hellman-group1-sha1 -- [fail] using small 1024-bit modulus (kex) diffie-hellman-group1-sha1 -- [fail] using small 1024-bit modulus
 `- [fail] vulnerable to the Logjam attack: https://en.wikipedia.org/wiki/Logjam_(computer_security)  `- [fail] vulnerable to the Logjam attack: https://en.wikipedia.org/wiki/Logjam_(computer_security)
 `- [fail] using broken SHA-1 hash algorithm  `- [fail] using broken SHA-1 hash algorithm
 `- [warn] does not provide protection against post-quantum attacks
`- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28 `- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28
`- [info] removed in OpenSSH 6.9: https://www.openssh.com/txt/release-6.9 `- [info] removed in OpenSSH 6.9: https://www.openssh.com/txt/release-6.9
@@ -131,5 +118,5 @@
(rec) -umac-64@openssh.com -- mac algorithm to remove  (rec) -umac-64@openssh.com -- mac algorithm to remove 
# additional info # additional info
(nfo) For hardening guides on common OSes, please see: <https://www.ssh-audit.com/hardening_guides.html> (nfo) For hardening guides on common OSes, a built-in list can be viewed with --list-hardening-guides, or an online list can be found at: <https://www.ssh-audit.com/hardening_guides.html>

View File

@@ -10,83 +10,7 @@
"none", "none",
"zlib@openssh.com" "zlib@openssh.com"
], ],
"cves": [ "cves": [],
{
"cvssv2": 7.8,
"description": "command injection via anomalous argument transfers",
"name": "CVE-2020-15778"
},
{
"cvssv2": 5.3,
"description": "enumerate usernames due to timing discrepancies",
"name": "CVE-2018-15473"
},
{
"cvssv2": 5.3,
"description": "readonly bypass via sftp",
"name": "CVE-2017-15906"
},
{
"cvssv2": 5.3,
"description": "enumerate usernames via challenge response",
"name": "CVE-2016-20012"
},
{
"cvssv2": 5.5,
"description": "bypass command restrictions via crafted X11 forwarding data",
"name": "CVE-2016-3115"
},
{
"cvssv2": 5.0,
"description": "cause DoS via crafted network traffic (out of bounds read)",
"name": "CVE-2016-1907"
},
{
"cvssv2": 6.9,
"description": "privilege escalation via leveraging sshd uid",
"name": "CVE-2015-6564"
},
{
"cvssv2": 1.9,
"description": "conduct impersonation attack",
"name": "CVE-2015-6563"
},
{
"cvssv2": 5.8,
"description": "bypass environment restrictions via specific string before wildcard",
"name": "CVE-2014-2532"
},
{
"cvssv2": 7.5,
"description": "cause DoS via triggering error condition (memory corruption)",
"name": "CVE-2014-1692"
},
{
"cvssv2": 3.5,
"description": "leak data via debug messages",
"name": "CVE-2012-0814"
},
{
"cvssv2": 3.5,
"description": "cause DoS via large value in certain length field (memory consumption)",
"name": "CVE-2011-5000"
},
{
"cvssv2": 5.0,
"description": "cause DoS via large number of connections (slot exhaustion)",
"name": "CVE-2010-5107"
},
{
"cvssv2": 4.0,
"description": "cause DoS via crafted glob expression (CPU and memory consumption)",
"name": "CVE-2010-4755"
},
{
"cvssv2": 7.5,
"description": "bypass authentication check via crafted values",
"name": "CVE-2010-4478"
}
],
"enc": [ "enc": [
{ {
"algorithm": "aes128-ctr", "algorithm": "aes128-ctr",
@@ -261,6 +185,9 @@
], ],
"info": [ "info": [
"available since OpenSSH 4.4" "available since OpenSSH 4.4"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -273,6 +200,9 @@
], ],
"info": [ "info": [
"available since OpenSSH 2.3.0" "available since OpenSSH 2.3.0"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -286,7 +216,8 @@
"available since OpenSSH 3.9, Dropbear SSH 0.53" "available since OpenSSH 3.9, Dropbear SSH 0.53"
], ],
"warn": [ "warn": [
"2048-bit modulus only provides 112-bits of symmetric strength" "2048-bit modulus only provides 112-bits of symmetric strength",
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -301,6 +232,9 @@
"info": [ "info": [
"removed in OpenSSH 6.9: https://www.openssh.com/txt/release-6.9", "removed in OpenSSH 6.9: https://www.openssh.com/txt/release-6.9",
"available since OpenSSH 2.3.0, Dropbear SSH 0.28" "available since OpenSSH 2.3.0, Dropbear SSH 0.28"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
} }

View File

@@ -4,34 +4,21 @@
(gen) compatibility: OpenSSH 5.6-6.6, Dropbear SSH 0.53+ (some functionality from 0.52) (gen) compatibility: OpenSSH 5.6-6.6, Dropbear SSH 0.53+ (some functionality from 0.52)
(gen) compression: enabled (zlib@openssh.com) (gen) compression: enabled (zlib@openssh.com)
# security
(cve) CVE-2020-15778 -- (CVSSv2: 7.8) command injection via anomalous argument transfers
(cve) CVE-2018-15473 -- (CVSSv2: 5.3) enumerate usernames due to timing discrepancies
(cve) CVE-2017-15906 -- (CVSSv2: 5.3) readonly bypass via sftp
(cve) CVE-2016-20012 -- (CVSSv2: 5.3) enumerate usernames via challenge response
(cve) CVE-2016-3115 -- (CVSSv2: 5.5) bypass command restrictions via crafted X11 forwarding data
(cve) CVE-2016-1907 -- (CVSSv2: 5.0) cause DoS via crafted network traffic (out of bounds read)
(cve) CVE-2015-6564 -- (CVSSv2: 6.9) privilege escalation via leveraging sshd uid
(cve) CVE-2015-6563 -- (CVSSv2: 1.9) conduct impersonation attack
(cve) CVE-2014-2532 -- (CVSSv2: 5.8) bypass environment restrictions via specific string before wildcard
(cve) CVE-2014-1692 -- (CVSSv2: 7.5) cause DoS via triggering error condition (memory corruption)
(cve) CVE-2012-0814 -- (CVSSv2: 3.5) leak data via debug messages
(cve) CVE-2011-5000 -- (CVSSv2: 3.5) cause DoS via large value in certain length field (memory consumption)
(cve) CVE-2010-5107 -- (CVSSv2: 5.0) cause DoS via large number of connections (slot exhaustion)
(cve) CVE-2010-4755 -- (CVSSv2: 4.0) cause DoS via crafted glob expression (CPU and memory consumption)
(cve) CVE-2010-4478 -- (CVSSv2: 7.5) bypass authentication check via crafted values
# key exchange algorithms # key exchange algorithms
(kex) diffie-hellman-group-exchange-sha256 (1024-bit) -- [fail] using small 1024-bit modulus (kex) diffie-hellman-group-exchange-sha256 (1024-bit) -- [fail] using small 1024-bit modulus
 `- [warn] does not provide protection against post-quantum attacks
`- [info] available since OpenSSH 4.4 `- [info] available since OpenSSH 4.4
(kex) diffie-hellman-group-exchange-sha1 (1024-bit) -- [fail] using small 1024-bit modulus (kex) diffie-hellman-group-exchange-sha1 (1024-bit) -- [fail] using small 1024-bit modulus
 `- [warn] does not provide protection against post-quantum attacks
`- [info] available since OpenSSH 2.3.0 `- [info] available since OpenSSH 2.3.0
(kex) diffie-hellman-group14-sha1 -- [fail] using broken SHA-1 hash algorithm (kex) diffie-hellman-group14-sha1 -- [fail] using broken SHA-1 hash algorithm
 `- [warn] 2048-bit modulus only provides 112-bits of symmetric strength  `- [warn] 2048-bit modulus only provides 112-bits of symmetric strength
 `- [warn] does not provide protection against post-quantum attacks
`- [info] available since OpenSSH 3.9, Dropbear SSH 0.53 `- [info] available since OpenSSH 3.9, Dropbear SSH 0.53
(kex) diffie-hellman-group1-sha1 -- [fail] using small 1024-bit modulus (kex) diffie-hellman-group1-sha1 -- [fail] using small 1024-bit modulus
 `- [fail] vulnerable to the Logjam attack: https://en.wikipedia.org/wiki/Logjam_(computer_security)  `- [fail] vulnerable to the Logjam attack: https://en.wikipedia.org/wiki/Logjam_(computer_security)
 `- [fail] using broken SHA-1 hash algorithm  `- [fail] using broken SHA-1 hash algorithm
 `- [warn] does not provide protection against post-quantum attacks
`- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28 `- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28
`- [info] removed in OpenSSH 6.9: https://www.openssh.com/txt/release-6.9 `- [info] removed in OpenSSH 6.9: https://www.openssh.com/txt/release-6.9
@@ -130,5 +117,5 @@
(rec) -umac-64@openssh.com -- mac algorithm to remove  (rec) -umac-64@openssh.com -- mac algorithm to remove 
# additional info # additional info
(nfo) For hardening guides on common OSes, please see: <https://www.ssh-audit.com/hardening_guides.html> (nfo) For hardening guides on common OSes, a built-in list can be viewed with --list-hardening-guides, or an online list can be found at: <https://www.ssh-audit.com/hardening_guides.html>

View File

@@ -10,83 +10,7 @@
"none", "none",
"zlib@openssh.com" "zlib@openssh.com"
], ],
"cves": [ "cves": [],
{
"cvssv2": 7.8,
"description": "command injection via anomalous argument transfers",
"name": "CVE-2020-15778"
},
{
"cvssv2": 5.3,
"description": "enumerate usernames due to timing discrepancies",
"name": "CVE-2018-15473"
},
{
"cvssv2": 5.3,
"description": "readonly bypass via sftp",
"name": "CVE-2017-15906"
},
{
"cvssv2": 5.3,
"description": "enumerate usernames via challenge response",
"name": "CVE-2016-20012"
},
{
"cvssv2": 5.5,
"description": "bypass command restrictions via crafted X11 forwarding data",
"name": "CVE-2016-3115"
},
{
"cvssv2": 5.0,
"description": "cause DoS via crafted network traffic (out of bounds read)",
"name": "CVE-2016-1907"
},
{
"cvssv2": 6.9,
"description": "privilege escalation via leveraging sshd uid",
"name": "CVE-2015-6564"
},
{
"cvssv2": 1.9,
"description": "conduct impersonation attack",
"name": "CVE-2015-6563"
},
{
"cvssv2": 5.8,
"description": "bypass environment restrictions via specific string before wildcard",
"name": "CVE-2014-2532"
},
{
"cvssv2": 7.5,
"description": "cause DoS via triggering error condition (memory corruption)",
"name": "CVE-2014-1692"
},
{
"cvssv2": 3.5,
"description": "leak data via debug messages",
"name": "CVE-2012-0814"
},
{
"cvssv2": 3.5,
"description": "cause DoS via large value in certain length field (memory consumption)",
"name": "CVE-2011-5000"
},
{
"cvssv2": 5.0,
"description": "cause DoS via large number of connections (slot exhaustion)",
"name": "CVE-2010-5107"
},
{
"cvssv2": 4.0,
"description": "cause DoS via crafted glob expression (CPU and memory consumption)",
"name": "CVE-2010-4755"
},
{
"cvssv2": 7.5,
"description": "bypass authentication check via crafted values",
"name": "CVE-2010-4478"
}
],
"enc": [ "enc": [
{ {
"algorithm": "aes128-ctr", "algorithm": "aes128-ctr",
@@ -261,6 +185,9 @@
], ],
"info": [ "info": [
"available since OpenSSH 4.4" "available since OpenSSH 4.4"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -273,6 +200,9 @@
], ],
"info": [ "info": [
"available since OpenSSH 2.3.0" "available since OpenSSH 2.3.0"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -286,7 +216,8 @@
"available since OpenSSH 3.9, Dropbear SSH 0.53" "available since OpenSSH 3.9, Dropbear SSH 0.53"
], ],
"warn": [ "warn": [
"2048-bit modulus only provides 112-bits of symmetric strength" "2048-bit modulus only provides 112-bits of symmetric strength",
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -301,6 +232,9 @@
"info": [ "info": [
"removed in OpenSSH 6.9: https://www.openssh.com/txt/release-6.9", "removed in OpenSSH 6.9: https://www.openssh.com/txt/release-6.9",
"available since OpenSSH 2.3.0, Dropbear SSH 0.28" "available since OpenSSH 2.3.0, Dropbear SSH 0.28"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
} }

View File

@@ -4,34 +4,21 @@
(gen) compatibility: OpenSSH 5.6-6.6, Dropbear SSH 0.53+ (some functionality from 0.52) (gen) compatibility: OpenSSH 5.6-6.6, Dropbear SSH 0.53+ (some functionality from 0.52)
(gen) compression: enabled (zlib@openssh.com) (gen) compression: enabled (zlib@openssh.com)
# security
(cve) CVE-2020-15778 -- (CVSSv2: 7.8) command injection via anomalous argument transfers
(cve) CVE-2018-15473 -- (CVSSv2: 5.3) enumerate usernames due to timing discrepancies
(cve) CVE-2017-15906 -- (CVSSv2: 5.3) readonly bypass via sftp
(cve) CVE-2016-20012 -- (CVSSv2: 5.3) enumerate usernames via challenge response
(cve) CVE-2016-3115 -- (CVSSv2: 5.5) bypass command restrictions via crafted X11 forwarding data
(cve) CVE-2016-1907 -- (CVSSv2: 5.0) cause DoS via crafted network traffic (out of bounds read)
(cve) CVE-2015-6564 -- (CVSSv2: 6.9) privilege escalation via leveraging sshd uid
(cve) CVE-2015-6563 -- (CVSSv2: 1.9) conduct impersonation attack
(cve) CVE-2014-2532 -- (CVSSv2: 5.8) bypass environment restrictions via specific string before wildcard
(cve) CVE-2014-1692 -- (CVSSv2: 7.5) cause DoS via triggering error condition (memory corruption)
(cve) CVE-2012-0814 -- (CVSSv2: 3.5) leak data via debug messages
(cve) CVE-2011-5000 -- (CVSSv2: 3.5) cause DoS via large value in certain length field (memory consumption)
(cve) CVE-2010-5107 -- (CVSSv2: 5.0) cause DoS via large number of connections (slot exhaustion)
(cve) CVE-2010-4755 -- (CVSSv2: 4.0) cause DoS via crafted glob expression (CPU and memory consumption)
(cve) CVE-2010-4478 -- (CVSSv2: 7.5) bypass authentication check via crafted values
# key exchange algorithms # key exchange algorithms
(kex) diffie-hellman-group-exchange-sha256 (1024-bit) -- [fail] using small 1024-bit modulus (kex) diffie-hellman-group-exchange-sha256 (1024-bit) -- [fail] using small 1024-bit modulus
 `- [warn] does not provide protection against post-quantum attacks
`- [info] available since OpenSSH 4.4 `- [info] available since OpenSSH 4.4
(kex) diffie-hellman-group-exchange-sha1 (1024-bit) -- [fail] using small 1024-bit modulus (kex) diffie-hellman-group-exchange-sha1 (1024-bit) -- [fail] using small 1024-bit modulus
 `- [warn] does not provide protection against post-quantum attacks
`- [info] available since OpenSSH 2.3.0 `- [info] available since OpenSSH 2.3.0
(kex) diffie-hellman-group14-sha1 -- [fail] using broken SHA-1 hash algorithm (kex) diffie-hellman-group14-sha1 -- [fail] using broken SHA-1 hash algorithm
 `- [warn] 2048-bit modulus only provides 112-bits of symmetric strength  `- [warn] 2048-bit modulus only provides 112-bits of symmetric strength
 `- [warn] does not provide protection against post-quantum attacks
`- [info] available since OpenSSH 3.9, Dropbear SSH 0.53 `- [info] available since OpenSSH 3.9, Dropbear SSH 0.53
(kex) diffie-hellman-group1-sha1 -- [fail] using small 1024-bit modulus (kex) diffie-hellman-group1-sha1 -- [fail] using small 1024-bit modulus
 `- [fail] vulnerable to the Logjam attack: https://en.wikipedia.org/wiki/Logjam_(computer_security)  `- [fail] vulnerable to the Logjam attack: https://en.wikipedia.org/wiki/Logjam_(computer_security)
 `- [fail] using broken SHA-1 hash algorithm  `- [fail] using broken SHA-1 hash algorithm
 `- [warn] does not provide protection against post-quantum attacks
`- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28 `- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28
`- [info] removed in OpenSSH 6.9: https://www.openssh.com/txt/release-6.9 `- [info] removed in OpenSSH 6.9: https://www.openssh.com/txt/release-6.9
@@ -129,5 +116,5 @@
(rec) -umac-64@openssh.com -- mac algorithm to remove  (rec) -umac-64@openssh.com -- mac algorithm to remove 
# additional info # additional info
(nfo) For hardening guides on common OSes, please see: <https://www.ssh-audit.com/hardening_guides.html> (nfo) For hardening guides on common OSes, a built-in list can be viewed with --list-hardening-guides, or an online list can be found at: <https://www.ssh-audit.com/hardening_guides.html>

View File

@@ -10,83 +10,7 @@
"none", "none",
"zlib@openssh.com" "zlib@openssh.com"
], ],
"cves": [ "cves": [],
{
"cvssv2": 7.8,
"description": "command injection via anomalous argument transfers",
"name": "CVE-2020-15778"
},
{
"cvssv2": 5.3,
"description": "enumerate usernames due to timing discrepancies",
"name": "CVE-2018-15473"
},
{
"cvssv2": 5.3,
"description": "readonly bypass via sftp",
"name": "CVE-2017-15906"
},
{
"cvssv2": 5.3,
"description": "enumerate usernames via challenge response",
"name": "CVE-2016-20012"
},
{
"cvssv2": 5.5,
"description": "bypass command restrictions via crafted X11 forwarding data",
"name": "CVE-2016-3115"
},
{
"cvssv2": 5.0,
"description": "cause DoS via crafted network traffic (out of bounds read)",
"name": "CVE-2016-1907"
},
{
"cvssv2": 6.9,
"description": "privilege escalation via leveraging sshd uid",
"name": "CVE-2015-6564"
},
{
"cvssv2": 1.9,
"description": "conduct impersonation attack",
"name": "CVE-2015-6563"
},
{
"cvssv2": 5.8,
"description": "bypass environment restrictions via specific string before wildcard",
"name": "CVE-2014-2532"
},
{
"cvssv2": 7.5,
"description": "cause DoS via triggering error condition (memory corruption)",
"name": "CVE-2014-1692"
},
{
"cvssv2": 3.5,
"description": "leak data via debug messages",
"name": "CVE-2012-0814"
},
{
"cvssv2": 3.5,
"description": "cause DoS via large value in certain length field (memory consumption)",
"name": "CVE-2011-5000"
},
{
"cvssv2": 5.0,
"description": "cause DoS via large number of connections (slot exhaustion)",
"name": "CVE-2010-5107"
},
{
"cvssv2": 4.0,
"description": "cause DoS via crafted glob expression (CPU and memory consumption)",
"name": "CVE-2010-4755"
},
{
"cvssv2": 7.5,
"description": "bypass authentication check via crafted values",
"name": "CVE-2010-4478"
}
],
"enc": [ "enc": [
{ {
"algorithm": "aes128-ctr", "algorithm": "aes128-ctr",
@@ -261,6 +185,9 @@
], ],
"info": [ "info": [
"available since OpenSSH 4.4" "available since OpenSSH 4.4"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -273,6 +200,9 @@
], ],
"info": [ "info": [
"available since OpenSSH 2.3.0" "available since OpenSSH 2.3.0"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -286,7 +216,8 @@
"available since OpenSSH 3.9, Dropbear SSH 0.53" "available since OpenSSH 3.9, Dropbear SSH 0.53"
], ],
"warn": [ "warn": [
"2048-bit modulus only provides 112-bits of symmetric strength" "2048-bit modulus only provides 112-bits of symmetric strength",
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -301,6 +232,9 @@
"info": [ "info": [
"removed in OpenSSH 6.9: https://www.openssh.com/txt/release-6.9", "removed in OpenSSH 6.9: https://www.openssh.com/txt/release-6.9",
"available since OpenSSH 2.3.0, Dropbear SSH 0.28" "available since OpenSSH 2.3.0, Dropbear SSH 0.28"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
} }

View File

@@ -4,34 +4,21 @@
(gen) compatibility: OpenSSH 5.6-6.6, Dropbear SSH 0.53+ (some functionality from 0.52) (gen) compatibility: OpenSSH 5.6-6.6, Dropbear SSH 0.53+ (some functionality from 0.52)
(gen) compression: enabled (zlib@openssh.com) (gen) compression: enabled (zlib@openssh.com)
# security
(cve) CVE-2020-15778 -- (CVSSv2: 7.8) command injection via anomalous argument transfers
(cve) CVE-2018-15473 -- (CVSSv2: 5.3) enumerate usernames due to timing discrepancies
(cve) CVE-2017-15906 -- (CVSSv2: 5.3) readonly bypass via sftp
(cve) CVE-2016-20012 -- (CVSSv2: 5.3) enumerate usernames via challenge response
(cve) CVE-2016-3115 -- (CVSSv2: 5.5) bypass command restrictions via crafted X11 forwarding data
(cve) CVE-2016-1907 -- (CVSSv2: 5.0) cause DoS via crafted network traffic (out of bounds read)
(cve) CVE-2015-6564 -- (CVSSv2: 6.9) privilege escalation via leveraging sshd uid
(cve) CVE-2015-6563 -- (CVSSv2: 1.9) conduct impersonation attack
(cve) CVE-2014-2532 -- (CVSSv2: 5.8) bypass environment restrictions via specific string before wildcard
(cve) CVE-2014-1692 -- (CVSSv2: 7.5) cause DoS via triggering error condition (memory corruption)
(cve) CVE-2012-0814 -- (CVSSv2: 3.5) leak data via debug messages
(cve) CVE-2011-5000 -- (CVSSv2: 3.5) cause DoS via large value in certain length field (memory consumption)
(cve) CVE-2010-5107 -- (CVSSv2: 5.0) cause DoS via large number of connections (slot exhaustion)
(cve) CVE-2010-4755 -- (CVSSv2: 4.0) cause DoS via crafted glob expression (CPU and memory consumption)
(cve) CVE-2010-4478 -- (CVSSv2: 7.5) bypass authentication check via crafted values
# key exchange algorithms # key exchange algorithms
(kex) diffie-hellman-group-exchange-sha256 (1024-bit) -- [fail] using small 1024-bit modulus (kex) diffie-hellman-group-exchange-sha256 (1024-bit) -- [fail] using small 1024-bit modulus
 `- [warn] does not provide protection against post-quantum attacks
`- [info] available since OpenSSH 4.4 `- [info] available since OpenSSH 4.4
(kex) diffie-hellman-group-exchange-sha1 (1024-bit) -- [fail] using small 1024-bit modulus (kex) diffie-hellman-group-exchange-sha1 (1024-bit) -- [fail] using small 1024-bit modulus
 `- [warn] does not provide protection against post-quantum attacks
`- [info] available since OpenSSH 2.3.0 `- [info] available since OpenSSH 2.3.0
(kex) diffie-hellman-group14-sha1 -- [fail] using broken SHA-1 hash algorithm (kex) diffie-hellman-group14-sha1 -- [fail] using broken SHA-1 hash algorithm
 `- [warn] 2048-bit modulus only provides 112-bits of symmetric strength  `- [warn] 2048-bit modulus only provides 112-bits of symmetric strength
 `- [warn] does not provide protection against post-quantum attacks
`- [info] available since OpenSSH 3.9, Dropbear SSH 0.53 `- [info] available since OpenSSH 3.9, Dropbear SSH 0.53
(kex) diffie-hellman-group1-sha1 -- [fail] using small 1024-bit modulus (kex) diffie-hellman-group1-sha1 -- [fail] using small 1024-bit modulus
 `- [fail] vulnerable to the Logjam attack: https://en.wikipedia.org/wiki/Logjam_(computer_security)  `- [fail] vulnerable to the Logjam attack: https://en.wikipedia.org/wiki/Logjam_(computer_security)
 `- [fail] using broken SHA-1 hash algorithm  `- [fail] using broken SHA-1 hash algorithm
 `- [warn] does not provide protection against post-quantum attacks
`- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28 `- [info] available since OpenSSH 2.3.0, Dropbear SSH 0.28
`- [info] removed in OpenSSH 6.9: https://www.openssh.com/txt/release-6.9 `- [info] removed in OpenSSH 6.9: https://www.openssh.com/txt/release-6.9
@@ -128,5 +115,5 @@
(rec) -umac-64@openssh.com -- mac algorithm to remove  (rec) -umac-64@openssh.com -- mac algorithm to remove 
# additional info # additional info
(nfo) For hardening guides on common OSes, please see: <https://www.ssh-audit.com/hardening_guides.html> (nfo) For hardening guides on common OSes, a built-in list can be viewed with --list-hardening-guides, or an online list can be found at: <https://www.ssh-audit.com/hardening_guides.html>

View File

@@ -39,5 +39,7 @@
], ],
"host": "localhost", "host": "localhost",
"passed": false, "passed": false,
"policy": "Hardened OpenSSH Server v8.0 (version 4)" "policy": "Hardened OpenSSH Server v8.0 (version 4)",
"port": 2222,
"warnings": []
} }

View File

@@ -62,5 +62,7 @@
], ],
"host": "localhost", "host": "localhost",
"passed": false, "passed": false,
"policy": "Hardened OpenSSH Server v8.0 (version 4)" "policy": "Hardened OpenSSH Server v8.0 (version 4)",
"port": 2222,
"warnings": []
} }

View File

@@ -2,5 +2,7 @@
"errors": [], "errors": [],
"host": "localhost", "host": "localhost",
"passed": true, "passed": true,
"policy": "Docker policy: test11 (version 1)" "policy": "Docker policy: test11 (version 1)",
"port": 2222,
"warnings": []
} }

View File

@@ -39,5 +39,7 @@
], ],
"host": "localhost", "host": "localhost",
"passed": false, "passed": false,
"policy": "Docker policy: test12 (version 1)" "policy": "Docker policy: test12 (version 1)",
"port": 2222,
"warnings": []
} }

View File

@@ -2,5 +2,7 @@
"errors": [], "errors": [],
"host": "localhost", "host": "localhost",
"passed": true, "passed": true,
"policy": "Docker policy: test13 (version 1)" "policy": "Docker policy: test13 (version 1)",
"port": 2222,
"warnings": []
} }

View File

@@ -15,5 +15,7 @@
], ],
"host": "localhost", "host": "localhost",
"passed": false, "passed": false,
"policy": "Docker policy: test14 (version 1)" "policy": "Docker policy: test14 (version 1)",
"port": 2222,
"warnings": []
} }

View File

@@ -2,5 +2,7 @@
"errors": [], "errors": [],
"host": "localhost", "host": "localhost",
"passed": true, "passed": true,
"policy": "Docker policy: test15 (version 1)" "policy": "Docker policy: test15 (version 1)",
"port": 2222,
"warnings": []
} }

View File

@@ -82,5 +82,7 @@
], ],
"host": "localhost", "host": "localhost",
"passed": false, "passed": false,
"policy": "Docker policy: test16 (version 1)" "policy": "Docker policy: test16 (version 1)",
"port": 2222,
"warnings": []
} }

View File

@@ -2,5 +2,7 @@
"errors": [], "errors": [],
"host": "localhost", "host": "localhost",
"passed": true, "passed": true,
"policy": "Docker policy: test17 (version 1)" "policy": "Docker policy: test17 (version 1)",
"port": 2222,
"warnings": []
} }

View File

@@ -0,0 +1,8 @@
{
"errors": [],
"host": "localhost",
"passed": true,
"policy": "Docker policy: test18 (version 1)",
"port": 2222,
"warnings": []
}

View File

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

View File

@@ -2,5 +2,7 @@
"errors": [], "errors": [],
"host": "localhost", "host": "localhost",
"passed": true, "passed": true,
"policy": "Docker policy: test6 (version 1)" "policy": "Docker policy: test6 (version 1)",
"port": 2222,
"warnings": []
} }

View File

@@ -10,28 +10,7 @@
"none", "none",
"zlib@openssh.com" "zlib@openssh.com"
], ],
"cves": [ "cves": [],
{
"cvssv2": 7.0,
"description": "privilege escalation via supplemental groups",
"name": "CVE-2021-41617"
},
{
"cvssv2": 7.8,
"description": "command injection via anomalous argument transfers",
"name": "CVE-2020-15778"
},
{
"cvssv2": 7.8,
"description": "memory corruption and local code execution via pre-authentication integer overflow",
"name": "CVE-2019-16905"
},
{
"cvssv2": 5.3,
"description": "enumerate usernames via challenge response",
"name": "CVE-2016-20012"
}
],
"enc": [ "enc": [
{ {
"algorithm": "chacha20-poly1305@openssh.com", "algorithm": "chacha20-poly1305@openssh.com",
@@ -87,6 +66,16 @@
} }
], ],
"fingerprints": [ "fingerprints": [
{
"hash": "Q6Llm0o4TrcUen4tnT2h4BDf2f+ina6dIJmVH8c40bg",
"hash_alg": "SHA256",
"hostkey": "ecdsa-sha2-nistp256"
},
{
"hash": "cc:e0:80:84:5b:05:98:64:24:43:52:3b:17:c8:94:89",
"hash_alg": "MD5",
"hostkey": "ecdsa-sha2-nistp256"
},
{ {
"hash": "UrnXIVH+7dlw8UqYocl48yUEcKrthGDQG2CPCgp7MxU", "hash": "UrnXIVH+7dlw8UqYocl48yUEcKrthGDQG2CPCgp7MxU",
"hash_alg": "SHA256", "hash_alg": "SHA256",
@@ -115,6 +104,9 @@
"info": [ "info": [
"default key exchange from OpenSSH 7.4 to 8.9", "default key exchange from OpenSSH 7.4 to 8.9",
"available since OpenSSH 7.4, Dropbear SSH 2018.76" "available since OpenSSH 7.4, Dropbear SSH 2018.76"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -124,6 +116,9 @@
"info": [ "info": [
"default key exchange from OpenSSH 6.5 to 7.3", "default key exchange from OpenSSH 6.5 to 7.3",
"available since OpenSSH 6.4, Dropbear SSH 2013.62" "available since OpenSSH 6.4, Dropbear SSH 2013.62"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -135,6 +130,9 @@
], ],
"info": [ "info": [
"available since OpenSSH 5.7, Dropbear SSH 2013.62" "available since OpenSSH 5.7, Dropbear SSH 2013.62"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -146,6 +144,9 @@
], ],
"info": [ "info": [
"available since OpenSSH 5.7, Dropbear SSH 2013.62" "available since OpenSSH 5.7, Dropbear SSH 2013.62"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -157,6 +158,9 @@
], ],
"info": [ "info": [
"available since OpenSSH 5.7, Dropbear SSH 2013.62" "available since OpenSSH 5.7, Dropbear SSH 2013.62"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -167,6 +171,9 @@
"info": [ "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).", "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" "available since OpenSSH 4.4"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -175,6 +182,9 @@
"notes": { "notes": {
"info": [ "info": [
"available since OpenSSH 7.3, Dropbear SSH 2016.73" "available since OpenSSH 7.3, Dropbear SSH 2016.73"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -183,6 +193,9 @@
"notes": { "notes": {
"info": [ "info": [
"available since OpenSSH 7.3" "available since OpenSSH 7.3"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -193,7 +206,8 @@
"available since OpenSSH 7.3, Dropbear SSH 2016.73" "available since OpenSSH 7.3, Dropbear SSH 2016.73"
], ],
"warn": [ "warn": [
"2048-bit modulus only provides 112-bits of symmetric strength" "2048-bit modulus only provides 112-bits of symmetric strength",
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -207,7 +221,8 @@
"available since OpenSSH 3.9, Dropbear SSH 0.53" "available since OpenSSH 3.9, Dropbear SSH 0.53"
], ],
"warn": [ "warn": [
"2048-bit modulus only provides 112-bits of symmetric strength" "2048-bit modulus only provides 112-bits of symmetric strength",
"does not provide protection against post-quantum attacks"
] ]
} }
} }
@@ -418,6 +433,14 @@
} }
}, },
"warning": { "warning": {
"chg": {
"kex": [
{
"name": "diffie-hellman-group-exchange-sha256",
"notes": "increase modulus size to 3072 bits or larger"
}
]
},
"del": { "del": {
"enc": [ "enc": [
{ {
@@ -426,9 +449,25 @@
} }
], ],
"kex": [ "kex": [
{
"name": "curve25519-sha256",
"notes": ""
},
{
"name": "curve25519-sha256@libssh.org",
"notes": ""
},
{ {
"name": "diffie-hellman-group14-sha256", "name": "diffie-hellman-group14-sha256",
"notes": "" "notes": ""
},
{
"name": "diffie-hellman-group16-sha512",
"notes": ""
},
{
"name": "diffie-hellman-group18-sha512",
"notes": ""
} }
], ],
"mac": [ "mac": [

View File

@@ -4,31 +4,35 @@
(gen) compatibility: OpenSSH 7.4+, Dropbear SSH 2020.79+ (gen) compatibility: OpenSSH 7.4+, Dropbear SSH 2020.79+
(gen) compression: enabled (zlib@openssh.com) (gen) compression: enabled (zlib@openssh.com)
# security
(cve) CVE-2021-41617 -- (CVSSv2: 7.0) privilege escalation via supplemental groups
(cve) CVE-2020-15778 -- (CVSSv2: 7.8) command injection via anomalous argument transfers
(cve) CVE-2019-16905 -- (CVSSv2: 7.8) memory corruption and local code execution via pre-authentication integer overflow
(cve) CVE-2016-20012 -- (CVSSv2: 5.3) enumerate usernames via challenge response
# key exchange algorithms # key exchange algorithms
(kex) curve25519-sha256 -- [info] available since OpenSSH 7.4, Dropbear SSH 2018.76 (kex) curve25519-sha256 -- [warn] does not provide protection against post-quantum attacks
 `- [info] default key exchange from OpenSSH 7.4 to 8.9 `- [info] available since OpenSSH 7.4, Dropbear SSH 2018.76
(kex) curve25519-sha256@libssh.org -- [info] available since OpenSSH 6.4, Dropbear SSH 2013.62 `- [info] default key exchange from OpenSSH 7.4 to 8.9
 `- [info] default key exchange from OpenSSH 6.5 to 7.3 (kex) curve25519-sha256@libssh.org -- [warn] does not provide protection against post-quantum attacks
`- [info] available since OpenSSH 6.4, Dropbear SSH 2013.62
`- [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
 `- [warn] does not provide protection against post-quantum attacks
`- [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
 `- [warn] does not provide protection against post-quantum attacks
`- [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
 `- [warn] does not provide protection against post-quantum attacks
`- [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 (4096-bit) -- [info] available since OpenSSH 4.4 (kex) diffie-hellman-group-exchange-sha256 (4096-bit) -- [warn] does not provide protection against post-quantum attacks
 `- [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] available since OpenSSH 4.4
(kex) diffie-hellman-group16-sha512 -- [info] available since OpenSSH 7.3, Dropbear SSH 2016.73 `- [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).
(kex) diffie-hellman-group18-sha512 -- [info] available since OpenSSH 7.3 (kex) diffie-hellman-group16-sha512 -- [warn] does not provide protection against post-quantum attacks
`- [info] available since OpenSSH 7.3, Dropbear SSH 2016.73
(kex) diffie-hellman-group18-sha512 -- [warn] does not provide protection against post-quantum attacks
`- [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
 `- [warn] does not provide protection against post-quantum attacks
`- [info] available since OpenSSH 7.3, Dropbear SSH 2016.73 `- [info] available since OpenSSH 7.3, Dropbear SSH 2016.73
(kex) diffie-hellman-group14-sha1 -- [fail] using broken SHA-1 hash algorithm (kex) diffie-hellman-group14-sha1 -- [fail] using broken SHA-1 hash algorithm
 `- [warn] 2048-bit modulus only provides 112-bits of symmetric strength  `- [warn] 2048-bit modulus only provides 112-bits of symmetric strength
 `- [warn] does not provide protection against post-quantum attacks
`- [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
@@ -86,8 +90,13 @@
(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) !diffie-hellman-group-exchange-sha256 -- kex algorithm to change (increase modulus size to 3072 bits or larger) 
(rec) -chacha20-poly1305@openssh.com -- enc algorithm to remove  (rec) -chacha20-poly1305@openssh.com -- enc algorithm to remove 
(rec) -curve25519-sha256 -- kex algorithm to remove 
(rec) -curve25519-sha256@libssh.org -- kex algorithm to remove 
(rec) -diffie-hellman-group14-sha256 -- kex algorithm to remove  (rec) -diffie-hellman-group14-sha256 -- kex algorithm to remove 
(rec) -diffie-hellman-group16-sha512 -- kex algorithm to remove 
(rec) -diffie-hellman-group18-sha512 -- 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 
(rec) -umac-128@openssh.com -- mac algorithm to remove  (rec) -umac-128@openssh.com -- mac algorithm to remove 
@@ -95,5 +104,5 @@
(rec) -umac-64@openssh.com -- mac algorithm to remove  (rec) -umac-64@openssh.com -- mac algorithm to remove 
# additional info # additional info
(nfo) For hardening guides on common OSes, please see: <https://www.ssh-audit.com/hardening_guides.html> (nfo) For hardening guides on common OSes, a built-in list can be viewed with --list-hardening-guides, or an online list can be found at: <https://www.ssh-audit.com/hardening_guides.html>

View File

@@ -10,28 +10,7 @@
"none", "none",
"zlib@openssh.com" "zlib@openssh.com"
], ],
"cves": [ "cves": [],
{
"cvssv2": 7.0,
"description": "privilege escalation via supplemental groups",
"name": "CVE-2021-41617"
},
{
"cvssv2": 7.8,
"description": "command injection via anomalous argument transfers",
"name": "CVE-2020-15778"
},
{
"cvssv2": 7.8,
"description": "memory corruption and local code execution via pre-authentication integer overflow",
"name": "CVE-2019-16905"
},
{
"cvssv2": 5.3,
"description": "enumerate usernames via challenge response",
"name": "CVE-2016-20012"
}
],
"enc": [ "enc": [
{ {
"algorithm": "chacha20-poly1305@openssh.com", "algorithm": "chacha20-poly1305@openssh.com",
@@ -105,6 +84,9 @@
"info": [ "info": [
"default key exchange from OpenSSH 7.4 to 8.9", "default key exchange from OpenSSH 7.4 to 8.9",
"available since OpenSSH 7.4, Dropbear SSH 2018.76" "available since OpenSSH 7.4, Dropbear SSH 2018.76"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -114,6 +96,9 @@
"info": [ "info": [
"default key exchange from OpenSSH 6.5 to 7.3", "default key exchange from OpenSSH 6.5 to 7.3",
"available since OpenSSH 6.4, Dropbear SSH 2013.62" "available since OpenSSH 6.4, Dropbear SSH 2013.62"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -125,6 +110,9 @@
], ],
"info": [ "info": [
"available since OpenSSH 5.7, Dropbear SSH 2013.62" "available since OpenSSH 5.7, Dropbear SSH 2013.62"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -136,6 +124,9 @@
], ],
"info": [ "info": [
"available since OpenSSH 5.7, Dropbear SSH 2013.62" "available since OpenSSH 5.7, Dropbear SSH 2013.62"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -147,6 +138,9 @@
], ],
"info": [ "info": [
"available since OpenSSH 5.7, Dropbear SSH 2013.62" "available since OpenSSH 5.7, Dropbear SSH 2013.62"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -157,6 +151,9 @@
"info": [ "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).", "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" "available since OpenSSH 4.4"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -165,6 +162,9 @@
"notes": { "notes": {
"info": [ "info": [
"available since OpenSSH 7.3, Dropbear SSH 2016.73" "available since OpenSSH 7.3, Dropbear SSH 2016.73"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -173,6 +173,9 @@
"notes": { "notes": {
"info": [ "info": [
"available since OpenSSH 7.3" "available since OpenSSH 7.3"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -183,7 +186,8 @@
"available since OpenSSH 7.3, Dropbear SSH 2016.73" "available since OpenSSH 7.3, Dropbear SSH 2016.73"
], ],
"warn": [ "warn": [
"2048-bit modulus only provides 112-bits of symmetric strength" "2048-bit modulus only provides 112-bits of symmetric strength",
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -197,7 +201,8 @@
"available since OpenSSH 3.9, Dropbear SSH 0.53" "available since OpenSSH 3.9, Dropbear SSH 0.53"
], ],
"warn": [ "warn": [
"2048-bit modulus only provides 112-bits of symmetric strength" "2048-bit modulus only provides 112-bits of symmetric strength",
"does not provide protection against post-quantum attacks"
] ]
} }
} }
@@ -377,6 +382,14 @@
} }
}, },
"warning": { "warning": {
"chg": {
"kex": [
{
"name": "diffie-hellman-group-exchange-sha256",
"notes": "increase modulus size to 3072 bits or larger"
}
]
},
"del": { "del": {
"enc": [ "enc": [
{ {
@@ -385,9 +398,25 @@
} }
], ],
"kex": [ "kex": [
{
"name": "curve25519-sha256",
"notes": ""
},
{
"name": "curve25519-sha256@libssh.org",
"notes": ""
},
{ {
"name": "diffie-hellman-group14-sha256", "name": "diffie-hellman-group14-sha256",
"notes": "" "notes": ""
},
{
"name": "diffie-hellman-group16-sha512",
"notes": ""
},
{
"name": "diffie-hellman-group18-sha512",
"notes": ""
} }
], ],
"mac": [ "mac": [

View File

@@ -4,31 +4,35 @@
(gen) compatibility: OpenSSH 7.4+, Dropbear SSH 2020.79+ (gen) compatibility: OpenSSH 7.4+, Dropbear SSH 2020.79+
(gen) compression: enabled (zlib@openssh.com) (gen) compression: enabled (zlib@openssh.com)
# security
(cve) CVE-2021-41617 -- (CVSSv2: 7.0) privilege escalation via supplemental groups
(cve) CVE-2020-15778 -- (CVSSv2: 7.8) command injection via anomalous argument transfers
(cve) CVE-2019-16905 -- (CVSSv2: 7.8) memory corruption and local code execution via pre-authentication integer overflow
(cve) CVE-2016-20012 -- (CVSSv2: 5.3) enumerate usernames via challenge response
# key exchange algorithms # key exchange algorithms
(kex) curve25519-sha256 -- [info] available since OpenSSH 7.4, Dropbear SSH 2018.76 (kex) curve25519-sha256 -- [warn] does not provide protection against post-quantum attacks
 `- [info] default key exchange from OpenSSH 7.4 to 8.9 `- [info] available since OpenSSH 7.4, Dropbear SSH 2018.76
(kex) curve25519-sha256@libssh.org -- [info] available since OpenSSH 6.4, Dropbear SSH 2013.62 `- [info] default key exchange from OpenSSH 7.4 to 8.9
 `- [info] default key exchange from OpenSSH 6.5 to 7.3 (kex) curve25519-sha256@libssh.org -- [warn] does not provide protection against post-quantum attacks
`- [info] available since OpenSSH 6.4, Dropbear SSH 2013.62
`- [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
 `- [warn] does not provide protection against post-quantum attacks
`- [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
 `- [warn] does not provide protection against post-quantum attacks
`- [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
 `- [warn] does not provide protection against post-quantum attacks
`- [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 (4096-bit) -- [info] available since OpenSSH 4.4 (kex) diffie-hellman-group-exchange-sha256 (4096-bit) -- [warn] does not provide protection against post-quantum attacks
 `- [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] available since OpenSSH 4.4
(kex) diffie-hellman-group16-sha512 -- [info] available since OpenSSH 7.3, Dropbear SSH 2016.73 `- [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).
(kex) diffie-hellman-group18-sha512 -- [info] available since OpenSSH 7.3 (kex) diffie-hellman-group16-sha512 -- [warn] does not provide protection against post-quantum attacks
`- [info] available since OpenSSH 7.3, Dropbear SSH 2016.73
(kex) diffie-hellman-group18-sha512 -- [warn] does not provide protection against post-quantum attacks
`- [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
 `- [warn] does not provide protection against post-quantum attacks
`- [info] available since OpenSSH 7.3, Dropbear SSH 2016.73 `- [info] available since OpenSSH 7.3, Dropbear SSH 2016.73
(kex) diffie-hellman-group14-sha1 -- [fail] using broken SHA-1 hash algorithm (kex) diffie-hellman-group14-sha1 -- [fail] using broken SHA-1 hash algorithm
 `- [warn] 2048-bit modulus only provides 112-bits of symmetric strength  `- [warn] 2048-bit modulus only provides 112-bits of symmetric strength
 `- [warn] does not provide protection against post-quantum attacks
`- [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
@@ -78,8 +82,13 @@
(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) !diffie-hellman-group-exchange-sha256 -- kex algorithm to change (increase modulus size to 3072 bits or larger) 
(rec) -chacha20-poly1305@openssh.com -- enc algorithm to remove  (rec) -chacha20-poly1305@openssh.com -- enc algorithm to remove 
(rec) -curve25519-sha256 -- kex algorithm to remove 
(rec) -curve25519-sha256@libssh.org -- kex algorithm to remove 
(rec) -diffie-hellman-group14-sha256 -- kex algorithm to remove  (rec) -diffie-hellman-group14-sha256 -- kex algorithm to remove 
(rec) -diffie-hellman-group16-sha512 -- kex algorithm to remove 
(rec) -diffie-hellman-group18-sha512 -- 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 
(rec) -umac-128@openssh.com -- mac algorithm to remove  (rec) -umac-128@openssh.com -- mac algorithm to remove 
@@ -87,5 +96,5 @@
(rec) -umac-64@openssh.com -- mac algorithm to remove  (rec) -umac-64@openssh.com -- mac algorithm to remove 
# additional info # additional info
(nfo) For hardening guides on common OSes, please see: <https://www.ssh-audit.com/hardening_guides.html> (nfo) For hardening guides on common OSes, a built-in list can be viewed with --list-hardening-guides, or an online list can be found at: <https://www.ssh-audit.com/hardening_guides.html>

View File

@@ -10,28 +10,7 @@
"none", "none",
"zlib@openssh.com" "zlib@openssh.com"
], ],
"cves": [ "cves": [],
{
"cvssv2": 7.0,
"description": "privilege escalation via supplemental groups",
"name": "CVE-2021-41617"
},
{
"cvssv2": 7.8,
"description": "command injection via anomalous argument transfers",
"name": "CVE-2020-15778"
},
{
"cvssv2": 7.8,
"description": "memory corruption and local code execution via pre-authentication integer overflow",
"name": "CVE-2019-16905"
},
{
"cvssv2": 5.3,
"description": "enumerate usernames via challenge response",
"name": "CVE-2016-20012"
}
],
"enc": [ "enc": [
{ {
"algorithm": "chacha20-poly1305@openssh.com", "algorithm": "chacha20-poly1305@openssh.com",
@@ -105,6 +84,9 @@
"info": [ "info": [
"default key exchange from OpenSSH 7.4 to 8.9", "default key exchange from OpenSSH 7.4 to 8.9",
"available since OpenSSH 7.4, Dropbear SSH 2018.76" "available since OpenSSH 7.4, Dropbear SSH 2018.76"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -114,6 +96,9 @@
"info": [ "info": [
"default key exchange from OpenSSH 6.5 to 7.3", "default key exchange from OpenSSH 6.5 to 7.3",
"available since OpenSSH 6.4, Dropbear SSH 2013.62" "available since OpenSSH 6.4, Dropbear SSH 2013.62"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -124,6 +109,9 @@
"info": [ "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).", "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" "available since OpenSSH 4.4"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
} }
@@ -167,16 +155,6 @@
"recommendations": { "recommendations": {
"informational": { "informational": {
"add": { "add": {
"kex": [
{
"name": "diffie-hellman-group16-sha512",
"notes": ""
},
{
"name": "diffie-hellman-group18-sha512",
"notes": ""
}
],
"key": [ "key": [
{ {
"name": "rsa-sha2-256", "name": "rsa-sha2-256",
@@ -190,12 +168,30 @@
} }
}, },
"warning": { "warning": {
"chg": {
"kex": [
{
"name": "diffie-hellman-group-exchange-sha256",
"notes": "increase modulus size to 3072 bits or larger"
}
]
},
"del": { "del": {
"enc": [ "enc": [
{ {
"name": "chacha20-poly1305@openssh.com", "name": "chacha20-poly1305@openssh.com",
"notes": "" "notes": ""
} }
],
"kex": [
{
"name": "curve25519-sha256",
"notes": ""
},
{
"name": "curve25519-sha256@libssh.org",
"notes": ""
}
] ]
} }
} }

View File

@@ -4,19 +4,16 @@
(gen) compatibility: OpenSSH 7.4+, Dropbear SSH 2020.79+ (gen) compatibility: OpenSSH 7.4+, Dropbear SSH 2020.79+
(gen) compression: enabled (zlib@openssh.com) (gen) compression: enabled (zlib@openssh.com)
# security
(cve) CVE-2021-41617 -- (CVSSv2: 7.0) privilege escalation via supplemental groups
(cve) CVE-2020-15778 -- (CVSSv2: 7.8) command injection via anomalous argument transfers
(cve) CVE-2019-16905 -- (CVSSv2: 7.8) memory corruption and local code execution via pre-authentication integer overflow
(cve) CVE-2016-20012 -- (CVSSv2: 5.3) enumerate usernames via challenge response
# key exchange algorithms # key exchange algorithms
(kex) curve25519-sha256 -- [info] available since OpenSSH 7.4, Dropbear SSH 2018.76 (kex) curve25519-sha256 -- [warn] does not provide protection against post-quantum attacks
 `- [info] default key exchange from OpenSSH 7.4 to 8.9 `- [info] available since OpenSSH 7.4, Dropbear SSH 2018.76
(kex) curve25519-sha256@libssh.org -- [info] available since OpenSSH 6.4, Dropbear SSH 2013.62 `- [info] default key exchange from OpenSSH 7.4 to 8.9
 `- [info] default key exchange from OpenSSH 6.5 to 7.3 (kex) curve25519-sha256@libssh.org -- [warn] does not provide protection against post-quantum attacks
(kex) diffie-hellman-group-exchange-sha256 (4096-bit) -- [info] available since OpenSSH 4.4 `- [info] available since OpenSSH 6.4, Dropbear SSH 2013.62
 `- [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] default key exchange from OpenSSH 6.5 to 7.3
(kex) diffie-hellman-group-exchange-sha256 (4096-bit) -- [warn] does not provide protection against post-quantum attacks
`- [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).
# host-key algorithms # host-key algorithms
(key) ssh-ed25519 -- [info] available since OpenSSH 6.5, Dropbear SSH 2020.79 (key) ssh-ed25519 -- [info] available since OpenSSH 6.5, Dropbear SSH 2020.79
@@ -40,12 +37,13 @@
(fin) ssh-ed25519: SHA256:UrnXIVH+7dlw8UqYocl48yUEcKrthGDQG2CPCgp7MxU (fin) ssh-ed25519: SHA256:UrnXIVH+7dlw8UqYocl48yUEcKrthGDQG2CPCgp7MxU
# algorithm recommendations (for OpenSSH 8.0) # algorithm recommendations (for OpenSSH 8.0)
(rec) +diffie-hellman-group16-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) !diffie-hellman-group-exchange-sha256 -- kex algorithm to change (increase modulus size to 3072 bits or larger) 
(rec) -chacha20-poly1305@openssh.com -- enc algorithm to remove  (rec) -chacha20-poly1305@openssh.com -- enc algorithm to remove 
(rec) -curve25519-sha256 -- kex algorithm to remove 
(rec) -curve25519-sha256@libssh.org -- kex algorithm to remove 
# additional info # additional info
(nfo) For hardening guides on common OSes, please see: <https://www.ssh-audit.com/hardening_guides.html> (nfo) For hardening guides on common OSes, a built-in list can be viewed with --list-hardening-guides, or an online list can be found at: <https://www.ssh-audit.com/hardening_guides.html>

View File

@@ -43,6 +43,9 @@
"info": [ "info": [
"default key exchange from OpenSSH 7.4 to 8.9", "default key exchange from OpenSSH 7.4 to 8.9",
"available since OpenSSH 7.4, Dropbear SSH 2018.76" "available since OpenSSH 7.4, Dropbear SSH 2018.76"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
}, },
@@ -52,6 +55,9 @@
"info": [ "info": [
"default key exchange from OpenSSH 6.5 to 7.3", "default key exchange from OpenSSH 6.5 to 7.3",
"available since OpenSSH 6.4, Dropbear SSH 2013.62" "available since OpenSSH 6.4, Dropbear SSH 2013.62"
],
"warn": [
"does not provide protection against post-quantum attacks"
] ]
} }
}, },

View File

@@ -4,10 +4,12 @@
(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 -- [warn] does not provide protection against post-quantum attacks
 `- [info] default key exchange from OpenSSH 7.4 to 8.9 `- [info] available since OpenSSH 7.4, Dropbear SSH 2018.76
(kex) curve25519-sha256@libssh.org -- [info] available since OpenSSH 6.4, Dropbear SSH 2013.62 `- [info] default key exchange from OpenSSH 7.4 to 8.9
 `- [info] default key exchange from OpenSSH 6.5 to 7.3 (kex) curve25519-sha256@libssh.org -- [warn] does not provide protection against post-quantum attacks
`- [info] available since OpenSSH 6.4, Dropbear SSH 2013.62
`- [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

View File

@@ -0,0 +1,17 @@
#
# Docker policy: test18
#
name = "Docker policy: test18"
version = 1
allow_algorithm_subset_and_reordering = false
allow_hostkey_subset_and_reordering = true
allow_larger_keys = false
banner = "SSH-2.0-OpenSSH_8.0"
compressions = none, zlib@openssh.com
host keys = ssh-rsa, rsa-sha2-256, rsa-sha2-512, ecdsa-sha2-nistp256, ssh-ed25519, x
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": 3072}, "rsa-sha2-256": {"hostkey_size": 3072}, "rsa-sha2-512": {"hostkey_size": 3072}, "ssh-ed25519": {"hostkey_size": 256}}
dh_modulus_sizes = {"diffie-hellman-group-exchange-sha256": 4096}

View File

@@ -8,7 +8,6 @@ class TestAuditConf:
def init(self, ssh_audit): def init(self, ssh_audit):
self.AuditConf = ssh_audit.AuditConf self.AuditConf = ssh_audit.AuditConf
self.OutputBuffer = ssh_audit.OutputBuffer() self.OutputBuffer = ssh_audit.OutputBuffer()
self.usage = ssh_audit.usage
self.process_commandline = process_commandline self.process_commandline = process_commandline
@staticmethod @staticmethod
@@ -16,8 +15,6 @@ class TestAuditConf:
options = { options = {
'host': '', 'host': '',
'port': 22, 'port': 22,
'ssh1': True,
'ssh2': True,
'batch': False, 'batch': False,
'colors': True, 'colors': True,
'verbose': False, 'verbose': False,
@@ -29,8 +26,6 @@ class TestAuditConf:
options[k] = v options[k] = v
assert conf.host == options['host'] assert conf.host == options['host']
assert conf.port == options['port'] assert conf.port == options['port']
assert conf.ssh1 is options['ssh1']
assert conf.ssh2 is options['ssh2']
assert conf.batch is options['batch'] assert conf.batch is options['batch']
assert conf.colors is options['colors'] assert conf.colors is options['colors']
assert conf.verbose is options['verbose'] assert conf.verbose is options['verbose']
@@ -44,7 +39,7 @@ class TestAuditConf:
def test_audit_conf_booleans(self): def test_audit_conf_booleans(self):
conf = self.AuditConf() conf = self.AuditConf()
for p in ['ssh1', 'ssh2', 'batch', 'colors', 'verbose']: for p in ['batch', 'colors', 'verbose']:
for v in [True, 1]: for v in [True, 1]:
setattr(conf, p, v) setattr(conf, p, v)
assert getattr(conf, p) is True assert getattr(conf, p) is True
@@ -107,7 +102,7 @@ class TestAuditConf:
def test_audit_conf_process_commandline(self): def test_audit_conf_process_commandline(self):
# pylint: disable=too-many-statements # pylint: disable=too-many-statements
c = lambda x: self.process_commandline(self.OutputBuffer, x.split(), self.usage) # noqa c = lambda x: self.process_commandline(self.OutputBuffer, x.split()) # noqa
with pytest.raises(SystemExit): with pytest.raises(SystemExit):
conf = c('') conf = c('')
with pytest.raises(SystemExit): with pytest.raises(SystemExit):
@@ -148,12 +143,6 @@ class TestAuditConf:
conf = c('localhost:99999') conf = c('localhost:99999')
with pytest.raises(SystemExit): with pytest.raises(SystemExit):
conf = c('-p 99999 localhost') conf = c('-p 99999 localhost')
conf = c('-1 localhost')
self._test_conf(conf, host='localhost', ssh1=True, ssh2=False)
conf = c('-2 localhost')
self._test_conf(conf, host='localhost', ssh1=False, ssh2=True)
conf = c('-12 localhost')
self._test_conf(conf, host='localhost', ssh1=True, ssh2=True)
conf = c('-4 localhost') conf = c('-4 localhost')
self._test_conf(conf, host='localhost', ipv4=True, ipv6=False, ipvo=(4,)) self._test_conf(conf, host='localhost', ipv4=True, ipv6=False, ipvo=(4,))
conf = c('-6 localhost') conf = c('-6 localhost')
@@ -163,7 +152,7 @@ class TestAuditConf:
conf = c('-64 localhost') conf = c('-64 localhost')
self._test_conf(conf, host='localhost', ipv4=True, ipv6=True, ipvo=(6, 4)) self._test_conf(conf, host='localhost', ipv4=True, ipv6=True, ipvo=(6, 4))
conf = c('-b localhost') conf = c('-b localhost')
self._test_conf(conf, host='localhost', batch=True, verbose=True) self._test_conf(conf, host='localhost', batch=True)
conf = c('-n localhost') conf = c('-n localhost')
self._test_conf(conf, host='localhost', colors=False) self._test_conf(conf, host='localhost', colors=False)
conf = c('-v localhost') conf = c('-v localhost')

View File

@@ -36,7 +36,7 @@ def test_prevent_runtime_error_regression(ssh_audit, kex):
kex.set_host_key("ssh-rsa7", b"\x00\x00\x00\x07ssh-rsa\x00\x00\x00", 1024, '', 0) kex.set_host_key("ssh-rsa7", b"\x00\x00\x00\x07ssh-rsa\x00\x00\x00", 1024, '', 0)
kex.set_host_key("ssh-rsa8", b"\x00\x00\x00\x07ssh-rsa\x00\x00\x00", 1024, '', 0) kex.set_host_key("ssh-rsa8", b"\x00\x00\x00\x07ssh-rsa\x00\x00\x00", 1024, '', 0)
rv = ssh_audit.build_struct('localhost', None, [], kex=kex) rv = ssh_audit.build_struct('localhost', None, kex=kex)
assert len(rv["fingerprints"]) == (9 * 2) # Each host key generates two hash fingerprints: one using SHA256, and one using MD5. assert len(rv["fingerprints"]) == (9 * 2) # Each host key generates two hash fingerprints: one using SHA256, and one using MD5.

View File

@@ -166,7 +166,6 @@ class TestErrors:
vsocket.rdata.append(b'SSH-1.3-ssh-audit-test\r\n') vsocket.rdata.append(b'SSH-1.3-ssh-audit-test\r\n')
vsocket.rdata.append(b'Protocol major versions differ.\n') vsocket.rdata.append(b'Protocol major versions differ.\n')
conf = self._conf() conf = self._conf()
conf.ssh1, conf.ssh2 = True, False
lines = self._audit(output_spy, conf) lines = self._audit(output_spy, conf)
assert len(lines) == 4 assert len(lines) == 4
assert 'error reading packet' in lines[-1] assert 'error reading packet' in lines[-1]

View File

@@ -0,0 +1,81 @@
import pytest
from ssh_audit.hardening_guides import Hardening_Guides
class TestHardeningGuides:
@pytest.fixture(autouse=True)
def init(self, ssh_audit):
self.OutputBuffer = ssh_audit.OutputBuffer()
def test_hardening_guides_consistency(self):
'''Ensure that the HARDENING_GUIDES struct is consistent.'''
# Required keys in each guide dict.
required_guide_fields = ["server_guide", "version", "version_date", "change_log", "notes", "commands"]
# Required keys in the commands dict.
required_command_fields = ["heading", "comment", "command"]
for name, guides in Hardening_Guides.HARDENING_GUIDES.items():
# Ensure the key (guide name) is a string.
assert type(name) is str
# Ensure the value (guides) is a list.
assert type(guides) is list
for guide in guides:
# Ensure each guide is a dict.
assert type(guide) is dict
# Ensure each required key is in this guide.
for required_guide_field in required_guide_fields:
assert required_guide_field in guide
# Check the guide values are the correct type.
assert type(guide["server_guide"]) is bool
assert type(guide["version"]) is int
assert type(guide["version_date"]) is str
assert type(guide["change_log"]) is str
assert type(guide["notes"]) is str
assert type(guide["commands"]) is list
# The version must be creater than zero.
assert guide["version"] > 0
# Ensure the format is "YYYY-MM-DD".
version_date = guide["version_date"]
date_fields = version_date.split("-")
assert len(date_fields) == 3
# Check that the year is 4 digits and greater than 0.
year = date_fields[0]
assert len(year) == 4
assert int(year) > 0
# Check that the month is 2 digits and between 1 and 12.
month = date_fields[1]
assert len(month) == 2
assert 1 <= int(month) <= 12
# Check that the day is 2 digits and between 1 and 31.
day = date_fields[2]
assert len(day) == 2
assert 1 <= int(day) <= 31
# Check that the change log is filled in.
assert len(guide["change_log"]) > 0
commands = guide["commands"]
for command in commands:
# Ensure each required key is in this command list.
for required_command_field in required_command_fields:
assert required_command_field in command
# Check that these fields are not empty.
assert len(command["heading"]) > 0
assert len(command["command"]) > 0

View File

@@ -52,8 +52,13 @@ class TestPolicy:
version_str = " (version %s)" % BUILTIN_POLICIES[policy_name]['version'] version_str = " (version %s)" % BUILTIN_POLICIES[policy_name]['version']
assert policy_name.endswith(version_str) assert policy_name.endswith(version_str)
# Ensure no extra fields are present. # Ensure version field is a string, but can be parsed as an integer.
assert len(required_fields) == len(BUILTIN_POLICIES[policy_name]) version_field = BUILTIN_POLICIES[policy_name]['version']
assert type(version_field) is str
try:
int(version_field)
except ValueError:
assert False, "version field of %s policy is not parseable as an integer." % policy_name
# Ensure that the changelog field is a string and non-empty. # Ensure that the changelog field is a string and non-empty.
assert type(BUILTIN_POLICIES[policy_name]['changelog']) is str assert type(BUILTIN_POLICIES[policy_name]['changelog']) is str
@@ -150,7 +155,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]\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" assert str(policy) == "Name: [Test Policy]\nVersion: [1]\nAllow Algorithm Subset and/or Reordering: False\nAllow Host Key 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):
@@ -297,7 +302,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() == 'fb84bce442cff2bce9bf653d6373a8a938e3bfcfbd1e876f51a08c1842df3cff' assert hashlib.sha256(pol_data.encode('ascii')).hexdigest() == '3862e56ea60c1ecba6dffbf724216c4391b6fe00a790b0075617477d4addf761'
def test_policy_evaluate_passing_1(self): def test_policy_evaluate_passing_1(self):

View File

@@ -1,174 +0,0 @@
import struct
import pytest
from ssh_audit.auditconf import AuditConf
from ssh_audit.fingerprint import Fingerprint
from ssh_audit.outputbuffer import OutputBuffer
from ssh_audit.protocol import Protocol
from ssh_audit.readbuf import ReadBuf
from ssh_audit.ssh1 import SSH1
from ssh_audit.ssh1_publickeymessage import SSH1_PublicKeyMessage
from ssh_audit.ssh_audit import audit
from ssh_audit.writebuf import WriteBuf
# pylint: disable=line-too-long,attribute-defined-outside-init
class TestSSH1:
@pytest.fixture(autouse=True)
def init(self, ssh_audit):
self.OutputBuffer = OutputBuffer
self.protocol = Protocol
self.ssh1 = SSH1
self.PublicKeyMessage = SSH1_PublicKeyMessage
self.rbuf = ReadBuf
self.wbuf = WriteBuf
self.audit = audit
self.AuditConf = AuditConf
self.fingerprint = Fingerprint
def _conf(self):
conf = self.AuditConf('localhost', 22)
conf.colors = False
conf.batch = True
conf.verbose = True
conf.ssh1 = True
conf.ssh2 = False
conf.skip_rate_test = True
return conf
def _create_ssh1_packet(self, payload, valid_crc=True):
padding = -(len(payload) + 4) % 8
plen = len(payload) + 4
pad_bytes = b'\x00' * padding
cksum = self.ssh1.crc32(pad_bytes + payload) if valid_crc else 0
data = struct.pack('>I', plen) + pad_bytes + payload + struct.pack('>I', cksum)
return data
@classmethod
def _server_key(cls):
return (1024, 0x10001, 0xee6552da432e0ac2c422df1a51287507748bfe3b5e3e4fa989a8f49fdc163a17754939ef18ef8a667ea3b71036a151fcd7f5e01ceef1e4439864baf3ac569047582c69d6c128212e0980dcb3168f00d371004039983f6033cd785b8b8f85096c7d9405cbfdc664e27c966356a6b4eb6ee20ad43414b50de18b22829c1880b551)
@classmethod
def _host_key(cls):
return (2048, 0x10001, 0xdfa20cd2a530ccc8c870aa60d9feb3b35deeab81c3215a96557abbd683d21f4600f38e475d87100da9a4404220eeb3bb5584e5a2b5b48ffda58530ea19104a32577d7459d91e76aa711b241050f4cc6d5327ccce254f371acad3be56d46eb5919b73f20dbdb1177b700f00891c5bf4ed128bb90ed541b778288285bcfa28432ab5cbcb8321b6e24760e998e0daa519f093a631e44276d7dd252ce0c08c75e2ab28a7349ead779f97d0f20a6d413bf3623cd216dc35375f6366690bcc41e3b2d5465840ec7ee0dc7e3f1c101d674a0c7dbccbc3942788b111396add2f8153b46a0e4b50d66e57ee92958f1c860dd97cc0e40e32febff915343ed53573142bdf4b)
def _pkm_payload(self):
w = self.wbuf()
w.write(b'\x88\x99\xaa\xbb\xcc\xdd\xee\xff')
b, e, m = self._server_key()
w.write_int(b).write_mpint1(e).write_mpint1(m)
b, e, m = self._host_key()
w.write_int(b).write_mpint1(e).write_mpint1(m)
w.write_int(2)
w.write_int(72)
w.write_int(36)
return w.write_flush()
def test_crc32(self):
assert self.ssh1.crc32(b'') == 0x00
assert self.ssh1.crc32(b'The quick brown fox jumps over the lazy dog') == 0xb9c60808
def test_fingerprint(self):
# pylint: disable=protected-access
b, e, m = self._host_key()
fpd = self.wbuf._create_mpint(m, False)
fpd += self.wbuf._create_mpint(e, False)
fp = self.fingerprint(fpd)
assert b == 2048
assert fp.md5 == 'MD5:9d:26:f8:39:fc:20:9d:9b:ca:cc:4a:0f:e1:93:f5:96'
assert fp.sha256 == 'SHA256:vZdx3mhzbvVJmn08t/ruv8WDhJ9jfKYsCTuSzot+QIs'
def _assert_pkm_keys(self, pkm, skey, hkey):
b, e, m = skey
assert pkm.server_key_bits == b
assert pkm.server_key_public_exponent == e
assert pkm.server_key_public_modulus == m
b, e, m = hkey
assert pkm.host_key_bits == b
assert pkm.host_key_public_exponent == e
assert pkm.host_key_public_modulus == m
def _assert_pkm_fields(self, pkm, skey, hkey):
assert pkm is not None
assert pkm.cookie == b'\x88\x99\xaa\xbb\xcc\xdd\xee\xff'
self._assert_pkm_keys(pkm, skey, hkey)
assert pkm.protocol_flags == 2
assert pkm.supported_ciphers_mask == 72
assert pkm.supported_ciphers == ['3des', 'blowfish']
assert pkm.supported_authentications_mask == 36
assert pkm.supported_authentications == ['rsa', 'tis']
fp = self.fingerprint(pkm.host_key_fingerprint_data)
assert fp.md5 == 'MD5:9d:26:f8:39:fc:20:9d:9b:ca:cc:4a:0f:e1:93:f5:96'
assert fp.sha256 == 'SHA256:vZdx3mhzbvVJmn08t/ruv8WDhJ9jfKYsCTuSzot+QIs'
def test_pkm_init(self):
cookie = b'\x88\x99\xaa\xbb\xcc\xdd\xee\xff'
pflags, cmask, amask = 2, 72, 36
skey, hkey = self._server_key(), self._host_key()
pkm = self.PublicKeyMessage(cookie, skey, hkey, pflags, cmask, amask)
self._assert_pkm_fields(pkm, skey, hkey)
for skey2 in ([], [0], [0, 1], [0, 1, 2, 3]):
with pytest.raises(ValueError):
pkm = self.PublicKeyMessage(cookie, skey2, hkey, pflags, cmask, amask)
for hkey2 in ([], [0], [0, 1], [0, 1, 2, 3]):
with pytest.raises(ValueError):
print(hkey2)
pkm = self.PublicKeyMessage(cookie, skey, hkey2, pflags, cmask, amask)
def test_pkm_read(self):
pkm = self.PublicKeyMessage.parse(self._pkm_payload())
self._assert_pkm_fields(pkm, self._server_key(), self._host_key())
def test_pkm_payload(self):
cookie = b'\x88\x99\xaa\xbb\xcc\xdd\xee\xff'
skey, hkey = self._server_key(), self._host_key()
pflags, cmask, amask = 2, 72, 36
pkm1 = self.PublicKeyMessage(cookie, skey, hkey, pflags, cmask, amask)
pkm2 = self.PublicKeyMessage.parse(self._pkm_payload())
assert pkm1.payload == pkm2.payload
def test_ssh1_server_simple(self, output_spy, virtual_socket):
vsocket = virtual_socket
w = self.wbuf()
w.write_byte(self.protocol.SMSG_PUBLIC_KEY)
w.write(self._pkm_payload())
vsocket.rdata.append(b'SSH-1.5-OpenSSH_7.2 ssh-audit-test\r\n')
vsocket.rdata.append(self._create_ssh1_packet(w.write_flush()))
output_spy.begin()
out = self.OutputBuffer()
self.audit(out, self._conf())
out.write()
lines = output_spy.flush()
assert len(lines) == 21
def test_ssh1_server_invalid_first_packet(self, output_spy, virtual_socket):
vsocket = virtual_socket
w = self.wbuf()
w.write_byte(self.protocol.SMSG_PUBLIC_KEY + 1)
w.write(self._pkm_payload())
vsocket.rdata.append(b'SSH-1.5-OpenSSH_7.2 ssh-audit-test\r\n')
vsocket.rdata.append(self._create_ssh1_packet(w.write_flush()))
output_spy.begin()
out = self.OutputBuffer()
ret = self.audit(out, self._conf())
out.write()
assert ret != 0
lines = output_spy.flush()
assert len(lines) == 14
assert 'unknown message' in lines[-1]
def test_ssh1_server_invalid_checksum(self, output_spy, virtual_socket):
vsocket = virtual_socket
w = self.wbuf()
w.write_byte(self.protocol.SMSG_PUBLIC_KEY + 1)
w.write(self._pkm_payload())
vsocket.rdata.append(b'SSH-1.5-OpenSSH_7.2 ssh-audit-test\r\n')
vsocket.rdata.append(self._create_ssh1_packet(w.write_flush(), False))
output_spy.begin()
out = self.OutputBuffer()
with pytest.raises(SystemExit):
self.audit(out, self._conf())
out.write()
lines = output_spy.flush()
assert len(lines) == 3
assert ('checksum' in lines[0]) or ('checksum' in lines[1]) or ('checksum' in lines[2])

View File

@@ -165,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) == 83 assert len(lines) == 74
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
@@ -179,7 +179,7 @@ class TestSSH2:
out.write() out.write()
assert ret != 0 assert ret != 0
lines = output_spy.flush() lines = output_spy.flush()
assert len(lines) == 9 assert len(lines) == 4
assert 'unknown message' in lines[-1] assert 'unknown message' in lines[-1]
def test_ssh2_gss_kex(self, output_spy, virtual_socket): def test_ssh2_gss_kex(self, output_spy, virtual_socket):

View File

@@ -8,6 +8,7 @@ class Test_SSH2_KexDB:
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def init(self): def init(self):
self.db = SSH2_KexDB.get_db() self.db = SSH2_KexDB.get_db()
self.pq_warning = SSH2_KexDB.WARN_NOT_PQ_SAFE
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.'''
@@ -33,3 +34,40 @@ class Test_SSH2_KexDB:
# The first entry denotes the versions when this algorithm was added to OpenSSH, Dropbear, and/or libssh, followed by when it was deprecated, and finally when it was removed. Hence it must have between 0 and 3 entries. # The first entry denotes the versions when this algorithm was added to OpenSSH, Dropbear, and/or libssh, followed by when it was deprecated, and finally when it was removed. Hence it must have between 0 and 3 entries.
added_entry = alg_data[0] added_entry = alg_data[0]
assert 0 <= len(added_entry) <= 3 assert 0 <= len(added_entry) <= 3
def test_kex_pq_unsafe(self):
'''Ensures that all key exchange algorithms are marked as post-quantum unsafe, unless they appear in a whitelist.'''
# These algorithms include protections against quantum attacks.
kex_pq_safe = [
"ecdh-nistp256-kyber-512r3-sha256-d00@openquantumsafe.org",
"ecdh-nistp384-kyber-768r3-sha384-d00@openquantumsafe.org",
"ecdh-nistp521-kyber-1024r3-sha512-d00@openquantumsafe.org",
"ext-info-c",
"ext-info-s",
"kex-strict-c-v00@openssh.com",
"kex-strict-s-v00@openssh.com",
"mlkem768x25519-sha256",
"sntrup4591761x25519-sha512@tinyssh.org",
"sntrup761x25519-sha512@openssh.com",
"sntrup761x25519-sha512",
"x25519-kyber-512r3-sha256-d00@amazon.com",
"x25519-kyber512-sha512@aws.amazon.com",
"mlkem768nistp256-sha256", # PQ safe, but has a conventional back-door.
"mlkem1024nistp384-sha384" # PQ safe, but has a conventional back-door.
]
failures = []
for kex_name in self.db['kex']:
# Skip key exchanges that are PQ safe.
if kex_name in kex_pq_safe:
continue
# Ensure all other kex exchanges have the proper PQ unsafe flag set in their warnings list.
alg_data = self.db['kex'][kex_name]
if len(alg_data) < 3 or self.pq_warning not in alg_data[2]:
failures.append(kex_name)
assert failures == []

17
tox.ini
View File

@@ -1,7 +1,7 @@
[tox] [tox]
envlist = envlist =
py{py3}-{test,pylint,flake8} py{py3}-{test,pylint,flake8}
py{38,39,310,311,312}-{test,mypy,pylint,flake8} py{38,39,310,311,312,313}-{test,mypy,pylint,flake8}
cov cov
skip_missing_interpreters = true skip_missing_interpreters = true
@@ -9,10 +9,10 @@ skip_missing_interpreters = true
deps = deps =
test: pytest test: pytest
test,cov: {[testenv:cov]deps} test,cov: {[testenv:cov]deps}
test,py{38,39,310,311,312}-{type,mypy}: colorama test,py{38,39,310,311,312,313}-{type,mypy}: colorama
py{38,39,310,311,312}-{type,mypy}: {[testenv:mypy]deps} py{38,39,310,311,312,313}-{type,mypy}: {[testenv:mypy]deps}
py{py3,38,39,310,311,312}-{lint,pylint},lint: {[testenv:pylint]deps} py{py3,38,39,310,311,312,313}-{lint,pylint},lint: {[testenv:pylint]deps}
py{py3,38,39,310,311,312}-{lint,flake8},lint: {[testenv:flake8]deps} py{py3,38,39,310,311,312,313}-{lint,flake8},lint: {[testenv:flake8]deps}
setenv = setenv =
SSHAUDIT = {toxinidir}/src SSHAUDIT = {toxinidir}/src
test: COVERAGE_FILE = {toxinidir}/.coverage.{envname} test: COVERAGE_FILE = {toxinidir}/.coverage.{envname}
@@ -24,9 +24,9 @@ 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{38,39,310,311,312}-{type,mypy}: {[testenv:mypy]commands} py{38,39,310,311,312,313}-{type,mypy}: {[testenv:mypy]commands}
py{py3,38,39,310,311,312}-{lint,pylint},lint: {[testenv:pylint]commands} py{py3,38,39,310,311,312,313}-{lint,pylint},lint: {[testenv:pylint]commands}
py{py3,38,39,310,311,312}-{lint,flake8},lint: {[testenv:flake8]commands} py{py3,38,39,310,311,312,313}-{lint,flake8},lint: {[testenv:flake8]commands}
#ignore_outcome = #ignore_outcome =
# type: true # type: true
@@ -96,6 +96,7 @@ disable =
too-many-lines, too-many-lines,
too-many-locals, too-many-locals,
too-many-nested-blocks, too-many-nested-blocks,
too-many-positional-arguments,
too-many-return-statements, too-many-return-statements,
too-many-statements, too-many-statements,
consider-using-f-string consider-using-f-string