From 489a24c564bb518f5d94ccdf4482a7859fd7cab9 Mon Sep 17 00:00:00 2001 From: Andris Raugulis Date: Wed, 5 Oct 2016 03:25:54 +0300 Subject: [PATCH] Fix banner protocol (1.99) recognition and clean banner comments. Add banner tests. --- ssh-audit.py | 6 ++-- test/test_banner.py | 68 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 test/test_banner.py diff --git a/ssh-audit.py b/ssh-audit.py index b29592a..a43b1dc 100755 --- a/ssh-audit.py +++ b/ssh-audit.py @@ -655,8 +655,8 @@ class SSH(object): return None class Banner(object): - _RXP, _RXR = r'SSH-\d\.\s*?\d+', r'(-([^\s]*)(?:\s+(.*))?)?' - RX_PROTOCOL = re.compile(_RXP.replace('\d', '(\d)')) + _RXP, _RXR = r'SSH-\d\.\s*?\d+', r'(-\s*([^\s]*)(?:\s+(.*))?)?' + RX_PROTOCOL = re.compile(re.sub(r'\\d(\+?)','(\\d\g<1>)', _RXP)) RX_BANNER = re.compile(r'^({0}(?:(?:-{0})*)){1}$'.format(_RXP, _RXR)) def __init__(self, protocol, software, comments): @@ -704,6 +704,8 @@ class SSH(object): if software is None and (mx.group(2) or '').startswith('-'): software = '' comments = (mx.group(4) or '').strip() or None + if comments is not None: + comments = re.sub('\s+', ' ', comments) return cls(protocol, software, comments) class Fingerprint(object): diff --git a/test/test_banner.py b/test/test_banner.py new file mode 100644 index 0000000..b2d9991 --- /dev/null +++ b/test/test_banner.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import pytest + + +class TestBanner(object): + @pytest.fixture(autouse=True) + def init(self, ssh_audit): + self.ssh = ssh_audit.SSH + + def test_simple_banners(self): + banner = lambda x: self.ssh.Banner.parse(x) + b = banner('SSH-2.0-OpenSSH_7.3') + assert b.protocol == (2, 0) + assert b.software == 'OpenSSH_7.3' + assert b.comments is None + assert str(b) == 'SSH-2.0-OpenSSH_7.3' + b = banner('SSH-1.99-Sun_SSH_1.1.3') + assert b.protocol == (1, 99) + assert b.software == 'Sun_SSH_1.1.3' + assert b.comments is None + assert str(b) == 'SSH-1.99-Sun_SSH_1.1.3' + b = banner('SSH-1.5-Cisco-1.25') + assert b.protocol == (1, 5) + assert b.software == 'Cisco-1.25' + assert b.comments is None + assert str(b) == 'SSH-1.5-Cisco-1.25' + + def test_invalid_banners(self): + b = lambda x: self.ssh.Banner.parse(x) + assert b('Something') is None + assert b('SSH-XXX-OpenSSH_7.3') is None + + def test_banners_with_spaces(self): + b = lambda x: self.ssh.Banner.parse(x) + s = 'SSH-2.0-OpenSSH_4.3p2' + assert str(b('SSH-2.0-OpenSSH_4.3p2 ')) == s + assert str(b('SSH-2.0- OpenSSH_4.3p2')) == s + assert str(b('SSH-2.0- OpenSSH_4.3p2 ')) == s + s = 'SSH-2.0-OpenSSH_4.3p2 Debian-9etch3 on i686-pc-linux-gnu' + assert str(b('SSH-2.0- OpenSSH_4.3p2 Debian-9etch3 on i686-pc-linux-gnu')) == s + assert str(b('SSH-2.0-OpenSSH_4.3p2 Debian-9etch3 on i686-pc-linux-gnu ')) == s + assert str(b('SSH-2.0- OpenSSH_4.3p2 Debian-9etch3 on i686-pc-linux-gnu ')) == s + + def test_banners_without_software(self): + b = lambda x: self.ssh.Banner.parse(x) + assert b('SSH-2.0').protocol == (2, 0) + assert b('SSH-2.0').software is None + assert b('SSH-2.0').comments is None + assert str(b('SSH-2.0')) == 'SSH-2.0' + assert b('SSH-2.0-').protocol == (2, 0) + assert b('SSH-2.0-').software == '' + assert b('SSH-2.0-').comments is None + assert str(b('SSH-2.0-')) == 'SSH-2.0-' + + def test_banners_with_comments(self): + b = lambda x: self.ssh.Banner.parse(x) + assert repr(b('SSH-2.0-OpenSSH_7.2p2 Ubuntu-1')) == '' + assert repr(b('SSH-1.99-OpenSSH_3.4p1 Debian 1:3.4p1-1.woody.3')) == '' + assert repr(b('SSH-1.5-1.3.7 F-SECURE SSH')) == '' + + def test_banners_with_multiple_protocols(self): + b = lambda x: self.ssh.Banner.parse(x) + assert str(b('SSH-1.99-SSH-1.99-OpenSSH_3.6.1p2')) == 'SSH-1.99-OpenSSH_3.6.1p2' + assert str(b('SSH-2.0-SSH-2.0-OpenSSH_4.3p2 Debian-9')) == 'SSH-2.0-OpenSSH_4.3p2 Debian-9' + assert str(b('SSH-1.99-SSH-2.0-dropbear_0.5')) == 'SSH-1.99-dropbear_0.5' + assert str(b('SSH-2.0-SSH-1.99-OpenSSH_4.2p1 SSH Secure Shell (non-commercial)')) == 'SSH-1.99-OpenSSH_4.2p1 SSH Secure Shell (non-commercial)' + assert str(b('SSH-1.99-SSH-1.99-SSH-1.99-OpenSSH_3.9p1')) == 'SSH-1.99-OpenSSH_3.9p1'