mirror of
https://github.com/jtesta/ssh-audit.git
synced 2025-07-06 05:57:50 -05:00
Compare commits
31 Commits
Author | SHA1 | Date | |
---|---|---|---|
3606863ebf | |||
cf1e069db4 | |||
1e1220807f | |||
0263769243 | |||
e6c31ee4f5 | |||
14e0ed0e00 | |||
0f21f2131c | |||
b6c64d296b | |||
1ec13c653e | |||
a401afd099 | |||
8a3ae321f1 | |||
e62b548677 | |||
fd85e247e7 | |||
e3a59a3e21 | |||
4ebefdf894 | |||
83544836c9 | |||
4c9b871f5c | |||
1d707276d7 | |||
166c93ace4 | |||
9759480ae4 | |||
fd3a1f7d41 | |||
08677d65b1 | |||
8c5493ae3e | |||
14af53cf04 | |||
bbf6204ce1 | |||
0df63c20ac | |||
209bcab427 | |||
eac81455a9 | |||
bce9e2b152 | |||
f5431559ff | |||
f2e6f1a71c |
3
.gitignore
vendored
3
.gitignore
vendored
@ -6,3 +6,6 @@ venv*/
|
||||
.coverage*
|
||||
reports/
|
||||
.scannerwork/
|
||||
pypi/sshaudit/LICENSE
|
||||
pypi/sshaudit/README.md
|
||||
pypi/sshaudit/sshaudit.py
|
||||
|
31
README.md
31
README.md
@ -5,10 +5,11 @@
|
||||
[](https://codecov.io/gh/arthepsy/ssh-audit)
|
||||
[](https://sq.evolutiongaming.com/dashboard?id=arthepsy-github%3Assh-audit%3Adevelop)
|
||||
-->
|
||||
**ssh-audit** is a tool for ssh server auditing.
|
||||
**ssh-audit** is a tool for ssh server & client configuration auditing.
|
||||
|
||||
## Features
|
||||
- SSH1 and SSH2 protocol server support;
|
||||
- analyze SSH client configuration;
|
||||
- grab banner, recognize device or software and operating system, detect compression;
|
||||
- gather key-exchange, host-key, encryption and message authentication code algorithms;
|
||||
- output algorithm information (available since, removed/disabled, unsafe/weak/legacy, etc);
|
||||
@ -16,11 +17,11 @@
|
||||
- output security information (related issues, assigned CVE list, etc);
|
||||
- analyze SSH version compatibility based on algorithm information;
|
||||
- historical information from OpenSSH, Dropbear SSH and libssh;
|
||||
- no dependencies, compatible with Python 2.6+, Python 3.x and PyPy;
|
||||
- no dependencies
|
||||
|
||||
## Usage
|
||||
```
|
||||
usage: ssh-audit.py [-1246pbnvlt] <host>
|
||||
usage: ssh-audit.py [-1246pbcnjvlt] <host>
|
||||
|
||||
-1, --ssh1 force ssh version 1 only
|
||||
-2, --ssh2 force ssh version 2 only
|
||||
@ -28,7 +29,11 @@ usage: ssh-audit.py [-1246pbnvlt] <host>
|
||||
-6, --ipv6 enable IPv6 (order of precedence)
|
||||
-p, --port=<port> port to connect
|
||||
-b, --batch batch output
|
||||
-c, --client-audit starts a server on port 2222 to audit client
|
||||
software config (use -p to change port;
|
||||
use -t to change timeout)
|
||||
-n, --no-colors disable colors
|
||||
-j, --json JSON output
|
||||
-v, --verbose verbose output
|
||||
-l, --level=<level> minimum output level (info|warn|fail)
|
||||
-t, --timeout=<secs> timeout (in seconds) for connection and reading
|
||||
@ -38,10 +43,24 @@ usage: ssh-audit.py [-1246pbnvlt] <host>
|
||||
* batch flag `-b` will output sections without header and without empty lines (implies verbose flag).
|
||||
* verbose flag `-v` will prefix each line with section type and algorithm name.
|
||||
|
||||
### Example
|
||||

|
||||
### Server Audit Example
|
||||
Below is a screen shot of the server-auditing output when connecting to an unhardened OpenSSH v5.3 service:
|
||||

|
||||
|
||||
### Client Audit Example
|
||||
Below is a screen shot of the client-auditing output when an unhardened OpenSSH v7.2 client connects:
|
||||

|
||||
|
||||
## ChangeLog
|
||||
### v2.1.0 (2019-11-14)
|
||||
- Added client software auditing functionality (see `-c` / `--client-audit` option).
|
||||
- Added JSON output option (see `-j` / `--json` option; credit [Andreas Jaggi](https://github.com/x-way)).
|
||||
- Fixed crash while scanning Solaris Sun_SSH.
|
||||
- Added 9 new key exchanges: `gss-group1-sha1-toWM5Slw5Ew8Mqkay+al2g==`, `gss-gex-sha1-toWM5Slw5Ew8Mqkay+al2g==`, `gss-group14-sha1-`, `gss-group14-sha1-toWM5Slw5Ew8Mqkay+al2g==`, `gss-group14-sha256-toWM5Slw5Ew8Mqkay+al2g==`, `gss-group15-sha512-toWM5Slw5Ew8Mqkay+al2g==`, `diffie-hellman-group15-sha256`, `ecdh-sha2-1.3.132.0.10`, `curve448-sha512`.
|
||||
- Added 1 new host key type: `ecdsa-sha2-1.3.132.0.10`.
|
||||
- Added 4 new ciphers: `idea-cbc`, `serpent128-cbc`, `serpent192-cbc`, `serpent256-cbc`.
|
||||
- Added 6 new MACs: `hmac-sha2-256-96-etm@openssh.com`, `hmac-sha2-512-96-etm@openssh.com`, `hmac-ripemd`, `hmac-sha256-96@ssh.com`, `umac-32@openssh.com`, `umac-96@openssh.com`.
|
||||
|
||||
### v2.0.0 (2019-08-29)
|
||||
- Forked from https://github.com/arthepsy/ssh-audit (development was stalled, and developer went MIA).
|
||||
- Added RSA host key length test.
|
||||
@ -51,7 +70,7 @@ usage: ssh-audit.py [-1246pbnvlt] <host>
|
||||
- Added 5 new key exchanges: `sntrup4591761x25519-sha512@tinyssh.org`, `diffie-hellman-group-exchange-sha256@ssh.com`, `diffie-hellman-group-exchange-sha512@ssh.com`, `diffie-hellman-group16-sha256`, `diffie-hellman-group17-sha512`.
|
||||
- Added 3 new encryption algorithms: `des-cbc-ssh1`, `blowfish-ctr`, `twofish-ctr`.
|
||||
- Added 10 new MACs: `hmac-sha2-56`, `hmac-sha2-224`, `hmac-sha2-384`, `hmac-sha3-256`, `hmac-sha3-384`, `hmac-sha3-512`, `hmac-sha256`, `hmac-sha256@ssh.com`, `hmac-sha512`, `hmac-512@ssh.com`.
|
||||
- Added command line argument (-t / --timeout) for connection & reading timeouts.
|
||||
- Added command line argument (`-t` / `--timeout`) for connection & reading timeouts.
|
||||
- Updated CVEs for libssh & Dropbear.
|
||||
|
||||
### v1.7.0 (2016-10-26)
|
||||
|
@ -385,24 +385,32 @@ function run_test {
|
||||
options=$4
|
||||
|
||||
server_exec=
|
||||
test_result=
|
||||
expected_result=
|
||||
test_result_stdout=
|
||||
test_result_json=
|
||||
expected_result_stdout=
|
||||
expected_result_json=
|
||||
test_name=
|
||||
if [[ $server_type == 'OpenSSH' ]]; then
|
||||
server_exec="/openssh/sshd-${version} -D -f /etc/ssh/sshd_config-${version}_${test_number}"
|
||||
test_result="${TEST_RESULT_DIR}/openssh_${version}_${test_number}.txt"
|
||||
expected_result="test/docker/expected_results/openssh_${version}_${test_number}.txt"
|
||||
test_result_stdout="${TEST_RESULT_DIR}/openssh_${version}_${test_number}.txt"
|
||||
test_result_json="${TEST_RESULT_DIR}/openssh_${version}_${test_number}.json"
|
||||
expected_result_stdout="test/docker/expected_results/openssh_${version}_${test_number}.txt"
|
||||
expected_result_json="test/docker/expected_results/openssh_${version}_${test_number}.json"
|
||||
test_name="OpenSSH ${version} ${test_number}"
|
||||
options=
|
||||
elif [[ $server_type == 'Dropbear' ]]; then
|
||||
server_exec="/dropbear/dropbear-${version} -F ${options}"
|
||||
test_result="${TEST_RESULT_DIR}/dropbear_${version}_${test_number}.txt"
|
||||
expected_result="test/docker/expected_results/dropbear_${version}_${test_number}.txt"
|
||||
test_result_stdout="${TEST_RESULT_DIR}/dropbear_${version}_${test_number}.txt"
|
||||
test_result_json="${TEST_RESULT_DIR}/dropbear_${version}_${test_number}.json"
|
||||
expected_result_stdout="test/docker/expected_results/dropbear_${version}_${test_number}.txt"
|
||||
expected_result_json="test/docker/expected_results/dropbear_${version}_${test_number}.json"
|
||||
test_name="Dropbear ${version} ${test_number}"
|
||||
elif [[ $server_type == 'TinySSH' ]]; then
|
||||
server_exec="/usr/bin/tcpserver -HRDl0 0.0.0.0 22 /tinysshd/tinyssh-20190101 -v /etc/tinyssh/"
|
||||
test_result="${TEST_RESULT_DIR}/tinyssh_${version}_${test_number}.txt"
|
||||
expected_result="test/docker/expected_results/tinyssh_${version}_${test_number}.txt"
|
||||
test_result_stdout="${TEST_RESULT_DIR}/tinyssh_${version}_${test_number}.txt"
|
||||
test_result_json="${TEST_RESULT_DIR}/tinyssh_${version}_${test_number}.json"
|
||||
expected_result_stdout="test/docker/expected_results/tinyssh_${version}_${test_number}.txt"
|
||||
expected_result_json="test/docker/expected_results/tinyssh_${version}_${test_number}.json"
|
||||
test_name="TinySSH ${version} ${test_number}"
|
||||
fi
|
||||
|
||||
@ -412,7 +420,14 @@ function run_test {
|
||||
exit 1
|
||||
fi
|
||||
|
||||
./ssh-audit.py localhost:2222 > $test_result
|
||||
./ssh-audit.py localhost:2222 > $test_result_stdout
|
||||
if [[ $? != 0 ]]; then
|
||||
echo -e "${REDB}Failed to run ssh-audit.py! (exit code: $?)${CLR}"
|
||||
docker container stop $cid > /dev/null
|
||||
exit 1
|
||||
fi
|
||||
|
||||
./ssh-audit.py -j localhost:2222 > $test_result_json
|
||||
if [[ $? != 0 ]]; then
|
||||
echo -e "${REDB}Failed to run ssh-audit.py! (exit code: $?)${CLR}"
|
||||
docker container stop $cid > /dev/null
|
||||
@ -429,19 +444,25 @@ function run_test {
|
||||
# we need to filter out the banner part of the output so we get stable, repeatable
|
||||
# results.
|
||||
if [[ $server_type == 'TinySSH' ]]; then
|
||||
grep -v "(gen) banner: " ${test_result} > "${test_result}.tmp"
|
||||
mv "${test_result}.tmp" ${test_result}
|
||||
grep -v "(gen) banner: " ${test_result_stdout} > "${test_result_stdout}.tmp"
|
||||
mv "${test_result_stdout}.tmp" ${test_result_stdout}
|
||||
cat "${test_result_json}" | perl -pe 's/"comments": ".*?"/"comments": ""/' | perl -pe 's/"raw": ".+?"/"raw": ""/' > "${test_result_json}.tmp"
|
||||
mv "${test_result_json}.tmp" ${test_result_json}
|
||||
fi
|
||||
|
||||
diff=`diff -u ${expected_result} ${test_result}`
|
||||
if [[ $? == 0 ]]; then
|
||||
echo -e "${test_name} ${GREEN}passed${CLR}."
|
||||
else
|
||||
diff=`diff -u ${expected_result_stdout} ${test_result_stdout}`
|
||||
if [[ $? != 0 ]]; then
|
||||
echo -e "${test_name} ${REDB}FAILED${CLR}.\n\n${diff}\n"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
diff=`diff -u ${expected_result_json} ${test_result_json}`
|
||||
if [[ $? != 0 ]]; then
|
||||
echo -e "${test_name} ${REDB}FAILED${CLR}.\n\n${diff}\n"
|
||||
exit 1
|
||||
fi
|
||||
echo -e "${test_name} ${GREEN}passed${CLR}."
|
||||
}
|
||||
|
||||
|
||||
# First check if docker is functional.
|
||||
|
1
pypi/MANIFEST.in
Normal file
1
pypi/MANIFEST.in
Normal file
@ -0,0 +1 @@
|
||||
include sshaudit/LICENSE
|
14
pypi/Makefile
Normal file
14
pypi/Makefile
Normal file
@ -0,0 +1,14 @@
|
||||
all:
|
||||
cp ../ssh-audit.py sshaudit/sshaudit.py
|
||||
cp ../LICENSE sshaudit/LICENSE
|
||||
cp ../README.md sshaudit/README.md
|
||||
python3 setup.py sdist bdist_wheel
|
||||
|
||||
uploadtest:
|
||||
twine upload --repository-url https://test.pypi.org/legacy/ dist/*
|
||||
|
||||
uploadprod:
|
||||
twine upload dist/*
|
||||
|
||||
clean:
|
||||
rm -rf build/ dist/ *.egg-info/ sshaudit/sshaudit.py sshaudit/LICENSE sshaudit/README.md
|
38
pypi/setup.py
Normal file
38
pypi/setup.py
Normal file
@ -0,0 +1,38 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
import re
|
||||
from setuptools import setup
|
||||
|
||||
|
||||
version = re.search('^VERSION\s*=\s*\'v(\d\.\d\.\d)\'', open('sshaudit/sshaudit.py').read(), re.M).group(1)
|
||||
print("\n\nPackaging ssh-audit v%s...\n\n" % version)
|
||||
|
||||
with open("sshaudit/README.md", "rb") as f:
|
||||
long_descr = f.read().decode("utf-8")
|
||||
|
||||
|
||||
setup(
|
||||
name = "ssh-audit",
|
||||
packages = ["sshaudit"],
|
||||
license = 'MIT',
|
||||
entry_points = {
|
||||
"console_scripts": ['ssh-audit = sshaudit.sshaudit:main']
|
||||
},
|
||||
version = version,
|
||||
description = "An SSH server configuration security auditing tool",
|
||||
long_description = long_descr,
|
||||
long_description_content_type = "text/markdown",
|
||||
author = "Joe Testa",
|
||||
author_email = "jtesta@positronsecurity.com",
|
||||
url = "https://github.com/jtesta/ssh-audit",
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Intended Audience :: Information Technology",
|
||||
"Intended Audience :: System Administrators",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Topic :: Security",
|
||||
"Topic :: Security :: Cryptography"
|
||||
])
|
0
pypi/sshaudit/__init__.py
Normal file
0
pypi/sshaudit/__init__.py
Normal file
4
pypi/sshaudit/__main__.py
Normal file
4
pypi/sshaudit/__main__.py
Normal file
@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from .sshaudit import main
|
||||
main()
|
352
ssh-audit.py
352
ssh-audit.py
@ -25,9 +25,10 @@
|
||||
THE SOFTWARE.
|
||||
"""
|
||||
from __future__ import print_function
|
||||
import binascii, os, io, sys, socket, struct, random, errno, getopt, re, hashlib, base64
|
||||
import base64, binascii, errno, hashlib, getopt, io, os, random, re, select, socket, struct, sys, json
|
||||
|
||||
VERSION = 'v2.0.0'
|
||||
VERSION = 'v2.1.0'
|
||||
SSH_HEADER = 'SSH-{0}-OpenSSH_8.0' # SSH software to impersonate
|
||||
|
||||
if sys.version_info.major < 3:
|
||||
print("\n!!!! NOTE: Python 2 is being considered for deprecation. If you have a good reason to need continued Python 2 support, please e-mail jtesta@positronsecurity.com with your rationale.\n\n")
|
||||
@ -61,7 +62,7 @@ def usage(err=None):
|
||||
uout.head('# {0} {1}, https://github.com/jtesta/ssh-audit\n'.format(p, VERSION))
|
||||
if err is not None and len(err) > 0:
|
||||
uout.fail('\n' + err)
|
||||
uout.info('usage: {0} [-1246pbnvlt] <host>\n'.format(p))
|
||||
uout.info('usage: {0} [-1246pbcnjvlt] <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')
|
||||
@ -69,7 +70,9 @@ def usage(err=None):
|
||||
uout.info(' -6, --ipv6 enable IPv6 (order of precedence)')
|
||||
uout.info(' -p, --port=<port> port to connect')
|
||||
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(' -n, --no-colors disable colors')
|
||||
uout.info(' -j, --json JSON output')
|
||||
uout.info(' -v, --verbose verbose output')
|
||||
uout.info(' -l, --level=<level> minimum output level (info|warn|fail)')
|
||||
uout.info(' -t, --timeout=<secs> timeout (in seconds) for connection and reading\n (default: 5)')
|
||||
@ -86,18 +89,21 @@ class AuditConf(object):
|
||||
self.ssh1 = True
|
||||
self.ssh2 = True
|
||||
self.batch = False
|
||||
self.client_audit = False
|
||||
self.colors = True
|
||||
self.json = False
|
||||
self.verbose = False
|
||||
self.level = 'info'
|
||||
self.ipvo = () # type: Sequence[int]
|
||||
self.ipv4 = False
|
||||
self.ipv6 = False
|
||||
self.timeout = 5.0
|
||||
self.timeout_set = False # Set to True when the user explicitly sets it.
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
# type: (str, Union[str, int, bool, Sequence[int]]) -> None
|
||||
valid = False
|
||||
if name in ['ssh1', 'ssh2', 'batch', 'colors', 'verbose']:
|
||||
if name in ['ssh1', 'ssh2', 'batch', 'client_audit', 'colors', 'verbose', 'timeout_set', 'json']:
|
||||
valid, value = True, True if bool(value) else False
|
||||
elif name in ['ipv4', 'ipv6']:
|
||||
valid = False
|
||||
@ -144,10 +150,10 @@ class AuditConf(object):
|
||||
# pylint: disable=too-many-branches
|
||||
aconf = cls()
|
||||
try:
|
||||
sopts = 'h1246p:bnvl:t:'
|
||||
lopts = ['help', 'ssh1', 'ssh2', 'ipv4', 'ipv6', 'port',
|
||||
'batch', 'no-colors', 'verbose', 'level=', 'timeout=']
|
||||
opts, args = getopt.getopt(args, sopts, lopts)
|
||||
sopts = 'h1246p:bcnjvl:t:'
|
||||
lopts = ['help', 'ssh1', 'ssh2', 'ipv4', 'ipv6', 'port', 'json',
|
||||
'batch', 'client-audit', 'no-colors', 'verbose', 'level=', 'timeout=']
|
||||
opts, args = getopt.gnu_getopt(args, sopts, lopts)
|
||||
except getopt.GetoptError as err:
|
||||
usage_cb(str(err))
|
||||
aconf.ssh1, aconf.ssh2 = False, False
|
||||
@ -168,8 +174,12 @@ class AuditConf(object):
|
||||
elif o in ('-b', '--batch'):
|
||||
aconf.batch = True
|
||||
aconf.verbose = True
|
||||
elif o in ('-c', '--client-audit'):
|
||||
aconf.client_audit = True
|
||||
elif o in ('-n', '--no-colors'):
|
||||
aconf.colors = False
|
||||
elif o in ('-j', '--json'):
|
||||
aconf.json = True
|
||||
elif o in ('-v', '--verbose'):
|
||||
aconf.verbose = True
|
||||
elif o in ('-l', '--level'):
|
||||
@ -178,8 +188,10 @@ class AuditConf(object):
|
||||
aconf.level = a
|
||||
elif o in ('-t', '--timeout'):
|
||||
aconf.timeout = float(a)
|
||||
if len(args) == 0:
|
||||
aconf.timeout_set = True
|
||||
if len(args) == 0 and aconf.client_audit == False:
|
||||
usage_cb()
|
||||
if aconf.client_audit == False:
|
||||
if oport is not None:
|
||||
host = args[0]
|
||||
else:
|
||||
@ -192,9 +204,13 @@ class AuditConf(object):
|
||||
host, oport = args[0], '22'
|
||||
else:
|
||||
host, oport = s[0], s[1] if len(s) > 1 else '22'
|
||||
port = utils.parse_int(oport)
|
||||
if not host:
|
||||
usage_cb('host is empty')
|
||||
else:
|
||||
host = None
|
||||
if oport is None:
|
||||
oport = '2222'
|
||||
port = utils.parse_int(oport)
|
||||
if port <= 0 or port > 65535:
|
||||
usage_cb('port {0} is not valid'.format(oport))
|
||||
aconf.host = host
|
||||
@ -213,6 +229,7 @@ class Output(object):
|
||||
self.batch = False
|
||||
self.verbose = False
|
||||
self.use_colors = True
|
||||
self.json = False
|
||||
self.__level = 0
|
||||
self.__colsupport = 'colorama' in sys.modules or os.name == 'posix'
|
||||
|
||||
@ -303,6 +320,7 @@ class SSH2(object): # pylint: disable=too-few-public-methods
|
||||
FAIL_DEPRECATED_CIPHER = 'deprecated cipher'
|
||||
FAIL_WEAK_CIPHER = 'using weak cipher'
|
||||
FAIL_PLAINTEXT = 'no encryption/integrity'
|
||||
FAIL_DEPRECATED_MAC = 'deprecated MAC'
|
||||
WARN_CURVES_WEAK = 'using weak elliptic curves'
|
||||
WARN_RNDSIG_KEY = 'using weak random number generator could reveal the key'
|
||||
WARN_MODULUS_SIZE = 'using small 1024-bit modulus'
|
||||
@ -312,14 +330,22 @@ class SSH2(object): # pylint: disable=too-few-public-methods
|
||||
WARN_CIPHER_WEAK = 'using weak cipher'
|
||||
WARN_ENCRYPT_AND_MAC = 'using encrypt-and-MAC mode'
|
||||
WARN_TAG_SIZE = 'using small 64-bit tag size'
|
||||
WARN_TAG_SIZE_96 = 'using small 96-bit tag size'
|
||||
WARN_EXPERIMENTAL = 'using experimental algorithm'
|
||||
|
||||
ALGORITHMS = {
|
||||
# Format: 'algorithm_name': [['version_first_appeared_in'], [reason_for_failure1, reason_for_failure2, ...], [warning1, warning2, ...]]
|
||||
'kex': {
|
||||
'diffie-hellman-group1-sha1': [['2.3.0,d0.28,l10.2', '6.6', '6.9'], [FAIL_OPENSSH67_UNSAFE, FAIL_OPENSSH70_LOGJAM], [WARN_MODULUS_SIZE, WARN_HASH_WEAK]],
|
||||
'gss-group1-sha1-toWM5Slw5Ew8Mqkay+al2g==': [[], [FAIL_OPENSSH67_UNSAFE, FAIL_OPENSSH70_LOGJAM], [WARN_MODULUS_SIZE, WARN_HASH_WEAK]],
|
||||
'gss-gex-sha1-toWM5Slw5Ew8Mqkay+al2g==': [[], [], [WARN_HASH_WEAK]],
|
||||
'gss-group14-sha1-': [[], [], [WARN_HASH_WEAK]],
|
||||
'gss-group14-sha1-toWM5Slw5Ew8Mqkay+al2g==': [[], [], [WARN_HASH_WEAK]],
|
||||
'gss-group14-sha256-toWM5Slw5Ew8Mqkay+al2g==': [[]],
|
||||
'gss-group15-sha512-toWM5Slw5Ew8Mqkay+al2g==': [[]],
|
||||
'diffie-hellman-group14-sha1': [['3.9,d0.53,l10.6.0'], [], [WARN_HASH_WEAK]],
|
||||
'diffie-hellman-group14-sha256': [['7.3,d2016.73']],
|
||||
'diffie-hellman-group15-sha256': [[]],
|
||||
'diffie-hellman-group15-sha512': [[]],
|
||||
'diffie-hellman-group16-sha256': [[]],
|
||||
'diffie-hellman-group16-sha512': [['7.3,d2016.73']],
|
||||
@ -332,12 +358,16 @@ class SSH2(object): # pylint: disable=too-few-public-methods
|
||||
'ecdh-sha2-nistp256': [['5.7,d2013.62,l10.6.0'], [WARN_CURVES_WEAK]],
|
||||
'ecdh-sha2-nistp384': [['5.7,d2013.62'], [WARN_CURVES_WEAK]],
|
||||
'ecdh-sha2-nistp521': [['5.7,d2013.62'], [WARN_CURVES_WEAK]],
|
||||
'ecdh-sha2-1.3.132.0.10': [[]], # ECDH over secp256k1 (i.e.: the Bitcoin curve)
|
||||
'curve25519-sha256@libssh.org': [['6.5,d2013.62,l10.6.0']],
|
||||
'curve25519-sha256': [['7.4']],
|
||||
'curve25519-sha256': [['7.4,d2018.76']],
|
||||
'curve448-sha512': [[]],
|
||||
'kexguess2@matt.ucc.asn.au': [['d2013.57']],
|
||||
'rsa1024-sha1': [[], [], [WARN_MODULUS_SIZE, WARN_HASH_WEAK]],
|
||||
'rsa2048-sha256': [[]],
|
||||
'sntrup4591761x25519-sha512@tinyssh.org': [['8.0'], [], [WARN_EXPERIMENTAL]],
|
||||
'ext-info-c': [[]], # Extension negotiation (RFC 8308)
|
||||
'ext-info-s': [[]], # Extension negotiation (RFC 8308)
|
||||
},
|
||||
'key': {
|
||||
'rsa-sha2-256': [['7.2']],
|
||||
@ -357,6 +387,7 @@ class SSH2(object): # pylint: disable=too-few-public-methods
|
||||
'ecdsa-sha2-nistp384-cert-v01@openssh.com': [['5.7'], [WARN_CURVES_WEAK], [WARN_RNDSIG_KEY]],
|
||||
'ecdsa-sha2-nistp521-cert-v01@openssh.com': [['5.7'], [WARN_CURVES_WEAK], [WARN_RNDSIG_KEY]],
|
||||
'ssh-rsa-sha256@ssh.com': [[]],
|
||||
'ecdsa-sha2-1.3.132.0.10': [[], [], [WARN_RNDSIG_KEY]], # ECDSA over secp256k1 (i.e.: the Bitcoin curve)
|
||||
},
|
||||
'enc': {
|
||||
'none': [['1.2.2,d2013.56,l10.2'], [FAIL_PLAINTEXT]],
|
||||
@ -374,9 +405,13 @@ class SSH2(object): # pylint: disable=too-few-public-methods
|
||||
'twofish128-ctr': [['d2015.68']],
|
||||
'twofish192-ctr': [[]],
|
||||
'twofish256-ctr': [['d2015.68']],
|
||||
'serpent128-cbc': [[], [FAIL_DEPRECATED_CIPHER], [WARN_CIPHER_MODE]],
|
||||
'serpent192-cbc': [[], [FAIL_DEPRECATED_CIPHER], [WARN_CIPHER_MODE]],
|
||||
'serpent256-cbc': [[], [FAIL_DEPRECATED_CIPHER], [WARN_CIPHER_MODE]],
|
||||
'serpent128-ctr': [[], [FAIL_DEPRECATED_CIPHER]],
|
||||
'serpent192-ctr': [[], [FAIL_DEPRECATED_CIPHER]],
|
||||
'serpent256-ctr': [[], [FAIL_DEPRECATED_CIPHER]],
|
||||
'idea-cbc': [[], [FAIL_DEPRECATED_CIPHER], [WARN_CIPHER_MODE]],
|
||||
'idea-ctr': [[], [FAIL_DEPRECATED_CIPHER]],
|
||||
'cast128-ctr': [[], [FAIL_DEPRECATED_CIPHER]],
|
||||
'cast128-cbc': [['2.1.0', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
|
||||
@ -412,23 +447,29 @@ class SSH2(object): # pylint: disable=too-few-public-methods
|
||||
'hmac-sha3-384': [[], [], [WARN_ENCRYPT_AND_MAC]],
|
||||
'hmac-sha3-512': [[], [], [WARN_ENCRYPT_AND_MAC]],
|
||||
'hmac-sha256': [[], [], [WARN_ENCRYPT_AND_MAC]],
|
||||
'hmac-sha256-96@ssh.com': [[], [], [WARN_ENCRYPT_AND_MAC, WARN_TAG_SIZE]],
|
||||
'hmac-sha256@ssh.com': [[], [], [WARN_ENCRYPT_AND_MAC]],
|
||||
'hmac-sha512': [[], [], [WARN_ENCRYPT_AND_MAC]],
|
||||
'hmac-sha512@ssh.com': [[], [], [WARN_ENCRYPT_AND_MAC]],
|
||||
'hmac-md5': [['2.1.0,d0.28', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_ENCRYPT_AND_MAC, WARN_HASH_WEAK]],
|
||||
'hmac-md5-96': [['2.5.0', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_ENCRYPT_AND_MAC, WARN_HASH_WEAK]],
|
||||
'hmac-ripemd': [[], [FAIL_DEPRECATED_MAC], [WARN_OPENSSH72_LEGACY, WARN_ENCRYPT_AND_MAC]],
|
||||
'hmac-ripemd160': [['2.5.0', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_ENCRYPT_AND_MAC]],
|
||||
'hmac-ripemd160@openssh.com': [['2.1.0', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_ENCRYPT_AND_MAC]],
|
||||
'umac-64@openssh.com': [['4.7'], [], [WARN_ENCRYPT_AND_MAC, WARN_TAG_SIZE]],
|
||||
'umac-128@openssh.com': [['6.2'], [], [WARN_ENCRYPT_AND_MAC]],
|
||||
'hmac-sha1-etm@openssh.com': [['6.2'], [], [WARN_HASH_WEAK]],
|
||||
'hmac-sha1-96-etm@openssh.com': [['6.2', '6.6', None], [FAIL_OPENSSH67_UNSAFE], [WARN_HASH_WEAK]],
|
||||
'hmac-sha2-256-96-etm@openssh.com': [[], [], [WARN_TAG_SIZE_96]], # Despite the @openssh.com tag, it doesn't appear that this was ever shipped with OpenSSH; it is only implemented in AsyncSSH (?).
|
||||
'hmac-sha2-512-96-etm@openssh.com': [[], [], [WARN_TAG_SIZE_96]], # Despite the @openssh.com tag, it doesn't appear that this was ever shipped with OpenSSH; it is only implemented in AsyncSSH (?).
|
||||
'hmac-sha2-256-etm@openssh.com': [['6.2']],
|
||||
'hmac-sha2-512-etm@openssh.com': [['6.2']],
|
||||
'hmac-md5-etm@openssh.com': [['6.2', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_HASH_WEAK]],
|
||||
'hmac-md5-96-etm@openssh.com': [['6.2', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_HASH_WEAK]],
|
||||
'hmac-ripemd160-etm@openssh.com': [['6.2', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY]],
|
||||
'umac-32@openssh.com': [[], [], [WARN_ENCRYPT_AND_MAC, WARN_TAG_SIZE]], # Despite having the @openssh.com suffix, this may never have shipped with OpenSSH (!).
|
||||
'umac-64-etm@openssh.com': [['6.2'], [], [WARN_TAG_SIZE]],
|
||||
'umac-96@openssh.com': [[], [], [WARN_ENCRYPT_AND_MAC]], # Despite having the @openssh.com suffix, this may never have shipped with OpenSSH (!).
|
||||
'umac-128-etm@openssh.com': [['6.2']],
|
||||
}
|
||||
} # type: Dict[str, Dict[str, List[List[Optional[str]]]]]
|
||||
@ -1193,6 +1234,7 @@ class SSH(object): # pylint: disable=too-few-public-methods
|
||||
class Protocol(object): # pylint: disable=too-few-public-methods
|
||||
# pylint: disable=bad-whitespace
|
||||
SMSG_PUBLIC_KEY = 2
|
||||
MSG_DEBUG = 4
|
||||
MSG_KEXINIT = 20
|
||||
MSG_NEWKEYS = 21
|
||||
MSG_KEXDH_INIT = 30
|
||||
@ -1207,6 +1249,7 @@ class SSH(object): # pylint: disable=too-few-public-methods
|
||||
DropbearSSH = 'Dropbear SSH'
|
||||
LibSSH = 'libssh'
|
||||
TinySSH = 'TinySSH'
|
||||
PuTTY = 'PuTTY'
|
||||
|
||||
class Software(object):
|
||||
def __init__(self, vendor, product, version, patch, os_version):
|
||||
@ -1410,6 +1453,9 @@ class SSH(object): # pylint: disable=too-few-public-methods
|
||||
mx = re.match(r'^tinyssh_(.*)', software)
|
||||
if bool(mx):
|
||||
return cls(None, SSH.Product.TinySSH, mx.group(1), None, None)
|
||||
mx = re.match(r'^PuTTY_Release_(.*)', software)
|
||||
if bool(mx):
|
||||
return cls(None, SSH.Product.PuTTY, mx.group(1), None, None)
|
||||
return None
|
||||
|
||||
class Banner(object):
|
||||
@ -1900,7 +1946,19 @@ class SSH(object): # pylint: disable=too-few-public-methods
|
||||
['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)']]
|
||||
['1.2', '2.1', 1, 'CVE-2000-0525', 10.0, 'execute arbitrary code (improper privileges)']],
|
||||
'PuTTY': [
|
||||
['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'],
|
||||
]
|
||||
} # type: Dict[str, List[List[Any]]]
|
||||
TXT = {
|
||||
'Dropbear SSH': [
|
||||
@ -1917,16 +1975,17 @@ class SSH(object): # pylint: disable=too-few-public-methods
|
||||
|
||||
SM_BANNER_SENT = 1
|
||||
|
||||
def __init__(self, host, port, ipvo, timeout):
|
||||
def __init__(self, host, port, ipvo, timeout, timeout_set):
|
||||
# type: (Optional[str], int) -> None
|
||||
super(SSH.Socket, self).__init__()
|
||||
self.__sock = None # type: Optional[socket.socket]
|
||||
self.__sock_map = {}
|
||||
self.__block_size = 8
|
||||
self.__state = 0
|
||||
self.__header = [] # type: List[text_type]
|
||||
self.__banner = None # type: Optional[SSH.Banner]
|
||||
if host is None:
|
||||
raise ValueError('undefined host')
|
||||
# if host is None:
|
||||
# raise ValueError('undefined host')
|
||||
nport = utils.parse_int(port)
|
||||
if nport < 1 or nport > 65535:
|
||||
raise ValueError('invalid port: {0}'.format(port))
|
||||
@ -1937,6 +1996,7 @@ class SSH(object): # pylint: disable=too-few-public-methods
|
||||
else:
|
||||
self.__ipvo = ()
|
||||
self.__timeout = timeout
|
||||
self.__timeout_set = timeout_set
|
||||
|
||||
|
||||
def _resolve(self, ipvo):
|
||||
@ -1962,6 +2022,65 @@ class SSH(object): # pylint: disable=too-few-public-methods
|
||||
out.fail('[exception] {0}'.format(e))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# Listens on a server socket and accepts one connection (used for
|
||||
# auditing client connections).
|
||||
def listen_and_accept(self):
|
||||
|
||||
try:
|
||||
# Socket to listen on all IPv4 addresses.
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
s.bind(('0.0.0.0', self.__port))
|
||||
s.listen()
|
||||
self.__sock_map[s.fileno()] = s
|
||||
except Exception as e:
|
||||
print("Warning: failed to listen on any IPv4 interfaces.")
|
||||
pass
|
||||
|
||||
try:
|
||||
# Socket to listen on all IPv6 addresses.
|
||||
s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
|
||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
|
||||
s.bind(('::', self.__port))
|
||||
s.listen()
|
||||
self.__sock_map[s.fileno()] = s
|
||||
except Exception as e:
|
||||
print("Warning: failed to listen on any IPv6 interfaces.")
|
||||
pass
|
||||
|
||||
# If we failed to listen on any interfaces, terminate.
|
||||
if len(self.__sock_map.keys()) == 0:
|
||||
print("Error: failed to listen on any IPv4 and IPv6 interfaces!")
|
||||
exit(-1)
|
||||
|
||||
# Wait for an incoming connection. If a timeout was explicitly
|
||||
# set by the user, terminate when it elapses.
|
||||
fds = None
|
||||
time_elapsed = 0.0
|
||||
interval = 1.0
|
||||
while True:
|
||||
# Wait for a connection on either socket.
|
||||
fds = select.select(self.__sock_map.keys(), [], [], interval)
|
||||
time_elapsed += interval
|
||||
|
||||
# We have incoming data on at least one of the sockets.
|
||||
if len(fds[0]) > 0:
|
||||
break
|
||||
|
||||
if self.__timeout_set and time_elapsed >= self.__timeout:
|
||||
print("Timeout elapsed. Terminating...")
|
||||
exit(-1)
|
||||
|
||||
# Accept the connection.
|
||||
c, addr = self.__sock_map[fds[0][0]].accept()
|
||||
self.client_host = addr[0]
|
||||
self.client_port = addr[1]
|
||||
c.settimeout(self.__timeout)
|
||||
self.__sock = c
|
||||
|
||||
|
||||
def connect(self):
|
||||
# type: () -> None
|
||||
err = None
|
||||
@ -1988,15 +2107,15 @@ class SSH(object): # pylint: disable=too-few-public-methods
|
||||
# type: (int) -> Tuple[Optional[SSH.Banner], List[text_type], Optional[str]]
|
||||
if self.__sock is None:
|
||||
return self.__banner, self.__header, 'not connected'
|
||||
banner = 'SSH-{0}-OpenSSH_7.4'.format('1.5' if sshv == 1 else '2.0')
|
||||
rto = self.__sock.gettimeout()
|
||||
self.__sock.settimeout(0.7)
|
||||
s, e = self.recv()
|
||||
self.__sock.settimeout(rto)
|
||||
if s < 0:
|
||||
return self.__banner, self.__header, e
|
||||
banner = SSH_HEADER.format('1.5' if sshv == 1 else '2.0')
|
||||
if self.__state < self.SM_BANNER_SENT:
|
||||
self.send_banner(banner)
|
||||
# rto = self.__sock.gettimeout()
|
||||
# self.__sock.settimeout(0.7)
|
||||
s, e = self.recv()
|
||||
# self.__sock.settimeout(rto)
|
||||
if s < 0:
|
||||
return self.__banner, self.__header, e
|
||||
e = None
|
||||
while self.__banner is None:
|
||||
if not s > 0:
|
||||
@ -2153,6 +2272,8 @@ class SSH(object): # pylint: disable=too-few-public-methods
|
||||
def __cleanup(self):
|
||||
# type: () -> None
|
||||
self._close_socket(self.__sock)
|
||||
for fd in self.__sock_map:
|
||||
self._close_socket(self.__sock_map[fd])
|
||||
self.__sock = None
|
||||
|
||||
|
||||
@ -2200,6 +2321,11 @@ class KexDH(object): # pragma: nocover
|
||||
# key blob (from which the fingerprint can be calculated).
|
||||
def recv_reply(self, s, parse_host_key_size=True):
|
||||
packet_type, payload = s.read_packet(2)
|
||||
|
||||
# Skip any & all MSG_DEBUG messages.
|
||||
while packet_type == SSH.Protocol.MSG_DEBUG:
|
||||
packet_type, payload = s.read_packet(2)
|
||||
|
||||
if packet_type != -1 and packet_type not in [SSH.Protocol.MSG_KEXDH_REPLY, SSH.Protocol.MSG_KEXDH_GEX_REPLY]:
|
||||
# TODO: change Exception to something more specific.
|
||||
raise Exception('Expected MSG_KEXDH_REPLY (%d) or MSG_KEXDH_GEX_REPLY (%d), but got %d instead.' % (SSH.Protocol.MSG_KEXDH_REPLY, SSH.Protocol.MSG_KEXDH_GEX_REPLY, packet_type))
|
||||
@ -2517,10 +2643,14 @@ class KexGroupExchange(KexDH):
|
||||
s.send_packet()
|
||||
|
||||
packet_type, payload = s.read_packet(2)
|
||||
if packet_type != SSH.Protocol.MSG_KEXDH_GEX_GROUP:
|
||||
if (packet_type != SSH.Protocol.MSG_KEXDH_GEX_GROUP) and (packet_type != SSH.Protocol.MSG_DEBUG):
|
||||
# TODO: replace with a better exception type.
|
||||
raise Exception('Expected MSG_KEXDH_GEX_REPLY (%d), but got %d instead.' % (SSH.Protocol.MSG_KEXDH_GEX_REPLY, packet_type))
|
||||
|
||||
# Skip any & all MSG_DEBUG messages.
|
||||
while packet_type == SSH.Protocol.MSG_DEBUG:
|
||||
packet_type, payload = s.read_packet(2)
|
||||
|
||||
# Parse the modulus (p) and generator (g) values from the server.
|
||||
ptr = 0
|
||||
p_len = struct.unpack('>I', payload[ptr:ptr + 4])[0]
|
||||
@ -2624,8 +2754,13 @@ def output_algorithm(alg_db, alg_type, alg_name, unknown_algs, alg_max_len=0, al
|
||||
f(' ' * len(prefix + alg_name) + comment)
|
||||
|
||||
|
||||
def output_compatibility(algs, for_server=True):
|
||||
def output_compatibility(algs, client_audit, for_server=True):
|
||||
# type: (SSH.Algorithms, bool) -> None
|
||||
|
||||
# Don't output any compatibility info if we're doing a client audit.
|
||||
if client_audit:
|
||||
return
|
||||
|
||||
ssh_timeframe = algs.get_ssh_timeframe(for_server)
|
||||
comp_text = []
|
||||
for ssh_prod in [SSH.Product.OpenSSH, SSH.Product.DropbearSSH]:
|
||||
@ -2650,7 +2785,7 @@ def output_compatibility(algs, for_server=True):
|
||||
out.good('(gen) compatibility: ' + ', '.join(comp_text))
|
||||
|
||||
|
||||
def output_security_sub(sub, software, padlen):
|
||||
def output_security_sub(sub, software, client_audit, padlen):
|
||||
# type: (str, Optional[SSH.Software], int) -> None
|
||||
secdb = SSH.Security.CVE if sub == 'cve' else SSH.Security.TXT
|
||||
if software is None or software.product not in secdb:
|
||||
@ -2661,9 +2796,11 @@ def output_security_sub(sub, software, padlen):
|
||||
continue
|
||||
target, name = line[2:4] # type: int, str
|
||||
is_server = target & 1 == 1
|
||||
# is_client = target & 2 == 2
|
||||
is_client = target & 2 == 2
|
||||
# is_local = target & 4 == 4
|
||||
if not is_server:
|
||||
|
||||
# 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':
|
||||
@ -2679,13 +2816,13 @@ def output_security_sub(sub, software, padlen):
|
||||
out.fail('(sec) {0}{1} -- {2}'.format(name, p, descr))
|
||||
|
||||
|
||||
def output_security(banner, padlen):
|
||||
def output_security(banner, client_audit, padlen):
|
||||
# type: (Optional[SSH.Banner], int) -> None
|
||||
with OutputBuffer() as obuf:
|
||||
if banner is not None:
|
||||
software = SSH.Software.parse(banner)
|
||||
output_security_sub('cve', software, padlen)
|
||||
output_security_sub('txt', software, padlen)
|
||||
output_security_sub('cve', software, client_audit, padlen)
|
||||
output_security_sub('txt', software, client_audit, padlen)
|
||||
if len(obuf) > 0:
|
||||
out.head('# security')
|
||||
obuf.flush()
|
||||
@ -2730,8 +2867,39 @@ def output_fingerprints(algs, sha256=True):
|
||||
out.sep()
|
||||
|
||||
|
||||
# Returns True if no warnings or failures encountered in configuration.
|
||||
def output_recommendations(algs, software, padlen=0):
|
||||
# type: (SSH.Algorithms, Optional[SSH.Software], int) -> None
|
||||
|
||||
ret = True
|
||||
# PuTTY's algorithms cannot be modified, so there's no point in issuing recommendations.
|
||||
if (software is not None) and (software.product == SSH.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 SSH.Security.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
|
||||
|
||||
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
|
||||
|
||||
for_server = True
|
||||
with OutputBuffer() as obuf:
|
||||
software, alg_rec = algs.get_recommendations(software, for_server)
|
||||
@ -2749,12 +2917,14 @@ def output_recommendations(algs, software, padlen=0):
|
||||
chg_additional_info = ''
|
||||
if action == 'del':
|
||||
an, sg, fn = 'remove', '-', out.warn
|
||||
ret = False
|
||||
if alg_rec[sshv][alg_type][action][name] >= 10:
|
||||
fn = out.fail
|
||||
elif action == 'add':
|
||||
an, sg, fn = 'append', '+', out.good
|
||||
elif action == 'chg':
|
||||
an, sg, fn = 'change', '!', out.fail
|
||||
ret = False
|
||||
chg_additional_info = ' (increase modulus size to 2048 bits or larger)'
|
||||
b = '(SSH{0})'.format(sshv) if sshv == 1 else ''
|
||||
fm = '(rec) {0}{1}{2}-- {3} algorithm to {4}{5} {6}'
|
||||
@ -2767,9 +2937,27 @@ def output_recommendations(algs, software, padlen=0):
|
||||
out.head('# algorithm recommendations {0}'.format(title))
|
||||
obuf.flush(True) # Sort the output so that it is always stable (needed for repeatable testing).
|
||||
out.sep()
|
||||
return ret
|
||||
|
||||
|
||||
def output(banner, header, kex=None, pkm=None):
|
||||
# Output additional information & notes.
|
||||
def output_info(algs, software, client_audit, any_problems, padlen=0):
|
||||
with OutputBuffer() as obuf:
|
||||
# Tell user that PuTTY cannot be hardened at the protocol-level.
|
||||
if client_audit and (software is not None) and (software.product == SSH.Product.PuTTY):
|
||||
out.warn('(nfo) PuTTY does not have the option of restricting any algorithms during the SSH handshake.')
|
||||
|
||||
# If any warnings or failures were given, print a link to the hardening guides.
|
||||
if any_problems:
|
||||
out.warn('(nfo) For hardening guides on common OSes, please see: <https://www.ssh-audit.com/hardening_guides.html>')
|
||||
|
||||
if len(obuf) > 0:
|
||||
out.head('# additional info')
|
||||
obuf.flush()
|
||||
out.sep()
|
||||
|
||||
|
||||
def output(banner, header, client_audit=False, kex=None, pkm=None):
|
||||
# type: (Optional[SSH.Banner], List[text_type], Optional[SSH2.Kex], Optional[SSH1.PublicKeyMessage]) -> None
|
||||
sshv = 1 if pkm is not None else 2
|
||||
algs = SSH.Algorithms(pkm, kex)
|
||||
@ -2788,7 +2976,7 @@ def output(banner, header, kex=None, pkm=None):
|
||||
out.good('(gen) software: {0}'.format(software))
|
||||
else:
|
||||
software = None
|
||||
output_compatibility(algs)
|
||||
output_compatibility(algs, client_audit)
|
||||
if kex is not None:
|
||||
compressions = [x for x in kex.server.compression if x != 'none']
|
||||
if len(compressions) > 0:
|
||||
@ -2801,7 +2989,7 @@ def output(banner, header, kex=None, pkm=None):
|
||||
obuf.flush()
|
||||
out.sep()
|
||||
maxlen = algs.maxlen + 1
|
||||
output_security(banner, maxlen)
|
||||
output_security(banner, client_audit, maxlen)
|
||||
unknown_algorithms = [] # Filled in by output_algorithms() with unidentified algs.
|
||||
if pkm is not None:
|
||||
adb = SSH1.KexDB.ALGORITHMS
|
||||
@ -2824,7 +3012,9 @@ def output(banner, header, kex=None, pkm=None):
|
||||
title, atype = 'message authentication code algorithms', 'mac'
|
||||
output_algorithms(title, adb, atype, kex.server.mac, unknown_algorithms, maxlen)
|
||||
output_fingerprints(algs, True)
|
||||
output_recommendations(algs, software, maxlen)
|
||||
perfect_config = output_recommendations(algs, software, maxlen)
|
||||
output_info(algs, software, client_audit, not perfect_config)
|
||||
|
||||
|
||||
# If we encountered any unknown algorithms, ask the user to report them.
|
||||
if len(unknown_algorithms) > 0:
|
||||
@ -2952,6 +3142,80 @@ class Utils(object):
|
||||
except: # pylint: disable=bare-except
|
||||
return -1.0
|
||||
|
||||
def build_struct(banner, kex=None, pkm=None):
|
||||
res = {
|
||||
"banner": {
|
||||
"raw": str(banner),
|
||||
"protocol": banner.protocol,
|
||||
"software": banner.software,
|
||||
"comments": banner.comments,
|
||||
},
|
||||
}
|
||||
if kex is not None:
|
||||
res['compression'] = kex.server.compression
|
||||
|
||||
res['kex'] = []
|
||||
alg_sizes = kex.dh_modulus_sizes()
|
||||
for algorithm in kex.kex_algorithms:
|
||||
entry = {
|
||||
'algorithm': algorithm,
|
||||
}
|
||||
if (alg_sizes is not None) and (algorithm in alg_sizes):
|
||||
hostkey_size, ca_size = alg_sizes[algorithm]
|
||||
entry['keysize'] = hostkey_size
|
||||
if ca_size > 0:
|
||||
entry['casize'] = ca_size
|
||||
res['kex'].append(entry)
|
||||
|
||||
res['key'] = []
|
||||
alg_sizes = kex.rsa_key_sizes()
|
||||
for algorithm in kex.key_algorithms:
|
||||
entry = {
|
||||
'algorithm': algorithm,
|
||||
}
|
||||
if (alg_sizes is not None) and (algorithm in alg_sizes):
|
||||
hostkey_size, ca_size = alg_sizes[algorithm]
|
||||
entry['keysize'] = hostkey_size
|
||||
if ca_size > 0:
|
||||
entry['casize'] = ca_size
|
||||
res['key'].append(entry)
|
||||
|
||||
res['enc'] = kex.server.encryption
|
||||
res['mac'] = kex.server.mac
|
||||
res['fingerprints'] = []
|
||||
host_keys = kex.host_keys()
|
||||
|
||||
# Normalize all RSA key types to 'ssh-rsa'. Otherwise, due to Python's order-indifference dictionary types, we would iterate key types in unpredictable orders, which interferes with the docker testing framework (i.e.: tests would fail because elements are reported out of order, even though the output is semantically the same).
|
||||
for host_key_type in host_keys.keys():
|
||||
if host_key_type in SSH2.HostKeyTest.RSA_FAMILY:
|
||||
val = host_keys[host_key_type]
|
||||
del(host_keys[host_key_type])
|
||||
host_keys['ssh-rsa'] = val
|
||||
|
||||
for host_key_type in sorted(host_keys):
|
||||
if host_keys[host_key_type] is None:
|
||||
continue
|
||||
|
||||
fp = SSH.Fingerprint(host_keys[host_key_type])
|
||||
|
||||
# Skip over certificate host types (or we would return invalid fingerprints).
|
||||
if '-cert-' in host_key_type:
|
||||
continue
|
||||
entry = {
|
||||
'type': host_key_type,
|
||||
'fp': fp.sha256,
|
||||
}
|
||||
res['fingerprints'].append(entry)
|
||||
else:
|
||||
res['key'] = ['ssh-rsa1']
|
||||
res['enc'] = pkm.supported_ciphers
|
||||
res['aut'] = pkm.supported_authentications
|
||||
res['fingerprints'] = [{
|
||||
'type': 'ssh-rsa1',
|
||||
'fp': SSH.Fingerprint(pkm.host_key_fingerprint_data).sha256,
|
||||
}]
|
||||
|
||||
return res
|
||||
|
||||
def audit(aconf, sshv=None):
|
||||
# type: (AuditConf, Optional[int]) -> None
|
||||
@ -2959,7 +3223,10 @@ def audit(aconf, sshv=None):
|
||||
out.verbose = aconf.verbose
|
||||
out.level = aconf.level
|
||||
out.use_colors = aconf.colors
|
||||
s = SSH.Socket(aconf.host, aconf.port, aconf.ipvo, aconf.timeout)
|
||||
s = SSH.Socket(aconf.host, aconf.port, aconf.ipvo, aconf.timeout, aconf.timeout_set)
|
||||
if aconf.client_audit:
|
||||
s.listen_and_accept()
|
||||
else:
|
||||
s.connect()
|
||||
if sshv is None:
|
||||
sshv = 2 if aconf.ssh2 else 1
|
||||
@ -3001,16 +3268,27 @@ def audit(aconf, sshv=None):
|
||||
sys.exit(1)
|
||||
if sshv == 1:
|
||||
pkm = SSH1.PublicKeyMessage.parse(payload)
|
||||
if aconf.json:
|
||||
print(json.dumps(build_struct(banner, pkm=pkm), sort_keys=True))
|
||||
else:
|
||||
output(banner, header, pkm=pkm)
|
||||
elif sshv == 2:
|
||||
kex = SSH2.Kex.parse(payload)
|
||||
if aconf.client_audit is False:
|
||||
SSH2.HostKeyTest.run(s, kex)
|
||||
SSH2.GEXTest.run(s, kex)
|
||||
output(banner, header, kex=kex)
|
||||
if aconf.json:
|
||||
print(json.dumps(build_struct(banner, kex=kex), sort_keys=True))
|
||||
else:
|
||||
output(banner, header, client_audit=aconf.client_audit, kex=kex)
|
||||
|
||||
|
||||
utils = Utils()
|
||||
out = Output()
|
||||
if __name__ == '__main__': # pragma: nocover
|
||||
|
||||
def main():
|
||||
conf = AuditConf.from_cmdline(sys.argv[1:], usage)
|
||||
audit(conf)
|
||||
|
||||
if __name__ == '__main__': # pragma: nocover
|
||||
main()
|
||||
|
1
test/docker/expected_results/dropbear_2019.78_test1.json
Normal file
1
test/docker/expected_results/dropbear_2019.78_test1.json
Normal file
@ -0,0 +1 @@
|
||||
{"banner": {"comments": null, "protocol": [2, 0], "raw": "SSH-2.0-dropbear_2019.78", "software": "dropbear_2019.78"}, "compression": ["zlib@openssh.com", "none"], "enc": ["aes128-ctr", "aes256-ctr", "aes128-cbc", "aes256-cbc", "3des-ctr", "3des-cbc"], "fingerprints": [{"fp": "SHA256:CDfAU12pjQS7/91kg7gYacza0U/6PDbE04Ic3IpYxkM", "type": "ssh-rsa"}], "kex": [{"algorithm": "curve25519-sha256"}, {"algorithm": "curve25519-sha256@libssh.org"}, {"algorithm": "ecdh-sha2-nistp521"}, {"algorithm": "ecdh-sha2-nistp384"}, {"algorithm": "ecdh-sha2-nistp256"}, {"algorithm": "diffie-hellman-group14-sha256"}, {"algorithm": "diffie-hellman-group14-sha1"}, {"algorithm": "kexguess2@matt.ucc.asn.au"}], "key": [{"algorithm": "ecdsa-sha2-nistp256"}, {"algorithm": "ssh-rsa", "keysize": 1024}, {"algorithm": "ssh-dss"}], "mac": ["hmac-sha1-96", "hmac-sha1", "hmac-sha2-256"]}
|
@ -1,11 +1,11 @@
|
||||
[0;36m# general[0m
|
||||
[0;32m(gen) banner: SSH-2.0-dropbear_2019.78[0m
|
||||
[0;32m(gen) software: Dropbear SSH 2019.78[0m
|
||||
[0;32m(gen) compatibility: OpenSSH 7.4+ (some functionality from 6.6), Dropbear SSH 2016.73+[0m
|
||||
[0;32m(gen) compatibility: OpenSSH 7.4+ (some functionality from 6.6), Dropbear SSH 2018.76+[0m
|
||||
[0;32m(gen) compression: enabled (zlib@openssh.com)[0m
|
||||
|
||||
[0;36m# key exchange algorithms[0m
|
||||
[0;32m(kex) curve25519-sha256 -- [info] available since OpenSSH 7.4[0m
|
||||
[0;32m(kex) curve25519-sha256 -- [info] available since OpenSSH 7.4, Dropbear SSH 2018.76[0m
|
||||
[0;32m(kex) curve25519-sha256@libssh.org -- [info] available since OpenSSH 6.5, Dropbear SSH 2013.62[0m
|
||||
[0;31m(kex) ecdh-sha2-nistp521 -- [fail] using weak elliptic curves[0m
|
||||
`- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62
|
||||
@ -80,3 +80,6 @@
|
||||
[0;33m(rec) -diffie-hellman-group14-sha1 -- kex algorithm to remove [0m
|
||||
[0;33m(rec) -hmac-sha1 -- mac algorithm to remove [0m
|
||||
|
||||
[0;36m# additional info[0m
|
||||
[0;33m(nfo) For hardening guides on common OSes, please see: <https://www.ssh-audit.com/hardening_guides.html>[0m
|
||||
|
||||
|
1
test/docker/expected_results/openssh_4.0p1_test1.json
Normal file
1
test/docker/expected_results/openssh_4.0p1_test1.json
Normal file
@ -0,0 +1 @@
|
||||
{"banner": {"comments": null, "protocol": [1, 99], "raw": "SSH-1.99-OpenSSH_4.0", "software": "OpenSSH_4.0"}, "compression": ["none", "zlib"], "enc": ["aes128-cbc", "3des-cbc", "blowfish-cbc", "cast128-cbc", "arcfour", "aes192-cbc", "aes256-cbc", "rijndael-cbc@lysator.liu.se", "aes128-ctr", "aes192-ctr", "aes256-ctr"], "fingerprints": [{"fp": "SHA256:YZ457EBcJTSxRKI3yXRgtAj3PBf5B9/F36b1SVooml4", "type": "ssh-rsa"}], "kex": [{"algorithm": "diffie-hellman-group-exchange-sha1", "keysize": 1024}, {"algorithm": "diffie-hellman-group14-sha1"}, {"algorithm": "diffie-hellman-group1-sha1"}], "key": [{"algorithm": "ssh-rsa", "keysize": 1024}, {"algorithm": "ssh-dss"}], "mac": ["hmac-md5", "hmac-sha1", "hmac-ripemd160", "hmac-ripemd160@openssh.com", "hmac-sha1-96", "hmac-md5-96"]}
|
@ -134,3 +134,6 @@
|
||||
[0;31m(rec) -rijndael-cbc@lysator.liu.se -- enc algorithm to remove [0m
|
||||
[0;31m(rec) -ssh-dss -- key algorithm to remove [0m
|
||||
|
||||
[0;36m# additional info[0m
|
||||
[0;33m(nfo) For hardening guides on common OSes, please see: <https://www.ssh-audit.com/hardening_guides.html>[0m
|
||||
|
||||
|
1
test/docker/expected_results/openssh_5.6p1_test1.json
Normal file
1
test/docker/expected_results/openssh_5.6p1_test1.json
Normal file
@ -0,0 +1 @@
|
||||
{"banner": {"comments": null, "protocol": [2, 0], "raw": "SSH-2.0-OpenSSH_5.6", "software": "OpenSSH_5.6"}, "compression": ["none", "zlib@openssh.com"], "enc": ["aes128-ctr", "aes192-ctr", "aes256-ctr", "arcfour256", "arcfour128", "aes128-cbc", "3des-cbc", "blowfish-cbc", "cast128-cbc", "aes192-cbc", "aes256-cbc", "arcfour", "rijndael-cbc@lysator.liu.se"], "fingerprints": [{"fp": "SHA256:YZ457EBcJTSxRKI3yXRgtAj3PBf5B9/F36b1SVooml4", "type": "ssh-rsa"}], "kex": [{"algorithm": "diffie-hellman-group-exchange-sha256", "keysize": 1024}, {"algorithm": "diffie-hellman-group-exchange-sha1", "keysize": 1024}, {"algorithm": "diffie-hellman-group14-sha1"}, {"algorithm": "diffie-hellman-group1-sha1"}], "key": [{"algorithm": "ssh-rsa", "keysize": 1024}, {"algorithm": "ssh-dss"}], "mac": ["hmac-md5", "hmac-sha1", "umac-64@openssh.com", "hmac-ripemd160", "hmac-ripemd160@openssh.com", "hmac-sha1-96", "hmac-md5-96"]}
|
@ -143,3 +143,6 @@
|
||||
[0;31m(rec) -ssh-dss -- key algorithm to remove [0m
|
||||
[0;33m(rec) -diffie-hellman-group14-sha1 -- kex algorithm to remove [0m
|
||||
|
||||
[0;36m# additional info[0m
|
||||
[0;33m(nfo) For hardening guides on common OSes, please see: <https://www.ssh-audit.com/hardening_guides.html>[0m
|
||||
|
||||
|
1
test/docker/expected_results/openssh_5.6p1_test2.json
Normal file
1
test/docker/expected_results/openssh_5.6p1_test2.json
Normal file
@ -0,0 +1 @@
|
||||
{"banner": {"comments": null, "protocol": [2, 0], "raw": "SSH-2.0-OpenSSH_5.6", "software": "OpenSSH_5.6"}, "compression": ["none", "zlib@openssh.com"], "enc": ["aes128-ctr", "aes192-ctr", "aes256-ctr", "arcfour256", "arcfour128", "aes128-cbc", "3des-cbc", "blowfish-cbc", "cast128-cbc", "aes192-cbc", "aes256-cbc", "arcfour", "rijndael-cbc@lysator.liu.se"], "fingerprints": [{"fp": "SHA256:YZ457EBcJTSxRKI3yXRgtAj3PBf5B9/F36b1SVooml4", "type": "ssh-rsa"}], "kex": [{"algorithm": "diffie-hellman-group-exchange-sha256", "keysize": 1024}, {"algorithm": "diffie-hellman-group-exchange-sha1", "keysize": 1024}, {"algorithm": "diffie-hellman-group14-sha1"}, {"algorithm": "diffie-hellman-group1-sha1"}], "key": [{"algorithm": "ssh-rsa", "keysize": 1024}, {"algorithm": "ssh-rsa-cert-v01@openssh.com", "casize": 1024, "keysize": 1024}], "mac": ["hmac-md5", "hmac-sha1", "umac-64@openssh.com", "hmac-ripemd160", "hmac-ripemd160@openssh.com", "hmac-sha1-96", "hmac-md5-96"]}
|
@ -141,3 +141,6 @@
|
||||
[0;31m(rec) -rijndael-cbc@lysator.liu.se -- enc algorithm to remove [0m
|
||||
[0;33m(rec) -diffie-hellman-group14-sha1 -- kex algorithm to remove [0m
|
||||
|
||||
[0;36m# additional info[0m
|
||||
[0;33m(nfo) For hardening guides on common OSes, please see: <https://www.ssh-audit.com/hardening_guides.html>[0m
|
||||
|
||||
|
1
test/docker/expected_results/openssh_5.6p1_test3.json
Normal file
1
test/docker/expected_results/openssh_5.6p1_test3.json
Normal file
@ -0,0 +1 @@
|
||||
{"banner": {"comments": null, "protocol": [2, 0], "raw": "SSH-2.0-OpenSSH_5.6", "software": "OpenSSH_5.6"}, "compression": ["none", "zlib@openssh.com"], "enc": ["aes128-ctr", "aes192-ctr", "aes256-ctr", "arcfour256", "arcfour128", "aes128-cbc", "3des-cbc", "blowfish-cbc", "cast128-cbc", "aes192-cbc", "aes256-cbc", "arcfour", "rijndael-cbc@lysator.liu.se"], "fingerprints": [{"fp": "SHA256:YZ457EBcJTSxRKI3yXRgtAj3PBf5B9/F36b1SVooml4", "type": "ssh-rsa"}], "kex": [{"algorithm": "diffie-hellman-group-exchange-sha256", "keysize": 1024}, {"algorithm": "diffie-hellman-group-exchange-sha1", "keysize": 1024}, {"algorithm": "diffie-hellman-group14-sha1"}, {"algorithm": "diffie-hellman-group1-sha1"}], "key": [{"algorithm": "ssh-rsa", "keysize": 1024}, {"algorithm": "ssh-rsa-cert-v01@openssh.com", "casize": 3072, "keysize": 1024}], "mac": ["hmac-md5", "hmac-sha1", "umac-64@openssh.com", "hmac-ripemd160", "hmac-ripemd160@openssh.com", "hmac-sha1-96", "hmac-md5-96"]}
|
@ -141,3 +141,6 @@
|
||||
[0;31m(rec) -rijndael-cbc@lysator.liu.se -- enc algorithm to remove [0m
|
||||
[0;33m(rec) -diffie-hellman-group14-sha1 -- kex algorithm to remove [0m
|
||||
|
||||
[0;36m# additional info[0m
|
||||
[0;33m(nfo) For hardening guides on common OSes, please see: <https://www.ssh-audit.com/hardening_guides.html>[0m
|
||||
|
||||
|
1
test/docker/expected_results/openssh_5.6p1_test4.json
Normal file
1
test/docker/expected_results/openssh_5.6p1_test4.json
Normal file
@ -0,0 +1 @@
|
||||
{"banner": {"comments": null, "protocol": [2, 0], "raw": "SSH-2.0-OpenSSH_5.6", "software": "OpenSSH_5.6"}, "compression": ["none", "zlib@openssh.com"], "enc": ["aes128-ctr", "aes192-ctr", "aes256-ctr", "arcfour256", "arcfour128", "aes128-cbc", "3des-cbc", "blowfish-cbc", "cast128-cbc", "aes192-cbc", "aes256-cbc", "arcfour", "rijndael-cbc@lysator.liu.se"], "fingerprints": [{"fp": "SHA256:nsWtdJ9Z67Vrf7OsUzQov7esXhsWAfVppArGh25u244", "type": "ssh-rsa"}], "kex": [{"algorithm": "diffie-hellman-group-exchange-sha256", "keysize": 1024}, {"algorithm": "diffie-hellman-group-exchange-sha1", "keysize": 1024}, {"algorithm": "diffie-hellman-group14-sha1"}, {"algorithm": "diffie-hellman-group1-sha1"}], "key": [{"algorithm": "ssh-rsa", "keysize": 3072}, {"algorithm": "ssh-rsa-cert-v01@openssh.com", "casize": 1024, "keysize": 3072}], "mac": ["hmac-md5", "hmac-sha1", "umac-64@openssh.com", "hmac-ripemd160", "hmac-ripemd160@openssh.com", "hmac-sha1-96", "hmac-md5-96"]}
|
@ -139,3 +139,6 @@
|
||||
[0;31m(rec) -rijndael-cbc@lysator.liu.se -- enc algorithm to remove [0m
|
||||
[0;33m(rec) -diffie-hellman-group14-sha1 -- kex algorithm to remove [0m
|
||||
|
||||
[0;36m# additional info[0m
|
||||
[0;33m(nfo) For hardening guides on common OSes, please see: <https://www.ssh-audit.com/hardening_guides.html>[0m
|
||||
|
||||
|
1
test/docker/expected_results/openssh_5.6p1_test5.json
Normal file
1
test/docker/expected_results/openssh_5.6p1_test5.json
Normal file
@ -0,0 +1 @@
|
||||
{"banner": {"comments": null, "protocol": [2, 0], "raw": "SSH-2.0-OpenSSH_5.6", "software": "OpenSSH_5.6"}, "compression": ["none", "zlib@openssh.com"], "enc": ["aes128-ctr", "aes192-ctr", "aes256-ctr", "arcfour256", "arcfour128", "aes128-cbc", "3des-cbc", "blowfish-cbc", "cast128-cbc", "aes192-cbc", "aes256-cbc", "arcfour", "rijndael-cbc@lysator.liu.se"], "fingerprints": [{"fp": "SHA256:nsWtdJ9Z67Vrf7OsUzQov7esXhsWAfVppArGh25u244", "type": "ssh-rsa"}], "kex": [{"algorithm": "diffie-hellman-group-exchange-sha256", "keysize": 1024}, {"algorithm": "diffie-hellman-group-exchange-sha1", "keysize": 1024}, {"algorithm": "diffie-hellman-group14-sha1"}, {"algorithm": "diffie-hellman-group1-sha1"}], "key": [{"algorithm": "ssh-rsa", "keysize": 3072}, {"algorithm": "ssh-rsa-cert-v01@openssh.com", "casize": 3072, "keysize": 3072}], "mac": ["hmac-md5", "hmac-sha1", "umac-64@openssh.com", "hmac-ripemd160", "hmac-ripemd160@openssh.com", "hmac-sha1-96", "hmac-md5-96"]}
|
@ -137,3 +137,6 @@
|
||||
[0;31m(rec) -rijndael-cbc@lysator.liu.se -- enc algorithm to remove [0m
|
||||
[0;33m(rec) -diffie-hellman-group14-sha1 -- kex algorithm to remove [0m
|
||||
|
||||
[0;36m# additional info[0m
|
||||
[0;33m(nfo) For hardening guides on common OSes, please see: <https://www.ssh-audit.com/hardening_guides.html>[0m
|
||||
|
||||
|
1
test/docker/expected_results/openssh_8.0p1_test1.json
Normal file
1
test/docker/expected_results/openssh_8.0p1_test1.json
Normal file
@ -0,0 +1 @@
|
||||
{"banner": {"comments": null, "protocol": [2, 0], "raw": "SSH-2.0-OpenSSH_8.0", "software": "OpenSSH_8.0"}, "compression": ["none", "zlib@openssh.com"], "enc": ["chacha20-poly1305@openssh.com", "aes128-ctr", "aes192-ctr", "aes256-ctr", "aes128-gcm@openssh.com", "aes256-gcm@openssh.com"], "fingerprints": [{"fp": "SHA256:UrnXIVH+7dlw8UqYocl48yUEcKrthGDQG2CPCgp7MxU", "type": "ssh-ed25519"}, {"fp": "SHA256:nsWtdJ9Z67Vrf7OsUzQov7esXhsWAfVppArGh25u244", "type": "ssh-rsa"}], "kex": [{"algorithm": "curve25519-sha256"}, {"algorithm": "curve25519-sha256@libssh.org"}, {"algorithm": "ecdh-sha2-nistp256"}, {"algorithm": "ecdh-sha2-nistp384"}, {"algorithm": "ecdh-sha2-nistp521"}, {"algorithm": "diffie-hellman-group-exchange-sha256", "keysize": 2048}, {"algorithm": "diffie-hellman-group16-sha512"}, {"algorithm": "diffie-hellman-group18-sha512"}, {"algorithm": "diffie-hellman-group14-sha256"}, {"algorithm": "diffie-hellman-group14-sha1"}], "key": [{"algorithm": "rsa-sha2-512", "keysize": 3072}, {"algorithm": "rsa-sha2-256", "keysize": 3072}, {"algorithm": "ssh-rsa", "keysize": 3072}, {"algorithm": "ecdsa-sha2-nistp256"}, {"algorithm": "ssh-ed25519"}], "mac": ["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"]}
|
@ -1,11 +1,11 @@
|
||||
[0;36m# general[0m
|
||||
[0;32m(gen) banner: SSH-2.0-OpenSSH_8.0[0m
|
||||
[0;32m(gen) software: OpenSSH 8.0[0m
|
||||
[0;32m(gen) compatibility: OpenSSH 7.4+, Dropbear SSH 2016.73+[0m
|
||||
[0;32m(gen) compatibility: OpenSSH 7.4+, Dropbear SSH 2018.76+[0m
|
||||
[0;32m(gen) compression: enabled (zlib@openssh.com)[0m
|
||||
|
||||
[0;36m# key exchange algorithms[0m
|
||||
[0;32m(kex) curve25519-sha256 -- [info] available since OpenSSH 7.4[0m
|
||||
[0;32m(kex) curve25519-sha256 -- [info] available since OpenSSH 7.4, Dropbear SSH 2018.76[0m
|
||||
[0;32m(kex) curve25519-sha256@libssh.org -- [info] available since OpenSSH 6.5, Dropbear SSH 2013.62[0m
|
||||
[0;31m(kex) ecdh-sha2-nistp256 -- [fail] using weak elliptic curves[0m
|
||||
`- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62
|
||||
@ -77,3 +77,6 @@
|
||||
[0;33m(rec) -umac-64-etm@openssh.com -- mac algorithm to remove [0m
|
||||
[0;33m(rec) -umac-64@openssh.com -- mac algorithm to remove [0m
|
||||
|
||||
[0;36m# additional info[0m
|
||||
[0;33m(nfo) For hardening guides on common OSes, please see: <https://www.ssh-audit.com/hardening_guides.html>[0m
|
||||
|
||||
|
1
test/docker/expected_results/openssh_8.0p1_test2.json
Normal file
1
test/docker/expected_results/openssh_8.0p1_test2.json
Normal file
@ -0,0 +1 @@
|
||||
{"banner": {"comments": null, "protocol": [2, 0], "raw": "SSH-2.0-OpenSSH_8.0", "software": "OpenSSH_8.0"}, "compression": ["none", "zlib@openssh.com"], "enc": ["chacha20-poly1305@openssh.com", "aes128-ctr", "aes192-ctr", "aes256-ctr", "aes128-gcm@openssh.com", "aes256-gcm@openssh.com"], "fingerprints": [{"fp": "SHA256:UrnXIVH+7dlw8UqYocl48yUEcKrthGDQG2CPCgp7MxU", "type": "ssh-ed25519"}], "kex": [{"algorithm": "curve25519-sha256"}, {"algorithm": "curve25519-sha256@libssh.org"}, {"algorithm": "ecdh-sha2-nistp256"}, {"algorithm": "ecdh-sha2-nistp384"}, {"algorithm": "ecdh-sha2-nistp521"}, {"algorithm": "diffie-hellman-group-exchange-sha256", "keysize": 2048}, {"algorithm": "diffie-hellman-group16-sha512"}, {"algorithm": "diffie-hellman-group18-sha512"}, {"algorithm": "diffie-hellman-group14-sha256"}, {"algorithm": "diffie-hellman-group14-sha1"}], "key": [{"algorithm": "ssh-ed25519"}, {"algorithm": "ssh-ed25519-cert-v01@openssh.com"}], "mac": ["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"]}
|
@ -1,11 +1,11 @@
|
||||
[0;36m# general[0m
|
||||
[0;32m(gen) banner: SSH-2.0-OpenSSH_8.0[0m
|
||||
[0;32m(gen) software: OpenSSH 8.0[0m
|
||||
[0;32m(gen) compatibility: OpenSSH 7.4+, Dropbear SSH 2016.73+[0m
|
||||
[0;32m(gen) compatibility: OpenSSH 7.4+, Dropbear SSH 2018.76+[0m
|
||||
[0;32m(gen) compression: enabled (zlib@openssh.com)[0m
|
||||
|
||||
[0;36m# key exchange algorithms[0m
|
||||
[0;32m(kex) curve25519-sha256 -- [info] available since OpenSSH 7.4[0m
|
||||
[0;32m(kex) curve25519-sha256 -- [info] available since OpenSSH 7.4, Dropbear SSH 2018.76[0m
|
||||
[0;32m(kex) curve25519-sha256@libssh.org -- [info] available since OpenSSH 6.5, Dropbear SSH 2013.62[0m
|
||||
[0;31m(kex) ecdh-sha2-nistp256 -- [fail] using weak elliptic curves[0m
|
||||
`- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62
|
||||
@ -73,3 +73,6 @@
|
||||
[0;33m(rec) -umac-64-etm@openssh.com -- mac algorithm to remove [0m
|
||||
[0;33m(rec) -umac-64@openssh.com -- mac algorithm to remove [0m
|
||||
|
||||
[0;36m# additional info[0m
|
||||
[0;33m(nfo) For hardening guides on common OSes, please see: <https://www.ssh-audit.com/hardening_guides.html>[0m
|
||||
|
||||
|
1
test/docker/expected_results/openssh_8.0p1_test3.json
Normal file
1
test/docker/expected_results/openssh_8.0p1_test3.json
Normal file
@ -0,0 +1 @@
|
||||
{"banner": {"comments": null, "protocol": [2, 0], "raw": "SSH-2.0-OpenSSH_8.0", "software": "OpenSSH_8.0"}, "compression": ["none", "zlib@openssh.com"], "enc": ["chacha20-poly1305@openssh.com", "aes256-gcm@openssh.com", "aes128-gcm@openssh.com", "aes256-ctr", "aes192-ctr", "aes128-ctr"], "fingerprints": [{"fp": "SHA256:UrnXIVH+7dlw8UqYocl48yUEcKrthGDQG2CPCgp7MxU", "type": "ssh-ed25519"}], "kex": [{"algorithm": "curve25519-sha256"}, {"algorithm": "curve25519-sha256@libssh.org"}, {"algorithm": "diffie-hellman-group-exchange-sha256", "keysize": 2048}], "key": [{"algorithm": "ssh-ed25519"}], "mac": ["hmac-sha2-256-etm@openssh.com", "hmac-sha2-512-etm@openssh.com", "umac-128-etm@openssh.com"]}
|
@ -1,11 +1,11 @@
|
||||
[0;36m# general[0m
|
||||
[0;32m(gen) banner: SSH-2.0-OpenSSH_8.0[0m
|
||||
[0;32m(gen) software: OpenSSH 8.0[0m
|
||||
[0;32m(gen) compatibility: OpenSSH 7.4+, Dropbear SSH 2013.62+[0m
|
||||
[0;32m(gen) compatibility: OpenSSH 7.4+, Dropbear SSH 2018.76+[0m
|
||||
[0;32m(gen) compression: enabled (zlib@openssh.com)[0m
|
||||
|
||||
[0;36m# key exchange algorithms[0m
|
||||
[0;32m(kex) curve25519-sha256 -- [info] available since OpenSSH 7.4[0m
|
||||
[0;32m(kex) curve25519-sha256 -- [info] available since OpenSSH 7.4, Dropbear SSH 2018.76[0m
|
||||
[0;32m(kex) curve25519-sha256@libssh.org -- [info] available since OpenSSH 6.5, Dropbear SSH 2013.62[0m
|
||||
[0;32m(kex) diffie-hellman-group-exchange-sha256 (2048-bit) -- [info] available since OpenSSH 4.4[0m
|
||||
|
||||
|
1
test/docker/expected_results/tinyssh_20190101_test1.json
Normal file
1
test/docker/expected_results/tinyssh_20190101_test1.json
Normal file
@ -0,0 +1 @@
|
||||
{"banner": {"comments": "", "protocol": [2, 0], "raw": "", "software": "tinyssh_noversion"}, "compression": ["none"], "enc": ["chacha20-poly1305@openssh.com"], "fingerprints": [{"fp": "SHA256:89ocln1x7KNqnMgWffGoYtD70ksJ4FrH7BMJHa7SrwU", "type": "ssh-ed25519"}], "kex": [{"algorithm": "curve25519-sha256"}, {"algorithm": "curve25519-sha256@libssh.org"}, {"algorithm": "sntrup4591761x25519-sha512@tinyssh.org"}], "key": [{"algorithm": "ssh-ed25519"}], "mac": ["hmac-sha2-256"]}
|
@ -1,10 +1,10 @@
|
||||
[0;36m# general[0m
|
||||
[0;32m(gen) software: TinySSH noversion[0m
|
||||
[0;32m(gen) compatibility: OpenSSH 8.0+, Dropbear SSH 2013.62+[0m
|
||||
[0;32m(gen) compatibility: OpenSSH 8.0+, Dropbear SSH 2018.76+[0m
|
||||
[0;32m(gen) compression: disabled[0m
|
||||
|
||||
[0;36m# key exchange algorithms[0m
|
||||
[0;32m(kex) curve25519-sha256 -- [info] available since OpenSSH 7.4[0m
|
||||
[0;32m(kex) curve25519-sha256 -- [info] available since OpenSSH 7.4, Dropbear SSH 2018.76[0m
|
||||
[0;32m(kex) curve25519-sha256@libssh.org -- [info] available since OpenSSH 6.5, Dropbear SSH 2013.62[0m
|
||||
[0;33m(kex) sntrup4591761x25519-sha512@tinyssh.org -- [warn] using experimental algorithm[0m
|
||||
`- [info] available since OpenSSH 8.0
|
||||
|
Reference in New Issue
Block a user