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

8 Commits

3 changed files with 45 additions and 5 deletions

View File

@ -51,7 +51,16 @@ Below is a screen shot of the server-auditing output when connecting to an unhar
Below is a screen shot of the client-auditing output when an unhardened OpenSSH v7.2 client connects:
![client_screenshot](https://user-images.githubusercontent.com/2982011/68867998-b946c100-06c4-11ea-975f-1f47e4178a74.png)
### 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)
## ChangeLog
### v2.1.1 (2019-11-26)
- Added 2 new host key types: `rsa-sha2-256-cert-v01@openssh.com`, `rsa-sha2-512-cert-v01@openssh.com`.
- Added 2 new ciphers: `des`, `3des`.
- Added 3 new PuTTY vulnerabilities.
- During client testing, client IP address is now listed in output.
### v2.1.0 (2019-11-14)
- Added client software auditing functionality (see `-c` / `--client-audit` option).
- Added JSON output option (see `-j` / `--json` option; credit [Andreas Jaggi](https://github.com/x-way)).

17
pypi/notes.txt Normal file
View File

@ -0,0 +1,17 @@
To create package and upload to test server:
# apt install virtualenv
$ virtualenv -p /usr/bin/python3 pypi_upload
$ cd pypi_upload; source bin/activate
$ pip3 install twine
$ cp -R path/to/ssh-audit .
$ cd ssh-audit/pypi
$ make
$ make uploadtest
To download from test server and verify:
$ virtualenv -p /usr/bin/python3 pypi_test
$ cd pypi_test; source bin/activate
$ pip3 install --index-url https://test.pypi.org/simple ssh-audit

View File

@ -27,7 +27,7 @@
from __future__ import print_function
import base64, binascii, errno, hashlib, getopt, io, os, random, re, select, socket, struct, sys, json
VERSION = 'v2.1.0'
VERSION = 'v2.1.1'
SSH_HEADER = 'SSH-{0}-OpenSSH_8.0' # SSH software to impersonate
if sys.version_info.major < 3:
@ -386,13 +386,17 @@ class SSH2(object): # pylint: disable=too-few-public-methods
'ecdsa-sha2-nistp256-cert-v01@openssh.com': [['5.7'], [WARN_CURVES_WEAK], [WARN_RNDSIG_KEY]],
'ecdsa-sha2-nistp384-cert-v01@openssh.com': [['5.7'], [WARN_CURVES_WEAK], [WARN_RNDSIG_KEY]],
'ecdsa-sha2-nistp521-cert-v01@openssh.com': [['5.7'], [WARN_CURVES_WEAK], [WARN_RNDSIG_KEY]],
'rsa-sha2-256-cert-v01@openssh.com': [['7.8']],
'rsa-sha2-512-cert-v01@openssh.com': [['7.8']],
'ssh-rsa-sha256@ssh.com': [[]],
'ecdsa-sha2-1.3.132.0.10': [[], [], [WARN_RNDSIG_KEY]], # ECDSA over secp256k1 (i.e.: the Bitcoin curve)
},
'enc': {
'none': [['1.2.2,d2013.56,l10.2'], [FAIL_PLAINTEXT]],
'des': [[], [FAIL_WEAK_CIPHER], [WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
'des-cbc': [[], [FAIL_WEAK_CIPHER], [WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
'des-cbc-ssh1': [[], [FAIL_WEAK_CIPHER], [WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
'3des': [[], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH74_UNSAFE, WARN_CIPHER_WEAK, WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
'3des-cbc': [['1.2.2,d0.28,l10.2', '6.6', None], [FAIL_OPENSSH67_UNSAFE], [WARN_OPENSSH74_UNSAFE, WARN_CIPHER_WEAK, WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
'3des-ctr': [['d0.52'], [FAIL_WEAK_CIPHER]],
'blowfish-cbc': [['1.2.2,d0.28,l10.2', '6.6,d0.52', '7.1,d0.52'], [FAIL_OPENSSH67_UNSAFE, FAIL_DBEAR53_DISABLED], [WARN_OPENSSH72_LEGACY, WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
@ -1948,6 +1952,9 @@ class SSH(object): # pylint: disable=too-few-public-methods
['1.2.3', '2.1.1', 1, 'CVE-2001-0361', 4.0, 'recover plaintext from ciphertext'],
['1.2', '2.1', 1, 'CVE-2000-0525', 10.0, 'execute arbitrary code (improper privileges)']],
'PuTTY': [
['0.0', '0.72', 2, 'CVE-2019-17069', 5.0, 'potential DOS by remote SSHv1 server'],
['0.71', '0.72', 2, 'CVE-2019-17068', 5.0, 'xterm bracketed paste mode command injection'],
['0.52', '0.72', 2, 'CVE-2019-17067', 7.5, 'port rebinding weakness in port forward tunnel handling'],
['0.0', '0.71', 2, 'CVE-2019-XXXX', 5.0, 'undefined vulnerability in obsolete SSHv1 protocol handling'],
['0.0', '0.71', 6, 'CVE-2019-XXXX', 5.0, 'local privilege escalation in Pageant'],
['0.0', '0.70', 2, 'CVE-2019-9898', 7.5, 'potential recycling of random numbers'],
@ -1997,6 +2004,8 @@ class SSH(object): # pylint: disable=too-few-public-methods
self.__ipvo = ()
self.__timeout = timeout
self.__timeout_set = timeout_set
self.client_host = None
self.client_port = None
def _resolve(self, ipvo):
@ -2957,11 +2966,14 @@ def output_info(algs, software, client_audit, any_problems, padlen=0):
out.sep()
def output(banner, header, client_audit=False, kex=None, pkm=None):
def output(banner, header, client_host=None, kex=None, pkm=None):
# type: (Optional[SSH.Banner], List[text_type], Optional[SSH2.Kex], Optional[SSH1.PublicKeyMessage]) -> None
client_audit = (client_host != None) # If set, this is a client audit.
sshv = 1 if pkm is not None else 2
algs = SSH.Algorithms(pkm, kex)
with OutputBuffer() as obuf:
if client_audit:
out.good('(gen) client IP: {0}'.format(client_host))
if len(header) > 0:
out.info('(gen) header: ' + '\n'.join(header))
if banner is not None:
@ -3142,7 +3154,7 @@ class Utils(object):
except: # pylint: disable=bare-except
return -1.0
def build_struct(banner, kex=None, pkm=None):
def build_struct(banner, kex=None, pkm=None, client_host=None):
res = {
"banner": {
"raw": str(banner),
@ -3151,6 +3163,8 @@ def build_struct(banner, kex=None, pkm=None):
"comments": banner.comments,
},
}
if client_host is not None:
res['client_ip'] = client_host
if kex is not None:
res['compression'] = kex.server.compression
@ -3278,9 +3292,9 @@ def audit(aconf, sshv=None):
SSH2.HostKeyTest.run(s, kex)
SSH2.GEXTest.run(s, kex)
if aconf.json:
print(json.dumps(build_struct(banner, kex=kex), sort_keys=True))
print(json.dumps(build_struct(banner, kex=kex, client_host=s.client_host), sort_keys=True))
else:
output(banner, header, client_audit=aconf.client_audit, kex=kex)
output(banner, header, client_host=s.client_host, kex=kex)
utils = Utils()