forked from p15670423/monkey
Agent: Add fingerprinting to IPScanner
This commit is contained in:
parent
0ff45e3af1
commit
438563af9c
|
@ -5,7 +5,13 @@ from queue import Queue
|
||||||
from threading import Event
|
from threading import Event
|
||||||
from typing import Callable, Dict, List
|
from typing import Callable, Dict, List
|
||||||
|
|
||||||
from infection_monkey.i_puppet import IPuppet, PingScanData, PortScanData
|
from infection_monkey.i_puppet import (
|
||||||
|
FingerprintData,
|
||||||
|
IPuppet,
|
||||||
|
PingScanData,
|
||||||
|
PortScanData,
|
||||||
|
PortStatus,
|
||||||
|
)
|
||||||
|
|
||||||
from .threading_utils import create_daemon_thread
|
from .threading_utils import create_daemon_thread
|
||||||
|
|
||||||
|
@ -13,7 +19,10 @@ logger = logging.getLogger()
|
||||||
|
|
||||||
IP = str
|
IP = str
|
||||||
Port = int
|
Port = int
|
||||||
Callback = Callable[[IP, PingScanData, Dict[Port, PortScanData]], None]
|
FingerprinterName = str
|
||||||
|
Callback = Callable[
|
||||||
|
[IP, PingScanData, Dict[Port, PortScanData], Dict[FingerprinterName, FingerprintData]], None
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class IPScanner:
|
class IPScanner:
|
||||||
|
@ -53,7 +62,12 @@ class IPScanner:
|
||||||
tcp_ports = options["tcp"]["ports"]
|
tcp_ports = options["tcp"]["ports"]
|
||||||
port_scan_data = self._scan_tcp_ports(ip, tcp_ports, tcp_timeout, stop)
|
port_scan_data = self._scan_tcp_ports(ip, tcp_ports, tcp_timeout, stop)
|
||||||
|
|
||||||
results_callback(ip, ping_scan_data, port_scan_data)
|
fingerprint_data = {}
|
||||||
|
if IPScanner._found_open_port(port_scan_data):
|
||||||
|
fingerprinters = options["fingerprinters"]
|
||||||
|
fingerprint_data = self._run_fingerprinters(ip, fingerprinters, stop)
|
||||||
|
|
||||||
|
results_callback(ip, ping_scan_data, port_scan_data, fingerprint_data)
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Detected the stop signal, scanning thread {threading.get_ident()} exiting"
|
f"Detected the stop signal, scanning thread {threading.get_ident()} exiting"
|
||||||
|
@ -64,7 +78,9 @@ class IPScanner:
|
||||||
f"ips_to_scan queue is empty, scanning thread {threading.get_ident()} exiting"
|
f"ips_to_scan queue is empty, scanning thread {threading.get_ident()} exiting"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _scan_tcp_ports(self, ip: str, ports: List[int], timeout: float, stop: Event):
|
def _scan_tcp_ports(
|
||||||
|
self, ip: str, ports: List[int], timeout: float, stop: Event
|
||||||
|
) -> Dict[int, PortScanData]:
|
||||||
port_scan_data = {}
|
port_scan_data = {}
|
||||||
|
|
||||||
for p in ports:
|
for p in ports:
|
||||||
|
@ -74,3 +90,22 @@ class IPScanner:
|
||||||
port_scan_data[p] = self._puppet.scan_tcp_port(ip, p, timeout)
|
port_scan_data[p] = self._puppet.scan_tcp_port(ip, p, timeout)
|
||||||
|
|
||||||
return port_scan_data
|
return port_scan_data
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _found_open_port(port_scan_data: Dict[int, PortScanData]):
|
||||||
|
for psd in port_scan_data.values():
|
||||||
|
if psd.status == PortStatus.OPEN:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _run_fingerprinters(self, ip: str, fingerprinters: List[str], stop: Event):
|
||||||
|
fingerprint_data = {}
|
||||||
|
|
||||||
|
for f in fingerprinters:
|
||||||
|
if stop.is_set():
|
||||||
|
break
|
||||||
|
|
||||||
|
fingerprint_data[f] = self._puppet.fingerprint(f, ip)
|
||||||
|
|
||||||
|
return fingerprint_data
|
||||||
|
|
|
@ -4,7 +4,7 @@ from unittest.mock import MagicMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from infection_monkey.i_puppet import PortScanData, PortStatus
|
from infection_monkey.i_puppet import FingerprintData, PortScanData, PortStatus
|
||||||
from infection_monkey.master import IPScanner
|
from infection_monkey.master import IPScanner
|
||||||
from infection_monkey.puppet.mock_puppet import MockPuppet
|
from infection_monkey.puppet.mock_puppet import MockPuppet
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ def scan_config():
|
||||||
"icmp": {
|
"icmp": {
|
||||||
"timeout_ms": 1000,
|
"timeout_ms": 1000,
|
||||||
},
|
},
|
||||||
|
"fingerprinters": {"HTTPFinger", "SMBFinger", "SSHFinger"},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -50,9 +51,16 @@ def assert_port_status(port_scan_data, expected_open_ports: Set[int]):
|
||||||
assert psd.status == PortStatus.CLOSED
|
assert psd.status == PortStatus.CLOSED
|
||||||
|
|
||||||
|
|
||||||
def assert_scan_results_no_1(ip, ping_scan_data, port_scan_data):
|
def assert_scan_results(ip, ping_scan_data, port_scan_data, fingerprint_data):
|
||||||
assert ip == "10.0.0.1"
|
if ip == "10.0.0.1":
|
||||||
|
assert_scan_results_no_1(ping_scan_data, port_scan_data, fingerprint_data)
|
||||||
|
elif ip == "10.0.0.3":
|
||||||
|
assert_scan_results_no_3(ping_scan_data, port_scan_data, fingerprint_data)
|
||||||
|
else:
|
||||||
|
assert_scan_results_host_down(ip, ping_scan_data, port_scan_data, fingerprint_data)
|
||||||
|
|
||||||
|
|
||||||
|
def assert_scan_results_no_1(ping_scan_data, port_scan_data, fingerprint_data):
|
||||||
assert ping_scan_data.response_received is True
|
assert ping_scan_data.response_received is True
|
||||||
assert ping_scan_data.os == WINDOWS_OS
|
assert ping_scan_data.os == WINDOWS_OS
|
||||||
|
|
||||||
|
@ -70,11 +78,22 @@ def assert_scan_results_no_1(ip, ping_scan_data, port_scan_data):
|
||||||
assert psd_3389.service == "tcp-3389"
|
assert psd_3389.service == "tcp-3389"
|
||||||
|
|
||||||
assert_port_status(port_scan_data, {445, 3389})
|
assert_port_status(port_scan_data, {445, 3389})
|
||||||
|
assert_fingerprint_results_no_1(fingerprint_data)
|
||||||
|
|
||||||
|
|
||||||
def assert_scan_results_no_3(ip, ping_scan_data, port_scan_data):
|
def assert_fingerprint_results_no_1(fingerprint_data):
|
||||||
assert ip == "10.0.0.3"
|
assert len(fingerprint_data.keys()) == 3
|
||||||
|
assert fingerprint_data["SSHFinger"].services == {}
|
||||||
|
assert fingerprint_data["HTTPFinger"].services == {}
|
||||||
|
|
||||||
|
assert fingerprint_data["SMBFinger"].os_type == WINDOWS_OS
|
||||||
|
assert fingerprint_data["SMBFinger"].os_version == "vista"
|
||||||
|
|
||||||
|
assert len(fingerprint_data["SMBFinger"].services.keys()) == 1
|
||||||
|
assert fingerprint_data["SMBFinger"].services["tcp-445"]["name"] == "smb_service_name"
|
||||||
|
|
||||||
|
|
||||||
|
def assert_scan_results_no_3(ping_scan_data, port_scan_data, fingerprint_data):
|
||||||
assert ping_scan_data.response_received is True
|
assert ping_scan_data.response_received is True
|
||||||
assert ping_scan_data.os == LINUX_OS
|
assert ping_scan_data.os == LINUX_OS
|
||||||
assert len(port_scan_data.keys()) == 6
|
assert len(port_scan_data.keys()) == 6
|
||||||
|
@ -91,15 +110,36 @@ def assert_scan_results_no_3(ip, ping_scan_data, port_scan_data):
|
||||||
assert psd_22.service == "tcp-22"
|
assert psd_22.service == "tcp-22"
|
||||||
|
|
||||||
assert_port_status(port_scan_data, {22, 443})
|
assert_port_status(port_scan_data, {22, 443})
|
||||||
|
assert_fingerprint_results_no_3(fingerprint_data)
|
||||||
|
|
||||||
|
|
||||||
def assert_scan_results_host_down(ip, ping_scan_data, port_scan_data):
|
def assert_fingerprint_results_no_3(fingerprint_data):
|
||||||
|
assert len(fingerprint_data.keys()) == 3
|
||||||
|
assert fingerprint_data["SMBFinger"].services == {}
|
||||||
|
|
||||||
|
assert fingerprint_data["SSHFinger"].os_type == LINUX_OS
|
||||||
|
assert fingerprint_data["SSHFinger"].os_version == "ubuntu"
|
||||||
|
|
||||||
|
assert len(fingerprint_data["SSHFinger"].services.keys()) == 1
|
||||||
|
assert fingerprint_data["SSHFinger"].services["tcp-22"]["name"] == "SSH"
|
||||||
|
assert fingerprint_data["SSHFinger"].services["tcp-22"]["banner"] == "SSH BANNER"
|
||||||
|
|
||||||
|
assert len(fingerprint_data["HTTPFinger"].services.keys()) == 2
|
||||||
|
assert fingerprint_data["HTTPFinger"].services["tcp-80"]["name"] == "http"
|
||||||
|
assert fingerprint_data["HTTPFinger"].services["tcp-80"]["data"] == ("SERVER_HEADERS", False)
|
||||||
|
assert fingerprint_data["HTTPFinger"].services["tcp-443"]["name"] == "http"
|
||||||
|
assert fingerprint_data["HTTPFinger"].services["tcp-443"]["data"] == ("SERVER_HEADERS_2", True)
|
||||||
|
|
||||||
|
|
||||||
|
def assert_scan_results_host_down(ip, ping_scan_data, port_scan_data, fingerprint_data):
|
||||||
assert ip not in {"10.0.0.1", "10.0.0.3"}
|
assert ip not in {"10.0.0.1", "10.0.0.3"}
|
||||||
|
|
||||||
assert ping_scan_data.response_received is False
|
assert ping_scan_data.response_received is False
|
||||||
assert len(port_scan_data.keys()) == 6
|
assert len(port_scan_data.keys()) == 6
|
||||||
assert_port_status(port_scan_data, set())
|
assert_port_status(port_scan_data, set())
|
||||||
|
|
||||||
|
assert fingerprint_data == {}
|
||||||
|
|
||||||
|
|
||||||
def test_scan_single_ip(callback, scan_config, stop):
|
def test_scan_single_ip(callback, scan_config, stop):
|
||||||
ips = ["10.0.0.1"]
|
ips = ["10.0.0.1"]
|
||||||
|
@ -109,8 +149,8 @@ def test_scan_single_ip(callback, scan_config, stop):
|
||||||
|
|
||||||
callback.assert_called_once()
|
callback.assert_called_once()
|
||||||
|
|
||||||
(ip, ping_scan_data, port_scan_data) = callback.call_args_list[0][0]
|
(ip, ping_scan_data, port_scan_data, fingerprint_data) = callback.call_args_list[0][0]
|
||||||
assert_scan_results_no_1(ip, ping_scan_data, port_scan_data)
|
assert_scan_results(ip, ping_scan_data, port_scan_data, fingerprint_data)
|
||||||
|
|
||||||
|
|
||||||
def test_scan_multiple_ips(callback, scan_config, stop):
|
def test_scan_multiple_ips(callback, scan_config, stop):
|
||||||
|
@ -121,17 +161,17 @@ def test_scan_multiple_ips(callback, scan_config, stop):
|
||||||
|
|
||||||
assert callback.call_count == 4
|
assert callback.call_count == 4
|
||||||
|
|
||||||
(ip, ping_scan_data, port_scan_data) = callback.call_args_list[0][0]
|
(ip, ping_scan_data, port_scan_data, fingerprint_data) = callback.call_args_list[0][0]
|
||||||
assert_scan_results_no_1(ip, ping_scan_data, port_scan_data)
|
assert_scan_results(ip, ping_scan_data, port_scan_data, fingerprint_data)
|
||||||
|
|
||||||
(ip, ping_scan_data, port_scan_data) = callback.call_args_list[1][0]
|
(ip, ping_scan_data, port_scan_data, fingerprint_data) = callback.call_args_list[1][0]
|
||||||
assert_scan_results_host_down(ip, ping_scan_data, port_scan_data)
|
assert_scan_results(ip, ping_scan_data, port_scan_data, fingerprint_data)
|
||||||
|
|
||||||
(ip, ping_scan_data, port_scan_data) = callback.call_args_list[2][0]
|
(ip, ping_scan_data, port_scan_data, fingerprint_data) = callback.call_args_list[2][0]
|
||||||
assert_scan_results_no_3(ip, ping_scan_data, port_scan_data)
|
assert_scan_results(ip, ping_scan_data, port_scan_data, fingerprint_data)
|
||||||
|
|
||||||
(ip, ping_scan_data, port_scan_data) = callback.call_args_list[3][0]
|
(ip, ping_scan_data, port_scan_data, fingerprint_data) = callback.call_args_list[3][0]
|
||||||
assert_scan_results_host_down(ip, ping_scan_data, port_scan_data)
|
assert_scan_results(ip, ping_scan_data, port_scan_data, fingerprint_data)
|
||||||
|
|
||||||
|
|
||||||
def test_scan_lots_of_ips(callback, scan_config, stop):
|
def test_scan_lots_of_ips(callback, scan_config, stop):
|
||||||
|
@ -182,3 +222,25 @@ def test_interrupt_port_scanning(callback, scan_config, stop):
|
||||||
ns.scan(ips, scan_config, callback, stop)
|
ns.scan(ips, scan_config, callback, stop)
|
||||||
|
|
||||||
assert puppet.scan_tcp_port.call_count == 2
|
assert puppet.scan_tcp_port.call_count == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_interrupt_fingerprinting(callback, scan_config, stop):
|
||||||
|
def stopable_fingerprint(port, *_):
|
||||||
|
# Block all threads here until 2 threads reach this barrier, then set stop
|
||||||
|
# and test that neither thread scans any more ports
|
||||||
|
stopable_fingerprint.barrier.wait()
|
||||||
|
stop.set()
|
||||||
|
|
||||||
|
return FingerprintData(None, None, {})
|
||||||
|
|
||||||
|
stopable_fingerprint.barrier = Barrier(2)
|
||||||
|
|
||||||
|
puppet = MockPuppet()
|
||||||
|
puppet.fingerprint = MagicMock(side_effect=stopable_fingerprint)
|
||||||
|
|
||||||
|
ips = ["10.0.0.1", "10.0.0.2", "10.0.0.3", "10.0.0.4"]
|
||||||
|
|
||||||
|
ns = IPScanner(puppet, num_workers=2)
|
||||||
|
ns.scan(ips, scan_config, callback, stop)
|
||||||
|
|
||||||
|
assert puppet.fingerprint.call_count == 2
|
||||||
|
|
Loading…
Reference in New Issue