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

25 Commits

Author SHA1 Message Date
4f2f995b62 Bumped version to v2.5.0. 2021-08-26 15:24:34 -04:00
134236fa7f Fixed badge link. 2021-08-26 14:39:24 -04:00
a6b658d194 Updated badges. 2021-08-26 13:12:13 -04:00
297a807f88 Added Github Actions support. 2021-08-26 12:47:48 -04:00
20d94df400 Updated Windows packaging instructions. 2021-08-26 12:18:06 -04:00
b76060cf49 Updated Tox test section. 2021-08-26 12:16:39 -04:00
1cf1c874db Added Python 3.10 support. 2021-08-26 10:56:43 -04:00
992d8233c9 Remove cache files created during build. 2021-08-26 10:47:43 -04:00
f377b7cea3 Now prompts user for release version, cleans up cached files from previous invokation, and resets all local changes upon completion. 2021-08-26 10:39:11 -04:00
70d9ab2e6b Check if -dev is in version string. (#106) 2021-08-25 14:24:10 -04:00
e7d320f602 Fixed new pylint warnings. 2021-08-25 13:28:30 -04:00
682cb66f85 Added OpenSSH v8.6 & v8.7 policies. 2021-08-25 12:30:38 -04:00
076681a671 Added 3 new key exchanges: gss-gex-sha1-eipGX3TCiQSrx573bT1o1Q==, gss-group1-sha1-eipGX3TCiQSrx573bT1o1Q==, gss-group14-sha1-eipGX3TCiQSrx573bT1o1Q== 2021-07-08 10:18:25 -04:00
98a1fb0315 Added two new MACs: 'AEAD_AES_128_GCM', and 'AEAD_AES_256_GCM'. 2021-06-28 21:59:41 -04:00
45da9f20ae Added 'rsa-sha2-512' and 'rsa-sha2-256' to OpenSSH 8.1 (and earlier) policies. 2021-05-31 15:49:56 -04:00
aa21df29e7 Now handles exceptions during server KEX parsing more gracefully. 2021-05-24 19:50:25 -04:00
32ed9242af Now prints JSON with indents when is used (useful for debugging). 2021-05-20 19:04:35 -04:00
07862489c4 Added MD5 fingerprint hashes to verbose output. 2021-05-20 18:03:24 -04:00
e508a963e7 Added 1 new MAC: hmac-ripemd160-96. 2021-05-20 14:17:37 -04:00
2f1a2a60b1 Added ToC to README.md (#101) 2021-03-04 18:23:12 -05:00
5eb669e01c Updated README. 2021-03-02 11:27:40 -05:00
8e9fe20fac SSH_Socket's constructor now takes an OutputBuffer for verbose & debugging output. 2021-03-02 11:25:37 -05:00
83bd049486 Debug Logging and visibility of SSH Connection errors (#99)
* Debug Logging and visibility of SSH Connection errors

* Updated date in man page
2021-03-02 11:06:40 -05:00
c483fe1861 Fixed a crash while doing host key tests. 2021-02-26 16:01:30 -05:00
741bd631e2 Updated packaging instructions. 2021-02-24 10:18:12 -05:00
37 changed files with 309 additions and 157 deletions

24
.github/workflows/tox.yaml vendored Normal file
View File

@ -0,0 +1,24 @@
name: ssh-audit
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.6, 3.7, 3.8, 3.9]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install codecov coveralls flake8 mypy pylint tox vulture
- name: Run Tox
run: |
tox

View File

@ -10,6 +10,7 @@ python:
- "3.7" - "3.7"
- "3.8" - "3.8"
- "3.9" - "3.9"
- "3.10"
cache: cache:
- pip - pip

View File

@ -11,26 +11,8 @@ However, if you can submit patches that pass all of our automated tests, then yo
Tox is used to do unit testing, linting with [pylint](http://pylint.pycqa.org/en/latest/) & [flake8](https://flake8.pycqa.org/en/latest/), and static type-checking with [mypy](https://mypy.readthedocs.io/en/stable/). Tox is used to do unit testing, linting with [pylint](http://pylint.pycqa.org/en/latest/) & [flake8](https://flake8.pycqa.org/en/latest/), and static type-checking with [mypy](https://mypy.readthedocs.io/en/stable/).
### Running tests on Ubuntu 18.04 and later
For Ubuntu 18.04 or later, install tox with `apt install tox`, then simply run `tox` in the top-level directory. Look for any error messages in the (verbose) output. For Ubuntu 18.04 or later, install tox with `apt install tox`, then simply run `tox` in the top-level directory. Look for any error messages in the (verbose) output.
### Running tests on Ubuntu 16.04
For Ubuntu 16.04 (which is still supported until April 2021), a newer version of tox is needed. The easiest way is to use virtualenv:
```
$ sudo apt install python3-virtualenv
$ virtualenv -p /usr/bin/python3 ~/venv_ssh-audit
$ source ~/venv_ssh-audit/bin/activate
$ pip install tox
```
Then, to run the tox tests:
```
$ source ~/venv_ssh-audit/bin/activate
$ cd path/to/ssh-audit
$ tox
```
## Docker Tests ## Docker Tests

View File

@ -2,8 +2,6 @@
An executable can only be made on a Windows host because the PyInstaller tool (https://www.pyinstaller.org/) does not support cross-compilation. An executable can only be made on a Windows host because the PyInstaller tool (https://www.pyinstaller.org/) does not support cross-compilation.
On a Windows machine, do the following:
1.) Install Python v3.9.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.9.x from https://www.python.org/. To make life easier, check the option to add Python to the PATH environment variable.
2.) Using pip, install pyinstaller and colorama: 2.) Using pip, install pyinstaller and colorama:
@ -12,14 +10,15 @@ On a Windows machine, do the following:
pip install pyinstaller colorama pip install pyinstaller colorama
``` ```
3.) Create the executable with: 3.) Install Cygwin (https://www.cygwin.com/).
4.) Create the executable with:
``` ```
cd src\ssh_audit $ ./build_windows_executable.sh
rename ssh_audit.py ssh-audit.py
pyinstaller -F --icon ..\..\windows_icon.ico ssh-audit.py
``` ```
# PyPI # PyPI
To create package and upload to test server: To create package and upload to test server:
@ -38,7 +37,7 @@ To download from test server and verify:
$ pip3 install --index-url https://test.pypi.org/simple ssh-audit $ pip3 install --index-url https://test.pypi.org/simple ssh-audit
``` ```
To upload to production server (hint: use username '__token__' and API token): To upload to production server (hint: use username '\_\_token\_\_' and API token):
``` ```
$ make -f Makefile.pypi uploadprod $ make -f Makefile.pypi uploadprod
@ -60,7 +59,7 @@ Install pre-requisites with:
``` ```
$ sudo apt install make snapcraft $ sudo apt install make snapcraft
$ sudo snap install review-tools $ sudo snap install review-tools lxd
``` ```
Initialize LXD (leave all options default): Initialize LXD (leave all options default):
@ -91,7 +90,7 @@ Build image with:
$ make -f Makefile.docker $ make -f Makefile.docker
``` ```
Then upload them to Dockerhub with: Then upload it to Dockerhub with:
``` ```
$ make -f Makefile.docker upload $ make -f Makefile.docker upload

View File

@ -1,14 +1,25 @@
# ssh-audit # ssh-audit
[![Build Status](https://travis-ci.org/jtesta/ssh-audit.svg?branch=master)](https://travis-ci.org/jtesta/ssh-audit) [![License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](https://github.com/jtesta/ssh-audit/blob/master/LICENSE)
<!-- [![PyPI Downloads](https://img.shields.io/pypi/dm/ssh-audit)](https://pypi.org/project/ssh-audit/)
[![appveyor build status](https://ci.appveyor.com/api/projects/status/4m5r73m0r023edil/branch/develop?svg=true)](https://ci.appveyor.com/project/arthepsy/ssh-audit) [![Docker Pulls](https://img.shields.io/docker/pulls/positronsecurity/ssh-audit)](https://hub.docker.com/r/positronsecurity/ssh-audit)
[![codecov](https://codecov.io/gh/arthepsy/ssh-audit/branch/develop/graph/badge.svg)](https://codecov.io/gh/arthepsy/ssh-audit) [![Build Status](https://github.com/jtesta/ssh-audit/actions/workflows/tox.yaml/badge.svg)](https://github.com/jtesta/ssh-audit/actions)
[![Quality Gate](https://sonarqube.com/api/badges/gate?key=arthepsy-github%3Assh-audit%3Adevelop&template=ROUNDED)](https://sq.evolutiongaming.com/dashboard?id=arthepsy-github%3Assh-audit%3Adevelop) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/jtesta/ssh-audit/blob/master/CONTRIBUTING.md)
-->
**ssh-audit** is a tool for ssh server & client configuration auditing. **ssh-audit** is a tool for ssh server & client configuration auditing.
[jtesta/ssh-audit](https://github.com/jtesta/ssh-audit/) (v2.0+) is the updated and maintained version of ssh-audit forked from [arthepsy/ssh-audit](https://github.com/arthepsy/ssh-audit) (v1.x) due to inactivity. [jtesta/ssh-audit](https://github.com/jtesta/ssh-audit/) (v2.0+) is the updated and maintained version of ssh-audit forked from [arthepsy/ssh-audit](https://github.com/arthepsy/ssh-audit) (v1.x) due to inactivity.
- [Features](#features)
- [Usage](#usage)
- [Screenshots](#screenshots)
- [Server Standard Audit Example](#server-standard-audit-example)
- [Server Policy Audit Example](#server-policy-audit-example)
- [Client Standard Audit Example](#client-standard-audit-example)
- [Hardening Guides](#hardening-guides)
- [Pre-Built Packages](#pre-built-packages)
- [Web Front-End](#web-front-end)
- [ChangeLog](#changelog)
## Features ## Features
- SSH1 and SSH2 protocol server support; - SSH1 and SSH2 protocol server support;
- analyze SSH client configuration; - analyze SSH client configuration;
@ -37,7 +48,8 @@ usage: ssh-audit.py [options] <host>
-c, --client-audit starts a server on port 2222 to audit client -c, --client-audit starts a server on port 2222 to audit client
software config (use -p to change port; software config (use -p to change port;
use -t to change timeout) use -t to change timeout)
-j, --json JSON output -d, --debug Enable debug output.
-j, --json JSON output (use -jj to enable indents)
-l, --level=<level> minimum output level (info|warn|fail) -l, --level=<level> minimum output level (info|warn|fail)
-L, --list-policies list all the official, built-in policies -L, --list-policies list all the official, built-in policies
--lookup=<alg1,alg2,...> looks up an algorithm(s) without --lookup=<alg1,alg2,...> looks up an algorithm(s) without
@ -115,6 +127,8 @@ To create a policy based on a target server (which can be manually edited):
ssh-audit -M new_policy.txt targetserver ssh-audit -M new_policy.txt targetserver
``` ```
## Screenshots
### Server Standard Audit Example ### Server Standard Audit Example
Below is a screen shot of the standard server-auditing output when connecting to an unhardened OpenSSH v5.3 service: Below is a screen shot of the standard server-auditing output when connecting to an unhardened OpenSSH v5.3 service:
![screenshot](https://user-images.githubusercontent.com/2982011/64388792-317e6f80-d00e-11e9-826e-a4934769bb07.png) ![screenshot](https://user-images.githubusercontent.com/2982011/64388792-317e6f80-d00e-11e9-826e-a4934769bb07.png)
@ -130,10 +144,10 @@ After applying the steps in the hardening guide (see below), the output changes
Below is a screen shot of the client-auditing output when an unhardened OpenSSH v7.2 client connects: Below is a screen shot of the client-auditing output when an unhardened OpenSSH v7.2 client connects:
![client_screenshot](https://user-images.githubusercontent.com/2982011/68867998-b946c100-06c4-11ea-975f-1f47e4178a74.png) ![client_screenshot](https://user-images.githubusercontent.com/2982011/68867998-b946c100-06c4-11ea-975f-1f47e4178a74.png)
### Hardening Guides ## Hardening Guides
Guides to harden server & client configuration can be found here: [https://www.ssh-audit.com/hardening_guides.html](https://www.ssh-audit.com/hardening_guides.html) Guides to harden server & client configuration can be found here: [https://www.ssh-audit.com/hardening_guides.html](https://www.ssh-audit.com/hardening_guides.html)
### Pre-Built Packages ## Pre-Built Packages
Pre-built packages are available for Windows (see the releases page), on PyPI, Snap, and Homebrew. Pre-built packages are available for Windows (see the releases page), on PyPI, Snap, and Homebrew.
To install from PyPI: To install from PyPI:
@ -157,10 +171,22 @@ $ docker pull positronsecurity/ssh-audit
``` ```
(Then run with: `docker run -it -p 2222:2222 positronsecurity/ssh-audit 10.1.1.1`) (Then run with: `docker run -it -p 2222:2222 positronsecurity/ssh-audit 10.1.1.1`)
### Web Front-End ## Web Front-End
For convenience, a web front-end on top of the command-line tool is available at [https://www.ssh-audit.com/](https://www.ssh-audit.com/). For convenience, a web front-end on top of the command-line tool is available at [https://www.ssh-audit.com/](https://www.ssh-audit.com/).
## ChangeLog ## ChangeLog
### v2.5.0 (2021-08-26)
- Fixed crash when running host key tests.
- Handles server connection failures more gracefully.
- Now prints JSON with indents when `-jj` is used (useful for debugging).
- Added MD5 fingerprints to verbose output.
- Added `-d`/`--debug` option for getting debugging output; credit [Adam Russell](https://github.com/thecliguy).
- Updated JSON output to include MD5 fingerprints. Note that this results in a breaking change in the 'fingerprints' dictionary format.
- Updated OpenSSH 8.1 (and earlier) policies to include `rsa-sha2-512` and `rsa-sha2-256`.
- Added OpenSSH v8.6 & v8.7 policies.
- Added 3 new key exchanges: `gss-gex-sha1-eipGX3TCiQSrx573bT1o1Q==`, `gss-group1-sha1-eipGX3TCiQSrx573bT1o1Q==`, and `gss-group14-sha1-eipGX3TCiQSrx573bT1o1Q==`.
- Added 3 new MACs: `hmac-ripemd160-96`, `AEAD_AES_128_GCM`, and `AEAD_AES_256_GCM`.
### v2.4.0 (2021-02-23) ### v2.4.0 (2021-02-23)
- Added multi-threaded scanning support. - Added multi-threaded scanning support.
- Added built-in Windows manual page (see `-m`/`--manual`); credit [Adam Russell](https://github.com/thecliguy). - Added built-in Windows manual page (see `-m`/`--manual`); credit [Adam Russell](https://github.com/thecliguy).

View File

@ -58,6 +58,29 @@ if [[ $? != 0 ]]; then
exit 1 exit 1
fi fi
# Prompt for the version to release.
echo -n "Enter the version to release, using format 'vX.X.X': "
read -r version
# Ensure that entered version fits required format.
if [[ ! $version =~ ^v[0-9]\.[0-9]\.[0-9]$ ]]; then
echo "Error: version string does not match format vX.X.X!"
exit 1
fi
# Verify that version is correct.
echo -n "Version will be set to '${version}'. Is this correct? (y/n): "
read -r yn
echo
if [[ $yn != "y" ]]; then
echo "Build cancelled."
exit 1
fi
# Reset any local changes made to globals.py from a previous run.
git checkout src/ssh_audit/globals.py 2> /dev/null
# Update the man page. # Update the man page.
./update_windows_man_page.sh ./update_windows_man_page.sh
if [[ $? != 0 ]]; then if [[ $? != 0 ]]; then
@ -68,12 +91,14 @@ fi
# Do all operations from this point from the main source directory. # Do all operations from this point from the main source directory.
pushd src/ssh_audit > /dev/null pushd src/ssh_audit > /dev/null
# Delete the executable if it exists from a prior run. # Delete the existing VERSION variable and add the value that the user entered, above.
if [[ -f dist/ssh-audit.exe ]]; then sed -i '/^VERSION/d' globals.py
rm dist/ssh-audit.exe echo "VERSION = '$version'" >> globals.py
fi
# Create a link from ssh_audit.py to ssh-audit.py. # Delete cached files if they exist from a prior run.
rm -rf dist/ build/ ssh-audit.spec
# Create a hard link from ssh_audit.py to ssh-audit.py.
if [[ ! -f ssh-audit.py ]]; then if [[ ! -f ssh-audit.py ]]; then
ln ssh_audit.py ssh-audit.py ln ssh_audit.py ssh-audit.py
fi fi
@ -83,10 +108,23 @@ pyinstaller -F --icon ../../windows_icon.ico ssh-audit.py
if [[ -f dist/ssh-audit.exe ]]; then if [[ -f dist/ssh-audit.exe ]]; then
echo -e "\nExecutable created in $(pwd)/dist/ssh-audit.exe\n" echo -e "\nExecutable created in $(pwd)/dist/ssh-audit.exe\n"
else
echo -e "\nFAILED to create $(pwd)/dist/ssh-audit.exe!\n"
exit 1
fi fi
# Remove the link we created, above. # Ensure that the version string doesn't have '-dev' in it.
rm ssh-audit.py X=`dist/ssh-audit.exe | grep -E 'ssh-audit.exe v.+\-dev'` > /dev/null
if [[ $? == 0 ]]; then
echo -e "\nError: executable's version number includes '-dev'."
exit 1
fi
# Remove the cache files created during the build process, along with the link we created, above.
rm -rf build/ ssh-audit.spec ssh-audit.py
# Reset the changes we made to globals.py.
git checkout globals.py 2> /dev/null
popd > /dev/null popd > /dev/null
exit 0 exit 0

View File

@ -700,10 +700,10 @@ run_custom_policy_test 'config2' 'test13' $PROGRAM_RETVAL_GOOD
run_custom_policy_test 'config2' 'test14' $PROGRAM_RETVAL_FAILURE run_custom_policy_test 'config2' 'test14' $PROGRAM_RETVAL_FAILURE
# Passing test for built-in OpenSSH 8.0p1 server policy. # Passing test for built-in OpenSSH 8.0p1 server policy.
run_builtin_policy_test "Hardened OpenSSH Server v8.0 (version 1)" "8.0p1" "test1" "-o HostKeyAlgorithms=ssh-ed25519 -o KexAlgorithms=curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256 -o Ciphers=chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr -o MACs=hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com" $PROGRAM_RETVAL_GOOD run_builtin_policy_test "Hardened OpenSSH Server v8.0 (version 1)" "8.0p1" "test1" "-o HostKeyAlgorithms=rsa-sha2-512,rsa-sha2-256,ssh-ed25519 -o KexAlgorithms=curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256 -o Ciphers=chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr -o MACs=hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com" $PROGRAM_RETVAL_GOOD
# Failing test for built-in OpenSSH 8.0p1 server policy (MACs not hardened). # Failing test for built-in OpenSSH 8.0p1 server policy (MACs not hardened).
run_builtin_policy_test "Hardened OpenSSH Server v8.0 (version 1)" "8.0p1" "test2" "-o HostKeyAlgorithms=ssh-ed25519 -o KexAlgorithms=curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256 -o Ciphers=chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr" $PROGRAM_RETVAL_FAILURE run_builtin_policy_test "Hardened OpenSSH Server v8.0 (version 1)" "8.0p1" "test2" "-o HostKeyAlgorithms=rsa-sha2-512,rsa-sha2-256,ssh-ed25519 -o KexAlgorithms=curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256 -o Ciphers=chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr" $PROGRAM_RETVAL_FAILURE
if [[ $num_failures == 0 ]]; then if [[ $num_failures == 0 ]]; then

View File

@ -1,7 +1,7 @@
""" """
The MIT License (MIT) The MIT License (MIT)
Copyright (C) 2017-2020 Joe Testa (jtesta@positronsecurity.com) Copyright (C) 2017-2021 Joe Testa (jtesta@positronsecurity.com)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu) Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
@ -55,7 +55,7 @@ class Algorithms:
if self.ssh1kex is None: if self.ssh1kex is None:
return None return None
item = Algorithms.Item(1, SSH1_KexDB.ALGORITHMS) item = Algorithms.Item(1, SSH1_KexDB.ALGORITHMS)
item.add('key', [u'ssh-rsa1']) item.add('key', ['ssh-rsa1'])
item.add('enc', self.ssh1kex.supported_ciphers) item.add('enc', self.ssh1kex.supported_ciphers)
item.add('aut', self.ssh1kex.supported_authentications) item.add('aut', self.ssh1kex.supported_authentications)
return item return item

View File

@ -1,7 +1,7 @@
""" """
The MIT License (MIT) The MIT License (MIT)
Copyright (C) 2017-2020 Joe Testa (jtesta@positronsecurity.com) Copyright (C) 2017-2021 Joe Testa (jtesta@positronsecurity.com)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu) Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
@ -41,6 +41,7 @@ class AuditConf:
self.client_audit = False self.client_audit = False
self.colors = True self.colors = True
self.json = False self.json = False
self.json_print_indent = False
self.verbose = False self.verbose = False
self.level = 'info' self.level = 'info'
self.ip_version_preference: List[int] = [] # Holds only 5 possible values: [] (no preference), [4] (use IPv4 only), [6] (use IPv6 only), [46] (use both IPv4 and IPv6, but prioritize v4), and [64] (use both IPv4 and IPv6, but prioritize v6). self.ip_version_preference: List[int] = [] # Holds only 5 possible values: [] (no preference), [4] (use IPv4 only), [6] (use IPv6 only), [46] (use both IPv4 and IPv6, but prioritize v4), and [64] (use both IPv4 and IPv6, but prioritize v6).
@ -57,10 +58,11 @@ class AuditConf:
self.list_policies = False self.list_policies = False
self.lookup = '' self.lookup = ''
self.manual = False self.manual = False
self.debug = False
def __setattr__(self, name: str, value: Union[str, int, float, bool, Sequence[int]]) -> None: def __setattr__(self, name: str, value: Union[str, int, float, bool, Sequence[int]]) -> None:
valid = False valid = False
if name in ['batch', 'client_audit', 'colors', 'json', 'list_policies', 'manual', 'make_policy', 'ssh1', 'ssh2', 'timeout_set', 'verbose']: if name in ['batch', 'client_audit', 'colors', 'json', 'json_print_indent', 'list_policies', 'manual', 'make_policy', 'ssh1', 'ssh2', 'timeout_set', 'verbose', 'debug']:
valid, value = True, bool(value) valid, value = True, bool(value)
elif name in ['ipv4', 'ipv6']: elif name in ['ipv4', 'ipv6']:
valid, value = True, bool(value) valid, value = True, bool(value)

View File

@ -1,7 +1,7 @@
""" """
The MIT License (MIT) The MIT License (MIT)
Copyright (C) 2017-2020 Joe Testa (jtesta@positronsecurity.com) Copyright (C) 2017-2021 Joe Testa (jtesta@positronsecurity.com)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu) Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
@ -33,11 +33,11 @@ class Fingerprint:
@property @property
def md5(self) -> str: def md5(self) -> str:
h = hashlib.md5(self.__fpd).hexdigest() h = hashlib.md5(self.__fpd).hexdigest()
r = u':'.join(h[i:i + 2] for i in range(0, len(h), 2)) r = ':'.join(h[i:i + 2] for i in range(0, len(h), 2))
return u'MD5:{}'.format(r) return 'MD5:{}'.format(r)
@property @property
def sha256(self) -> str: def sha256(self) -> str:
h = base64.b64encode(hashlib.sha256(self.__fpd).digest()) h = base64.b64encode(hashlib.sha256(self.__fpd).digest())
r = h.decode('ascii').rstrip('=') r = h.decode('ascii').rstrip('=')
return u'SHA256:{}'.format(r) return 'SHA256:{}'.format(r)

View File

@ -26,10 +26,13 @@
from typing import Dict, List, Set, Sequence, Tuple, Iterable # noqa: F401 from typing import Dict, List, Set, Sequence, Tuple, Iterable # noqa: F401
from typing import Callable, Optional, Union, Any # noqa: F401 from typing import Callable, Optional, Union, Any # noqa: F401
import traceback
from ssh_audit.kexdh import KexGroupExchange_SHA1, KexGroupExchange_SHA256 from ssh_audit.kexdh import KexGroupExchange_SHA1, KexGroupExchange_SHA256
from ssh_audit.ssh2_kexdb import SSH2_KexDB from ssh_audit.ssh2_kexdb import SSH2_KexDB
from ssh_audit.ssh2_kex import SSH2_Kex from ssh_audit.ssh2_kex import SSH2_Kex
from ssh_audit.ssh_socket import SSH_Socket from ssh_audit.ssh_socket import SSH_Socket
from ssh_audit.outputbuffer import OutputBuffer
# Performs DH group exchanges to find what moduli are supported, and checks # Performs DH group exchanges to find what moduli are supported, and checks
@ -38,16 +41,18 @@ class GEXTest:
# Creates a new connection to the server. Returns True on success, or False. # Creates a new connection to the server. Returns True on success, or False.
@staticmethod @staticmethod
def reconnect(s: 'SSH_Socket', kex: 'SSH2_Kex', gex_alg: str) -> bool: def reconnect(out: 'OutputBuffer', s: 'SSH_Socket', kex: 'SSH2_Kex', gex_alg: str) -> bool:
if s.is_connected(): if s.is_connected():
return True return True
err = s.connect() err = s.connect()
if err is not None: if err is not None:
out.v(err, write_now=True)
return False return False
_, _, err = s.get_banner() _, _, err = s.get_banner()
if err is not None: if err is not None:
out.v(err, write_now=True)
s.close() s.close()
return False return False
@ -55,15 +60,19 @@ class GEXTest:
# server's own values. # server's own values.
s.send_kexinit(key_exchanges=[gex_alg], hostkeys=kex.key_algorithms, ciphers=kex.server.encryption, macs=kex.server.mac, compressions=kex.server.compression, languages=kex.server.languages) s.send_kexinit(key_exchanges=[gex_alg], hostkeys=kex.key_algorithms, ciphers=kex.server.encryption, macs=kex.server.mac, compressions=kex.server.compression, languages=kex.server.languages)
# Parse the server's KEX. try:
_, payload = s.read_packet(2) # Parse the server's KEX.
SSH2_Kex.parse(payload) _, payload = s.read_packet(2)
SSH2_Kex.parse(payload)
except Exception:
out.v("Failed to parse server's kex. Stack trace:\n%s" % str(traceback.format_exc()), write_now=True)
return False
return True return True
# Runs the DH moduli test against the specified target. # Runs the DH moduli test against the specified target.
@staticmethod @staticmethod
def run(s: 'SSH_Socket', kex: 'SSH2_Kex') -> None: def run(out: 'OutputBuffer', s: 'SSH_Socket', kex: 'SSH2_Kex') -> None:
GEX_ALGS = { GEX_ALGS = {
'diffie-hellman-group-exchange-sha1': KexGroupExchange_SHA1, 'diffie-hellman-group-exchange-sha1': KexGroupExchange_SHA1,
'diffie-hellman-group-exchange-sha256': KexGroupExchange_SHA256, 'diffie-hellman-group-exchange-sha256': KexGroupExchange_SHA256,
@ -77,13 +86,14 @@ class GEXTest:
# Check if the server supports any of the group-exchange # Check if the server supports any of the group-exchange
# algorithms. If so, test each one. # algorithms. If so, test each one.
for gex_alg in GEX_ALGS: for gex_alg, kex_group_class in GEX_ALGS.items():
if gex_alg in kex.kex_algorithms: if gex_alg in kex.kex_algorithms:
out.d('Preparing to perform DH group exchange using ' + gex_alg + '...', write_now=True)
if GEXTest.reconnect(s, kex, gex_alg) is False: if GEXTest.reconnect(out, s, kex, gex_alg) is False:
break break
kex_group = GEX_ALGS[gex_alg]() kex_group = kex_group_class()
smallest_modulus = -1 smallest_modulus = -1
# First try a range of weak sizes. # First try a range of weak sizes.
@ -110,7 +120,9 @@ class GEXTest:
if bits >= smallest_modulus > 0: if bits >= smallest_modulus > 0:
break break
if GEXTest.reconnect(s, kex, gex_alg) is False: out.d('Preparing to perform DH group exchange using ' + gex_alg + ' with modulus size ' + str(bits) + '...', write_now=True)
if GEXTest.reconnect(out, s, kex, gex_alg) is False:
reconnect_failed = True reconnect_failed = True
break break

View File

@ -21,7 +21,7 @@
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
""" """
VERSION = 'v2.4.0' VERSION = 'v2.5.0'
SSH_HEADER = 'SSH-{0}-OpenSSH_8.2' # SSH software to impersonate SSH_HEADER = 'SSH-{0}-OpenSSH_8.2' # SSH software to impersonate
GITHUB_ISSUES_URL = 'https://github.com/jtesta/ssh-audit/issues' # The URL to the Github issues tracker. GITHUB_ISSUES_URL = 'https://github.com/jtesta/ssh-audit/issues' # The URL to the Github issues tracker.
WINDOWS_MAN_PAGE = '' WINDOWS_MAN_PAGE = ''

View File

@ -26,10 +26,13 @@
from typing import Dict, List, Set, Sequence, Tuple, Iterable # noqa: F401 from typing import Dict, List, Set, Sequence, Tuple, Iterable # noqa: F401
from typing import Callable, Optional, Union, Any # noqa: F401 from typing import Callable, Optional, Union, Any # noqa: F401
import traceback
from ssh_audit.kexdh import KexDH, KexGroup1, KexGroup14_SHA1, KexGroup14_SHA256, KexCurve25519_SHA256, KexGroup16_SHA512, KexGroup18_SHA512, KexGroupExchange_SHA1, KexGroupExchange_SHA256, KexNISTP256, KexNISTP384, KexNISTP521 from ssh_audit.kexdh import KexDH, KexGroup1, KexGroup14_SHA1, KexGroup14_SHA256, KexCurve25519_SHA256, KexGroup16_SHA512, KexGroup18_SHA512, KexGroupExchange_SHA1, KexGroupExchange_SHA256, KexNISTP256, KexNISTP384, KexNISTP521
from ssh_audit.ssh2_kex import SSH2_Kex from ssh_audit.ssh2_kex import SSH2_Kex
from ssh_audit.ssh2_kexdb import SSH2_KexDB from ssh_audit.ssh2_kexdb import SSH2_KexDB
from ssh_audit.ssh_socket import SSH_Socket from ssh_audit.ssh_socket import SSH_Socket
from ssh_audit.outputbuffer import OutputBuffer
# Obtains host keys, checks their size, and derives their fingerprints. # Obtains host keys, checks their size, and derives their fingerprints.
@ -52,7 +55,7 @@ class HostKeyTest:
} }
@staticmethod @staticmethod
def run(s: 'SSH_Socket', server_kex: 'SSH2_Kex') -> None: def run(out: 'OutputBuffer', s: 'SSH_Socket', server_kex: 'SSH2_Kex') -> None:
KEX_TO_DHGROUP = { KEX_TO_DHGROUP = {
'diffie-hellman-group1-sha1': KexGroup1, 'diffie-hellman-group1-sha1': KexGroup1,
'diffie-hellman-group14-sha1': KexGroup14_SHA1, 'diffie-hellman-group14-sha1': KexGroup14_SHA1,
@ -80,10 +83,10 @@ class HostKeyTest:
break break
if kex_str is not None and kex_group is not None: if kex_str is not None and kex_group is not None:
HostKeyTest.perform_test(s, server_kex, kex_str, kex_group, HostKeyTest.HOST_KEY_TYPES) HostKeyTest.perform_test(out, s, server_kex, kex_str, kex_group, HostKeyTest.HOST_KEY_TYPES)
@staticmethod @staticmethod
def perform_test(s: 'SSH_Socket', server_kex: 'SSH2_Kex', kex_str: str, kex_group: 'KexDH', host_key_types: Dict[str, Dict[str, bool]]) -> None: def perform_test(out: 'OutputBuffer', s: 'SSH_Socket', server_kex: 'SSH2_Kex', kex_str: str, kex_group: 'KexDH', host_key_types: Dict[str, Dict[str, bool]]) -> None:
hostkey_modulus_size = 0 hostkey_modulus_size = 0
ca_modulus_size = 0 ca_modulus_size = 0
@ -101,6 +104,8 @@ class HostKeyTest:
# If this host key type is supported by the server, we test it. # If this host key type is supported by the server, we test it.
if host_key_type in server_kex.key_algorithms: if host_key_type in server_kex.key_algorithms:
out.d('Preparing to obtain ' + host_key_type + ' host key...', write_now=True)
cert = host_key_types[host_key_type]['cert'] cert = host_key_types[host_key_type]['cert']
variable_key_len = host_key_types[host_key_type]['variable_key_len'] variable_key_len = host_key_types[host_key_type]['variable_key_len']
@ -108,20 +113,25 @@ class HostKeyTest:
if not s.is_connected(): if not s.is_connected():
err = s.connect() err = s.connect()
if err is not None: if err is not None:
out.v(err, write_now=True)
return return
_, _, err = s.get_banner() _, _, err = s.get_banner()
if err is not None: if err is not None:
out.v(err, write_now=True)
s.close() s.close()
return return
# Send our KEX using the specified group-exchange and most of the server's own values. # Send our KEX using the specified group-exchange and most of the server's own values.
s.send_kexinit(key_exchanges=[kex_str], hostkeys=[host_key_type], ciphers=server_kex.server.encryption, macs=server_kex.server.mac, compressions=server_kex.server.compression, languages=server_kex.server.languages) s.send_kexinit(key_exchanges=[kex_str], hostkeys=[host_key_type], ciphers=server_kex.server.encryption, macs=server_kex.server.mac, compressions=server_kex.server.compression, languages=server_kex.server.languages)
# Parse the server's KEX. try:
_, payload = s.read_packet() # Parse the server's KEX.
SSH2_Kex.parse(payload) _, payload = s.read_packet()
SSH2_Kex.parse(payload)
except Exception:
out.v("Failed to parse server's kex. Stack trace:\n%s" % str(traceback.format_exc()), write_now=True)
return
# Do the initial DH exchange. The server responds back # Do the initial DH exchange. The server responds back
# with the host key and its length. Bingo. We also get back the host key fingerprint. # with the host key and its length. Bingo. We also get back the host key fingerprint.

View File

@ -1,7 +1,7 @@
""" """
The MIT License (MIT) The MIT License (MIT)
Copyright (C) 2017-2020 Joe Testa (jtesta@positronsecurity.com) Copyright (C) 2017-2021 Joe Testa (jtesta@positronsecurity.com)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu) Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
@ -37,8 +37,8 @@ from ssh_audit.ssh_socket import SSH_Socket
class KexDH: # pragma: nocover class KexDH: # pragma: nocover
def __init__(self, kex_name: str, hash_alg: str, g: int, p: int) -> None: def __init__(self, kex_name: str, hash_alg: str, g: int, p: int) -> None:
self.__kex_name = kex_name self.__kex_name = kex_name # pylint: disable=unused-private-member
self.__hash_alg = hash_alg self.__hash_alg = hash_alg # pylint: disable=unused-private-member
self.__g = 0 self.__g = 0
self.__p = 0 self.__p = 0
self.__q = 0 self.__q = 0
@ -46,10 +46,10 @@ class KexDH: # pragma: nocover
self.__e = 0 self.__e = 0
self.set_params(g, p) self.set_params(g, p)
self.__ed25519_pubkey: Optional[bytes] = None self.__ed25519_pubkey: Optional[bytes] = None # pylint: disable=unused-private-member
self.__hostkey_type: Optional[bytes] = None self.__hostkey_type: Optional[bytes] = None
self.__hostkey_e = 0 self.__hostkey_e = 0 # pylint: disable=unused-private-member
self.__hostkey_n = 0 self.__hostkey_n = 0 # pylint: disable=unused-private-member
self.__hostkey_n_len = 0 # Length of the host key modulus. self.__hostkey_n_len = 0 # Length of the host key modulus.
self.__ca_n_len = 0 # Length of the CA key modulus (if hostkey is a cert). self.__ca_n_len = 0 # Length of the CA key modulus (if hostkey is a cert).
@ -121,11 +121,11 @@ class KexDH: # pragma: nocover
# The public key exponent. # The public key exponent.
hostkey_e, hostkey_e_len, ptr = KexDH.__get_bytes(hostkey, ptr) hostkey_e, hostkey_e_len, ptr = KexDH.__get_bytes(hostkey, ptr)
self.__hostkey_e = int(binascii.hexlify(hostkey_e), 16) self.__hostkey_e = int(binascii.hexlify(hostkey_e), 16) # pylint: disable=unused-private-member
# Here is the modulus size & actual modulus of the host key public key. # Here is the modulus size & actual modulus of the host key public key.
hostkey_n, self.__hostkey_n_len, ptr = KexDH.__get_bytes(hostkey, ptr) hostkey_n, self.__hostkey_n_len, ptr = KexDH.__get_bytes(hostkey, ptr)
self.__hostkey_n = int(binascii.hexlify(hostkey_n), 16) self.__hostkey_n = int(binascii.hexlify(hostkey_n), 16) # pylint: disable=unused-private-member
# If this is an RSA certificate, continue parsing to extract the CA # If this is an RSA certificate, continue parsing to extract the CA
# key. # key.
@ -327,7 +327,7 @@ class KexGroupExchange(KexDH):
s.send_packet() s.send_packet()
packet_type, payload = s.read_packet(2) packet_type, payload = s.read_packet(2)
if (packet_type != Protocol.MSG_KEXDH_GEX_GROUP) and (packet_type != Protocol.MSG_DEBUG): # pylint: disable=consider-using-in if packet_type not in [Protocol.MSG_KEXDH_GEX_GROUP, Protocol.MSG_DEBUG]:
# TODO: replace with a better exception type. # TODO: replace with a better exception type.
raise Exception('Expected MSG_KEXDH_GEX_REPLY (%d), but got %d instead.' % (Protocol.MSG_KEXDH_GEX_REPLY, packet_type)) raise Exception('Expected MSG_KEXDH_GEX_REPLY (%d), but got %d instead.' % (Protocol.MSG_KEXDH_GEX_REPLY, packet_type))

View File

@ -47,6 +47,7 @@ class OutputBuffer:
self.section: List[str] = [] self.section: List[str] = []
self.batch = False self.batch = False
self.verbose = False self.verbose = False
self.debug = False
self.use_colors = True self.use_colors = True
self.json = False self.json = False
self.__level = 0 self.__level = 0
@ -167,7 +168,16 @@ class OutputBuffer:
def v(self, s: str, write_now: bool = False) -> 'OutputBuffer': def v(self, s: str, write_now: bool = False) -> 'OutputBuffer':
'''Prints a message if verbose output is enabled.''' '''Prints a message if verbose output is enabled.'''
if self.verbose: if self.verbose or self.debug:
self.info(s)
if write_now:
self.write()
return self
def d(self, s: str, write_now: bool = False) -> 'OutputBuffer':
'''Prints a message if verbose output is enabled.'''
if self.debug:
self.info(s) self.info(s)
if write_now: if write_now:
self.write() self.write()

View File

@ -1,7 +1,7 @@
""" """
The MIT License (MIT) The MIT License (MIT)
Copyright (C) 2020 Joe Testa (jtesta@positronsecurity.com) Copyright (C) 2020-2021 Joe Testa (jtesta@positronsecurity.com)
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@ -49,15 +49,15 @@ class Policy:
# Generic OpenSSH Server policies # Generic OpenSSH Server policies
'Hardened OpenSSH Server v7.7 (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519'], 'optional_host_keys': ['ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'cakey_sizes': None, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 2048}, 'server_policy': True}, 'Hardened OpenSSH Server v7.7 (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'cakey_sizes': None, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 2048}, 'server_policy': True},
'Hardened OpenSSH Server v7.8 (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519'], 'optional_host_keys': ['ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'cakey_sizes': None, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 2048}, 'server_policy': True}, 'Hardened OpenSSH Server v7.8 (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'cakey_sizes': None, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 2048}, 'server_policy': True},
'Hardened OpenSSH Server v7.9 (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519'], 'optional_host_keys': ['ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'cakey_sizes': None, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 2048}, 'server_policy': True}, 'Hardened OpenSSH Server v7.9 (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'cakey_sizes': None, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 2048}, 'server_policy': True},
'Hardened OpenSSH Server v8.0 (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519'], 'optional_host_keys': ['ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'cakey_sizes': None, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 2048}, 'server_policy': True}, 'Hardened OpenSSH Server v8.0 (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'cakey_sizes': None, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 2048}, 'server_policy': True},
'Hardened OpenSSH Server v8.1 (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'cakey_sizes': None, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 2048}, 'server_policy': True}, 'Hardened OpenSSH Server v8.1 (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'cakey_sizes': None, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 2048}, 'server_policy': True},
'Hardened OpenSSH Server v8.2 (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {'rsa-sha2-256': 4096, 'rsa-sha2-512': 4096}, 'cakey_sizes': {'rsa-sha2-256-cert-v01@openssh.com': 4096, 'rsa-sha2-512-cert-v01@openssh.com': 4096}, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 2048}, 'server_policy': True}, 'Hardened OpenSSH Server v8.2 (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {'rsa-sha2-256': 4096, 'rsa-sha2-512': 4096}, 'cakey_sizes': {'rsa-sha2-256-cert-v01@openssh.com': 4096, 'rsa-sha2-512-cert-v01@openssh.com': 4096}, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 2048}, 'server_policy': True},
@ -67,6 +67,10 @@ class Policy:
'Hardened OpenSSH Server v8.5 (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': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {'rsa-sha2-256': 4096, 'rsa-sha2-512': 4096}, 'cakey_sizes': {'rsa-sha2-256-cert-v01@openssh.com': 4096, 'rsa-sha2-512-cert-v01@openssh.com': 4096}, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 2048}, 'server_policy': True}, 'Hardened OpenSSH Server v8.5 (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': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {'rsa-sha2-256': 4096, 'rsa-sha2-512': 4096}, 'cakey_sizes': {'rsa-sha2-256-cert-v01@openssh.com': 4096, 'rsa-sha2-512-cert-v01@openssh.com': 4096}, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 2048}, 'server_policy': True},
'Hardened OpenSSH Server v8.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': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {'rsa-sha2-256': 4096, 'rsa-sha2-512': 4096}, 'cakey_sizes': {'rsa-sha2-256-cert-v01@openssh.com': 4096, 'rsa-sha2-512-cert-v01@openssh.com': 4096}, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 2048}, 'server_policy': True},
'Hardened OpenSSH Server v8.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': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {'rsa-sha2-256': 4096, 'rsa-sha2-512': 4096}, 'cakey_sizes': {'rsa-sha2-256-cert-v01@openssh.com': 4096, 'rsa-sha2-512-cert-v01@openssh.com': 4096}, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 2048}, 'server_policy': True},
# Ubuntu Client policies # Ubuntu Client policies
@ -113,7 +117,7 @@ class Policy:
if policy_file is not None: if policy_file is not None:
try: try:
with open(policy_file, "r") as f: with open(policy_file, "r", encoding='utf-8') as f:
policy_data = f.read() policy_data = f.read()
except FileNotFoundError: except FileNotFoundError:
print("Error: policy file not found: %s" % policy_file) print("Error: policy file not found: %s" % policy_file)
@ -421,8 +425,8 @@ macs = %s
server_policy_names = [] server_policy_names = []
client_policy_names = [] client_policy_names = []
for policy_name in Policy.BUILTIN_POLICIES: for policy_name, policy in Policy.BUILTIN_POLICIES.items():
if Policy.BUILTIN_POLICIES[policy_name]['server_policy']: if policy['server_policy']:
server_policy_names.append(policy_name) server_policy_names.append(policy_name)
else: else:
client_policy_names.append(policy_name) client_policy_names.append(policy_name)

View File

@ -90,7 +90,7 @@ class SSH1_PublicKeyMessage:
@property @property
def supported_ciphers(self) -> List[str]: def supported_ciphers(self) -> List[str]:
ciphers = [] ciphers = []
for i in range(len(SSH1.CIPHERS)): for i in range(len(SSH1.CIPHERS)): # pylint: disable=consider-using-enumerate
if self.__supported_ciphers_mask & (1 << i) != 0: if self.__supported_ciphers_mask & (1 << i) != 0:
ciphers.append(Utils.to_text(SSH1.CIPHERS[i])) ciphers.append(Utils.to_text(SSH1.CIPHERS[i]))
return ciphers return ciphers

View File

@ -1,7 +1,7 @@
""" """
The MIT License (MIT) The MIT License (MIT)
Copyright (C) 2017-2020 Joe Testa (jtesta@positronsecurity.com) Copyright (C) 2017-2021 Joe Testa (jtesta@positronsecurity.com)
Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu) Copyright (C) 2017 Andris Raugulis (moo@arthepsy.eu)
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
@ -66,10 +66,13 @@ class SSH2_KexDB: # pylint: disable=too-few-public-methods
'kex': { 'kex': {
'diffie-hellman-group1-sha1': [['2.3.0,d0.28,l10.2', '6.6', '6.9'], [FAIL_1024BIT_MODULUS, FAIL_OPENSSH67_UNSAFE, FAIL_OPENSSH70_LOGJAM], [WARN_HASH_WEAK]], 'diffie-hellman-group1-sha1': [['2.3.0,d0.28,l10.2', '6.6', '6.9'], [FAIL_1024BIT_MODULUS, FAIL_OPENSSH67_UNSAFE, FAIL_OPENSSH70_LOGJAM], [WARN_HASH_WEAK]],
'gss-group1-sha1-toWM5Slw5Ew8Mqkay+al2g==': [[], [FAIL_1024BIT_MODULUS, FAIL_OPENSSH67_UNSAFE, FAIL_OPENSSH70_LOGJAM], [WARN_HASH_WEAK]], 'gss-group1-sha1-toWM5Slw5Ew8Mqkay+al2g==': [[], [FAIL_1024BIT_MODULUS, FAIL_OPENSSH67_UNSAFE, FAIL_OPENSSH70_LOGJAM], [WARN_HASH_WEAK]],
'gss-gex-sha1-eipGX3TCiQSrx573bT1o1Q==': [[], [], [WARN_HASH_WEAK]],
'gss-gex-sha1-toWM5Slw5Ew8Mqkay+al2g==': [[], [], [WARN_HASH_WEAK]], 'gss-gex-sha1-toWM5Slw5Ew8Mqkay+al2g==': [[], [], [WARN_HASH_WEAK]],
'gss-gex-sha1-': [[], [], [WARN_HASH_WEAK]], 'gss-gex-sha1-': [[], [], [WARN_HASH_WEAK]],
'gss-group1-sha1-eipGX3TCiQSrx573bT1o1Q==': [[], [FAIL_1024BIT_MODULUS], [WARN_HASH_WEAK]],
'gss-group1-sha1-': [[], [FAIL_1024BIT_MODULUS], [WARN_HASH_WEAK]], 'gss-group1-sha1-': [[], [FAIL_1024BIT_MODULUS], [WARN_HASH_WEAK]],
'gss-group14-sha1-': [[], [], [WARN_HASH_WEAK]], 'gss-group14-sha1-': [[], [], [WARN_HASH_WEAK]],
'gss-group14-sha1-eipGX3TCiQSrx573bT1o1Q==': [[], [], [WARN_HASH_WEAK]],
'gss-group14-sha1-toWM5Slw5Ew8Mqkay+al2g==': [[], [], [WARN_HASH_WEAK]], 'gss-group14-sha1-toWM5Slw5Ew8Mqkay+al2g==': [[], [], [WARN_HASH_WEAK]],
'gss-group14-sha256-': [[]], 'gss-group14-sha256-': [[]],
'gss-group14-sha256-toWM5Slw5Ew8Mqkay+al2g==': [[]], 'gss-group14-sha256-toWM5Slw5Ew8Mqkay+al2g==': [[]],
@ -250,6 +253,7 @@ class SSH2_KexDB: # pylint: disable=too-few-public-methods
'hmac-md5-96': [['2.5.0', '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-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': [['2.5.0', '6.6', '7.1'], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH72_LEGACY, WARN_ENCRYPT_AND_MAC]],
'hmac-ripemd160-96': [[], [FAIL_DEPRECATED_MAC], [WARN_ENCRYPT_AND_MAC, WARN_TAG_SIZE]],
'hmac-ripemd160@openssh.com': [['2.1.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-64@openssh.com': [['4.7'], [], [WARN_ENCRYPT_AND_MAC, WARN_TAG_SIZE]],
'umac-128@openssh.com': [['6.2'], [], [WARN_ENCRYPT_AND_MAC]], 'umac-128@openssh.com': [['6.2'], [], [WARN_ENCRYPT_AND_MAC]],
@ -270,5 +274,7 @@ class SSH2_KexDB: # pylint: disable=too-few-public-methods
'aes256-gcm': [[]], 'aes256-gcm': [[]],
'chacha20-poly1305@openssh.com': [[]], # Despite the @openssh.com tag, this was never shipped as a MAC in OpenSSH (only as a cipher); it is only implemented as a MAC in Syncplify. 'chacha20-poly1305@openssh.com': [[]], # Despite the @openssh.com tag, this was never shipped as a MAC in OpenSSH (only as a cipher); it is only implemented as a MAC in Syncplify.
'crypticore-mac@ssh.com': [[], [FAIL_UNPROVEN]], 'crypticore-mac@ssh.com': [[], [FAIL_UNPROVEN]],
'AEAD_AES_128_GCM': [[]],
'AEAD_AES_256_GCM': [[]],
} }
} }

View File

@ -84,7 +84,8 @@ def usage(err: Optional[str] = None) -> None:
uout.info(' -6, --ipv6 enable IPv6 (order of precedence)') uout.info(' -6, --ipv6 enable IPv6 (order of precedence)')
uout.info(' -b, --batch batch output') uout.info(' -b, --batch batch output')
uout.info(' -c, --client-audit starts a server on port 2222 to audit client\n software config (use -p to change port;\n use -t to change timeout)') uout.info(' -c, --client-audit starts a server on port 2222 to audit client\n software config (use -p to change port;\n use -t to change timeout)')
uout.info(' -j, --json JSON output') uout.info(' -d, --debug debug output')
uout.info(' -j, --json JSON output (use -jj to enable indents)')
uout.info(' -l, --level=<level> minimum output level (info|warn|fail)') uout.info(' -l, --level=<level> minimum output level (info|warn|fail)')
uout.info(' -L, --list-policies list all the official, built-in policies') uout.info(' -L, --list-policies list all the official, built-in policies')
uout.info(' --lookup=<alg1,alg2,...> looks up an algorithm(s) without\n connecting to a server') uout.info(' --lookup=<alg1,alg2,...> looks up an algorithm(s) without\n connecting to a server')
@ -263,7 +264,7 @@ def output_security(out: OutputBuffer, banner: Optional[Banner], client_audit: b
out.sep() out.sep()
def output_fingerprints(out: OutputBuffer, algs: Algorithms, is_json_output: bool, sha256: bool = True) -> None: def output_fingerprints(out: OutputBuffer, algs: Algorithms, is_json_output: bool) -> None:
with out: with out:
fps = [] fps = []
if algs.ssh1kex is not None: if algs.ssh1kex is not None:
@ -290,10 +291,12 @@ def output_fingerprints(out: OutputBuffer, algs: Algorithms, is_json_output: boo
fps = sorted(fps) fps = sorted(fps)
for fpp in fps: for fpp in fps:
name, fp = fpp name, fp = fpp
fpo = fp.sha256 if sha256 else fp.md5 out.good('(fin) {}: {}'.format(name, fp.sha256))
# p = '' if out.batch else ' ' * (padlen - len(name))
# out.good('(fin) {0}{1} -- {2} {3}'.format(name, p, bits, fpo)) # Output the MD5 hash too if verbose mode is enabled.
out.good('(fin) {}: {}'.format(name, fpo)) if out.verbose:
out.info('(fin) {}: {} -- [info] do not rely on MD5 fingerprints for server identification; it is insecure for this use case'.format(name, fp.md5))
if not out.is_section_empty() and not is_json_output: if not out.is_section_empty() and not is_json_output:
out.head('# fingerprints') out.head('# fingerprints')
out.flush_section() out.flush_section()
@ -467,14 +470,14 @@ def output(out: OutputBuffer, aconf: AuditConf, banner: Optional[Banner], header
program_retval = output_algorithms(out, title, adb, atype, kex.server.encryption, unknown_algorithms, aconf.json, program_retval, maxlen) program_retval = output_algorithms(out, title, adb, atype, kex.server.encryption, unknown_algorithms, aconf.json, program_retval, maxlen)
title, atype = 'message authentication code algorithms', 'mac' title, atype = 'message authentication code algorithms', 'mac'
program_retval = output_algorithms(out, title, adb, atype, kex.server.mac, unknown_algorithms, aconf.json, program_retval, maxlen) program_retval = output_algorithms(out, title, adb, atype, kex.server.mac, unknown_algorithms, aconf.json, program_retval, maxlen)
output_fingerprints(out, algs, aconf.json, True) output_fingerprints(out, algs, aconf.json)
perfect_config = output_recommendations(out, algs, software, aconf.json, maxlen) perfect_config = output_recommendations(out, algs, software, aconf.json, maxlen)
output_info(out, software, client_audit, not perfect_config, aconf.json) output_info(out, software, client_audit, not perfect_config, aconf.json)
if aconf.json: if aconf.json:
out.reset() out.reset()
# Build & write the JSON struct. # Build & write the JSON struct.
out.info(json.dumps(build_struct(aconf.host, banner, kex=kex, client_host=client_host), sort_keys=True)) out.info(json.dumps(build_struct(aconf.host, banner, kex=kex, client_host=client_host), indent=4 if aconf.json_print_indent else None, sort_keys=True))
elif len(unknown_algorithms) > 0: # If we encountered any unknown algorithms, ask the user to report them. elif len(unknown_algorithms) > 0: # If we encountered any unknown algorithms, ask the user to report them.
out.warn("\n\n!!! WARNING: unknown algorithm(s) found!: %s. Please email the full output above to the maintainer (jtesta@positronsecurity.com), or create a Github issue at <https://github.com/jtesta/ssh-audit/issues>.\n" % ','.join(unknown_algorithms)) out.warn("\n\n!!! WARNING: unknown algorithm(s) found!: %s. Please email the full output above to the maintainer (jtesta@positronsecurity.com), or create a Github issue at <https://github.com/jtesta/ssh-audit/issues>.\n" % ','.join(unknown_algorithms))
@ -489,7 +492,7 @@ def evaluate_policy(out: OutputBuffer, aconf: AuditConf, banner: Optional['Banne
passed, error_struct, error_str = aconf.policy.evaluate(banner, kex) passed, error_struct, error_str = aconf.policy.evaluate(banner, kex)
if aconf.json: if aconf.json:
json_struct = {'host': aconf.host, 'policy': aconf.policy.get_name_and_version(), 'passed': passed, 'errors': error_struct} json_struct = {'host': aconf.host, 'policy': aconf.policy.get_name_and_version(), 'passed': passed, 'errors': error_struct}
out.info(json.dumps(json_struct, sort_keys=True)) out.info(json.dumps(json_struct, indent=4 if aconf.json_print_indent else None, sort_keys=True))
else: else:
spacing = '' spacing = ''
if aconf.client_audit: if aconf.client_audit:
@ -560,7 +563,7 @@ def make_policy(aconf: AuditConf, banner: Optional['Banner'], kex: Optional['SSH
# Open with mode 'x' (creates the file, or fails if it already exist). # Open with mode 'x' (creates the file, or fails if it already exist).
succeeded = True succeeded = True
try: try:
with open(aconf.policy_file, 'x') as f: with open(aconf.policy_file, 'x', encoding='utf-8') as f:
f.write(policy_data) f.write(policy_data)
except FileExistsError: except FileExistsError:
succeeded = False succeeded = False
@ -575,8 +578,8 @@ def process_commandline(out: OutputBuffer, args: List[str], usage_cb: Callable[.
# pylint: disable=too-many-branches # pylint: disable=too-many-branches
aconf = AuditConf() aconf = AuditConf()
try: try:
sopts = 'h1246M:p:P:jbcnvl:t:T:Lm' sopts = 'h1246M:p:P:jbcnvl:t:T:Lmd'
lopts = ['help', 'ssh1', 'ssh2', 'ipv4', 'ipv6', 'make-policy=', 'port=', 'policy=', 'json', 'batch', 'client-audit', 'no-colors', 'verbose', 'level=', 'timeout=', 'targets=', 'list-policies', 'lookup=', 'threads=', 'manual'] lopts = ['help', 'ssh1', 'ssh2', 'ipv4', 'ipv6', 'make-policy=', 'port=', 'policy=', 'json', 'batch', 'client-audit', 'no-colors', 'verbose', 'level=', 'timeout=', 'targets=', 'list-policies', 'lookup=', 'threads=', 'manual', 'debug']
opts, args = getopt.gnu_getopt(args, sopts, lopts) opts, args = getopt.gnu_getopt(args, sopts, lopts)
except getopt.GetoptError as err: except getopt.GetoptError as err:
usage_cb(str(err)) usage_cb(str(err))
@ -606,7 +609,10 @@ def process_commandline(out: OutputBuffer, args: List[str], usage_cb: Callable[.
aconf.colors = False aconf.colors = False
out.use_colors = False out.use_colors = False
elif o in ('-j', '--json'): elif o in ('-j', '--json'):
aconf.json = True if aconf.json: # If specified twice, enable indent printing.
aconf.json_print_indent = True
else:
aconf.json = True
elif o in ('-v', '--verbose'): elif o in ('-v', '--verbose'):
aconf.verbose = True aconf.verbose = True
out.verbose = True out.verbose = True
@ -632,6 +638,9 @@ def process_commandline(out: OutputBuffer, args: List[str], usage_cb: Callable[.
aconf.lookup = a aconf.lookup = a
elif o in ('-m', '--manual'): elif o in ('-m', '--manual'):
aconf.manual = True aconf.manual = True
elif o in ('-d', '--debug'):
aconf.debug = True
out.debug = True
if len(args) == 0 and aconf.client_audit is False and aconf.target_file is None and aconf.list_policies is False and aconf.lookup == '' and aconf.manual is False: if len(args) == 0 and aconf.client_audit is False and aconf.target_file is None and aconf.list_policies is False and aconf.lookup == '' and aconf.manual is False:
usage_cb() usage_cb()
@ -672,7 +681,7 @@ def process_commandline(out: OutputBuffer, args: List[str], usage_cb: Callable[.
# If a file containing a list of targets was given, read it. # If a file containing a list of targets was given, read it.
if aconf.target_file is not None: if aconf.target_file is not None:
with open(aconf.target_file, 'r') as f: with open(aconf.target_file, 'r', encoding='utf-8') as f:
aconf.target_list = f.readlines() aconf.target_list = f.readlines()
# Strip out whitespace from each line in target file, and skip empty lines. # Strip out whitespace from each line in target file, and skip empty lines.
@ -783,11 +792,18 @@ def build_struct(target_host: str, banner: Optional['Banner'], kex: Optional['SS
# Skip over certificate host types (or we would return invalid fingerprints). # Skip over certificate host types (or we would return invalid fingerprints).
if '-cert-' in host_key_type: if '-cert-' in host_key_type:
continue continue
entry = {
'type': host_key_type, # Add the SHA256 and MD5 fingerprints.
'fp': fp.sha256, res['fingerprints'].append({
} 'hostkey': host_key_type,
res['fingerprints'].append(entry) 'hash_alg': 'SHA256',
'hash': fp.sha256[7:]
})
res['fingerprints'].append({
'hostkey': host_key_type,
'hash_alg': 'MD5',
'hash': fp.md5[4:]
})
else: else:
pkm_supported_ciphers = None pkm_supported_ciphers = None
pkm_supported_authentications = None pkm_supported_authentications = None
@ -813,15 +829,18 @@ def audit(out: OutputBuffer, aconf: AuditConf, sshv: Optional[int] = None, print
program_retval = exitcodes.GOOD program_retval = exitcodes.GOOD
out.batch = aconf.batch out.batch = aconf.batch
out.verbose = aconf.verbose out.verbose = aconf.verbose
out.debug = aconf.debug
out.level = aconf.level out.level = aconf.level
out.use_colors = aconf.colors out.use_colors = aconf.colors
s = SSH_Socket(aconf.host, aconf.port, aconf.ip_version_preference, aconf.timeout, aconf.timeout_set) s = SSH_Socket(out, aconf.host, aconf.port, aconf.ip_version_preference, aconf.timeout, aconf.timeout_set)
if aconf.client_audit: if aconf.client_audit:
out.v("Listening for client connection on port %d..." % aconf.port, write_now=True) out.v("Listening for client connection on port %d..." % aconf.port, write_now=True)
s.listen_and_accept() s.listen_and_accept()
else: else:
out.v("Connecting to %s:%d..." % ('[%s]' % aconf.host if Utils.is_ipv6_address(aconf.host) else aconf.host, aconf.port), write_now=True) out.v("Starting audit of %s:%d..." % ('[%s]' % aconf.host if Utils.is_ipv6_address(aconf.host) else aconf.host, aconf.port), write_now=True)
err = s.connect() err = s.connect()
if err is not None: if err is not None:
out.fail(err) out.fail(err)
@ -850,10 +869,10 @@ def audit(out: OutputBuffer, aconf: AuditConf, sshv: Optional[int] = None, print
if len(payload) > 0: if len(payload) > 0:
payload_txt = payload.decode('utf-8') payload_txt = payload.decode('utf-8')
else: else:
payload_txt = u'empty' payload_txt = 'empty'
except UnicodeDecodeError: except UnicodeDecodeError:
payload_txt = u'"{}"'.format(repr(payload).lstrip('b')[1:-1]) payload_txt = '"{}"'.format(repr(payload).lstrip('b')[1:-1])
if payload_txt == u'Protocol major versions differ.': if payload_txt == 'Protocol major versions differ.':
if sshv == 2 and aconf.ssh1: if sshv == 2 and aconf.ssh1:
ret = audit(out, aconf, 1) ret = audit(out, aconf, 1)
out.write() out.write()
@ -876,10 +895,15 @@ def audit(out: OutputBuffer, aconf: AuditConf, sshv: Optional[int] = None, print
if sshv == 1: if sshv == 1:
program_retval = output(out, aconf, banner, header, pkm=SSH1_PublicKeyMessage.parse(payload)) program_retval = output(out, aconf, banner, header, pkm=SSH1_PublicKeyMessage.parse(payload))
elif sshv == 2: elif sshv == 2:
kex = SSH2_Kex.parse(payload) try:
kex = SSH2_Kex.parse(payload)
except Exception:
out.fail("Failed to parse server's kex. Stack trace:\n%s" % str(traceback.format_exc()))
return exitcodes.CONNECTION_ERROR
if aconf.client_audit is False: if aconf.client_audit is False:
HostKeyTest.run(s, kex) HostKeyTest.run(out, s, kex)
GEXTest.run(s, kex) GEXTest.run(out, s, kex)
# This is a standard audit scan. # This is a standard audit scan.
if (aconf.policy is None) and (aconf.make_policy is False): if (aconf.policy is None) and (aconf.make_policy is False):
@ -948,8 +972,8 @@ def algorithm_lookup(out: OutputBuffer, alg_names: str) -> int:
similar_algorithms = [ similar_algorithms = [
alg_unknown + " --> (" + alg_type + ") " + alg_name alg_unknown + " --> (" + alg_type + ") " + alg_name
for alg_unknown in algorithms_not_found for alg_unknown in algorithms_not_found
for alg_type in adb for alg_type, alg_names in adb.items()
for alg_name in adb[alg_type] for alg_name in alg_names
# Perform a case-insensitive comparison using 'casefold' # Perform a case-insensitive comparison using 'casefold'
# and match substrings using the 'in' operator. # and match substrings using the 'in' operator.
if alg_unknown.casefold() in alg_name.casefold() if alg_unknown.casefold() in alg_name.casefold()

View File

@ -52,8 +52,9 @@ class SSH_Socket(ReadBuf, WriteBuf):
SM_BANNER_SENT = 1 SM_BANNER_SENT = 1
def __init__(self, host: Optional[str], port: int, ip_version_preference: List[int] = [], timeout: Union[int, float] = 5, timeout_set: bool = False) -> None: # pylint: disable=dangerous-default-value def __init__(self, outputbuffer: 'OutputBuffer', host: Optional[str], port: int, ip_version_preference: List[int] = [], timeout: Union[int, float] = 5, timeout_set: bool = False) -> None: # pylint: disable=dangerous-default-value
super(SSH_Socket, self).__init__() super(SSH_Socket, self).__init__()
self.__outputbuffer = outputbuffer
self.__sock: Optional[socket.socket] = None self.__sock: Optional[socket.socket] = None
self.__sock_map: Dict[int, socket.socket] = {} self.__sock_map: Dict[int, socket.socket] = {}
self.__block_size = 8 self.__block_size = 8
@ -90,7 +91,7 @@ class SSH_Socket(ReadBuf, WriteBuf):
if socktype == socket.SOCK_STREAM: if socktype == socket.SOCK_STREAM:
yield af, addr yield af, addr
except socket.error as e: except socket.error as e:
OutputBuffer().fail('[exception] {}'.format(e)).write() self.__outputbuffer.fail('[exception] {}'.format(e)).write()
sys.exit(exitcodes.CONNECTION_ERROR) sys.exit(exitcodes.CONNECTION_ERROR)
# Listens on a server socket and accepts one connection (used for # Listens on a server socket and accepts one connection (used for
@ -156,6 +157,7 @@ class SSH_Socket(ReadBuf, WriteBuf):
try: try:
s = socket.socket(af, socket.SOCK_STREAM) s = socket.socket(af, socket.SOCK_STREAM)
s.settimeout(self.__timeout) s.settimeout(self.__timeout)
self.__outputbuffer.d(("Connecting to %s:%d..." % ('[%s]' % addr[0] if Utils.is_ipv6_address(addr[0]) else addr[0], addr[1])), write_now=True)
s.connect(addr) s.connect(addr)
self.__sock = s self.__sock = s
return None return None
@ -170,6 +172,8 @@ class SSH_Socket(ReadBuf, WriteBuf):
return '[exception] {}'.format(errm) return '[exception] {}'.format(errm)
def get_banner(self, sshv: int = 2) -> Tuple[Optional['Banner'], List[str], Optional[str]]: def get_banner(self, sshv: int = 2) -> Tuple[Optional['Banner'], List[str], Optional[str]]:
self.__outputbuffer.d('Getting banner...', write_now=True)
if self.__sock is None: if self.__sock is None:
return self.__banner, self.__header, 'not connected' return self.__banner, self.__header, 'not connected'
if self.__banner is not None: if self.__banner is not None:
@ -229,6 +233,8 @@ class SSH_Socket(ReadBuf, WriteBuf):
def send_kexinit(self, key_exchanges: List[str] = ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'ecdh-sha2-nistp256', 'ecdh-sha2-nistp384', 'ecdh-sha2-nistp521', 'diffie-hellman-group-exchange-sha256', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group14-sha256'], hostkeys: List[str] = ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ssh-ed25519'], ciphers: List[str] = ['chacha20-poly1305@openssh.com', 'aes128-ctr', 'aes192-ctr', 'aes256-ctr', 'aes128-gcm@openssh.com', 'aes256-gcm@openssh.com'], macs: List[str] = ['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'], compressions: List[str] = ['none', 'zlib@openssh.com'], languages: List[str] = ['']) -> None: # pylint: disable=dangerous-default-value def send_kexinit(self, key_exchanges: List[str] = ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'ecdh-sha2-nistp256', 'ecdh-sha2-nistp384', 'ecdh-sha2-nistp521', 'diffie-hellman-group-exchange-sha256', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group14-sha256'], hostkeys: List[str] = ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ssh-ed25519'], ciphers: List[str] = ['chacha20-poly1305@openssh.com', 'aes128-ctr', 'aes192-ctr', 'aes256-ctr', 'aes128-gcm@openssh.com', 'aes256-gcm@openssh.com'], macs: List[str] = ['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'], compressions: List[str] = ['none', 'zlib@openssh.com'], languages: List[str] = ['']) -> None: # pylint: disable=dangerous-default-value
'''Sends the list of supported host keys, key exchanges, ciphers, and MACs. Emulates OpenSSH v8.2.''' '''Sends the list of supported host keys, key exchanges, ciphers, and MACs. Emulates OpenSSH v8.2.'''
self.__outputbuffer.d('KEX initialisation...', write_now=True)
kexparty = SSH2_KexParty(ciphers, macs, compressions, languages) kexparty = SSH2_KexParty(ciphers, macs, compressions, languages)
kex = SSH2_Kex(os.urandom(16), key_exchanges, hostkeys, kexparty, kexparty, False, 0) kex = SSH2_Kex(os.urandom(16), key_exchanges, hostkeys, kexparty, kexparty, False, 0)
@ -268,7 +274,7 @@ class SSH_Socket(ReadBuf, WriteBuf):
payload_length = packet_length - padding_length - 1 payload_length = packet_length - padding_length - 1
check_size = 4 + 1 + payload_length + padding_length check_size = 4 + 1 + payload_length + padding_length
if check_size % self.__block_size != 0: if check_size % self.__block_size != 0:
OutputBuffer().fail('[exception] invalid ssh packet (block size)').write() self.__outputbuffer.fail('[exception] invalid ssh packet (block size)').write()
sys.exit(exitcodes.CONNECTION_ERROR) sys.exit(exitcodes.CONNECTION_ERROR)
self.ensure_read(payload_length) self.ensure_read(payload_length)
if sshv == 1: if sshv == 1:
@ -283,7 +289,7 @@ class SSH_Socket(ReadBuf, WriteBuf):
if sshv == 1: if sshv == 1:
rcrc = SSH1.crc32(padding + payload) rcrc = SSH1.crc32(padding + payload)
if crc != rcrc: if crc != rcrc:
OutputBuffer().fail('[exception] packet checksum CRC32 mismatch.').write() self.__outputbuffer.fail('[exception] packet checksum CRC32 mismatch.').write()
sys.exit(exitcodes.CONNECTION_ERROR) sys.exit(exitcodes.CONNECTION_ERROR)
else: else:
self.ensure_read(padding_length) self.ensure_read(padding_length)
@ -332,6 +338,6 @@ class SSH_Socket(ReadBuf, WriteBuf):
def __cleanup(self) -> None: def __cleanup(self) -> None:
self._close_socket(self.__sock) self._close_socket(self.__sock)
for fd in self.__sock_map: for sock in self.__sock_map.values():
self._close_socket(self.__sock_map[fd]) self._close_socket(sock)
self.__sock = None self.__sock = None

View File

@ -54,7 +54,7 @@ class WriteBuf:
return self.write(v) return self.write(v)
def write_list(self, v: List[str]) -> 'WriteBuf': def write_list(self, v: List[str]) -> 'WriteBuf':
return self.write_string(u','.join(v)) return self.write_string(','.join(v))
@classmethod @classmethod
def _bitlength(cls, n: int) -> int: def _bitlength(cls, n: int) -> int:

View File

@ -1,4 +1,4 @@
.TH SSH-AUDIT 1 "February 7, 2021" .TH SSH-AUDIT 1 "March 2, 2021"
.SH NAME .SH NAME
\fBssh-audit\fP \- SSH server & client configuration auditor \fBssh-audit\fP \- SSH server & client configuration auditor
.SH SYNOPSIS .SH SYNOPSIS
@ -46,10 +46,15 @@ Enables grepable output.
.br .br
Starts a server on port 2222 to audit client software configuration. Use -p/--port=<port> to change port and -t/--timeout=<secs> to change listen timeout. Starts a server on port 2222 to audit client software configuration. Use -p/--port=<port> to change port and -t/--timeout=<secs> to change listen timeout.
.TP
.B -d, \-\-debug
.br
Enable debug output.
.TP .TP
.B -j, \-\-json .B -j, \-\-json
.br .br
Output results in JSON format. Output results in JSON format. Specify twice (-jj) to enable indent printing (useful for debugging).
.TP .TP
.B -l, \-\-level=<info|warn|fail> .B -l, \-\-level=<info|warn|fail>

View File

@ -1 +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"], "target": "localhost"} {"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": [{"hash": "CDfAU12pjQS7/91kg7gYacza0U/6PDbE04Ic3IpYxkM", "hash_alg": "SHA256", "hostkey": "ssh-rsa"}, {"hash": "63:7f:54:f7:0a:28:7f:75:0b:f4:07:0b:fc:66:51:a2", "hash_alg": "MD5", "hostkey": "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"], "target": "localhost"}

View File

@ -1 +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"], "target": "localhost"} {"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": [{"hash": "YZ457EBcJTSxRKI3yXRgtAj3PBf5B9/F36b1SVooml4", "hash_alg": "SHA256", "hostkey": "ssh-rsa"}, {"hash": "3c:c3:38:f8:55:39:c0:4a:5a:17:89:60:2c:a1:fc:6a", "hash_alg": "MD5", "hostkey": "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"], "target": "localhost"}

View File

@ -1 +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"], "target": "localhost"} {"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": [{"hash": "YZ457EBcJTSxRKI3yXRgtAj3PBf5B9/F36b1SVooml4", "hash_alg": "SHA256", "hostkey": "ssh-rsa"}, {"hash": "3c:c3:38:f8:55:39:c0:4a:5a:17:89:60:2c:a1:fc:6a", "hash_alg": "MD5", "hostkey": "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"], "target": "localhost"}

View File

@ -1 +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"], "target": "localhost"} {"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": [{"hash": "YZ457EBcJTSxRKI3yXRgtAj3PBf5B9/F36b1SVooml4", "hash_alg": "SHA256", "hostkey": "ssh-rsa"}, {"hash": "3c:c3:38:f8:55:39:c0:4a:5a:17:89:60:2c:a1:fc:6a", "hash_alg": "MD5", "hostkey": "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"], "target": "localhost"}

View File

@ -1 +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"], "target": "localhost"} {"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": [{"hash": "YZ457EBcJTSxRKI3yXRgtAj3PBf5B9/F36b1SVooml4", "hash_alg": "SHA256", "hostkey": "ssh-rsa"}, {"hash": "3c:c3:38:f8:55:39:c0:4a:5a:17:89:60:2c:a1:fc:6a", "hash_alg": "MD5", "hostkey": "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"], "target": "localhost"}

View File

@ -1 +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"], "target": "localhost"} {"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": [{"hash": "nsWtdJ9Z67Vrf7OsUzQov7esXhsWAfVppArGh25u244", "hash_alg": "SHA256", "hostkey": "ssh-rsa"}, {"hash": "18:e2:51:fe:21:6c:78:d0:b8:cf:32:d4:bd:56:42:e1", "hash_alg": "MD5", "hostkey": "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"], "target": "localhost"}

View File

@ -1 +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"], "target": "localhost"} {"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": [{"hash": "nsWtdJ9Z67Vrf7OsUzQov7esXhsWAfVppArGh25u244", "hash_alg": "SHA256", "hostkey": "ssh-rsa"}, {"hash": "18:e2:51:fe:21:6c:78:d0:b8:cf:32:d4:bd:56:42:e1", "hash_alg": "MD5", "hostkey": "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"], "target": "localhost"}

View File

@ -1 +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"], "target": "localhost"} {"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": [{"hash": "UrnXIVH+7dlw8UqYocl48yUEcKrthGDQG2CPCgp7MxU", "hash_alg": "SHA256", "hostkey": "ssh-ed25519"}, {"hash": "1e:0c:7b:34:73:bf:52:41:b0:f9:d1:a9:ab:98:c7:c9", "hash_alg": "MD5", "hostkey": "ssh-ed25519"}, {"hash": "nsWtdJ9Z67Vrf7OsUzQov7esXhsWAfVppArGh25u244", "hash_alg": "SHA256", "hostkey": "ssh-rsa"}, {"hash": "18:e2:51:fe:21:6c:78:d0:b8:cf:32:d4:bd:56:42:e1", "hash_alg": "MD5", "hostkey": "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"], "target": "localhost"}

View File

@ -1 +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"], "target": "localhost"} {"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": [{"hash": "UrnXIVH+7dlw8UqYocl48yUEcKrthGDQG2CPCgp7MxU", "hash_alg": "SHA256", "hostkey": "ssh-ed25519"}, {"hash": "1e:0c:7b:34:73:bf:52:41:b0:f9:d1:a9:ab:98:c7:c9", "hash_alg": "MD5", "hostkey": "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"], "target": "localhost"}

View File

@ -1 +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"], "target": "localhost"} {"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": [{"hash": "UrnXIVH+7dlw8UqYocl48yUEcKrthGDQG2CPCgp7MxU", "hash_alg": "SHA256", "hostkey": "ssh-ed25519"}, {"hash": "1e:0c:7b:34:73:bf:52:41:b0:f9:d1:a9:ab:98:c7:c9", "hash_alg": "MD5", "hostkey": "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"], "target": "localhost"}

View File

@ -1 +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"], "target": "localhost"} {"banner": {"comments": "", "protocol": [2, 0], "raw": "", "software": "tinyssh_noversion"}, "compression": ["none"], "enc": ["chacha20-poly1305@openssh.com"], "fingerprints": [{"hash": "89ocln1x7KNqnMgWffGoYtD70ksJ4FrH7BMJHa7SrwU", "hash_alg": "SHA256", "hostkey": "ssh-ed25519"}, {"hash": "dd:9c:6d:f9:b0:8c:af:fa:c2:65:81:5d:5d:56:f8:21", "hash_alg": "MD5", "hostkey": "ssh-ed25519"}], "kex": [{"algorithm": "curve25519-sha256"}, {"algorithm": "curve25519-sha256@libssh.org"}, {"algorithm": "sntrup4591761x25519-sha512@tinyssh.org"}], "key": [{"algorithm": "ssh-ed25519"}], "mac": ["hmac-sha2-256"], "target": "localhost"}

View File

@ -37,7 +37,7 @@ def test_prevent_runtime_error_regression(ssh_audit, kex):
rv = ssh_audit.build_struct('localhost', banner=None, kex=kex) rv = ssh_audit.build_struct('localhost', banner=None, kex=kex)
assert len(rv["fingerprints"]) == 9 assert len(rv["fingerprints"]) == (9 * 2) # Each host key generates two hash fingerprints: one using SHA256, and one using MD5.
for key in ['banner', 'compression', 'enc', 'fingerprints', 'kex', 'key', 'mac']: for key in ['banner', 'compression', 'enc', 'fingerprints', 'kex', 'key', 'mac']:
assert key in rv assert key in rv

View File

@ -8,6 +8,7 @@ class TestResolve:
def init(self, ssh_audit): def init(self, ssh_audit):
self.AuditConf = ssh_audit.AuditConf self.AuditConf = ssh_audit.AuditConf
self.audit = ssh_audit.audit self.audit = ssh_audit.audit
self.OutputBuffer = ssh_audit.OutputBuffer
self.ssh_socket = ssh_audit.SSH_Socket self.ssh_socket = ssh_audit.SSH_Socket
def _conf(self): def _conf(self):
@ -20,7 +21,7 @@ class TestResolve:
vsocket = virtual_socket vsocket = virtual_socket
vsocket.gsock.addrinfodata['localhost#22'] = socket.gaierror(8, 'hostname nor servname provided, or not known') vsocket.gsock.addrinfodata['localhost#22'] = socket.gaierror(8, 'hostname nor servname provided, or not known')
conf = self._conf() conf = self._conf()
s = self.ssh_socket('localhost', 22, conf.ip_version_preference) s = self.ssh_socket(self.OutputBuffer(), 'localhost', 22, conf.ip_version_preference)
output_spy.begin() output_spy.begin()
with pytest.raises(SystemExit): with pytest.raises(SystemExit):
list(s._resolve()) list(s._resolve())
@ -32,7 +33,7 @@ class TestResolve:
vsocket = virtual_socket vsocket = virtual_socket
vsocket.gsock.addrinfodata['localhost#22'] = [] vsocket.gsock.addrinfodata['localhost#22'] = []
conf = self._conf() conf = self._conf()
s = self.ssh_socket('localhost', 22, conf.ip_version_preference) s = self.ssh_socket(self.OutputBuffer(), 'localhost', 22, conf.ip_version_preference)
output_spy.begin() output_spy.begin()
r = list(s._resolve()) r = list(s._resolve())
assert len(r) == 0 assert len(r) == 0
@ -40,7 +41,7 @@ class TestResolve:
def test_resolve_ipv4(self, virtual_socket): def test_resolve_ipv4(self, virtual_socket):
conf = self._conf() conf = self._conf()
conf.ipv4 = True conf.ipv4 = True
s = self.ssh_socket('localhost', 22, conf.ip_version_preference) s = self.ssh_socket(self.OutputBuffer(), 'localhost', 22, conf.ip_version_preference)
r = list(s._resolve()) r = list(s._resolve())
assert len(r) == 1 assert len(r) == 1
assert r[0] == (socket.AF_INET, ('127.0.0.1', 22)) assert r[0] == (socket.AF_INET, ('127.0.0.1', 22))
@ -48,14 +49,14 @@ class TestResolve:
def test_resolve_ipv6(self, virtual_socket): def test_resolve_ipv6(self, virtual_socket):
conf = self._conf() conf = self._conf()
conf.ipv6 = True conf.ipv6 = True
s = self.ssh_socket('localhost', 22, conf.ip_version_preference) s = self.ssh_socket(self.OutputBuffer(), 'localhost', 22, conf.ip_version_preference)
r = list(s._resolve()) r = list(s._resolve())
assert len(r) == 1 assert len(r) == 1
assert r[0] == (socket.AF_INET6, ('::1', 22)) assert r[0] == (socket.AF_INET6, ('::1', 22))
def test_resolve_ipv46_both(self, virtual_socket): def test_resolve_ipv46_both(self, virtual_socket):
conf = self._conf() conf = self._conf()
s = self.ssh_socket('localhost', 22, conf.ip_version_preference) s = self.ssh_socket(self.OutputBuffer(), 'localhost', 22, conf.ip_version_preference)
r = list(s._resolve()) r = list(s._resolve())
assert len(r) == 2 assert len(r) == 2
assert r[0] == (socket.AF_INET, ('127.0.0.1', 22)) assert r[0] == (socket.AF_INET, ('127.0.0.1', 22))
@ -65,7 +66,7 @@ class TestResolve:
conf = self._conf() conf = self._conf()
conf.ipv4 = True conf.ipv4 = True
conf.ipv6 = True conf.ipv6 = True
s = self.ssh_socket('localhost', 22, conf.ip_version_preference) s = self.ssh_socket(self.OutputBuffer(), 'localhost', 22, conf.ip_version_preference)
r = list(s._resolve()) r = list(s._resolve())
assert len(r) == 2 assert len(r) == 2
assert r[0] == (socket.AF_INET, ('127.0.0.1', 22)) assert r[0] == (socket.AF_INET, ('127.0.0.1', 22))
@ -73,7 +74,7 @@ class TestResolve:
conf = self._conf() conf = self._conf()
conf.ipv6 = True conf.ipv6 = True
conf.ipv4 = True conf.ipv4 = True
s = self.ssh_socket('localhost', 22, conf.ip_version_preference) s = self.ssh_socket(self.OutputBuffer(), 'localhost', 22, conf.ip_version_preference)
r = list(s._resolve()) r = list(s._resolve())
assert len(r) == 2 assert len(r) == 2
assert r[0] == (socket.AF_INET6, ('::1', 22)) assert r[0] == (socket.AF_INET6, ('::1', 22))

View File

@ -1,5 +1,6 @@
import pytest import pytest
from ssh_audit.outputbuffer import OutputBuffer
from ssh_audit.ssh_socket import SSH_Socket from ssh_audit.ssh_socket import SSH_Socket
@ -7,24 +8,25 @@ from ssh_audit.ssh_socket import SSH_Socket
class TestSocket: class TestSocket:
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def init(self, ssh_audit): def init(self, ssh_audit):
self.OutputBuffer = OutputBuffer
self.ssh_socket = SSH_Socket self.ssh_socket = SSH_Socket
def test_invalid_host(self, virtual_socket): def test_invalid_host(self, virtual_socket):
with pytest.raises(ValueError): with pytest.raises(ValueError):
self.ssh_socket(None, 22) self.ssh_socket(self.OutputBuffer(), None, 22)
def test_invalid_port(self, virtual_socket): def test_invalid_port(self, virtual_socket):
with pytest.raises(ValueError): with pytest.raises(ValueError):
self.ssh_socket('localhost', 'abc') self.ssh_socket(self.OutputBuffer(), 'localhost', 'abc')
with pytest.raises(ValueError): with pytest.raises(ValueError):
self.ssh_socket('localhost', -1) self.ssh_socket(self.OutputBuffer(), 'localhost', -1)
with pytest.raises(ValueError): with pytest.raises(ValueError):
self.ssh_socket('localhost', 0) self.ssh_socket(self.OutputBuffer(), 'localhost', 0)
with pytest.raises(ValueError): with pytest.raises(ValueError):
self.ssh_socket('localhost', 65536) self.ssh_socket(self.OutputBuffer(), 'localhost', 65536)
def test_not_connected_socket(self, virtual_socket): def test_not_connected_socket(self, virtual_socket):
sock = self.ssh_socket('localhost', 22) sock = self.ssh_socket(self.OutputBuffer(), 'localhost', 22)
banner, header, err = sock.get_banner() banner, header, err = sock.get_banner()
assert banner is None assert banner is None
assert len(header) == 0 assert len(header) == 0

View File

@ -138,7 +138,7 @@ class TestSSH1:
self.audit(out, self._conf()) self.audit(out, self._conf())
out.write() out.write()
lines = output_spy.flush() lines = output_spy.flush()
assert len(lines) == 16 assert len(lines) == 17
def test_ssh1_server_invalid_first_packet(self, output_spy, virtual_socket): def test_ssh1_server_invalid_first_packet(self, output_spy, virtual_socket):
vsocket = virtual_socket vsocket = virtual_socket