Merge pull request #1708 from guardicore/1603-refactor-ssh-fingerprinter

Agent: refactor ssh fingerprinter to fit the new model
This commit is contained in:
Mike Salvatore 2022-02-10 07:11:41 -05:00 committed by GitHub
commit aadc055f74
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 145 additions and 57 deletions

View File

@ -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")

View File

@ -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)

View File

@ -0,0 +1,45 @@
import re
from typing import Dict, Optional, Tuple
from infection_monkey.i_puppet import FingerprintData, IFingerprinter, PingScanData, PortScanData
SSH_REGEX = r"SSH-\d\.\d-OpenSSH"
LINUX_DIST_SSH = ["ubuntu", "debian"]
DISPLAY_NAME = "SSH"
class SSHFingerprinter(IFingerprinter):
def __init__(self):
self._banner_regex = re.compile(SSH_REGEX, re.IGNORECASE)
def get_host_fingerprint(
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 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": DISPLAY_NAME,
"port": ps_data.port,
"name": "ssh",
}
return FingerprintData(os_type, os_version, services)
@staticmethod
def _get_host_os(banner) -> Tuple[Optional[str], Optional[str]]:
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

View File

@ -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

View File

@ -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",
},
},
)