mirror of
https://github.com/jtesta/ssh-audit.git
synced 2026-05-25 15:31:23 +02:00
Compare commits
20 Commits
v3.1.0
...
c7bf1adb0e
| Author | SHA1 | Date | |
|---|---|---|---|
| c7bf1adb0e | |||
| a4f508374a | |||
| 6f39407a8c | |||
| cb0f6b63d7 | |||
| 3313046714 | |||
| 8ee0deade1 | |||
| 699739d42a | |||
| a958fd1fec | |||
| c33f419224 | |||
| 6ee4899b4f | |||
| 20fbb706b0 | |||
| 73b669b49d | |||
| f326d58068 | |||
| b72f6a420f | |||
| aab105c398 | |||
| fe65b5df8a | |||
| 44393c56b3 | |||
| 164356e776 | |||
| c8e075ad13 | |||
| eebeac99a0 |
+12
-9
@@ -1,16 +1,19 @@
|
||||
FROM python:3-slim
|
||||
# syntax=docker/dockerfile:latest
|
||||
FROM scratch AS files
|
||||
|
||||
WORKDIR /
|
||||
# Copy ssh-audit code to temporary container
|
||||
COPY ssh-audit.py /
|
||||
COPY src/ /
|
||||
|
||||
FROM python:3-alpine AS runtime
|
||||
|
||||
# Update the image to remediate any vulnerabilities.
|
||||
RUN apt clean && apt update && apt -y dist-upgrade && apt clean && rm -rf /var/lib/apt/lists/*
|
||||
RUN apk upgrade -U --no-cache -a -l && \
|
||||
# Remove suid & sgid bits from all files.
|
||||
find / -xdev -perm /6000 -exec chmod ug-s {} \; 2> /dev/null || true
|
||||
|
||||
# Remove suid & sgid bits from all files.
|
||||
RUN find / -xdev -perm /6000 -exec chmod ug-s {} \; 2> /dev/null || true
|
||||
|
||||
# Copy the ssh-audit code.
|
||||
COPY ssh-audit.py .
|
||||
COPY src/ .
|
||||
# Copy the ssh-audit code from files container.
|
||||
COPY --from=files / /
|
||||
|
||||
# Allow listening on 2222/tcp for client auditing.
|
||||
EXPOSE 2222
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (C) 2017-2023 Joe Testa (jtesta@positronsecurity.com)
|
||||
Copyright (C) 2017-2024 Joe Testa (jtesta@positronsecurity.com)
|
||||
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
|
||||
|
||||
|
||||
|
||||
+14
-2
@@ -4,10 +4,22 @@ ifeq ($(VERSION),)
|
||||
endif
|
||||
|
||||
all:
|
||||
docker build -t positronsecurity/ssh-audit:${VERSION} .
|
||||
./add_builtin_man_page.sh
|
||||
docker buildx create --name multiarch --use || exit 0
|
||||
docker buildx build \
|
||||
--platform linux/amd64,linux/arm64,linux/arm/v7 \
|
||||
--tag positronsecurity/ssh-audit:${VERSION} \
|
||||
--tag positronsecurity/ssh-audit:latest \
|
||||
.
|
||||
docker buildx build \
|
||||
--tag positronsecurity/ssh-audit:${VERSION} \
|
||||
--tag positronsecurity/ssh-audit:latest \
|
||||
--load \
|
||||
--builder=multiarch \
|
||||
.
|
||||
|
||||
upload:
|
||||
docker login
|
||||
docker login -u positronsecurity
|
||||
docker buildx build \
|
||||
--platform linux/amd64,linux/arm64,linux/arm/v7 \
|
||||
--tag positronsecurity/ssh-audit:${VERSION} \
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
all:
|
||||
./add_builtin_man_page.sh
|
||||
rm -rf /tmp/pypi_upload
|
||||
virtualenv -p /usr/bin/python3 /tmp/pypi_upload/
|
||||
cp -R src /tmp/pypi_upload/
|
||||
|
||||
+8
-5
@@ -2,7 +2,7 @@
|
||||
|
||||
An executable can only be made on a Windows host because the PyInstaller tool (https://www.pyinstaller.org/) does not support cross-compilation.
|
||||
|
||||
1.) Install Python v3.11.x from https://www.python.org/. To make life easier, check the option to add Python to the PATH environment variable.
|
||||
1.) Install Python v3.x from https://www.python.org/. To make life easier, check the option to add Python to the PATH environment variable.
|
||||
|
||||
2.) Install Cygwin (https://www.cygwin.com/).
|
||||
|
||||
@@ -15,7 +15,7 @@ An executable can only be made on a Windows host because the PyInstaller tool (h
|
||||
|
||||
# PyPI
|
||||
|
||||
To create package and upload to test server:
|
||||
To create package and upload to test server (hint: use username '\_\_token\_\_' and API token for test.pypi.org):
|
||||
|
||||
```
|
||||
$ sudo apt install python3-virtualenv python3.10-venv
|
||||
@@ -31,7 +31,7 @@ To download from test server and verify:
|
||||
$ pip3 install --index-url https://test.pypi.org/simple ssh-audit
|
||||
```
|
||||
|
||||
To upload to production server (hint: use username '\_\_token\_\_' and API token):
|
||||
To upload to production server (hint: use username '\_\_token\_\_' and API token for production pypi.org):
|
||||
|
||||
```
|
||||
$ make -f Makefile.pypi uploadprod
|
||||
@@ -61,19 +61,22 @@ Upload the snap with:
|
||||
$ snapcraft export-login ~/snap_creds.txt
|
||||
$ export SNAPCRAFT_STORE_CREDENTIALS=$(cat ~/snap_creds.txt)
|
||||
$ snapcraft upload --release=beta ssh-audit_*.snap
|
||||
$ snapcraft upload --release=stable ssh-audit_*.snap
|
||||
$ snapcraft status ssh-audit # Note the revision number of the beta channel.
|
||||
$ snapcraft release ssh-audit X stable # Fill in with the revision number.
|
||||
```
|
||||
|
||||
|
||||
# Docker
|
||||
|
||||
Ensure that the buildx plugin is available by following the installation instructions available at: https://docs.docker.com/engine/install/ubuntu/
|
||||
|
||||
Build a local image with:
|
||||
|
||||
```
|
||||
$ make -f Makefile.docker
|
||||
```
|
||||
|
||||
Create a multi-architecture build and upload it to Dockerhub with:
|
||||
Create a multi-architecture build and upload it to Dockerhub with (hint: use the API token as the password):
|
||||
|
||||
```
|
||||
$ make -f Makefile.docker upload
|
||||
|
||||
@@ -57,7 +57,8 @@ usage: ssh-audit.py [options] <host>
|
||||
-L, --list-policies list all the official, built-in policies
|
||||
--lookup=<alg1,alg2,...> looks up an algorithm(s) without
|
||||
connecting to a server
|
||||
-m, --manual print the man page (Windows only)
|
||||
-m, --manual print the man page (Docker, PyPI, Snap, and Windows
|
||||
builds only)
|
||||
-M, --make-policy=<policy.txt> creates a policy based on the target server
|
||||
(i.e.: the target server has the ideal
|
||||
configuration that other servers should
|
||||
@@ -178,6 +179,16 @@ For convenience, a web front-end on top of the command-line tool is available at
|
||||
|
||||
## ChangeLog
|
||||
|
||||
### v3.2.0-dev (???)
|
||||
- Expanded filter of CBC ciphers to flag for the Terrapin vulnerability. It now includes more rarely found ciphers.
|
||||
- Color output is disabled if the `NO_COLOR` environment variable is set (see https://no-color.org/).
|
||||
- Fixed parsing of ecdsa-sha2-nistp* CA signatures on host keys. Additionally, they are now flagged as potentially back-doored, just as standard host keys are.
|
||||
- The built-in man page (`-m`, `--manual`) is now available on Docker, PyPI, and Snap builds, in addition to the Windows build.
|
||||
- Snap builds are now architecture-independent.
|
||||
- Gracefully handle rare exceptions (i.e.: crashes) while performing GEX tests.
|
||||
- Added built-in policy for OpenSSH 9.7.
|
||||
- Changed Docker base image from `python:3-slim` to `python:3-alpine`, resulting in a 59% reduction in image size; credit [Daniel Thamdrup](https://github.com/dallemon).
|
||||
|
||||
### v3.1.0 (2023-12-20)
|
||||
- Added test for the Terrapin message prefix truncation vulnerability ([CVE-2023-48795](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-48795)).
|
||||
- Dropped support for Python 3.7 (EOL was reached in June 2023).
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (C) 2021 Joe Testa (jtesta@positronsecurity.com)
|
||||
# Copyright (C) 2021-2024 Joe Testa (jtesta@positronsecurity.com)
|
||||
# Copyright (C) 2021 Adam Russell (<adam[at]thecliguy[dot]co[dot]uk>)
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
@@ -26,22 +26,21 @@
|
||||
#
|
||||
|
||||
################################################################################
|
||||
# update_windows_man_page.sh
|
||||
# add_builtin_man_page.sh
|
||||
#
|
||||
# PURPOSE
|
||||
# Since Windows lacks a manual reader it's necessary to provide an alternative
|
||||
# means of reading the man page.
|
||||
# Since some platforms lack a manual reader it's necessary to provide an
|
||||
# alternative means of reading the man page.
|
||||
#
|
||||
# This script should be run as part of the ssh-audit packaging process for
|
||||
# Windows. It populates the 'WINDOWS_MAN_PAGE' variable in 'globals.py' with
|
||||
# the contents of the man page. Windows users can then print the content of
|
||||
# 'WINDOWS_MAN_PAGE' by invoking ssh-audit with the manual parameters
|
||||
# (--manual / -m).
|
||||
# Docker, PyPI, Snap, and Windows. It populates the 'BUILTIN_MAN_PAGE'
|
||||
# variable in 'globals.py' with the contents of the man page. Users can then
|
||||
# see the man page with "ssh-audit [--manual|-m]".
|
||||
#
|
||||
# Cygwin is required.
|
||||
# Linux or Cygwin is required to run this script.
|
||||
#
|
||||
# USAGE
|
||||
# update_windows_man_page.sh [-m <path-to-man-page>] [-g <path-to-globals.py>]
|
||||
# add_builtin_man_page.sh [-m <path-to-man-page>] [-g <path-to-globals.py>]
|
||||
#
|
||||
################################################################################
|
||||
|
||||
@@ -102,7 +101,7 @@ command -v sed >/dev/null 2>&1 || { echo >&2 "sed not found."; exit 1; }
|
||||
git checkout "${GLOBALS_PY}" > /dev/null 2>&1
|
||||
|
||||
# Remove the Windows man page placeholder from 'globals.py'.
|
||||
sed -i '/^WINDOWS_MAN_PAGE/d' "${GLOBALS_PY}"
|
||||
sed -i '/^BUILTIN_MAN_PAGE/d' "${GLOBALS_PY}"
|
||||
|
||||
echo "Processing man page at ${MAN_PAGE} and placing output into ${GLOBALS_PY}..."
|
||||
|
||||
@@ -116,7 +115,7 @@ echo "Processing man page at ${MAN_PAGE} and placing output into ${GLOBALS_PY}..
|
||||
# escape sequence. Not required under Cygwin because man outputs ANSI escape
|
||||
# codes automatically.
|
||||
|
||||
echo WINDOWS_MAN_PAGE = '"""' >> "${GLOBALS_PY}"
|
||||
echo BUILTIN_MAN_PAGE = '"""' >> "${GLOBALS_PY}"
|
||||
|
||||
if [[ "${PLATFORM}" == CYGWIN* ]]; then
|
||||
MANWIDTH=80 MAN_KEEP_FORMATTING=1 man "${MAN_PAGE}" | sed $'s/\u2010/-/g' >> "${GLOBALS_PY}"
|
||||
+4
-1
@@ -3,7 +3,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (C) 2021 Joe Testa (jtesta@positronsecurity.com)
|
||||
# Copyright (C) 2021-2024 Joe Testa (jtesta@positronsecurity.com)
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -44,6 +44,9 @@ rm -rf parts/ prime/ snap/ stage/ build/ dist/ src/*.egg-info/ ssh-audit*.snap
|
||||
git checkout snapcraft.yaml 2> /dev/null
|
||||
git checkout src/ssh_audit/globals.py 2> /dev/null
|
||||
|
||||
# Add the built-in manual page.
|
||||
./add_builtin_man_page.sh
|
||||
|
||||
# Get the version from the globals.py file.
|
||||
version=$(grep VERSION src/ssh_audit/globals.py | awk 'BEGIN {FS="="} ; {print $2}' | tr -d '[:space:]')
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (C) 2021 Joe Testa (jtesta@positronsecurity.com)
|
||||
# Copyright (C) 2021-2024 Joe Testa (jtesta@positronsecurity.com)
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -77,7 +77,7 @@ fi
|
||||
git checkout src/ssh_audit/globals.py 2> /dev/null
|
||||
|
||||
# Update the man page.
|
||||
./update_windows_man_page.sh
|
||||
./add_builtin_man_page.sh
|
||||
retval=$?
|
||||
if [[ ${retval} != 0 ]]; then
|
||||
echo "Failed to run ./update_windows_man_page.sh"
|
||||
|
||||
@@ -8,6 +8,9 @@ description: |
|
||||
base: core22
|
||||
grade: stable
|
||||
confinement: strict
|
||||
architectures:
|
||||
- build-on: [amd64]
|
||||
build-for: [all]
|
||||
|
||||
apps:
|
||||
ssh-audit:
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
"""
|
||||
import struct
|
||||
import traceback
|
||||
|
||||
# pylint: disable=unused-import
|
||||
@@ -65,7 +66,7 @@ class GEXTest:
|
||||
# Parse the server's KEX.
|
||||
_, payload = s.read_packet(2)
|
||||
SSH2_Kex.parse(out, payload)
|
||||
except KexDHException:
|
||||
except (KexDHException, struct.error):
|
||||
out.v("Failed to parse server's kex. Stack trace:\n%s" % str(traceback.format_exc()), write_now=True)
|
||||
return False
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
THE SOFTWARE.
|
||||
"""
|
||||
# The version to display.
|
||||
VERSION = 'v3.1.0'
|
||||
VERSION = 'v3.2.0-dev'
|
||||
|
||||
# SSH software to impersonate
|
||||
SSH_HEADER = 'SSH-{0}-OpenSSH_8.2'
|
||||
@@ -30,8 +30,8 @@ SSH_HEADER = 'SSH-{0}-OpenSSH_8.2'
|
||||
# The URL to the Github issues tracker.
|
||||
GITHUB_ISSUES_URL = 'https://github.com/jtesta/ssh-audit/issues'
|
||||
|
||||
# The man page. Only filled in on Windows systems.
|
||||
WINDOWS_MAN_PAGE = ''
|
||||
# The man page. Only filled in on Docker, PyPI, Snap, and Windows builds.
|
||||
BUILTIN_MAN_PAGE = ''
|
||||
|
||||
# True when installed from a Snap package, otherwise False.
|
||||
SNAP_PACKAGE = False
|
||||
|
||||
@@ -180,7 +180,7 @@ class HostKeyTest:
|
||||
hostkey_min_good = 256
|
||||
hostkey_min_warn = 224
|
||||
hostkey_warn_str = HostKeyTest.SMALL_ECC_MODULUS_WARNING
|
||||
if ca_key_type.startswith('ssh-ed25519') or host_key_type.startswith('ecdsa-sha2-nistp'):
|
||||
if ca_key_type.startswith('ssh-ed25519') or ca_key_type.startswith('ecdsa-sha2-nistp'):
|
||||
cakey_min_good = 256
|
||||
cakey_min_warn = 224
|
||||
cakey_warn_str = HostKeyTest.SMALL_ECC_MODULUS_WARNING
|
||||
@@ -209,6 +209,10 @@ class HostKeyTest:
|
||||
elif (0 < ca_modulus_size < cakey_min_good) and (cakey_warn_str not in key_warn_comments):
|
||||
key_warn_comments.append(cakey_warn_str)
|
||||
|
||||
# If the CA key type uses ECDSA with a NIST P-curve, fail it for possibly being back-doored.
|
||||
if ca_key_type.startswith('ecdsa-sha2-nistp'):
|
||||
key_fail_comments.append('CA key uses elliptic curves that are suspected as being backdoored by the U.S. National Security Agency')
|
||||
|
||||
# If this host key type is in the RSA family, then mark them all as parsed (since results in one are valid for them all).
|
||||
if host_key_type in HostKeyTest.RSA_FAMILY:
|
||||
for rsa_type in HostKeyTest.RSA_FAMILY:
|
||||
|
||||
+11
-2
@@ -80,7 +80,7 @@ class KexDH: # pragma: nocover
|
||||
# contains the host key, among other things. Function returns the host
|
||||
# key blob (from which the fingerprint can be calculated).
|
||||
def recv_reply(self, s: 'SSH_Socket', parse_host_key_size: bool = True) -> Optional[bytes]:
|
||||
# Reset the CA info, in case it was set from a prior invokation.
|
||||
# Reset the CA info, in case it was set from a prior invocation.
|
||||
self.__hostkey_type = ''
|
||||
self.__hostkey_e = 0 # pylint: disable=unused-private-member
|
||||
self.__hostkey_n = 0 # pylint: disable=unused-private-member
|
||||
@@ -100,7 +100,7 @@ class KexDH: # pragma: nocover
|
||||
# A connection error occurred. We can't parse anything, so just
|
||||
# return. The host key modulus (and perhaps certificate modulus)
|
||||
# will remain at length 0.
|
||||
self.out.d("KexDH.recv_reply(): received packge_type == -1.")
|
||||
self.out.d("KexDH.recv_reply(): received package_type == -1.")
|
||||
return None
|
||||
|
||||
# Get the host key blob, F, and signature.
|
||||
@@ -212,6 +212,15 @@ class KexDH: # pragma: nocover
|
||||
# CA's modulus. Bingo.
|
||||
ca_key_n, ca_key_n_len, ptr = KexDH.__get_bytes(ca_key, ptr) # pylint: disable=unused-variable
|
||||
|
||||
if ca_key_type.startswith("ecdsa-sha2-nistp") and ca_key_n_len > 0:
|
||||
self.out.d("Found ecdsa-sha2-nistp* CA key type.")
|
||||
|
||||
# 0x04 signifies that this is an uncompressed public key (meaning that full X and Y values are provided in ca_key_n.
|
||||
if ca_key_n[0] == 4:
|
||||
ca_key_n_len = ca_key_n_len - 1 # Subtract the 0x04 byte.
|
||||
ca_key_n_len = int(ca_key_n_len / 2) # Divide by 2 since the modulus is the size of either the X or Y value.
|
||||
|
||||
|
||||
else:
|
||||
self.out.d("Certificate type %u found; this is not usually valid in the context of a host key! Skipping it..." % cert_type)
|
||||
|
||||
|
||||
@@ -94,6 +94,8 @@ class Policy:
|
||||
|
||||
'Hardened OpenSSH Server v9.6 (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['sntrup761x25519-sha512@openssh.com', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256', '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', '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},
|
||||
|
||||
|
||||
# Ubuntu Client policies
|
||||
|
||||
@@ -419,11 +421,11 @@ macs = %s
|
||||
hostkey_types = list(self._hostkey_sizes.keys())
|
||||
hostkey_types.sort() # Sorted to make testing output repeatable.
|
||||
for hostkey_type in hostkey_types:
|
||||
expected_hostkey_size = self._hostkey_sizes[hostkey_type]['hostkey_size']
|
||||
expected_hostkey_size = cast(int, self._hostkey_sizes[hostkey_type]['hostkey_size'])
|
||||
server_host_keys = kex.host_keys()
|
||||
if hostkey_type in server_host_keys:
|
||||
actual_hostkey_size = server_host_keys[hostkey_type]['hostkey_size']
|
||||
if actual_hostkey_size != expected_hostkey_size:
|
||||
actual_hostkey_size = cast(int, server_host_keys[hostkey_type]['hostkey_size'])
|
||||
if actual_hostkey_size < expected_hostkey_size:
|
||||
ret = False
|
||||
self._append_error(errors, 'Host key (%s) sizes' % hostkey_type, [str(expected_hostkey_size)], None, [str(actual_hostkey_size)])
|
||||
|
||||
@@ -439,7 +441,7 @@ macs = %s
|
||||
ret = False
|
||||
self._append_error(errors, 'CA signature type', [expected_ca_key_type], None, [actual_ca_key_type])
|
||||
# Ensure that the actual and expected signature sizes match.
|
||||
elif actual_ca_key_size != expected_ca_key_size:
|
||||
elif actual_ca_key_size < expected_ca_key_size:
|
||||
ret = False
|
||||
self._append_error(errors, 'CA signature size (%s)' % actual_ca_key_type, [str(expected_ca_key_size)], None, [str(actual_ca_key_size)])
|
||||
|
||||
@@ -462,7 +464,7 @@ macs = %s
|
||||
expected_dh_modulus_size = self._dh_modulus_sizes[dh_modulus_type]
|
||||
if dh_modulus_type in kex.dh_modulus_sizes():
|
||||
actual_dh_modulus_size = kex.dh_modulus_sizes()[dh_modulus_type]
|
||||
if expected_dh_modulus_size != actual_dh_modulus_size:
|
||||
if expected_dh_modulus_size > actual_dh_modulus_size:
|
||||
ret = False
|
||||
self._append_error(errors, 'Group exchange (%s) modulus sizes' % dh_modulus_type, [str(expected_dh_modulus_size)], None, [str(actual_dh_modulus_size)])
|
||||
|
||||
|
||||
+23
-21
@@ -39,7 +39,7 @@ from typing import cast, Callable, Optional, Union, Any # noqa: F401
|
||||
from ssh_audit.globals import SNAP_PACKAGE
|
||||
from ssh_audit.globals import SNAP_PERMISSIONS_ERROR
|
||||
from ssh_audit.globals import VERSION
|
||||
from ssh_audit.globals import WINDOWS_MAN_PAGE
|
||||
from ssh_audit.globals import BUILTIN_MAN_PAGE
|
||||
from ssh_audit.algorithm import Algorithm
|
||||
from ssh_audit.algorithms import Algorithms
|
||||
from ssh_audit.auditconf import AuditConf
|
||||
@@ -106,7 +106,8 @@ def usage(uout: OutputBuffer, err: Optional[str] = None) -> None:
|
||||
uout.info(' --lookup=<alg1,alg2,...> looks up an algorithm(s) without\n connecting to a server')
|
||||
uout.info(' -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')
|
||||
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(' -t, --timeout=<secs> timeout (in seconds) for connection and reading\n (default: 5)')
|
||||
@@ -364,11 +365,8 @@ def output_recommendations(out: OutputBuffer, algs: Algorithms, algorithm_recomm
|
||||
for cve_list in VersionVulnerabilityDB.CVE['PuTTY']:
|
||||
vuln_version = float(cve_list[1])
|
||||
cvssv2_severity = cve_list[4]
|
||||
|
||||
if vuln_version > max_vuln_version:
|
||||
max_vuln_version = vuln_version
|
||||
if cvssv2_severity > max_cvssv2_severity:
|
||||
max_cvssv2_severity = cvssv2_severity
|
||||
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:
|
||||
@@ -491,7 +489,7 @@ def post_process_findings(banner: Optional[Banner], algs: Algorithms, client_aud
|
||||
if algs.ssh2kex is not None:
|
||||
ciphers_supported = algs.ssh2kex.client.encryption if client_audit else algs.ssh2kex.server.encryption
|
||||
for cipher in ciphers_supported:
|
||||
if cipher.endswith("-cbc"):
|
||||
if cipher.endswith("-cbc") or cipher.endswith("-cbc@openssh.org") or cipher.endswith("-cbc@ssh.com") or cipher == "rijndael-cbc@lysator.liu.se":
|
||||
ret.append(cipher)
|
||||
|
||||
return ret
|
||||
@@ -501,7 +499,7 @@ def post_process_findings(banner: Optional[Banner], algs: Algorithms, client_aud
|
||||
ret = []
|
||||
|
||||
for cipher in db["enc"]:
|
||||
if cipher.endswith("-cbc") and cipher not in _get_cbc_ciphers_enabled(algs):
|
||||
if (cipher.endswith("-cbc") or cipher.endswith("-cbc@openssh.org") or cipher.endswith("-cbc@ssh.com") or cipher == "rijndael-cbc@lysator.liu.se") and cipher not in _get_cbc_ciphers_enabled(algs):
|
||||
ret.append(cipher)
|
||||
|
||||
return ret
|
||||
@@ -814,6 +812,7 @@ def list_policies(out: OutputBuffer) -> None:
|
||||
out.fail("Error: no built-in policies found!")
|
||||
else:
|
||||
out.info("\nHint: Use -P and provide the full name of a policy to run a policy scan with.\n")
|
||||
out.info("Note: the general OpenSSH policies apply to the official releases only. OS distributions may back-port changes that cause failures (for example, Debian 11 back-ported the strict KEX mode into their package of OpenSSH v8.4, whereas it was only officially added to OpenSSH v9.6 and later). In these cases, consider creating a custom policy (-M option).\n")
|
||||
out.write()
|
||||
|
||||
|
||||
@@ -857,6 +856,11 @@ def process_commandline(out: OutputBuffer, args: List[str], usage_cb: Callable[.
|
||||
aconf = AuditConf()
|
||||
|
||||
enable_colors = not any(i in args for i in ['--no-colors', '-n'])
|
||||
|
||||
# Disable colors if the NO_COLOR environment variable is set.
|
||||
if "NO_COLOR" in os.environ:
|
||||
enable_colors = False
|
||||
|
||||
aconf.colors = enable_colors
|
||||
out.use_colors = enable_colors
|
||||
|
||||
@@ -1409,23 +1413,21 @@ def target_worker_thread(host: str, port: int, shared_aconf: AuditConf) -> Tuple
|
||||
return ret, string_output
|
||||
|
||||
|
||||
def windows_manual(out: OutputBuffer) -> int:
|
||||
'''Prints the man page on Windows. Returns an exitcodes.* flag.'''
|
||||
def builtin_manual(out: OutputBuffer) -> int:
|
||||
'''Prints the man page (Docker, PyPI, Snap, and Windows builds only). Returns an exitcodes.* flag.'''
|
||||
|
||||
retval = exitcodes.GOOD
|
||||
|
||||
if sys.platform != 'win32':
|
||||
out.fail("The '-m' and '--manual' parameters are reserved for use on Windows only.\nUsers of other operating systems should read the man page.")
|
||||
retval = exitcodes.FAILURE
|
||||
return retval
|
||||
builtin_man_page = BUILTIN_MAN_PAGE
|
||||
if builtin_man_page == "":
|
||||
out.fail("The '-m' and '--manual' parameters are reserved for use in Docker, PyPI, Snap,\nand Windows builds only. Users of other platforms should read the system man\npage.")
|
||||
return exitcodes.FAILURE
|
||||
|
||||
# If colors are disabled, strip the ANSI color codes from the man page.
|
||||
windows_man_page = WINDOWS_MAN_PAGE
|
||||
if not out.use_colors:
|
||||
windows_man_page = re.sub(r'\x1b\[\d+?m', '', windows_man_page)
|
||||
builtin_man_page = re.sub(r'\x1b\[\d+?m', '', builtin_man_page)
|
||||
|
||||
out.info(windows_man_page)
|
||||
return retval
|
||||
out.info(builtin_man_page)
|
||||
return exitcodes.GOOD
|
||||
|
||||
|
||||
def get_permitted_syntax_for_gex_test() -> Dict[str, str]:
|
||||
@@ -1519,7 +1521,7 @@ def main() -> int:
|
||||
# to output a plain text version of the man page.
|
||||
if (sys.platform == 'win32') and ('colorama' not in sys.modules):
|
||||
out.use_colors = False
|
||||
retval = windows_manual(out)
|
||||
retval = builtin_manual(out)
|
||||
out.write()
|
||||
sys.exit(retval)
|
||||
|
||||
|
||||
@@ -246,8 +246,7 @@ class SSH_Socket(ReadBuf, WriteBuf):
|
||||
|
||||
def send_banner(self, banner: str) -> None:
|
||||
self.send(banner.encode() + b'\r\n')
|
||||
if self.__state < self.SM_BANNER_SENT:
|
||||
self.__state = self.SM_BANNER_SENT
|
||||
self.__state = max(self.__state, self.SM_BANNER_SENT)
|
||||
|
||||
def ensure_read(self, size: int) -> None:
|
||||
while self.unread_len < size:
|
||||
|
||||
@@ -122,7 +122,7 @@ class VersionVulnerabilityDB: # pylint: disable=too-few-public-methods
|
||||
['2.1', '4.1p1', 1, 'CVE-2005-2798', 5.0, 'leak data about authentication credentials'],
|
||||
['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 directoy traversal'],
|
||||
['3.0', '3.4p1', 1, 'CVE-2004-0175', 4.3, 'leak data through directory traversal'],
|
||||
['1.2', '3.9p1', 1, 'CVE-2003-1562', 7.6, 'leak data about authentication credentials'],
|
||||
['3.1p1', '3.7.1p1', 1, 'CVE-2003-0787', 7.5, 'privilege escalation via modifying stack'],
|
||||
['3.1p1', '3.7.1p1', 1, 'CVE-2003-0786', 10.0, 'privilege escalation via bypassing authentication'],
|
||||
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
.TH SSH-AUDIT 1 "March 13, 2022"
|
||||
.TH SSH-AUDIT 1 "February 16, 2024"
|
||||
.SH NAME
|
||||
\fBssh-audit\fP \- SSH server & client configuration auditor
|
||||
.SH SYNOPSIS
|
||||
@@ -104,7 +104,7 @@ Look up the security information of an algorithm(s) in the internal database. D
|
||||
.TP
|
||||
.B -m, \-\-manual
|
||||
.br
|
||||
Print the man page (Windows only).
|
||||
Print the man page (Docker, PyPI, Snap, and Windows builds only).
|
||||
|
||||
.TP
|
||||
.B -M, \-\-make-policy=<custom_policy.txt>
|
||||
@@ -114,7 +114,7 @@ Creates a policy based on the target server. Useful when other servers should b
|
||||
.TP
|
||||
.B -n, \-\-no-colors
|
||||
.br
|
||||
Disable color output.
|
||||
Disable color output. Automatically set when the NO_COLOR environment variable is set.
|
||||
|
||||
.TP
|
||||
.B -p, \-\-port=<port>
|
||||
|
||||
Reference in New Issue
Block a user