mirror of
https://github.com/jtesta/ssh-audit.git
synced 2025-07-06 14:02:49 -05:00
Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
dc36622b50 | |||
8e0b83176a | |||
a16eb2d6cb | |||
2848c1fb16 | |||
2cff202b32 | |||
dae92513fd | |||
e101e22720 | |||
b3a46e8318 |
@ -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:
|
||||

|
||||
|
||||
### 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
17
pypi/notes.txt
Normal 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
|
24
ssh-audit.py
24
ssh-audit.py
@ -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()
|
||||
|
Reference in New Issue
Block a user