#2768 - Added listen_on_ports attribute to IOSoftware. updated software manager so that it sends copies of payloads to listening ports too. Added integration test that installs a listening service to snoop on DB traffic.
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
|
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
|
||||||
|
from copy import deepcopy
|
||||||
from ipaddress import IPv4Address, IPv4Network
|
from ipaddress import IPv4Address, IPv4Network
|
||||||
from typing import Any, Dict, List, Optional, Tuple, TYPE_CHECKING, Union
|
from typing import Any, Dict, List, Optional, Tuple, TYPE_CHECKING, Union
|
||||||
|
|
||||||
@@ -76,6 +77,8 @@ class SoftwareManager:
|
|||||||
for software in self.port_protocol_mapping.values():
|
for software in self.port_protocol_mapping.values():
|
||||||
if software.operating_state in {ApplicationOperatingState.RUNNING, ServiceOperatingState.RUNNING}:
|
if software.operating_state in {ApplicationOperatingState.RUNNING, ServiceOperatingState.RUNNING}:
|
||||||
open_ports.append(software.port)
|
open_ports.append(software.port)
|
||||||
|
if software.listen_on_ports:
|
||||||
|
open_ports += list(software.listen_on_ports)
|
||||||
return open_ports
|
return open_ports
|
||||||
|
|
||||||
def check_port_is_open(self, port: Port, protocol: IPProtocol) -> bool:
|
def check_port_is_open(self, port: Port, protocol: IPProtocol) -> bool:
|
||||||
@@ -223,7 +226,9 @@ class SoftwareManager:
|
|||||||
frame: Frame,
|
frame: Frame,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Receive a payload from the SessionManager and forward it to the corresponding service or application.
|
Receive a payload from the SessionManager and forward it to the corresponding service or applications.
|
||||||
|
|
||||||
|
This function handles both software assigned a specific port, and software listening in on other ports.
|
||||||
|
|
||||||
:param payload: The payload being received.
|
:param payload: The payload being received.
|
||||||
:param session: The transport session the payload originates from.
|
:param session: The transport session the payload originates from.
|
||||||
@@ -231,11 +236,17 @@ class SoftwareManager:
|
|||||||
if payload.__class__.__name__ == "PortScanPayload":
|
if payload.__class__.__name__ == "PortScanPayload":
|
||||||
self.software.get("NMAP").receive(payload=payload, session_id=session_id)
|
self.software.get("NMAP").receive(payload=payload, session_id=session_id)
|
||||||
return
|
return
|
||||||
receiver: Optional[Union[Service, Application]] = self.port_protocol_mapping.get((port, protocol), None)
|
main_receiver = self.port_protocol_mapping.get((port, protocol), None)
|
||||||
if receiver:
|
listening_receivers = [software for software in self.software.values() if port in software.listen_on_ports]
|
||||||
receiver.receive(
|
receivers = [main_receiver] + listening_receivers if main_receiver else listening_receivers
|
||||||
payload=payload, session_id=session_id, from_network_interface=from_network_interface, frame=frame
|
if receivers:
|
||||||
)
|
for receiver in receivers:
|
||||||
|
receiver.receive(
|
||||||
|
payload=deepcopy(payload),
|
||||||
|
session_id=session_id,
|
||||||
|
from_network_interface=from_network_interface,
|
||||||
|
frame=frame,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.sys_log.warning(f"No service or application found for port {port} and protocol {protocol}")
|
self.sys_log.warning(f"No service or application found for port {port} and protocol {protocol}")
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -377,6 +377,8 @@ class DatabaseService(Service):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
result = {"status_code": 401, "type": "sql"}
|
result = {"status_code": 401, "type": "sql"}
|
||||||
|
else:
|
||||||
|
self.sys_log.info(f"{self.name}: Ignoring payload as it is not a Database payload")
|
||||||
self.send(payload=result, session_id=session_id)
|
self.send(payload=result, session_id=session_id)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ from abc import abstractmethod
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from ipaddress import IPv4Address, IPv4Network
|
from ipaddress import IPv4Address, IPv4Network
|
||||||
from typing import Any, Dict, Optional, TYPE_CHECKING, Union
|
from typing import Any, Dict, Optional, Set, TYPE_CHECKING, Union
|
||||||
|
|
||||||
from prettytable import MARKDOWN, PrettyTable
|
from prettytable import MARKDOWN, PrettyTable
|
||||||
|
from pydantic import Field
|
||||||
|
|
||||||
from primaite.interface.request import RequestResponse
|
from primaite.interface.request import RequestResponse
|
||||||
from primaite.simulator.core import RequestManager, RequestType, SimComponent
|
from primaite.simulator.core import RequestManager, RequestType, SimComponent
|
||||||
@@ -252,6 +253,8 @@ class IOSoftware(Software):
|
|||||||
"Indicates if the software uses UDP protocol for communication. Default is True."
|
"Indicates if the software uses UDP protocol for communication. Default is True."
|
||||||
port: Port
|
port: Port
|
||||||
"The port to which the software is connected."
|
"The port to which the software is connected."
|
||||||
|
listen_on_ports: Set[Port] = Field(default_factory=set)
|
||||||
|
"The set of ports to listen on."
|
||||||
protocol: IPProtocol
|
protocol: IPProtocol
|
||||||
"The IP Protocol the Software operates on."
|
"The IP Protocol the Software operates on."
|
||||||
_connections: Dict[str, Dict] = {}
|
_connections: Dict[str, Dict] = {}
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
|
||||||
|
from typing import Any, Dict, List, Set
|
||||||
|
|
||||||
|
from pydantic import Field
|
||||||
|
|
||||||
|
from primaite.simulator.network.transmission.network_layer import IPProtocol
|
||||||
|
from primaite.simulator.network.transmission.transport_layer import Port
|
||||||
|
from primaite.simulator.system.applications.database_client import DatabaseClient
|
||||||
|
from primaite.simulator.system.services.database.database_service import DatabaseService
|
||||||
|
from primaite.simulator.system.services.service import Service
|
||||||
|
|
||||||
|
|
||||||
|
class _DatabaseListener(Service):
|
||||||
|
name: str = "DatabaseListener"
|
||||||
|
protocol: IPProtocol = IPProtocol.TCP
|
||||||
|
port: Port = Port.NONE
|
||||||
|
listen_on_ports: Set[Port] = {Port.POSTGRES_SERVER}
|
||||||
|
payloads_received: List[Any] = Field(default_factory=list)
|
||||||
|
|
||||||
|
def receive(self, payload: Any, session_id: str, **kwargs) -> bool:
|
||||||
|
self.payloads_received.append(payload)
|
||||||
|
self.sys_log.info(f"{self.name}: received payload {payload}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def describe_state(self) -> Dict:
|
||||||
|
return super().describe_state()
|
||||||
|
|
||||||
|
|
||||||
|
def test_http_listener(client_server):
|
||||||
|
computer, server = client_server
|
||||||
|
|
||||||
|
server.software_manager.install(DatabaseService)
|
||||||
|
server_db = server.software_manager.software["DatabaseService"]
|
||||||
|
server_db.start()
|
||||||
|
|
||||||
|
server.software_manager.install(_DatabaseListener)
|
||||||
|
server_db_listener: _DatabaseListener = server.software_manager.software["DatabaseListener"]
|
||||||
|
server_db_listener.start()
|
||||||
|
|
||||||
|
computer.software_manager.install(DatabaseClient)
|
||||||
|
computer_db_client: DatabaseClient = computer.software_manager.software["DatabaseClient"]
|
||||||
|
|
||||||
|
computer_db_client.run()
|
||||||
|
computer_db_client.server_ip_address = server.network_interface[1].ip_address
|
||||||
|
|
||||||
|
assert len(server_db_listener.payloads_received) == 0
|
||||||
|
computer.session_manager.receive_payload_from_software_manager(
|
||||||
|
payload="masquerade as Database traffic",
|
||||||
|
dst_ip_address=server.network_interface[1].ip_address,
|
||||||
|
dst_port=Port.POSTGRES_SERVER,
|
||||||
|
ip_protocol=IPProtocol.TCP,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(server_db_listener.payloads_received) == 1
|
||||||
|
|
||||||
|
db_connection = computer_db_client.get_new_connection()
|
||||||
|
|
||||||
|
assert db_connection
|
||||||
|
|
||||||
|
assert len(server_db_listener.payloads_received) == 2
|
||||||
|
|
||||||
|
assert db_connection.query("SELECT")
|
||||||
|
|
||||||
|
assert len(server_db_listener.payloads_received) == 3
|
||||||
Reference in New Issue
Block a user