mirror of
https://github.com/jtesta/ssh-audit.git
synced 2025-07-06 05:57:50 -05:00
Compare commits
25 Commits
Author | SHA1 | Date | |
---|---|---|---|
4f2f995b62 | |||
134236fa7f | |||
a6b658d194 | |||
297a807f88 | |||
20d94df400 | |||
b76060cf49 | |||
1cf1c874db | |||
992d8233c9 | |||
f377b7cea3 | |||
70d9ab2e6b | |||
e7d320f602 | |||
682cb66f85 | |||
076681a671 | |||
98a1fb0315 | |||
45da9f20ae | |||
aa21df29e7 | |||
32ed9242af | |||
07862489c4 | |||
e508a963e7 | |||
2f1a2a60b1 | |||
5eb669e01c | |||
8e9fe20fac | |||
83bd049486 | |||
c483fe1861 | |||
741bd631e2 |
24
.github/workflows/tox.yaml
vendored
Normal file
24
.github/workflows/tox.yaml
vendored
Normal 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
|
@ -10,6 +10,7 @@ python:
|
|||||||
- "3.7"
|
- "3.7"
|
||||||
- "3.8"
|
- "3.8"
|
||||||
- "3.9"
|
- "3.9"
|
||||||
|
- "3.10"
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
- pip
|
- pip
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
19
PACKAGING.md
19
PACKAGING.md
@ -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,8 +90,8 @@ 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
|
||||||
```
|
```
|
||||||
|
46
README.md
46
README.md
@ -1,14 +1,25 @@
|
|||||||
# ssh-audit
|
# ssh-audit
|
||||||
[](https://travis-ci.org/jtesta/ssh-audit)
|
[](https://github.com/jtesta/ssh-audit/blob/master/LICENSE)
|
||||||
<!--
|
[](https://pypi.org/project/ssh-audit/)
|
||||||
[](https://ci.appveyor.com/project/arthepsy/ssh-audit)
|
[](https://hub.docker.com/r/positronsecurity/ssh-audit)
|
||||||
[](https://codecov.io/gh/arthepsy/ssh-audit)
|
[](https://github.com/jtesta/ssh-audit/actions)
|
||||||
[](https://sq.evolutiongaming.com/dashboard?id=arthepsy-github%3Assh-audit%3Adevelop)
|
[](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:
|
||||||

|

|
||||||
@ -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:
|
||||||

|

|
||||||
|
|
||||||
### 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).
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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 = ''
|
||||||
|
@ -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.
|
||||||
|
@ -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))
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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': [[]],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
@ -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:
|
||||||
|
@ -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>
|
||||||
|
@ -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"}
|
||||||
|
@ -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"}
|
||||||
|
@ -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"}
|
||||||
|
@ -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"}
|
||||||
|
@ -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"}
|
||||||
|
@ -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"}
|
||||||
|
@ -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"}
|
||||||
|
@ -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"}
|
||||||
|
@ -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"}
|
||||||
|
@ -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"}
|
||||||
|
@ -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"}
|
||||||
|
@ -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
|
||||||
|
@ -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))
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
Reference in New Issue
Block a user