From 1c7ec9c41f66257e52ba27063e06c09a5fdd62f3 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Thu, 10 Feb 2022 11:22:30 +0200 Subject: [PATCH 1/2] Agent: refactor ssh fingerprinter to fit the new model --- monkey/infection_monkey/master/mock_master.py | 6 +- monkey/infection_monkey/monkey.py | 2 + .../network/ssh_fingerprinter.py | 43 +++++++++ monkey/infection_monkey/network/sshfinger.py | 54 ----------- .../network/test_ssh_fingerprinter.py | 95 +++++++++++++++++++ 5 files changed, 143 insertions(+), 57 deletions(-) create mode 100644 monkey/infection_monkey/network/ssh_fingerprinter.py delete mode 100644 monkey/infection_monkey/network/sshfinger.py create mode 100644 monkey/tests/unit_tests/infection_monkey/network/test_ssh_fingerprinter.py diff --git a/monkey/infection_monkey/master/mock_master.py b/monkey/infection_monkey/master/mock_master.py index 274f960f8..ddb5ccffb 100644 --- a/monkey/infection_monkey/master/mock_master.py +++ b/monkey/infection_monkey/master/mock_master.py @@ -89,13 +89,13 @@ class MockMaster(IMaster): machine_1 = self._hosts["10.0.0.1"] machine_3 = self._hosts["10.0.0.3"] - self._puppet.fingerprint("SMBFinger", machine_1, None, None) + self._puppet.fingerprint("SMBFinger", machine_1, None, None, None) self._telemetry_messenger.send_telemetry(ScanTelem(machine_1)) - self._puppet.fingerprint("SMBFinger", machine_3, None, None) + self._puppet.fingerprint("SMBFinger", machine_3, None, None, None) self._telemetry_messenger.send_telemetry(ScanTelem(machine_3)) - self._puppet.fingerprint("HTTPFinger", machine_3, None, None) + self._puppet.fingerprint("HTTPFinger", machine_3, None, None, None) self._telemetry_messenger.send_telemetry(ScanTelem(machine_3)) logger.info("Finished running fingerprinters on potential victims") diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index d15603e48..e06a39689 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -23,6 +23,7 @@ from infection_monkey.network.http_fingerprinter import HTTPFingerprinter from infection_monkey.network.info import get_local_network_interfaces from infection_monkey.network.mssql_fingerprinter import MSSQLFingerprinter from infection_monkey.network.smb_fingerprinter import SMBFingerprinter +from infection_monkey.network.ssh_fingerprinter import SSHFingerprinter from infection_monkey.payload.ransomware.ransomware_payload import RansomwarePayload from infection_monkey.puppet.puppet import Puppet from infection_monkey.system_singleton import SystemSingleton @@ -192,6 +193,7 @@ class InfectionMonkey: puppet.load_plugin("http", HTTPFingerprinter(), PluginType.FINGERPRINTER) puppet.load_plugin("mssql", MSSQLFingerprinter(), PluginType.FINGERPRINTER) puppet.load_plugin("smb", SMBFingerprinter(), PluginType.FINGERPRINTER) + puppet.load_plugin("ssh", SSHFingerprinter(), PluginType.FINGERPRINTER) puppet.load_plugin("ransomware", RansomwarePayload(), PluginType.PAYLOAD) diff --git a/monkey/infection_monkey/network/ssh_fingerprinter.py b/monkey/infection_monkey/network/ssh_fingerprinter.py new file mode 100644 index 000000000..14c9ae70e --- /dev/null +++ b/monkey/infection_monkey/network/ssh_fingerprinter.py @@ -0,0 +1,43 @@ +import re +from typing import Dict, Tuple, Union + +from infection_monkey.i_puppet import FingerprintData, IFingerprinter, PortScanData + +SSH_PORT = 22 +SSH_REGEX = r"SSH-\d\.\d-OpenSSH" +LINUX_DIST_SSH = ["ubuntu", "debian"] + + +class SSHFingerprinter(IFingerprinter): + _SCANNED_SERVICE = "SSH" + + def __init__(self): + self._banner_regex = re.compile(SSH_REGEX, re.IGNORECASE) + + def get_host_fingerprint( + self, host: str, _ping_scan_data, port_scan_data: Dict[int, PortScanData], _options + ) -> FingerprintData: + os_type = None + os_version = None + services = {} + + for ps_data in list(port_scan_data.values()): + if ps_data.banner and self._banner_regex.search(ps_data.banner): + os_type, os_version = self._get_host_os(ps_data.banner) + services[f"tcp-{ps_data.port}"] = { + "display_name": SSHFingerprinter._SCANNED_SERVICE, + "port": ps_data.port, + "name": "ssh", + } + return FingerprintData(os_type, os_version, services) + + @staticmethod + def _get_host_os(banner) -> Tuple[Union[str, None], Union[str, None]]: + os = None + os_version = None + for dist in LINUX_DIST_SSH: + if banner.lower().find(dist) != -1: + os_version = banner.split(" ").pop().strip() + os = "linux" + + return os, os_version diff --git a/monkey/infection_monkey/network/sshfinger.py b/monkey/infection_monkey/network/sshfinger.py deleted file mode 100644 index df21ef35b..000000000 --- a/monkey/infection_monkey/network/sshfinger.py +++ /dev/null @@ -1,54 +0,0 @@ -import re - -import infection_monkey.config -from infection_monkey.network.HostFinger import HostFinger -from infection_monkey.network.tools import check_tcp_port - -SSH_PORT = 22 -SSH_SERVICE_DEFAULT = "tcp-22" -SSH_REGEX = r"SSH-\d\.\d-OpenSSH" -TIMEOUT = 10 -BANNER_READ = 1024 -LINUX_DIST_SSH = ["ubuntu", "debian"] - - -class SSHFinger(HostFinger): - _SCANNED_SERVICE = "SSH" - - def __init__(self): - self._config = infection_monkey.config.WormConfiguration - self._banner_regex = re.compile(SSH_REGEX, re.IGNORECASE) - - @staticmethod - def _banner_match(service, host, banner): - host.services[service]["name"] = "ssh" - for dist in LINUX_DIST_SSH: - if banner.lower().find(dist) != -1: - host.os["type"] = "linux" - os_version = banner.split(" ").pop().strip() - if "version" not in host.os: - host.os["version"] = os_version - - break - - def get_host_fingerprint(self, host): - - for name, data in list(host.services.items()): - banner = data.get("banner", "") - if self._banner_regex.search(banner): - self._banner_match(name, host, banner) - host.services[SSH_SERVICE_DEFAULT]["display_name"] = self._SCANNED_SERVICE - return - - is_open, banner = check_tcp_port(host.ip_addr, SSH_PORT, TIMEOUT, True) - - if is_open: - self.init_service(host.services, SSH_SERVICE_DEFAULT, SSH_PORT) - - if banner: - host.services[SSH_SERVICE_DEFAULT]["banner"] = banner - if self._banner_regex.search(banner): - self._banner_match(SSH_SERVICE_DEFAULT, host, banner) - return True - - return False diff --git a/monkey/tests/unit_tests/infection_monkey/network/test_ssh_fingerprinter.py b/monkey/tests/unit_tests/infection_monkey/network/test_ssh_fingerprinter.py new file mode 100644 index 000000000..b3df98cd9 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/network/test_ssh_fingerprinter.py @@ -0,0 +1,95 @@ +import pytest + +from infection_monkey.i_puppet import FingerprintData, PortScanData, PortStatus +from infection_monkey.network.ssh_fingerprinter import SSHFingerprinter + + +@pytest.fixture +def ssh_fingerprinter(): + return SSHFingerprinter() + + +def test_no_ssh_ports_open(ssh_fingerprinter): + port_scan_data = { + 22: PortScanData(22, PortStatus.CLOSED, "", "tcp-22"), + 123: PortScanData(123, PortStatus.OPEN, "", "tcp-123"), + 443: PortScanData(443, PortStatus.CLOSED, "", "tcp-443"), + } + results = ssh_fingerprinter.get_host_fingerprint("127.0.0.1", None, port_scan_data, None) + + assert results == FingerprintData(None, None, {}) + + +def test_no_os(ssh_fingerprinter): + port_scan_data = { + 22: PortScanData(22, PortStatus.OPEN, "SSH-2.0-OpenSSH_8.2p1", "tcp-22"), + 2222: PortScanData(2222, PortStatus.OPEN, "SSH-2.0-OpenSSH_8.2p1", "tcp-2222"), + 443: PortScanData(443, PortStatus.CLOSED, "", "tcp-443"), + 8080: PortScanData(8080, PortStatus.CLOSED, "", "tcp-8080"), + } + results = ssh_fingerprinter.get_host_fingerprint("127.0.0.1", None, port_scan_data, None) + + assert results == FingerprintData( + None, + None, + { + "tcp-22": { + "display_name": "SSH", + "port": 22, + "name": "ssh", + }, + "tcp-2222": { + "display_name": "SSH", + "port": 2222, + "name": "ssh", + }, + }, + ) + + +def test_ssh_os(ssh_fingerprinter): + port_scan_data = { + 22: PortScanData(22, PortStatus.OPEN, "SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.2", "tcp-22"), + 443: PortScanData(443, PortStatus.CLOSED, "", "tcp-443"), + 8080: PortScanData(8080, PortStatus.CLOSED, "", "tcp-8080"), + } + results = ssh_fingerprinter.get_host_fingerprint("127.0.0.1", None, port_scan_data, None) + + assert results == FingerprintData( + "linux", + "Ubuntu-4ubuntu0.2", + { + "tcp-22": { + "display_name": "SSH", + "port": 22, + "name": "ssh", + } + }, + ) + + +def test_multiple_os(ssh_fingerprinter): + port_scan_data = { + 22: PortScanData(22, PortStatus.OPEN, "SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.2", "tcp-22"), + 2222: PortScanData(2222, PortStatus.OPEN, "SSH-2.0-OpenSSH_8.2p1 Debian", "tcp-2222"), + 443: PortScanData(443, PortStatus.CLOSED, "", "tcp-443"), + 8080: PortScanData(8080, PortStatus.CLOSED, "", "tcp-8080"), + } + results = ssh_fingerprinter.get_host_fingerprint("127.0.0.1", None, port_scan_data, None) + + assert results == FingerprintData( + "linux", + "Debian", + { + "tcp-22": { + "display_name": "SSH", + "port": 22, + "name": "ssh", + }, + "tcp-2222": { + "display_name": "SSH", + "port": 2222, + "name": "ssh", + }, + }, + ) From f9b803b1ae9a537e8dc498f7fc2bd23e7aadfdde Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 10 Feb 2022 07:08:02 -0500 Subject: [PATCH 2/2] Agent: Minor code quality improvements to SSHFingerprinter --- .../network/ssh_fingerprinter.py | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/monkey/infection_monkey/network/ssh_fingerprinter.py b/monkey/infection_monkey/network/ssh_fingerprinter.py index 14c9ae70e..32aa20ad9 100644 --- a/monkey/infection_monkey/network/ssh_fingerprinter.py +++ b/monkey/infection_monkey/network/ssh_fingerprinter.py @@ -1,38 +1,40 @@ import re -from typing import Dict, Tuple, Union +from typing import Dict, Optional, Tuple -from infection_monkey.i_puppet import FingerprintData, IFingerprinter, PortScanData +from infection_monkey.i_puppet import FingerprintData, IFingerprinter, PingScanData, PortScanData -SSH_PORT = 22 SSH_REGEX = r"SSH-\d\.\d-OpenSSH" LINUX_DIST_SSH = ["ubuntu", "debian"] +DISPLAY_NAME = "SSH" class SSHFingerprinter(IFingerprinter): - _SCANNED_SERVICE = "SSH" - def __init__(self): self._banner_regex = re.compile(SSH_REGEX, re.IGNORECASE) def get_host_fingerprint( - self, host: str, _ping_scan_data, port_scan_data: Dict[int, PortScanData], _options + self, + host: str, + _ping_scan_data: PingScanData, + port_scan_data: Dict[int, PortScanData], + _options: Dict, ) -> FingerprintData: os_type = None os_version = None services = {} - for ps_data in list(port_scan_data.values()): + for ps_data in port_scan_data.values(): if ps_data.banner and self._banner_regex.search(ps_data.banner): os_type, os_version = self._get_host_os(ps_data.banner) services[f"tcp-{ps_data.port}"] = { - "display_name": SSHFingerprinter._SCANNED_SERVICE, + "display_name": DISPLAY_NAME, "port": ps_data.port, "name": "ssh", } return FingerprintData(os_type, os_version, services) @staticmethod - def _get_host_os(banner) -> Tuple[Union[str, None], Union[str, None]]: + def _get_host_os(banner) -> Tuple[Optional[str], Optional[str]]: os = None os_version = None for dist in LINUX_DIST_SSH: