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: Below is a screen shot of the client-auditing output when an unhardened OpenSSH v7.2 client connects:
![client_screenshot](https://user-images.githubusercontent.com/2982011/68867998-b946c100-06c4-11ea-975f-1f47e4178a74.png) ![client_screenshot](https://user-images.githubusercontent.com/2982011/68867998-b946c100-06c4-11ea-975f-1f47e4178a74.png)
### Hardening Guides
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 ## 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) ### v2.1.0 (2019-11-14)
- Added client software auditing functionality (see `-c` / `--client-audit` option). - 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)). - 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 from __future__ import print_function
import base64, binascii, errno, hashlib, getopt, io, os, random, re, select, socket, struct, sys, json 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 SSH_HEADER = 'SSH-{0}-OpenSSH_8.0' # SSH software to impersonate
if sys.version_info.major < 3: 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-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-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]], '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': [[]], 'ssh-rsa-sha256@ssh.com': [[]],
'ecdsa-sha2-1.3.132.0.10': [[], [], [WARN_RNDSIG_KEY]], # ECDSA over secp256k1 (i.e.: the Bitcoin curve) 'ecdsa-sha2-1.3.132.0.10': [[], [], [WARN_RNDSIG_KEY]], # ECDSA over secp256k1 (i.e.: the Bitcoin curve)
}, },
'enc': { 'enc': {
'none': [['1.2.2,d2013.56,l10.2'], [FAIL_PLAINTEXT]], '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': [[], [FAIL_WEAK_CIPHER], [WARN_CIPHER_MODE, WARN_BLOCK_SIZE]],
'des-cbc-ssh1': [[], [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-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]], '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]], '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.3', '2.1.1', 1, 'CVE-2001-0361', 4.0, 'recover plaintext from ciphertext'],
['1.2', '2.1', 1, 'CVE-2000-0525', 10.0, 'execute arbitrary code (improper privileges)']], ['1.2', '2.1', 1, 'CVE-2000-0525', 10.0, 'execute arbitrary code (improper privileges)']],
'PuTTY': [ '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', 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.71', 6, 'CVE-2019-XXXX', 5.0, 'local privilege escalation in Pageant'],
['0.0', '0.70', 2, 'CVE-2019-9898', 7.5, 'potential recycling of random numbers'], ['0.0', '0.70', 2, 'CVE-2019-9898', 7.5, 'potential recycling of random numbers'],
@ -1997,6 +2004,8 @@ class SSH(object): # pylint: disable=too-few-public-methods
self.__ipvo = () self.__ipvo = ()
self.__timeout = timeout self.__timeout = timeout
self.__timeout_set = timeout_set self.__timeout_set = timeout_set
self.client_host = None
self.client_port = None
def _resolve(self, ipvo): def _resolve(self, ipvo):
@ -2957,11 +2966,14 @@ def output_info(algs, software, client_audit, any_problems, padlen=0):
out.sep() 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 # 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 sshv = 1 if pkm is not None else 2
algs = SSH.Algorithms(pkm, kex) algs = SSH.Algorithms(pkm, kex)
with OutputBuffer() as obuf: with OutputBuffer() as obuf:
if client_audit:
out.good('(gen) client IP: {0}'.format(client_host))
if len(header) > 0: if len(header) > 0:
out.info('(gen) header: ' + '\n'.join(header)) out.info('(gen) header: ' + '\n'.join(header))
if banner is not None: if banner is not None:
@ -3142,7 +3154,7 @@ class Utils(object):
except: # pylint: disable=bare-except except: # pylint: disable=bare-except
return -1.0 return -1.0
def build_struct(banner, kex=None, pkm=None): def build_struct(banner, kex=None, pkm=None, client_host=None):
res = { res = {
"banner": { "banner": {
"raw": str(banner), "raw": str(banner),
@ -3151,6 +3163,8 @@ def build_struct(banner, kex=None, pkm=None):
"comments": banner.comments, "comments": banner.comments,
}, },
} }
if client_host is not None:
res['client_ip'] = client_host
if kex is not None: if kex is not None:
res['compression'] = kex.server.compression res['compression'] = kex.server.compression
@ -3278,9 +3292,9 @@ def audit(aconf, sshv=None):
SSH2.HostKeyTest.run(s, kex) SSH2.HostKeyTest.run(s, kex)
SSH2.GEXTest.run(s, kex) SSH2.GEXTest.run(s, kex)
if aconf.json: 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: else:
output(banner, header, client_audit=aconf.client_audit, kex=kex) output(banner, header, client_host=s.client_host, kex=kex)
utils = Utils() utils = Utils()