mirror of
https://github.com/jtesta/ssh-audit.git
synced 2024-11-25 20:11:40 +01:00
Flake8 fixes (#35)
* Apply Flake8 also on `setup.py` modified: tox.ini * Fix W605 - invalid escape syntax modified: packages/setup.py modified: tox.ini * Update comment about Flake8: W504 W503 and W504 are mutual exclusive - so we have to keep one of them. modified: tox.ini * Fix F841 - variable assigned but never used modified: ssh-audit.py modified: tox.ini * Fix E741 - ambiguous variable name 'l' modified: ssh-audit.py modified: tox.ini * Fix E712 - comparison to False should be 'if cond is False' ... and not 'if conf == False'. modified: ssh-audit.py modified: tox.ini * Fix E711 - comparison to None should be 'if cond is not None' ... and not 'if cond != None'. modified: ssh-audit.py modified: tox.ini * Fix E305 - expected 2 blank lines ... after class or function definition, found 1. modified: ssh-audit.py modified: tox.ini * Fix E303 - too many blank lines modified: ssh-audit.py modified: tox.ini * Fix E303 - too many blank lines modified: ssh-audit.py modified: tox.ini * Fix E301 - expected 1 blank line, found 0 No code change necessary, probably fixed by another commit. modified: tox.ini * Fix E265 - block comment should start with '# ' There is lots of commented out code, which usually should be just deleted. I will keep it for now, as I am not yet very familiar with the code base. modified: ssh-audit.py modified: tox.ini * Fix E261 - at least two spaces before inline comment modified: ssh-audit.py modified: tox.ini * Fix E251 - unexpected spaces around keyword / parameter equals modified: packages/setup.py modified: tox.ini * Fix E231 - missing whitespace after ',' No code change necessary, probably fixed by previous commit. modified: tox.ini * Fix E226 - missing whitespace around arithmetic operator modified: ssh-audit.py modified: tox.ini * Fix W293 - blank line contains whitespace modified: ssh-audit.py modified: tox.ini * Fix E221 - multiple spaces before operator modified: ssh-audit.py modified: tox.ini * Update comment about Flake 8 E241 Lots of data is formatted as tables, so this warning is disabled for a good reason. modified: tox.ini * Fix E401 - multiple imports on one line modified: ssh-audit.py modified: tox.ini * Do not ignore Flake8 warning F401 ... as there were no errors in source code anyway. modified: tox.ini * Fix F821 - undefined name modified: ssh-audit.py modified: tox.ini * Reformat ignore section for Flake8 modified: tox.ini * Flake8 test suite modified: test/conftest.py modified: test/test_auditconf.py modified: test/test_banner.py modified: test/test_buffer.py modified: test/test_errors.py modified: test/test_output.py modified: test/test_resolve.py modified: test/test_socket.py modified: test/test_software.py modified: test/test_ssh1.py modified: test/test_ssh2.py modified: test/test_ssh_algorithm.py modified: test/test_utils.py modified: test/test_version_compare.py modified: tox.ini
This commit is contained in:
parent
29d874b450
commit
246a41d46f
@ -5,7 +5,7 @@ import re
|
|||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
|
|
||||||
|
|
||||||
version = re.search('^VERSION\s*=\s*\'v(\d\.\d\.\d)\'', open('sshaudit/sshaudit.py').read(), re.M).group(1)
|
version = re.search(r'^VERSION\s*=\s*\'v(\d\.\d\.\d)\'', open('sshaudit/sshaudit.py').read(), re.M).group(1)
|
||||||
print("\n\nPackaging ssh-audit v%s...\n\n" % version)
|
print("\n\nPackaging ssh-audit v%s...\n\n" % version)
|
||||||
|
|
||||||
with open("sshaudit/README.md", "rb") as f:
|
with open("sshaudit/README.md", "rb") as f:
|
||||||
@ -13,20 +13,20 @@ with open("sshaudit/README.md", "rb") as f:
|
|||||||
|
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name = "ssh-audit",
|
name="ssh-audit",
|
||||||
packages = ["sshaudit"],
|
packages=["sshaudit"],
|
||||||
license = 'MIT',
|
license='MIT',
|
||||||
entry_points = {
|
entry_points={
|
||||||
"console_scripts": ['ssh-audit = sshaudit.sshaudit:main']
|
"console_scripts": ['ssh-audit = sshaudit.sshaudit:main']
|
||||||
},
|
},
|
||||||
version = version,
|
version=version,
|
||||||
description = "An SSH server & client configuration security auditing tool",
|
description="An SSH server & client configuration security auditing tool",
|
||||||
long_description = long_descr,
|
long_description=long_descr,
|
||||||
long_description_content_type = "text/markdown",
|
long_description_content_type="text/markdown",
|
||||||
author = "Joe Testa",
|
author="Joe Testa",
|
||||||
author_email = "jtesta@positronsecurity.com",
|
author_email="jtesta@positronsecurity.com",
|
||||||
url = "https://github.com/jtesta/ssh-audit",
|
url="https://github.com/jtesta/ssh-audit",
|
||||||
classifiers = [
|
classifiers=[
|
||||||
"Development Status :: 5 - Production/Stable",
|
"Development Status :: 5 - Production/Stable",
|
||||||
"Intended Audience :: Information Technology",
|
"Intended Audience :: Information Technology",
|
||||||
"Intended Audience :: System Administrators",
|
"Intended Audience :: System Administrators",
|
||||||
|
104
ssh-audit.py
104
ssh-audit.py
@ -25,7 +25,20 @@
|
|||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
"""
|
"""
|
||||||
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
|
||||||
|
import binascii
|
||||||
|
import errno
|
||||||
|
import getopt
|
||||||
|
import hashlib
|
||||||
|
import io
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import re
|
||||||
|
import select
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
VERSION = 'v2.2.1-dev'
|
VERSION = 'v2.2.1-dev'
|
||||||
@ -41,7 +54,7 @@ if sys.version_info >= (3,): # pragma: nocover
|
|||||||
else: # pragma: nocover
|
else: # pragma: nocover
|
||||||
import StringIO as _StringIO # pylint: disable=import-error
|
import StringIO as _StringIO # pylint: disable=import-error
|
||||||
StringIO = BytesIO = _StringIO.StringIO
|
StringIO = BytesIO = _StringIO.StringIO
|
||||||
text_type = unicode # pylint: disable=undefined-variable
|
text_type = unicode # pylint: disable=undefined-variable # noqa: F821
|
||||||
binary_type = str
|
binary_type = str
|
||||||
try: # pragma: nocover
|
try: # pragma: nocover
|
||||||
# pylint: disable=unused-import
|
# pylint: disable=unused-import
|
||||||
@ -190,9 +203,9 @@ class AuditConf(object):
|
|||||||
elif o in ('-t', '--timeout'):
|
elif o in ('-t', '--timeout'):
|
||||||
aconf.timeout = float(a)
|
aconf.timeout = float(a)
|
||||||
aconf.timeout_set = True
|
aconf.timeout_set = True
|
||||||
if len(args) == 0 and aconf.client_audit == False:
|
if len(args) == 0 and aconf.client_audit is False:
|
||||||
usage_cb()
|
usage_cb()
|
||||||
if aconf.client_audit == False:
|
if aconf.client_audit is False:
|
||||||
if oport is not None:
|
if oport is not None:
|
||||||
host = args[0]
|
host = args[0]
|
||||||
else:
|
else:
|
||||||
@ -700,7 +713,7 @@ class SSH2(object): # pylint: disable=too-few-public-methods
|
|||||||
'ecdh-sha2-nistp256': KexNISTP256,
|
'ecdh-sha2-nistp256': KexNISTP256,
|
||||||
'ecdh-sha2-nistp384': KexNISTP384,
|
'ecdh-sha2-nistp384': KexNISTP384,
|
||||||
'ecdh-sha2-nistp521': KexNISTP521,
|
'ecdh-sha2-nistp521': KexNISTP521,
|
||||||
#'kexguess2@matt.ucc.asn.au': ???
|
# 'kexguess2@matt.ucc.asn.au': ???
|
||||||
}
|
}
|
||||||
|
|
||||||
# Pick the first kex algorithm that the server supports, which we
|
# Pick the first kex algorithm that the server supports, which we
|
||||||
@ -798,7 +811,6 @@ class SSH2(object): # pylint: disable=too-few-public-methods
|
|||||||
else:
|
else:
|
||||||
host_key_types[host_key_type]['parsed'] = True
|
host_key_types[host_key_type]['parsed'] = True
|
||||||
|
|
||||||
|
|
||||||
# Performs DH group exchanges to find what moduli are supported, and checks
|
# Performs DH group exchanges to find what moduli are supported, and checks
|
||||||
# their size.
|
# their size.
|
||||||
class GEXTest(object):
|
class GEXTest(object):
|
||||||
@ -865,7 +877,7 @@ class SSH2(object): # pylint: disable=too-few-public-methods
|
|||||||
# got here, doesn't mean the server is vulnerable...
|
# got here, doesn't mean the server is vulnerable...
|
||||||
smallest_modulus = kex_group.get_dh_modulus_size()
|
smallest_modulus = kex_group.get_dh_modulus_size()
|
||||||
|
|
||||||
except Exception as e: # pylint: disable=bare-except
|
except Exception: # pylint: disable=bare-except
|
||||||
pass
|
pass
|
||||||
finally:
|
finally:
|
||||||
s.close()
|
s.close()
|
||||||
@ -887,9 +899,9 @@ class SSH2(object): # pylint: disable=too-few-public-methods
|
|||||||
kex_group.send_init_gex(s, bits, bits, bits)
|
kex_group.send_init_gex(s, bits, bits, bits)
|
||||||
kex_group.recv_reply(s, False)
|
kex_group.recv_reply(s, False)
|
||||||
smallest_modulus = kex_group.get_dh_modulus_size()
|
smallest_modulus = kex_group.get_dh_modulus_size()
|
||||||
except Exception as e: # pylint: disable=bare-except
|
except Exception: # pylint: disable=bare-except
|
||||||
#import traceback
|
# import traceback
|
||||||
#print(traceback.format_exc())
|
# print(traceback.format_exc())
|
||||||
pass
|
pass
|
||||||
finally:
|
finally:
|
||||||
# The server is in a state that is not re-testable,
|
# The server is in a state that is not re-testable,
|
||||||
@ -897,7 +909,6 @@ class SSH2(object): # pylint: disable=too-few-public-methods
|
|||||||
# connection.
|
# connection.
|
||||||
s.close()
|
s.close()
|
||||||
|
|
||||||
|
|
||||||
if smallest_modulus > 0:
|
if smallest_modulus > 0:
|
||||||
kex.set_dh_modulus_size(gex_alg, smallest_modulus)
|
kex.set_dh_modulus_size(gex_alg, smallest_modulus)
|
||||||
|
|
||||||
@ -919,6 +930,7 @@ class SSH2(object): # pylint: disable=too-few-public-methods
|
|||||||
if reconnect_failed:
|
if reconnect_failed:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
class SSH1(object):
|
class SSH1(object):
|
||||||
class CRC32(object):
|
class CRC32(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -935,8 +947,8 @@ class SSH1(object):
|
|||||||
|
|
||||||
def calc(self, v):
|
def calc(self, v):
|
||||||
# type: (binary_type) -> int
|
# type: (binary_type) -> int
|
||||||
crc, l = 0, len(v)
|
crc, length = 0, len(v)
|
||||||
for i in range(l):
|
for i in range(length):
|
||||||
n = ord(v[i:i + 1])
|
n = ord(v[i:i + 1])
|
||||||
n = n ^ (crc & 0xff)
|
n = n ^ (crc & 0xff)
|
||||||
crc = (crc >> 8) ^ self._table[n]
|
crc = (crc >> 8) ^ self._table[n]
|
||||||
@ -1189,6 +1201,7 @@ class ReadBuf(object):
|
|||||||
self._len = 0
|
self._len = 0
|
||||||
super(ReadBuf, self).reset()
|
super(ReadBuf, self).reset()
|
||||||
|
|
||||||
|
|
||||||
class WriteBuf(object):
|
class WriteBuf(object):
|
||||||
def __init__(self, data=None):
|
def __init__(self, data=None):
|
||||||
# type: (Optional[binary_type]) -> None
|
# type: (Optional[binary_type]) -> None
|
||||||
@ -1609,7 +1622,7 @@ class SSH(object): # pylint: disable=too-few-public-methods
|
|||||||
|
|
||||||
def __getitem__(self, product):
|
def __getitem__(self, product):
|
||||||
# type: (str) -> Sequence[Optional[str]]
|
# type: (str) -> Sequence[Optional[str]]
|
||||||
return tuple(self.__storage.get(product, [None]*4))
|
return tuple(self.__storage.get(product, [None] * 4))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
# type: () -> str
|
# type: () -> str
|
||||||
@ -1640,7 +1653,7 @@ class SSH(object): # pylint: disable=too-few-public-methods
|
|||||||
ssh_versions[ssh_prod] = ssh_ver
|
ssh_versions[ssh_prod] = ssh_ver
|
||||||
for ssh_product, ssh_version in ssh_versions.items():
|
for ssh_product, ssh_version in ssh_versions.items():
|
||||||
if ssh_product not in self.__storage:
|
if ssh_product not in self.__storage:
|
||||||
self.__storage[ssh_product] = [None]*4
|
self.__storage[ssh_product] = [None] * 4
|
||||||
prev = self[ssh_product][pos]
|
prev = self[ssh_product][pos]
|
||||||
if (prev is None or
|
if (prev is None or
|
||||||
(prev < ssh_version and pos % 2 == 0) or
|
(prev < ssh_version and pos % 2 == 0) or
|
||||||
@ -1783,19 +1796,18 @@ class SSH(object): # pylint: disable=too-few-public-methods
|
|||||||
if software is not None:
|
if software is not None:
|
||||||
if software.product not in vproducts:
|
if software.product not in vproducts:
|
||||||
unknown_software = True
|
unknown_software = True
|
||||||
#
|
|
||||||
# The code below is commented out because it would try to guess what the server is,
|
# The code below is commented out because it would try to guess what the server is,
|
||||||
# usually resulting in wild & incorrect recommendations.
|
# usually resulting in wild & incorrect recommendations.
|
||||||
#
|
# if software is None:
|
||||||
# if software is None:
|
# ssh_timeframe = self.get_ssh_timeframe(for_server)
|
||||||
# ssh_timeframe = self.get_ssh_timeframe(for_server)
|
# for product in vproducts:
|
||||||
# for product in vproducts:
|
# if product not in ssh_timeframe:
|
||||||
# if product not in ssh_timeframe:
|
# continue
|
||||||
# continue
|
# version = ssh_timeframe.get_from(product, for_server)
|
||||||
# version = ssh_timeframe.get_from(product, for_server)
|
# if version is not None:
|
||||||
# if version is not None:
|
# software = SSH.Software(None, product, version, None, None)
|
||||||
# software = SSH.Software(None, product, version, None, None)
|
# break
|
||||||
# break
|
|
||||||
rec = {} # type: Dict[int, Dict[str, Dict[str, Dict[str, int]]]]
|
rec = {} # type: Dict[int, Dict[str, Dict[str, Dict[str, int]]]]
|
||||||
if software is None:
|
if software is None:
|
||||||
unknown_software = True
|
unknown_software = True
|
||||||
@ -2057,7 +2069,6 @@ class SSH(object): # pylint: disable=too-few-public-methods
|
|||||||
self.client_host = None
|
self.client_host = None
|
||||||
self.client_port = None
|
self.client_port = None
|
||||||
|
|
||||||
|
|
||||||
def _resolve(self, ipvo):
|
def _resolve(self, ipvo):
|
||||||
# type: (Sequence[int]) -> Iterable[Tuple[int, Tuple[Any, ...]]]
|
# type: (Sequence[int]) -> Iterable[Tuple[int, Tuple[Any, ...]]]
|
||||||
ipvo = tuple([x for x in utils.unique_seq(ipvo) if x in (4, 6)])
|
ipvo = tuple([x for x in utils.unique_seq(ipvo) if x in (4, 6)])
|
||||||
@ -2081,7 +2092,6 @@ class SSH(object): # pylint: disable=too-few-public-methods
|
|||||||
out.fail('[exception] {0}'.format(e))
|
out.fail('[exception] {0}'.format(e))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
# Listens on a server socket and accepts one connection (used for
|
# Listens on a server socket and accepts one connection (used for
|
||||||
# auditing client connections).
|
# auditing client connections).
|
||||||
def listen_and_accept(self):
|
def listen_and_accept(self):
|
||||||
@ -2093,7 +2103,7 @@ class SSH(object): # pylint: disable=too-few-public-methods
|
|||||||
s.bind(('0.0.0.0', self.__port))
|
s.bind(('0.0.0.0', self.__port))
|
||||||
s.listen()
|
s.listen()
|
||||||
self.__sock_map[s.fileno()] = s
|
self.__sock_map[s.fileno()] = s
|
||||||
except Exception as e:
|
except Exception:
|
||||||
print("Warning: failed to listen on any IPv4 interfaces.")
|
print("Warning: failed to listen on any IPv4 interfaces.")
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -2105,7 +2115,7 @@ class SSH(object): # pylint: disable=too-few-public-methods
|
|||||||
s.bind(('::', self.__port))
|
s.bind(('::', self.__port))
|
||||||
s.listen()
|
s.listen()
|
||||||
self.__sock_map[s.fileno()] = s
|
self.__sock_map[s.fileno()] = s
|
||||||
except Exception as e:
|
except Exception:
|
||||||
print("Warning: failed to listen on any IPv6 interfaces.")
|
print("Warning: failed to listen on any IPv6 interfaces.")
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -2139,7 +2149,6 @@ class SSH(object): # pylint: disable=too-few-public-methods
|
|||||||
c.settimeout(self.__timeout)
|
c.settimeout(self.__timeout)
|
||||||
self.__sock = c
|
self.__sock = c
|
||||||
|
|
||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
# type: () -> None
|
# type: () -> None
|
||||||
err = None
|
err = None
|
||||||
@ -2169,10 +2178,10 @@ class SSH(object): # pylint: disable=too-few-public-methods
|
|||||||
banner = SSH_HEADER.format('1.5' if sshv == 1 else '2.0')
|
banner = SSH_HEADER.format('1.5' if sshv == 1 else '2.0')
|
||||||
if self.__state < self.SM_BANNER_SENT:
|
if self.__state < self.SM_BANNER_SENT:
|
||||||
self.send_banner(banner)
|
self.send_banner(banner)
|
||||||
# rto = self.__sock.gettimeout()
|
# rto = self.__sock.gettimeout()
|
||||||
# self.__sock.settimeout(0.7)
|
# self.__sock.settimeout(0.7)
|
||||||
s, e = self.recv()
|
s, e = self.recv()
|
||||||
# self.__sock.settimeout(rto)
|
# self.__sock.settimeout(rto)
|
||||||
if s < 0:
|
if s < 0:
|
||||||
return self.__banner, self.__header, e
|
return self.__banner, self.__header, e
|
||||||
e = None
|
e = None
|
||||||
@ -2357,7 +2366,6 @@ class KexDH(object): # pragma: nocover
|
|||||||
self.__f = 0
|
self.__f = 0
|
||||||
self.__h_sig = 0
|
self.__h_sig = 0
|
||||||
|
|
||||||
|
|
||||||
def set_params(self, g, p):
|
def set_params(self, g, p):
|
||||||
self.__g = g
|
self.__g = g
|
||||||
self.__p = p
|
self.__p = p
|
||||||
@ -2365,7 +2373,6 @@ class KexDH(object): # pragma: nocover
|
|||||||
self.__x = 0
|
self.__x = 0
|
||||||
self.__e = 0
|
self.__e = 0
|
||||||
|
|
||||||
|
|
||||||
def send_init(self, s, init_msg=SSH.Protocol.MSG_KEXDH_INIT):
|
def send_init(self, s, init_msg=SSH.Protocol.MSG_KEXDH_INIT):
|
||||||
# type: (SSH.Socket) -> None
|
# type: (SSH.Socket) -> None
|
||||||
r = random.SystemRandom()
|
r = random.SystemRandom()
|
||||||
@ -2403,7 +2410,6 @@ class KexDH(object): # pragma: nocover
|
|||||||
|
|
||||||
key_id = principles = None # pylint: disable=unused-variable
|
key_id = principles = None # pylint: disable=unused-variable
|
||||||
critical_options = extensions = None # pylint: disable=unused-variable
|
critical_options = extensions = None # pylint: disable=unused-variable
|
||||||
valid_after = valid_before = None # pylint: disable=unused-variable
|
|
||||||
nonce = ca_key = ca_key_type = None # pylint: disable=unused-variable
|
nonce = ca_key = ca_key_type = None # pylint: disable=unused-variable
|
||||||
ca_key_e = ca_key_n = None # pylint: disable=unused-variable
|
ca_key_e = ca_key_n = None # pylint: disable=unused-variable
|
||||||
|
|
||||||
@ -2455,12 +2461,10 @@ class KexDH(object): # pragma: nocover
|
|||||||
# The principles, which are... I don't know what.
|
# The principles, which are... I don't know what.
|
||||||
principles, principles_len, ptr = KexDH.__get_bytes(hostkey, ptr)
|
principles, principles_len, ptr = KexDH.__get_bytes(hostkey, ptr)
|
||||||
|
|
||||||
# The timestamp that this certificate is valid after.
|
# Skip over the timestamp that this certificate is valid after.
|
||||||
valid_after = hostkey[ptr:ptr + 8]
|
|
||||||
ptr += 8
|
ptr += 8
|
||||||
|
|
||||||
# The timestamp that this certificate is valid before.
|
# Skip over the timestamp that this certificate is valid before.
|
||||||
valid_before = hostkey[ptr:ptr + 8]
|
|
||||||
ptr += 8
|
ptr += 8
|
||||||
|
|
||||||
# TODO: validate the principles, and time range.
|
# TODO: validate the principles, and time range.
|
||||||
@ -2895,7 +2899,7 @@ def output_fingerprints(algs, sha256=True):
|
|||||||
if algs.ssh1kex is not None:
|
if algs.ssh1kex is not None:
|
||||||
name = 'ssh-rsa1'
|
name = 'ssh-rsa1'
|
||||||
fp = SSH.Fingerprint(algs.ssh1kex.host_key_fingerprint_data)
|
fp = SSH.Fingerprint(algs.ssh1kex.host_key_fingerprint_data)
|
||||||
#bits = algs.ssh1kex.host_key_bits
|
# bits = algs.ssh1kex.host_key_bits
|
||||||
fps.append((name, fp))
|
fps.append((name, fp))
|
||||||
if algs.ssh2kex is not None:
|
if algs.ssh2kex is not None:
|
||||||
host_keys = algs.ssh2kex.host_keys()
|
host_keys = algs.ssh2kex.host_keys()
|
||||||
@ -2917,8 +2921,8 @@ def output_fingerprints(algs, sha256=True):
|
|||||||
for fpp in fps:
|
for fpp in fps:
|
||||||
name, fp = fpp
|
name, fp = fpp
|
||||||
fpo = fp.sha256 if sha256 else fp.md5
|
fpo = fp.sha256 if sha256 else fp.md5
|
||||||
#p = '' if out.batch else ' ' * (padlen - len(name))
|
# p = '' if out.batch else ' ' * (padlen - len(name))
|
||||||
#out.good('(fin) {0}{1} -- {2} {3}'.format(name, p, bits, fpo))
|
# out.good('(fin) {0}{1} -- {2} {3}'.format(name, p, bits, fpo))
|
||||||
out.good('(fin) {0}: {1}'.format(name, fpo))
|
out.good('(fin) {0}: {1}'.format(name, fpo))
|
||||||
if len(obuf) > 0:
|
if len(obuf) > 0:
|
||||||
out.head('# fingerprints')
|
out.head('# fingerprints')
|
||||||
@ -3018,7 +3022,7 @@ def output_info(algs, software, client_audit, any_problems, padlen=0):
|
|||||||
|
|
||||||
def output(banner, header, client_host=None, 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.
|
client_audit = client_host is not 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:
|
||||||
@ -3077,11 +3081,11 @@ def output(banner, header, client_host=None, kex=None, pkm=None):
|
|||||||
perfect_config = output_recommendations(algs, software, maxlen)
|
perfect_config = output_recommendations(algs, software, maxlen)
|
||||||
output_info(algs, software, client_audit, not perfect_config)
|
output_info(algs, software, client_audit, not perfect_config)
|
||||||
|
|
||||||
|
|
||||||
# If we encountered any unknown algorithms, ask the user to report them.
|
# If we encountered any unknown algorithms, ask the user to report them.
|
||||||
if len(unknown_algorithms) > 0:
|
if len(unknown_algorithms) > 0:
|
||||||
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))
|
||||||
|
|
||||||
|
|
||||||
class Utils(object):
|
class Utils(object):
|
||||||
@classmethod
|
@classmethod
|
||||||
def _type_err(cls, v, target):
|
def _type_err(cls, v, target):
|
||||||
@ -3204,6 +3208,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, client_host=None):
|
def build_struct(banner, kex=None, pkm=None, client_host=None):
|
||||||
res = {
|
res = {
|
||||||
"banner": {
|
"banner": {
|
||||||
@ -3281,6 +3286,7 @@ def build_struct(banner, kex=None, pkm=None, client_host=None):
|
|||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
def audit(aconf, sshv=None):
|
def audit(aconf, sshv=None):
|
||||||
# type: (AuditConf, Optional[int]) -> None
|
# type: (AuditConf, Optional[int]) -> None
|
||||||
out.batch = aconf.batch
|
out.batch = aconf.batch
|
||||||
@ -3350,9 +3356,11 @@ def audit(aconf, sshv=None):
|
|||||||
utils = Utils()
|
utils = Utils()
|
||||||
out = Output()
|
out = Output()
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
conf = AuditConf.from_cmdline(sys.argv[1:], usage)
|
conf = AuditConf.from_cmdline(sys.argv[1:], usage)
|
||||||
audit(conf)
|
audit(conf)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__': # pragma: nocover
|
if __name__ == '__main__': # pragma: nocover
|
||||||
main()
|
main()
|
||||||
|
@ -52,7 +52,6 @@ class TestErrors(object):
|
|||||||
assert 'timed out' in lines[-1]
|
assert 'timed out' in lines[-1]
|
||||||
|
|
||||||
def test_recv_empty(self, output_spy, virtual_socket):
|
def test_recv_empty(self, output_spy, virtual_socket):
|
||||||
vsocket = virtual_socket
|
|
||||||
lines = self._audit(output_spy)
|
lines = self._audit(output_spy)
|
||||||
assert len(lines) == 1
|
assert len(lines) == 1
|
||||||
assert 'did not receive banner' in lines[-1]
|
assert 'did not receive banner' in lines[-1]
|
||||||
|
@ -25,7 +25,7 @@ class TestResolve(object):
|
|||||||
conf = self._conf()
|
conf = self._conf()
|
||||||
output_spy.begin()
|
output_spy.begin()
|
||||||
with pytest.raises(SystemExit):
|
with pytest.raises(SystemExit):
|
||||||
r = list(s._resolve(conf.ipvo))
|
list(s._resolve(conf.ipvo))
|
||||||
lines = output_spy.flush()
|
lines = output_spy.flush()
|
||||||
assert len(lines) == 1
|
assert len(lines) == 1
|
||||||
assert 'hostname nor servname provided' in lines[-1]
|
assert 'hostname nor servname provided' in lines[-1]
|
||||||
@ -40,7 +40,6 @@ class TestResolve(object):
|
|||||||
assert len(r) == 0
|
assert len(r) == 0
|
||||||
|
|
||||||
def test_resolve_ipv4(self, virtual_socket):
|
def test_resolve_ipv4(self, virtual_socket):
|
||||||
vsocket = virtual_socket
|
|
||||||
conf = self._conf()
|
conf = self._conf()
|
||||||
conf.ipv4 = True
|
conf.ipv4 = True
|
||||||
s = self.ssh.Socket('localhost', 22)
|
s = self.ssh.Socket('localhost', 22)
|
||||||
@ -49,7 +48,6 @@ class TestResolve(object):
|
|||||||
assert r[0] == (socket.AF_INET, ('127.0.0.1', 22))
|
assert r[0] == (socket.AF_INET, ('127.0.0.1', 22))
|
||||||
|
|
||||||
def test_resolve_ipv6(self, virtual_socket):
|
def test_resolve_ipv6(self, virtual_socket):
|
||||||
vsocket = virtual_socket
|
|
||||||
s = self.ssh.Socket('localhost', 22)
|
s = self.ssh.Socket('localhost', 22)
|
||||||
conf = self._conf()
|
conf = self._conf()
|
||||||
conf.ipv6 = True
|
conf.ipv6 = True
|
||||||
@ -58,7 +56,6 @@ class TestResolve(object):
|
|||||||
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):
|
||||||
vsocket = virtual_socket
|
|
||||||
s = self.ssh.Socket('localhost', 22)
|
s = self.ssh.Socket('localhost', 22)
|
||||||
conf = self._conf()
|
conf = self._conf()
|
||||||
r = list(s._resolve(conf.ipvo))
|
r = list(s._resolve(conf.ipvo))
|
||||||
@ -67,7 +64,6 @@ class TestResolve(object):
|
|||||||
assert r[1] == (socket.AF_INET6, ('::1', 22))
|
assert r[1] == (socket.AF_INET6, ('::1', 22))
|
||||||
|
|
||||||
def test_resolve_ipv46_order(self, virtual_socket):
|
def test_resolve_ipv46_order(self, virtual_socket):
|
||||||
vsocket = virtual_socket
|
|
||||||
s = self.ssh.Socket('localhost', 22)
|
s = self.ssh.Socket('localhost', 22)
|
||||||
conf = self._conf()
|
conf = self._conf()
|
||||||
conf.ipv4 = True
|
conf.ipv4 = True
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import socket
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
@ -12,17 +11,17 @@ class TestSocket(object):
|
|||||||
|
|
||||||
def test_invalid_host(self, virtual_socket):
|
def test_invalid_host(self, virtual_socket):
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
s = self.ssh.Socket(None, 22)
|
self.ssh.Socket(None, 22)
|
||||||
|
|
||||||
def test_invalid_port(self, virtual_socket):
|
def test_invalid_port(self, virtual_socket):
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
s = self.ssh.Socket('localhost', 'abc')
|
self.ssh.Socket('localhost', 'abc')
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
s = self.ssh.Socket('localhost', -1)
|
self.ssh.Socket('localhost', -1)
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
s = self.ssh.Socket('localhost', 0)
|
self.ssh.Socket('localhost', 0)
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
s = self.ssh.Socket('localhost', 65536)
|
self.ssh.Socket('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('localhost', 22)
|
||||||
|
@ -95,10 +95,10 @@ class TestSSH1(object):
|
|||||||
skey, hkey = self._server_key(), self._host_key()
|
skey, hkey = self._server_key(), self._host_key()
|
||||||
pkm = self.ssh1.PublicKeyMessage(cookie, skey, hkey, pflags, cmask, amask)
|
pkm = self.ssh1.PublicKeyMessage(cookie, skey, hkey, pflags, cmask, amask)
|
||||||
self._assert_pkm_fields(pkm, skey, hkey)
|
self._assert_pkm_fields(pkm, skey, hkey)
|
||||||
for skey2 in ([], [0], [0,1], [0,1,2,3]):
|
for skey2 in ([], [0], [0, 1], [0, 1, 2, 3]):
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
pkm = self.ssh1.PublicKeyMessage(cookie, skey2, hkey, pflags, cmask, amask)
|
pkm = self.ssh1.PublicKeyMessage(cookie, skey2, hkey, pflags, cmask, amask)
|
||||||
for hkey2 in ([], [0], [0,1], [0,1,2,3]):
|
for hkey2 in ([], [0], [0, 1], [0, 1, 2, 3]):
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
print(hkey2)
|
print(hkey2)
|
||||||
pkm = self.ssh1.PublicKeyMessage(cookie, skey, hkey2, pflags, cmask, amask)
|
pkm = self.ssh1.PublicKeyMessage(cookie, skey, hkey2, pflags, cmask, amask)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import struct, os
|
import os
|
||||||
|
import struct
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@ -105,15 +105,15 @@ class TestVersionCompare(object):
|
|||||||
versions.append('2015.{0}'.format(i))
|
versions.append('2015.{0}'.format(i))
|
||||||
for i in range(72, 75):
|
for i in range(72, 75):
|
||||||
versions.append('2016.{0}'.format(i))
|
versions.append('2016.{0}'.format(i))
|
||||||
l = len(versions)
|
length = len(versions)
|
||||||
for i in range(l):
|
for i in range(length):
|
||||||
v = versions[i]
|
v = versions[i]
|
||||||
s = self.get_dropbear_software(v)
|
s = self.get_dropbear_software(v)
|
||||||
assert s.compare_version(v) == 0
|
assert s.compare_version(v) == 0
|
||||||
if i - 1 >= 0:
|
if i - 1 >= 0:
|
||||||
vbefore = versions[i - 1]
|
vbefore = versions[i - 1]
|
||||||
assert s.compare_version(vbefore) > 0
|
assert s.compare_version(vbefore) > 0
|
||||||
if i + 1 < l:
|
if i + 1 < length:
|
||||||
vnext = versions[i + 1]
|
vnext = versions[i + 1]
|
||||||
assert s.compare_version(vnext) < 0
|
assert s.compare_version(vnext) < 0
|
||||||
|
|
||||||
@ -164,15 +164,15 @@ class TestVersionCompare(object):
|
|||||||
versions.append('6.{0}'.format(i))
|
versions.append('6.{0}'.format(i))
|
||||||
for i in range(0, 4):
|
for i in range(0, 4):
|
||||||
versions.append('7.{0}'.format(i))
|
versions.append('7.{0}'.format(i))
|
||||||
l = len(versions)
|
length = len(versions)
|
||||||
for i in range(l):
|
for i in range(length):
|
||||||
v = versions[i]
|
v = versions[i]
|
||||||
s = self.get_openssh_software(v)
|
s = self.get_openssh_software(v)
|
||||||
assert s.compare_version(v) == 0
|
assert s.compare_version(v) == 0
|
||||||
if i - 1 >= 0:
|
if i - 1 >= 0:
|
||||||
vbefore = versions[i - 1]
|
vbefore = versions[i - 1]
|
||||||
assert s.compare_version(vbefore) > 0
|
assert s.compare_version(vbefore) > 0
|
||||||
if i + 1 < l:
|
if i + 1 < length:
|
||||||
vnext = versions[i + 1]
|
vnext = versions[i + 1]
|
||||||
assert s.compare_version(vnext) < 0
|
assert s.compare_version(vnext) < 0
|
||||||
|
|
||||||
@ -202,14 +202,14 @@ class TestVersionCompare(object):
|
|||||||
versions.append('0.6.{0}'.format(i))
|
versions.append('0.6.{0}'.format(i))
|
||||||
for i in range(0, 5):
|
for i in range(0, 5):
|
||||||
versions.append('0.7.{0}'.format(i))
|
versions.append('0.7.{0}'.format(i))
|
||||||
l = len(versions)
|
length = len(versions)
|
||||||
for i in range(l):
|
for i in range(length):
|
||||||
v = versions[i]
|
v = versions[i]
|
||||||
s = self.get_libssh_software(v)
|
s = self.get_libssh_software(v)
|
||||||
assert s.compare_version(v) == 0
|
assert s.compare_version(v) == 0
|
||||||
if i - 1 >= 0:
|
if i - 1 >= 0:
|
||||||
vbefore = versions[i - 1]
|
vbefore = versions[i - 1]
|
||||||
assert s.compare_version(vbefore) > 0
|
assert s.compare_version(vbefore) > 0
|
||||||
if i + 1 < l:
|
if i + 1 < length:
|
||||||
vnext = versions[i + 1]
|
vnext = versions[i + 1]
|
||||||
assert s.compare_version(vnext) < 0
|
assert s.compare_version(vnext) < 0
|
||||||
|
41
tox.ini
41
tox.ini
@ -80,7 +80,7 @@ commands =
|
|||||||
deps =
|
deps =
|
||||||
flake8
|
flake8
|
||||||
commands =
|
commands =
|
||||||
flake8 {posargs:{env:SSHAUDIT}}
|
flake8 {posargs:{env:SSHAUDIT} {toxinidir}/packages/setup.py {toxinidir}/test} --statistics
|
||||||
|
|
||||||
[testenv:vulture]
|
[testenv:vulture]
|
||||||
deps =
|
deps =
|
||||||
@ -137,42 +137,13 @@ max-module-lines = 2500
|
|||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
ignore =
|
ignore =
|
||||||
# indentation contains tabs
|
W191, # indentation contains tabs
|
||||||
W191,
|
E101, # indentation contains mixed spaces and tabs
|
||||||
# blank line contains whitespace
|
E241, # multiple spaces after operator; should be kept for tabular data
|
||||||
W293,
|
E501, # line too long
|
||||||
# indentation contains mixed spaces and tabs
|
|
||||||
E101,
|
|
||||||
# multiple spaces before operator
|
|
||||||
E221,
|
|
||||||
# multiple spaces after operator
|
|
||||||
E241,
|
|
||||||
# multiple imports on one line
|
|
||||||
E401,
|
|
||||||
# line too long
|
|
||||||
E501,
|
|
||||||
# module imported but unused
|
|
||||||
F401,
|
|
||||||
# undefined name
|
|
||||||
F821,
|
|
||||||
# these exceptions should be handled one by one
|
|
||||||
E117, # over-indented
|
E117, # over-indented
|
||||||
E126, # continuation line over-indented for hanging indent
|
E126, # continuation line over-indented for hanging indent
|
||||||
E128, # continuation line under-indented for visual indent
|
E128, # continuation line under-indented for visual indent
|
||||||
E226, # missing whitespace around arithmetic operator
|
|
||||||
E231, # missing whitespace after ','
|
|
||||||
E251, # unexpected spaces around keyword / parameter equals
|
|
||||||
E261, # at least two spaces before inline comment
|
|
||||||
E265, # block comment should start with '# '
|
|
||||||
E301, # expected 1 blank line, found 0
|
|
||||||
E302, # expected 2 blank lines, found 1
|
|
||||||
E303, # too many blank lines (2)
|
|
||||||
E305, # expected 2 blank lines after class or function definition, found 1
|
|
||||||
E711, # comparison to None should be 'if cond is not None:'
|
|
||||||
E712, # comparison to False should be 'if cond is False:' or 'if not cond:'
|
|
||||||
E722, # do not use bare 'except'
|
E722, # do not use bare 'except'
|
||||||
E741, # ambiguous variable name 'l'
|
|
||||||
F601, # dictionary key 'ecdsa-sha2-1.3.132.0.10' repeated with different values
|
F601, # dictionary key 'ecdsa-sha2-1.3.132.0.10' repeated with different values
|
||||||
F841, # local variable 'e' is assigned to but never used
|
W504, # line break after binary operator; this (or W503) has to stay
|
||||||
W504, # line break after binary operator
|
|
||||||
W605, # invalid escape sequence '\s'
|
|
||||||
|
Loading…
Reference in New Issue
Block a user