From 79d5b8bed1fcc9792eefdcb654b5e59e23246be5 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Tue, 30 Aug 2022 20:08:54 +0000 Subject: [PATCH 001/175] Agent: Add TCPRelay class --- monkey/infection_monkey/tcp_relay.py | 31 ++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 monkey/infection_monkey/tcp_relay.py diff --git a/monkey/infection_monkey/tcp_relay.py b/monkey/infection_monkey/tcp_relay.py new file mode 100644 index 000000000..23a6cb843 --- /dev/null +++ b/monkey/infection_monkey/tcp_relay.py @@ -0,0 +1,31 @@ +from threading import Event, Thread +from time import sleep + +from infection_monkey.transport.tcp import TcpProxy + + +class TCPRelay(Thread): + """Provides and manages a TCP proxy connection.""" + + def __init__(self, local_port: int, target_addr: str, target_port: int): + self._stopped = Event() + self._local_port = local_port + self._target_addr = target_addr + self._target_port = target_port + super(TCPRelay, self).__init__(name="MonkeyTcpRelayThread") + self.daemon = True + + def run(self): + proxy = TcpProxy( + local_port=self._local_port, dest_host=self._target_addr, dest_port=self._target_port + ) + proxy.start() + + while not self._stopped.is_set(): + sleep(0.001) + + proxy.stop() + proxy.join() + + def stop(self): + self._stopped.set() From 9425a9463a8ddfeab2248a938850dfcac0df1fdd Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Tue, 30 Aug 2022 20:53:16 +0000 Subject: [PATCH 002/175] Agent: Track relay users in TCPRelay --- monkey/infection_monkey/tcp_relay.py | 29 +++++++++++++- monkey/infection_monkey/transport/tcp.py | 33 +++++++++++++++- .../infection_monkey/test_tcp_relay.py | 39 +++++++++++++++++++ 3 files changed, 97 insertions(+), 4 deletions(-) create mode 100644 monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py diff --git a/monkey/infection_monkey/tcp_relay.py b/monkey/infection_monkey/tcp_relay.py index 23a6cb843..ed8340dd7 100644 --- a/monkey/infection_monkey/tcp_relay.py +++ b/monkey/infection_monkey/tcp_relay.py @@ -1,9 +1,16 @@ -from threading import Event, Thread +from dataclasses import dataclass +from threading import Event, Lock, Thread from time import sleep +from typing import List from infection_monkey.transport.tcp import TcpProxy +@dataclass +class RelayUser: + address: str + + class TCPRelay(Thread): """Provides and manages a TCP proxy connection.""" @@ -14,10 +21,16 @@ class TCPRelay(Thread): self._target_port = target_port super(TCPRelay, self).__init__(name="MonkeyTcpRelayThread") self.daemon = True + self._relay_users: List[RelayUser] = [] + self._lock = Lock() def run(self): proxy = TcpProxy( - local_port=self._local_port, dest_host=self._target_addr, dest_port=self._target_port + local_port=self._local_port, + dest_host=self._target_addr, + dest_port=self._target_port, + client_connected=self.on_user_connected, + client_disconnected=self.on_user_disconnected, ) proxy.start() @@ -29,3 +42,15 @@ class TCPRelay(Thread): def stop(self): self._stopped.set() + + def on_user_connected(self, user: str): + with self._lock: + self._relay_users.append(RelayUser(user)) + + def on_user_disconnected(self, user: str): + with self._lock: + self._relay_users = [u for u in self._relay_users if u.address != user] + + def relay_users(self) -> List[RelayUser]: + with self._lock: + return self._relay_users.copy() diff --git a/monkey/infection_monkey/transport/tcp.py b/monkey/infection_monkey/transport/tcp.py index 83c631c3b..637d095d0 100644 --- a/monkey/infection_monkey/transport/tcp.py +++ b/monkey/infection_monkey/transport/tcp.py @@ -1,7 +1,9 @@ import select import socket +from functools import partial from logging import getLogger from threading import Thread +from typing import Callable from infection_monkey.transport.base import ( PROXY_TIMEOUT, @@ -16,7 +18,13 @@ logger = getLogger(__name__) class SocketsPipe(Thread): - def __init__(self, source, dest, timeout=SOCKET_READ_TIMEOUT): + def __init__( + self, + source, + dest, + timeout=SOCKET_READ_TIMEOUT, + client_disconnected: Callable[[str], None] = None, + ): Thread.__init__(self) self.source = source self.dest = dest @@ -24,6 +32,7 @@ class SocketsPipe(Thread): self._keep_connection = True super(SocketsPipe, self).__init__() self.daemon = True + self._client_disconnected = client_disconnected def run(self): sockets = [self.source, self.dest] @@ -48,9 +57,24 @@ class SocketsPipe(Thread): self.source.close() self.dest.close() + if self._client_disconnected: + self._client_disconnected() class TcpProxy(TransportProxyBase): + def __init__( + self, + local_port, + dest_host=None, + dest_port=None, + local_host="", + client_connected: Callable[[str], None] = None, + client_disconnected: Callable[[str], None] = None, + ): + super().__init__(local_port, dest_host, dest_port, local_host) + self._client_connected = client_connected + self._client_disconnected = client_disconnected + def run(self): pipes = [] l_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -72,7 +96,10 @@ class TcpProxy(TransportProxyBase): dest.close() continue - pipe = SocketsPipe(source, dest) + on_disconnect = ( + partial(self._client_connected, address[0]) if self._client_connected else None + ) + pipe = SocketsPipe(source, dest, on_disconnect) pipes.append(pipe) logger.debug( "piping sockets %s:%s->%s:%s", @@ -81,6 +108,8 @@ class TcpProxy(TransportProxyBase): self.dest_host, self.dest_port, ) + if self._client_connected: + self._client_connected(address[0]) pipe.start() l_socket.close() diff --git a/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py b/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py new file mode 100644 index 000000000..4c0dc2bc9 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py @@ -0,0 +1,39 @@ +from threading import Thread + +from monkey.infection_monkey.tcp_relay import TCPRelay + + +def join_or_kill_thread(thread: Thread, timeout: float): + thread.join(timeout) + if thread.is_alive(): + thread.daemon = True + return False + return True + + +def test_stops(): + relay = TCPRelay(9975, "0.0.0.0", 9976) + relay.start() + relay.stop() + + assert join_or_kill_thread(relay, 0.1) + + +def test_user_added(): + relay = TCPRelay(9975, "0.0.0.0", 9976) + new_user = "0.0.0.1" + relay.on_user_connected(new_user) + + users = relay.relay_users() + assert len(users) == 1 + assert users[0].address == new_user + + +def test_user_removed(): + relay = TCPRelay(9975, "0.0.0.0", 9976) + new_user = "0.0.0.1" + relay.on_user_connected(new_user) + relay.on_user_disconnected(new_user) + + users = relay.relay_users() + assert len(users) == 0 From cd0b3077cfa339d76916e94207b2772ead1720e7 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Tue, 30 Aug 2022 21:31:06 +0000 Subject: [PATCH 003/175] Agent: Notify TCPRelay of exploit --- monkey/infection_monkey/tcp_relay.py | 9 +++++++ ...xploit_intercepting_telemetry_messenger.py | 22 +++++++++++++--- ...xploit_intercepting_telemetry_messenger.py | 26 ++++++++++++------- 3 files changed, 43 insertions(+), 14 deletions(-) diff --git a/monkey/infection_monkey/tcp_relay.py b/monkey/infection_monkey/tcp_relay.py index ed8340dd7..eaa564c3b 100644 --- a/monkey/infection_monkey/tcp_relay.py +++ b/monkey/infection_monkey/tcp_relay.py @@ -22,6 +22,7 @@ class TCPRelay(Thread): super(TCPRelay, self).__init__(name="MonkeyTcpRelayThread") self.daemon = True self._relay_users: List[RelayUser] = [] + self._potential_users: List[RelayUser] = [] self._lock = Lock() def run(self): @@ -44,13 +45,21 @@ class TCPRelay(Thread): self._stopped.set() def on_user_connected(self, user: str): + """Handle new user connection.""" with self._lock: self._relay_users.append(RelayUser(user)) def on_user_disconnected(self, user: str): + """Handle user disconnection.""" with self._lock: self._relay_users = [u for u in self._relay_users if u.address != user] def relay_users(self) -> List[RelayUser]: + """Get the list of users connected to the relay.""" with self._lock: return self._relay_users.copy() + + def on_potential_new_user(self, user: str): + """Notify TCPRelay that a new user may try and connect.""" + with self._lock: + self._potential_users.append(RelayUser(user)) diff --git a/monkey/infection_monkey/telemetry/messengers/exploit_intercepting_telemetry_messenger.py b/monkey/infection_monkey/telemetry/messengers/exploit_intercepting_telemetry_messenger.py index b2a254061..5eb8f0310 100644 --- a/monkey/infection_monkey/telemetry/messengers/exploit_intercepting_telemetry_messenger.py +++ b/monkey/infection_monkey/telemetry/messengers/exploit_intercepting_telemetry_messenger.py @@ -1,5 +1,6 @@ from functools import singledispatch +from infection_monkey.tcp_relay import TCPRelay from infection_monkey.telemetry.exploit_telem import ExploitTelem from infection_monkey.telemetry.i_telem import ITelem from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger @@ -7,26 +8,39 @@ from infection_monkey.tunnel import MonkeyTunnel class ExploitInterceptingTelemetryMessenger(ITelemetryMessenger): - def __init__(self, telemetry_messenger: ITelemetryMessenger, tunnel: MonkeyTunnel): + def __init__( + self, telemetry_messenger: ITelemetryMessenger, tunnel: MonkeyTunnel, relay: TCPRelay = None + ): self._telemetry_messenger = telemetry_messenger self._tunnel = tunnel + self._relay = relay def send_telemetry(self, telemetry: ITelem): - _send_telemetry(telemetry, self._telemetry_messenger, self._tunnel) + _send_telemetry(telemetry, self._telemetry_messenger, self._tunnel, self._relay) # Note: We can use @singledispatchmethod instead of @singledispatch if we migrate to Python 3.8 or # later. @singledispatch def _send_telemetry( - telemetry: ITelem, telemetry_messenger: ITelemetryMessenger, tunnel: MonkeyTunnel + telemetry: ITelem, + telemetry_messenger: ITelemetryMessenger, + tunnel: MonkeyTunnel, + relay: TCPRelay = None, ): telemetry_messenger.send_telemetry(telemetry) @_send_telemetry.register -def _(telemetry: ExploitTelem, telemetry_messenger: ITelemetryMessenger, tunnel: MonkeyTunnel): +def _( + telemetry: ExploitTelem, + telemetry_messenger: ITelemetryMessenger, + tunnel: MonkeyTunnel, + relay: TCPRelay = None, +): if telemetry.propagation_result is True: tunnel.set_wait_for_exploited_machines() + if relay: + relay.on_potential_new_user(str(telemetry.host["ip_addr"])) telemetry_messenger.send_telemetry(telemetry) diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_exploit_intercepting_telemetry_messenger.py b/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_exploit_intercepting_telemetry_messenger.py index 969489107..7e9e1e7fd 100644 --- a/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_exploit_intercepting_telemetry_messenger.py +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_exploit_intercepting_telemetry_messenger.py @@ -8,7 +8,7 @@ from infection_monkey.telemetry.messengers.exploit_intercepting_telemetry_messen ) -class MockExpliotTelem(ExploitTelem): +class MockExploitTelem(ExploitTelem): def __init__(self, propagation_success): erd = ExploiterResultData() erd.propagation_success = propagation_success @@ -21,42 +21,48 @@ class MockExpliotTelem(ExploitTelem): def test_generic_telemetry(TestTelem): mock_telemetry_messenger = MagicMock() mock_tunnel = MagicMock() + mock_relay = MagicMock() telemetry_messenger = ExploitInterceptingTelemetryMessenger( - mock_telemetry_messenger, mock_tunnel + mock_telemetry_messenger, mock_tunnel, mock_relay ) telemetry_messenger.send_telemetry(TestTelem()) assert mock_telemetry_messenger.send_telemetry.called assert not mock_tunnel.set_wait_for_exploited_machines.called + assert not mock_relay.on_potential_new_user.called -def test_propagation_successful_expliot_telemetry(): +def test_propagation_successful_exploit_telemetry(): mock_telemetry_messenger = MagicMock() mock_tunnel = MagicMock() - mock_expliot_telem = MockExpliotTelem(True) + mock_relay = MagicMock() + mock_exploit_telem = MockExploitTelem(True) telemetry_messenger = ExploitInterceptingTelemetryMessenger( - mock_telemetry_messenger, mock_tunnel + mock_telemetry_messenger, mock_tunnel, mock_relay ) - telemetry_messenger.send_telemetry(mock_expliot_telem) + telemetry_messenger.send_telemetry(mock_exploit_telem) assert mock_telemetry_messenger.send_telemetry.called assert mock_tunnel.set_wait_for_exploited_machines.called + assert mock_relay.on_potential_new_user.called -def test_propagation_failed_expliot_telemetry(): +def test_propagation_failed_exploit_telemetry(): mock_telemetry_messenger = MagicMock() mock_tunnel = MagicMock() - mock_expliot_telem = MockExpliotTelem(False) + mock_relay = MagicMock() + mock_exploit_telem = MockExploitTelem(False) telemetry_messenger = ExploitInterceptingTelemetryMessenger( - mock_telemetry_messenger, mock_tunnel + mock_telemetry_messenger, mock_tunnel, mock_relay ) - telemetry_messenger.send_telemetry(mock_expliot_telem) + telemetry_messenger.send_telemetry(mock_exploit_telem) assert mock_telemetry_messenger.send_telemetry.called assert not mock_tunnel.set_wait_for_exploited_machines.called + assert not mock_relay.on_potential_new_user.called From 4b5d93beb07699fb85f2882efc1bae9cc46fdedc Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Wed, 31 Aug 2022 12:06:13 +0000 Subject: [PATCH 004/175] Agent: Add disconnect protocol to TCPRelay --- monkey/infection_monkey/tcp_relay.py | 15 +++++++++++++-- monkey/infection_monkey/transport/tcp.py | 13 +++++++++++-- .../unit_tests/infection_monkey/test_tcp_relay.py | 13 ++++++++++++- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/monkey/infection_monkey/tcp_relay.py b/monkey/infection_monkey/tcp_relay.py index eaa564c3b..3d7c536cc 100644 --- a/monkey/infection_monkey/tcp_relay.py +++ b/monkey/infection_monkey/tcp_relay.py @@ -32,6 +32,7 @@ class TCPRelay(Thread): dest_port=self._target_port, client_connected=self.on_user_connected, client_disconnected=self.on_user_disconnected, + client_data_received=self.on_user_data_received, ) proxy.start() @@ -47,12 +48,12 @@ class TCPRelay(Thread): def on_user_connected(self, user: str): """Handle new user connection.""" with self._lock: + self._potential_users = [u for u in self._potential_users if u.address != user] self._relay_users.append(RelayUser(user)) def on_user_disconnected(self, user: str): """Handle user disconnection.""" - with self._lock: - self._relay_users = [u for u in self._relay_users if u.address != user] + pass def relay_users(self) -> List[RelayUser]: """Get the list of users connected to the relay.""" @@ -63,3 +64,13 @@ class TCPRelay(Thread): """Notify TCPRelay that a new user may try and connect.""" with self._lock: self._potential_users.append(RelayUser(user)) + + def on_user_data_received(self, data: bytes, user: str) -> bool: + if data.startswith(b"-"): + self._disconnect_user(user) + return False + return True + + def _disconnect_user(self, user: str): + with self._lock: + self._relay_users = [u for u in self._relay_users if u.address != user] diff --git a/monkey/infection_monkey/transport/tcp.py b/monkey/infection_monkey/transport/tcp.py index 637d095d0..b174e0578 100644 --- a/monkey/infection_monkey/transport/tcp.py +++ b/monkey/infection_monkey/transport/tcp.py @@ -17,6 +17,10 @@ SOCKET_READ_TIMEOUT = 10 logger = getLogger(__name__) +def _default_client_data_received(_: bytes, client=None) -> bool: + return True + + class SocketsPipe(Thread): def __init__( self, @@ -24,6 +28,7 @@ class SocketsPipe(Thread): dest, timeout=SOCKET_READ_TIMEOUT, client_disconnected: Callable[[str], None] = None, + client_data_received: Callable[[bytes], bool] = _default_client_data_received, ): Thread.__init__(self) self.source = source @@ -33,6 +38,7 @@ class SocketsPipe(Thread): super(SocketsPipe, self).__init__() self.daemon = True self._client_disconnected = client_disconnected + self._client_data_received = client_data_received def run(self): sockets = [self.source, self.dest] @@ -47,7 +53,7 @@ class SocketsPipe(Thread): data = r.recv(READ_BUFFER_SIZE) except Exception: break - if data: + if data and self._client_data_received(data): try: other.sendall(data) update_last_serve_time() @@ -70,10 +76,12 @@ class TcpProxy(TransportProxyBase): local_host="", client_connected: Callable[[str], None] = None, client_disconnected: Callable[[str], None] = None, + client_data_received: Callable[[bytes, str], bool] = _default_client_data_received, ): super().__init__(local_port, dest_host, dest_port, local_host) self._client_connected = client_connected self._client_disconnected = client_disconnected + self._client_data_received = client_data_received def run(self): pipes = [] @@ -99,7 +107,8 @@ class TcpProxy(TransportProxyBase): on_disconnect = ( partial(self._client_connected, address[0]) if self._client_connected else None ) - pipe = SocketsPipe(source, dest, on_disconnect) + on_data_received = partial(self._client_data_received, client=address[0]) + pipe = SocketsPipe(source, dest, on_disconnect, on_data_received) pipes.append(pipe) logger.debug( "piping sockets %s:%s->%s:%s", diff --git a/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py b/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py index 4c0dc2bc9..3f22875d9 100644 --- a/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py +++ b/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py @@ -29,11 +29,22 @@ def test_user_added(): assert users[0].address == new_user -def test_user_removed(): +def test_user_not_removed_on_disconnect(): + # A user should only be disconnected when they send a disconnect request relay = TCPRelay(9975, "0.0.0.0", 9976) new_user = "0.0.0.1" relay.on_user_connected(new_user) relay.on_user_disconnected(new_user) + users = relay.relay_users() + assert len(users) == 1 + + +def test_user_removed_on_request(): + relay = TCPRelay(9975, "0.0.0.0", 9976) + new_user = "0.0.0.1" + relay.on_user_connected(new_user) + relay.on_user_data_received(b"-", "0.0.0.1") + users = relay.relay_users() assert len(users) == 0 From 31ff85ad3c3a6e95ff8a96d15023d5b056677242 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Wed, 31 Aug 2022 13:51:54 +0000 Subject: [PATCH 005/175] Agent: Add timeout to wait for pending clients --- monkey/infection_monkey/tcp_relay.py | 32 ++++++++++++++++--- .../infection_monkey/test_tcp_relay.py | 28 ++++++++++++---- 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/monkey/infection_monkey/tcp_relay.py b/monkey/infection_monkey/tcp_relay.py index 3d7c536cc..dd317bdd3 100644 --- a/monkey/infection_monkey/tcp_relay.py +++ b/monkey/infection_monkey/tcp_relay.py @@ -1,24 +1,34 @@ from dataclasses import dataclass from threading import Event, Lock, Thread -from time import sleep +from time import sleep, time from typing import List from infection_monkey.transport.tcp import TcpProxy +DEFAULT_NEW_CLIENT_TIMEOUT = 3 # Wait up to 3 seconds for potential new clients to connect + @dataclass class RelayUser: address: str + time: float class TCPRelay(Thread): """Provides and manages a TCP proxy connection.""" - def __init__(self, local_port: int, target_addr: str, target_port: int): + def __init__( + self, + local_port: int, + target_addr: str, + target_port: int, + new_client_timeout: float = DEFAULT_NEW_CLIENT_TIMEOUT, + ): self._stopped = Event() self._local_port = local_port self._target_addr = target_addr self._target_port = target_port + self._new_client_timeout = new_client_timeout super(TCPRelay, self).__init__(name="MonkeyTcpRelayThread") self.daemon = True self._relay_users: List[RelayUser] = [] @@ -39,6 +49,8 @@ class TCPRelay(Thread): while not self._stopped.is_set(): sleep(0.001) + self._wait_for_users_to_disconnect() + proxy.stop() proxy.join() @@ -49,7 +61,7 @@ class TCPRelay(Thread): """Handle new user connection.""" with self._lock: self._potential_users = [u for u in self._potential_users if u.address != user] - self._relay_users.append(RelayUser(user)) + self._relay_users.append(RelayUser(user, time())) def on_user_disconnected(self, user: str): """Handle user disconnection.""" @@ -63,7 +75,7 @@ class TCPRelay(Thread): def on_potential_new_user(self, user: str): """Notify TCPRelay that a new user may try and connect.""" with self._lock: - self._potential_users.append(RelayUser(user)) + self._potential_users.append(RelayUser(user, time())) def on_user_data_received(self, data: bytes, user: str) -> bool: if data.startswith(b"-"): @@ -74,3 +86,15 @@ class TCPRelay(Thread): def _disconnect_user(self, user: str): with self._lock: self._relay_users = [u for u in self._relay_users if u.address != user] + + def _wait_for_users_to_disconnect(self): + stop = False + while not stop: + sleep(0.01) + current_time = time() + most_recent_potential_time = max( + self._potential_users, key=lambda u: u.time, default=RelayUser("", 0) + ).time + potential_elapsed = current_time - most_recent_potential_time + + stop = not self._potential_users or potential_elapsed > self._new_client_timeout diff --git a/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py b/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py index 3f22875d9..48dcce2a6 100644 --- a/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py +++ b/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py @@ -4,19 +4,21 @@ from monkey.infection_monkey.tcp_relay import TCPRelay def join_or_kill_thread(thread: Thread, timeout: float): + """Whether or not the thread joined in the given timeout period.""" thread.join(timeout) if thread.is_alive(): - thread.daemon = True + # Cannot set daemon status of active thread: thread.daemon = True return False return True -def test_stops(): - relay = TCPRelay(9975, "0.0.0.0", 9976) - relay.start() - relay.stop() +# This will fail unless TcpProxy is updated to do non-blocking accepts +# def test_stops(): +# relay = TCPRelay(9975, "0.0.0.0", 9976) +# relay.start() +# relay.stop() - assert join_or_kill_thread(relay, 0.1) +# assert join_or_kill_thread(relay, 0.2) def test_user_added(): @@ -48,3 +50,17 @@ def test_user_removed_on_request(): users = relay.relay_users() assert len(users) == 0 + + +# This will fail unless TcpProxy is updated to do non-blocking accepts +# @pytest.mark.slow +# def test_waits_for_exploited_machines(): +# relay = TCPRelay(9975, "0.0.0.0", 9976, new_client_timeout=0.2) +# new_user = "0.0.0.1" +# relay.start() + +# relay.on_potential_new_user(new_user) +# relay.stop() + +# assert not join_or_kill_thread(relay, 0.1) # Should be waiting +# assert join_or_kill_thread(relay, 1) # Should be done waiting From 302cdaac70d0f279585a2b7cf35b2a2b4e7f9447 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Thu, 1 Sep 2022 09:57:46 +0200 Subject: [PATCH 006/175] Agent: Use event.wait() when running TCPRelay --- monkey/infection_monkey/tcp_relay.py | 3 +-- vulture_allowlist.py | 3 +++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/tcp_relay.py b/monkey/infection_monkey/tcp_relay.py index dd317bdd3..bb396de86 100644 --- a/monkey/infection_monkey/tcp_relay.py +++ b/monkey/infection_monkey/tcp_relay.py @@ -46,8 +46,7 @@ class TCPRelay(Thread): ) proxy.start() - while not self._stopped.is_set(): - sleep(0.001) + self._stopped.wait() self._wait_for_users_to_disconnect() diff --git a/vulture_allowlist.py b/vulture_allowlist.py index 5ab45088a..da14d4a42 100644 --- a/vulture_allowlist.py +++ b/vulture_allowlist.py @@ -286,6 +286,9 @@ event deserialize serialized_event +# TODO: Remove after #2231 is closed +relay_users + # pydantic base models underscore_attrs_are_private extra From 94fba0fdf02b491f190c1cf931661db05bf52221 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Thu, 1 Sep 2022 10:17:13 +0200 Subject: [PATCH 007/175] Agent: Rename RelayUser.time to last_update_time --- monkey/infection_monkey/tcp_relay.py | 2 +- vulture_allowlist.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/tcp_relay.py b/monkey/infection_monkey/tcp_relay.py index bb396de86..9fa4be1b6 100644 --- a/monkey/infection_monkey/tcp_relay.py +++ b/monkey/infection_monkey/tcp_relay.py @@ -11,7 +11,7 @@ DEFAULT_NEW_CLIENT_TIMEOUT = 3 # Wait up to 3 seconds for potential new clients @dataclass class RelayUser: address: str - time: float + last_update_time: float class TCPRelay(Thread): diff --git a/vulture_allowlist.py b/vulture_allowlist.py index da14d4a42..9f10fdedf 100644 --- a/vulture_allowlist.py +++ b/vulture_allowlist.py @@ -288,6 +288,7 @@ serialized_event # TODO: Remove after #2231 is closed relay_users +last_update_time # pydantic base models underscore_attrs_are_private From ecdf04a85e81e8b38339ca90521c885f6b8f7387 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Thu, 1 Sep 2022 10:35:29 +0200 Subject: [PATCH 008/175] Agent: Change relay control message '-' to 'infection-monkey-relay-control-message: -' --- monkey/infection_monkey/tcp_relay.py | 3 ++- monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/tcp_relay.py b/monkey/infection_monkey/tcp_relay.py index 9fa4be1b6..243204b76 100644 --- a/monkey/infection_monkey/tcp_relay.py +++ b/monkey/infection_monkey/tcp_relay.py @@ -6,6 +6,7 @@ from typing import List from infection_monkey.transport.tcp import TcpProxy DEFAULT_NEW_CLIENT_TIMEOUT = 3 # Wait up to 3 seconds for potential new clients to connect +RELAY_CONTROL_MESSAGE = b"infection-monkey-relay-control-message: -" @dataclass @@ -77,7 +78,7 @@ class TCPRelay(Thread): self._potential_users.append(RelayUser(user, time())) def on_user_data_received(self, data: bytes, user: str) -> bool: - if data.startswith(b"-"): + if data.startswith(RELAY_CONTROL_MESSAGE): self._disconnect_user(user) return False return True diff --git a/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py b/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py index 48dcce2a6..02c577797 100644 --- a/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py +++ b/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py @@ -1,6 +1,6 @@ from threading import Thread -from monkey.infection_monkey.tcp_relay import TCPRelay +from monkey.infection_monkey.tcp_relay import RELAY_CONTROL_MESSAGE, TCPRelay def join_or_kill_thread(thread: Thread, timeout: float): @@ -46,7 +46,7 @@ def test_user_removed_on_request(): relay = TCPRelay(9975, "0.0.0.0", 9976) new_user = "0.0.0.1" relay.on_user_connected(new_user) - relay.on_user_data_received(b"-", "0.0.0.1") + relay.on_user_data_received(RELAY_CONTROL_MESSAGE, "0.0.0.1") users = relay.relay_users() assert len(users) == 0 From d478e992273aa96dc9e8079b94773bc98e3e5885 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Thu, 1 Sep 2022 11:29:03 +0200 Subject: [PATCH 009/175] Agent: Make TCPRelay non-optional in ExploitInterceptingTelemetryMessenger --- .../messengers/exploit_intercepting_telemetry_messenger.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/telemetry/messengers/exploit_intercepting_telemetry_messenger.py b/monkey/infection_monkey/telemetry/messengers/exploit_intercepting_telemetry_messenger.py index 5eb8f0310..0d1f4127c 100644 --- a/monkey/infection_monkey/telemetry/messengers/exploit_intercepting_telemetry_messenger.py +++ b/monkey/infection_monkey/telemetry/messengers/exploit_intercepting_telemetry_messenger.py @@ -9,7 +9,7 @@ from infection_monkey.tunnel import MonkeyTunnel class ExploitInterceptingTelemetryMessenger(ITelemetryMessenger): def __init__( - self, telemetry_messenger: ITelemetryMessenger, tunnel: MonkeyTunnel, relay: TCPRelay = None + self, telemetry_messenger: ITelemetryMessenger, tunnel: MonkeyTunnel, relay: TCPRelay ): self._telemetry_messenger = telemetry_messenger self._tunnel = tunnel @@ -26,7 +26,7 @@ def _send_telemetry( telemetry: ITelem, telemetry_messenger: ITelemetryMessenger, tunnel: MonkeyTunnel, - relay: TCPRelay = None, + relay: TCPRelay, ): telemetry_messenger.send_telemetry(telemetry) @@ -36,7 +36,7 @@ def _( telemetry: ExploitTelem, telemetry_messenger: ITelemetryMessenger, tunnel: MonkeyTunnel, - relay: TCPRelay = None, + relay: TCPRelay, ): if telemetry.propagation_result is True: tunnel.set_wait_for_exploited_machines() From 1d394bbd2e53771ec386ad390f80668569f88cd4 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Thu, 1 Sep 2022 11:39:25 +0200 Subject: [PATCH 010/175] Agent: Fix docstrings in TCPRelay --- monkey/infection_monkey/tcp_relay.py | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/monkey/infection_monkey/tcp_relay.py b/monkey/infection_monkey/tcp_relay.py index 243204b76..4acc55ee3 100644 --- a/monkey/infection_monkey/tcp_relay.py +++ b/monkey/infection_monkey/tcp_relay.py @@ -16,7 +16,9 @@ class RelayUser: class TCPRelay(Thread): - """Provides and manages a TCP proxy connection.""" + """ + Provides and manages a TCP proxy connection. + """ def __init__( self, @@ -58,7 +60,11 @@ class TCPRelay(Thread): self._stopped.set() def on_user_connected(self, user: str): - """Handle new user connection.""" + """ + Handle new user connection. + + :param user: A user which will be added to the relay + """ with self._lock: self._potential_users = [u for u in self._potential_users if u.address != user] self._relay_users.append(RelayUser(user, time())) @@ -68,16 +74,28 @@ class TCPRelay(Thread): pass def relay_users(self) -> List[RelayUser]: - """Get the list of users connected to the relay.""" + """ + Get the list of users connected to the relay. + """ with self._lock: return self._relay_users.copy() def on_potential_new_user(self, user: str): - """Notify TCPRelay that a new user may try and connect.""" + """ + Notify TCPRelay that a new user may try and connect. + + :param user: A potential user that tries to connect to the relay + """ with self._lock: self._potential_users.append(RelayUser(user, time())) def on_user_data_received(self, data: bytes, user: str) -> bool: + """ + Disconnect a user which a specific starting data. + + :param data: The data that a relay recieved + :param user: User which send the data + """ if data.startswith(RELAY_CONTROL_MESSAGE): self._disconnect_user(user) return False From 9a3afb051d3aeff9f386a735b39db898e4762dd2 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Thu, 1 Sep 2022 12:10:21 +0200 Subject: [PATCH 011/175] Agent: Use IPv4Address for RelayUser.address --- monkey/infection_monkey/tcp_relay.py | 25 +++++------ .../infection_monkey/test_tcp_relay.py | 43 +++++++++++-------- 2 files changed, 38 insertions(+), 30 deletions(-) diff --git a/monkey/infection_monkey/tcp_relay.py b/monkey/infection_monkey/tcp_relay.py index 4acc55ee3..235800d12 100644 --- a/monkey/infection_monkey/tcp_relay.py +++ b/monkey/infection_monkey/tcp_relay.py @@ -1,4 +1,5 @@ from dataclasses import dataclass +from ipaddress import IPv4Address from threading import Event, Lock, Thread from time import sleep, time from typing import List @@ -11,7 +12,7 @@ RELAY_CONTROL_MESSAGE = b"infection-monkey-relay-control-message: -" @dataclass class RelayUser: - address: str + address: IPv4Address last_update_time: float @@ -59,17 +60,17 @@ class TCPRelay(Thread): def stop(self): self._stopped.set() - def on_user_connected(self, user: str): + def on_user_connected(self, user_address: IPv4Address): """ Handle new user connection. :param user: A user which will be added to the relay """ with self._lock: - self._potential_users = [u for u in self._potential_users if u.address != user] - self._relay_users.append(RelayUser(user, time())) + self._potential_users = [u for u in self._potential_users if u.address != user_address] + self._relay_users.append(RelayUser(user_address, time())) - def on_user_disconnected(self, user: str): + def on_user_disconnected(self, user_address: IPv4Address): """Handle user disconnection.""" pass @@ -80,16 +81,16 @@ class TCPRelay(Thread): with self._lock: return self._relay_users.copy() - def on_potential_new_user(self, user: str): + def on_potential_new_user(self, user_address: IPv4Address): """ Notify TCPRelay that a new user may try and connect. :param user: A potential user that tries to connect to the relay """ with self._lock: - self._potential_users.append(RelayUser(user, time())) + self._potential_users.append(RelayUser(user_address, time())) - def on_user_data_received(self, data: bytes, user: str) -> bool: + def on_user_data_received(self, data: bytes, user_address: IPv4Address) -> bool: """ Disconnect a user which a specific starting data. @@ -97,13 +98,13 @@ class TCPRelay(Thread): :param user: User which send the data """ if data.startswith(RELAY_CONTROL_MESSAGE): - self._disconnect_user(user) + self._disconnect_user(user_address) return False return True - def _disconnect_user(self, user: str): + def _disconnect_user(self, user_address: IPv4Address): with self._lock: - self._relay_users = [u for u in self._relay_users if u.address != user] + self._relay_users = [u for u in self._relay_users if u.address != user_address] def _wait_for_users_to_disconnect(self): stop = False @@ -111,7 +112,7 @@ class TCPRelay(Thread): sleep(0.01) current_time = time() most_recent_potential_time = max( - self._potential_users, key=lambda u: u.time, default=RelayUser("", 0) + self._potential_users, key=lambda u: u.time, default=RelayUser(IPv4Address(""), 0.0) ).time potential_elapsed = current_time - most_recent_potential_time diff --git a/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py b/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py index 02c577797..5caa70e0c 100644 --- a/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py +++ b/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py @@ -1,7 +1,20 @@ +from ipaddress import IPv4Address from threading import Thread +import pytest + from monkey.infection_monkey.tcp_relay import RELAY_CONTROL_MESSAGE, TCPRelay +NEW_USER_ADDRESS = IPv4Address("0.0.0.1") +LOCAL_PORT = 9975 +TARGET_ADDRESS = "0.0.0.0" +TARGET_PORT = 9976 + + +@pytest.fixture +def tcp_relay(): + return TCPRelay(LOCAL_PORT, TARGET_ADDRESS, TARGET_PORT) + def join_or_kill_thread(thread: Thread, timeout: float): """Whether or not the thread joined in the given timeout period.""" @@ -21,34 +34,28 @@ def join_or_kill_thread(thread: Thread, timeout: float): # assert join_or_kill_thread(relay, 0.2) -def test_user_added(): - relay = TCPRelay(9975, "0.0.0.0", 9976) - new_user = "0.0.0.1" - relay.on_user_connected(new_user) +def test_user_added(tcp_relay): + tcp_relay.on_user_connected(NEW_USER_ADDRESS) - users = relay.relay_users() + users = tcp_relay.relay_users() assert len(users) == 1 - assert users[0].address == new_user + assert users[0].address == NEW_USER_ADDRESS -def test_user_not_removed_on_disconnect(): +def test_user_not_removed_on_disconnect(tcp_relay): # A user should only be disconnected when they send a disconnect request - relay = TCPRelay(9975, "0.0.0.0", 9976) - new_user = "0.0.0.1" - relay.on_user_connected(new_user) - relay.on_user_disconnected(new_user) + tcp_relay.on_user_connected(NEW_USER_ADDRESS) + tcp_relay.on_user_disconnected(NEW_USER_ADDRESS) - users = relay.relay_users() + users = tcp_relay.relay_users() assert len(users) == 1 -def test_user_removed_on_request(): - relay = TCPRelay(9975, "0.0.0.0", 9976) - new_user = "0.0.0.1" - relay.on_user_connected(new_user) - relay.on_user_data_received(RELAY_CONTROL_MESSAGE, "0.0.0.1") +def test_user_removed_on_request(tcp_relay): + tcp_relay.on_user_connected(NEW_USER_ADDRESS) + tcp_relay.on_user_data_received(RELAY_CONTROL_MESSAGE, NEW_USER_ADDRESS) - users = relay.relay_users() + users = tcp_relay.relay_users() assert len(users) == 0 From 9fae6cca20eaadc73a137c8f867f65a2de9d686f Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Thu, 1 Sep 2022 12:56:26 +0200 Subject: [PATCH 012/175] Agent: Use dictionary for relay and potential users in TCPRelay --- monkey/infection_monkey/tcp_relay.py | 25 +++++++++++-------- .../infection_monkey/test_tcp_relay.py | 2 +- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/monkey/infection_monkey/tcp_relay.py b/monkey/infection_monkey/tcp_relay.py index 235800d12..77c4808b3 100644 --- a/monkey/infection_monkey/tcp_relay.py +++ b/monkey/infection_monkey/tcp_relay.py @@ -2,7 +2,7 @@ from dataclasses import dataclass from ipaddress import IPv4Address from threading import Event, Lock, Thread from time import sleep, time -from typing import List +from typing import Dict from infection_monkey.transport.tcp import TcpProxy @@ -35,8 +35,8 @@ class TCPRelay(Thread): self._new_client_timeout = new_client_timeout super(TCPRelay, self).__init__(name="MonkeyTcpRelayThread") self.daemon = True - self._relay_users: List[RelayUser] = [] - self._potential_users: List[RelayUser] = [] + self._relay_users: Dict[IPv4Address, RelayUser] = {} + self._potential_users: Dict[IPv4Address, RelayUser] = {} self._lock = Lock() def run(self): @@ -67,14 +67,16 @@ class TCPRelay(Thread): :param user: A user which will be added to the relay """ with self._lock: - self._potential_users = [u for u in self._potential_users if u.address != user_address] - self._relay_users.append(RelayUser(user_address, time())) + if user_address in self._potential_users: + del self._potential_users[user_address] + + self._relay_users[user_address] = RelayUser(user_address, time()) def on_user_disconnected(self, user_address: IPv4Address): """Handle user disconnection.""" pass - def relay_users(self) -> List[RelayUser]: + def relay_users(self) -> Dict[IPv4Address, RelayUser]: """ Get the list of users connected to the relay. """ @@ -88,7 +90,7 @@ class TCPRelay(Thread): :param user: A potential user that tries to connect to the relay """ with self._lock: - self._potential_users.append(RelayUser(user_address, time())) + self._potential_users[user_address] = RelayUser(user_address, time()) def on_user_data_received(self, data: bytes, user_address: IPv4Address) -> bool: """ @@ -104,7 +106,8 @@ class TCPRelay(Thread): def _disconnect_user(self, user_address: IPv4Address): with self._lock: - self._relay_users = [u for u in self._relay_users if u.address != user_address] + if user_address in self._relay_users: + del self._relay_users[user_address] def _wait_for_users_to_disconnect(self): stop = False @@ -112,8 +115,10 @@ class TCPRelay(Thread): sleep(0.01) current_time = time() most_recent_potential_time = max( - self._potential_users, key=lambda u: u.time, default=RelayUser(IPv4Address(""), 0.0) - ).time + self._potential_users.values(), + key=lambda ru: ru.last_update_time, + default=RelayUser(IPv4Address(""), 0.0), + ).last_update_time potential_elapsed = current_time - most_recent_potential_time stop = not self._potential_users or potential_elapsed > self._new_client_timeout diff --git a/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py b/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py index 5caa70e0c..70c1c6bbb 100644 --- a/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py +++ b/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py @@ -39,7 +39,7 @@ def test_user_added(tcp_relay): users = tcp_relay.relay_users() assert len(users) == 1 - assert users[0].address == NEW_USER_ADDRESS + assert NEW_USER_ADDRESS in users def test_user_not_removed_on_disconnect(tcp_relay): From 6e0dc0ca138f084a65888a1c8f15bc0eb86e9979 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Thu, 1 Sep 2022 13:27:40 +0200 Subject: [PATCH 013/175] Agent: Use super().__init__(...) in TCPRelay --- monkey/infection_monkey/tcp_relay.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/tcp_relay.py b/monkey/infection_monkey/tcp_relay.py index 77c4808b3..4d8cebe29 100644 --- a/monkey/infection_monkey/tcp_relay.py +++ b/monkey/infection_monkey/tcp_relay.py @@ -33,7 +33,7 @@ class TCPRelay(Thread): self._target_addr = target_addr self._target_port = target_port self._new_client_timeout = new_client_timeout - super(TCPRelay, self).__init__(name="MonkeyTcpRelayThread") + super().__init__(name="MonkeyTcpRelayThread") self.daemon = True self._relay_users: Dict[IPv4Address, RelayUser] = {} self._potential_users: Dict[IPv4Address, RelayUser] = {} From a7549632225de5cde73c77d663711a1b3f98e6e1 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Thu, 1 Sep 2022 13:32:07 +0200 Subject: [PATCH 014/175] Agent: Remove on_user_disconnected in TCPRelay --- monkey/infection_monkey/tcp_relay.py | 4 ---- .../tests/unit_tests/infection_monkey/test_tcp_relay.py | 9 --------- 2 files changed, 13 deletions(-) diff --git a/monkey/infection_monkey/tcp_relay.py b/monkey/infection_monkey/tcp_relay.py index 4d8cebe29..812c4e6e3 100644 --- a/monkey/infection_monkey/tcp_relay.py +++ b/monkey/infection_monkey/tcp_relay.py @@ -72,10 +72,6 @@ class TCPRelay(Thread): self._relay_users[user_address] = RelayUser(user_address, time()) - def on_user_disconnected(self, user_address: IPv4Address): - """Handle user disconnection.""" - pass - def relay_users(self) -> Dict[IPv4Address, RelayUser]: """ Get the list of users connected to the relay. diff --git a/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py b/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py index 70c1c6bbb..17d5c84b2 100644 --- a/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py +++ b/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py @@ -42,15 +42,6 @@ def test_user_added(tcp_relay): assert NEW_USER_ADDRESS in users -def test_user_not_removed_on_disconnect(tcp_relay): - # A user should only be disconnected when they send a disconnect request - tcp_relay.on_user_connected(NEW_USER_ADDRESS) - tcp_relay.on_user_disconnected(NEW_USER_ADDRESS) - - users = tcp_relay.relay_users() - assert len(users) == 1 - - def test_user_removed_on_request(tcp_relay): tcp_relay.on_user_connected(NEW_USER_ADDRESS) tcp_relay.on_user_data_received(RELAY_CONTROL_MESSAGE, NEW_USER_ADDRESS) From 43a1ddb714823136c41983a6f13bea3c9e304de5 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Thu, 1 Sep 2022 13:38:31 +0200 Subject: [PATCH 015/175] Agent: Rename on_potential_new_user to add_potential_user --- monkey/infection_monkey/tcp_relay.py | 2 +- .../messengers/exploit_intercepting_telemetry_messenger.py | 2 +- monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/tcp_relay.py b/monkey/infection_monkey/tcp_relay.py index 812c4e6e3..ce6d405bf 100644 --- a/monkey/infection_monkey/tcp_relay.py +++ b/monkey/infection_monkey/tcp_relay.py @@ -79,7 +79,7 @@ class TCPRelay(Thread): with self._lock: return self._relay_users.copy() - def on_potential_new_user(self, user_address: IPv4Address): + def add_potential_user(self, user_address: IPv4Address): """ Notify TCPRelay that a new user may try and connect. diff --git a/monkey/infection_monkey/telemetry/messengers/exploit_intercepting_telemetry_messenger.py b/monkey/infection_monkey/telemetry/messengers/exploit_intercepting_telemetry_messenger.py index 0d1f4127c..60bd464b7 100644 --- a/monkey/infection_monkey/telemetry/messengers/exploit_intercepting_telemetry_messenger.py +++ b/monkey/infection_monkey/telemetry/messengers/exploit_intercepting_telemetry_messenger.py @@ -41,6 +41,6 @@ def _( if telemetry.propagation_result is True: tunnel.set_wait_for_exploited_machines() if relay: - relay.on_potential_new_user(str(telemetry.host["ip_addr"])) + relay.add_potential_user(str(telemetry.host["ip_addr"])) telemetry_messenger.send_telemetry(telemetry) diff --git a/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py b/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py index 17d5c84b2..ab8c7c632 100644 --- a/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py +++ b/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py @@ -57,7 +57,7 @@ def test_user_removed_on_request(tcp_relay): # new_user = "0.0.0.1" # relay.start() -# relay.on_potential_new_user(new_user) +# relay.add_potential_user(new_user) # relay.stop() # assert not join_or_kill_thread(relay, 0.1) # Should be waiting From 508b8119398b742f7ad4bbc401b4ced8fc82ce9d Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Thu, 1 Sep 2022 13:41:50 +0200 Subject: [PATCH 016/175] Agent: Rename on_user_connected to add_relay_user --- monkey/infection_monkey/tcp_relay.py | 2 +- monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py | 4 ++-- vulture_allowlist.py | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/tcp_relay.py b/monkey/infection_monkey/tcp_relay.py index ce6d405bf..8cdb287c6 100644 --- a/monkey/infection_monkey/tcp_relay.py +++ b/monkey/infection_monkey/tcp_relay.py @@ -60,7 +60,7 @@ class TCPRelay(Thread): def stop(self): self._stopped.set() - def on_user_connected(self, user_address: IPv4Address): + def add_relay_user(self, user_address: IPv4Address): """ Handle new user connection. diff --git a/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py b/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py index ab8c7c632..c011ccd4f 100644 --- a/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py +++ b/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py @@ -35,7 +35,7 @@ def join_or_kill_thread(thread: Thread, timeout: float): def test_user_added(tcp_relay): - tcp_relay.on_user_connected(NEW_USER_ADDRESS) + tcp_relay.add_relay_user(NEW_USER_ADDRESS) users = tcp_relay.relay_users() assert len(users) == 1 @@ -43,7 +43,7 @@ def test_user_added(tcp_relay): def test_user_removed_on_request(tcp_relay): - tcp_relay.on_user_connected(NEW_USER_ADDRESS) + tcp_relay.add_relay_user(NEW_USER_ADDRESS) tcp_relay.on_user_data_received(RELAY_CONTROL_MESSAGE, NEW_USER_ADDRESS) users = tcp_relay.relay_users() diff --git a/vulture_allowlist.py b/vulture_allowlist.py index 9f10fdedf..a6b99952f 100644 --- a/vulture_allowlist.py +++ b/vulture_allowlist.py @@ -289,6 +289,7 @@ serialized_event # TODO: Remove after #2231 is closed relay_users last_update_time +add_relay_user # pydantic base models underscore_attrs_are_private From eee06467f6bc120d86e38052d1d228c59ba058d7 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Thu, 1 Sep 2022 13:44:39 +0200 Subject: [PATCH 017/175] Agent: Add TODO to rethink on_user_disconnected --- monkey/infection_monkey/tcp_relay.py | 3 +-- monkey/infection_monkey/transport/tcp.py | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/tcp_relay.py b/monkey/infection_monkey/tcp_relay.py index 8cdb287c6..833d9b48f 100644 --- a/monkey/infection_monkey/tcp_relay.py +++ b/monkey/infection_monkey/tcp_relay.py @@ -44,8 +44,7 @@ class TCPRelay(Thread): local_port=self._local_port, dest_host=self._target_addr, dest_port=self._target_port, - client_connected=self.on_user_connected, - client_disconnected=self.on_user_disconnected, + client_connected=self.add_relay_user, client_data_received=self.on_user_data_received, ) proxy.start() diff --git a/monkey/infection_monkey/transport/tcp.py b/monkey/infection_monkey/transport/tcp.py index b174e0578..dc87c67d0 100644 --- a/monkey/infection_monkey/transport/tcp.py +++ b/monkey/infection_monkey/transport/tcp.py @@ -80,6 +80,7 @@ class TcpProxy(TransportProxyBase): ): super().__init__(local_port, dest_host, dest_port, local_host) self._client_connected = client_connected + # TODO: Rethink client_disconnected self._client_disconnected = client_disconnected self._client_data_received = client_data_received From c9b7f924a31a4c9521bfd5309e21648d7658f121 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Thu, 1 Sep 2022 13:58:14 +0200 Subject: [PATCH 018/175] UT: Fix ExploitingInterceptingTelemetryMessenger tests --- .../test_exploit_intercepting_telemetry_messenger.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_exploit_intercepting_telemetry_messenger.py b/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_exploit_intercepting_telemetry_messenger.py index 7e9e1e7fd..0758a5e4d 100644 --- a/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_exploit_intercepting_telemetry_messenger.py +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_exploit_intercepting_telemetry_messenger.py @@ -31,7 +31,7 @@ def test_generic_telemetry(TestTelem): assert mock_telemetry_messenger.send_telemetry.called assert not mock_tunnel.set_wait_for_exploited_machines.called - assert not mock_relay.on_potential_new_user.called + assert not mock_relay.add_potential_user.called def test_propagation_successful_exploit_telemetry(): @@ -48,7 +48,7 @@ def test_propagation_successful_exploit_telemetry(): assert mock_telemetry_messenger.send_telemetry.called assert mock_tunnel.set_wait_for_exploited_machines.called - assert mock_relay.on_potential_new_user.called + assert mock_relay.add_potential_user.called def test_propagation_failed_exploit_telemetry(): @@ -65,4 +65,4 @@ def test_propagation_failed_exploit_telemetry(): assert mock_telemetry_messenger.send_telemetry.called assert not mock_tunnel.set_wait_for_exploited_machines.called - assert not mock_relay.on_potential_new_user.called + assert not mock_relay.add_potential_user.called From 2e7be823a115c80d61bf9065ea0ebfdec2e5129d Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Thu, 1 Sep 2022 13:14:07 +0000 Subject: [PATCH 019/175] Agent: Revert old TcpProxy and create a new one --- monkey/infection_monkey/network/relay/tcp.py | 127 +++++++++++++++++++ monkey/infection_monkey/tcp_relay.py | 2 +- monkey/infection_monkey/transport/tcp.py | 45 +------ 3 files changed, 131 insertions(+), 43 deletions(-) create mode 100644 monkey/infection_monkey/network/relay/tcp.py diff --git a/monkey/infection_monkey/network/relay/tcp.py b/monkey/infection_monkey/network/relay/tcp.py new file mode 100644 index 000000000..dc87c67d0 --- /dev/null +++ b/monkey/infection_monkey/network/relay/tcp.py @@ -0,0 +1,127 @@ +import select +import socket +from functools import partial +from logging import getLogger +from threading import Thread +from typing import Callable + +from infection_monkey.transport.base import ( + PROXY_TIMEOUT, + TransportProxyBase, + update_last_serve_time, +) + +READ_BUFFER_SIZE = 8192 +SOCKET_READ_TIMEOUT = 10 + +logger = getLogger(__name__) + + +def _default_client_data_received(_: bytes, client=None) -> bool: + return True + + +class SocketsPipe(Thread): + def __init__( + self, + source, + dest, + timeout=SOCKET_READ_TIMEOUT, + client_disconnected: Callable[[str], None] = None, + client_data_received: Callable[[bytes], bool] = _default_client_data_received, + ): + Thread.__init__(self) + self.source = source + self.dest = dest + self.timeout = timeout + self._keep_connection = True + super(SocketsPipe, self).__init__() + self.daemon = True + self._client_disconnected = client_disconnected + self._client_data_received = client_data_received + + def run(self): + sockets = [self.source, self.dest] + while self._keep_connection: + self._keep_connection = False + rlist, wlist, xlist = select.select(sockets, [], sockets, self.timeout) + if xlist: + break + for r in rlist: + other = self.dest if r is self.source else self.source + try: + data = r.recv(READ_BUFFER_SIZE) + except Exception: + break + if data and self._client_data_received(data): + try: + other.sendall(data) + update_last_serve_time() + except Exception: + break + self._keep_connection = True + + self.source.close() + self.dest.close() + if self._client_disconnected: + self._client_disconnected() + + +class TcpProxy(TransportProxyBase): + def __init__( + self, + local_port, + dest_host=None, + dest_port=None, + local_host="", + client_connected: Callable[[str], None] = None, + client_disconnected: Callable[[str], None] = None, + client_data_received: Callable[[bytes, str], bool] = _default_client_data_received, + ): + super().__init__(local_port, dest_host, dest_port, local_host) + self._client_connected = client_connected + # TODO: Rethink client_disconnected + self._client_disconnected = client_disconnected + self._client_data_received = client_data_received + + def run(self): + pipes = [] + l_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + l_socket.bind((self.local_host, self.local_port)) + l_socket.settimeout(PROXY_TIMEOUT) + l_socket.listen(5) + + while not self._stopped: + try: + source, address = l_socket.accept() + except socket.timeout: + continue + + dest = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + dest.connect((self.dest_host, self.dest_port)) + except socket.error: + source.close() + dest.close() + continue + + on_disconnect = ( + partial(self._client_connected, address[0]) if self._client_connected else None + ) + on_data_received = partial(self._client_data_received, client=address[0]) + pipe = SocketsPipe(source, dest, on_disconnect, on_data_received) + pipes.append(pipe) + logger.debug( + "piping sockets %s:%s->%s:%s", + address[0], + address[1], + self.dest_host, + self.dest_port, + ) + if self._client_connected: + self._client_connected(address[0]) + pipe.start() + + l_socket.close() + for pipe in pipes: + pipe.join() diff --git a/monkey/infection_monkey/tcp_relay.py b/monkey/infection_monkey/tcp_relay.py index 833d9b48f..c49bdad22 100644 --- a/monkey/infection_monkey/tcp_relay.py +++ b/monkey/infection_monkey/tcp_relay.py @@ -4,7 +4,7 @@ from threading import Event, Lock, Thread from time import sleep, time from typing import Dict -from infection_monkey.transport.tcp import TcpProxy +from infection_monkey.network.relay.tcp import TcpProxy DEFAULT_NEW_CLIENT_TIMEOUT = 3 # Wait up to 3 seconds for potential new clients to connect RELAY_CONTROL_MESSAGE = b"infection-monkey-relay-control-message: -" diff --git a/monkey/infection_monkey/transport/tcp.py b/monkey/infection_monkey/transport/tcp.py index dc87c67d0..83c631c3b 100644 --- a/monkey/infection_monkey/transport/tcp.py +++ b/monkey/infection_monkey/transport/tcp.py @@ -1,9 +1,7 @@ import select import socket -from functools import partial from logging import getLogger from threading import Thread -from typing import Callable from infection_monkey.transport.base import ( PROXY_TIMEOUT, @@ -17,19 +15,8 @@ SOCKET_READ_TIMEOUT = 10 logger = getLogger(__name__) -def _default_client_data_received(_: bytes, client=None) -> bool: - return True - - class SocketsPipe(Thread): - def __init__( - self, - source, - dest, - timeout=SOCKET_READ_TIMEOUT, - client_disconnected: Callable[[str], None] = None, - client_data_received: Callable[[bytes], bool] = _default_client_data_received, - ): + def __init__(self, source, dest, timeout=SOCKET_READ_TIMEOUT): Thread.__init__(self) self.source = source self.dest = dest @@ -37,8 +24,6 @@ class SocketsPipe(Thread): self._keep_connection = True super(SocketsPipe, self).__init__() self.daemon = True - self._client_disconnected = client_disconnected - self._client_data_received = client_data_received def run(self): sockets = [self.source, self.dest] @@ -53,7 +38,7 @@ class SocketsPipe(Thread): data = r.recv(READ_BUFFER_SIZE) except Exception: break - if data and self._client_data_received(data): + if data: try: other.sendall(data) update_last_serve_time() @@ -63,27 +48,9 @@ class SocketsPipe(Thread): self.source.close() self.dest.close() - if self._client_disconnected: - self._client_disconnected() class TcpProxy(TransportProxyBase): - def __init__( - self, - local_port, - dest_host=None, - dest_port=None, - local_host="", - client_connected: Callable[[str], None] = None, - client_disconnected: Callable[[str], None] = None, - client_data_received: Callable[[bytes, str], bool] = _default_client_data_received, - ): - super().__init__(local_port, dest_host, dest_port, local_host) - self._client_connected = client_connected - # TODO: Rethink client_disconnected - self._client_disconnected = client_disconnected - self._client_data_received = client_data_received - def run(self): pipes = [] l_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -105,11 +72,7 @@ class TcpProxy(TransportProxyBase): dest.close() continue - on_disconnect = ( - partial(self._client_connected, address[0]) if self._client_connected else None - ) - on_data_received = partial(self._client_data_received, client=address[0]) - pipe = SocketsPipe(source, dest, on_disconnect, on_data_received) + pipe = SocketsPipe(source, dest) pipes.append(pipe) logger.debug( "piping sockets %s:%s->%s:%s", @@ -118,8 +81,6 @@ class TcpProxy(TransportProxyBase): self.dest_host, self.dest_port, ) - if self._client_connected: - self._client_connected(address[0]) pipe.start() l_socket.close() From 1f723b174eb9c91c186921d4d9cc35a8491ac0a8 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Thu, 1 Sep 2022 13:54:17 +0000 Subject: [PATCH 020/175] Agent: Add TCPConnectionHandler --- .../network/relay/tcp_connection_handler.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 monkey/infection_monkey/network/relay/tcp_connection_handler.py diff --git a/monkey/infection_monkey/network/relay/tcp_connection_handler.py b/monkey/infection_monkey/network/relay/tcp_connection_handler.py new file mode 100644 index 000000000..9b0c4f023 --- /dev/null +++ b/monkey/infection_monkey/network/relay/tcp_connection_handler.py @@ -0,0 +1,43 @@ +import socket +from ipaddress import IPv4Address +from threading import Thread +from typing import Callable + +PROXY_TIMEOUT = 2.5 + + +class TCPConnectionHandler(Thread): + """Accepts connections on a TCP socket.""" + + def __init__( + self, + local_port: int, + local_host: str = "", + client_connected: Callable[[socket.socket, IPv4Address], None] = None, + ): + self.local_port = local_port + self.local_host = local_host + self._client_connected = client_connected + super().__init__() + self.daemon = True + self._stopped = False + + def run(self): + l_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + l_socket.bind((self.local_host, self.local_port)) + l_socket.settimeout(PROXY_TIMEOUT) + l_socket.listen(5) + + while not self._stopped: + try: + source, address = l_socket.accept() + except socket.timeout: + continue + + if self._client_connected: + self._client_connected(source, IPv4Address(address[0])) + + l_socket.close() + + def stop(self): + self._stopped = True From 15dee35aa4f5514043624e10de9d285ba7a6accb Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Thu, 1 Sep 2022 16:02:27 +0200 Subject: [PATCH 021/175] Agent: Add RelayUserHandler --- .../network/relay/relay_user_handler.py | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 monkey/infection_monkey/network/relay/relay_user_handler.py diff --git a/monkey/infection_monkey/network/relay/relay_user_handler.py b/monkey/infection_monkey/network/relay/relay_user_handler.py new file mode 100644 index 000000000..ba1855655 --- /dev/null +++ b/monkey/infection_monkey/network/relay/relay_user_handler.py @@ -0,0 +1,66 @@ +import socket +from dataclasses import dataclass +from ipaddress import IPv4Address +from time import time +from typing import Callable, Dict + +from threding import Lock + +RELAY_CONTROL_MESSAGE = b"infection-monkey-relay-control-message: -" + + +@dataclass +class RelayUser: + address: IPv4Address + last_update_time: float + + +class RelayUserHandler: + def __init__(self, spawn_new_pipe: Callable[[socket.socket, IPv4Address], None]): + self._relay_users: Dict[IPv4Address, RelayUser] = {} + self._potential_users: Dict[IPv4Address, RelayUser] = {} + self._spawn_new_pipe = spawn_new_pipe + + self._lock = Lock() + + def add_relay_user(self, source_socket: socket.socket, user_address: IPv4Address): + """ + Handle new user connection. + + :param source_socket: A source socket + :param user_addres: An address defining RelayUser which will be added to the relay + """ + + with self._lock: + if user_address in self._potential_users: + del self._potential_users[user_address] + + self._relay_users[user_address] = RelayUser(user_address, time()) + self._spawn_new_pipe(source_socket, user_address) + + def add_potential_user(self, user_address: IPv4Address): + """ + Notify TCPRelay that a new user may try and connect + + :param user_address: An address defining potential RelayUser + that tries to connect to the relay + """ + with self._lock: + self._potential_users[user_address] = RelayUser(user_address, time()) + + def on_user_data_recieved(self, data: bytes, user_address: IPv4Address) -> bool: + """ + Disconnect a user with a specific starting data. + + :param data: The data that a relay recieved + :param user_address: An address defining RelayUser which recieved the data + """ + if data.startswith(RELAY_CONTROL_MESSAGE): + self._disconnect_user(user_address) + return False + return True + + def _disconnect_user(self, user_address: IPv4Address): + with self._lock: + if user_address in self._relay_users: + del self._relay_users[user_address] From d6931a64148aeb15729e221d0d37d916bc1452fb Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Thu, 1 Sep 2022 15:03:16 +0000 Subject: [PATCH 022/175] Agent: Clean up RelayUserHandler Add get_potential_users and remove pipe handling --- .../network/relay/relay_user_handler.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/monkey/infection_monkey/network/relay/relay_user_handler.py b/monkey/infection_monkey/network/relay/relay_user_handler.py index ba1855655..364f5985e 100644 --- a/monkey/infection_monkey/network/relay/relay_user_handler.py +++ b/monkey/infection_monkey/network/relay/relay_user_handler.py @@ -1,10 +1,8 @@ -import socket from dataclasses import dataclass from ipaddress import IPv4Address +from threading import Lock from time import time -from typing import Callable, Dict - -from threding import Lock +from typing import Dict RELAY_CONTROL_MESSAGE = b"infection-monkey-relay-control-message: -" @@ -16,19 +14,18 @@ class RelayUser: class RelayUserHandler: - def __init__(self, spawn_new_pipe: Callable[[socket.socket, IPv4Address], None]): + def __init__(self): self._relay_users: Dict[IPv4Address, RelayUser] = {} self._potential_users: Dict[IPv4Address, RelayUser] = {} - self._spawn_new_pipe = spawn_new_pipe self._lock = Lock() - def add_relay_user(self, source_socket: socket.socket, user_address: IPv4Address): + def add_relay_user(self, user_address: IPv4Address): """ Handle new user connection. :param source_socket: A source socket - :param user_addres: An address defining RelayUser which will be added to the relay + :param user_address: An address defining RelayUser which will be added to the relay """ with self._lock: @@ -36,7 +33,6 @@ class RelayUserHandler: del self._potential_users[user_address] self._relay_users[user_address] = RelayUser(user_address, time()) - self._spawn_new_pipe(source_socket, user_address) def add_potential_user(self, user_address: IPv4Address): """ @@ -48,12 +44,12 @@ class RelayUserHandler: with self._lock: self._potential_users[user_address] = RelayUser(user_address, time()) - def on_user_data_recieved(self, data: bytes, user_address: IPv4Address) -> bool: + def on_user_data_received(self, data: bytes, user_address: IPv4Address) -> bool: """ Disconnect a user with a specific starting data. - :param data: The data that a relay recieved - :param user_address: An address defining RelayUser which recieved the data + :param data: The data that a relay received + :param user_address: An address defining RelayUser which received the data """ if data.startswith(RELAY_CONTROL_MESSAGE): self._disconnect_user(user_address) @@ -64,3 +60,7 @@ class RelayUserHandler: with self._lock: if user_address in self._relay_users: del self._relay_users[user_address] + + def get_potential_users(self) -> Dict[IPv4Address, RelayUser]: + with self._lock: + return self._potential_users.copy() From 0e869462b54e759dd8a08dea7f6097cdfc435d00 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Thu, 1 Sep 2022 15:06:47 +0000 Subject: [PATCH 023/175] Agent: Refactor TCPRelay Integrate TCPConnectionHandler and RelayUserHandler into TCPRelay Remove TCPProxy --- .../network/relay/__init__.py | 3 + monkey/infection_monkey/network/relay/tcp.py | 68 +----------- monkey/infection_monkey/tcp_relay.py | 104 +++++++----------- 3 files changed, 41 insertions(+), 134 deletions(-) create mode 100644 monkey/infection_monkey/network/relay/__init__.py diff --git a/monkey/infection_monkey/network/relay/__init__.py b/monkey/infection_monkey/network/relay/__init__.py new file mode 100644 index 000000000..a3a83d9a2 --- /dev/null +++ b/monkey/infection_monkey/network/relay/__init__.py @@ -0,0 +1,3 @@ +from .relay_user_handler import RelayUser, RelayUserHandler +from .tcp_connection_handler import TCPConnectionHandler +from .tcp import SocketsPipe diff --git a/monkey/infection_monkey/network/relay/tcp.py b/monkey/infection_monkey/network/relay/tcp.py index dc87c67d0..0ae6e630b 100644 --- a/monkey/infection_monkey/network/relay/tcp.py +++ b/monkey/infection_monkey/network/relay/tcp.py @@ -1,15 +1,9 @@ import select -import socket -from functools import partial from logging import getLogger from threading import Thread from typing import Callable -from infection_monkey.transport.base import ( - PROXY_TIMEOUT, - TransportProxyBase, - update_last_serve_time, -) +from infection_monkey.transport.base import update_last_serve_time READ_BUFFER_SIZE = 8192 SOCKET_READ_TIMEOUT = 10 @@ -65,63 +59,3 @@ class SocketsPipe(Thread): self.dest.close() if self._client_disconnected: self._client_disconnected() - - -class TcpProxy(TransportProxyBase): - def __init__( - self, - local_port, - dest_host=None, - dest_port=None, - local_host="", - client_connected: Callable[[str], None] = None, - client_disconnected: Callable[[str], None] = None, - client_data_received: Callable[[bytes, str], bool] = _default_client_data_received, - ): - super().__init__(local_port, dest_host, dest_port, local_host) - self._client_connected = client_connected - # TODO: Rethink client_disconnected - self._client_disconnected = client_disconnected - self._client_data_received = client_data_received - - def run(self): - pipes = [] - l_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - l_socket.bind((self.local_host, self.local_port)) - l_socket.settimeout(PROXY_TIMEOUT) - l_socket.listen(5) - - while not self._stopped: - try: - source, address = l_socket.accept() - except socket.timeout: - continue - - dest = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - try: - dest.connect((self.dest_host, self.dest_port)) - except socket.error: - source.close() - dest.close() - continue - - on_disconnect = ( - partial(self._client_connected, address[0]) if self._client_connected else None - ) - on_data_received = partial(self._client_data_received, client=address[0]) - pipe = SocketsPipe(source, dest, on_disconnect, on_data_received) - pipes.append(pipe) - logger.debug( - "piping sockets %s:%s->%s:%s", - address[0], - address[1], - self.dest_host, - self.dest_port, - ) - if self._client_connected: - self._client_connected(address[0]) - pipe.start() - - l_socket.close() - for pipe in pipes: - pipe.join() diff --git a/monkey/infection_monkey/tcp_relay.py b/monkey/infection_monkey/tcp_relay.py index c49bdad22..586663a0a 100644 --- a/monkey/infection_monkey/tcp_relay.py +++ b/monkey/infection_monkey/tcp_relay.py @@ -1,19 +1,17 @@ -from dataclasses import dataclass +import socket from ipaddress import IPv4Address from threading import Event, Lock, Thread from time import sleep, time -from typing import Dict +from typing import List -from infection_monkey.network.relay.tcp import TcpProxy +from infection_monkey.network.relay import ( + RelayUser, + RelayUserHandler, + SocketsPipe, + TCPConnectionHandler, +) DEFAULT_NEW_CLIENT_TIMEOUT = 3 # Wait up to 3 seconds for potential new clients to connect -RELAY_CONTROL_MESSAGE = b"infection-monkey-relay-control-message: -" - - -@dataclass -class RelayUser: - address: IPv4Address - last_update_time: float class TCPRelay(Thread): @@ -29,91 +27,63 @@ class TCPRelay(Thread): new_client_timeout: float = DEFAULT_NEW_CLIENT_TIMEOUT, ): self._stopped = Event() + + self._user_handler = RelayUserHandler() + self._connection_handler = TCPConnectionHandler( + local_port, client_connected=self._user_connected + ) self._local_port = local_port self._target_addr = target_addr self._target_port = target_port self._new_client_timeout = new_client_timeout super().__init__(name="MonkeyTcpRelayThread") self.daemon = True - self._relay_users: Dict[IPv4Address, RelayUser] = {} - self._potential_users: Dict[IPv4Address, RelayUser] = {} self._lock = Lock() + self._pipes: List[SocketsPipe] = [] def run(self): - proxy = TcpProxy( - local_port=self._local_port, - dest_host=self._target_addr, - dest_port=self._target_port, - client_connected=self.add_relay_user, - client_data_received=self.on_user_data_received, - ) - proxy.start() + self._connection_handler.start() self._stopped.wait() - self._wait_for_users_to_disconnect() - proxy.stop() - proxy.join() + self._connection_handler.stop() + self._connection_handler.join() + + [pipe.join() for pipe in self._pipes] def stop(self): self._stopped.set() - def add_relay_user(self, user_address: IPv4Address): - """ - Handle new user connection. + def _user_connected(self, source: socket.socket, user_addr: IPv4Address): + self._user_handler.add_relay_user(user_addr) + self._spawn_pipe(source) - :param user: A user which will be added to the relay - """ - with self._lock: - if user_address in self._potential_users: - del self._potential_users[user_address] + def _spawn_pipe(self, source: socket.socket): + dest = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + dest.connect((self._target_addr, self._target_port)) + except socket.error: + source.close() + dest.close() - self._relay_users[user_address] = RelayUser(user_address, time()) - - def relay_users(self) -> Dict[IPv4Address, RelayUser]: - """ - Get the list of users connected to the relay. - """ - with self._lock: - return self._relay_users.copy() - - def add_potential_user(self, user_address: IPv4Address): - """ - Notify TCPRelay that a new user may try and connect. - - :param user: A potential user that tries to connect to the relay - """ - with self._lock: - self._potential_users[user_address] = RelayUser(user_address, time()) - - def on_user_data_received(self, data: bytes, user_address: IPv4Address) -> bool: - """ - Disconnect a user which a specific starting data. - - :param data: The data that a relay recieved - :param user: User which send the data - """ - if data.startswith(RELAY_CONTROL_MESSAGE): - self._disconnect_user(user_address) - return False - return True - - def _disconnect_user(self, user_address: IPv4Address): - with self._lock: - if user_address in self._relay_users: - del self._relay_users[user_address] + pipe = SocketsPipe( + source, dest, client_data_received=self._user_handler.on_user_data_received + ) + self._pipes.append(pipe) + pipe.run() def _wait_for_users_to_disconnect(self): stop = False while not stop: sleep(0.01) current_time = time() + potential_users = self._user_handler.get_potential_users() most_recent_potential_time = max( - self._potential_users.values(), + potential_users.values(), key=lambda ru: ru.last_update_time, default=RelayUser(IPv4Address(""), 0.0), ).last_update_time potential_elapsed = current_time - most_recent_potential_time - stop = not self._potential_users or potential_elapsed > self._new_client_timeout + stop = not potential_users or potential_elapsed > self._new_client_timeout From ef39a8afd16c0feda05d1ac9c43edc2914d4216f Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Thu, 1 Sep 2022 19:15:23 +0000 Subject: [PATCH 024/175] Agent: Add TCPPipeSpawner class --- .../network/relay/__init__.py | 1 + .../network/relay/tcp_pipe_spawner.py | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 monkey/infection_monkey/network/relay/tcp_pipe_spawner.py diff --git a/monkey/infection_monkey/network/relay/__init__.py b/monkey/infection_monkey/network/relay/__init__.py index a3a83d9a2..41bc81e80 100644 --- a/monkey/infection_monkey/network/relay/__init__.py +++ b/monkey/infection_monkey/network/relay/__init__.py @@ -1,3 +1,4 @@ from .relay_user_handler import RelayUser, RelayUserHandler from .tcp_connection_handler import TCPConnectionHandler from .tcp import SocketsPipe +from .tcp_pipe_spawner import TCPPipeSpawner diff --git a/monkey/infection_monkey/network/relay/tcp_pipe_spawner.py b/monkey/infection_monkey/network/relay/tcp_pipe_spawner.py new file mode 100644 index 000000000..16523dfe3 --- /dev/null +++ b/monkey/infection_monkey/network/relay/tcp_pipe_spawner.py @@ -0,0 +1,25 @@ +import socket +from ipaddress import IPv4Address +from typing import Callable + +from .tcp import SocketsPipe + + +class TCPPipeSpawner: + def __init__(self, target_addr: IPv4Address, target_port: int): + self._target_addr = target_addr + self._target_port = target_port + + def spawn_pipe(self, source: socket.socket) -> SocketsPipe: + dest = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + dest.connect((self._target_addr, self._target_port)) + except socket.error as err: + source.close() + dest.close() + raise err + + return SocketsPipe(source, dest, client_data_received=self._client_data_received) + + def notify_client_data_received(self, callback: Callable[[bytes], bool]): + self._client_data_received = callback From b1c1d2bd395a93c6214364f4692ac0c72864ee86 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Thu, 1 Sep 2022 19:19:05 +0000 Subject: [PATCH 025/175] Agent: Allow callback registration on TCPConnectionHandler --- .../network/relay/tcp_connection_handler.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/monkey/infection_monkey/network/relay/tcp_connection_handler.py b/monkey/infection_monkey/network/relay/tcp_connection_handler.py index 9b0c4f023..4e88edd0b 100644 --- a/monkey/infection_monkey/network/relay/tcp_connection_handler.py +++ b/monkey/infection_monkey/network/relay/tcp_connection_handler.py @@ -41,3 +41,11 @@ class TCPConnectionHandler(Thread): def stop(self): self._stopped = True + + def notify_client_connected(self, callback: Callable[[socket.socket, IPv4Address], None]): + """ + Register to be notified when a client connects. + + :param callback: Callable used to notify when a client connects. + """ + self._client_connected = callback From 46573c9f2dba5dfdb8185dc6f27207d3a40ee63b Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Thu, 1 Sep 2022 19:24:13 +0000 Subject: [PATCH 026/175] UT: Add tests for RelayUserHandler --- .../network/relay/test_relay_user_handler.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 monkey/tests/unit_tests/infection_monkey/network/relay/test_relay_user_handler.py diff --git a/monkey/tests/unit_tests/infection_monkey/network/relay/test_relay_user_handler.py b/monkey/tests/unit_tests/infection_monkey/network/relay/test_relay_user_handler.py new file mode 100644 index 000000000..34aaf6bc7 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/network/relay/test_relay_user_handler.py @@ -0,0 +1,23 @@ +from ipaddress import IPv4Address + +from monkey.infection_monkey.network.relay import RelayUserHandler + + +def test_potential_users_added(): + user_address = IPv4Address("0.0.0.0") + handler = RelayUserHandler() + + assert len(handler.get_potential_users()) == 0 + handler.add_potential_user(user_address) + assert len(handler.get_potential_users()) == 1 + assert user_address in handler.get_potential_users() + + +def test_potential_user_removed_on_matching_user_added(): + user_address = IPv4Address("0.0.0.0") + handler = RelayUserHandler() + + handler.add_potential_user(user_address) + handler.add_relay_user(user_address) + + assert len(handler.get_potential_users()) == 0 From 62e4fb89bbda1af14d24a1069cec575f9d6d8ca2 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Thu, 1 Sep 2022 19:28:06 +0000 Subject: [PATCH 027/175] Agent: Inject dependencies into TCPRelay --- monkey/infection_monkey/tcp_relay.py | 30 +++---- .../infection_monkey/test_tcp_relay.py | 88 ++++++++++++++----- 2 files changed, 77 insertions(+), 41 deletions(-) diff --git a/monkey/infection_monkey/tcp_relay.py b/monkey/infection_monkey/tcp_relay.py index 586663a0a..66097b335 100644 --- a/monkey/infection_monkey/tcp_relay.py +++ b/monkey/infection_monkey/tcp_relay.py @@ -9,6 +9,7 @@ from infection_monkey.network.relay import ( RelayUserHandler, SocketsPipe, TCPConnectionHandler, + TCPPipeSpawner, ) DEFAULT_NEW_CLIENT_TIMEOUT = 3 # Wait up to 3 seconds for potential new clients to connect @@ -21,20 +22,18 @@ class TCPRelay(Thread): def __init__( self, - local_port: int, - target_addr: str, - target_port: int, + relay_user_handler: RelayUserHandler, + connection_handler: TCPConnectionHandler, + pipe_spawner: TCPPipeSpawner, new_client_timeout: float = DEFAULT_NEW_CLIENT_TIMEOUT, ): self._stopped = Event() - self._user_handler = RelayUserHandler() - self._connection_handler = TCPConnectionHandler( - local_port, client_connected=self._user_connected - ) - self._local_port = local_port - self._target_addr = target_addr - self._target_port = target_port + self._user_handler = relay_user_handler + self._connection_handler = connection_handler + self._connection_handler.notify_client_connected(self._user_connected) + self._pipe_spawner = pipe_spawner + self._pipe_spawner.notify_client_data_received(self._user_handler.on_user_data_received) self._new_client_timeout = new_client_timeout super().__init__(name="MonkeyTcpRelayThread") self.daemon = True @@ -60,16 +59,7 @@ class TCPRelay(Thread): self._spawn_pipe(source) def _spawn_pipe(self, source: socket.socket): - dest = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - try: - dest.connect((self._target_addr, self._target_port)) - except socket.error: - source.close() - dest.close() - - pipe = SocketsPipe( - source, dest, client_data_received=self._user_handler.on_user_data_received - ) + pipe = self._pipe_spawner.spawn_pipe(source) self._pipes.append(pipe) pipe.run() diff --git a/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py b/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py index c011ccd4f..c913e21ef 100644 --- a/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py +++ b/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py @@ -1,9 +1,15 @@ +import socket from ipaddress import IPv4Address from threading import Thread +from typing import Callable +from unittest.mock import MagicMock import pytest -from monkey.infection_monkey.tcp_relay import RELAY_CONTROL_MESSAGE, TCPRelay +from monkey.infection_monkey.network.relay.relay_user_handler import ( # RELAY_CONTROL_MESSAGE, + RelayUserHandler, +) +from monkey.infection_monkey.tcp_relay import TCPRelay NEW_USER_ADDRESS = IPv4Address("0.0.0.1") LOCAL_PORT = 9975 @@ -11,9 +17,51 @@ TARGET_ADDRESS = "0.0.0.0" TARGET_PORT = 9976 +class FakeConnectionHandler: + def notify_client_connected(self, callback: Callable[[socket.socket, IPv4Address], None]): + self.cb = callback + + def client_connected(self, socket: socket.socket, addr: IPv4Address): + self.cb(socket, addr) + + def start(self): + pass + + def stop(self): + pass + + def join(self): + pass + + +class FakePipeSpawner: + spawn_pipe = MagicMock() + + def notify_client_data_received(self, callback: Callable[[bytes], bool]): + self.cb = callback + + def send_client_data(self, data: bytes): + self.cb(data) + + @pytest.fixture -def tcp_relay(): - return TCPRelay(LOCAL_PORT, TARGET_ADDRESS, TARGET_PORT) +def relay_user_handler() -> RelayUserHandler: + return RelayUserHandler() + + +@pytest.fixture +def pipe_spawner(): + return FakePipeSpawner() + + +@pytest.fixture +def connection_handler(): + return FakeConnectionHandler() + + +@pytest.fixture +def tcp_relay(relay_user_handler, connection_handler, pipe_spawner) -> TCPRelay: + return TCPRelay(relay_user_handler, connection_handler, pipe_spawner) def join_or_kill_thread(thread: Thread, timeout: float): @@ -25,29 +73,27 @@ def join_or_kill_thread(thread: Thread, timeout: float): return True -# This will fail unless TcpProxy is updated to do non-blocking accepts -# def test_stops(): -# relay = TCPRelay(9975, "0.0.0.0", 9976) -# relay.start() -# relay.stop() - -# assert join_or_kill_thread(relay, 0.2) +def test_user_added_when_user_connected(connection_handler, relay_user_handler, tcp_relay): + # sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + # connection_handler.client_connected(sock, NEW_USER_ADDRESS) + # assert len(relay_user_handler.get_relay_users()) == 1 + pass -def test_user_added(tcp_relay): - tcp_relay.add_relay_user(NEW_USER_ADDRESS) - - users = tcp_relay.relay_users() - assert len(users) == 1 - assert NEW_USER_ADDRESS in users +def test_pipe_created_when_user_connected(connection_handler, pipe_spawner, tcp_relay): + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + connection_handler.client_connected(sock, NEW_USER_ADDRESS) + assert pipe_spawner.spawn_pipe.called -def test_user_removed_on_request(tcp_relay): - tcp_relay.add_relay_user(NEW_USER_ADDRESS) - tcp_relay.on_user_data_received(RELAY_CONTROL_MESSAGE, NEW_USER_ADDRESS) +def test_user_removed_on_request(relay_user_handler, pipe_spawner, tcp_relay): + relay_user_handler.add_relay_user(NEW_USER_ADDRESS) - users = tcp_relay.relay_users() - assert len(users) == 0 + # pipe_spawner.send_client_data(RELAY_CONTROL_MESSAGE, NEW_USER_ADDRESS) + + # users = relay_user_handler.get_relay_users() + # assert len(users) == 0 + pass # This will fail unless TcpProxy is updated to do non-blocking accepts From b179f602c4b9c2d3ff703802a21548aeb16fc71a Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Fri, 2 Sep 2022 15:05:19 +0000 Subject: [PATCH 028/175] Agent: Give TCPConnectionHandler thread a name --- .../network/relay/tcp_connection_handler.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/monkey/infection_monkey/network/relay/tcp_connection_handler.py b/monkey/infection_monkey/network/relay/tcp_connection_handler.py index 4e88edd0b..bcd44b3d5 100644 --- a/monkey/infection_monkey/network/relay/tcp_connection_handler.py +++ b/monkey/infection_monkey/network/relay/tcp_connection_handler.py @@ -1,6 +1,6 @@ import socket from ipaddress import IPv4Address -from threading import Thread +from threading import Event, Thread from typing import Callable PROXY_TIMEOUT = 2.5 @@ -18,9 +18,8 @@ class TCPConnectionHandler(Thread): self.local_port = local_port self.local_host = local_host self._client_connected = client_connected - super().__init__() - self.daemon = True - self._stopped = False + super().__init__(name="TCPConnectionHandler", daemon=True) + self._stopped = Event() def run(self): l_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -40,7 +39,7 @@ class TCPConnectionHandler(Thread): l_socket.close() def stop(self): - self._stopped = True + self._stopped.set() def notify_client_connected(self, callback: Callable[[socket.socket, IPv4Address], None]): """ From bbc9cf16e62e73a04eff9225bac1e3948a884a5e Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Fri, 2 Sep 2022 19:17:43 +0000 Subject: [PATCH 029/175] Agent: Add RelayConnectionHandler --- .../network/relay/__init__.py | 1 + .../network/relay/relay_connection_handler.py | 23 +++++++ .../network/relay/relay_user_handler.py | 9 ++- .../relay/test_relay_connection_handler.py | 64 +++++++++++++++++++ 4 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 monkey/infection_monkey/network/relay/relay_connection_handler.py create mode 100644 monkey/tests/unit_tests/infection_monkey/network/relay/test_relay_connection_handler.py diff --git a/monkey/infection_monkey/network/relay/__init__.py b/monkey/infection_monkey/network/relay/__init__.py index 41bc81e80..5817c0a15 100644 --- a/monkey/infection_monkey/network/relay/__init__.py +++ b/monkey/infection_monkey/network/relay/__init__.py @@ -1,3 +1,4 @@ +from .relay_connection_handler import RelayConnectionHandler from .relay_user_handler import RelayUser, RelayUserHandler from .tcp_connection_handler import TCPConnectionHandler from .tcp import SocketsPipe diff --git a/monkey/infection_monkey/network/relay/relay_connection_handler.py b/monkey/infection_monkey/network/relay/relay_connection_handler.py new file mode 100644 index 000000000..8f039488b --- /dev/null +++ b/monkey/infection_monkey/network/relay/relay_connection_handler.py @@ -0,0 +1,23 @@ +import socket +from ipaddress import IPv4Address + +from .relay_user_handler import RelayUserHandler +from .tcp_pipe_spawner import TCPPipeSpawner + +RELAY_CONTROL_MESSAGE = b"infection-monkey-relay-control-message: -" + + +class RelayConnectionHandler: + def __init__(self, pipe_spawner: TCPPipeSpawner, relay_user_handler: RelayUserHandler): + self._pipe_spawner = pipe_spawner + self._relay_user_handler = relay_user_handler + + def handle_new_connection(self, sock: socket.socket): + control_message = sock.recv(socket.MSG_PEEK) + addr, _ = sock.getpeername() # TODO check the type of the addr object + if control_message.startswith(RELAY_CONTROL_MESSAGE): + + self._relay_user_handler.disconnect_user(IPv4Address(addr)) + else: + self._relay_user_handler.add_relay_user(IPv4Address(addr)) + self._pipe_spawner.spawn_pipe(sock) diff --git a/monkey/infection_monkey/network/relay/relay_user_handler.py b/monkey/infection_monkey/network/relay/relay_user_handler.py index 364f5985e..0a32988b5 100644 --- a/monkey/infection_monkey/network/relay/relay_user_handler.py +++ b/monkey/infection_monkey/network/relay/relay_user_handler.py @@ -52,11 +52,16 @@ class RelayUserHandler: :param user_address: An address defining RelayUser which received the data """ if data.startswith(RELAY_CONTROL_MESSAGE): - self._disconnect_user(user_address) + self.disconnect_user(user_address) return False return True - def _disconnect_user(self, user_address: IPv4Address): + def disconnect_user(self, user_address: IPv4Address): + """ + Handle when a user disconnects. + + :param user_address: The address of the disconnecting user. + """ with self._lock: if user_address in self._relay_users: del self._relay_users[user_address] diff --git a/monkey/tests/unit_tests/infection_monkey/network/relay/test_relay_connection_handler.py b/monkey/tests/unit_tests/infection_monkey/network/relay/test_relay_connection_handler.py new file mode 100644 index 000000000..c871681b6 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/network/relay/test_relay_connection_handler.py @@ -0,0 +1,64 @@ +import socket +from ipaddress import IPv4Address +from unittest.mock import MagicMock + +import pytest + +from monkey.infection_monkey.network.relay import ( + RelayConnectionHandler, + RelayUserHandler, + TCPPipeSpawner, +) +from monkey.infection_monkey.network.relay.relay_connection_handler import RELAY_CONTROL_MESSAGE + +USER_ADDRESS = "0.0.0.1" + + +@pytest.fixture +def pipe_spawner(): + return MagicMock(spec=TCPPipeSpawner) + + +@pytest.fixture +def relay_user_handler(): + return MagicMock(spec=RelayUserHandler) + + +@pytest.fixture +def close_socket(): + sock = MagicMock(spec=socket.socket) + sock.recv.return_value = RELAY_CONTROL_MESSAGE + sock.getpeername.return_value = (USER_ADDRESS, 12345) + return sock + + +@pytest.fixture +def data_socket(): + sock = MagicMock(spec=socket.socket) + sock.recv.return_value = b"some data" + sock.getpeername.return_value = (USER_ADDRESS, 12345) + return sock + + +def test_control_message_disconnects_user(pipe_spawner, relay_user_handler, close_socket): + connection_handler = RelayConnectionHandler(pipe_spawner, relay_user_handler) + + connection_handler.handle_new_connection(close_socket) + + relay_user_handler.disconnect_user.assert_called_once_with(IPv4Address(USER_ADDRESS)) + + +def test_connection_spawns_pipe(pipe_spawner, relay_user_handler, data_socket): + connection_handler = RelayConnectionHandler(pipe_spawner, relay_user_handler) + + connection_handler.handle_new_connection(data_socket) + + assert pipe_spawner.spawn_pipe.called + + +def test_connection_adds_user(pipe_spawner, relay_user_handler, data_socket): + connection_handler = RelayConnectionHandler(pipe_spawner, relay_user_handler) + + connection_handler.handle_new_connection(data_socket) + + relay_user_handler.add_relay_user.assert_called_once_with(IPv4Address(USER_ADDRESS)) From 72144faefcb6d3d6e2bbb9ea71f5901b80aad00a Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Fri, 2 Sep 2022 19:58:01 +0000 Subject: [PATCH 030/175] Agent: Update TCPRelay to separate responsbilities --- .../network/relay/relay_user_handler.py | 33 +++--- monkey/infection_monkey/network/relay/tcp.py | 10 +- .../network/relay/tcp_connection_handler.py | 19 +-- .../network/relay/tcp_pipe_spawner.py | 19 ++- monkey/infection_monkey/tcp_relay.py | 53 +++------ .../network/relay/test_relay_user_handler.py | 42 ++++--- .../infection_monkey/test_tcp_relay.py | 110 ------------------ 7 files changed, 77 insertions(+), 209 deletions(-) delete mode 100644 monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py diff --git a/monkey/infection_monkey/network/relay/relay_user_handler.py b/monkey/infection_monkey/network/relay/relay_user_handler.py index 0a32988b5..bce43cc4e 100644 --- a/monkey/infection_monkey/network/relay/relay_user_handler.py +++ b/monkey/infection_monkey/network/relay/relay_user_handler.py @@ -4,7 +4,7 @@ from threading import Lock from time import time from typing import Dict -RELAY_CONTROL_MESSAGE = b"infection-monkey-relay-control-message: -" +DEFAULT_NEW_CLIENT_TIMEOUT = 3 # Wait up to 3 seconds for potential new clients to connect @dataclass @@ -14,7 +14,8 @@ class RelayUser: class RelayUserHandler: - def __init__(self): + def __init__(self, new_client_timeout: float = DEFAULT_NEW_CLIENT_TIMEOUT): + self._new_client_timeout = new_client_timeout self._relay_users: Dict[IPv4Address, RelayUser] = {} self._potential_users: Dict[IPv4Address, RelayUser] = {} @@ -44,18 +45,6 @@ class RelayUserHandler: with self._lock: self._potential_users[user_address] = RelayUser(user_address, time()) - def on_user_data_received(self, data: bytes, user_address: IPv4Address) -> bool: - """ - Disconnect a user with a specific starting data. - - :param data: The data that a relay received - :param user_address: An address defining RelayUser which received the data - """ - if data.startswith(RELAY_CONTROL_MESSAGE): - self.disconnect_user(user_address) - return False - return True - def disconnect_user(self, user_address: IPv4Address): """ Handle when a user disconnects. @@ -66,6 +55,16 @@ class RelayUserHandler: if user_address in self._relay_users: del self._relay_users[user_address] - def get_potential_users(self) -> Dict[IPv4Address, RelayUser]: - with self._lock: - return self._potential_users.copy() + def has_potential_users(self) -> bool: + """ + Return whether or not we have any potential users. + """ + current_time = time() + self._potential_users = dict( + filter( + lambda ru: (current_time - ru[1].last_update_time) < self._new_client_timeout, + self._potential_users.items(), + ) + ) + + return len(self._potential_users) > 0 diff --git a/monkey/infection_monkey/network/relay/tcp.py b/monkey/infection_monkey/network/relay/tcp.py index 0ae6e630b..d60f954ab 100644 --- a/monkey/infection_monkey/network/relay/tcp.py +++ b/monkey/infection_monkey/network/relay/tcp.py @@ -11,10 +11,6 @@ SOCKET_READ_TIMEOUT = 10 logger = getLogger(__name__) -def _default_client_data_received(_: bytes, client=None) -> bool: - return True - - class SocketsPipe(Thread): def __init__( self, @@ -22,7 +18,6 @@ class SocketsPipe(Thread): dest, timeout=SOCKET_READ_TIMEOUT, client_disconnected: Callable[[str], None] = None, - client_data_received: Callable[[bytes], bool] = _default_client_data_received, ): Thread.__init__(self) self.source = source @@ -32,13 +27,12 @@ class SocketsPipe(Thread): super(SocketsPipe, self).__init__() self.daemon = True self._client_disconnected = client_disconnected - self._client_data_received = client_data_received def run(self): sockets = [self.source, self.dest] while self._keep_connection: self._keep_connection = False - rlist, wlist, xlist = select.select(sockets, [], sockets, self.timeout) + rlist, _, xlist = select.select(sockets, [], sockets, self.timeout) if xlist: break for r in rlist: @@ -47,7 +41,7 @@ class SocketsPipe(Thread): data = r.recv(READ_BUFFER_SIZE) except Exception: break - if data and self._client_data_received(data): + if data: try: other.sendall(data) update_last_serve_time() diff --git a/monkey/infection_monkey/network/relay/tcp_connection_handler.py b/monkey/infection_monkey/network/relay/tcp_connection_handler.py index bcd44b3d5..dd8d315e3 100644 --- a/monkey/infection_monkey/network/relay/tcp_connection_handler.py +++ b/monkey/infection_monkey/network/relay/tcp_connection_handler.py @@ -1,7 +1,6 @@ import socket -from ipaddress import IPv4Address from threading import Event, Thread -from typing import Callable +from typing import Callable, List PROXY_TIMEOUT = 2.5 @@ -13,7 +12,7 @@ class TCPConnectionHandler(Thread): self, local_port: int, local_host: str = "", - client_connected: Callable[[socket.socket, IPv4Address], None] = None, + client_connected: List[Callable[[socket.socket], None]] = [], ): self.local_port = local_port self.local_host = local_host @@ -29,22 +28,14 @@ class TCPConnectionHandler(Thread): while not self._stopped: try: - source, address = l_socket.accept() + source, _ = l_socket.accept() except socket.timeout: continue - if self._client_connected: - self._client_connected(source, IPv4Address(address[0])) + for notify_client_connected in self._client_connected: + notify_client_connected(source) l_socket.close() def stop(self): self._stopped.set() - - def notify_client_connected(self, callback: Callable[[socket.socket, IPv4Address], None]): - """ - Register to be notified when a client connects. - - :param callback: Callable used to notify when a client connects. - """ - self._client_connected = callback diff --git a/monkey/infection_monkey/network/relay/tcp_pipe_spawner.py b/monkey/infection_monkey/network/relay/tcp_pipe_spawner.py index 16523dfe3..651043380 100644 --- a/monkey/infection_monkey/network/relay/tcp_pipe_spawner.py +++ b/monkey/infection_monkey/network/relay/tcp_pipe_spawner.py @@ -1,16 +1,21 @@ import socket from ipaddress import IPv4Address -from typing import Callable +from typing import List from .tcp import SocketsPipe class TCPPipeSpawner: + """ + Creates bi-directional pipes between the configured client and other clients. + """ + def __init__(self, target_addr: IPv4Address, target_port: int): self._target_addr = target_addr self._target_port = target_port + self._pipes: List[SocketsPipe] = [] - def spawn_pipe(self, source: socket.socket) -> SocketsPipe: + def spawn_pipe(self, source: socket.socket): dest = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: dest.connect((self._target_addr, self._target_port)) @@ -19,7 +24,11 @@ class TCPPipeSpawner: dest.close() raise err - return SocketsPipe(source, dest, client_data_received=self._client_data_received) + # TODO: have SocketsPipe notify TCPPipeSpawner when it's done + pipe = SocketsPipe(source, dest) + self._pipes.append(pipe) + pipe.run() - def notify_client_data_received(self, callback: Callable[[bytes], bool]): - self._client_data_received = callback + def has_open_pipes(self) -> bool: + self._pipes = [p for p in self._pipes if p.is_alive()] + return len(self._pipes) > 0 diff --git a/monkey/infection_monkey/tcp_relay.py b/monkey/infection_monkey/tcp_relay.py index 66097b335..7f8a9abe5 100644 --- a/monkey/infection_monkey/tcp_relay.py +++ b/monkey/infection_monkey/tcp_relay.py @@ -1,18 +1,7 @@ -import socket -from ipaddress import IPv4Address from threading import Event, Lock, Thread -from time import sleep, time -from typing import List +from time import sleep -from infection_monkey.network.relay import ( - RelayUser, - RelayUserHandler, - SocketsPipe, - TCPConnectionHandler, - TCPPipeSpawner, -) - -DEFAULT_NEW_CLIENT_TIMEOUT = 3 # Wait up to 3 seconds for potential new clients to connect +from infection_monkey.network.relay import RelayUserHandler, TCPConnectionHandler, TCPPipeSpawner class TCPRelay(Thread): @@ -25,20 +14,15 @@ class TCPRelay(Thread): relay_user_handler: RelayUserHandler, connection_handler: TCPConnectionHandler, pipe_spawner: TCPPipeSpawner, - new_client_timeout: float = DEFAULT_NEW_CLIENT_TIMEOUT, ): self._stopped = Event() self._user_handler = relay_user_handler self._connection_handler = connection_handler - self._connection_handler.notify_client_connected(self._user_connected) self._pipe_spawner = pipe_spawner - self._pipe_spawner.notify_client_data_received(self._user_handler.on_user_data_received) - self._new_client_timeout = new_client_timeout super().__init__(name="MonkeyTcpRelayThread") self.daemon = True self._lock = Lock() - self._pipes: List[SocketsPipe] = [] def run(self): self._connection_handler.start() @@ -48,32 +32,21 @@ class TCPRelay(Thread): self._connection_handler.stop() self._connection_handler.join() - - [pipe.join() for pipe in self._pipes] + self._wait_for_pipes_to_close() def stop(self): self._stopped.set() - def _user_connected(self, source: socket.socket, user_addr: IPv4Address): - self._user_handler.add_relay_user(user_addr) - self._spawn_pipe(source) - - def _spawn_pipe(self, source: socket.socket): - pipe = self._pipe_spawner.spawn_pipe(source) - self._pipes.append(pipe) - pipe.run() - def _wait_for_users_to_disconnect(self): - stop = False - while not stop: + """ + Blocks until the users disconnect or the timeout has elapsed. + """ + while self._user_handler.has_potential_users(): sleep(0.01) - current_time = time() - potential_users = self._user_handler.get_potential_users() - most_recent_potential_time = max( - potential_users.values(), - key=lambda ru: ru.last_update_time, - default=RelayUser(IPv4Address(""), 0.0), - ).last_update_time - potential_elapsed = current_time - most_recent_potential_time - stop = not potential_users or potential_elapsed > self._new_client_timeout + def _wait_for_pipes_to_close(self): + """ + Blocks until the pipes have closed. + """ + while self._pipe_spawner.has_open_pipes(): + sleep(0.01) diff --git a/monkey/tests/unit_tests/infection_monkey/network/relay/test_relay_user_handler.py b/monkey/tests/unit_tests/infection_monkey/network/relay/test_relay_user_handler.py index 34aaf6bc7..ca0eb4103 100644 --- a/monkey/tests/unit_tests/infection_monkey/network/relay/test_relay_user_handler.py +++ b/monkey/tests/unit_tests/infection_monkey/network/relay/test_relay_user_handler.py @@ -1,23 +1,35 @@ from ipaddress import IPv4Address +from time import sleep + +import pytest from monkey.infection_monkey.network.relay import RelayUserHandler - -def test_potential_users_added(): - user_address = IPv4Address("0.0.0.0") - handler = RelayUserHandler() - - assert len(handler.get_potential_users()) == 0 - handler.add_potential_user(user_address) - assert len(handler.get_potential_users()) == 1 - assert user_address in handler.get_potential_users() +USER_ADDRESS = IPv4Address("0.0.0.0") -def test_potential_user_removed_on_matching_user_added(): - user_address = IPv4Address("0.0.0.0") - handler = RelayUserHandler() +@pytest.fixture +def handler(): + return RelayUserHandler() - handler.add_potential_user(user_address) - handler.add_relay_user(user_address) - assert len(handler.get_potential_users()) == 0 +def test_potential_users_added(handler): + assert not handler.has_potential_users() + handler.add_potential_user(USER_ADDRESS) + assert handler.has_potential_users() + + +def test_potential_user_removed_on_matching_user_added(handler): + handler.add_potential_user(USER_ADDRESS) + handler.add_relay_user(USER_ADDRESS) + + assert not handler.has_potential_users() + + +def test_potential_users_time_out(): + handler = RelayUserHandler(new_client_timeout=0.001) + + handler.add_potential_user(USER_ADDRESS) + sleep(0.003) + + assert not handler.has_potential_users() diff --git a/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py b/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py deleted file mode 100644 index c913e21ef..000000000 --- a/monkey/tests/unit_tests/infection_monkey/test_tcp_relay.py +++ /dev/null @@ -1,110 +0,0 @@ -import socket -from ipaddress import IPv4Address -from threading import Thread -from typing import Callable -from unittest.mock import MagicMock - -import pytest - -from monkey.infection_monkey.network.relay.relay_user_handler import ( # RELAY_CONTROL_MESSAGE, - RelayUserHandler, -) -from monkey.infection_monkey.tcp_relay import TCPRelay - -NEW_USER_ADDRESS = IPv4Address("0.0.0.1") -LOCAL_PORT = 9975 -TARGET_ADDRESS = "0.0.0.0" -TARGET_PORT = 9976 - - -class FakeConnectionHandler: - def notify_client_connected(self, callback: Callable[[socket.socket, IPv4Address], None]): - self.cb = callback - - def client_connected(self, socket: socket.socket, addr: IPv4Address): - self.cb(socket, addr) - - def start(self): - pass - - def stop(self): - pass - - def join(self): - pass - - -class FakePipeSpawner: - spawn_pipe = MagicMock() - - def notify_client_data_received(self, callback: Callable[[bytes], bool]): - self.cb = callback - - def send_client_data(self, data: bytes): - self.cb(data) - - -@pytest.fixture -def relay_user_handler() -> RelayUserHandler: - return RelayUserHandler() - - -@pytest.fixture -def pipe_spawner(): - return FakePipeSpawner() - - -@pytest.fixture -def connection_handler(): - return FakeConnectionHandler() - - -@pytest.fixture -def tcp_relay(relay_user_handler, connection_handler, pipe_spawner) -> TCPRelay: - return TCPRelay(relay_user_handler, connection_handler, pipe_spawner) - - -def join_or_kill_thread(thread: Thread, timeout: float): - """Whether or not the thread joined in the given timeout period.""" - thread.join(timeout) - if thread.is_alive(): - # Cannot set daemon status of active thread: thread.daemon = True - return False - return True - - -def test_user_added_when_user_connected(connection_handler, relay_user_handler, tcp_relay): - # sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - # connection_handler.client_connected(sock, NEW_USER_ADDRESS) - # assert len(relay_user_handler.get_relay_users()) == 1 - pass - - -def test_pipe_created_when_user_connected(connection_handler, pipe_spawner, tcp_relay): - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - connection_handler.client_connected(sock, NEW_USER_ADDRESS) - assert pipe_spawner.spawn_pipe.called - - -def test_user_removed_on_request(relay_user_handler, pipe_spawner, tcp_relay): - relay_user_handler.add_relay_user(NEW_USER_ADDRESS) - - # pipe_spawner.send_client_data(RELAY_CONTROL_MESSAGE, NEW_USER_ADDRESS) - - # users = relay_user_handler.get_relay_users() - # assert len(users) == 0 - pass - - -# This will fail unless TcpProxy is updated to do non-blocking accepts -# @pytest.mark.slow -# def test_waits_for_exploited_machines(): -# relay = TCPRelay(9975, "0.0.0.0", 9976, new_client_timeout=0.2) -# new_user = "0.0.0.1" -# relay.start() - -# relay.add_potential_user(new_user) -# relay.stop() - -# assert not join_or_kill_thread(relay, 0.1) # Should be waiting -# assert join_or_kill_thread(relay, 1) # Should be done waiting From 5a8d8ccad500d3ce86e117ea4a6ef1fbca6cbc52 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Fri, 2 Sep 2022 20:07:22 +0000 Subject: [PATCH 031/175] Agent: Add name to SocketsPipe thread --- monkey/infection_monkey/network/relay/tcp.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/monkey/infection_monkey/network/relay/tcp.py b/monkey/infection_monkey/network/relay/tcp.py index d60f954ab..14e0848da 100644 --- a/monkey/infection_monkey/network/relay/tcp.py +++ b/monkey/infection_monkey/network/relay/tcp.py @@ -19,12 +19,11 @@ class SocketsPipe(Thread): timeout=SOCKET_READ_TIMEOUT, client_disconnected: Callable[[str], None] = None, ): - Thread.__init__(self) self.source = source self.dest = dest self.timeout = timeout self._keep_connection = True - super(SocketsPipe, self).__init__() + super().__init__(name=f"SocketsPipeThread-{self.ident}") self.daemon = True self._client_disconnected = client_disconnected From 6abcaa9bd65ccd6973f17f44ff8405a329fc0bc5 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 5 Sep 2022 20:03:17 -0400 Subject: [PATCH 032/175] Agent: Remove extra newline in RelayConnectionHandler --- .../infection_monkey/network/relay/relay_connection_handler.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monkey/infection_monkey/network/relay/relay_connection_handler.py b/monkey/infection_monkey/network/relay/relay_connection_handler.py index 8f039488b..fe08a5a61 100644 --- a/monkey/infection_monkey/network/relay/relay_connection_handler.py +++ b/monkey/infection_monkey/network/relay/relay_connection_handler.py @@ -16,7 +16,6 @@ class RelayConnectionHandler: control_message = sock.recv(socket.MSG_PEEK) addr, _ = sock.getpeername() # TODO check the type of the addr object if control_message.startswith(RELAY_CONTROL_MESSAGE): - self._relay_user_handler.disconnect_user(IPv4Address(addr)) else: self._relay_user_handler.add_relay_user(IPv4Address(addr)) From 483bd943bde36e34369aa0af9887aa3f671a3427 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Tue, 6 Sep 2022 12:14:52 +0000 Subject: [PATCH 033/175] Agent: Rename tcp.py to sockets_pipe.py --- monkey/infection_monkey/network/relay/__init__.py | 2 +- .../infection_monkey/network/relay/{tcp.py => sockets_pipe.py} | 0 monkey/infection_monkey/network/relay/tcp_pipe_spawner.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename monkey/infection_monkey/network/relay/{tcp.py => sockets_pipe.py} (100%) diff --git a/monkey/infection_monkey/network/relay/__init__.py b/monkey/infection_monkey/network/relay/__init__.py index 5817c0a15..737d5baf1 100644 --- a/monkey/infection_monkey/network/relay/__init__.py +++ b/monkey/infection_monkey/network/relay/__init__.py @@ -1,5 +1,5 @@ from .relay_connection_handler import RelayConnectionHandler from .relay_user_handler import RelayUser, RelayUserHandler +from .sockets_pipe import SocketsPipe from .tcp_connection_handler import TCPConnectionHandler -from .tcp import SocketsPipe from .tcp_pipe_spawner import TCPPipeSpawner diff --git a/monkey/infection_monkey/network/relay/tcp.py b/monkey/infection_monkey/network/relay/sockets_pipe.py similarity index 100% rename from monkey/infection_monkey/network/relay/tcp.py rename to monkey/infection_monkey/network/relay/sockets_pipe.py diff --git a/monkey/infection_monkey/network/relay/tcp_pipe_spawner.py b/monkey/infection_monkey/network/relay/tcp_pipe_spawner.py index 651043380..22183dd6c 100644 --- a/monkey/infection_monkey/network/relay/tcp_pipe_spawner.py +++ b/monkey/infection_monkey/network/relay/tcp_pipe_spawner.py @@ -2,7 +2,7 @@ import socket from ipaddress import IPv4Address from typing import List -from .tcp import SocketsPipe +from .sockets_pipe import SocketsPipe class TCPPipeSpawner: From 7a7ea96dbb64d01af20140c13580796d8d7dd744 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Tue, 6 Sep 2022 12:27:07 +0000 Subject: [PATCH 034/175] Agent: Set daemon thread in superclass constructor --- monkey/infection_monkey/network/relay/sockets_pipe.py | 3 +-- monkey/infection_monkey/tcp_relay.py | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/monkey/infection_monkey/network/relay/sockets_pipe.py b/monkey/infection_monkey/network/relay/sockets_pipe.py index 14e0848da..3749c6c57 100644 --- a/monkey/infection_monkey/network/relay/sockets_pipe.py +++ b/monkey/infection_monkey/network/relay/sockets_pipe.py @@ -23,8 +23,7 @@ class SocketsPipe(Thread): self.dest = dest self.timeout = timeout self._keep_connection = True - super().__init__(name=f"SocketsPipeThread-{self.ident}") - self.daemon = True + super().__init__(name=f"SocketsPipeThread-{self.ident}", daemon=True) self._client_disconnected = client_disconnected def run(self): diff --git a/monkey/infection_monkey/tcp_relay.py b/monkey/infection_monkey/tcp_relay.py index 7f8a9abe5..a79adf334 100644 --- a/monkey/infection_monkey/tcp_relay.py +++ b/monkey/infection_monkey/tcp_relay.py @@ -20,8 +20,7 @@ class TCPRelay(Thread): self._user_handler = relay_user_handler self._connection_handler = connection_handler self._pipe_spawner = pipe_spawner - super().__init__(name="MonkeyTcpRelayThread") - self.daemon = True + super().__init__(name="MonkeyTcpRelayThread", daemon=True) self._lock = Lock() def run(self): From 311807cf310bb2be9a5a215518a9cbfac12f48ab Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Tue, 6 Sep 2022 12:48:19 +0000 Subject: [PATCH 035/175] Agent: Update RelayUserHandler docstrings --- monkey/infection_monkey/network/relay/relay_user_handler.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/network/relay/relay_user_handler.py b/monkey/infection_monkey/network/relay/relay_user_handler.py index bce43cc4e..8db32a981 100644 --- a/monkey/infection_monkey/network/relay/relay_user_handler.py +++ b/monkey/infection_monkey/network/relay/relay_user_handler.py @@ -14,6 +14,8 @@ class RelayUser: class RelayUserHandler: + """Manages membership to a network relay.""" + def __init__(self, new_client_timeout: float = DEFAULT_NEW_CLIENT_TIMEOUT): self._new_client_timeout = new_client_timeout self._relay_users: Dict[IPv4Address, RelayUser] = {} @@ -37,7 +39,7 @@ class RelayUserHandler: def add_potential_user(self, user_address: IPv4Address): """ - Notify TCPRelay that a new user may try and connect + Notify RelayUserHandler that a new user may try and connect. :param user_address: An address defining potential RelayUser that tries to connect to the relay From 906edf3662ce74300684b6813d89be616926aab1 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Tue, 6 Sep 2022 12:52:00 +0000 Subject: [PATCH 036/175] Agent: Remove unneccessary call in SocketsPipe --- monkey/infection_monkey/network/relay/sockets_pipe.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/monkey/infection_monkey/network/relay/sockets_pipe.py b/monkey/infection_monkey/network/relay/sockets_pipe.py index 3749c6c57..8031c541d 100644 --- a/monkey/infection_monkey/network/relay/sockets_pipe.py +++ b/monkey/infection_monkey/network/relay/sockets_pipe.py @@ -3,8 +3,6 @@ from logging import getLogger from threading import Thread from typing import Callable -from infection_monkey.transport.base import update_last_serve_time - READ_BUFFER_SIZE = 8192 SOCKET_READ_TIMEOUT = 10 @@ -42,7 +40,6 @@ class SocketsPipe(Thread): if data: try: other.sendall(data) - update_last_serve_time() except Exception: break self._keep_connection = True From 333e587aabbc98e6e10effc9341fdc36264875c9 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Tue, 6 Sep 2022 13:20:19 +0000 Subject: [PATCH 037/175] Agent: Use is_set() of threading.Event --- monkey/infection_monkey/network/relay/tcp_connection_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/network/relay/tcp_connection_handler.py b/monkey/infection_monkey/network/relay/tcp_connection_handler.py index dd8d315e3..6064e378b 100644 --- a/monkey/infection_monkey/network/relay/tcp_connection_handler.py +++ b/monkey/infection_monkey/network/relay/tcp_connection_handler.py @@ -26,7 +26,7 @@ class TCPConnectionHandler(Thread): l_socket.settimeout(PROXY_TIMEOUT) l_socket.listen(5) - while not self._stopped: + while not self._stopped.is_set(): try: source, _ = l_socket.accept() except socket.timeout: From d7398e1014e90449a02b4ee8b19fe6be16609591 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Tue, 6 Sep 2022 14:03:17 +0000 Subject: [PATCH 038/175] Agent: Move keep_connection into the run() method --- monkey/infection_monkey/network/relay/sockets_pipe.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/monkey/infection_monkey/network/relay/sockets_pipe.py b/monkey/infection_monkey/network/relay/sockets_pipe.py index 8031c541d..0b33cb533 100644 --- a/monkey/infection_monkey/network/relay/sockets_pipe.py +++ b/monkey/infection_monkey/network/relay/sockets_pipe.py @@ -20,14 +20,14 @@ class SocketsPipe(Thread): self.source = source self.dest = dest self.timeout = timeout - self._keep_connection = True super().__init__(name=f"SocketsPipeThread-{self.ident}", daemon=True) self._client_disconnected = client_disconnected def run(self): sockets = [self.source, self.dest] - while self._keep_connection: - self._keep_connection = False + keep_connection = True + while keep_connection: + keep_connection = False rlist, _, xlist = select.select(sockets, [], sockets, self.timeout) if xlist: break @@ -42,7 +42,7 @@ class SocketsPipe(Thread): other.sendall(data) except Exception: break - self._keep_connection = True + keep_connection = True self.source.close() self.dest.close() From b5345a38d9aa8c840816111f68cd41a0fe0579f0 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Tue, 6 Sep 2022 10:05:41 -0400 Subject: [PATCH 039/175] Agent: Rename TCPConnectionHandler parameters Co-authored-by: Mike Salvatore --- .../network/relay/tcp_connection_handler.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/monkey/infection_monkey/network/relay/tcp_connection_handler.py b/monkey/infection_monkey/network/relay/tcp_connection_handler.py index 6064e378b..d8c7b8337 100644 --- a/monkey/infection_monkey/network/relay/tcp_connection_handler.py +++ b/monkey/infection_monkey/network/relay/tcp_connection_handler.py @@ -10,19 +10,19 @@ class TCPConnectionHandler(Thread): def __init__( self, - local_port: int, - local_host: str = "", + bind_host: str, + bind_port: int, client_connected: List[Callable[[socket.socket], None]] = [], ): - self.local_port = local_port - self.local_host = local_host + self.local_port = bind_port + self.local_host = bind_host self._client_connected = client_connected super().__init__(name="TCPConnectionHandler", daemon=True) self._stopped = Event() def run(self): l_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - l_socket.bind((self.local_host, self.local_port)) + l_socket.bind((self.bind_host, self.bind_port)) l_socket.settimeout(PROXY_TIMEOUT) l_socket.listen(5) From 3ad96a7e6b236b195aadfcd4007bfebd73be2d01 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Tue, 6 Sep 2022 14:21:07 +0000 Subject: [PATCH 040/175] Agent: Simplify RELAY_CONTROL_MESSAGE import --- monkey/infection_monkey/network/relay/__init__.py | 2 +- .../network/relay/test_relay_connection_handler.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/network/relay/__init__.py b/monkey/infection_monkey/network/relay/__init__.py index 737d5baf1..ad5d325c5 100644 --- a/monkey/infection_monkey/network/relay/__init__.py +++ b/monkey/infection_monkey/network/relay/__init__.py @@ -1,4 +1,4 @@ -from .relay_connection_handler import RelayConnectionHandler +from .relay_connection_handler import RelayConnectionHandler, RELAY_CONTROL_MESSAGE from .relay_user_handler import RelayUser, RelayUserHandler from .sockets_pipe import SocketsPipe from .tcp_connection_handler import TCPConnectionHandler diff --git a/monkey/tests/unit_tests/infection_monkey/network/relay/test_relay_connection_handler.py b/monkey/tests/unit_tests/infection_monkey/network/relay/test_relay_connection_handler.py index c871681b6..f82432dae 100644 --- a/monkey/tests/unit_tests/infection_monkey/network/relay/test_relay_connection_handler.py +++ b/monkey/tests/unit_tests/infection_monkey/network/relay/test_relay_connection_handler.py @@ -5,11 +5,11 @@ from unittest.mock import MagicMock import pytest from monkey.infection_monkey.network.relay import ( + RELAY_CONTROL_MESSAGE, RelayConnectionHandler, RelayUserHandler, TCPPipeSpawner, ) -from monkey.infection_monkey.network.relay.relay_connection_handler import RELAY_CONTROL_MESSAGE USER_ADDRESS = "0.0.0.1" From e438a8b9c7d045881ea39bce593d1f2a1fc5dace Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Tue, 6 Sep 2022 14:41:17 +0000 Subject: [PATCH 041/175] Agent: Use EggTimer --- monkey/infection_monkey/Pipfile | 1 + monkey/infection_monkey/Pipfile.lock | 335 ++++++++---------- .../network/relay/relay_user_handler.py | 18 +- 3 files changed, 163 insertions(+), 191 deletions(-) diff --git a/monkey/infection_monkey/Pipfile b/monkey/infection_monkey/Pipfile index 0158a7870..cb2da9762 100644 --- a/monkey/infection_monkey/Pipfile +++ b/monkey/infection_monkey/Pipfile @@ -28,6 +28,7 @@ marshmallow = "*" marshmallow-enum = "*" pypubsub = "*" pydantic = "*" +egg-timer = "*" [dev-packages] ldap3 = "*" diff --git a/monkey/infection_monkey/Pipfile.lock b/monkey/infection_monkey/Pipfile.lock index 4bc8564ce..25947e8e6 100644 --- a/monkey/infection_monkey/Pipfile.lock +++ b/monkey/infection_monkey/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "0ae0a7c88cba4dbd3ad91fd472f6bf12399a9819931d1bf3a936623fa2bfcb6d" + "sha256": "4035f0b638bdef6971dcf5c52a0aa0f2efc76310ad5a1ea853b6d21495ffa2b0" }, "pipfile-spec": 6, "requires": { @@ -18,11 +18,11 @@ "default": { "aiosmb": { "hashes": [ - "sha256:0afa901093f0ad91d0b8421dec66c80bd2e9cb237a8da405984413a5d7475398", - "sha256:0e98390ba00fdc4190e698f184dfcf72b02b592cdfe9274e03cc7316ac4ee368" + "sha256:2668d63ae6e6ca30a999696c444d4afc349f85a872a204994394aa6abbf5673d", + "sha256:b0b0e7068e757b8d3a8e4be2ebf2b499933d9fa7853bc3a8e198d6a57dc77131" ], "markers": "python_version >= '3.7'", - "version": "==0.3.8" + "version": "==0.4.0" }, "aiowinreg": { "hashes": [ @@ -46,13 +46,21 @@ ], "version": "==1.5.1" }, + "asyauth": { + "hashes": [ + "sha256:a7a7998dcfa672ebae9837ba4996df237412a7f335c0f8291bb14091d0e6d19a", + "sha256:f20c497b02d17f25298d3701fb65c9a337893b68a9c7dddecc09ab956b09f828" + ], + "markers": "python_version >= '3.7'", + "version": "==0.0.2" + }, "asysocks": { "hashes": [ - "sha256:23d5fcfae71a75826c3ed787bd9b1bc3b189ec37658961bce83c9e99455e354c", - "sha256:731eda25d41783c5243153d3cb4f9357fef337c7317135488afab9ecd6b7f1a1" + "sha256:8f4516088ebec7f08d8c549e5e75e7a86b41e6043af920ba4895cf3f6c654150", + "sha256:d4c619c9d2e8be0cbdd21fa635a7eaf5886809edc948e2f76625b33b3cccfc47" ], "markers": "python_version >= '3.6'", - "version": "==0.1.7" + "version": "==0.2.1" }, "attrs": { "hashes": [ @@ -71,20 +79,21 @@ }, "bcrypt": { "hashes": [ - "sha256:2b02d6bfc6336d1094276f3f588aa1225a598e27f8e3388f4db9948cb707b521", - "sha256:433c410c2177057705da2a9f2cd01dd157493b2a7ac14c8593a16b3dab6b6bfb", - "sha256:4e029cef560967fb0cf4a802bcf4d562d3d6b4b1bf81de5ec1abbe0f1adb027e", - "sha256:61bae49580dce88095d669226d5076d0b9d927754cedbdf76c6c9f5099ad6f26", - "sha256:6d2cb9d969bfca5bc08e45864137276e4c3d3d7de2b162171def3d188bf9d34a", - "sha256:7180d98a96f00b1050e93f5b0f556e658605dd9f524d0b0e68ae7944673f525e", - "sha256:7d9ba2e41e330d2af4af6b1b6ec9e6128e91343d0b4afb9282e54e5508f31baa", - "sha256:7ff2069240c6bbe49109fe84ca80508773a904f5a8cb960e02a977f7f519b129", - "sha256:88273d806ab3a50d06bc6a2fc7c87d737dd669b76ad955f449c43095389bc8fb", - "sha256:a2c46100e315c3a5b90fdc53e429c006c5f962529bc27e1dfd656292c20ccc40", - "sha256:cd43303d6b8a165c29ec6756afd169faba9396a9472cdff753fe9f19b96ce2fa" + "sha256:0b0f0c7141622a31e9734b7f649451147c04ebb5122327ac0bd23744df84be90", + "sha256:1c3334446fac200499e8bc04a530ce3cf0b3d7151e0e4ac5c0dddd3d95e97843", + "sha256:2d0dd19aad87e4ab882ef1d12df505f4c52b28b69666ce83c528f42c07379227", + "sha256:594780b364fb45f2634c46ec8d3e61c1c0f1811c4f2da60e8eb15594ecbf93ed", + "sha256:7c7dd6c1f05bf89e65261d97ac3a6520f34c2acb369afb57e3ea4449be6ff8fd", + "sha256:845b1daf4df2dd94d2fdbc9454953ca9dd0e12970a0bfc9f3dcc6faea3fa96e4", + "sha256:8780e69f9deec9d60f947b169507d2c9816e4f11548f1f7ebee2af38b9b22ae4", + "sha256:bf413f2a9b0a2950fc750998899013f2e718d20fa4a58b85ca50b6df5ed1bbf9", + "sha256:bfb67f6a6c72dfb0a02f3df51550aa1862708e55128b22543e2b42c74f3620d7", + "sha256:c59c170fc9225faad04dde1ba61d85b413946e8ce2e5f5f5ff30dfd67283f319", + "sha256:dc6ec3dc19b1c193b2f7cf279d3e32e7caf447532fbcb7af0906fe4398900c33", + "sha256:ede0f506554571c8eda80db22b83c139303ec6b595b8f60c4c8157bdd0bdee36" ], "markers": "python_version >= '3.6'", - "version": "==3.2.2" + "version": "==4.0.0" }, "certifi": { "hashes": [ @@ -173,11 +182,11 @@ }, "charset-normalizer": { "hashes": [ - "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5", - "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413" + "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845", + "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" ], "markers": "python_version >= '3.6'", - "version": "==2.1.0" + "version": "==2.1.1" }, "click": { "hashes": [ @@ -238,13 +247,21 @@ "markers": "python_version >= '3.6' and python_version < '4'", "version": "==2.2.1" }, + "egg-timer": { + "hashes": [ + "sha256:125efb1fdc1582e3354dbbf3218010968a875b97bf7c10e00c8ca6010d9c18ad", + "sha256:12b789c8d17d1aab11c5b994bad336089a4c1ac5e0565e3ad2dd0470eae34939" + ], + "index": "pypi", + "version": "==1.0.0.post1" + }, "flask": { "hashes": [ - "sha256:3c604c48c3d5b4c63e72134044c0b4fe90ff01ef65280b9fe2d38c8860d99fe5", - "sha256:9c2b81b9b1edcc835af72d600f1955e713a065e7cb41d7e51ee762b449d9c65d" + "sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b", + "sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526" ], "markers": "python_version >= '3.7'", - "version": "==2.2.1" + "version": "==2.2.2" }, "future": { "hashes": [ @@ -388,11 +405,11 @@ }, "marshmallow": { "hashes": [ - "sha256:00040ab5ea0c608e8787137627a8efae97fabd60552a05dc889c888f814e75eb", - "sha256:635fb65a3285a31a30f276f30e958070f5214c7196202caa5c7ecf28f5274bc7" + "sha256:1172ce82765bf26c24a3f9299ed6dbeeca4d213f638eaa39a37772656d7ce408", + "sha256:48e2d88d4ab431ad5a17c25556d9da529ea6e966876f2a38d274082e270287f0" ], "index": "pypi", - "version": "==3.17.0" + "version": "==3.17.1" }, "marshmallow-enum": { "hashes": [ @@ -412,19 +429,19 @@ }, "minikerberos": { "hashes": [ - "sha256:3c383f67ebcf6f28105ed54f623a6a5c677a24e3f0c9ad69ed453f77e569d714", - "sha256:789f802263fa1882f701b123f6eec048b45cd731bf1b528870005daf07402047" + "sha256:c05cfcd846b1973b2b0d501e9e9fa2a263543d7762b052fc803fc1de849286a3", + "sha256:e912eb4bea899e1875707e7998001ed1047e1b32d5d7bf74d8b6137acf9614d3" ], "markers": "python_version >= '3.6'", - "version": "==0.2.20" + "version": "==0.3.1" }, "msldap": { "hashes": [ - "sha256:ac8174ed7e0162eb64b3e9dfeff13b2e1021612d0a4b2cfc6b8e5bed7c00ffe0", - "sha256:e2c22a6e396b4d7d65d73863ed44612120e8e2570ff895b5421ddf6a350085bb" + "sha256:236eacd04b0d2886e71b2890ec6c67fc626e1b9812c93a0fe21e998697415927", + "sha256:ccb5c1f40de165141931659eb71d4bbad326665aaff7bf23dd0dccb410dfa78c" ], "markers": "python_version >= '3.7'", - "version": "==0.3.40" + "version": "==0.4.0" }, "netifaces": { "hashes": [ @@ -500,55 +517,54 @@ "hashes": [ "sha256:a5488a3dd1fd021ce33f969780b88fe0f7eebb76eb20996d7318f307612a045b" ], - "index": "pypi", "markers": "sys_platform == 'win32'", "version": "==2022.5.30" }, "prompt-toolkit": { "hashes": [ - "sha256:859b283c50bde45f5f97829f77a4674d1c1fcd88539364f1b28a37805cfd89c0", - "sha256:d8916d3f62a7b67ab353a952ce4ced6a1d2587dfe9ef8ebc30dd7c386751f289" + "sha256:9696f386133df0fc8ca5af4895afe5d78f5fcfe5258111c2a79a1c3e41ffa96d", + "sha256:9ada952c9d1787f52ff6d5f3484d0b4df8952787c087edf6a1f7c2cb1ea88148" ], "markers": "python_full_version >= '3.6.2'", - "version": "==3.0.30" + "version": "==3.0.31" }, "psutil": { "hashes": [ - "sha256:068935df39055bf27a29824b95c801c7a5130f118b806eee663cad28dca97685", - "sha256:0904727e0b0a038830b019551cf3204dd48ef5c6868adc776e06e93d615fc5fc", - "sha256:0f15a19a05f39a09327345bc279c1ba4a8cfb0172cc0d3c7f7d16c813b2e7d36", - "sha256:19f36c16012ba9cfc742604df189f2f28d2720e23ff7d1e81602dbe066be9fd1", - "sha256:20b27771b077dcaa0de1de3ad52d22538fe101f9946d6dc7869e6f694f079329", - "sha256:28976df6c64ddd6320d281128817f32c29b539a52bdae5e192537bc338a9ec81", - "sha256:29a442e25fab1f4d05e2655bb1b8ab6887981838d22effa2396d584b740194de", - "sha256:3054e923204b8e9c23a55b23b6df73a8089ae1d075cb0bf711d3e9da1724ded4", - "sha256:32c52611756096ae91f5d1499fe6c53b86f4a9ada147ee42db4991ba1520e574", - "sha256:3a76ad658641172d9c6e593de6fe248ddde825b5866464c3b2ee26c35da9d237", - "sha256:44d1826150d49ffd62035785a9e2c56afcea66e55b43b8b630d7706276e87f22", - "sha256:4b6750a73a9c4a4e689490ccb862d53c7b976a2a35c4e1846d049dcc3f17d83b", - "sha256:56960b9e8edcca1456f8c86a196f0c3d8e3e361320071c93378d41445ffd28b0", - "sha256:57f1819b5d9e95cdfb0c881a8a5b7d542ed0b7c522d575706a80bedc848c8954", - "sha256:58678bbadae12e0db55186dc58f2888839228ac9f41cc7848853539b70490021", - "sha256:645bd4f7bb5b8633803e0b6746ff1628724668681a434482546887d22c7a9537", - "sha256:799759d809c31aab5fe4579e50addf84565e71c1dc9f1c31258f159ff70d3f87", - "sha256:79c9108d9aa7fa6fba6e668b61b82facc067a6b81517cab34d07a84aa89f3df0", - "sha256:91c7ff2a40c373d0cc9121d54bc5f31c4fa09c346528e6a08d1845bce5771ffc", - "sha256:9272167b5f5fbfe16945be3db475b3ce8d792386907e673a209da686176552af", - "sha256:944c4b4b82dc4a1b805329c980f270f170fdc9945464223f2ec8e57563139cf4", - "sha256:a6a11e48cb93a5fa606306493f439b4aa7c56cb03fc9ace7f6bfa21aaf07c453", - "sha256:a8746bfe4e8f659528c5c7e9af5090c5a7d252f32b2e859c584ef7d8efb1e689", - "sha256:abd9246e4cdd5b554a2ddd97c157e292ac11ef3e7af25ac56b08b455c829dca8", - "sha256:b14ee12da9338f5e5b3a3ef7ca58b3cba30f5b66f7662159762932e6d0b8f680", - "sha256:b88f75005586131276634027f4219d06e0561292be8bd6bc7f2f00bdabd63c4e", - "sha256:c7be9d7f5b0d206f0bbc3794b8e16fb7dbc53ec9e40bbe8787c6f2d38efcf6c9", - "sha256:d2d006286fbcb60f0b391741f520862e9b69f4019b4d738a2a45728c7e952f1b", - "sha256:db417f0865f90bdc07fa30e1aadc69b6f4cad7f86324b02aa842034efe8d8c4d", - "sha256:e7e10454cb1ab62cc6ce776e1c135a64045a11ec4c6d254d3f7689c16eb3efd2", - "sha256:f65f9a46d984b8cd9b3750c2bdb419b2996895b005aefa6cbaba9a143b1ce2c5", - "sha256:fea896b54f3a4ae6f790ac1d017101252c93f6fe075d0e7571543510f11d2676" + "sha256:14b29f581b5edab1f133563272a6011925401804d52d603c5c606936b49c8b97", + "sha256:256098b4f6ffea6441eb54ab3eb64db9ecef18f6a80d7ba91549195d55420f84", + "sha256:39ec06dc6c934fb53df10c1672e299145ce609ff0611b569e75a88f313634969", + "sha256:404f4816c16a2fcc4eaa36d7eb49a66df2d083e829d3e39ee8759a411dbc9ecf", + "sha256:42638876b7f5ef43cef8dcf640d3401b27a51ee3fa137cb2aa2e72e188414c32", + "sha256:4642fd93785a29353d6917a23e2ac6177308ef5e8be5cc17008d885cb9f70f12", + "sha256:4fb54941aac044a61db9d8eb56fc5bee207db3bc58645d657249030e15ba3727", + "sha256:561dec454853846d1dd0247b44c2e66a0a0c490f937086930ec4b8f83bf44f06", + "sha256:5d39e3a2d5c40efa977c9a8dd4f679763c43c6c255b1340a56489955dbca767c", + "sha256:614337922702e9be37a39954d67fdb9e855981624d8011a9927b8f2d3c9625d9", + "sha256:67b33f27fc0427483b61563a16c90d9f3b547eeb7af0ef1b9fe024cdc9b3a6ea", + "sha256:68b35cbff92d1f7103d8f1db77c977e72f49fcefae3d3d2b91c76b0e7aef48b8", + "sha256:7cbb795dcd8ed8fd238bc9e9f64ab188f3f4096d2e811b5a82da53d164b84c3f", + "sha256:8f024fbb26c8daf5d70287bb3edfafa22283c255287cf523c5d81721e8e5d82c", + "sha256:91aa0dac0c64688667b4285fa29354acfb3e834e1fd98b535b9986c883c2ce1d", + "sha256:94e621c6a4ddb2573d4d30cba074f6d1aa0186645917df42c811c473dd22b339", + "sha256:9770c1d25aee91417eba7869139d629d6328a9422ce1cdd112bd56377ca98444", + "sha256:b1928b9bf478d31fdffdb57101d18f9b70ed4e9b0e41af751851813547b2a9ab", + "sha256:b2f248ffc346f4f4f0d747ee1947963613216b06688be0be2e393986fe20dbbb", + "sha256:b315febaebae813326296872fdb4be92ad3ce10d1d742a6b0c49fb619481ed0b", + "sha256:b3591616fa07b15050b2f87e1cdefd06a554382e72866fcc0ab2be9d116486c8", + "sha256:b4018d5f9b6651f9896c7a7c2c9f4652e4eea53f10751c4e7d08a9093ab587ec", + "sha256:d75291912b945a7351d45df682f9644540d564d62115d4a20d45fa17dc2d48f8", + "sha256:dc9bda7d5ced744622f157cc8d8bdd51735dafcecff807e928ff26bdb0ff097d", + "sha256:e3ac2c0375ef498e74b9b4ec56df3c88be43fe56cac465627572dbfb21c4be34", + "sha256:e4c4a7636ffc47b7141864f1c5e7d649f42c54e49da2dd3cceb1c5f5d29bfc85", + "sha256:ed29ea0b9a372c5188cdb2ad39f937900a10fb5478dc077283bf86eeac678ef1", + "sha256:f40ba362fefc11d6bea4403f070078d60053ed422255bd838cd86a40674364c9", + "sha256:f4cb67215c10d4657e320037109939b1c1d2fd70ca3d76301992f89fe2edb1f1", + "sha256:f7929a516125f62399d6e8e026129c8835f6c5a3aab88c3fff1a05ee8feb840d", + "sha256:fd331866628d18223a4265371fd255774affd86244fc307ef66eaf00de0633d5", + "sha256:feb861a10b6c3bb00701063b37e4afc754f8217f0f09c42280586bd6ac712b5c" ], "index": "pypi", - "version": "==5.9.1" + "version": "==5.9.2" }, "pyasn1": { "hashes": [ @@ -632,44 +648,45 @@ }, "pydantic": { "hashes": [ - "sha256:1061c6ee6204f4f5a27133126854948e3b3d51fcc16ead2e5d04378c199b2f44", - "sha256:19b5686387ea0d1ea52ecc4cffb71abb21702c5e5b2ac626fd4dbaa0834aa49d", - "sha256:2bd446bdb7755c3a94e56d7bdfd3ee92396070efa8ef3a34fab9579fe6aa1d84", - "sha256:328558c9f2eed77bd8fffad3cef39dbbe3edc7044517f4625a769d45d4cf7555", - "sha256:32e0b4fb13ad4db4058a7c3c80e2569adbd810c25e6ca3bbd8b2a9cc2cc871d7", - "sha256:3ee0d69b2a5b341fc7927e92cae7ddcfd95e624dfc4870b32a85568bd65e6131", - "sha256:4aafd4e55e8ad5bd1b19572ea2df546ccace7945853832bb99422a79c70ce9b8", - "sha256:4b3946f87e5cef3ba2e7bd3a4eb5a20385fe36521d6cc1ebf3c08a6697c6cfb3", - "sha256:4de71c718c9756d679420c69f216776c2e977459f77e8f679a4a961dc7304a56", - "sha256:5565a49effe38d51882cb7bac18bda013cdb34d80ac336428e8908f0b72499b0", - "sha256:5803ad846cdd1ed0d97eb00292b870c29c1f03732a010e66908ff48a762f20e4", - "sha256:5da164119602212a3fe7e3bc08911a89db4710ae51444b4224c2382fd09ad453", - "sha256:615661bfc37e82ac677543704437ff737418e4ea04bef9cf11c6d27346606044", - "sha256:78a4d6bdfd116a559aeec9a4cfe77dda62acc6233f8b56a716edad2651023e5e", - "sha256:7d0f183b305629765910eaad707800d2f47c6ac5bcfb8c6397abdc30b69eeb15", - "sha256:7ead3cd020d526f75b4188e0a8d71c0dbbe1b4b6b5dc0ea775a93aca16256aeb", - "sha256:84d76ecc908d917f4684b354a39fd885d69dd0491be175f3465fe4b59811c001", - "sha256:8cb0bc509bfb71305d7a59d00163d5f9fc4530f0881ea32c74ff4f74c85f3d3d", - "sha256:91089b2e281713f3893cd01d8e576771cd5bfdfbff5d0ed95969f47ef6d676c3", - "sha256:9c9e04a6cdb7a363d7cb3ccf0efea51e0abb48e180c0d31dca8d247967d85c6e", - "sha256:a8c5360a0297a713b4123608a7909e6869e1b56d0e96eb0d792c27585d40757f", - "sha256:afacf6d2a41ed91fc631bade88b1d319c51ab5418870802cedb590b709c5ae3c", - "sha256:b34ba24f3e2d0b39b43f0ca62008f7ba962cff51efa56e64ee25c4af6eed987b", - "sha256:bd67cb2c2d9602ad159389c29e4ca964b86fa2f35c2faef54c3eb28b4efd36c8", - "sha256:c0f5e142ef8217019e3eef6ae1b6b55f09a7a15972958d44fbd228214cede567", - "sha256:cdb4272678db803ddf94caa4f94f8672e9a46bae4a44f167095e4d06fec12979", - "sha256:d70916235d478404a3fa8c997b003b5f33aeac4686ac1baa767234a0f8ac2326", - "sha256:d8ce3fb0841763a89322ea0432f1f59a2d3feae07a63ea2c958b2315e1ae8adb", - "sha256:e0b214e57623a535936005797567231a12d0da0c29711eb3514bc2b3cd008d0f", - "sha256:e631c70c9280e3129f071635b81207cad85e6c08e253539467e4ead0e5b219aa", - "sha256:e78578f0c7481c850d1c969aca9a65405887003484d24f6110458fb02cca7747", - "sha256:f0ca86b525264daa5f6b192f216a0d1e860b7383e3da1c65a1908f9c02f42801", - "sha256:f1a68f4f65a9ee64b6ccccb5bf7e17db07caebd2730109cb8a95863cfa9c4e55", - "sha256:fafe841be1103f340a24977f61dee76172e4ae5f647ab9e7fd1e1fca51524f08", - "sha256:ff68fc85355532ea77559ede81f35fff79a6a5543477e168ab3a381887caea76" + "sha256:05e00dbebbe810b33c7a7362f231893183bcc4251f3f2ff991c31d5c08240c42", + "sha256:06094d18dd5e6f2bbf93efa54991c3240964bb663b87729ac340eb5014310624", + "sha256:0b959f4d8211fc964772b595ebb25f7652da3f22322c007b6fed26846a40685e", + "sha256:19b3b9ccf97af2b7519c42032441a891a5e05c68368f40865a90eb88833c2559", + "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709", + "sha256:1ee433e274268a4b0c8fde7ad9d58ecba12b069a033ecc4645bb6303c062d2e9", + "sha256:216f3bcbf19c726b1cc22b099dd409aa371f55c08800bcea4c44c8f74b73478d", + "sha256:2d0567e60eb01bccda3a4df01df677adf6b437958d35c12a3ac3e0f078b0ee52", + "sha256:2e05aed07fa02231dbf03d0adb1be1d79cabb09025dd45aa094aa8b4e7b9dcda", + "sha256:352aedb1d71b8b0736c6d56ad2bd34c6982720644b0624462059ab29bd6e5912", + "sha256:355639d9afc76bcb9b0c3000ddcd08472ae75318a6eb67a15866b87e2efa168c", + "sha256:37c90345ec7dd2f1bcef82ce49b6235b40f282b94d3eec47e801baf864d15525", + "sha256:4b8795290deaae348c4eba0cebb196e1c6b98bdbe7f50b2d0d9a4a99716342fe", + "sha256:5760e164b807a48a8f25f8aa1a6d857e6ce62e7ec83ea5d5c5a802eac81bad41", + "sha256:6eb843dcc411b6a2237a694f5e1d649fc66c6064d02b204a7e9d194dff81eb4b", + "sha256:7b5ba54d026c2bd2cb769d3468885f23f43710f651688e91f5fb1edcf0ee9283", + "sha256:7c2abc4393dea97a4ccbb4ec7d8658d4e22c4765b7b9b9445588f16c71ad9965", + "sha256:81a7b66c3f499108b448f3f004801fcd7d7165fb4200acb03f1c2402da73ce4c", + "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410", + "sha256:9300fcbebf85f6339a02c6994b2eb3ff1b9c8c14f502058b5bf349d42447dcf5", + "sha256:9cabf4a7f05a776e7793e72793cd92cc865ea0e83a819f9ae4ecccb1b8aa6116", + "sha256:a1f5a63a6dfe19d719b1b6e6106561869d2efaca6167f84f5ab9347887d78b98", + "sha256:a4c805731c33a8db4b6ace45ce440c4ef5336e712508b4d9e1aafa617dc9907f", + "sha256:ae544c47bec47a86bc7d350f965d8b15540e27e5aa4f55170ac6a75e5f73b644", + "sha256:b97890e56a694486f772d36efd2ba31612739bc6f3caeee50e9e7e3ebd2fdd13", + "sha256:bb6ad4489af1bac6955d38ebcb95079a836af31e4c4f74aba1ca05bb9f6027bd", + "sha256:bedf309630209e78582ffacda64a21f96f3ed2e51fbf3962d4d488e503420254", + "sha256:c1ba1afb396148bbc70e9eaa8c06c1716fdddabaf86e7027c5988bae2a829ab6", + "sha256:c33602f93bfb67779f9c507e4d69451664524389546bacfe1bee13cae6dc7488", + "sha256:c4aac8e7103bf598373208f6299fa9a5cfd1fc571f2d40bf1dd1955a63d6eeb5", + "sha256:c6f981882aea41e021f72779ce2a4e87267458cc4d39ea990729e21ef18f0f8c", + "sha256:cc78cc83110d2f275ec1970e7a831f4e371ee92405332ebfe9860a715f8336e1", + "sha256:d49f3db871575e0426b12e2f32fdb25e579dea16486a26e5a0474af87cb1ab0a", + "sha256:dd3f9a40c16daf323cf913593083698caee97df2804aa36c4b3175d5ac1b92a2", + "sha256:e0bedafe4bc165ad0a56ac0bd7695df25c50f76961da29c050712596cf092d6d", + "sha256:e9069e1b01525a96e6ff49e25876d90d5a563bc31c658289a8772ae186552236" ], "index": "pypi", - "version": "==1.9.2" + "version": "==1.10.2" }, "pyinstaller": { "hashes": [ @@ -690,11 +707,11 @@ }, "pyinstaller-hooks-contrib": { "hashes": [ - "sha256:c4210fc50282c9c6a918e485e0bfae9405592390508e3be9fde19acc2213da56", - "sha256:e46f099934dd4577fb1ddcf37a99fa04027c92f8f5291c8802f326345988d001" + "sha256:d1dd6ea059dc30e77813cc12a5efa8b1d228e7da8f5b884fe11775f946db1784", + "sha256:e5edd4094175e78c178ef987b61be19efff6caa23d266ade456fc753e847f62e" ], "markers": "python_version >= '3.7'", - "version": "==2022.8" + "version": "==2022.10" }, "pymssql": { "hashes": [ @@ -795,49 +812,29 @@ }, "pyspnego": { "hashes": [ - "sha256:1ee612f20c843365fbc6cf7f95c526b4ee8795281641a9bb87083622a2f87939", - "sha256:284ca7a6218344bb90aeae02fb1d2ed73e5c991d6e4c16c0df404aeab5eb58a3", - "sha256:416fd2d67e82b44ba3d2d9062485056e4dde3c141630170e9190379d6b19972a", - "sha256:4c1be83e0aca12d64f5eec638259c77eaa8bf552c89ac69f0af2322a3be9afeb", - "sha256:4d1ea987b9c2539457235793014e0d9c5e4766da9e4e028d4b6b596cfbe53828", - "sha256:725df2030e5d1b1155bb696eca3d684f403164da8e6a6b0bee3eb02f8748f72b", - "sha256:7320539f45da463029e12f3742102085d2a0343bfe77ac550c11d2fdac1d34f5", - "sha256:77b86002082f278c3f5935d8b428a0d0659ea709e305537294ba95fc49907339", - "sha256:aa93d94775d01bf70d16840426d1ddd58c11a6a71c4d0d1d7e529ad541fa0a60", - "sha256:c2abca03b6d3c71d7ec9678c7b2220d99d9a29ef204b4c52549080169e586310", - "sha256:e6645107f200fb7bf6698512d04ea0790b292028861ce169eb97e5ad8eba14ed", - "sha256:f4784d9f8e9c50a36109d715a140150add1990fce16805a56217e8ccaf69d234" + "sha256:15cd6d3fc4d18b4b7f80259bfab1580c87dc9677d47e7cf801dad71dc23d1afc", + "sha256:210a2248060a2d789a333f7553a1a478d21812f675c507393169143cbf038d9b", + "sha256:4e967f29c099c196cbf4622587cd27e8c61f20adf78b1d3007b72596e60c9f23", + "sha256:4fab51afb757be21d543ddf78aaeb83db600a7e7daec773568db90d4b7499a2c", + "sha256:53d30afbef1255cb1a8930c14604184b07f989b6ac295a1397eac8c27fd59d8b", + "sha256:838f875ee55004a274f6470460e62b7713237ae8b66a02680a2f31e43b3b5387", + "sha256:b78a3370ace76209a52dc7816636a8c8437e323637eefe86a2193cc4ec352b3b", + "sha256:e08709c4e0838bf37d4ef8ceff2163a51abe2b071e285bb5774de5b73eab214f", + "sha256:ea8570c5363e5dd359aaf599eac6f70116e0ada734ebe557e17cc608c8bb93fc", + "sha256:fa2946ba5059f79d13cb8c47e83474de55569c16ed8f953cc47a24dda6f38f57" ], - "markers": "python_version >= '3.6'", - "version": "==0.5.3" + "markers": "python_version >= '3.7'", + "version": "==0.6.0" }, "pywin32": { - "hashes": [ - "sha256:25746d841201fd9f96b648a248f731c1dec851c9a08b8e33da8b56148e4c65cc", - "sha256:30c53d6ce44c12a316a06c153ea74152d3b1342610f1b99d40ba2795e5af0269", - "sha256:3c7bacf5e24298c86314f03fa20e16558a4e4138fc34615d7de4070c23e65af3", - "sha256:4f32145913a2447736dad62495199a8e280a77a0ca662daa2332acf849f0be48", - "sha256:7ffa0c0fa4ae4077e8b8aa73800540ef8c24530057768c3ac57c609f99a14fd4", - "sha256:94037b5259701988954931333aafd39cf897e990852115656b014ce72e052e96", - "sha256:bb2ea2aa81e96eee6a6b79d87e1d1648d3f8b87f9a64499e0b92b30d141e76df", - "sha256:be253e7b14bc601718f014d2832e4c18a5b023cbe72db826da63df76b77507a1", - "sha256:cbbe34dad39bdbaa2889a424d28752f1b4971939b14b1bb48cbf0182a3bcfc43", - "sha256:d24a3382f013b21aa24a5cfbfad5a2cd9926610c0affde3e8ab5b3d7dbcf4ac9", - "sha256:d3ee45adff48e0551d1aa60d2ec066fec006083b791f5c3527c40cd8aefac71f", - "sha256:de9827c23321dcf43d2f288f09f3b6d772fee11e809015bdae9e69fe13213988", - "sha256:ead865a2e179b30fb717831f73cf4373401fc62fbc3455a0889a7ddac848f83e", - "sha256:f64c0377cf01b61bd5e76c25e1480ca8ab3b73f0c4add50538d332afdf8f69c5" - ], - "index": "pypi", - "markers": "sys_platform == 'win32'", - "version": "==304" + "sys_platform": "== 'win32'", + "version": "*" }, "pywin32-ctypes": { "hashes": [ "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942", "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98" ], - "index": "pypi", "markers": "sys_platform == 'win32'", "version": "==0.2.0" }, @@ -858,11 +855,11 @@ }, "setuptools": { "hashes": [ - "sha256:7c7854ee1429a240090297628dc9f75b35318d193537968e2dc14010ee2f5bca", - "sha256:dc2662692f47d99cb8ae15a784529adeed535bcd7c277fee0beccf961522baf6" + "sha256:2e24e0bec025f035a2e72cdd1961119f557d78ad331bb00ff82efb2ab8da8e82", + "sha256:7732871f4f7fa58fb6bdcaeadb0161b2bd046c85905dbaa066bdcbcc81953b57" ], "markers": "python_version >= '3.7'", - "version": "==63.4.1" + "version": "==65.3.0" }, "six": { "hashes": [ @@ -874,11 +871,11 @@ }, "tqdm": { "hashes": [ - "sha256:40be55d30e200777a307a7585aee69e4eabb46b4ec6a4b4a5f2d9f11e7d5408d", - "sha256:74a2cdefe14d11442cedf3ba4e21a3b84ff9a2dbdc6cfae2c34addb2a14a5ea6" + "sha256:5f4f682a004951c1b450bc753c710e9280c5746ce6ffedee253ddbcbf54cf1e4", + "sha256:6fee160d6ffcd1b1c68c65f14c829c22832bc401726335ce92c52d395944a6a1" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==4.64.0" + "version": "==4.64.1" }, "twisted": { "extras": [ @@ -891,24 +888,6 @@ "markers": "python_full_version >= '3.6.7'", "version": "==22.4.0" }, - "twisted-iocpsupport": { - "hashes": [ - "sha256:306becd6e22ab6e8e4f36b6bdafd9c92e867c98a5ce517b27fdd27760ee7ae41", - "sha256:3c61742cb0bc6c1ac117a7e5f422c129832f0c295af49e01d8a6066df8cfc04d", - "sha256:72068b206ee809c9c596b57b5287259ea41ddb4774d86725b19f35bf56aa32a9", - "sha256:7d972cfa8439bdcb35a7be78b7ef86d73b34b808c74be56dfa785c8a93b851bf", - "sha256:81b3abe3527b367da0220482820cb12a16c661672b7bcfcde328902890d63323", - "sha256:851b3735ca7e8102e661872390e3bce88f8901bece95c25a0c8bb9ecb8a23d32", - "sha256:985c06a33f5c0dae92c71a036d1ea63872ee86a21dd9b01e1f287486f15524b4", - "sha256:9dbb8823b49f06d4de52721b47de4d3b3026064ef4788ce62b1a21c57c3fff6f", - "sha256:b435857b9efcbfc12f8c326ef0383f26416272260455bbca2cd8d8eca470c546", - "sha256:b76b4eed9b27fd63ddb0877efdd2d15835fdcb6baa745cb85b66e5d016ac2878", - "sha256:b9fed67cf0f951573f06d560ac2f10f2a4bbdc6697770113a2fc396ea2cb2565", - "sha256:bf4133139d77fc706d8f572e6b7d82871d82ec7ef25d685c2351bdacfb701415" - ], - "markers": "platform_system == 'Windows'", - "version": "==1.0.2" - }, "typing-extensions": { "hashes": [ "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02", @@ -919,11 +898,11 @@ }, "unicrypto": { "hashes": [ - "sha256:0487f9dd9009c326ee9531a79412ae18ad673425a1c800d64947b96fdeb04cdf", - "sha256:fab49ee41926bb31be49552aa135f7aedc04436b49c8fe326d7b6a823810575e" + "sha256:4d1de0f0a379bb4c213302ae61d927eb8f98179bde9a0ffb8e120998a0c882a6", + "sha256:9d5dd858ad5ad608fa524987b17e8855d64d6d2450ca0ca11638f4d92fc6c80b" ], "markers": "python_version >= '3.6'", - "version": "==0.0.8" + "version": "==0.0.9" }, "urllib3": { "hashes": [ @@ -942,11 +921,11 @@ }, "werkzeug": { "hashes": [ - "sha256:4d7013ef96fd197d1cdeb03e066c6c5a491ccb44758a5b2b91137319383e5a5a", - "sha256:7e1db6a5ba6b9a8be061e47e900456355b8714c0f238b0313f53afce1a55a79a" + "sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f", + "sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5" ], "markers": "python_version >= '3.7'", - "version": "==2.2.1" + "version": "==2.2.2" }, "winacl": { "hashes": [ @@ -956,14 +935,6 @@ "markers": "python_version >= '3.6'", "version": "==0.1.3" }, - "winsspi": { - "hashes": [ - "sha256:2f5a8d2c4b9f459144426909e26a74e550512e23b6cf9af52c2a00003c7c3fdb", - "sha256:59b7c7595f91414528cfd80c6cfc77ec6f5e4e28185ebd6418f8368ddc7aca82" - ], - "markers": "python_version >= '3.6'", - "version": "==0.0.10" - }, "winsys-3.x": { "hashes": [ "sha256:cef3df1dce2a5a71efa46446e6007ad9f7dbae31e83ffcc2ea3485c00c914cc3" diff --git a/monkey/infection_monkey/network/relay/relay_user_handler.py b/monkey/infection_monkey/network/relay/relay_user_handler.py index 8db32a981..e73049a63 100644 --- a/monkey/infection_monkey/network/relay/relay_user_handler.py +++ b/monkey/infection_monkey/network/relay/relay_user_handler.py @@ -1,16 +1,17 @@ from dataclasses import dataclass from ipaddress import IPv4Address from threading import Lock -from time import time from typing import Dict +from egg_timer import EggTimer + DEFAULT_NEW_CLIENT_TIMEOUT = 3 # Wait up to 3 seconds for potential new clients to connect @dataclass class RelayUser: address: IPv4Address - last_update_time: float + timer: EggTimer class RelayUserHandler: @@ -35,7 +36,8 @@ class RelayUserHandler: if user_address in self._potential_users: del self._potential_users[user_address] - self._relay_users[user_address] = RelayUser(user_address, time()) + timer = EggTimer() + self._relay_users[user_address] = RelayUser(user_address, timer) def add_potential_user(self, user_address: IPv4Address): """ @@ -45,7 +47,9 @@ class RelayUserHandler: that tries to connect to the relay """ with self._lock: - self._potential_users[user_address] = RelayUser(user_address, time()) + timer = EggTimer() + timer.set(self._new_client_timeout) + self._potential_users[user_address] = RelayUser(user_address, timer) def disconnect_user(self, user_address: IPv4Address): """ @@ -61,12 +65,8 @@ class RelayUserHandler: """ Return whether or not we have any potential users. """ - current_time = time() self._potential_users = dict( - filter( - lambda ru: (current_time - ru[1].last_update_time) < self._new_client_timeout, - self._potential_users.items(), - ) + filter(lambda ru: not ru[1].timer.is_expired(), self._potential_users.items()) ) return len(self._potential_users) > 0 From c021d54c05f67685d7eba7989eba363bc62dee5b Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Tue, 6 Sep 2022 14:57:10 +0000 Subject: [PATCH 042/175] Agent: Reduce relay shutdown responsiveness --- monkey/infection_monkey/tcp_relay.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/tcp_relay.py b/monkey/infection_monkey/tcp_relay.py index a79adf334..abe7ae56a 100644 --- a/monkey/infection_monkey/tcp_relay.py +++ b/monkey/infection_monkey/tcp_relay.py @@ -41,11 +41,11 @@ class TCPRelay(Thread): Blocks until the users disconnect or the timeout has elapsed. """ while self._user_handler.has_potential_users(): - sleep(0.01) + sleep(0.5) def _wait_for_pipes_to_close(self): """ Blocks until the pipes have closed. """ while self._pipe_spawner.has_open_pipes(): - sleep(0.01) + sleep(0.5) From 14de4db9faa6120515d5f63e753964f7918073af Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Tue, 6 Sep 2022 16:25:37 +0000 Subject: [PATCH 043/175] UT: Use more specific assert in test_connection_spawns_pipe --- .../network/relay/test_relay_connection_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/tests/unit_tests/infection_monkey/network/relay/test_relay_connection_handler.py b/monkey/tests/unit_tests/infection_monkey/network/relay/test_relay_connection_handler.py index f82432dae..92aff10fc 100644 --- a/monkey/tests/unit_tests/infection_monkey/network/relay/test_relay_connection_handler.py +++ b/monkey/tests/unit_tests/infection_monkey/network/relay/test_relay_connection_handler.py @@ -53,7 +53,7 @@ def test_connection_spawns_pipe(pipe_spawner, relay_user_handler, data_socket): connection_handler.handle_new_connection(data_socket) - assert pipe_spawner.spawn_pipe.called + pipe_spawner.spawn_pipe.assert_called_once_with(data_socket) def test_connection_adds_user(pipe_spawner, relay_user_handler, data_socket): From c601f2214a015632a15d77d3f653cf3922299906 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Tue, 6 Sep 2022 16:39:41 +0000 Subject: [PATCH 044/175] Agent: Use del_key instead of del --- monkey/common/utils/code_utils.py | 4 ++-- monkey/infection_monkey/network/relay/relay_user_handler.py | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/monkey/common/utils/code_utils.py b/monkey/common/utils/code_utils.py index 52fdcbaac..4ddff97e2 100644 --- a/monkey/common/utils/code_utils.py +++ b/monkey/common/utils/code_utils.py @@ -1,6 +1,6 @@ import queue from collections.abc import MutableSequence -from typing import Any, List, MutableMapping, TypeVar +from typing import Any, Dict, List, MutableMapping, Type, TypeVar T = TypeVar("T") @@ -16,7 +16,7 @@ class abstractstatic(staticmethod): class Singleton(type): - _instances = {} + _instances: Dict[Type, type] = {} def __call__(cls, *args, **kwargs): if cls not in cls._instances: diff --git a/monkey/infection_monkey/network/relay/relay_user_handler.py b/monkey/infection_monkey/network/relay/relay_user_handler.py index e73049a63..6af2c841e 100644 --- a/monkey/infection_monkey/network/relay/relay_user_handler.py +++ b/monkey/infection_monkey/network/relay/relay_user_handler.py @@ -5,6 +5,8 @@ from typing import Dict from egg_timer import EggTimer +from monkey.common.utils.code_utils import del_key + DEFAULT_NEW_CLIENT_TIMEOUT = 3 # Wait up to 3 seconds for potential new clients to connect @@ -34,7 +36,7 @@ class RelayUserHandler: with self._lock: if user_address in self._potential_users: - del self._potential_users[user_address] + del_key(self._potential_users, user_address) timer = EggTimer() self._relay_users[user_address] = RelayUser(user_address, timer) @@ -59,7 +61,7 @@ class RelayUserHandler: """ with self._lock: if user_address in self._relay_users: - del self._relay_users[user_address] + del_key(self._relay_users, user_address) def has_potential_users(self) -> bool: """ From 83cc5fc336308fdc4942806716b64da216c061ad Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 6 Sep 2022 14:03:36 -0400 Subject: [PATCH 045/175] Agent: Fix Pipfile.lock `pipenv install` was run on Linux without `--keep-outdated`, which removed Windows-only dependencies. This commit fixes that. --- monkey/infection_monkey/Pipfile.lock | 317 +++++++++++++++------------ 1 file changed, 181 insertions(+), 136 deletions(-) diff --git a/monkey/infection_monkey/Pipfile.lock b/monkey/infection_monkey/Pipfile.lock index 25947e8e6..cb73432a9 100644 --- a/monkey/infection_monkey/Pipfile.lock +++ b/monkey/infection_monkey/Pipfile.lock @@ -18,11 +18,11 @@ "default": { "aiosmb": { "hashes": [ - "sha256:2668d63ae6e6ca30a999696c444d4afc349f85a872a204994394aa6abbf5673d", - "sha256:b0b0e7068e757b8d3a8e4be2ebf2b499933d9fa7853bc3a8e198d6a57dc77131" + "sha256:0afa901093f0ad91d0b8421dec66c80bd2e9cb237a8da405984413a5d7475398", + "sha256:0e98390ba00fdc4190e698f184dfcf72b02b592cdfe9274e03cc7316ac4ee368" ], "markers": "python_version >= '3.7'", - "version": "==0.4.0" + "version": "==0.3.8" }, "aiowinreg": { "hashes": [ @@ -56,11 +56,11 @@ }, "asysocks": { "hashes": [ - "sha256:8f4516088ebec7f08d8c549e5e75e7a86b41e6043af920ba4895cf3f6c654150", - "sha256:d4c619c9d2e8be0cbdd21fa635a7eaf5886809edc948e2f76625b33b3cccfc47" + "sha256:23d5fcfae71a75826c3ed787bd9b1bc3b189ec37658961bce83c9e99455e354c", + "sha256:731eda25d41783c5243153d3cb4f9357fef337c7317135488afab9ecd6b7f1a1" ], "markers": "python_version >= '3.6'", - "version": "==0.2.1" + "version": "==0.1.7" }, "attrs": { "hashes": [ @@ -79,21 +79,20 @@ }, "bcrypt": { "hashes": [ - "sha256:0b0f0c7141622a31e9734b7f649451147c04ebb5122327ac0bd23744df84be90", - "sha256:1c3334446fac200499e8bc04a530ce3cf0b3d7151e0e4ac5c0dddd3d95e97843", - "sha256:2d0dd19aad87e4ab882ef1d12df505f4c52b28b69666ce83c528f42c07379227", - "sha256:594780b364fb45f2634c46ec8d3e61c1c0f1811c4f2da60e8eb15594ecbf93ed", - "sha256:7c7dd6c1f05bf89e65261d97ac3a6520f34c2acb369afb57e3ea4449be6ff8fd", - "sha256:845b1daf4df2dd94d2fdbc9454953ca9dd0e12970a0bfc9f3dcc6faea3fa96e4", - "sha256:8780e69f9deec9d60f947b169507d2c9816e4f11548f1f7ebee2af38b9b22ae4", - "sha256:bf413f2a9b0a2950fc750998899013f2e718d20fa4a58b85ca50b6df5ed1bbf9", - "sha256:bfb67f6a6c72dfb0a02f3df51550aa1862708e55128b22543e2b42c74f3620d7", - "sha256:c59c170fc9225faad04dde1ba61d85b413946e8ce2e5f5f5ff30dfd67283f319", - "sha256:dc6ec3dc19b1c193b2f7cf279d3e32e7caf447532fbcb7af0906fe4398900c33", - "sha256:ede0f506554571c8eda80db22b83c139303ec6b595b8f60c4c8157bdd0bdee36" + "sha256:2b02d6bfc6336d1094276f3f588aa1225a598e27f8e3388f4db9948cb707b521", + "sha256:433c410c2177057705da2a9f2cd01dd157493b2a7ac14c8593a16b3dab6b6bfb", + "sha256:4e029cef560967fb0cf4a802bcf4d562d3d6b4b1bf81de5ec1abbe0f1adb027e", + "sha256:61bae49580dce88095d669226d5076d0b9d927754cedbdf76c6c9f5099ad6f26", + "sha256:6d2cb9d969bfca5bc08e45864137276e4c3d3d7de2b162171def3d188bf9d34a", + "sha256:7180d98a96f00b1050e93f5b0f556e658605dd9f524d0b0e68ae7944673f525e", + "sha256:7d9ba2e41e330d2af4af6b1b6ec9e6128e91343d0b4afb9282e54e5508f31baa", + "sha256:7ff2069240c6bbe49109fe84ca80508773a904f5a8cb960e02a977f7f519b129", + "sha256:88273d806ab3a50d06bc6a2fc7c87d737dd669b76ad955f449c43095389bc8fb", + "sha256:a2c46100e315c3a5b90fdc53e429c006c5f962529bc27e1dfd656292c20ccc40", + "sha256:cd43303d6b8a165c29ec6756afd169faba9396a9472cdff753fe9f19b96ce2fa" ], "markers": "python_version >= '3.6'", - "version": "==4.0.0" + "version": "==3.2.2" }, "certifi": { "hashes": [ @@ -182,11 +181,11 @@ }, "charset-normalizer": { "hashes": [ - "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845", - "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" + "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5", + "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413" ], "markers": "python_version >= '3.6'", - "version": "==2.1.1" + "version": "==2.1.0" }, "click": { "hashes": [ @@ -257,11 +256,11 @@ }, "flask": { "hashes": [ - "sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b", - "sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526" + "sha256:3c604c48c3d5b4c63e72134044c0b4fe90ff01ef65280b9fe2d38c8860d99fe5", + "sha256:9c2b81b9b1edcc835af72d600f1955e713a065e7cb41d7e51ee762b449d9c65d" ], "markers": "python_version >= '3.7'", - "version": "==2.2.2" + "version": "==2.2.1" }, "future": { "hashes": [ @@ -405,11 +404,11 @@ }, "marshmallow": { "hashes": [ - "sha256:1172ce82765bf26c24a3f9299ed6dbeeca4d213f638eaa39a37772656d7ce408", - "sha256:48e2d88d4ab431ad5a17c25556d9da529ea6e966876f2a38d274082e270287f0" + "sha256:00040ab5ea0c608e8787137627a8efae97fabd60552a05dc889c888f814e75eb", + "sha256:635fb65a3285a31a30f276f30e958070f5214c7196202caa5c7ecf28f5274bc7" ], "index": "pypi", - "version": "==3.17.1" + "version": "==3.17.0" }, "marshmallow-enum": { "hashes": [ @@ -429,19 +428,19 @@ }, "minikerberos": { "hashes": [ - "sha256:c05cfcd846b1973b2b0d501e9e9fa2a263543d7762b052fc803fc1de849286a3", - "sha256:e912eb4bea899e1875707e7998001ed1047e1b32d5d7bf74d8b6137acf9614d3" + "sha256:3c383f67ebcf6f28105ed54f623a6a5c677a24e3f0c9ad69ed453f77e569d714", + "sha256:789f802263fa1882f701b123f6eec048b45cd731bf1b528870005daf07402047" ], "markers": "python_version >= '3.6'", - "version": "==0.3.1" + "version": "==0.2.20" }, "msldap": { "hashes": [ - "sha256:236eacd04b0d2886e71b2890ec6c67fc626e1b9812c93a0fe21e998697415927", - "sha256:ccb5c1f40de165141931659eb71d4bbad326665aaff7bf23dd0dccb410dfa78c" + "sha256:ac8174ed7e0162eb64b3e9dfeff13b2e1021612d0a4b2cfc6b8e5bed7c00ffe0", + "sha256:e2c22a6e396b4d7d65d73863ed44612120e8e2570ff895b5421ddf6a350085bb" ], "markers": "python_version >= '3.7'", - "version": "==0.4.0" + "version": "==0.3.40" }, "netifaces": { "hashes": [ @@ -517,54 +516,55 @@ "hashes": [ "sha256:a5488a3dd1fd021ce33f969780b88fe0f7eebb76eb20996d7318f307612a045b" ], + "index": "pypi", "markers": "sys_platform == 'win32'", "version": "==2022.5.30" }, "prompt-toolkit": { "hashes": [ - "sha256:9696f386133df0fc8ca5af4895afe5d78f5fcfe5258111c2a79a1c3e41ffa96d", - "sha256:9ada952c9d1787f52ff6d5f3484d0b4df8952787c087edf6a1f7c2cb1ea88148" + "sha256:859b283c50bde45f5f97829f77a4674d1c1fcd88539364f1b28a37805cfd89c0", + "sha256:d8916d3f62a7b67ab353a952ce4ced6a1d2587dfe9ef8ebc30dd7c386751f289" ], "markers": "python_full_version >= '3.6.2'", - "version": "==3.0.31" + "version": "==3.0.30" }, "psutil": { "hashes": [ - "sha256:14b29f581b5edab1f133563272a6011925401804d52d603c5c606936b49c8b97", - "sha256:256098b4f6ffea6441eb54ab3eb64db9ecef18f6a80d7ba91549195d55420f84", - "sha256:39ec06dc6c934fb53df10c1672e299145ce609ff0611b569e75a88f313634969", - "sha256:404f4816c16a2fcc4eaa36d7eb49a66df2d083e829d3e39ee8759a411dbc9ecf", - "sha256:42638876b7f5ef43cef8dcf640d3401b27a51ee3fa137cb2aa2e72e188414c32", - "sha256:4642fd93785a29353d6917a23e2ac6177308ef5e8be5cc17008d885cb9f70f12", - "sha256:4fb54941aac044a61db9d8eb56fc5bee207db3bc58645d657249030e15ba3727", - "sha256:561dec454853846d1dd0247b44c2e66a0a0c490f937086930ec4b8f83bf44f06", - "sha256:5d39e3a2d5c40efa977c9a8dd4f679763c43c6c255b1340a56489955dbca767c", - "sha256:614337922702e9be37a39954d67fdb9e855981624d8011a9927b8f2d3c9625d9", - "sha256:67b33f27fc0427483b61563a16c90d9f3b547eeb7af0ef1b9fe024cdc9b3a6ea", - "sha256:68b35cbff92d1f7103d8f1db77c977e72f49fcefae3d3d2b91c76b0e7aef48b8", - "sha256:7cbb795dcd8ed8fd238bc9e9f64ab188f3f4096d2e811b5a82da53d164b84c3f", - "sha256:8f024fbb26c8daf5d70287bb3edfafa22283c255287cf523c5d81721e8e5d82c", - "sha256:91aa0dac0c64688667b4285fa29354acfb3e834e1fd98b535b9986c883c2ce1d", - "sha256:94e621c6a4ddb2573d4d30cba074f6d1aa0186645917df42c811c473dd22b339", - "sha256:9770c1d25aee91417eba7869139d629d6328a9422ce1cdd112bd56377ca98444", - "sha256:b1928b9bf478d31fdffdb57101d18f9b70ed4e9b0e41af751851813547b2a9ab", - "sha256:b2f248ffc346f4f4f0d747ee1947963613216b06688be0be2e393986fe20dbbb", - "sha256:b315febaebae813326296872fdb4be92ad3ce10d1d742a6b0c49fb619481ed0b", - "sha256:b3591616fa07b15050b2f87e1cdefd06a554382e72866fcc0ab2be9d116486c8", - "sha256:b4018d5f9b6651f9896c7a7c2c9f4652e4eea53f10751c4e7d08a9093ab587ec", - "sha256:d75291912b945a7351d45df682f9644540d564d62115d4a20d45fa17dc2d48f8", - "sha256:dc9bda7d5ced744622f157cc8d8bdd51735dafcecff807e928ff26bdb0ff097d", - "sha256:e3ac2c0375ef498e74b9b4ec56df3c88be43fe56cac465627572dbfb21c4be34", - "sha256:e4c4a7636ffc47b7141864f1c5e7d649f42c54e49da2dd3cceb1c5f5d29bfc85", - "sha256:ed29ea0b9a372c5188cdb2ad39f937900a10fb5478dc077283bf86eeac678ef1", - "sha256:f40ba362fefc11d6bea4403f070078d60053ed422255bd838cd86a40674364c9", - "sha256:f4cb67215c10d4657e320037109939b1c1d2fd70ca3d76301992f89fe2edb1f1", - "sha256:f7929a516125f62399d6e8e026129c8835f6c5a3aab88c3fff1a05ee8feb840d", - "sha256:fd331866628d18223a4265371fd255774affd86244fc307ef66eaf00de0633d5", - "sha256:feb861a10b6c3bb00701063b37e4afc754f8217f0f09c42280586bd6ac712b5c" + "sha256:068935df39055bf27a29824b95c801c7a5130f118b806eee663cad28dca97685", + "sha256:0904727e0b0a038830b019551cf3204dd48ef5c6868adc776e06e93d615fc5fc", + "sha256:0f15a19a05f39a09327345bc279c1ba4a8cfb0172cc0d3c7f7d16c813b2e7d36", + "sha256:19f36c16012ba9cfc742604df189f2f28d2720e23ff7d1e81602dbe066be9fd1", + "sha256:20b27771b077dcaa0de1de3ad52d22538fe101f9946d6dc7869e6f694f079329", + "sha256:28976df6c64ddd6320d281128817f32c29b539a52bdae5e192537bc338a9ec81", + "sha256:29a442e25fab1f4d05e2655bb1b8ab6887981838d22effa2396d584b740194de", + "sha256:3054e923204b8e9c23a55b23b6df73a8089ae1d075cb0bf711d3e9da1724ded4", + "sha256:32c52611756096ae91f5d1499fe6c53b86f4a9ada147ee42db4991ba1520e574", + "sha256:3a76ad658641172d9c6e593de6fe248ddde825b5866464c3b2ee26c35da9d237", + "sha256:44d1826150d49ffd62035785a9e2c56afcea66e55b43b8b630d7706276e87f22", + "sha256:4b6750a73a9c4a4e689490ccb862d53c7b976a2a35c4e1846d049dcc3f17d83b", + "sha256:56960b9e8edcca1456f8c86a196f0c3d8e3e361320071c93378d41445ffd28b0", + "sha256:57f1819b5d9e95cdfb0c881a8a5b7d542ed0b7c522d575706a80bedc848c8954", + "sha256:58678bbadae12e0db55186dc58f2888839228ac9f41cc7848853539b70490021", + "sha256:645bd4f7bb5b8633803e0b6746ff1628724668681a434482546887d22c7a9537", + "sha256:799759d809c31aab5fe4579e50addf84565e71c1dc9f1c31258f159ff70d3f87", + "sha256:79c9108d9aa7fa6fba6e668b61b82facc067a6b81517cab34d07a84aa89f3df0", + "sha256:91c7ff2a40c373d0cc9121d54bc5f31c4fa09c346528e6a08d1845bce5771ffc", + "sha256:9272167b5f5fbfe16945be3db475b3ce8d792386907e673a209da686176552af", + "sha256:944c4b4b82dc4a1b805329c980f270f170fdc9945464223f2ec8e57563139cf4", + "sha256:a6a11e48cb93a5fa606306493f439b4aa7c56cb03fc9ace7f6bfa21aaf07c453", + "sha256:a8746bfe4e8f659528c5c7e9af5090c5a7d252f32b2e859c584ef7d8efb1e689", + "sha256:abd9246e4cdd5b554a2ddd97c157e292ac11ef3e7af25ac56b08b455c829dca8", + "sha256:b14ee12da9338f5e5b3a3ef7ca58b3cba30f5b66f7662159762932e6d0b8f680", + "sha256:b88f75005586131276634027f4219d06e0561292be8bd6bc7f2f00bdabd63c4e", + "sha256:c7be9d7f5b0d206f0bbc3794b8e16fb7dbc53ec9e40bbe8787c6f2d38efcf6c9", + "sha256:d2d006286fbcb60f0b391741f520862e9b69f4019b4d738a2a45728c7e952f1b", + "sha256:db417f0865f90bdc07fa30e1aadc69b6f4cad7f86324b02aa842034efe8d8c4d", + "sha256:e7e10454cb1ab62cc6ce776e1c135a64045a11ec4c6d254d3f7689c16eb3efd2", + "sha256:f65f9a46d984b8cd9b3750c2bdb419b2996895b005aefa6cbaba9a143b1ce2c5", + "sha256:fea896b54f3a4ae6f790ac1d017101252c93f6fe075d0e7571543510f11d2676" ], "index": "pypi", - "version": "==5.9.2" + "version": "==5.9.1" }, "pyasn1": { "hashes": [ @@ -648,45 +648,44 @@ }, "pydantic": { "hashes": [ - "sha256:05e00dbebbe810b33c7a7362f231893183bcc4251f3f2ff991c31d5c08240c42", - "sha256:06094d18dd5e6f2bbf93efa54991c3240964bb663b87729ac340eb5014310624", - "sha256:0b959f4d8211fc964772b595ebb25f7652da3f22322c007b6fed26846a40685e", - "sha256:19b3b9ccf97af2b7519c42032441a891a5e05c68368f40865a90eb88833c2559", - "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709", - "sha256:1ee433e274268a4b0c8fde7ad9d58ecba12b069a033ecc4645bb6303c062d2e9", - "sha256:216f3bcbf19c726b1cc22b099dd409aa371f55c08800bcea4c44c8f74b73478d", - "sha256:2d0567e60eb01bccda3a4df01df677adf6b437958d35c12a3ac3e0f078b0ee52", - "sha256:2e05aed07fa02231dbf03d0adb1be1d79cabb09025dd45aa094aa8b4e7b9dcda", - "sha256:352aedb1d71b8b0736c6d56ad2bd34c6982720644b0624462059ab29bd6e5912", - "sha256:355639d9afc76bcb9b0c3000ddcd08472ae75318a6eb67a15866b87e2efa168c", - "sha256:37c90345ec7dd2f1bcef82ce49b6235b40f282b94d3eec47e801baf864d15525", - "sha256:4b8795290deaae348c4eba0cebb196e1c6b98bdbe7f50b2d0d9a4a99716342fe", - "sha256:5760e164b807a48a8f25f8aa1a6d857e6ce62e7ec83ea5d5c5a802eac81bad41", - "sha256:6eb843dcc411b6a2237a694f5e1d649fc66c6064d02b204a7e9d194dff81eb4b", - "sha256:7b5ba54d026c2bd2cb769d3468885f23f43710f651688e91f5fb1edcf0ee9283", - "sha256:7c2abc4393dea97a4ccbb4ec7d8658d4e22c4765b7b9b9445588f16c71ad9965", - "sha256:81a7b66c3f499108b448f3f004801fcd7d7165fb4200acb03f1c2402da73ce4c", - "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410", - "sha256:9300fcbebf85f6339a02c6994b2eb3ff1b9c8c14f502058b5bf349d42447dcf5", - "sha256:9cabf4a7f05a776e7793e72793cd92cc865ea0e83a819f9ae4ecccb1b8aa6116", - "sha256:a1f5a63a6dfe19d719b1b6e6106561869d2efaca6167f84f5ab9347887d78b98", - "sha256:a4c805731c33a8db4b6ace45ce440c4ef5336e712508b4d9e1aafa617dc9907f", - "sha256:ae544c47bec47a86bc7d350f965d8b15540e27e5aa4f55170ac6a75e5f73b644", - "sha256:b97890e56a694486f772d36efd2ba31612739bc6f3caeee50e9e7e3ebd2fdd13", - "sha256:bb6ad4489af1bac6955d38ebcb95079a836af31e4c4f74aba1ca05bb9f6027bd", - "sha256:bedf309630209e78582ffacda64a21f96f3ed2e51fbf3962d4d488e503420254", - "sha256:c1ba1afb396148bbc70e9eaa8c06c1716fdddabaf86e7027c5988bae2a829ab6", - "sha256:c33602f93bfb67779f9c507e4d69451664524389546bacfe1bee13cae6dc7488", - "sha256:c4aac8e7103bf598373208f6299fa9a5cfd1fc571f2d40bf1dd1955a63d6eeb5", - "sha256:c6f981882aea41e021f72779ce2a4e87267458cc4d39ea990729e21ef18f0f8c", - "sha256:cc78cc83110d2f275ec1970e7a831f4e371ee92405332ebfe9860a715f8336e1", - "sha256:d49f3db871575e0426b12e2f32fdb25e579dea16486a26e5a0474af87cb1ab0a", - "sha256:dd3f9a40c16daf323cf913593083698caee97df2804aa36c4b3175d5ac1b92a2", - "sha256:e0bedafe4bc165ad0a56ac0bd7695df25c50f76961da29c050712596cf092d6d", - "sha256:e9069e1b01525a96e6ff49e25876d90d5a563bc31c658289a8772ae186552236" + "sha256:1061c6ee6204f4f5a27133126854948e3b3d51fcc16ead2e5d04378c199b2f44", + "sha256:19b5686387ea0d1ea52ecc4cffb71abb21702c5e5b2ac626fd4dbaa0834aa49d", + "sha256:2bd446bdb7755c3a94e56d7bdfd3ee92396070efa8ef3a34fab9579fe6aa1d84", + "sha256:328558c9f2eed77bd8fffad3cef39dbbe3edc7044517f4625a769d45d4cf7555", + "sha256:32e0b4fb13ad4db4058a7c3c80e2569adbd810c25e6ca3bbd8b2a9cc2cc871d7", + "sha256:3ee0d69b2a5b341fc7927e92cae7ddcfd95e624dfc4870b32a85568bd65e6131", + "sha256:4aafd4e55e8ad5bd1b19572ea2df546ccace7945853832bb99422a79c70ce9b8", + "sha256:4b3946f87e5cef3ba2e7bd3a4eb5a20385fe36521d6cc1ebf3c08a6697c6cfb3", + "sha256:4de71c718c9756d679420c69f216776c2e977459f77e8f679a4a961dc7304a56", + "sha256:5565a49effe38d51882cb7bac18bda013cdb34d80ac336428e8908f0b72499b0", + "sha256:5803ad846cdd1ed0d97eb00292b870c29c1f03732a010e66908ff48a762f20e4", + "sha256:5da164119602212a3fe7e3bc08911a89db4710ae51444b4224c2382fd09ad453", + "sha256:615661bfc37e82ac677543704437ff737418e4ea04bef9cf11c6d27346606044", + "sha256:78a4d6bdfd116a559aeec9a4cfe77dda62acc6233f8b56a716edad2651023e5e", + "sha256:7d0f183b305629765910eaad707800d2f47c6ac5bcfb8c6397abdc30b69eeb15", + "sha256:7ead3cd020d526f75b4188e0a8d71c0dbbe1b4b6b5dc0ea775a93aca16256aeb", + "sha256:84d76ecc908d917f4684b354a39fd885d69dd0491be175f3465fe4b59811c001", + "sha256:8cb0bc509bfb71305d7a59d00163d5f9fc4530f0881ea32c74ff4f74c85f3d3d", + "sha256:91089b2e281713f3893cd01d8e576771cd5bfdfbff5d0ed95969f47ef6d676c3", + "sha256:9c9e04a6cdb7a363d7cb3ccf0efea51e0abb48e180c0d31dca8d247967d85c6e", + "sha256:a8c5360a0297a713b4123608a7909e6869e1b56d0e96eb0d792c27585d40757f", + "sha256:afacf6d2a41ed91fc631bade88b1d319c51ab5418870802cedb590b709c5ae3c", + "sha256:b34ba24f3e2d0b39b43f0ca62008f7ba962cff51efa56e64ee25c4af6eed987b", + "sha256:bd67cb2c2d9602ad159389c29e4ca964b86fa2f35c2faef54c3eb28b4efd36c8", + "sha256:c0f5e142ef8217019e3eef6ae1b6b55f09a7a15972958d44fbd228214cede567", + "sha256:cdb4272678db803ddf94caa4f94f8672e9a46bae4a44f167095e4d06fec12979", + "sha256:d70916235d478404a3fa8c997b003b5f33aeac4686ac1baa767234a0f8ac2326", + "sha256:d8ce3fb0841763a89322ea0432f1f59a2d3feae07a63ea2c958b2315e1ae8adb", + "sha256:e0b214e57623a535936005797567231a12d0da0c29711eb3514bc2b3cd008d0f", + "sha256:e631c70c9280e3129f071635b81207cad85e6c08e253539467e4ead0e5b219aa", + "sha256:e78578f0c7481c850d1c969aca9a65405887003484d24f6110458fb02cca7747", + "sha256:f0ca86b525264daa5f6b192f216a0d1e860b7383e3da1c65a1908f9c02f42801", + "sha256:f1a68f4f65a9ee64b6ccccb5bf7e17db07caebd2730109cb8a95863cfa9c4e55", + "sha256:fafe841be1103f340a24977f61dee76172e4ae5f647ab9e7fd1e1fca51524f08", + "sha256:ff68fc85355532ea77559ede81f35fff79a6a5543477e168ab3a381887caea76" ], "index": "pypi", - "version": "==1.10.2" + "version": "==1.9.2" }, "pyinstaller": { "hashes": [ @@ -707,11 +706,11 @@ }, "pyinstaller-hooks-contrib": { "hashes": [ - "sha256:d1dd6ea059dc30e77813cc12a5efa8b1d228e7da8f5b884fe11775f946db1784", - "sha256:e5edd4094175e78c178ef987b61be19efff6caa23d266ade456fc753e847f62e" + "sha256:c4210fc50282c9c6a918e485e0bfae9405592390508e3be9fde19acc2213da56", + "sha256:e46f099934dd4577fb1ddcf37a99fa04027c92f8f5291c8802f326345988d001" ], "markers": "python_version >= '3.7'", - "version": "==2022.10" + "version": "==2022.8" }, "pymssql": { "hashes": [ @@ -812,29 +811,49 @@ }, "pyspnego": { "hashes": [ - "sha256:15cd6d3fc4d18b4b7f80259bfab1580c87dc9677d47e7cf801dad71dc23d1afc", - "sha256:210a2248060a2d789a333f7553a1a478d21812f675c507393169143cbf038d9b", - "sha256:4e967f29c099c196cbf4622587cd27e8c61f20adf78b1d3007b72596e60c9f23", - "sha256:4fab51afb757be21d543ddf78aaeb83db600a7e7daec773568db90d4b7499a2c", - "sha256:53d30afbef1255cb1a8930c14604184b07f989b6ac295a1397eac8c27fd59d8b", - "sha256:838f875ee55004a274f6470460e62b7713237ae8b66a02680a2f31e43b3b5387", - "sha256:b78a3370ace76209a52dc7816636a8c8437e323637eefe86a2193cc4ec352b3b", - "sha256:e08709c4e0838bf37d4ef8ceff2163a51abe2b071e285bb5774de5b73eab214f", - "sha256:ea8570c5363e5dd359aaf599eac6f70116e0ada734ebe557e17cc608c8bb93fc", - "sha256:fa2946ba5059f79d13cb8c47e83474de55569c16ed8f953cc47a24dda6f38f57" + "sha256:1ee612f20c843365fbc6cf7f95c526b4ee8795281641a9bb87083622a2f87939", + "sha256:284ca7a6218344bb90aeae02fb1d2ed73e5c991d6e4c16c0df404aeab5eb58a3", + "sha256:416fd2d67e82b44ba3d2d9062485056e4dde3c141630170e9190379d6b19972a", + "sha256:4c1be83e0aca12d64f5eec638259c77eaa8bf552c89ac69f0af2322a3be9afeb", + "sha256:4d1ea987b9c2539457235793014e0d9c5e4766da9e4e028d4b6b596cfbe53828", + "sha256:725df2030e5d1b1155bb696eca3d684f403164da8e6a6b0bee3eb02f8748f72b", + "sha256:7320539f45da463029e12f3742102085d2a0343bfe77ac550c11d2fdac1d34f5", + "sha256:77b86002082f278c3f5935d8b428a0d0659ea709e305537294ba95fc49907339", + "sha256:aa93d94775d01bf70d16840426d1ddd58c11a6a71c4d0d1d7e529ad541fa0a60", + "sha256:c2abca03b6d3c71d7ec9678c7b2220d99d9a29ef204b4c52549080169e586310", + "sha256:e6645107f200fb7bf6698512d04ea0790b292028861ce169eb97e5ad8eba14ed", + "sha256:f4784d9f8e9c50a36109d715a140150add1990fce16805a56217e8ccaf69d234" ], - "markers": "python_version >= '3.7'", - "version": "==0.6.0" + "markers": "python_version >= '3.6'", + "version": "==0.5.3" }, "pywin32": { - "sys_platform": "== 'win32'", - "version": "*" + "hashes": [ + "sha256:25746d841201fd9f96b648a248f731c1dec851c9a08b8e33da8b56148e4c65cc", + "sha256:30c53d6ce44c12a316a06c153ea74152d3b1342610f1b99d40ba2795e5af0269", + "sha256:3c7bacf5e24298c86314f03fa20e16558a4e4138fc34615d7de4070c23e65af3", + "sha256:4f32145913a2447736dad62495199a8e280a77a0ca662daa2332acf849f0be48", + "sha256:7ffa0c0fa4ae4077e8b8aa73800540ef8c24530057768c3ac57c609f99a14fd4", + "sha256:94037b5259701988954931333aafd39cf897e990852115656b014ce72e052e96", + "sha256:bb2ea2aa81e96eee6a6b79d87e1d1648d3f8b87f9a64499e0b92b30d141e76df", + "sha256:be253e7b14bc601718f014d2832e4c18a5b023cbe72db826da63df76b77507a1", + "sha256:cbbe34dad39bdbaa2889a424d28752f1b4971939b14b1bb48cbf0182a3bcfc43", + "sha256:d24a3382f013b21aa24a5cfbfad5a2cd9926610c0affde3e8ab5b3d7dbcf4ac9", + "sha256:d3ee45adff48e0551d1aa60d2ec066fec006083b791f5c3527c40cd8aefac71f", + "sha256:de9827c23321dcf43d2f288f09f3b6d772fee11e809015bdae9e69fe13213988", + "sha256:ead865a2e179b30fb717831f73cf4373401fc62fbc3455a0889a7ddac848f83e", + "sha256:f64c0377cf01b61bd5e76c25e1480ca8ab3b73f0c4add50538d332afdf8f69c5" + ], + "index": "pypi", + "markers": "sys_platform == 'win32'", + "version": "==304" }, "pywin32-ctypes": { "hashes": [ "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942", "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98" ], + "index": "pypi", "markers": "sys_platform == 'win32'", "version": "==0.2.0" }, @@ -855,11 +874,11 @@ }, "setuptools": { "hashes": [ - "sha256:2e24e0bec025f035a2e72cdd1961119f557d78ad331bb00ff82efb2ab8da8e82", - "sha256:7732871f4f7fa58fb6bdcaeadb0161b2bd046c85905dbaa066bdcbcc81953b57" + "sha256:7c7854ee1429a240090297628dc9f75b35318d193537968e2dc14010ee2f5bca", + "sha256:dc2662692f47d99cb8ae15a784529adeed535bcd7c277fee0beccf961522baf6" ], "markers": "python_version >= '3.7'", - "version": "==65.3.0" + "version": "==63.4.1" }, "six": { "hashes": [ @@ -871,11 +890,11 @@ }, "tqdm": { "hashes": [ - "sha256:5f4f682a004951c1b450bc753c710e9280c5746ce6ffedee253ddbcbf54cf1e4", - "sha256:6fee160d6ffcd1b1c68c65f14c829c22832bc401726335ce92c52d395944a6a1" + "sha256:40be55d30e200777a307a7585aee69e4eabb46b4ec6a4b4a5f2d9f11e7d5408d", + "sha256:74a2cdefe14d11442cedf3ba4e21a3b84ff9a2dbdc6cfae2c34addb2a14a5ea6" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==4.64.1" + "version": "==4.64.0" }, "twisted": { "extras": [ @@ -888,6 +907,24 @@ "markers": "python_full_version >= '3.6.7'", "version": "==22.4.0" }, + "twisted-iocpsupport": { + "hashes": [ + "sha256:306becd6e22ab6e8e4f36b6bdafd9c92e867c98a5ce517b27fdd27760ee7ae41", + "sha256:3c61742cb0bc6c1ac117a7e5f422c129832f0c295af49e01d8a6066df8cfc04d", + "sha256:72068b206ee809c9c596b57b5287259ea41ddb4774d86725b19f35bf56aa32a9", + "sha256:7d972cfa8439bdcb35a7be78b7ef86d73b34b808c74be56dfa785c8a93b851bf", + "sha256:81b3abe3527b367da0220482820cb12a16c661672b7bcfcde328902890d63323", + "sha256:851b3735ca7e8102e661872390e3bce88f8901bece95c25a0c8bb9ecb8a23d32", + "sha256:985c06a33f5c0dae92c71a036d1ea63872ee86a21dd9b01e1f287486f15524b4", + "sha256:9dbb8823b49f06d4de52721b47de4d3b3026064ef4788ce62b1a21c57c3fff6f", + "sha256:b435857b9efcbfc12f8c326ef0383f26416272260455bbca2cd8d8eca470c546", + "sha256:b76b4eed9b27fd63ddb0877efdd2d15835fdcb6baa745cb85b66e5d016ac2878", + "sha256:b9fed67cf0f951573f06d560ac2f10f2a4bbdc6697770113a2fc396ea2cb2565", + "sha256:bf4133139d77fc706d8f572e6b7d82871d82ec7ef25d685c2351bdacfb701415" + ], + "markers": "platform_system == 'Windows'", + "version": "==1.0.2" + }, "typing-extensions": { "hashes": [ "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02", @@ -898,11 +935,11 @@ }, "unicrypto": { "hashes": [ - "sha256:4d1de0f0a379bb4c213302ae61d927eb8f98179bde9a0ffb8e120998a0c882a6", - "sha256:9d5dd858ad5ad608fa524987b17e8855d64d6d2450ca0ca11638f4d92fc6c80b" + "sha256:0487f9dd9009c326ee9531a79412ae18ad673425a1c800d64947b96fdeb04cdf", + "sha256:fab49ee41926bb31be49552aa135f7aedc04436b49c8fe326d7b6a823810575e" ], "markers": "python_version >= '3.6'", - "version": "==0.0.9" + "version": "==0.0.8" }, "urllib3": { "hashes": [ @@ -921,11 +958,11 @@ }, "werkzeug": { "hashes": [ - "sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f", - "sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5" + "sha256:4d7013ef96fd197d1cdeb03e066c6c5a491ccb44758a5b2b91137319383e5a5a", + "sha256:7e1db6a5ba6b9a8be061e47e900456355b8714c0f238b0313f53afce1a55a79a" ], "markers": "python_version >= '3.7'", - "version": "==2.2.2" + "version": "==2.2.1" }, "winacl": { "hashes": [ @@ -935,6 +972,14 @@ "markers": "python_version >= '3.6'", "version": "==0.1.3" }, + "winsspi": { + "hashes": [ + "sha256:2f5a8d2c4b9f459144426909e26a74e550512e23b6cf9af52c2a00003c7c3fdb", + "sha256:59b7c7595f91414528cfd80c6cfc77ec6f5e4e28185ebd6418f8368ddc7aca82" + ], + "markers": "python_version >= '3.6'", + "version": "==0.0.10" + }, "winsys-3.x": { "hashes": [ "sha256:cef3df1dce2a5a71efa46446e6007ad9f7dbae31e83ffcc2ea3485c00c914cc3" From 066947c59f0bee20e028fc5729cf073e7a6d083a Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Tue, 6 Sep 2022 18:03:01 +0000 Subject: [PATCH 046/175] Agent: Remove closed pipes from TCPPipeSpawner --- .../network/relay/sockets_pipe.py | 61 +++++++++++-------- .../network/relay/tcp_pipe_spawner.py | 31 +++++++--- 2 files changed, 60 insertions(+), 32 deletions(-) diff --git a/monkey/infection_monkey/network/relay/sockets_pipe.py b/monkey/infection_monkey/network/relay/sockets_pipe.py index 0b33cb533..ff8e82c04 100644 --- a/monkey/infection_monkey/network/relay/sockets_pipe.py +++ b/monkey/infection_monkey/network/relay/sockets_pipe.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import select from logging import getLogger from threading import Thread @@ -14,37 +16,46 @@ class SocketsPipe(Thread): self, source, dest, + pipe_closed: Callable[[SocketsPipe], None], timeout=SOCKET_READ_TIMEOUT, - client_disconnected: Callable[[str], None] = None, ): self.source = source self.dest = dest self.timeout = timeout super().__init__(name=f"SocketsPipeThread-{self.ident}", daemon=True) - self._client_disconnected = client_disconnected + self._pipe_closed = pipe_closed + + def _pipe(self): + sockets = [self.source, self.dest] + while True: + # TODO: Figure out how to capture when the socket times out. + read_list, _, except_list = select.select(sockets, [], sockets, self.timeout) + if except_list: + raise Exception("select() failed") + + if not read_list: + raise TimeoutError("") + + for r in read_list: + other = self.dest if r is self.source else self.source + data = r.recv(READ_BUFFER_SIZE) + if data: + other.sendall(data) def run(self): - sockets = [self.source, self.dest] - keep_connection = True - while keep_connection: - keep_connection = False - rlist, _, xlist = select.select(sockets, [], sockets, self.timeout) - if xlist: - break - for r in rlist: - other = self.dest if r is self.source else self.source - try: - data = r.recv(READ_BUFFER_SIZE) - except Exception: - break - if data: - try: - other.sendall(data) - except Exception: - break - keep_connection = True + try: + self._pipe() + except Exception as err: + logger.debug(err) - self.source.close() - self.dest.close() - if self._client_disconnected: - self._client_disconnected() + try: + self.source.close() + except Exception as err: + logger.debug(f"Error while closing source socket: {err}") + + try: + self.dest.close() + except Exception as err: + logger.debug(f"Error while closing destination socket: {err}") + + self._pipe_closed(self) diff --git a/monkey/infection_monkey/network/relay/tcp_pipe_spawner.py b/monkey/infection_monkey/network/relay/tcp_pipe_spawner.py index 22183dd6c..5ffebaaea 100644 --- a/monkey/infection_monkey/network/relay/tcp_pipe_spawner.py +++ b/monkey/infection_monkey/network/relay/tcp_pipe_spawner.py @@ -1,6 +1,7 @@ import socket from ipaddress import IPv4Address -from typing import List +from threading import Lock +from typing import Set from .sockets_pipe import SocketsPipe @@ -13,9 +14,16 @@ class TCPPipeSpawner: def __init__(self, target_addr: IPv4Address, target_port: int): self._target_addr = target_addr self._target_port = target_port - self._pipes: List[SocketsPipe] = [] + self._pipes: Set[SocketsPipe] = set() + self._lock = Lock() def spawn_pipe(self, source: socket.socket): + """ + Attempt to create a pipe on between the configured client and the provided socket + + :param source: A socket to the connecting client. + :raises socket.error: If a socket to the configured client could not be created. + """ dest = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: dest.connect((self._target_addr, self._target_port)) @@ -24,11 +32,20 @@ class TCPPipeSpawner: dest.close() raise err - # TODO: have SocketsPipe notify TCPPipeSpawner when it's done - pipe = SocketsPipe(source, dest) - self._pipes.append(pipe) + pipe = SocketsPipe(source, dest, self._handle_pipe_closed) + with self._lock: + self._pipes.add(pipe) pipe.run() def has_open_pipes(self) -> bool: - self._pipes = [p for p in self._pipes if p.is_alive()] - return len(self._pipes) > 0 + """Return whether or not the TCPPipeSpawner has any open pipes.""" + with self._lock: + for p in self._pipes: + if p.is_alive(): + return True + + return False + + def _handle_pipe_closed(self, pipe: SocketsPipe): + with self._lock: + self._pipes.discard(pipe) From 65f4edc6255c025ef6f0295ee1e0dcc88dc17f7a Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Tue, 6 Sep 2022 18:26:45 +0000 Subject: [PATCH 047/175] Agent: Add InterruptableThreadMixin --- .../network/relay/tcp_connection_handler.py | 12 +++++------- monkey/infection_monkey/tcp_relay.py | 12 ++++-------- monkey/infection_monkey/utils/threading.py | 9 +++++++++ 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/monkey/infection_monkey/network/relay/tcp_connection_handler.py b/monkey/infection_monkey/network/relay/tcp_connection_handler.py index d8c7b8337..4515e364e 100644 --- a/monkey/infection_monkey/network/relay/tcp_connection_handler.py +++ b/monkey/infection_monkey/network/relay/tcp_connection_handler.py @@ -1,11 +1,13 @@ import socket -from threading import Event, Thread +from threading import Thread from typing import Callable, List +from infection_monkey.utils.threading import InterruptableThreadMixin + PROXY_TIMEOUT = 2.5 -class TCPConnectionHandler(Thread): +class TCPConnectionHandler(Thread, InterruptableThreadMixin): """Accepts connections on a TCP socket.""" def __init__( @@ -18,7 +20,6 @@ class TCPConnectionHandler(Thread): self.local_host = bind_host self._client_connected = client_connected super().__init__(name="TCPConnectionHandler", daemon=True) - self._stopped = Event() def run(self): l_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -26,7 +27,7 @@ class TCPConnectionHandler(Thread): l_socket.settimeout(PROXY_TIMEOUT) l_socket.listen(5) - while not self._stopped.is_set(): + while not self._interrupted.is_set(): try: source, _ = l_socket.accept() except socket.timeout: @@ -36,6 +37,3 @@ class TCPConnectionHandler(Thread): notify_client_connected(source) l_socket.close() - - def stop(self): - self._stopped.set() diff --git a/monkey/infection_monkey/tcp_relay.py b/monkey/infection_monkey/tcp_relay.py index abe7ae56a..a12d8a0d9 100644 --- a/monkey/infection_monkey/tcp_relay.py +++ b/monkey/infection_monkey/tcp_relay.py @@ -1,10 +1,11 @@ -from threading import Event, Lock, Thread +from threading import Lock, Thread from time import sleep from infection_monkey.network.relay import RelayUserHandler, TCPConnectionHandler, TCPPipeSpawner +from infection_monkey.utils.threading import InterruptableThreadMixin -class TCPRelay(Thread): +class TCPRelay(Thread, InterruptableThreadMixin): """ Provides and manages a TCP proxy connection. """ @@ -15,8 +16,6 @@ class TCPRelay(Thread): connection_handler: TCPConnectionHandler, pipe_spawner: TCPPipeSpawner, ): - self._stopped = Event() - self._user_handler = relay_user_handler self._connection_handler = connection_handler self._pipe_spawner = pipe_spawner @@ -26,16 +25,13 @@ class TCPRelay(Thread): def run(self): self._connection_handler.start() - self._stopped.wait() + self._interrupted.wait() self._wait_for_users_to_disconnect() self._connection_handler.stop() self._connection_handler.join() self._wait_for_pipes_to_close() - def stop(self): - self._stopped.set() - def _wait_for_users_to_disconnect(self): """ Blocks until the users disconnect or the timeout has elapsed. diff --git a/monkey/infection_monkey/utils/threading.py b/monkey/infection_monkey/utils/threading.py index 0443978e6..be28aa0b1 100644 --- a/monkey/infection_monkey/utils/threading.py +++ b/monkey/infection_monkey/utils/threading.py @@ -107,3 +107,12 @@ def interruptible_function(*, msg: Optional[str] = None, default_return_value: A return _wrapper return _decorator + + +class InterruptableThreadMixin: + def __init__(self): + self._interrupted = Event() + + def stop(self): + """Stop a running thread.""" + self._interrupted.set() From 017aabe8f7fb36d3e005985c896bca78bba37b39 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 6 Sep 2022 14:35:19 -0400 Subject: [PATCH 048/175] Agent: Change relay__user_handler.DEFAULT_NEW_CLIENT_TIMEOUT --- monkey/infection_monkey/network/relay/relay_user_handler.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/network/relay/relay_user_handler.py b/monkey/infection_monkey/network/relay/relay_user_handler.py index 6af2c841e..49fda7847 100644 --- a/monkey/infection_monkey/network/relay/relay_user_handler.py +++ b/monkey/infection_monkey/network/relay/relay_user_handler.py @@ -5,9 +5,11 @@ from typing import Dict from egg_timer import EggTimer +from common.common_consts.timeouts import MEDIUM_REQUEST_TIMEOUT from monkey.common.utils.code_utils import del_key -DEFAULT_NEW_CLIENT_TIMEOUT = 3 # Wait up to 3 seconds for potential new clients to connect +# Wait for potential new clients to connect +DEFAULT_NEW_CLIENT_TIMEOUT = 2.5 * MEDIUM_REQUEST_TIMEOUT @dataclass From 17356ca0791b041553a0cdfb45f7308682fcc1c0 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 6 Sep 2022 14:42:53 -0400 Subject: [PATCH 049/175] Agent: Fix del_key() import in relay_user_handler.py --- monkey/infection_monkey/network/relay/relay_user_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/network/relay/relay_user_handler.py b/monkey/infection_monkey/network/relay/relay_user_handler.py index 49fda7847..e9ee00d04 100644 --- a/monkey/infection_monkey/network/relay/relay_user_handler.py +++ b/monkey/infection_monkey/network/relay/relay_user_handler.py @@ -6,7 +6,7 @@ from typing import Dict from egg_timer import EggTimer from common.common_consts.timeouts import MEDIUM_REQUEST_TIMEOUT -from monkey.common.utils.code_utils import del_key +from common.utils.code_utils import del_key # Wait for potential new clients to connect DEFAULT_NEW_CLIENT_TIMEOUT = 2.5 * MEDIUM_REQUEST_TIMEOUT From 7edb15d825870d9112aaf11977dc4c103118474c Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 6 Sep 2022 14:43:23 -0400 Subject: [PATCH 050/175] Agent: Fix host/port initialization in TCPConnectionHandler --- .../infection_monkey/network/relay/tcp_connection_handler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/network/relay/tcp_connection_handler.py b/monkey/infection_monkey/network/relay/tcp_connection_handler.py index 4515e364e..b6deac0ed 100644 --- a/monkey/infection_monkey/network/relay/tcp_connection_handler.py +++ b/monkey/infection_monkey/network/relay/tcp_connection_handler.py @@ -16,8 +16,8 @@ class TCPConnectionHandler(Thread, InterruptableThreadMixin): bind_port: int, client_connected: List[Callable[[socket.socket], None]] = [], ): - self.local_port = bind_port - self.local_host = bind_host + self.bind_host = bind_host + self.bind_port = bind_port self._client_connected = client_connected super().__init__(name="TCPConnectionHandler", daemon=True) From 1cd544e227137981585531ab8ac23bd8110fbd7a Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 6 Sep 2022 14:50:42 -0400 Subject: [PATCH 051/175] Agent: Call mixin constructor in TCPConnectionHandler --- .../infection_monkey/network/relay/tcp_connection_handler.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/network/relay/tcp_connection_handler.py b/monkey/infection_monkey/network/relay/tcp_connection_handler.py index b6deac0ed..b70a133bf 100644 --- a/monkey/infection_monkey/network/relay/tcp_connection_handler.py +++ b/monkey/infection_monkey/network/relay/tcp_connection_handler.py @@ -19,7 +19,9 @@ class TCPConnectionHandler(Thread, InterruptableThreadMixin): self.bind_host = bind_host self.bind_port = bind_port self._client_connected = client_connected - super().__init__(name="TCPConnectionHandler", daemon=True) + + Thread.__init__(self, name="TCPConnectionHandler", daemon=True) + InterruptableThreadMixin.__init__(self) def run(self): l_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) From 76fe0be990317bc91b7839c7115f8eeb950d5d4b Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 6 Sep 2022 14:54:13 -0400 Subject: [PATCH 052/175] Agent: Address addr type TODO in RelayConnectionHandler --- .../network/relay/relay_connection_handler.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/network/relay/relay_connection_handler.py b/monkey/infection_monkey/network/relay/relay_connection_handler.py index fe08a5a61..156b78ddd 100644 --- a/monkey/infection_monkey/network/relay/relay_connection_handler.py +++ b/monkey/infection_monkey/network/relay/relay_connection_handler.py @@ -13,10 +13,13 @@ class RelayConnectionHandler: self._relay_user_handler = relay_user_handler def handle_new_connection(self, sock: socket.socket): + addr, _ = sock.getpeername() + addr = IPv4Address(addr) + control_message = sock.recv(socket.MSG_PEEK) - addr, _ = sock.getpeername() # TODO check the type of the addr object + if control_message.startswith(RELAY_CONTROL_MESSAGE): - self._relay_user_handler.disconnect_user(IPv4Address(addr)) + self._relay_user_handler.disconnect_user(addr) else: - self._relay_user_handler.add_relay_user(IPv4Address(addr)) + self._relay_user_handler.add_relay_user(addr) self._pipe_spawner.spawn_pipe(sock) From 813c5f9d3bf258a606fb29b2f38d6559378365d7 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Tue, 6 Sep 2022 19:16:49 +0000 Subject: [PATCH 053/175] Agent: Clarify exceptions in SocketsPipe._pipe() --- monkey/infection_monkey/network/relay/sockets_pipe.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/network/relay/sockets_pipe.py b/monkey/infection_monkey/network/relay/sockets_pipe.py index ff8e82c04..ccb051252 100644 --- a/monkey/infection_monkey/network/relay/sockets_pipe.py +++ b/monkey/infection_monkey/network/relay/sockets_pipe.py @@ -28,13 +28,12 @@ class SocketsPipe(Thread): def _pipe(self): sockets = [self.source, self.dest] while True: - # TODO: Figure out how to capture when the socket times out. read_list, _, except_list = select.select(sockets, [], sockets, self.timeout) if except_list: - raise Exception("select() failed") + raise Exception("select() failed on sockets {except_list}") if not read_list: - raise TimeoutError("") + raise TimeoutError("pipe did not receive data for {self.timeout} seconds") for r in read_list: other = self.dest if r is self.source else self.source From bdc8de026eb1fed658bb26a0e47ffe8347eb5fcf Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Tue, 6 Sep 2022 19:26:26 +0000 Subject: [PATCH 054/175] Agent: Move TCPRelay into network.relay package --- monkey/infection_monkey/{ => network/relay}/tcp_relay.py | 0 .../messengers/exploit_intercepting_telemetry_messenger.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename monkey/infection_monkey/{ => network/relay}/tcp_relay.py (100%) diff --git a/monkey/infection_monkey/tcp_relay.py b/monkey/infection_monkey/network/relay/tcp_relay.py similarity index 100% rename from monkey/infection_monkey/tcp_relay.py rename to monkey/infection_monkey/network/relay/tcp_relay.py diff --git a/monkey/infection_monkey/telemetry/messengers/exploit_intercepting_telemetry_messenger.py b/monkey/infection_monkey/telemetry/messengers/exploit_intercepting_telemetry_messenger.py index 60bd464b7..8f3d15ca3 100644 --- a/monkey/infection_monkey/telemetry/messengers/exploit_intercepting_telemetry_messenger.py +++ b/monkey/infection_monkey/telemetry/messengers/exploit_intercepting_telemetry_messenger.py @@ -1,6 +1,6 @@ from functools import singledispatch -from infection_monkey.tcp_relay import TCPRelay +from infection_monkey.network.relay.tcp_relay import TCPRelay from infection_monkey.telemetry.exploit_telem import ExploitTelem from infection_monkey.telemetry.i_telem import ITelem from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger From efac1dae980674f420f117a2a52325d6875cf57f Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Tue, 6 Sep 2022 19:33:03 +0000 Subject: [PATCH 055/175] Agent: Pass correct type to add_potential_user() --- .../messengers/exploit_intercepting_telemetry_messenger.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/telemetry/messengers/exploit_intercepting_telemetry_messenger.py b/monkey/infection_monkey/telemetry/messengers/exploit_intercepting_telemetry_messenger.py index 8f3d15ca3..1e5c6591a 100644 --- a/monkey/infection_monkey/telemetry/messengers/exploit_intercepting_telemetry_messenger.py +++ b/monkey/infection_monkey/telemetry/messengers/exploit_intercepting_telemetry_messenger.py @@ -1,4 +1,5 @@ from functools import singledispatch +from ipaddress import IPv4Address from infection_monkey.network.relay.tcp_relay import TCPRelay from infection_monkey.telemetry.exploit_telem import ExploitTelem @@ -41,6 +42,6 @@ def _( if telemetry.propagation_result is True: tunnel.set_wait_for_exploited_machines() if relay: - relay.add_potential_user(str(telemetry.host["ip_addr"])) + relay.add_potential_user(IPv4Address(telemetry.host["ip_addr"])) telemetry_messenger.send_telemetry(telemetry) From 7ebc403029ae31a19f8425cdaf95ab48ddd584c6 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Tue, 6 Sep 2022 19:54:53 +0000 Subject: [PATCH 056/175] Agent: Add docstrings to RelayConnectionHandler --- .../network/relay/relay_connection_handler.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/monkey/infection_monkey/network/relay/relay_connection_handler.py b/monkey/infection_monkey/network/relay/relay_connection_handler.py index 156b78ddd..3d7755fb9 100644 --- a/monkey/infection_monkey/network/relay/relay_connection_handler.py +++ b/monkey/infection_monkey/network/relay/relay_connection_handler.py @@ -8,11 +8,18 @@ RELAY_CONTROL_MESSAGE = b"infection-monkey-relay-control-message: -" class RelayConnectionHandler: + """Handles new relay connections.""" + def __init__(self, pipe_spawner: TCPPipeSpawner, relay_user_handler: RelayUserHandler): self._pipe_spawner = pipe_spawner self._relay_user_handler = relay_user_handler def handle_new_connection(self, sock: socket.socket): + """ + Spawn a new pipe, or remove the user if the user requested to disconnect. + + :param sock: The socket for the new connection. + """ addr, _ = sock.getpeername() addr = IPv4Address(addr) From 1e0b26af8b2c24a55835886f400dc17366c0e4f2 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Tue, 6 Sep 2022 19:55:25 +0000 Subject: [PATCH 057/175] Agent: Add docstring to SocketsPipe --- monkey/infection_monkey/network/relay/sockets_pipe.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/monkey/infection_monkey/network/relay/sockets_pipe.py b/monkey/infection_monkey/network/relay/sockets_pipe.py index ccb051252..e31dfaaf4 100644 --- a/monkey/infection_monkey/network/relay/sockets_pipe.py +++ b/monkey/infection_monkey/network/relay/sockets_pipe.py @@ -12,6 +12,8 @@ logger = getLogger(__name__) class SocketsPipe(Thread): + """Manages a pipe between two sockets.""" + def __init__( self, source, From b6588925e2e893f9522e3ef378f08eff0326fa0c Mon Sep 17 00:00:00 2001 From: vakarisz Date: Thu, 8 Sep 2022 16:56:32 +0300 Subject: [PATCH 058/175] BB: Add tunneling-13 machine --- .../blackbox/gcp_test_machine_list.py | 16 +++++++--- envs/monkey_zoo/docs/fullDocs.md | 32 +++++++++++++++++++ envs/monkey_zoo/terraform/images.tf | 4 +++ envs/monkey_zoo/terraform/monkey_zoo.tf | 15 +++++++++ 4 files changed, 63 insertions(+), 4 deletions(-) diff --git a/envs/monkey_zoo/blackbox/gcp_test_machine_list.py b/envs/monkey_zoo/blackbox/gcp_test_machine_list.py index 9aa5ef41f..f00af692a 100644 --- a/envs/monkey_zoo/blackbox/gcp_test_machine_list.py +++ b/envs/monkey_zoo/blackbox/gcp_test_machine_list.py @@ -11,6 +11,7 @@ GCP_TEST_MACHINE_LIST = { "tunneling-10", "tunneling-11", "tunneling-12", + "tunneling-13", "zerologon-25", ], "europe-west1-b": [ @@ -49,10 +50,6 @@ DEPTH_1_A = { DEPTH_3_A = { "europe-west3-a": [ - "tunneling-9", - "tunneling-10", - "tunneling-11", - "tunneling-12", "mimikatz-15", ], "europe-west1-b": [ @@ -63,6 +60,16 @@ DEPTH_3_A = { ], } +DEPTH_4_A = { + "europe-west1-b": [ + "tunneling-9", + "tunneling-10", + "tunneling-12", + "tunneling-13", + ], +} + + POWERSHELL_EXPLOITER_REUSE = { "europe-west1-b": [ "powershell-3-46", @@ -88,6 +95,7 @@ GCP_SINGLE_TEST_LIST = { "test_depth_2_a": DEPTH_2_A, "test_depth_1_a": DEPTH_1_A, "test_depth_3_a": DEPTH_3_A, + "test_depth_4_a": DEPTH_4_A, "test_powershell_exploiter_credentials_reuse": POWERSHELL_EXPLOITER_REUSE, "test_zerologon_exploiter": ZEROLOGON, "test_wmi_and_mimikatz_exploiters": WMI_AND_MIMIKATZ, diff --git a/envs/monkey_zoo/docs/fullDocs.md b/envs/monkey_zoo/docs/fullDocs.md index 077ccfc59..9a52d5687 100644 --- a/envs/monkey_zoo/docs/fullDocs.md +++ b/envs/monkey_zoo/docs/fullDocs.md @@ -384,6 +384,38 @@ Update all requirements using deployment script:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Nr. 13 Tunneling M5

+

(10.2.0.13)

(Exploitable)
OS:Ubuntu 18 x64
Default service’s port:22
Root password:prM2qsroTI
Server’s config:Configured to disable traffic from/to 10.2.0.10 and 10.2.0.11(via ufw and iptables)
Notes:Accessible only through Nr.12
+ diff --git a/envs/monkey_zoo/terraform/images.tf b/envs/monkey_zoo/terraform/images.tf index 3dadc5876..a33953252 100644 --- a/envs/monkey_zoo/terraform/images.tf +++ b/envs/monkey_zoo/terraform/images.tf @@ -23,6 +23,10 @@ data "google_compute_image" "tunneling-12" { name = "tunneling-12" project = local.monkeyzoo_project } +data "google_compute_image" "tunneling-13" { + name = "tunneling-13" + project = local.monkeyzoo_project +} data "google_compute_image" "sshkeys-11" { name = "sshkeys-11" project = local.monkeyzoo_project diff --git a/envs/monkey_zoo/terraform/monkey_zoo.tf b/envs/monkey_zoo/terraform/monkey_zoo.tf index de0b922f5..2d98e4eaa 100644 --- a/envs/monkey_zoo/terraform/monkey_zoo.tf +++ b/envs/monkey_zoo/terraform/monkey_zoo.tf @@ -144,6 +144,21 @@ resource "google_compute_instance_from_template" "tunneling-12" { } } +resource "google_compute_instance_from_template" "tunneling-13" { + name = "${local.resource_prefix}tunneling-13" + source_instance_template = local.default_ubuntu + boot_disk{ + initialize_params { + image = data.google_compute_image.tunneling-13.self_link + } + auto_delete = true + } + network_interface{ + subnetwork="${local.resource_prefix}tunneling2-main" + network_ip="10.2.0.13" + } +} + resource "google_compute_instance_from_template" "sshkeys-11" { name = "${local.resource_prefix}sshkeys-11" source_instance_template = local.default_ubuntu From 72c76319d1aa95c950f970cc5d9d38f253df5597 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Thu, 8 Sep 2022 16:58:14 +0300 Subject: [PATCH 059/175] BB: Move tunneling to depth 4a test suite --- envs/monkey_zoo/blackbox/test_blackbox.py | 6 ++ .../blackbox/test_configurations/depth_3_a.py | 11 +--- .../blackbox/test_configurations/depth_4_a.py | 65 +++++++++++++++++++ 3 files changed, 72 insertions(+), 10 deletions(-) create mode 100644 envs/monkey_zoo/blackbox/test_configurations/depth_4_a.py diff --git a/envs/monkey_zoo/blackbox/test_blackbox.py b/envs/monkey_zoo/blackbox/test_blackbox.py index 16ee4c0be..be8f4fe2c 100644 --- a/envs/monkey_zoo/blackbox/test_blackbox.py +++ b/envs/monkey_zoo/blackbox/test_blackbox.py @@ -18,6 +18,7 @@ from envs.monkey_zoo.blackbox.test_configurations import ( wmi_mimikatz_test_configuration, zerologon_test_configuration, ) +from envs.monkey_zoo.blackbox.test_configurations.depth_4_a import depth_4_a_test_configuration from envs.monkey_zoo.blackbox.test_configurations.test_configuration import TestConfiguration from envs.monkey_zoo.blackbox.tests.exploitation import ExploitationTest from envs.monkey_zoo.blackbox.utils.gcp_machine_handlers import ( @@ -123,6 +124,11 @@ class TestMonkeyBlackbox: island_client, depth_3_a_test_configuration, "Depth3A test suite" ) + def test_depth_4_a(self, island_client): + TestMonkeyBlackbox.run_exploitation_test( + island_client, depth_4_a_test_configuration, "Depth4A test suite" + ) + # Not grouped because can only be ran on windows @pytest.mark.skip_powershell_reuse def test_powershell_exploiter_credentials_reuse(self, island_client): diff --git a/envs/monkey_zoo/blackbox/test_configurations/depth_3_a.py b/envs/monkey_zoo/blackbox/test_configurations/depth_3_a.py index 0a39a5e59..0b971d2d5 100644 --- a/envs/monkey_zoo/blackbox/test_configurations/depth_3_a.py +++ b/envs/monkey_zoo/blackbox/test_configurations/depth_3_a.py @@ -16,14 +16,12 @@ from .utils import ( # Tests: # Powershell (10.2.3.45, 10.2.3.46, 10.2.3.47, 10.2.3.48) -# Tunneling (SSH brute force) (10.2.2.9, 10.2.1.10, 10.2.0.12, 10.2.0.11) # WMI pass the hash (10.2.2.15) def _add_exploiters(agent_configuration: AgentConfiguration) -> AgentConfiguration: brute_force = [ PluginConfiguration(name="PowerShellExploiter", options={}), - PluginConfiguration(name="SSHExploiter", options={}), PluginConfiguration(name="WmiExploiter", options={"smb_download_timeout": 30}), ] @@ -32,21 +30,17 @@ def _add_exploiters(agent_configuration: AgentConfiguration) -> AgentConfigurati def _add_subnets(agent_configuration: AgentConfiguration) -> AgentConfiguration: subnets = [ - "10.2.2.9", "10.2.3.45", "10.2.3.46", "10.2.3.47", "10.2.3.48", - "10.2.1.10", - "10.2.0.12", - "10.2.0.11", "10.2.2.15", ] return add_subnets(agent_configuration, subnets) def _add_tcp_ports(agent_configuration: AgentConfiguration) -> AgentConfiguration: - ports = [22, 135, 5985, 5986] + ports = [135, 5985, 5986] return add_tcp_ports(agent_configuration, ports) @@ -60,9 +54,6 @@ CREDENTIALS = ( Credentials(Username("m0nk3y"), None), Credentials(Username("m0nk3y-user"), None), Credentials(None, Password("Passw0rd!")), - Credentials(None, Password("3Q=(Ge(+&w]*")), - Credentials(None, Password("`))jU7L(w}")), - Credentials(None, Password("t67TC5ZDmz")), Credentials(None, NTHash("d0f0132b308a0c4e5d1029cc06f48692")), Credentials(None, NTHash("5da0889ea2081aa79f6852294cba4a5e")), Credentials(None, NTHash("50c9987a6bf1ac59398df9f911122c9b")), diff --git a/envs/monkey_zoo/blackbox/test_configurations/depth_4_a.py b/envs/monkey_zoo/blackbox/test_configurations/depth_4_a.py new file mode 100644 index 000000000..83e9dc785 --- /dev/null +++ b/envs/monkey_zoo/blackbox/test_configurations/depth_4_a.py @@ -0,0 +1,65 @@ +import dataclasses + +from common.agent_configuration import AgentConfiguration, PluginConfiguration +from common.credentials import Credentials, Password, Username + +from .noop import noop_test_configuration +from .utils import ( + add_exploiters, + add_subnets, + add_tcp_ports, + replace_agent_configuration, + replace_propagation_credentials, + set_keep_tunnel_open_time, + set_maximum_depth, +) + +# Tests: +# Tunneling (SSH brute force) (10.2.2.9, 10.2.1.10, 10.2.0.12, 10.2.0.13) + + +def _add_exploiters(agent_configuration: AgentConfiguration) -> AgentConfiguration: + brute_force = [ + PluginConfiguration(name="SSHExploiter", options={}), + PluginConfiguration(name="WmiExploiter", options={"smb_download_timeout": 30}), + ] + + return add_exploiters(agent_configuration, brute_force=brute_force, vulnerability=[]) + + +def _add_subnets(agent_configuration: AgentConfiguration) -> AgentConfiguration: + subnets = [ + "10.2.2.9", + "10.2.1.10", + "10.2.0.12", + "10.2.2.13", + ] + return add_subnets(agent_configuration, subnets) + + +def _add_tcp_ports(agent_configuration: AgentConfiguration) -> AgentConfiguration: + ports = [22, 135, 5985, 5986] + return add_tcp_ports(agent_configuration, ports) + + +test_agent_configuration = set_maximum_depth(noop_test_configuration.agent_configuration, 4) +test_agent_configuration = set_keep_tunnel_open_time(test_agent_configuration, 20) +test_agent_configuration = _add_exploiters(test_agent_configuration) +test_agent_configuration = _add_subnets(test_agent_configuration) +test_agent_configuration = _add_tcp_ports(test_agent_configuration) + +CREDENTIALS = ( + Credentials(Username("m0nk3y"), None), + Credentials(None, Password("3Q=(Ge(+&w]*")), + Credentials(None, Password("`))jU7L(w}")), + Credentials(None, Password("prM2qsroTI")), + Credentials(None, Password("t67TC5ZDmz")), +) + +depth_4_a_test_configuration = dataclasses.replace(noop_test_configuration) +replace_agent_configuration( + test_configuration=depth_4_a_test_configuration, agent_configuration=test_agent_configuration +) +replace_propagation_credentials( + test_configuration=depth_4_a_test_configuration, propagation_credentials=CREDENTIALS +) From e4d49f5a12991817baff15af3913071d9abed81b Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Fri, 2 Sep 2022 16:10:37 +0200 Subject: [PATCH 060/175] Agent: Add servers list to ExploiterWrapper --- monkey/infection_monkey/exploit/exploiter_wrapper.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/exploit/exploiter_wrapper.py b/monkey/infection_monkey/exploit/exploiter_wrapper.py index 8b69ce9d5..8c8527afe 100644 --- a/monkey/infection_monkey/exploit/exploiter_wrapper.py +++ b/monkey/infection_monkey/exploit/exploiter_wrapper.py @@ -1,5 +1,5 @@ import threading -from typing import Dict, Type +from typing import Dict, List, Type from common.event_queue import IAgentEventQueue from infection_monkey.model import VictimHost @@ -24,11 +24,13 @@ class ExploiterWrapper: telemetry_messenger: ITelemetryMessenger, event_queue: IAgentEventQueue, agent_binary_repository: IAgentBinaryRepository, + servers: List[str], ): self._exploit_class = exploit_class self._telemetry_messenger = telemetry_messenger self._event_queue = event_queue self._agent_binary_repository = agent_binary_repository + self._servers = servers def exploit_host( self, host: VictimHost, current_depth: int, options: Dict, interrupt: threading.Event @@ -36,6 +38,7 @@ class ExploiterWrapper: exploiter = self._exploit_class() return exploiter.exploit_host( host, + self._servers, current_depth, self._telemetry_messenger, self._event_queue, @@ -49,10 +52,12 @@ class ExploiterWrapper: telemetry_messenger: ITelemetryMessenger, event_queue: IAgentEventQueue, agent_binary_repository: IAgentBinaryRepository, + servers: List[str], ): self._telemetry_messenger = telemetry_messenger self._event_queue = event_queue self._agent_binary_repository = agent_binary_repository + self._servers = servers def wrap(self, exploit_class: Type[HostExploiter]): return ExploiterWrapper.Inner( @@ -60,4 +65,5 @@ class ExploiterWrapper: self._telemetry_messenger, self._event_queue, self._agent_binary_repository, + self._servers, ) From 95ea5e481e356316ed713d23a87a12660b5de845 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Fri, 2 Sep 2022 16:11:08 +0200 Subject: [PATCH 061/175] Agent: Accept list of servers in HostExploiter --- monkey/infection_monkey/exploit/HostExploiter.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/exploit/HostExploiter.py b/monkey/infection_monkey/exploit/HostExploiter.py index bb5d7f87d..db8feb1fd 100644 --- a/monkey/infection_monkey/exploit/HostExploiter.py +++ b/monkey/infection_monkey/exploit/HostExploiter.py @@ -2,7 +2,7 @@ import logging import threading from abc import abstractmethod from datetime import datetime -from typing import Dict +from typing import Dict, List from common.event_queue import IAgentEventQueue from common.utils.exceptions import FailedExploitationError @@ -35,6 +35,7 @@ class HostExploiter: self.event_queue = None self.options = {} self.exploit_result = {} + self.servers = [] def set_start_time(self): self.exploit_info["started"] = datetime.now().isoformat() @@ -58,6 +59,7 @@ class HostExploiter: def exploit_host( self, host, + servers: List[str], current_depth: int, telemetry_messenger: ITelemetryMessenger, event_queue: IAgentEventQueue, @@ -66,6 +68,7 @@ class HostExploiter: interrupt: threading.Event, ): self.host = host + self.servers = servers self.current_depth = current_depth self.telemetry_messenger = telemetry_messenger self.event_queue = event_queue From 42de7140f302453596f4b062302b98c5c82d1c9b Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Fri, 2 Sep 2022 16:13:00 +0200 Subject: [PATCH 062/175] Agent: Modify exploiters to use list of servers --- monkey/infection_monkey/exploit/hadoop.py | 2 +- monkey/infection_monkey/exploit/log4shell.py | 2 +- monkey/infection_monkey/exploit/mssqlexec.py | 2 +- monkey/infection_monkey/exploit/powershell.py | 8 ++++---- monkey/infection_monkey/exploit/smbexec.py | 4 ++-- monkey/infection_monkey/exploit/sshexec.py | 2 +- monkey/infection_monkey/exploit/web_rce.py | 6 ++++-- monkey/infection_monkey/exploit/wmiexec.py | 4 ++-- 8 files changed, 16 insertions(+), 14 deletions(-) diff --git a/monkey/infection_monkey/exploit/hadoop.py b/monkey/infection_monkey/exploit/hadoop.py index 2c0ceaa73..1b7c54470 100644 --- a/monkey/infection_monkey/exploit/hadoop.py +++ b/monkey/infection_monkey/exploit/hadoop.py @@ -104,7 +104,7 @@ class HadoopExploiter(WebRCE): def _build_command(self, path, http_path): # Build command to execute - monkey_cmd = build_monkey_commandline(self.host, self.current_depth + 1) + monkey_cmd = build_monkey_commandline(self.servers, self.current_depth + 1) if self.host.is_windows(): base_command = HADOOP_WINDOWS_COMMAND else: diff --git a/monkey/infection_monkey/exploit/log4shell.py b/monkey/infection_monkey/exploit/log4shell.py index fc925091b..399a2706e 100644 --- a/monkey/infection_monkey/exploit/log4shell.py +++ b/monkey/infection_monkey/exploit/log4shell.py @@ -115,7 +115,7 @@ class Log4ShellExploiter(WebRCE): def _build_command(self, path: PurePath, http_path) -> str: # Build command to execute - monkey_cmd = build_monkey_commandline(self.host, self.current_depth + 1, location=path) + monkey_cmd = build_monkey_commandline(self.servers, self.current_depth + 1, location=path) if self.host.is_windows(): base_command = LOG4SHELL_WINDOWS_COMMAND else: diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index b037c782a..6fd8e27cb 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -179,7 +179,7 @@ class MSSQLExploiter(HostExploiter): def _build_agent_launch_command(self, agent_path_on_victim: PureWindowsPath) -> str: agent_args = build_monkey_commandline( - self.host, self.current_depth + 1, agent_path_on_victim + self.servers, self.current_depth + 1, agent_path_on_victim ) return f"{agent_path_on_victim} {DROPPER_ARG} {agent_args}" diff --git a/monkey/infection_monkey/exploit/powershell.py b/monkey/infection_monkey/exploit/powershell.py index d6b626f9f..ff0f0db4e 100644 --- a/monkey/infection_monkey/exploit/powershell.py +++ b/monkey/infection_monkey/exploit/powershell.py @@ -15,7 +15,7 @@ from infection_monkey.exploit.powershell_utils.powershell_client import ( PowerShellClient, ) from infection_monkey.exploit.tools.helpers import get_agent_dst_path, get_random_file_suffix -from infection_monkey.model import DROPPER_ARG, RUN_MONKEY, VictimHost +from infection_monkey.model import DROPPER_ARG, RUN_MONKEY from infection_monkey.utils.commands import build_monkey_commandline from infection_monkey.utils.environment import is_windows_os from infection_monkey.utils.threading import interruptible_iter @@ -169,7 +169,7 @@ class PowerShellExploiter(HostExploiter): def _run_monkey_executable_on_victim(self, executable_path): monkey_execution_command = build_monkey_execution_command( - self.host, self.current_depth + 1, executable_path + self.servers, self.current_depth + 1, executable_path ) logger.info( @@ -179,9 +179,9 @@ class PowerShellExploiter(HostExploiter): self._client.execute_cmd_as_detached_process(monkey_execution_command) -def build_monkey_execution_command(host: VictimHost, depth: int, executable_path: str) -> str: +def build_monkey_execution_command(servers: List[str], depth: int, executable_path: str) -> str: monkey_params = build_monkey_commandline( - target_host=host, + servers, depth=depth, location=executable_path, ) diff --git a/monkey/infection_monkey/exploit/smbexec.py b/monkey/infection_monkey/exploit/smbexec.py index 272f150eb..abf8b4f47 100644 --- a/monkey/infection_monkey/exploit/smbexec.py +++ b/monkey/infection_monkey/exploit/smbexec.py @@ -91,14 +91,14 @@ class SMBExploiter(HostExploiter): cmdline = DROPPER_CMDLINE_DETACHED_WINDOWS % { "dropper_path": remote_full_path } + build_monkey_commandline( - self.host, + self.servers, self.current_depth + 1, str(dest_path), ) else: cmdline = MONKEY_CMDLINE_DETACHED_WINDOWS % { "monkey_path": remote_full_path - } + build_monkey_commandline(self.host, self.current_depth + 1) + } + build_monkey_commandline(self.servers, self.current_depth + 1) smb_conn = None for str_bind_format, port in SMBExploiter.KNOWN_PROTOCOLS.values(): diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index 69b29c813..3ff128203 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -245,7 +245,7 @@ class SSHExploiter(HostExploiter): try: cmdline = f"{monkey_path_on_victim} {MONKEY_ARG}" - cmdline += build_monkey_commandline(self.host, self.current_depth + 1) + cmdline += build_monkey_commandline(self.servers, self.current_depth + 1) cmdline += " > /dev/null 2>&1 &" ssh.exec_command(cmdline, timeout=SSH_EXEC_TIMEOUT) diff --git a/monkey/infection_monkey/exploit/web_rce.py b/monkey/infection_monkey/exploit/web_rce.py index 4083aa928..f4cbd7948 100644 --- a/monkey/infection_monkey/exploit/web_rce.py +++ b/monkey/infection_monkey/exploit/web_rce.py @@ -370,14 +370,16 @@ class WebRCE(HostExploiter): default_path = self.get_default_dropper_path() if default_path is False: return False - monkey_cmd = build_monkey_commandline(self.host, self.current_depth + 1, default_path) + monkey_cmd = build_monkey_commandline( + self.servers, self.current_depth + 1, default_path + ) command = RUN_MONKEY % { "monkey_path": path, "monkey_type": DROPPER_ARG, "parameters": monkey_cmd, } else: - monkey_cmd = build_monkey_commandline(self.host, self.current_depth + 1) + monkey_cmd = build_monkey_commandline(self.servers, self.current_depth + 1) command = RUN_MONKEY % { "monkey_path": path, "monkey_type": MONKEY_ARG, diff --git a/monkey/infection_monkey/exploit/wmiexec.py b/monkey/infection_monkey/exploit/wmiexec.py index 6c5b189f7..8cfd27a3d 100644 --- a/monkey/infection_monkey/exploit/wmiexec.py +++ b/monkey/infection_monkey/exploit/wmiexec.py @@ -103,14 +103,14 @@ class WmiExploiter(HostExploiter): cmdline = DROPPER_CMDLINE_WINDOWS % { "dropper_path": remote_full_path } + build_monkey_commandline( - self.host, + self.servers, self.current_depth + 1, DROPPER_TARGET_PATH_WIN64, ) else: cmdline = MONKEY_CMDLINE_WINDOWS % { "monkey_path": remote_full_path - } + build_monkey_commandline(self.host, self.current_depth + 1) + } + build_monkey_commandline(self.servers, self.current_depth + 1) # execute the remote monkey result = WmiTools.get_object(wmi_connection, "Win32_Process").Create( From e6b0e43580ae0b5003513d43135a14373a4571d1 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Fri, 2 Sep 2022 16:15:36 +0200 Subject: [PATCH 063/175] UT: Fix Powershell unit tests to accept list of servers --- .../unit_tests/infection_monkey/exploit/test_powershell.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/exploit/test_powershell.py b/monkey/tests/unit_tests/infection_monkey/exploit/test_powershell.py index bf388d6a9..e01a545d2 100644 --- a/monkey/tests/unit_tests/infection_monkey/exploit/test_powershell.py +++ b/monkey/tests/unit_tests/infection_monkey/exploit/test_powershell.py @@ -6,7 +6,6 @@ import pytest from infection_monkey.exploit import powershell from infection_monkey.exploit.tools.helpers import AGENT_BINARY_PATH_WIN64 -from infection_monkey.model.host import VictimHost # Use the path_win32api_get_user_name fixture for all tests in this module pytestmark = pytest.mark.usefixtures("patch_win32api_get_user_name") @@ -16,6 +15,8 @@ PASSWORD_LIST = ["pass1", "pass2"] LM_HASH_LIST = ["bogo_lm_1"] NT_HASH_LIST = ["bogo_nt_1", "bogo_nt_2"] +bogus_servers = ["1.1.1.1:5000", "2.2.2.2:5007"] + mock_agent_binary_repository = MagicMock() mock_agent_binary_repository.get_agent_binary.return_value = BytesIO(b"BINARY_EXECUTABLE") @@ -33,6 +34,7 @@ def powershell_arguments(http_and_https_both_enabled_host): } arguments = { "host": http_and_https_both_enabled_host, + "servers": bogus_servers, "options": options, "current_depth": 2, "telemetry_messenger": MagicMock(), @@ -179,11 +181,10 @@ def test_login_attempts_correctly_reported(monkeypatch, powershell_exploiter, po def test_build_monkey_execution_command(): - host = VictimHost("127.0.0.1") depth = 2 executable_path = "/tmp/test-monkey" - cmd = powershell.build_monkey_execution_command(host, depth, executable_path) + cmd = powershell.build_monkey_execution_command(bogus_servers, depth, executable_path) assert f"-d {depth}" in cmd assert executable_path in cmd From ba187ed8306dfff80b5ad3e8c8f6f649162690d6 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Fri, 2 Sep 2022 11:33:45 +0200 Subject: [PATCH 064/175] Agent: Modify command line arguments to accept list of servers Removes tunnel argument --- monkey/infection_monkey/monkey.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index abb0964ca..77c7be333 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -95,8 +95,8 @@ class InfectionMonkey: logger.info("Monkey is initializing...") self._singleton = SystemSingleton() self._opts = self._get_arguments(args) - self._cmd_island_ip, self._cmd_island_port = address_to_ip_port(self._opts.server) - self._control_client = ControlClient(self._opts.server) + self._cmd_island_ip, self._cmd_island_port = address_to_ip_port(self._opts.servers) + self._control_client = ControlClient(self._opts.servers) # TODO Refactor the telemetry messengers to accept control client # and remove control_client_object ControlClient.control_client_object = self._control_client @@ -110,8 +110,7 @@ class InfectionMonkey: def _get_arguments(args): arg_parser = argparse.ArgumentParser() arg_parser.add_argument("-p", "--parent") - arg_parser.add_argument("-t", "--tunnel") - arg_parser.add_argument("-s", "--server") + arg_parser.add_argument("-s", "--servers", type=lambda arg: arg.strip().split(",")) arg_parser.add_argument("-d", "--depth", type=positive_int, default=0) opts = arg_parser.parse_args(args) InfectionMonkey._log_arguments(opts) @@ -154,12 +153,12 @@ class InfectionMonkey: if self._current_server_is_set(): logger.debug(f"Default server set to: {self._control_client.server_address}") else: - raise Exception(f"Monkey couldn't find server with {self._opts.tunnel} default tunnel.") + raise Exception(f"Monkey couldn't find servers: {self._opts.servers} .") self._control_client.wakeup(parent=self._opts.parent) def _current_server_is_set(self) -> bool: - if self._control_client.find_server(default_tunnel=self._opts.tunnel): + if self._control_client.find_server(default_tunnel=self._opts.servers): return True return False From a79b579cdc221df0582e38cf9dcc492313253e1d Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Mon, 5 Sep 2022 12:40:56 +0200 Subject: [PATCH 065/175] Agent: Construct AutomatedMaster with list of servers --- monkey/infection_monkey/monkey.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 966d27842..f8b523b66 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -154,7 +154,8 @@ class InfectionMonkey: logger.debug(f"Default server set to: {self._control_client.server_address}") else: raise Exception( - f"Failed to connect to the island via any known server address: {self._opts.servers}" + f"Failed to connect to the island via " + f"any known server address: {self._opts.servers}" ) self._control_client.wakeup(parent=self._opts.parent) @@ -219,6 +220,7 @@ class InfectionMonkey: self._master = AutomatedMaster( self._current_depth, + self._opts.servers, puppet, telemetry_messenger, victim_host_factory, From c44a0406eee08d923e69cce719cda4ab029dd282 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Fri, 2 Sep 2022 11:42:21 +0200 Subject: [PATCH 066/175] Changelog: Add entry for removing -t/--tunnel from agent command line arguments --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a7e48601..dd69b49fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -103,6 +103,7 @@ Changelog](https://keepachangelog.com/en/1.0.0/). - "/api/configuration/import" endpoint. #2002 - "/api/configuration/export" endpoint. #2002 - "/api/island-configuration" endpoint. #2003 +- "-t/--tunnel" from agent command line arguments. #2216 ### Fixed - A bug in network map page that caused delay of telemetry log loading. #1545 From 8b2107d19783c212cdc278258d075ac786710753 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Mon, 5 Sep 2022 12:41:43 +0200 Subject: [PATCH 067/175] Agent: Accept list of servers in AutomatedMaster --- monkey/infection_monkey/master/automated_master.py | 6 ++++-- .../infection_monkey/master/test_automated_master.py | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/monkey/infection_monkey/master/automated_master.py b/monkey/infection_monkey/master/automated_master.py index 45b329ad1..6959e6bf1 100644 --- a/monkey/infection_monkey/master/automated_master.py +++ b/monkey/infection_monkey/master/automated_master.py @@ -2,7 +2,7 @@ import logging import threading import time from ipaddress import IPv4Interface -from typing import Any, Callable, Collection, List, Optional +from typing import Any, Callable, Collection, List, Optional, Sequence from common.agent_configuration import CustomPBAConfiguration, PluginConfiguration from common.utils import Timer @@ -35,6 +35,7 @@ class AutomatedMaster(IMaster): def __init__( self, current_depth: Optional[int], + servers: Sequence[str], puppet: IPuppet, telemetry_messenger: ITelemetryMessenger, victim_host_factory: VictimHostFactory, @@ -43,6 +44,7 @@ class AutomatedMaster(IMaster): credentials_store: IPropagationCredentialsRepository, ): self._current_depth = current_depth + self._servers = servers self._puppet = puppet self._telemetry_messenger = telemetry_messenger self._control_channel = control_channel @@ -175,7 +177,7 @@ class AutomatedMaster(IMaster): logger.info(f"Current depth is {current_depth}") if maximum_depth_reached(config.propagation.maximum_depth, current_depth): - self._propagator.propagate(config.propagation, current_depth, self._stop) + self._propagator.propagate(config.propagation, current_depth, self._servers, self._stop) else: logger.info("Skipping propagation: maximum depth reached") diff --git a/monkey/tests/unit_tests/infection_monkey/master/test_automated_master.py b/monkey/tests/unit_tests/infection_monkey/master/test_automated_master.py index 9029ce480..1ebcc886f 100644 --- a/monkey/tests/unit_tests/infection_monkey/master/test_automated_master.py +++ b/monkey/tests/unit_tests/infection_monkey/master/test_automated_master.py @@ -14,7 +14,7 @@ INTERVAL = 0.001 def test_terminate_without_start(): - m = AutomatedMaster(None, None, None, None, MagicMock(), [], MagicMock()) + m = AutomatedMaster(None, [], None, None, None, MagicMock(), [], MagicMock()) # Test that call to terminate does not raise exception m.terminate() @@ -34,7 +34,7 @@ def test_stop_if_cant_get_config_from_island(monkeypatch): monkeypatch.setattr( "infection_monkey.master.automated_master.CHECK_FOR_TERMINATE_INTERVAL_SEC", INTERVAL ) - m = AutomatedMaster(None, None, None, None, cc, [], MagicMock()) + m = AutomatedMaster(None, [], None, None, None, cc, [], MagicMock()) m.start() assert cc.get_config.call_count == CHECK_FOR_CONFIG_COUNT @@ -73,7 +73,7 @@ def test_stop_if_cant_get_stop_signal_from_island(monkeypatch, sleep_and_return_ "infection_monkey.master.automated_master.CHECK_FOR_TERMINATE_INTERVAL_SEC", INTERVAL ) - m = AutomatedMaster(None, None, None, None, cc, [], MagicMock()) + m = AutomatedMaster(None, [], None, None, None, cc, [], MagicMock()) m.start() assert cc.should_agent_stop.call_count == CHECK_FOR_STOP_AGENT_COUNT From c9a9367dd1e10ce0867d7d566c5329dd92438961 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Fri, 2 Sep 2022 11:47:56 +0200 Subject: [PATCH 068/175] Changelog: Change -s/--servers to accept list of servers --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd69b49fb..a945cba14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,7 @@ Changelog](https://keepachangelog.com/en/1.0.0/). - `/api/registration` endpoint to `/api/register`. #2105 - `/api/file-upload` endpoit to `/api/pba/upload`. #2154 - Improved the speed of ransomware encryption by 2-3x. #2123 +- "-s/--servers" accepts list of servers separated by comma. #2216 ### Removed - VSFTPD exploiter. #1533 From f104f31dcd34d4a150bfb3e626dd0a7d3ecebe15 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Mon, 5 Sep 2022 13:07:05 +0200 Subject: [PATCH 069/175] Agent: Pass list of servers to Exploiter.exploit_hosts --- monkey/infection_monkey/master/exploiter.py | 11 ++++++++--- .../infection_monkey/master/test_exploiter.py | 7 +++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/monkey/infection_monkey/master/exploiter.py b/monkey/infection_monkey/master/exploiter.py index 0c743674c..501e46e9a 100644 --- a/monkey/infection_monkey/master/exploiter.py +++ b/monkey/infection_monkey/master/exploiter.py @@ -53,6 +53,7 @@ class Exploiter: exploitation_config: ExploitationConfiguration, hosts_to_exploit: Queue, current_depth: int, + servers: Sequence[str], results_callback: Callback, scan_completed: Event, stop: Event, @@ -67,6 +68,7 @@ class Exploiter: exploiters_to_run, hosts_to_exploit, current_depth, + servers, results_callback, scan_completed, stop, @@ -103,6 +105,7 @@ class Exploiter: exploiters_to_run: Sequence[PluginConfiguration], hosts_to_exploit: Queue, current_depth: int, + servers: Sequence[str], results_callback: Callback, scan_completed: Event, stop: Event, @@ -113,7 +116,7 @@ class Exploiter: try: victim_host = hosts_to_exploit.get(timeout=QUEUE_TIMEOUT) self._run_all_exploiters( - exploiters_to_run, victim_host, current_depth, results_callback, stop + exploiters_to_run, victim_host, current_depth, servers, results_callback, stop ) except queue.Empty: if _all_hosts_have_been_processed(scan_completed, hosts_to_exploit): @@ -130,6 +133,7 @@ class Exploiter: exploiters_to_run: Sequence[PluginConfiguration], victim_host: VictimHost, current_depth: int, + servers: Sequence[str], results_callback: Callback, stop: Event, ): @@ -147,7 +151,7 @@ class Exploiter: continue exploiter_results = self._run_exploiter( - exploiter_name, exploiter.options, victim_host, current_depth, stop + exploiter_name, exploiter.options, victim_host, current_depth, servers, stop ) results_callback(exploiter_name, victim_host, exploiter_results) @@ -160,6 +164,7 @@ class Exploiter: options: Dict, victim_host: VictimHost, current_depth: int, + servers: Sequence[str], stop: Event, ) -> ExploiterResultData: logger.debug(f"Attempting to use {exploiter_name} on {victim_host.ip_addr}") @@ -169,7 +174,7 @@ class Exploiter: try: return self._puppet.exploit_host( - exploiter_name, victim_host, current_depth, options, stop + exploiter_name, victim_host, current_depth, servers, options, stop ) except Exception as ex: msg = ( diff --git a/monkey/tests/unit_tests/infection_monkey/master/test_exploiter.py b/monkey/tests/unit_tests/infection_monkey/master/test_exploiter.py index 45288fbb7..d8e5ef6b1 100644 --- a/monkey/tests/unit_tests/infection_monkey/master/test_exploiter.py +++ b/monkey/tests/unit_tests/infection_monkey/master/test_exploiter.py @@ -85,6 +85,7 @@ def get_host_exploit_combos_from_call_args_list(call_args_list): CREDENTIALS_FOR_PROPAGATION = {"usernames": ["m0nk3y", "user"], "passwords": ["1234", "pword"]} +SERVERS = ["127.0.0.1:5000", "10.10.10.10:5007"] def get_credentials_for_propagation(): @@ -98,7 +99,7 @@ def run_exploiters(exploiter_config, hosts_to_exploit, callback, scan_completed, scan_completed.set() e = Exploiter(puppet, num_workers, get_credentials_for_propagation) - e.exploit_hosts(exploiter_config, hosts, 1, callback, scan_completed, stop) + e.exploit_hosts(exploiter_config, hosts, 1, SERVERS, callback, scan_completed, stop) return inner @@ -143,7 +144,9 @@ def test_stop_after_callback(exploiter_config, callback, scan_completed, stop, h # Intentionally NOT setting scan_completed.set(); _callback() will set stop e = Exploiter(MockPuppet(), callback_barrier_count + 2, get_credentials_for_propagation) - e.exploit_hosts(exploiter_config, hosts_to_exploit, 1, stoppable_callback, scan_completed, stop) + e.exploit_hosts( + exploiter_config, hosts_to_exploit, 1, SERVERS, stoppable_callback, scan_completed, stop + ) assert stoppable_callback.call_count == 2 From 1776b3b0b701e53aa63605d4463817e6b2d9606f Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 2 Sep 2022 08:02:19 -0400 Subject: [PATCH 070/175] Agent: Modify exception message when server connection fails --- monkey/infection_monkey/monkey.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 77c7be333..966d27842 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -153,7 +153,9 @@ class InfectionMonkey: if self._current_server_is_set(): logger.debug(f"Default server set to: {self._control_client.server_address}") else: - raise Exception(f"Monkey couldn't find servers: {self._opts.servers} .") + raise Exception( + f"Failed to connect to the island via any known server address: {self._opts.servers}" + ) self._control_client.wakeup(parent=self._opts.parent) From b2f0b54d37ce8bf69491e32bce711fb200127ca1 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Mon, 5 Sep 2022 13:07:38 +0200 Subject: [PATCH 071/175] Agent: Add list of server when propagating --- monkey/infection_monkey/master/propagator.py | 16 ++++++++++++++-- .../infection_monkey/master/test_propagator.py | 10 +++++++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/monkey/infection_monkey/master/propagator.py b/monkey/infection_monkey/master/propagator.py index 03a5d5ec7..74a0a41ca 100644 --- a/monkey/infection_monkey/master/propagator.py +++ b/monkey/infection_monkey/master/propagator.py @@ -47,7 +47,11 @@ class Propagator: self._hosts_to_exploit: Queue = Queue() def propagate( - self, propagation_config: PropagationConfiguration, current_depth: int, stop: Event + self, + propagation_config: PropagationConfiguration, + current_depth: int, + servers: Sequence[str], + stop: Event, ): logger.info("Attempting to propagate") @@ -66,7 +70,13 @@ class Propagator: exploit_thread = create_daemon_thread( target=self._exploit_hosts, name="PropagatorExploitThread", - args=(propagation_config.exploitation, current_depth, network_scan_completed, stop), + args=( + propagation_config.exploitation, + current_depth, + servers, + network_scan_completed, + stop, + ), ) scan_thread.start() @@ -167,6 +177,7 @@ class Propagator: self, exploitation_config: ExploitationConfiguration, current_depth: int, + servers: Sequence[str], network_scan_completed: Event, stop: Event, ): @@ -176,6 +187,7 @@ class Propagator: exploitation_config, self._hosts_to_exploit, current_depth, + servers, self._process_exploit_attempts, network_scan_completed, stop, diff --git a/monkey/tests/unit_tests/infection_monkey/master/test_propagator.py b/monkey/tests/unit_tests/infection_monkey/master/test_propagator.py index 53136e755..8f1b51274 100644 --- a/monkey/tests/unit_tests/infection_monkey/master/test_propagator.py +++ b/monkey/tests/unit_tests/infection_monkey/master/test_propagator.py @@ -110,6 +110,8 @@ os_windows = "windows" os_linux = "linux" +SERVERS = ["127.0.0.1:5000", "10.10.10.10:5007"] + @pytest.fixture def mock_ip_scanner(): @@ -134,6 +136,7 @@ class StubExploiter: exploiters_to_run, hosts_to_exploit, current_depth, + servers, results_callback, scan_completed, stop, @@ -171,7 +174,7 @@ def test_scan_result_processing( subnets=["10.0.0.1", "10.0.0.2", "10.0.0.3"], ) propagation_config = get_propagation_config(default_agent_configuration, targets) - p.propagate(propagation_config, 1, Event()) + p.propagate(propagation_config, 1, SERVERS, Event()) assert len(telemetry_messenger_spy.telemetries) == 3 @@ -204,6 +207,7 @@ class MockExploiter: exploiters_to_run, hosts_to_exploit, current_depth, + servers, results_callback, scan_completed, stop, @@ -269,7 +273,7 @@ def test_exploiter_result_processing( subnets=["10.0.0.1", "10.0.0.2", "10.0.0.3"], ) propagation_config = get_propagation_config(default_agent_configuration, targets) - p.propagate(propagation_config, 1, Event()) + p.propagate(propagation_config, 1, SERVERS, Event()) exploit_telems = [t for t in telemetry_messenger_spy.telemetries if isinstance(t, ExploitTelem)] assert len(exploit_telems) == 4 @@ -310,7 +314,7 @@ def test_scan_target_generation( subnets=["10.0.0.0/29", "172.10.20.30"], ) propagation_config = get_propagation_config(default_agent_configuration, targets) - p.propagate(propagation_config, 1, Event()) + p.propagate(propagation_config, 1, SERVERS, Event()) expected_ip_scan_list = [ "10.0.0.0", From 0f73cc91f238ed2111f0f4c47111beb66d2791d1 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Fri, 2 Sep 2022 15:26:51 +0200 Subject: [PATCH 072/175] Changelog: Add entry for changing "-s/--server" to "-s/--servers" --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a945cba14..92c2bbd00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,7 @@ Changelog](https://keepachangelog.com/en/1.0.0/). - `/api/registration` endpoint to `/api/register`. #2105 - `/api/file-upload` endpoit to `/api/pba/upload`. #2154 - Improved the speed of ransomware encryption by 2-3x. #2123 +- "-s/--server" to "-s/--servers". #2216 - "-s/--servers" accepts list of servers separated by comma. #2216 ### Removed From 8fc0d52b8b9f9c1386569edcac3a7a3036588b09 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Mon, 5 Sep 2022 13:11:41 +0200 Subject: [PATCH 073/175] Agent: Pass list of servers in HostExploiter.exploit_host --- monkey/infection_monkey/exploit/HostExploiter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/exploit/HostExploiter.py b/monkey/infection_monkey/exploit/HostExploiter.py index db8feb1fd..6c7128677 100644 --- a/monkey/infection_monkey/exploit/HostExploiter.py +++ b/monkey/infection_monkey/exploit/HostExploiter.py @@ -2,7 +2,7 @@ import logging import threading from abc import abstractmethod from datetime import datetime -from typing import Dict, List +from typing import Dict, Sequence from common.event_queue import IAgentEventQueue from common.utils.exceptions import FailedExploitationError @@ -59,7 +59,7 @@ class HostExploiter: def exploit_host( self, host, - servers: List[str], + servers: Sequence[str], current_depth: int, telemetry_messenger: ITelemetryMessenger, event_queue: IAgentEventQueue, From 4ad2c35c942a681ccacee01ca04d6a02a28d7feb Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Fri, 2 Sep 2022 12:12:29 +0200 Subject: [PATCH 074/175] UT: Remove tunnel from build_monkey_commandline --- .../infection_monkey/utils/test_commands.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py b/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py index bbd27274e..f6bbcb691 100644 --- a/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py @@ -12,8 +12,6 @@ def test_build_monkey_commandline_explicitly_arguments(): expected = [ "-p", "101010", - "-t", - "10.10.101.10", "-s", "127.127.127.127:5000", "-d", @@ -22,7 +20,7 @@ def test_build_monkey_commandline_explicitly_arguments(): "C:\\windows\\abc", ] actual = build_monkey_commandline_explicitly( - "101010", "10.10.101.10", "127.127.127.127:5000", 0, "C:\\windows\\abc" + "101010", "127.127.127.127:5000", 0, "C:\\windows\\abc" ) assert expected == actual @@ -46,16 +44,12 @@ def test_get_monkey_commandline_windows(): "m0nk3y", "-p", "101010", - "-t", - "10.10.101.10", ] actual = get_monkey_commandline_windows( "C:\\windows\\abc", [ "-p", "101010", - "-t", - "10.10.101.10", ], ) @@ -68,16 +62,12 @@ def test_get_monkey_commandline_linux(): "m0nk3y", "-p", "101010", - "-t", - "10.10.101.10", ] actual = get_monkey_commandline_linux( "/home/user/monkey-linux-64", [ "-p", "101010", - "-t", - "10.10.101.10", ], ) From 33f20c7da0f51c3b5e9041f29fafc67977c3383d Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Fri, 2 Sep 2022 16:17:39 +0200 Subject: [PATCH 075/175] Agent: Remove tunnel argument from dropper --- monkey/infection_monkey/dropper.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/monkey/infection_monkey/dropper.py b/monkey/infection_monkey/dropper.py index 3ba310492..6f92feaea 100644 --- a/monkey/infection_monkey/dropper.py +++ b/monkey/infection_monkey/dropper.py @@ -44,7 +44,6 @@ class MonkeyDrops(object): def __init__(self, args): arg_parser = argparse.ArgumentParser() arg_parser.add_argument("-p", "--parent") - arg_parser.add_argument("-t", "--tunnel") arg_parser.add_argument("-s", "--server") arg_parser.add_argument("-d", "--depth", type=positive_int, default=0) arg_parser.add_argument("-l", "--location") @@ -132,7 +131,6 @@ class MonkeyDrops(object): monkey_options = build_monkey_commandline_explicitly( parent=self.opts.parent, - tunnel=self.opts.tunnel, server=self.opts.server, depth=self.opts.depth, location=None, From c51217507a3fe34ee606383518f6fe40043ff779 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Mon, 5 Sep 2022 13:24:33 +0200 Subject: [PATCH 076/175] Agent: Pass servers to exploit_host in ExploiterWrapper.Inner --- .../exploit/exploiter_wrapper.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/monkey/infection_monkey/exploit/exploiter_wrapper.py b/monkey/infection_monkey/exploit/exploiter_wrapper.py index 8c8527afe..ac65a1d8d 100644 --- a/monkey/infection_monkey/exploit/exploiter_wrapper.py +++ b/monkey/infection_monkey/exploit/exploiter_wrapper.py @@ -1,5 +1,5 @@ import threading -from typing import Dict, List, Type +from typing import Dict, Sequence, Type from common.event_queue import IAgentEventQueue from infection_monkey.model import VictimHost @@ -24,21 +24,24 @@ class ExploiterWrapper: telemetry_messenger: ITelemetryMessenger, event_queue: IAgentEventQueue, agent_binary_repository: IAgentBinaryRepository, - servers: List[str], ): self._exploit_class = exploit_class self._telemetry_messenger = telemetry_messenger self._event_queue = event_queue self._agent_binary_repository = agent_binary_repository - self._servers = servers def exploit_host( - self, host: VictimHost, current_depth: int, options: Dict, interrupt: threading.Event + self, + host: VictimHost, + servers: Sequence[str], + current_depth: int, + options: Dict, + interrupt: threading.Event, ): exploiter = self._exploit_class() return exploiter.exploit_host( host, - self._servers, + servers, current_depth, self._telemetry_messenger, self._event_queue, @@ -52,12 +55,10 @@ class ExploiterWrapper: telemetry_messenger: ITelemetryMessenger, event_queue: IAgentEventQueue, agent_binary_repository: IAgentBinaryRepository, - servers: List[str], ): self._telemetry_messenger = telemetry_messenger self._event_queue = event_queue self._agent_binary_repository = agent_binary_repository - self._servers = servers def wrap(self, exploit_class: Type[HostExploiter]): return ExploiterWrapper.Inner( @@ -65,5 +66,4 @@ class ExploiterWrapper: self._telemetry_messenger, self._event_queue, self._agent_binary_repository, - self._servers, ) From ddc4f4d836e9e24871e03b37923617f668a98c99 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Fri, 2 Sep 2022 12:13:20 +0200 Subject: [PATCH 077/175] Agent: Remove tunnel from aget build command functions --- monkey/infection_monkey/utils/commands.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/monkey/infection_monkey/utils/commands.py b/monkey/infection_monkey/utils/commands.py index b66a622e9..c2c7e188c 100644 --- a/monkey/infection_monkey/utils/commands.py +++ b/monkey/infection_monkey/utils/commands.py @@ -13,7 +13,6 @@ def build_monkey_commandline(target_host: VictimHost, depth: int, location: str return " " + " ".join( build_monkey_commandline_explicitly( GUID, - target_host.default_tunnel, target_host.default_server, depth, location, @@ -23,7 +22,6 @@ def build_monkey_commandline(target_host: VictimHost, depth: int, location: str def build_monkey_commandline_explicitly( parent: str = None, - tunnel: str = None, server: str = None, depth: int = None, location: str = None, @@ -33,9 +31,6 @@ def build_monkey_commandline_explicitly( if parent is not None: cmdline.append("-p") cmdline.append(str(parent)) - if tunnel is not None: - cmdline.append("-t") - cmdline.append(str(tunnel)) if server is not None: cmdline.append("-s") cmdline.append(str(server)) From e842c45e42f76c8753100edfcadfe62d83ee582d Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Fri, 2 Sep 2022 16:19:21 +0200 Subject: [PATCH 078/175] Agent: Add list of servers to dropper --- monkey/infection_monkey/dropper.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/dropper.py b/monkey/infection_monkey/dropper.py index 6f92feaea..f443f08ad 100644 --- a/monkey/infection_monkey/dropper.py +++ b/monkey/infection_monkey/dropper.py @@ -44,7 +44,7 @@ class MonkeyDrops(object): def __init__(self, args): arg_parser = argparse.ArgumentParser() arg_parser.add_argument("-p", "--parent") - arg_parser.add_argument("-s", "--server") + arg_parser.add_argument("-s", "--servers", type=lambda arg: arg.strip().split(",")) arg_parser.add_argument("-d", "--depth", type=positive_int, default=0) arg_parser.add_argument("-l", "--location") arg_parser.add_argument("-vp", "--vulnerable-port") @@ -131,7 +131,7 @@ class MonkeyDrops(object): monkey_options = build_monkey_commandline_explicitly( parent=self.opts.parent, - server=self.opts.server, + servers=self.opts.servers, depth=self.opts.depth, location=None, ) From f93125f671fe7a438dd7453f56b6f995d6965a31 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Mon, 5 Sep 2022 13:25:56 +0200 Subject: [PATCH 079/175] Agent: Pass servers when running exploits in Puppet --- monkey/infection_monkey/puppet/puppet.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/puppet/puppet.py b/monkey/infection_monkey/puppet/puppet.py index ec77ffa35..5f522ffe9 100644 --- a/monkey/infection_monkey/puppet/puppet.py +++ b/monkey/infection_monkey/puppet/puppet.py @@ -70,11 +70,12 @@ class Puppet(IPuppet): name: str, host: VictimHost, current_depth: int, + servers: Sequence[str], options: Dict, interrupt: threading.Event, ) -> ExploiterResultData: exploiter = self._plugin_registry.get_plugin(name, PluginType.EXPLOITER) - return exploiter.exploit_host(host, current_depth, options, interrupt) + return exploiter.exploit_host(host, servers, current_depth, options, interrupt) def run_payload(self, name: str, options: Dict, interrupt: threading.Event): payload = self._plugin_registry.get_plugin(name, PluginType.PAYLOAD) From 067d50f3c40759188e361ab3293d2960d55440ed Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Fri, 2 Sep 2022 14:35:13 +0200 Subject: [PATCH 080/175] Agent: Modify agent build command line to accept list of servers --- monkey/infection_monkey/utils/commands.py | 25 +++++++------ .../infection_monkey/utils/test_commands.py | 37 +++++++++++-------- 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/monkey/infection_monkey/utils/commands.py b/monkey/infection_monkey/utils/commands.py index c2c7e188c..c290b3893 100644 --- a/monkey/infection_monkey/utils/commands.py +++ b/monkey/infection_monkey/utils/commands.py @@ -1,19 +1,20 @@ +from typing import List, Optional + from infection_monkey.config import GUID from infection_monkey.exploit.tools.helpers import AGENT_BINARY_PATH_LINUX, AGENT_BINARY_PATH_WIN64 from infection_monkey.model import CMD_CARRY_OUT, CMD_EXE, MONKEY_ARG -from infection_monkey.model.host import VictimHost # Dropper target paths DROPPER_TARGET_PATH_LINUX = AGENT_BINARY_PATH_LINUX DROPPER_TARGET_PATH_WIN64 = AGENT_BINARY_PATH_WIN64 -def build_monkey_commandline(target_host: VictimHost, depth: int, location: str = None) -> str: +def build_monkey_commandline(servers: List[str], depth: int, location: Optional[str] = None) -> str: return " " + " ".join( build_monkey_commandline_explicitly( GUID, - target_host.default_server, + servers, depth, location, ) @@ -21,19 +22,19 @@ def build_monkey_commandline(target_host: VictimHost, depth: int, location: str def build_monkey_commandline_explicitly( - parent: str = None, - server: str = None, - depth: int = None, - location: str = None, -) -> list: + parent: Optional[str] = None, + servers: Optional[List[str]] = None, + depth: Optional[int] = None, + location: Optional[str] = None, +) -> List[str]: cmdline = [] if parent is not None: cmdline.append("-p") cmdline.append(str(parent)) - if server is not None: + if servers: cmdline.append("-s") - cmdline.append(str(server)) + cmdline.append(",".join(servers)) if depth is not None: cmdline.append("-d") cmdline.append(str(depth)) @@ -44,13 +45,13 @@ def build_monkey_commandline_explicitly( return cmdline -def get_monkey_commandline_windows(destination_path: str, monkey_cmd_args: list) -> list: +def get_monkey_commandline_windows(destination_path: str, monkey_cmd_args: List[str]) -> List[str]: monkey_cmdline = [CMD_EXE, CMD_CARRY_OUT, destination_path, MONKEY_ARG] return monkey_cmdline + monkey_cmd_args -def get_monkey_commandline_linux(destination_path: str, monkey_cmd_args: list) -> list: +def get_monkey_commandline_linux(destination_path: str, monkey_cmd_args: List[str]) -> List[str]: monkey_cmdline = [destination_path, MONKEY_ARG] return monkey_cmdline + monkey_cmd_args diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py b/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py index f6bbcb691..a20fd9e03 100644 --- a/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py @@ -1,5 +1,6 @@ +import pytest + from infection_monkey.config import GUID -from infection_monkey.model.host import VictimHost from infection_monkey.utils.commands import ( build_monkey_commandline, build_monkey_commandline_explicitly, @@ -13,14 +14,14 @@ def test_build_monkey_commandline_explicitly_arguments(): "-p", "101010", "-s", - "127.127.127.127:5000", + "127.127.127.127:5000,138.138.138.138:5007", "-d", "0", "-l", "C:\\windows\\abc", ] actual = build_monkey_commandline_explicitly( - "101010", "127.127.127.127:5000", 0, "C:\\windows\\abc" + "101010", ["127.127.127.127:5000", "138.138.138.138:5007"], 0, "C:\\windows\\abc" ) assert expected == actual @@ -44,13 +45,12 @@ def test_get_monkey_commandline_windows(): "m0nk3y", "-p", "101010", + "-s", + "127.127.127.127:5000,138.138.138.138:5007", ] actual = get_monkey_commandline_windows( "C:\\windows\\abc", - [ - "-p", - "101010", - ], + ["-p", "101010", "-s", "127.127.127.127:5000,138.138.138.138:5007"], ) assert expected == actual @@ -62,23 +62,30 @@ def test_get_monkey_commandline_linux(): "m0nk3y", "-p", "101010", + "-s", + "127.127.127.127:5000,138.138.138.138:5007", ] actual = get_monkey_commandline_linux( "/home/user/monkey-linux-64", - [ - "-p", - "101010", - ], + ["-p", "101010", "-s", "127.127.127.127:5000,138.138.138.138:5007"], ) assert expected == actual def test_build_monkey_commandline(): - example_host = VictimHost(ip_addr="bla") - example_host.set_island_address("101010", "5000") + servers = ["10.10.10.10:5000", "11.11.11.11:5007"] - expected = f" -p {GUID} -s 101010:5000 -d 0 -l /home/bla" - actual = build_monkey_commandline(target_host=example_host, depth=0, location="/home/bla") + expected = f" -p {GUID} -s 10.10.10.10:5000,11.11.11.11:5007 -d 0 -l /home/bla" + actual = build_monkey_commandline(servers=servers, depth=0, location="/home/bla") + + assert expected == actual + + +@pytest.mark.parametrize("servers", [None, []]) +def test_build_monkey_commandline_empty_servers(servers): + + expected = f" -p {GUID} -d 0 -l /home/bla" + actual = build_monkey_commandline(servers, depth=0, location="/home/bla") assert expected == actual From 4fca8ccf5d3963e39feabf48fcac7ad867a237e8 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Mon, 5 Sep 2022 14:52:24 +0200 Subject: [PATCH 081/175] UT: Fix credentials passed to exploiter test --- .../unit_tests/infection_monkey/master/test_exploiter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/master/test_exploiter.py b/monkey/tests/unit_tests/infection_monkey/master/test_exploiter.py index d8e5ef6b1..d62b40161 100644 --- a/monkey/tests/unit_tests/infection_monkey/master/test_exploiter.py +++ b/monkey/tests/unit_tests/infection_monkey/master/test_exploiter.py @@ -66,7 +66,7 @@ def hosts_to_exploit(hosts): def enqueue_hosts(hosts: Iterable[VictimHost]): - q = Queue() + q: Queue = Queue() for h in hosts: q.put(h) @@ -125,7 +125,7 @@ def test_credentials_passed_to_exploiter(run_exploiters): run_exploiters(mock_puppet, 1) for call_args in mock_puppet.exploit_host.call_args_list: - assert call_args[0][3].get("credentials") == CREDENTIALS_FOR_PROPAGATION + assert call_args[0][4].get("credentials") == CREDENTIALS_FOR_PROPAGATION def test_stop_after_callback(exploiter_config, callback, scan_completed, stop, hosts_to_exploit): From ac058c77883c3b5ab9d94a9b482384129da81a4d Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Wed, 7 Sep 2022 13:20:20 +0200 Subject: [PATCH 082/175] Agent: Add TODO to rework address_to_ip_port to use list of servers --- monkey/infection_monkey/monkey.py | 1 + 1 file changed, 1 insertion(+) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index f8b523b66..feebfd2e6 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -95,6 +95,7 @@ class InfectionMonkey: logger.info("Monkey is initializing...") self._singleton = SystemSingleton() self._opts = self._get_arguments(args) + # TODO: Change address to ip port to use list of servers self._cmd_island_ip, self._cmd_island_port = address_to_ip_port(self._opts.servers) self._control_client = ControlClient(self._opts.servers) # TODO Refactor the telemetry messengers to accept control client From 804bd4eadbfe63ca49d94cbab0eb4ca03fee2696 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Wed, 7 Sep 2022 13:29:37 +0200 Subject: [PATCH 083/175] Agent: Modify find_server to accept list of servers --- monkey/infection_monkey/control.py | 93 +++++++++++++------ .../infection_monkey/test_control.py | 54 +++++++++++ 2 files changed, 117 insertions(+), 30 deletions(-) diff --git a/monkey/infection_monkey/control.py b/monkey/infection_monkey/control.py index 8d1e48a22..5ed3f24a5 100644 --- a/monkey/infection_monkey/control.py +++ b/monkey/infection_monkey/control.py @@ -1,20 +1,24 @@ import json import logging import platform +import socket from socket import gethostname -from typing import Mapping, Optional +from typing import Mapping, Optional, Sequence import requests from requests.exceptions import ConnectionError import infection_monkey.tunnel as tunnel from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT +from common.network.network_utils import address_to_ip_port from infection_monkey.config import GUID from infection_monkey.network.info import get_host_subnets, local_ips +from infection_monkey.network.relay import RELAY_CONTROL_MESSAGE from infection_monkey.transport.http import HTTPConnectProxy from infection_monkey.transport.tcp import TcpProxy from infection_monkey.utils import agent_process from infection_monkey.utils.environment import is_windows_os +from infection_monkey.utils.threading import create_daemon_thread requests.packages.urllib3.disable_warnings() @@ -63,37 +67,66 @@ class ControlClient: timeout=MEDIUM_REQUEST_TIMEOUT, ) - def find_server(self, default_tunnel=None): - logger.debug(f"Trying to wake up with Monkey Island server: {self.server_address}") - if default_tunnel: - logger.debug("default_tunnel: %s" % (default_tunnel,)) + def find_server(self, servers: Sequence[str]): + logger.debug(f"Trying to wake up with servers: {', '.join(servers)}") - try: - debug_message = "Trying to connect to server: %s" % self.server_address - if self.proxies: - debug_message += " through proxies: %s" % self.proxies - logger.debug(debug_message) - requests.get( # noqa: DUO123 - f"https://{self.server_address}/api?action=is-up", - verify=False, - proxies=self.proxies, - timeout=MEDIUM_REQUEST_TIMEOUT, - ) - return True - except ConnectionError as exc: - logger.warning("Error connecting to control server %s: %s", self.server_address, exc) + while servers: + server = servers[0] - if self.proxies: - return False - else: - logger.info("Starting tunnel lookup...") - proxy_find = tunnel.find_tunnel(default=default_tunnel) - if proxy_find: - self.set_proxies(proxy_find) - return self.find_server() - else: - logger.info("No tunnel found") - return False + try: + debug_message = f"Trying to connect to server: {server}" + logger.debug(debug_message) + requests.get( # noqa: DUO123 + f"https://{server}/api?action=is-up", + verify=False, + timeout=MEDIUM_REQUEST_TIMEOUT, + ) + + # We remove the server that has been succesfull + servers.remove(server) + + # TODO: Check how we are going to set the server address that the ControlCLient + # is going to use + # self.server_address = server + + # If we have any other server we send them RELAY_CONTROL_MESSAGE + if servers: + for ss in servers: + t = create_daemon_thread( + target=ControlClient._send_relay_control_message, + name="SendControlRelayMessageThread", + args=(ss,), + ) + t.start() + t.join() + + return True + except ConnectionError as err: + logger.error(f"Unable to connect to server/relay {server}: {err}") + servers.remove(server) + except TimeoutError as err: + logger.error(f"Timed out while connecting to server/relay {server}: {err}") + servers.remove(server) + except Exception as err: + logger.error( + f"Exception encountered when trying to connect to server/relay {server}: {err}" + ) + servers.remove(server) + + return False + + @staticmethod + def _send_relay_control_message(server: str): + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as d_socket: + d_socket.settimeout(MEDIUM_REQUEST_TIMEOUT) + + try: + address, port = address_to_ip_port(server) + d_socket.connect((address, int(port))) + d_socket.send(RELAY_CONTROL_MESSAGE) + logger.info(f"Control message was sent to the server/relay {server}") + except OSError as err: + logger.error(f"Error connecting to socket {server}: {err}") def set_proxies(self, proxy_find): """ diff --git a/monkey/tests/unit_tests/infection_monkey/test_control.py b/monkey/tests/unit_tests/infection_monkey/test_control.py index b90087ebf..6cd3df844 100644 --- a/monkey/tests/unit_tests/infection_monkey/test_control.py +++ b/monkey/tests/unit_tests/infection_monkey/test_control.py @@ -1,7 +1,31 @@ +from unittest.mock import MagicMock + import pytest +import requests from monkey.infection_monkey.control import ControlClient +SERVER_1 = "1.1.1.1:12312" +SERVER_2 = "2.2.2.2:4321" +SERVER_3 = "3.3.3.3:3142" +SERVER_4 = "4.4.4.4:5000" + + +class MockConnectionError: + def __init__(self, *args, **kwargs): + raise requests.exceptions.ConnectionError + + +class RequestsGetArgument: + def __init__(self, *args, **kwargs): + if SERVER_1 in args[0]: + MockConnectionError() + + +@pytest.fixture +def servers(): + return [SERVER_1, SERVER_2, SERVER_3, SERVER_4] + @pytest.mark.parametrize( "is_windows_os,expected_proxy_string", @@ -14,3 +38,33 @@ def test_control_set_proxies(monkeypatch, is_windows_os, expected_proxy_string): control_client.set_proxies(("8.8.8.8", "45455")) assert control_client.proxies["https"] == expected_proxy_string + + +def test_control_find_server_any_exception(monkeypatch, servers): + monkeypatch.setattr("infection_monkey.control.requests.get", MockConnectionError) + + cc = ControlClient(servers) + + return_value = cc.find_server(servers) + + assert return_value is False + assert servers == [] + + +def test_control_find_server_socket(monkeypatch, servers): + mock_connect = MagicMock() + mock_send = MagicMock() + monkeypatch.setattr("infection_monkey.control.requests.get", RequestsGetArgument) + monkeypatch.setattr("infection_monkey.control.socket.socket.connect", mock_connect) + monkeypatch.setattr("infection_monkey.control.socket.socket.send", mock_send) + + cc = ControlClient(servers) + + return_value = cc.find_server(servers) + + assert len(servers) == 2 + assert return_value is True + assert mock_connect.call_count == 2 + assert mock_send.call_count == 2 + # TODO: be sure that connect is called with SERVER_3 and SERVER_4 + # assert mock_connect.call_args == SERVER_3 From a39917d9aa86807bb86ebcf99480bca13e09c814 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Wed, 7 Sep 2022 17:57:47 +0530 Subject: [PATCH 084/175] UT: Fix test_control_find_server_socket to check call parameters --- .../unit_tests/infection_monkey/test_control.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/test_control.py b/monkey/tests/unit_tests/infection_monkey/test_control.py index 6cd3df844..2298589d6 100644 --- a/monkey/tests/unit_tests/infection_monkey/test_control.py +++ b/monkey/tests/unit_tests/infection_monkey/test_control.py @@ -1,8 +1,11 @@ +from unittest import mock from unittest.mock import MagicMock import pytest import requests +from infection_monkey.network.relay import RELAY_CONTROL_MESSAGE +from monkey.common.network.network_utils import address_to_ip_port from monkey.infection_monkey.control import ControlClient SERVER_1 = "1.1.1.1:12312" @@ -65,6 +68,12 @@ def test_control_find_server_socket(monkeypatch, servers): assert len(servers) == 2 assert return_value is True assert mock_connect.call_count == 2 + + server_3_ip, server_3_port = address_to_ip_port(SERVER_3) + server_4_ip, server_4_port = address_to_ip_port(SERVER_4) + mock_connect.assert_has_calls( + [mock.call((server_3_ip, int(server_3_port))), mock.call((server_4_ip, int(server_4_port)))] + ) + assert mock_send.call_count == 2 - # TODO: be sure that connect is called with SERVER_3 and SERVER_4 - # assert mock_connect.call_args == SERVER_3 + mock_send.assert_called_with(RELAY_CONTROL_MESSAGE) From f847757a9a7e4a445364888abb23530c75b43405 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Wed, 7 Sep 2022 18:00:08 +0530 Subject: [PATCH 085/175] UT: Rename test_control_find_server_any_exception -> test_control_find_server__no_available_relays --- monkey/tests/unit_tests/infection_monkey/test_control.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/test_control.py b/monkey/tests/unit_tests/infection_monkey/test_control.py index 2298589d6..181e38d9f 100644 --- a/monkey/tests/unit_tests/infection_monkey/test_control.py +++ b/monkey/tests/unit_tests/infection_monkey/test_control.py @@ -43,14 +43,12 @@ def test_control_set_proxies(monkeypatch, is_windows_os, expected_proxy_string): assert control_client.proxies["https"] == expected_proxy_string -def test_control_find_server_any_exception(monkeypatch, servers): +def test_control_find_server__no_available_relays(monkeypatch, servers): monkeypatch.setattr("infection_monkey.control.requests.get", MockConnectionError) cc = ControlClient(servers) - return_value = cc.find_server(servers) - - assert return_value is False + assert cc.find_server(servers) is False assert servers == [] From 47f838cf9f830e81c3cdcccaf14eb5f51311ff5e Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Wed, 7 Sep 2022 18:01:56 +0530 Subject: [PATCH 086/175] UT: Rename test_control_find_server_socket -> test_control_find_server__control_message_sent_to_necessary_relays --- monkey/tests/unit_tests/infection_monkey/test_control.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/tests/unit_tests/infection_monkey/test_control.py b/monkey/tests/unit_tests/infection_monkey/test_control.py index 181e38d9f..7400e98ac 100644 --- a/monkey/tests/unit_tests/infection_monkey/test_control.py +++ b/monkey/tests/unit_tests/infection_monkey/test_control.py @@ -52,7 +52,7 @@ def test_control_find_server__no_available_relays(monkeypatch, servers): assert servers == [] -def test_control_find_server_socket(monkeypatch, servers): +def test_control_find_server__control_message_sent_to_necessary_relays(monkeypatch, servers): mock_connect = MagicMock() mock_send = MagicMock() monkeypatch.setattr("infection_monkey.control.requests.get", RequestsGetArgument) From 0239a1be6bcb9aad1356c93955d3a204621d08aa Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Wed, 7 Sep 2022 18:07:22 +0530 Subject: [PATCH 087/175] UT: Simplify test logic in test_control_find_server__control_message_sent_to_necessary_relays --- monkey/tests/unit_tests/infection_monkey/test_control.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/test_control.py b/monkey/tests/unit_tests/infection_monkey/test_control.py index 7400e98ac..c5b8c0fb9 100644 --- a/monkey/tests/unit_tests/infection_monkey/test_control.py +++ b/monkey/tests/unit_tests/infection_monkey/test_control.py @@ -61,14 +61,12 @@ def test_control_find_server__control_message_sent_to_necessary_relays(monkeypat cc = ControlClient(servers) - return_value = cc.find_server(servers) - + assert cc.find_server(servers) is True assert len(servers) == 2 - assert return_value is True - assert mock_connect.call_count == 2 server_3_ip, server_3_port = address_to_ip_port(SERVER_3) server_4_ip, server_4_port = address_to_ip_port(SERVER_4) + assert mock_connect.call_count == 2 mock_connect.assert_has_calls( [mock.call((server_3_ip, int(server_3_port))), mock.call((server_4_ip, int(server_4_port)))] ) From cd91b3e42abbfdfe6f49d79f9c254cd8988ea5e4 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Wed, 7 Sep 2022 18:12:24 +0530 Subject: [PATCH 088/175] UT: Rename RequestsGetArgument -> MockRequestsGetResponsePerServerArgument --- monkey/tests/unit_tests/infection_monkey/test_control.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/test_control.py b/monkey/tests/unit_tests/infection_monkey/test_control.py index c5b8c0fb9..7131dc5ab 100644 --- a/monkey/tests/unit_tests/infection_monkey/test_control.py +++ b/monkey/tests/unit_tests/infection_monkey/test_control.py @@ -19,7 +19,7 @@ class MockConnectionError: raise requests.exceptions.ConnectionError -class RequestsGetArgument: +class MockRequestsGetResponsePerServerArgument: def __init__(self, *args, **kwargs): if SERVER_1 in args[0]: MockConnectionError() @@ -55,7 +55,9 @@ def test_control_find_server__no_available_relays(monkeypatch, servers): def test_control_find_server__control_message_sent_to_necessary_relays(monkeypatch, servers): mock_connect = MagicMock() mock_send = MagicMock() - monkeypatch.setattr("infection_monkey.control.requests.get", RequestsGetArgument) + monkeypatch.setattr( + "infection_monkey.control.requests.get", MockRequestsGetResponsePerServerArgument + ) monkeypatch.setattr("infection_monkey.control.socket.socket.connect", mock_connect) monkeypatch.setattr("infection_monkey.control.socket.socket.send", mock_send) From 02a919123b182920b181f9b4611be2453696ef0e Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Wed, 7 Sep 2022 14:25:22 +0200 Subject: [PATCH 089/175] Agent: Remove thread join in find_server --- monkey/infection_monkey/control.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monkey/infection_monkey/control.py b/monkey/infection_monkey/control.py index 5ed3f24a5..606f858fa 100644 --- a/monkey/infection_monkey/control.py +++ b/monkey/infection_monkey/control.py @@ -98,7 +98,6 @@ class ControlClient: args=(ss,), ) t.start() - t.join() return True except ConnectionError as err: From 178b296f755dc5b52627b83a9b29d50f2acb1e08 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Wed, 7 Sep 2022 14:49:32 +0200 Subject: [PATCH 090/175] Agent: Use iterator in ControlClient.find_server --- monkey/infection_monkey/control.py | 32 ++++++++++-------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/monkey/infection_monkey/control.py b/monkey/infection_monkey/control.py index 606f858fa..2d758d5cf 100644 --- a/monkey/infection_monkey/control.py +++ b/monkey/infection_monkey/control.py @@ -70,8 +70,9 @@ class ControlClient: def find_server(self, servers: Sequence[str]): logger.debug(f"Trying to wake up with servers: {', '.join(servers)}") - while servers: - server = servers[0] + server_iterator = (s for s in servers) + + for server in server_iterator: try: debug_message = f"Trying to connect to server: {server}" @@ -82,37 +83,26 @@ class ControlClient: timeout=MEDIUM_REQUEST_TIMEOUT, ) - # We remove the server that has been succesfull - servers.remove(server) - + break # TODO: Check how we are going to set the server address that the ControlCLient # is going to use # self.server_address = server - - # If we have any other server we send them RELAY_CONTROL_MESSAGE - if servers: - for ss in servers: - t = create_daemon_thread( - target=ControlClient._send_relay_control_message, - name="SendControlRelayMessageThread", - args=(ss,), - ) - t.start() - - return True except ConnectionError as err: logger.error(f"Unable to connect to server/relay {server}: {err}") - servers.remove(server) except TimeoutError as err: logger.error(f"Timed out while connecting to server/relay {server}: {err}") - servers.remove(server) except Exception as err: logger.error( f"Exception encountered when trying to connect to server/relay {server}: {err}" ) - servers.remove(server) - return False + for server in server_iterator: + t = create_daemon_thread( + target=ControlClient._send_relay_control_message, + name="SendControlRelayMessageThread", + args=(server,), + ) + t.start() @staticmethod def _send_relay_control_message(server: str): From 789d6b8441cb537a47ba12b20e1d958596cbd1fa Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Wed, 7 Sep 2022 15:06:21 +0200 Subject: [PATCH 091/175] Agent: Move ControlClient.find_server in network/relay/utls.py --- monkey/infection_monkey/control.py | 57 +----------------- monkey/infection_monkey/monkey.py | 3 +- .../infection_monkey/network/relay/utils.py | 58 +++++++++++++++++++ 3 files changed, 61 insertions(+), 57 deletions(-) create mode 100644 monkey/infection_monkey/network/relay/utils.py diff --git a/monkey/infection_monkey/control.py b/monkey/infection_monkey/control.py index 2d758d5cf..3aedeb272 100644 --- a/monkey/infection_monkey/control.py +++ b/monkey/infection_monkey/control.py @@ -1,24 +1,19 @@ import json import logging import platform -import socket from socket import gethostname -from typing import Mapping, Optional, Sequence +from typing import Mapping, Optional import requests -from requests.exceptions import ConnectionError import infection_monkey.tunnel as tunnel from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT -from common.network.network_utils import address_to_ip_port from infection_monkey.config import GUID from infection_monkey.network.info import get_host_subnets, local_ips -from infection_monkey.network.relay import RELAY_CONTROL_MESSAGE from infection_monkey.transport.http import HTTPConnectProxy from infection_monkey.transport.tcp import TcpProxy from infection_monkey.utils import agent_process from infection_monkey.utils.environment import is_windows_os -from infection_monkey.utils.threading import create_daemon_thread requests.packages.urllib3.disable_warnings() @@ -67,56 +62,6 @@ class ControlClient: timeout=MEDIUM_REQUEST_TIMEOUT, ) - def find_server(self, servers: Sequence[str]): - logger.debug(f"Trying to wake up with servers: {', '.join(servers)}") - - server_iterator = (s for s in servers) - - for server in server_iterator: - - try: - debug_message = f"Trying to connect to server: {server}" - logger.debug(debug_message) - requests.get( # noqa: DUO123 - f"https://{server}/api?action=is-up", - verify=False, - timeout=MEDIUM_REQUEST_TIMEOUT, - ) - - break - # TODO: Check how we are going to set the server address that the ControlCLient - # is going to use - # self.server_address = server - except ConnectionError as err: - logger.error(f"Unable to connect to server/relay {server}: {err}") - except TimeoutError as err: - logger.error(f"Timed out while connecting to server/relay {server}: {err}") - except Exception as err: - logger.error( - f"Exception encountered when trying to connect to server/relay {server}: {err}" - ) - - for server in server_iterator: - t = create_daemon_thread( - target=ControlClient._send_relay_control_message, - name="SendControlRelayMessageThread", - args=(server,), - ) - t.start() - - @staticmethod - def _send_relay_control_message(server: str): - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as d_socket: - d_socket.settimeout(MEDIUM_REQUEST_TIMEOUT) - - try: - address, port = address_to_ip_port(server) - d_socket.connect((address, int(port))) - d_socket.send(RELAY_CONTROL_MESSAGE) - logger.info(f"Control message was sent to the server/relay {server}") - except OSError as err: - logger.error(f"Error connecting to socket {server}: {err}") - def set_proxies(self, proxy_find): """ Note: The proxy schema changes between different versions of requests and urllib3, diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index feebfd2e6..1af0266bb 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -42,6 +42,7 @@ from infection_monkey.master.control_channel import ControlChannel from infection_monkey.model import VictimHostFactory from infection_monkey.network.firewall import app as firewall from infection_monkey.network.info import get_network_interfaces +from infection_monkey.network.relay.utils import find_server from infection_monkey.network_scanning.elasticsearch_fingerprinter import ElasticSearchFingerprinter from infection_monkey.network_scanning.http_fingerprinter import HTTPFingerprinter from infection_monkey.network_scanning.mssql_fingerprinter import MSSQLFingerprinter @@ -162,7 +163,7 @@ class InfectionMonkey: self._control_client.wakeup(parent=self._opts.parent) def _current_server_is_set(self) -> bool: - if self._control_client.find_server(default_tunnel=self._opts.servers): + if find_server(servers=self._opts.servers): return True return False diff --git a/monkey/infection_monkey/network/relay/utils.py b/monkey/infection_monkey/network/relay/utils.py new file mode 100644 index 000000000..56462dc33 --- /dev/null +++ b/monkey/infection_monkey/network/relay/utils.py @@ -0,0 +1,58 @@ +import logging +import socket +from typing import Sequence + +import requests + +from common.common_consts.timeouts import MEDIUM_REQUEST_TIMEOUT +from common.network.network_utils import address_to_ip_port +from infection_monkey.network.relay import RELAY_CONTROL_MESSAGE +from infection_monkey.utils.threading import create_daemon_thread + +logger = logging.getLogger(__name__) + + +def find_server(self, servers: Sequence[str]): + logger.debug(f"Trying to wake up with servers: {', '.join(servers)}") + + server_iterator = (s for s in servers) + for server in server_iterator: + try: + debug_message = f"Trying to connect to server: {server}" + logger.debug(debug_message) + requests.get( # noqa: DUO123 + f"https://{server}/api?action=is-up", + verify=False, + timeout=MEDIUM_REQUEST_TIMEOUT, + ) + + break + except requests.exceptions.ConnectionError as err: + logger.error(f"Unable to connect to server/relay {server}: {err}") + except TimeoutError as err: + logger.error(f"Timed out while connecting to server/relay {server}: {err}") + except Exception as err: + logger.error( + f"Exception encountered when trying to connect to server/relay {server}: {err}" + ) + + for server in server_iterator: + t = create_daemon_thread( + target=_send_relay_control_message, + name="SendControlRelayMessageThread", + args=(server,), + ) + t.start() + + +def _send_relay_control_message(server: str): + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as d_socket: + d_socket.settimeout(MEDIUM_REQUEST_TIMEOUT) + + try: + address, port = address_to_ip_port(server) + d_socket.connect((address, int(port))) + d_socket.send(RELAY_CONTROL_MESSAGE) + logger.info(f"Control message was sent to the server/relay {server}") + except OSError as err: + logger.error(f"Error connecting to socket {server}: {err}") From 18659b654fdea601574573a68fb85829cab42dcd Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Wed, 7 Sep 2022 18:57:21 +0530 Subject: [PATCH 092/175] Agent: Return server to connect to or None from `find_server` --- monkey/infection_monkey/network/relay/utils.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/network/relay/utils.py b/monkey/infection_monkey/network/relay/utils.py index 56462dc33..66f0b1d1e 100644 --- a/monkey/infection_monkey/network/relay/utils.py +++ b/monkey/infection_monkey/network/relay/utils.py @@ -1,6 +1,6 @@ import logging import socket -from typing import Sequence +from typing import Optional, Sequence import requests @@ -12,7 +12,9 @@ from infection_monkey.utils.threading import create_daemon_thread logger = logging.getLogger(__name__) -def find_server(self, servers: Sequence[str]): +def find_server(self, servers: Sequence[str]) -> Optional[str]: + server_found = None + logger.debug(f"Trying to wake up with servers: {', '.join(servers)}") server_iterator = (s for s in servers) @@ -26,6 +28,8 @@ def find_server(self, servers: Sequence[str]): timeout=MEDIUM_REQUEST_TIMEOUT, ) + server_found = server + break except requests.exceptions.ConnectionError as err: logger.error(f"Unable to connect to server/relay {server}: {err}") @@ -44,6 +48,8 @@ def find_server(self, servers: Sequence[str]): ) t.start() + return server_found + def _send_relay_control_message(server: str): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as d_socket: From 94dc8cf37785b3b9d1a8ca50d160df98d25a1d82 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Wed, 7 Sep 2022 19:00:51 +0530 Subject: [PATCH 093/175] Agent: Use `find_servers` to pass valid server to `ControlClient` --- monkey/infection_monkey/monkey.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 1af0266bb..2a70b763c 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -96,9 +96,11 @@ class InfectionMonkey: logger.info("Monkey is initializing...") self._singleton = SystemSingleton() self._opts = self._get_arguments(args) - # TODO: Change address to ip port to use list of servers - self._cmd_island_ip, self._cmd_island_port = address_to_ip_port(self._opts.servers) - self._control_client = ControlClient(self._opts.servers) + + server = find_server(self._opts.servers) + self._cmd_island_ip, self._cmd_island_port = address_to_ip_port(server) + self._control_client = ControlClient(server_address=server) + # TODO Refactor the telemetry messengers to accept control client # and remove control_client_object ControlClient.control_client_object = self._control_client From c6c6cf1e7926fb36737ca1750679e62f13f38397 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Wed, 7 Sep 2022 19:02:14 +0530 Subject: [PATCH 094/175] Agent: Add TODO about variable naming in `InfectionMonkey` --- monkey/infection_monkey/monkey.py | 1 + 1 file changed, 1 insertion(+) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 2a70b763c..596cd0d7f 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -97,6 +97,7 @@ class InfectionMonkey: self._singleton = SystemSingleton() self._opts = self._get_arguments(args) + # TODO: Revisit variable names server = find_server(self._opts.servers) self._cmd_island_ip, self._cmd_island_port = address_to_ip_port(server) self._control_client = ControlClient(server_address=server) From bb2b4aaf6c2b58330e1ef44254e4e99e742ecc6d Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Wed, 7 Sep 2022 16:45:47 +0200 Subject: [PATCH 095/175] Agent: Separate responsibilites in network.relay.utils.find_server --- .../infection_monkey/network/relay/utils.py | 19 +++++----- .../network/relay/test_utils.py | 35 +++++++++++++++++++ 2 files changed, 45 insertions(+), 9 deletions(-) create mode 100644 monkey/tests/unit_tests/infection_monkey/network/relay/test_utils.py diff --git a/monkey/infection_monkey/network/relay/utils.py b/monkey/infection_monkey/network/relay/utils.py index 66f0b1d1e..8d3a76277 100644 --- a/monkey/infection_monkey/network/relay/utils.py +++ b/monkey/infection_monkey/network/relay/utils.py @@ -1,6 +1,6 @@ import logging import socket -from typing import Optional, Sequence +from typing import Iterable, Optional import requests @@ -12,13 +12,12 @@ from infection_monkey.utils.threading import create_daemon_thread logger = logging.getLogger(__name__) -def find_server(self, servers: Sequence[str]) -> Optional[str]: +def find_server(servers: Iterable[str]) -> Optional[str]: server_found = None logger.debug(f"Trying to wake up with servers: {', '.join(servers)}") - server_iterator = (s for s in servers) - for server in server_iterator: + for server in servers: try: debug_message = f"Trying to connect to server: {server}" logger.debug(debug_message) @@ -40,18 +39,20 @@ def find_server(self, servers: Sequence[str]) -> Optional[str]: f"Exception encountered when trying to connect to server/relay {server}: {err}" ) - for server in server_iterator: + return server_found + + +def send_relay_control_message(servers: Iterable[str]): + for server in servers: t = create_daemon_thread( - target=_send_relay_control_message, + target=_open_socket_to_server, name="SendControlRelayMessageThread", args=(server,), ) t.start() - return server_found - -def _send_relay_control_message(server: str): +def _open_socket_to_server(server: str): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as d_socket: d_socket.settimeout(MEDIUM_REQUEST_TIMEOUT) diff --git a/monkey/tests/unit_tests/infection_monkey/network/relay/test_utils.py b/monkey/tests/unit_tests/infection_monkey/network/relay/test_utils.py new file mode 100644 index 000000000..15c4f46da --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/network/relay/test_utils.py @@ -0,0 +1,35 @@ +import pytest +import requests + +from infection_monkey.network.relay.utils import find_server + +SERVER_1 = "1.1.1.1:12312" +SERVER_2 = "2.2.2.2:4321" +SERVER_3 = "3.3.3.3:3142" +SERVER_4 = "4.4.4.4:5000" + + +class MockConnectionError: + def __init__(self, *args, **kwargs): + raise requests.exceptions.ConnectionError + + +class MockRequestsGetResponsePerServerArgument: + def __init__(self, *args, **kwargs): + if SERVER_1 in args[0]: + MockConnectionError() + + +@pytest.fixture +def servers(): + return [SERVER_1, SERVER_2, SERVER_3, SERVER_4] + + +@pytest.mark.parametrize( + "mock_requests_get, expected", + [(MockConnectionError, None), (MockRequestsGetResponsePerServerArgument, SERVER_2)], +) +def test_find_server__no_available_relays(monkeypatch, servers, mock_requests_get, expected): + monkeypatch.setattr("infection_monkey.control.requests.get", mock_requests_get) + + assert find_server(servers) is expected From 65226d5a9c4768547cd41ef64a9ca40a3076808f Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Wed, 7 Sep 2022 16:46:44 +0200 Subject: [PATCH 096/175] UT: Move test_find_server under network.relay.utils --- .../infection_monkey/test_control.py | 61 ------------------- 1 file changed, 61 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/test_control.py b/monkey/tests/unit_tests/infection_monkey/test_control.py index 7131dc5ab..b90087ebf 100644 --- a/monkey/tests/unit_tests/infection_monkey/test_control.py +++ b/monkey/tests/unit_tests/infection_monkey/test_control.py @@ -1,34 +1,7 @@ -from unittest import mock -from unittest.mock import MagicMock - import pytest -import requests -from infection_monkey.network.relay import RELAY_CONTROL_MESSAGE -from monkey.common.network.network_utils import address_to_ip_port from monkey.infection_monkey.control import ControlClient -SERVER_1 = "1.1.1.1:12312" -SERVER_2 = "2.2.2.2:4321" -SERVER_3 = "3.3.3.3:3142" -SERVER_4 = "4.4.4.4:5000" - - -class MockConnectionError: - def __init__(self, *args, **kwargs): - raise requests.exceptions.ConnectionError - - -class MockRequestsGetResponsePerServerArgument: - def __init__(self, *args, **kwargs): - if SERVER_1 in args[0]: - MockConnectionError() - - -@pytest.fixture -def servers(): - return [SERVER_1, SERVER_2, SERVER_3, SERVER_4] - @pytest.mark.parametrize( "is_windows_os,expected_proxy_string", @@ -41,37 +14,3 @@ def test_control_set_proxies(monkeypatch, is_windows_os, expected_proxy_string): control_client.set_proxies(("8.8.8.8", "45455")) assert control_client.proxies["https"] == expected_proxy_string - - -def test_control_find_server__no_available_relays(monkeypatch, servers): - monkeypatch.setattr("infection_monkey.control.requests.get", MockConnectionError) - - cc = ControlClient(servers) - - assert cc.find_server(servers) is False - assert servers == [] - - -def test_control_find_server__control_message_sent_to_necessary_relays(monkeypatch, servers): - mock_connect = MagicMock() - mock_send = MagicMock() - monkeypatch.setattr( - "infection_monkey.control.requests.get", MockRequestsGetResponsePerServerArgument - ) - monkeypatch.setattr("infection_monkey.control.socket.socket.connect", mock_connect) - monkeypatch.setattr("infection_monkey.control.socket.socket.send", mock_send) - - cc = ControlClient(servers) - - assert cc.find_server(servers) is True - assert len(servers) == 2 - - server_3_ip, server_3_port = address_to_ip_port(SERVER_3) - server_4_ip, server_4_port = address_to_ip_port(SERVER_4) - assert mock_connect.call_count == 2 - mock_connect.assert_has_calls( - [mock.call((server_3_ip, int(server_3_port))), mock.call((server_4_ip, int(server_4_port)))] - ) - - assert mock_send.call_count == 2 - mock_send.assert_called_with(RELAY_CONTROL_MESSAGE) From e539495545ff554c7787ec027c8a91b97f7b0f94 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Wed, 7 Sep 2022 16:47:47 +0200 Subject: [PATCH 097/175] Agent: Find server and send control relay message to all other servers --- monkey/infection_monkey/monkey.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 596cd0d7f..539d5c61d 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -42,7 +42,7 @@ from infection_monkey.master.control_channel import ControlChannel from infection_monkey.model import VictimHostFactory from infection_monkey.network.firewall import app as firewall from infection_monkey.network.info import get_network_interfaces -from infection_monkey.network.relay.utils import find_server +from infection_monkey.network.relay.utils import find_server, send_relay_control_message from infection_monkey.network_scanning.elasticsearch_fingerprinter import ElasticSearchFingerprinter from infection_monkey.network_scanning.http_fingerprinter import HTTPFingerprinter from infection_monkey.network_scanning.mssql_fingerprinter import MSSQLFingerprinter @@ -98,9 +98,9 @@ class InfectionMonkey: self._opts = self._get_arguments(args) # TODO: Revisit variable names - server = find_server(self._opts.servers) - self._cmd_island_ip, self._cmd_island_port = address_to_ip_port(server) - self._control_client = ControlClient(server_address=server) + self._get_server() + self._cmd_island_ip, self._cmd_island_port = address_to_ip_port(self.server) + self._control_client = ControlClient(server_address=self.server) # TODO Refactor the telemetry messengers to accept control client # and remove control_client_object @@ -122,6 +122,11 @@ class InfectionMonkey: return opts + def _get_server(self): + servers_iterator = (s for s in self._opts.servers) + self.server = find_server(servers_iterator) + send_relay_control_message(servers_iterator) + @staticmethod def _log_arguments(args): arg_string = " ".join([f"{key}: {value}" for key, value in vars(args).items()]) @@ -159,14 +164,13 @@ class InfectionMonkey: logger.debug(f"Default server set to: {self._control_client.server_address}") else: raise Exception( - f"Failed to connect to the island via " - f"any known server address: {self._opts.servers}" + f"Failed to connect to the island via " f"any known server address: {self.server}" ) self._control_client.wakeup(parent=self._opts.parent) def _current_server_is_set(self) -> bool: - if find_server(servers=self._opts.servers): + if self.server: return True return False From 60f9aa6a4e479937dbb8fe1e8e99f733dddd8d1f Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Wed, 7 Sep 2022 19:15:25 +0200 Subject: [PATCH 098/175] Agent: Rework relay.utils.find_server a bit --- monkey/infection_monkey/network/relay/utils.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/monkey/infection_monkey/network/relay/utils.py b/monkey/infection_monkey/network/relay/utils.py index 8d3a76277..459900e0d 100644 --- a/monkey/infection_monkey/network/relay/utils.py +++ b/monkey/infection_monkey/network/relay/utils.py @@ -13,23 +13,19 @@ logger = logging.getLogger(__name__) def find_server(servers: Iterable[str]) -> Optional[str]: - server_found = None - logger.debug(f"Trying to wake up with servers: {', '.join(servers)}") for server in servers: + logger.debug(f"Trying to connect to server: {server}") + try: - debug_message = f"Trying to connect to server: {server}" - logger.debug(debug_message) requests.get( # noqa: DUO123 f"https://{server}/api?action=is-up", verify=False, timeout=MEDIUM_REQUEST_TIMEOUT, ) - server_found = server - - break + return server except requests.exceptions.ConnectionError as err: logger.error(f"Unable to connect to server/relay {server}: {err}") except TimeoutError as err: @@ -39,7 +35,7 @@ def find_server(servers: Iterable[str]) -> Optional[str]: f"Exception encountered when trying to connect to server/relay {server}: {err}" ) - return server_found + return None def send_relay_control_message(servers: Iterable[str]): From a0f566ef49b4b477c2e605f810de1312fef38484 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Wed, 31 Aug 2022 18:44:18 +0000 Subject: [PATCH 099/175] Agent: Add network utilities for connections --- monkey/infection_monkey/network/tools.py | 37 +++++++++++++++++++ .../network/test_network_tools.py | 13 +++++++ 2 files changed, 50 insertions(+) create mode 100644 monkey/tests/unit_tests/infection_monkey/network/test_network_tools.py diff --git a/monkey/infection_monkey/network/tools.py b/monkey/infection_monkey/network/tools.py index c612a7e48..9bf69e28a 100644 --- a/monkey/infection_monkey/network/tools.py +++ b/monkey/infection_monkey/network/tools.py @@ -3,6 +3,7 @@ import select import socket import struct import sys +from typing import List, Tuple, Union from common.common_consts.timeouts import CONNECTION_TIMEOUT from infection_monkey.network.info import get_routes @@ -90,3 +91,39 @@ def get_interface_to_target(dst): paths.sort() ret = paths[-1][1] return ret[1] + + +def connect(connections: List[str]) -> Tuple[socket.socket, str, int]: + """ + Attempt to connect to addresses in the given list. + + :param connections: The addresses to try and connect to. + :return: The socket, address, and port of the connection. + :raises: ConnectionError if no connection could be established. + :raises: ValueError if an improper connection is provided. + """ + for connection in connections: + ip, _, port = connection.rpartition(":") + ip = ip.strip("[]") + sock = try_connect(ip, int(port)) + if sock: + return sock, ip, int(port) + + raise ConnectionError + + +def try_connect(ip: str, port: int) -> Union[socket.socket, None]: + """ + Attempt to establish a connection. + + :param ip: The IP to use. + :param port: The port to use. + :return: The socket on a successful connection, otherwise None. + """ + try: + logging.debug(f"Attempting to connect to {ip}:{port}") + sock = socket.create_connection((ip, port), timeout=1) + except Exception: + return None + + return sock diff --git a/monkey/tests/unit_tests/infection_monkey/network/test_network_tools.py b/monkey/tests/unit_tests/infection_monkey/network/test_network_tools.py new file mode 100644 index 000000000..77a4e8b00 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/network/test_network_tools.py @@ -0,0 +1,13 @@ +import pytest + +from infection_monkey.network.tools import connect + + +def test_connect_raises_with_empty_list(): + with pytest.raises(ConnectionError): + connect([]) + + +def test_connect_raises_with_bad_data(): + with pytest.raises(ValueError): + connect(["no-port"]) From 20172230f180314ce07e12dc06a0e8b09f81975f Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Wed, 7 Sep 2022 19:39:14 +0200 Subject: [PATCH 100/175] Agent: Rework send_control_relay_message a bit --- monkey/infection_monkey/network/relay/utils.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/network/relay/utils.py b/monkey/infection_monkey/network/relay/utils.py index 459900e0d..c6f479f8a 100644 --- a/monkey/infection_monkey/network/relay/utils.py +++ b/monkey/infection_monkey/network/relay/utils.py @@ -52,10 +52,13 @@ def _open_socket_to_server(server: str): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as d_socket: d_socket.settimeout(MEDIUM_REQUEST_TIMEOUT) + ip, port = address_to_ip_port(server) + logger.info(f"Control message was sent to the server/relay {server}") + try: - address, port = address_to_ip_port(server) - d_socket.connect((address, int(port))) + d_socket.connect((ip, int(port))) d_socket.send(RELAY_CONTROL_MESSAGE) - logger.info(f"Control message was sent to the server/relay {server}") except OSError as err: logger.error(f"Error connecting to socket {server}: {err}") + except TimeoutError as err: + logger.error(f"Timed out while connecting to socket {server}: {err}") From 2fd99318baad23f53fc40d5b358a3d289b5f6c89 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Fri, 2 Sep 2022 13:30:06 +0000 Subject: [PATCH 101/175] Agent: Replace MonkeyTunnel with TCPRelay --- monkey/infection_monkey/monkey.py | 49 +++++++++++++------ .../network/relay/__init__.py | 2 + .../infection_monkey/network/relay/utils.py | 22 +++++++++ ...xploit_intercepting_telemetry_messenger.py | 22 ++++----- ...xploit_intercepting_telemetry_messenger.py | 24 ++++----- 5 files changed, 75 insertions(+), 44 deletions(-) create mode 100644 monkey/infection_monkey/network/relay/utils.py diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index f8b523b66..26949ab8e 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -3,7 +3,7 @@ import logging import os import subprocess import sys -from ipaddress import IPv4Interface +from ipaddress import IPv4Address, IPv4Interface from pathlib import Path, WindowsPath from typing import List @@ -41,7 +41,13 @@ from infection_monkey.master import AutomatedMaster from infection_monkey.master.control_channel import ControlChannel from infection_monkey.model import VictimHostFactory from infection_monkey.network.firewall import app as firewall -from infection_monkey.network.info import get_network_interfaces +from infection_monkey.network.info import get_free_tcp_port, get_network_interfaces +from infection_monkey.network.relay import ( + build_tcprelay_deps, + RelayUserHandler, + TCPRelay, +) +from infection_monkey.network.tools import connect from infection_monkey.network_scanning.elasticsearch_fingerprinter import ElasticSearchFingerprinter from infection_monkey.network_scanning.http_fingerprinter import HTTPFingerprinter from infection_monkey.network_scanning.mssql_fingerprinter import MSSQLFingerprinter @@ -100,11 +106,11 @@ class InfectionMonkey: # TODO Refactor the telemetry messengers to accept control client # and remove control_client_object ControlClient.control_client_object = self._control_client - self._monkey_inbound_tunnel = None self._telemetry_messenger = LegacyTelemetryMessengerAdapter() self._current_depth = self._opts.depth self._master = None - self._inbound_tunnel_opened = False + self._relay_user_handler: RelayUserHandler + self._relay: TCPRelay @staticmethod def _get_arguments(args): @@ -180,14 +186,27 @@ class InfectionMonkey: control_channel.register_agent(self._opts.parent) config = control_channel.get_config() - self._monkey_inbound_tunnel = self._control_client.create_control_tunnel( - config.keep_tunnel_open_time + + local_port = get_free_tcp_port() + sock, ip_str, port = connect([self._opts.server]) + sock.close() + user_handler, connection_handler, pipe_spawner = build_tcprelay_deps( + local_port, + IPv4Address(ip_str), + port, + client_disconnect_timeout=config.keep_tunnel_open_time, ) - if self._monkey_inbound_tunnel and maximum_depth_reached( + self._relay_user_handler = user_handler + self._relay = TCPRelay( + self._relay_user_handler, + connection_handler, + pipe_spawner, + ) + + if self._relay and maximum_depth_reached( config.propagation.maximum_depth, self._current_depth ): - self._inbound_tunnel_opened = True - self._monkey_inbound_tunnel.start() + self._relay.start() StateTelem(is_done=False, version=get_version()).send() TunnelTelem(self._control_client.proxies).send() @@ -215,7 +234,7 @@ class InfectionMonkey: victim_host_factory = self._build_victim_host_factory(local_network_interfaces) telemetry_messenger = ExploitInterceptingTelemetryMessenger( - self._telemetry_messenger, self._monkey_inbound_tunnel + self._telemetry_messenger, self._relay_user_handler ) self._master = AutomatedMaster( @@ -374,9 +393,7 @@ class InfectionMonkey: on_island = self._running_on_island(local_network_interfaces) logger.debug(f"This agent is running on the island: {on_island}") - return VictimHostFactory( - self._monkey_inbound_tunnel, self._cmd_island_ip, self._cmd_island_port, on_island - ) + return VictimHostFactory(None, self._cmd_island_ip, self._cmd_island_port, on_island) def _running_on_island(self, local_network_interfaces: List[IPv4Interface]) -> bool: server_ip, _ = address_to_ip_port(self._control_client.server_address) @@ -394,9 +411,9 @@ class InfectionMonkey: reset_signal_handlers() - if self._inbound_tunnel_opened: - self._monkey_inbound_tunnel.stop() - self._monkey_inbound_tunnel.join() + if self._relay and self._relay.is_alive(): + self._relay.stop() + self._relay.join() if firewall.is_enabled(): firewall.remove_firewall_rule() diff --git a/monkey/infection_monkey/network/relay/__init__.py b/monkey/infection_monkey/network/relay/__init__.py index ad5d325c5..8a4ec4731 100644 --- a/monkey/infection_monkey/network/relay/__init__.py +++ b/monkey/infection_monkey/network/relay/__init__.py @@ -3,3 +3,5 @@ from .relay_user_handler import RelayUser, RelayUserHandler from .sockets_pipe import SocketsPipe from .tcp_connection_handler import TCPConnectionHandler from .tcp_pipe_spawner import TCPPipeSpawner +from .tcp_relay import TCPRelay +from .utils import build_tcprelay_deps diff --git a/monkey/infection_monkey/network/relay/utils.py b/monkey/infection_monkey/network/relay/utils.py new file mode 100644 index 000000000..e4321ad43 --- /dev/null +++ b/monkey/infection_monkey/network/relay/utils.py @@ -0,0 +1,22 @@ +from . import TCPConnectionHandler, TCPPipeSpawner, RelayUserHandler, RelayConnectionHandler +from ipaddress import IPv4Address +from typing import Tuple + + +def build_tcprelay_deps( + local_port: int, dest_addr: IPv4Address, dest_port: int, client_disconnect_timeout: float +) -> Tuple[RelayUserHandler, TCPPipeSpawner, TCPConnectionHandler]: + + # TODO: Add the timeouts + relay_user_handler = RelayUserHandler() + pipe_spawner = TCPPipeSpawner(dest_addr, dest_port) + relay_filter = RelayConnectionHandler(pipe_spawner, relay_user_handler) + connection_handler = TCPConnectionHandler( + bind_host="", + bind_port=local_port, + client_connected=[ + relay_filter.handle_new_connection, + ], + ) + + return relay_user_handler, pipe_spawner, connection_handler diff --git a/monkey/infection_monkey/telemetry/messengers/exploit_intercepting_telemetry_messenger.py b/monkey/infection_monkey/telemetry/messengers/exploit_intercepting_telemetry_messenger.py index 1e5c6591a..f151591f3 100644 --- a/monkey/infection_monkey/telemetry/messengers/exploit_intercepting_telemetry_messenger.py +++ b/monkey/infection_monkey/telemetry/messengers/exploit_intercepting_telemetry_messenger.py @@ -1,23 +1,21 @@ from functools import singledispatch from ipaddress import IPv4Address -from infection_monkey.network.relay.tcp_relay import TCPRelay +from infection_monkey.network.relay import RelayUserHandler from infection_monkey.telemetry.exploit_telem import ExploitTelem from infection_monkey.telemetry.i_telem import ITelem from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger -from infection_monkey.tunnel import MonkeyTunnel class ExploitInterceptingTelemetryMessenger(ITelemetryMessenger): def __init__( - self, telemetry_messenger: ITelemetryMessenger, tunnel: MonkeyTunnel, relay: TCPRelay + self, telemetry_messenger: ITelemetryMessenger, relay_user_handler: RelayUserHandler ): self._telemetry_messenger = telemetry_messenger - self._tunnel = tunnel - self._relay = relay + self._relay_user_handler = relay_user_handler def send_telemetry(self, telemetry: ITelem): - _send_telemetry(telemetry, self._telemetry_messenger, self._tunnel, self._relay) + _send_telemetry(telemetry, self._telemetry_messenger, self._relay_user_handler) # Note: We can use @singledispatchmethod instead of @singledispatch if we migrate to Python 3.8 or @@ -26,8 +24,7 @@ class ExploitInterceptingTelemetryMessenger(ITelemetryMessenger): def _send_telemetry( telemetry: ITelem, telemetry_messenger: ITelemetryMessenger, - tunnel: MonkeyTunnel, - relay: TCPRelay, + relay_user_handler: RelayUserHandler, ): telemetry_messenger.send_telemetry(telemetry) @@ -36,12 +33,11 @@ def _send_telemetry( def _( telemetry: ExploitTelem, telemetry_messenger: ITelemetryMessenger, - tunnel: MonkeyTunnel, - relay: TCPRelay, + relay_user_handler: RelayUserHandler, ): if telemetry.propagation_result is True: - tunnel.set_wait_for_exploited_machines() - if relay: - relay.add_potential_user(IPv4Address(telemetry.host["ip_addr"])) + if relay_user_handler: + address = IPv4Address(str(telemetry.host["ip_addr"])) + relay_user_handler.add_potential_user(address) telemetry_messenger.send_telemetry(telemetry) diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_exploit_intercepting_telemetry_messenger.py b/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_exploit_intercepting_telemetry_messenger.py index 0758a5e4d..50533fc75 100644 --- a/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_exploit_intercepting_telemetry_messenger.py +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_exploit_intercepting_telemetry_messenger.py @@ -20,49 +20,43 @@ class MockExploitTelem(ExploitTelem): def test_generic_telemetry(TestTelem): mock_telemetry_messenger = MagicMock() - mock_tunnel = MagicMock() - mock_relay = MagicMock() + mock_relay_user_handler = MagicMock() telemetry_messenger = ExploitInterceptingTelemetryMessenger( - mock_telemetry_messenger, mock_tunnel, mock_relay + mock_telemetry_messenger, mock_relay_user_handler ) telemetry_messenger.send_telemetry(TestTelem()) assert mock_telemetry_messenger.send_telemetry.called - assert not mock_tunnel.set_wait_for_exploited_machines.called - assert not mock_relay.add_potential_user.called + assert not mock_relay_user_handler.add_potential_user.called def test_propagation_successful_exploit_telemetry(): mock_telemetry_messenger = MagicMock() - mock_tunnel = MagicMock() - mock_relay = MagicMock() + mock_relay_user_handler = MagicMock() mock_exploit_telem = MockExploitTelem(True) telemetry_messenger = ExploitInterceptingTelemetryMessenger( - mock_telemetry_messenger, mock_tunnel, mock_relay + mock_telemetry_messenger, mock_relay_user_handler ) telemetry_messenger.send_telemetry(mock_exploit_telem) assert mock_telemetry_messenger.send_telemetry.called - assert mock_tunnel.set_wait_for_exploited_machines.called - assert mock_relay.add_potential_user.called + assert mock_relay_user_handler.add_potential_user.called def test_propagation_failed_exploit_telemetry(): mock_telemetry_messenger = MagicMock() - mock_tunnel = MagicMock() - mock_relay = MagicMock() + mock_relay_user_handler = MagicMock() mock_exploit_telem = MockExploitTelem(False) telemetry_messenger = ExploitInterceptingTelemetryMessenger( - mock_telemetry_messenger, mock_tunnel, mock_relay + mock_telemetry_messenger, mock_relay_user_handler ) telemetry_messenger.send_telemetry(mock_exploit_telem) assert mock_telemetry_messenger.send_telemetry.called - assert not mock_tunnel.set_wait_for_exploited_machines.called - assert not mock_relay.add_potential_user.called + assert not mock_relay_user_handler.add_potential_user.called From b89ba06fd16cfa4506974aa90a687d80c7137727 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Wed, 7 Sep 2022 19:47:14 +0200 Subject: [PATCH 102/175] UT: Rename test_find_server__no_available_relays to test_find_server --- .../unit_tests/infection_monkey/network/relay/test_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/tests/unit_tests/infection_monkey/network/relay/test_utils.py b/monkey/tests/unit_tests/infection_monkey/network/relay/test_utils.py index 15c4f46da..bf4d70056 100644 --- a/monkey/tests/unit_tests/infection_monkey/network/relay/test_utils.py +++ b/monkey/tests/unit_tests/infection_monkey/network/relay/test_utils.py @@ -29,7 +29,7 @@ def servers(): "mock_requests_get, expected", [(MockConnectionError, None), (MockRequestsGetResponsePerServerArgument, SERVER_2)], ) -def test_find_server__no_available_relays(monkeypatch, servers, mock_requests_get, expected): +def test_find_server(monkeypatch, servers, mock_requests_get, expected): monkeypatch.setattr("infection_monkey.control.requests.get", mock_requests_get) assert find_server(servers) is expected From 732751f0078045bbacbcdb5edc5f3a5f5eab9968 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Fri, 2 Sep 2022 13:49:41 +0000 Subject: [PATCH 103/175] Project: Fix vulture warnings --- vulture_allowlist.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/vulture_allowlist.py b/vulture_allowlist.py index 5578f3fff..65756c95f 100644 --- a/vulture_allowlist.py +++ b/vulture_allowlist.py @@ -299,10 +299,9 @@ event deserialize serialized_event -# TODO: Remove after #2231 is closed -relay_users -last_update_time -add_relay_user +# TODO: Remove when removing Tunnel code +create_control_tunnel +set_wait_for_exploited_machines # pydantic base models underscore_attrs_are_private From 6ee15e22b83c8fa7c69a68c4ad18d601ac069029 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Wed, 7 Sep 2022 19:53:54 +0200 Subject: [PATCH 104/175] Agent: Rework call of find_server in monkey.py --- monkey/infection_monkey/monkey.py | 36 ++++++++++++------------------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 539d5c61d..a7dfe4aa8 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -98,9 +98,9 @@ class InfectionMonkey: self._opts = self._get_arguments(args) # TODO: Revisit variable names - self._get_server() - self._cmd_island_ip, self._cmd_island_port = address_to_ip_port(self.server) - self._control_client = ControlClient(server_address=self.server) + server = self._get_server() + self._cmd_island_ip, self._cmd_island_port = address_to_ip_port(server) + self._control_client = ControlClient(server_address=server) # TODO Refactor the telemetry messengers to accept control client # and remove control_client_object @@ -124,9 +124,18 @@ class InfectionMonkey: def _get_server(self): servers_iterator = (s for s in self._opts.servers) - self.server = find_server(servers_iterator) + server = find_server(servers_iterator) + if server: + logger.debug(f"Default server set to: {server}") + else: + # TODO: Exit here + raise Exception( + f"Failed to connect to the island via any known servers: {self._opts.servers}" + ) send_relay_control_message(servers_iterator) + return server + @staticmethod def _log_arguments(args): arg_string = " ".join([f"{key}: {value}" for key, value in vars(args).items()]) @@ -140,7 +149,7 @@ class InfectionMonkey: logger.info("Agent is starting...") logger.info(f"Agent GUID: {GUID}") - self._connect_to_island() + self._control_client.wakeup(parent=self._opts.parent) # TODO: Reevaluate who is responsible to send this information if is_windows_os(): @@ -158,23 +167,6 @@ class InfectionMonkey: self._setup() self._master.start() - def _connect_to_island(self): - # Sets island's IP and port for monkey to communicate to - if self._current_server_is_set(): - logger.debug(f"Default server set to: {self._control_client.server_address}") - else: - raise Exception( - f"Failed to connect to the island via " f"any known server address: {self.server}" - ) - - self._control_client.wakeup(parent=self._opts.parent) - - def _current_server_is_set(self) -> bool: - if self.server: - return True - - return False - def _setup(self): logger.debug("Starting the setup phase.") From 5c7c54e45045234330aa06111e642c0a359e7b97 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Wed, 7 Sep 2022 12:17:49 +0000 Subject: [PATCH 105/175] Agent: Use timeout in build_tcprelay_deps() --- monkey/infection_monkey/network/relay/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/network/relay/utils.py b/monkey/infection_monkey/network/relay/utils.py index e4321ad43..81bb00203 100644 --- a/monkey/infection_monkey/network/relay/utils.py +++ b/monkey/infection_monkey/network/relay/utils.py @@ -1,14 +1,14 @@ -from . import TCPConnectionHandler, TCPPipeSpawner, RelayUserHandler, RelayConnectionHandler from ipaddress import IPv4Address from typing import Tuple +from . import RelayConnectionHandler, RelayUserHandler, TCPConnectionHandler, TCPPipeSpawner + def build_tcprelay_deps( local_port: int, dest_addr: IPv4Address, dest_port: int, client_disconnect_timeout: float ) -> Tuple[RelayUserHandler, TCPPipeSpawner, TCPConnectionHandler]: - # TODO: Add the timeouts - relay_user_handler = RelayUserHandler() + relay_user_handler = RelayUserHandler(client_disconnect_timeout=client_disconnect_timeout) pipe_spawner = TCPPipeSpawner(dest_addr, dest_port) relay_filter = RelayConnectionHandler(pipe_spawner, relay_user_handler) connection_handler = TCPConnectionHandler( From fb1554840ac5f87e8e21cc26040e63b3118250bf Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Thu, 8 Sep 2022 13:22:13 +0530 Subject: [PATCH 106/175] Agent: Fix log message in InfectionMonkey._get_server() --- monkey/infection_monkey/monkey.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index a7dfe4aa8..efa3046b0 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -126,7 +126,7 @@ class InfectionMonkey: servers_iterator = (s for s in self._opts.servers) server = find_server(servers_iterator) if server: - logger.debug(f"Default server set to: {server}") + logger.info(f"Successfully connected to the island via {server}") else: # TODO: Exit here raise Exception( From bbdc378a0ddbca9ac8631d53bb05a1090fdcbc86 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Wed, 7 Sep 2022 13:31:11 +0000 Subject: [PATCH 107/175] Agent: Hand TCPRelay build it's own dependencies --- monkey/infection_monkey/monkey.py | 17 ++------- .../network/relay/__init__.py | 1 - .../network/relay/tcp_relay.py | 36 +++++++++++++++---- .../infection_monkey/network/relay/utils.py | 22 ------------ ...xploit_intercepting_telemetry_messenger.py | 18 +++++----- ...xploit_intercepting_telemetry_messenger.py | 18 +++++----- 6 files changed, 49 insertions(+), 63 deletions(-) delete mode 100644 monkey/infection_monkey/network/relay/utils.py diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 26949ab8e..c6337148d 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -42,11 +42,7 @@ from infection_monkey.master.control_channel import ControlChannel from infection_monkey.model import VictimHostFactory from infection_monkey.network.firewall import app as firewall from infection_monkey.network.info import get_free_tcp_port, get_network_interfaces -from infection_monkey.network.relay import ( - build_tcprelay_deps, - RelayUserHandler, - TCPRelay, -) +from infection_monkey.network.relay import TCPRelay from infection_monkey.network.tools import connect from infection_monkey.network_scanning.elasticsearch_fingerprinter import ElasticSearchFingerprinter from infection_monkey.network_scanning.http_fingerprinter import HTTPFingerprinter @@ -109,7 +105,6 @@ class InfectionMonkey: self._telemetry_messenger = LegacyTelemetryMessengerAdapter() self._current_depth = self._opts.depth self._master = None - self._relay_user_handler: RelayUserHandler self._relay: TCPRelay @staticmethod @@ -190,18 +185,12 @@ class InfectionMonkey: local_port = get_free_tcp_port() sock, ip_str, port = connect([self._opts.server]) sock.close() - user_handler, connection_handler, pipe_spawner = build_tcprelay_deps( + self._relay = TCPRelay( local_port, IPv4Address(ip_str), port, client_disconnect_timeout=config.keep_tunnel_open_time, ) - self._relay_user_handler = user_handler - self._relay = TCPRelay( - self._relay_user_handler, - connection_handler, - pipe_spawner, - ) if self._relay and maximum_depth_reached( config.propagation.maximum_depth, self._current_depth @@ -234,7 +223,7 @@ class InfectionMonkey: victim_host_factory = self._build_victim_host_factory(local_network_interfaces) telemetry_messenger = ExploitInterceptingTelemetryMessenger( - self._telemetry_messenger, self._relay_user_handler + self._telemetry_messenger, self._relay ) self._master = AutomatedMaster( diff --git a/monkey/infection_monkey/network/relay/__init__.py b/monkey/infection_monkey/network/relay/__init__.py index 8a4ec4731..563b972c4 100644 --- a/monkey/infection_monkey/network/relay/__init__.py +++ b/monkey/infection_monkey/network/relay/__init__.py @@ -4,4 +4,3 @@ from .sockets_pipe import SocketsPipe from .tcp_connection_handler import TCPConnectionHandler from .tcp_pipe_spawner import TCPPipeSpawner from .tcp_relay import TCPRelay -from .utils import build_tcprelay_deps diff --git a/monkey/infection_monkey/network/relay/tcp_relay.py b/monkey/infection_monkey/network/relay/tcp_relay.py index f605cdc9d..a8793d91f 100644 --- a/monkey/infection_monkey/network/relay/tcp_relay.py +++ b/monkey/infection_monkey/network/relay/tcp_relay.py @@ -1,7 +1,13 @@ +from ipaddress import IPv4Address from threading import Lock, Thread from time import sleep -from infection_monkey.network.relay import RelayUserHandler, TCPConnectionHandler, TCPPipeSpawner +from infection_monkey.network.relay import ( + RelayConnectionHandler, + RelayUserHandler, + TCPConnectionHandler, + TCPPipeSpawner, +) from infection_monkey.utils.threading import InterruptableThreadMixin @@ -12,13 +18,21 @@ class TCPRelay(Thread, InterruptableThreadMixin): def __init__( self, - relay_user_handler: RelayUserHandler, - connection_handler: TCPConnectionHandler, - pipe_spawner: TCPPipeSpawner, + local_port: int, + dest_addr: IPv4Address, + dest_port: int, + client_disconnect_timeout: float, ): - self._user_handler = relay_user_handler - self._connection_handler = connection_handler - self._pipe_spawner = pipe_spawner + self._user_handler = RelayUserHandler(client_disconnect_timeout=client_disconnect_timeout) + self._pipe_spawner = TCPPipeSpawner(dest_addr, dest_port) + relay_filter = RelayConnectionHandler(self._pipe_spawner, self._user_handler) + self._connection_handler = TCPConnectionHandler( + bind_host="", + bind_port=local_port, + client_connected=[ + relay_filter.handle_new_connection, + ], + ) super().__init__(name="MonkeyTcpRelayThread", daemon=True) self._lock = Lock() @@ -32,6 +46,14 @@ class TCPRelay(Thread, InterruptableThreadMixin): self._connection_handler.join() self._wait_for_pipes_to_close() + def add_potential_user(self, user_address: IPv4Address): + """ + Notify TCPRelay of a user that may try to connect. + + :param user_address: The address of the potential new user. + """ + self._user_handler.add_potential_user(user_address) + def _wait_for_users_to_disconnect(self): """ Blocks until the users disconnect or the timeout has elapsed. diff --git a/monkey/infection_monkey/network/relay/utils.py b/monkey/infection_monkey/network/relay/utils.py deleted file mode 100644 index 81bb00203..000000000 --- a/monkey/infection_monkey/network/relay/utils.py +++ /dev/null @@ -1,22 +0,0 @@ -from ipaddress import IPv4Address -from typing import Tuple - -from . import RelayConnectionHandler, RelayUserHandler, TCPConnectionHandler, TCPPipeSpawner - - -def build_tcprelay_deps( - local_port: int, dest_addr: IPv4Address, dest_port: int, client_disconnect_timeout: float -) -> Tuple[RelayUserHandler, TCPPipeSpawner, TCPConnectionHandler]: - - relay_user_handler = RelayUserHandler(client_disconnect_timeout=client_disconnect_timeout) - pipe_spawner = TCPPipeSpawner(dest_addr, dest_port) - relay_filter = RelayConnectionHandler(pipe_spawner, relay_user_handler) - connection_handler = TCPConnectionHandler( - bind_host="", - bind_port=local_port, - client_connected=[ - relay_filter.handle_new_connection, - ], - ) - - return relay_user_handler, pipe_spawner, connection_handler diff --git a/monkey/infection_monkey/telemetry/messengers/exploit_intercepting_telemetry_messenger.py b/monkey/infection_monkey/telemetry/messengers/exploit_intercepting_telemetry_messenger.py index f151591f3..cb59aa0ae 100644 --- a/monkey/infection_monkey/telemetry/messengers/exploit_intercepting_telemetry_messenger.py +++ b/monkey/infection_monkey/telemetry/messengers/exploit_intercepting_telemetry_messenger.py @@ -1,21 +1,19 @@ from functools import singledispatch from ipaddress import IPv4Address -from infection_monkey.network.relay import RelayUserHandler +from infection_monkey.network.relay import TCPRelay from infection_monkey.telemetry.exploit_telem import ExploitTelem from infection_monkey.telemetry.i_telem import ITelem from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger class ExploitInterceptingTelemetryMessenger(ITelemetryMessenger): - def __init__( - self, telemetry_messenger: ITelemetryMessenger, relay_user_handler: RelayUserHandler - ): + def __init__(self, telemetry_messenger: ITelemetryMessenger, relay: TCPRelay): self._telemetry_messenger = telemetry_messenger - self._relay_user_handler = relay_user_handler + self._relay = relay def send_telemetry(self, telemetry: ITelem): - _send_telemetry(telemetry, self._telemetry_messenger, self._relay_user_handler) + _send_telemetry(telemetry, self._telemetry_messenger, self._relay) # Note: We can use @singledispatchmethod instead of @singledispatch if we migrate to Python 3.8 or @@ -24,7 +22,7 @@ class ExploitInterceptingTelemetryMessenger(ITelemetryMessenger): def _send_telemetry( telemetry: ITelem, telemetry_messenger: ITelemetryMessenger, - relay_user_handler: RelayUserHandler, + relay: TCPRelay, ): telemetry_messenger.send_telemetry(telemetry) @@ -33,11 +31,11 @@ def _send_telemetry( def _( telemetry: ExploitTelem, telemetry_messenger: ITelemetryMessenger, - relay_user_handler: RelayUserHandler, + relay: TCPRelay, ): if telemetry.propagation_result is True: - if relay_user_handler: + if relay: address = IPv4Address(str(telemetry.host["ip_addr"])) - relay_user_handler.add_potential_user(address) + relay.add_potential_user(address) telemetry_messenger.send_telemetry(telemetry) diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_exploit_intercepting_telemetry_messenger.py b/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_exploit_intercepting_telemetry_messenger.py index 50533fc75..61ca1b971 100644 --- a/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_exploit_intercepting_telemetry_messenger.py +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_exploit_intercepting_telemetry_messenger.py @@ -20,43 +20,43 @@ class MockExploitTelem(ExploitTelem): def test_generic_telemetry(TestTelem): mock_telemetry_messenger = MagicMock() - mock_relay_user_handler = MagicMock() + mock_relay = MagicMock() telemetry_messenger = ExploitInterceptingTelemetryMessenger( - mock_telemetry_messenger, mock_relay_user_handler + mock_telemetry_messenger, mock_relay ) telemetry_messenger.send_telemetry(TestTelem()) assert mock_telemetry_messenger.send_telemetry.called - assert not mock_relay_user_handler.add_potential_user.called + assert not mock_relay.add_potential_user.called def test_propagation_successful_exploit_telemetry(): mock_telemetry_messenger = MagicMock() - mock_relay_user_handler = MagicMock() + mock_relay = MagicMock() mock_exploit_telem = MockExploitTelem(True) telemetry_messenger = ExploitInterceptingTelemetryMessenger( - mock_telemetry_messenger, mock_relay_user_handler + mock_telemetry_messenger, mock_relay ) telemetry_messenger.send_telemetry(mock_exploit_telem) assert mock_telemetry_messenger.send_telemetry.called - assert mock_relay_user_handler.add_potential_user.called + assert mock_relay.add_potential_user.called def test_propagation_failed_exploit_telemetry(): mock_telemetry_messenger = MagicMock() - mock_relay_user_handler = MagicMock() + mock_relay = MagicMock() mock_exploit_telem = MockExploitTelem(False) telemetry_messenger = ExploitInterceptingTelemetryMessenger( - mock_telemetry_messenger, mock_relay_user_handler + mock_telemetry_messenger, mock_relay ) telemetry_messenger.send_telemetry(mock_exploit_telem) assert mock_telemetry_messenger.send_telemetry.called - assert not mock_relay_user_handler.add_potential_user.called + assert not mock_relay.add_potential_user.called From f436bf7b8c757d9e561310e1ca885b0ec921ce45 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Thu, 8 Sep 2022 13:24:11 +0530 Subject: [PATCH 108/175] Agent: Remove irrelevant comment from InfectionMonkey._get_server() --- monkey/infection_monkey/monkey.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index efa3046b0..4a8685aae 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -128,7 +128,6 @@ class InfectionMonkey: if server: logger.info(f"Successfully connected to the island via {server}") else: - # TODO: Exit here raise Exception( f"Failed to connect to the island via any known servers: {self._opts.servers}" ) From f212425842a7e827b8725fdf2c0d83960af0e402 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Wed, 7 Sep 2022 13:45:07 +0000 Subject: [PATCH 109/175] Agent: Pass server list to connect() --- monkey/infection_monkey/monkey.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index c6337148d..3d24c93d3 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -183,7 +183,7 @@ class InfectionMonkey: config = control_channel.get_config() local_port = get_free_tcp_port() - sock, ip_str, port = connect([self._opts.server]) + sock, ip_str, port = connect(self._opts.servers) sock.close() self._relay = TCPRelay( local_port, From 78d32053a219551006743737e27b71dfbcd8f3ad Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Thu, 8 Sep 2022 13:30:56 +0530 Subject: [PATCH 110/175] Agent: Rename send_relay_control_message -> send_remove_from_waitlist_control_message_to_relays --- monkey/infection_monkey/monkey.py | 7 +++++-- monkey/infection_monkey/network/relay/utils.py | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 4a8685aae..6d8e6bac6 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -42,7 +42,10 @@ from infection_monkey.master.control_channel import ControlChannel from infection_monkey.model import VictimHostFactory from infection_monkey.network.firewall import app as firewall from infection_monkey.network.info import get_network_interfaces -from infection_monkey.network.relay.utils import find_server, send_relay_control_message +from infection_monkey.network.relay.utils import ( + find_server, + send_remove_from_waitlist_control_message_to_relays, +) from infection_monkey.network_scanning.elasticsearch_fingerprinter import ElasticSearchFingerprinter from infection_monkey.network_scanning.http_fingerprinter import HTTPFingerprinter from infection_monkey.network_scanning.mssql_fingerprinter import MSSQLFingerprinter @@ -131,7 +134,7 @@ class InfectionMonkey: raise Exception( f"Failed to connect to the island via any known servers: {self._opts.servers}" ) - send_relay_control_message(servers_iterator) + send_remove_from_waitlist_control_message_to_relays(servers_iterator) return server diff --git a/monkey/infection_monkey/network/relay/utils.py b/monkey/infection_monkey/network/relay/utils.py index c6f479f8a..91bbf75fc 100644 --- a/monkey/infection_monkey/network/relay/utils.py +++ b/monkey/infection_monkey/network/relay/utils.py @@ -38,7 +38,7 @@ def find_server(servers: Iterable[str]) -> Optional[str]: return None -def send_relay_control_message(servers: Iterable[str]): +def send_remove_from_waitlist_control_message_to_relays(servers: Iterable[str]): for server in servers: t = create_daemon_thread( target=_open_socket_to_server, From aac0bfe90be76c2ba5309432b2c3f0e5605cd7f1 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Wed, 7 Sep 2022 13:58:31 +0000 Subject: [PATCH 111/175] Agent: Use addres_to_ip_port in connect() --- monkey/common/network/network_utils.py | 7 +++++++ monkey/infection_monkey/network/tools.py | 8 +++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/monkey/common/network/network_utils.py b/monkey/common/network/network_utils.py index f686268e0..13a54dce1 100644 --- a/monkey/common/network/network_utils.py +++ b/monkey/common/network/network_utils.py @@ -2,6 +2,13 @@ from typing import Optional, Tuple def address_to_ip_port(address: str) -> Tuple[str, Optional[str]]: + """ + Split a string containing an IP address (and optionally a port) into IP and Port components. + Currently only works for IPv4 addresses. + + :param address: The address string. + :return: Tuple of IP and port strings. The port may be None if no port was in the address. + """ if ":" in address: ip, port = address.split(":") return ip, port or None diff --git a/monkey/infection_monkey/network/tools.py b/monkey/infection_monkey/network/tools.py index 9bf69e28a..1f0c276e3 100644 --- a/monkey/infection_monkey/network/tools.py +++ b/monkey/infection_monkey/network/tools.py @@ -6,6 +6,7 @@ import sys from typing import List, Tuple, Union from common.common_consts.timeouts import CONNECTION_TIMEOUT +from common.network.network_utils import address_to_ip_port from infection_monkey.network.info import get_routes DEFAULT_TIMEOUT = CONNECTION_TIMEOUT @@ -103,13 +104,14 @@ def connect(connections: List[str]) -> Tuple[socket.socket, str, int]: :raises: ValueError if an improper connection is provided. """ for connection in connections: - ip, _, port = connection.rpartition(":") - ip = ip.strip("[]") + ip, port = address_to_ip_port(connection) + if port is None: + raise ValueError("Connection does not contain a port") sock = try_connect(ip, int(port)) if sock: return sock, ip, int(port) - raise ConnectionError + raise ConnectionError("Could not connect to a server in the server list") def try_connect(ip: str, port: int) -> Union[socket.socket, None]: From 6bfe6bc79d3d480bab39af6f54aeda2442e6ace5 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Thu, 8 Sep 2022 13:31:56 +0530 Subject: [PATCH 112/175] Common: Rename SendControlRelayMessageThread -> SendRemoveFromWaitlistControlMessageToRelaysThread --- monkey/infection_monkey/network/relay/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/network/relay/utils.py b/monkey/infection_monkey/network/relay/utils.py index 91bbf75fc..956945068 100644 --- a/monkey/infection_monkey/network/relay/utils.py +++ b/monkey/infection_monkey/network/relay/utils.py @@ -42,7 +42,7 @@ def send_remove_from_waitlist_control_message_to_relays(servers: Iterable[str]): for server in servers: t = create_daemon_thread( target=_open_socket_to_server, - name="SendControlRelayMessageThread", + name="SendRemoveFromWaitlistControlMessageToRelaysThread", args=(server,), ) t.start() From 28f555498f7cc5365f3c4608b08b7cb40c4fdbc1 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Wed, 7 Sep 2022 14:25:57 +0000 Subject: [PATCH 113/175] Agent: No need to check relay existence --- monkey/infection_monkey/monkey.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 3d24c93d3..728c463a0 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -192,9 +192,7 @@ class InfectionMonkey: client_disconnect_timeout=config.keep_tunnel_open_time, ) - if self._relay and maximum_depth_reached( - config.propagation.maximum_depth, self._current_depth - ): + if maximum_depth_reached(config.propagation.maximum_depth, self._current_depth): self._relay.start() StateTelem(is_done=False, version=get_version()).send() From 7661027c6c50b42e2f2b340f30b4a31bc55791b4 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Thu, 8 Sep 2022 13:37:25 +0530 Subject: [PATCH 114/175] Agent: Don't catch TimeoutError in _open_socket_to_server() since OSError is already being caught --- monkey/infection_monkey/network/relay/utils.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/monkey/infection_monkey/network/relay/utils.py b/monkey/infection_monkey/network/relay/utils.py index 956945068..5923002b6 100644 --- a/monkey/infection_monkey/network/relay/utils.py +++ b/monkey/infection_monkey/network/relay/utils.py @@ -60,5 +60,3 @@ def _open_socket_to_server(server: str): d_socket.send(RELAY_CONTROL_MESSAGE) except OSError as err: logger.error(f"Error connecting to socket {server}: {err}") - except TimeoutError as err: - logger.error(f"Timed out while connecting to socket {server}: {err}") From 526139bef1586994ae91db252809b85500b6506e Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Wed, 7 Sep 2022 14:50:28 +0000 Subject: [PATCH 115/175] Agent: Remove connect() --- monkey/infection_monkey/monkey.py | 7 +--- monkey/infection_monkey/network/tools.py | 39 ------------------- .../network/test_network_tools.py | 13 ------- 3 files changed, 2 insertions(+), 57 deletions(-) delete mode 100644 monkey/tests/unit_tests/infection_monkey/network/test_network_tools.py diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 728c463a0..d2ea3db0b 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -43,7 +43,6 @@ from infection_monkey.model import VictimHostFactory from infection_monkey.network.firewall import app as firewall from infection_monkey.network.info import get_free_tcp_port, get_network_interfaces from infection_monkey.network.relay import TCPRelay -from infection_monkey.network.tools import connect from infection_monkey.network_scanning.elasticsearch_fingerprinter import ElasticSearchFingerprinter from infection_monkey.network_scanning.http_fingerprinter import HTTPFingerprinter from infection_monkey.network_scanning.mssql_fingerprinter import MSSQLFingerprinter @@ -183,12 +182,10 @@ class InfectionMonkey: config = control_channel.get_config() local_port = get_free_tcp_port() - sock, ip_str, port = connect(self._opts.servers) - sock.close() self._relay = TCPRelay( local_port, - IPv4Address(ip_str), - port, + IPv4Address(self._cmd_island_ip), + self._cmd_island_port, client_disconnect_timeout=config.keep_tunnel_open_time, ) diff --git a/monkey/infection_monkey/network/tools.py b/monkey/infection_monkey/network/tools.py index 1f0c276e3..c612a7e48 100644 --- a/monkey/infection_monkey/network/tools.py +++ b/monkey/infection_monkey/network/tools.py @@ -3,10 +3,8 @@ import select import socket import struct import sys -from typing import List, Tuple, Union from common.common_consts.timeouts import CONNECTION_TIMEOUT -from common.network.network_utils import address_to_ip_port from infection_monkey.network.info import get_routes DEFAULT_TIMEOUT = CONNECTION_TIMEOUT @@ -92,40 +90,3 @@ def get_interface_to_target(dst): paths.sort() ret = paths[-1][1] return ret[1] - - -def connect(connections: List[str]) -> Tuple[socket.socket, str, int]: - """ - Attempt to connect to addresses in the given list. - - :param connections: The addresses to try and connect to. - :return: The socket, address, and port of the connection. - :raises: ConnectionError if no connection could be established. - :raises: ValueError if an improper connection is provided. - """ - for connection in connections: - ip, port = address_to_ip_port(connection) - if port is None: - raise ValueError("Connection does not contain a port") - sock = try_connect(ip, int(port)) - if sock: - return sock, ip, int(port) - - raise ConnectionError("Could not connect to a server in the server list") - - -def try_connect(ip: str, port: int) -> Union[socket.socket, None]: - """ - Attempt to establish a connection. - - :param ip: The IP to use. - :param port: The port to use. - :return: The socket on a successful connection, otherwise None. - """ - try: - logging.debug(f"Attempting to connect to {ip}:{port}") - sock = socket.create_connection((ip, port), timeout=1) - except Exception: - return None - - return sock diff --git a/monkey/tests/unit_tests/infection_monkey/network/test_network_tools.py b/monkey/tests/unit_tests/infection_monkey/network/test_network_tools.py deleted file mode 100644 index 77a4e8b00..000000000 --- a/monkey/tests/unit_tests/infection_monkey/network/test_network_tools.py +++ /dev/null @@ -1,13 +0,0 @@ -import pytest - -from infection_monkey.network.tools import connect - - -def test_connect_raises_with_empty_list(): - with pytest.raises(ConnectionError): - connect([]) - - -def test_connect_raises_with_bad_data(): - with pytest.raises(ValueError): - connect(["no-port"]) From cb4af415c1773a8522ffa2c4429d88a1988692e6 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Thu, 8 Sep 2022 13:39:45 +0530 Subject: [PATCH 116/175] Agent: Rename _open_socket_to_server -> _send_remove_from_waitlist_control_message_to_relay --- monkey/infection_monkey/network/relay/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/network/relay/utils.py b/monkey/infection_monkey/network/relay/utils.py index 5923002b6..333eafd6d 100644 --- a/monkey/infection_monkey/network/relay/utils.py +++ b/monkey/infection_monkey/network/relay/utils.py @@ -41,14 +41,14 @@ def find_server(servers: Iterable[str]) -> Optional[str]: def send_remove_from_waitlist_control_message_to_relays(servers: Iterable[str]): for server in servers: t = create_daemon_thread( - target=_open_socket_to_server, + target=_send_remove_from_waitlist_control_message_to_relay, name="SendRemoveFromWaitlistControlMessageToRelaysThread", args=(server,), ) t.start() -def _open_socket_to_server(server: str): +def _send_remove_from_waitlist_control_message_to_relay(server: str): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as d_socket: d_socket.settimeout(MEDIUM_REQUEST_TIMEOUT) From 4c795343d0a4347a6b820cfd9e24c211cb06bb30 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Wed, 7 Sep 2022 15:15:35 +0000 Subject: [PATCH 117/175] Agent: Fix maximum_depth_reached() --- monkey/infection_monkey/master/automated_master.py | 2 +- monkey/infection_monkey/monkey.py | 2 +- monkey/infection_monkey/utils/propagation.py | 10 +++++++++- .../infection_monkey/utils/test_propagation.py | 6 +++--- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/monkey/infection_monkey/master/automated_master.py b/monkey/infection_monkey/master/automated_master.py index 6959e6bf1..dcda3de2c 100644 --- a/monkey/infection_monkey/master/automated_master.py +++ b/monkey/infection_monkey/master/automated_master.py @@ -176,7 +176,7 @@ class AutomatedMaster(IMaster): current_depth = self._current_depth if self._current_depth is not None else 0 logger.info(f"Current depth is {current_depth}") - if maximum_depth_reached(config.propagation.maximum_depth, current_depth): + if not maximum_depth_reached(config.propagation.maximum_depth, current_depth): self._propagator.propagate(config.propagation, current_depth, self._servers, self._stop) else: logger.info("Skipping propagation: maximum depth reached") diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index d2ea3db0b..3d895d5a1 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -189,7 +189,7 @@ class InfectionMonkey: client_disconnect_timeout=config.keep_tunnel_open_time, ) - if maximum_depth_reached(config.propagation.maximum_depth, self._current_depth): + if not maximum_depth_reached(config.propagation.maximum_depth, self._current_depth): self._relay.start() StateTelem(is_done=False, version=get_version()).send() diff --git a/monkey/infection_monkey/utils/propagation.py b/monkey/infection_monkey/utils/propagation.py index 2da2e7bee..9b9f0a03d 100644 --- a/monkey/infection_monkey/utils/propagation.py +++ b/monkey/infection_monkey/utils/propagation.py @@ -1,2 +1,10 @@ def maximum_depth_reached(maximum_depth: int, current_depth: int) -> bool: - return maximum_depth > current_depth + """ + Return whether or not the current depth has eclipsed the maximum depth. + Values are nonnegative. Depth should increase from zero. + + :param maximum_depth: The maximum depth. + :param current_depth: The current depth. + :return: True if the current depth has reached the maximum depth, otherwise False. + """ + return current_depth >= maximum_depth diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_propagation.py b/monkey/tests/unit_tests/infection_monkey/utils/test_propagation.py index 19b2c18b5..77cbbc2ff 100644 --- a/monkey/tests/unit_tests/infection_monkey/utils/test_propagation.py +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_propagation.py @@ -5,18 +5,18 @@ def test_maximum_depth_reached__current_less_than_max(): maximum_depth = 2 current_depth = 1 - assert maximum_depth_reached(maximum_depth, current_depth) is True + assert maximum_depth_reached(maximum_depth, current_depth) is False def test_maximum_depth_reached__current_greater_than_max(): maximum_depth = 2 current_depth = 3 - assert maximum_depth_reached(maximum_depth, current_depth) is False + assert maximum_depth_reached(maximum_depth, current_depth) is True def test_maximum_depth_reached__current_equal_to_max(): maximum_depth = 2 current_depth = maximum_depth - assert maximum_depth_reached(maximum_depth, current_depth) is False + assert maximum_depth_reached(maximum_depth, current_depth) is True From 90dcb0a91e1875fc0829ad8c982a1438e738ffe3 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Wed, 31 Aug 2022 14:38:35 +0000 Subject: [PATCH 118/175] Agent: Wait for relay users to disconnect --- .../network/relay/relay_user_handler.py | 19 ++++++++++++++++++- .../network/relay/tcp_relay.py | 2 +- .../network/relay/test_relay_user_handler.py | 15 +++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/network/relay/relay_user_handler.py b/monkey/infection_monkey/network/relay/relay_user_handler.py index e9ee00d04..2c83f0b13 100644 --- a/monkey/infection_monkey/network/relay/relay_user_handler.py +++ b/monkey/infection_monkey/network/relay/relay_user_handler.py @@ -10,6 +10,7 @@ from common.utils.code_utils import del_key # Wait for potential new clients to connect DEFAULT_NEW_CLIENT_TIMEOUT = 2.5 * MEDIUM_REQUEST_TIMEOUT +DEFAULT_DISCONNECT_TIMEOUT = 60 * 10 # Wait up to 10 minutes for clients to disconnect @dataclass @@ -21,8 +22,13 @@ class RelayUser: class RelayUserHandler: """Manages membership to a network relay.""" - def __init__(self, new_client_timeout: float = DEFAULT_NEW_CLIENT_TIMEOUT): + def __init__( + self, + new_client_timeout: float = DEFAULT_NEW_CLIENT_TIMEOUT, + client_disconnect_timeout: float = DEFAULT_DISCONNECT_TIMEOUT, + ): self._new_client_timeout = new_client_timeout + self._client_disconnect_timeout = client_disconnect_timeout self._relay_users: Dict[IPv4Address, RelayUser] = {} self._potential_users: Dict[IPv4Address, RelayUser] = {} @@ -41,6 +47,7 @@ class RelayUserHandler: del_key(self._potential_users, user_address) timer = EggTimer() + timer.set(self._client_disconnect_timeout) self._relay_users[user_address] = RelayUser(user_address, timer) def add_potential_user(self, user_address: IPv4Address): @@ -74,3 +81,13 @@ class RelayUserHandler: ) return len(self._potential_users) > 0 + + def has_connected_users(self) -> bool: + """ + Return whether or not we have any relay users. + """ + self._relay_users = dict( + filter(lambda ru: not ru[1].timer.is_expired(), self._relay_users.items()) + ) + + return len(self._relay_users) > 0 diff --git a/monkey/infection_monkey/network/relay/tcp_relay.py b/monkey/infection_monkey/network/relay/tcp_relay.py index a12d8a0d9..f605cdc9d 100644 --- a/monkey/infection_monkey/network/relay/tcp_relay.py +++ b/monkey/infection_monkey/network/relay/tcp_relay.py @@ -36,7 +36,7 @@ class TCPRelay(Thread, InterruptableThreadMixin): """ Blocks until the users disconnect or the timeout has elapsed. """ - while self._user_handler.has_potential_users(): + while self._user_handler.has_potential_users() or self._user_handler.has_connected_users(): sleep(0.5) def _wait_for_pipes_to_close(self): diff --git a/monkey/tests/unit_tests/infection_monkey/network/relay/test_relay_user_handler.py b/monkey/tests/unit_tests/infection_monkey/network/relay/test_relay_user_handler.py index ca0eb4103..6f3ecb8fa 100644 --- a/monkey/tests/unit_tests/infection_monkey/network/relay/test_relay_user_handler.py +++ b/monkey/tests/unit_tests/infection_monkey/network/relay/test_relay_user_handler.py @@ -33,3 +33,18 @@ def test_potential_users_time_out(): sleep(0.003) assert not handler.has_potential_users() + + +def test_relay_users_added(handler): + assert not handler.has_connected_users() + handler.add_relay_user(USER_ADDRESS) + assert handler.has_connected_users() + + +def test_relay_users_time_out(): + handler = RelayUserHandler(client_disconnect_timeout=0.001) + + handler.add_relay_user(USER_ADDRESS) + sleep(0.003) + + assert not handler.has_connected_users() From aa1c31efb2560a99c9d8c883112b93843774b58a Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Thu, 8 Sep 2022 13:42:11 +0530 Subject: [PATCH 119/175] Agent: Rename RELAY_CONTROL_MESSAGE -> RELAY_CONTROL_MESSAGE_REMOVE_FROM_WAITLIST --- monkey/infection_monkey/network/relay/__init__.py | 5 ++++- .../network/relay/relay_connection_handler.py | 4 ++-- monkey/infection_monkey/network/relay/utils.py | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/monkey/infection_monkey/network/relay/__init__.py b/monkey/infection_monkey/network/relay/__init__.py index ad5d325c5..61728b643 100644 --- a/monkey/infection_monkey/network/relay/__init__.py +++ b/monkey/infection_monkey/network/relay/__init__.py @@ -1,4 +1,7 @@ -from .relay_connection_handler import RelayConnectionHandler, RELAY_CONTROL_MESSAGE +from .relay_connection_handler import ( + RelayConnectionHandler, + RELAY_CONTROL_MESSAGE_REMOVE_FROM_WAITLIST, +) from .relay_user_handler import RelayUser, RelayUserHandler from .sockets_pipe import SocketsPipe from .tcp_connection_handler import TCPConnectionHandler diff --git a/monkey/infection_monkey/network/relay/relay_connection_handler.py b/monkey/infection_monkey/network/relay/relay_connection_handler.py index 3d7755fb9..4b4475e52 100644 --- a/monkey/infection_monkey/network/relay/relay_connection_handler.py +++ b/monkey/infection_monkey/network/relay/relay_connection_handler.py @@ -4,7 +4,7 @@ from ipaddress import IPv4Address from .relay_user_handler import RelayUserHandler from .tcp_pipe_spawner import TCPPipeSpawner -RELAY_CONTROL_MESSAGE = b"infection-monkey-relay-control-message: -" +RELAY_CONTROL_MESSAGE_REMOVE_FROM_WAITLIST = b"infection-monkey-relay-control-message: -" class RelayConnectionHandler: @@ -25,7 +25,7 @@ class RelayConnectionHandler: control_message = sock.recv(socket.MSG_PEEK) - if control_message.startswith(RELAY_CONTROL_MESSAGE): + if control_message.startswith(RELAY_CONTROL_MESSAGE_REMOVE_FROM_WAITLIST): self._relay_user_handler.disconnect_user(addr) else: self._relay_user_handler.add_relay_user(addr) diff --git a/monkey/infection_monkey/network/relay/utils.py b/monkey/infection_monkey/network/relay/utils.py index 333eafd6d..096500c5b 100644 --- a/monkey/infection_monkey/network/relay/utils.py +++ b/monkey/infection_monkey/network/relay/utils.py @@ -6,7 +6,7 @@ import requests from common.common_consts.timeouts import MEDIUM_REQUEST_TIMEOUT from common.network.network_utils import address_to_ip_port -from infection_monkey.network.relay import RELAY_CONTROL_MESSAGE +from infection_monkey.network.relay import RELAY_CONTROL_MESSAGE_REMOVE_FROM_WAITLIST from infection_monkey.utils.threading import create_daemon_thread logger = logging.getLogger(__name__) @@ -57,6 +57,6 @@ def _send_remove_from_waitlist_control_message_to_relay(server: str): try: d_socket.connect((ip, int(port))) - d_socket.send(RELAY_CONTROL_MESSAGE) + d_socket.send(RELAY_CONTROL_MESSAGE_REMOVE_FROM_WAITLIST) except OSError as err: logger.error(f"Error connecting to socket {server}: {err}") From 33aac19831b09b2961f73a1907e45904feaf8b20 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Wed, 7 Sep 2022 17:55:44 +0000 Subject: [PATCH 120/175] Agent: Try well-known ports before other ports --- monkey/common/utils/code_utils.py | 15 ++++++- monkey/infection_monkey/network/info.py | 23 +++++++---- monkey/infection_monkey/network/ports.py | 15 +++++++ .../common/utils/test_code_utils.py | 10 ++++- .../infection_monkey/network/test_info.py | 40 +++++++++++++++++++ 5 files changed, 93 insertions(+), 10 deletions(-) create mode 100644 monkey/infection_monkey/network/ports.py create mode 100644 monkey/tests/unit_tests/infection_monkey/network/test_info.py diff --git a/monkey/common/utils/code_utils.py b/monkey/common/utils/code_utils.py index db87903bc..2a2804d54 100644 --- a/monkey/common/utils/code_utils.py +++ b/monkey/common/utils/code_utils.py @@ -1,5 +1,6 @@ import queue -from typing import Any, Dict, List, MutableMapping, Type, TypeVar +from bisect import bisect_left +from typing import Any, Dict, List, MutableMapping, Sequence, Type, TypeVar T = TypeVar("T") @@ -48,3 +49,15 @@ def del_key(mapping: MutableMapping[T, Any], key: T): del mapping[key] except KeyError: pass + + +def in_sorted_sequence(item: Any, seq: Sequence[Any]) -> bool: + """ + Provides fast search in the case that the sequence is sorted. + + :param item: The item to search in the list. + :param seq: The sorted sequence in which to search the item. + :return: True if the item was found in the list, otherwise false. + """ + i = bisect_left(seq, item) + return i != len(seq) and seq[i] == item diff --git a/monkey/infection_monkey/network/info.py b/monkey/infection_monkey/network/info.py index 12748b8a0..9d3347e79 100644 --- a/monkey/infection_monkey/network/info.py +++ b/monkey/infection_monkey/network/info.py @@ -3,14 +3,17 @@ import socket import struct from dataclasses import dataclass from ipaddress import IPv4Interface -from random import randint # noqa: DUO102 +from random import shuffle # noqa: DUO102 from typing import List import netifaces import psutil +from common.utils.code_utils import in_sorted_sequence from infection_monkey.utils.environment import is_windows_os +from .ports import COMMON_PORTS + # Timeout for monkey connections LOOPBACK_NAME = b"lo" SIOCGIFADDR = 0x8915 # get PA address @@ -119,15 +122,19 @@ else: def get_free_tcp_port(min_range=1024, max_range=65535): + + in_use = sorted([conn.laddr[1] for conn in psutil.net_connections()]) + + for port in COMMON_PORTS: + if not in_sorted_sequence(port, in_use): + return port + min_range = max(1, min_range) max_range = min(65535, max_range) - - in_use = [conn.laddr[1] for conn in psutil.net_connections()] - - for i in range(min_range, max_range): - port = randint(min_range, max_range) - - if port not in in_use: + ports = list(range(min_range, max_range)) + shuffle(ports) + for port in ports: + if not in_sorted_sequence(port, in_use): return port return None diff --git a/monkey/infection_monkey/network/ports.py b/monkey/infection_monkey/network/ports.py new file mode 100644 index 000000000..e1b5e4e22 --- /dev/null +++ b/monkey/infection_monkey/network/ports.py @@ -0,0 +1,15 @@ +from typing import List + +COMMON_PORTS: List[int] = [ + 1025, # NFS, IIS + 1433, # Microsoft SQL Server + 1434, # Microsoft SQL Monitor + 1720, # h323q931 + 1723, # Microsoft PPTP VPN + 3306, # mysql + 3389, # Windows Terminal Server (RDP) + 5900, # vnc + 6001, # X11:1 + 8080, # http-proxy + 8888, # sun-answerbook +] diff --git a/monkey/tests/unit_tests/common/utils/test_code_utils.py b/monkey/tests/unit_tests/common/utils/test_code_utils.py index e5980723d..676b8cb88 100644 --- a/monkey/tests/unit_tests/common/utils/test_code_utils.py +++ b/monkey/tests/unit_tests/common/utils/test_code_utils.py @@ -1,6 +1,6 @@ from queue import Queue -from common.utils.code_utils import del_key, queue_to_list +from common.utils.code_utils import del_key, in_sorted_sequence, queue_to_list def test_empty_queue_to_empty_list(): @@ -40,3 +40,11 @@ def test_del_key__nonexistant_key(): # This test passes if the following call does not raise an error del_key(my_dict, key_to_delete) + + +def test_in_sorted_sequence__finds_item(): + assert in_sorted_sequence(99, range(100)) + + +def test_in_sorted_sequence__does_not_find_nonexistent_item(): + assert not in_sorted_sequence(101, range(100)) diff --git a/monkey/tests/unit_tests/infection_monkey/network/test_info.py b/monkey/tests/unit_tests/infection_monkey/network/test_info.py new file mode 100644 index 000000000..8dab89e4b --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/network/test_info.py @@ -0,0 +1,40 @@ +from dataclasses import dataclass +from typing import Tuple + +import pytest + +from infection_monkey.network.info import get_free_tcp_port +from infection_monkey.network.ports import COMMON_PORTS + + +@dataclass +class Connection: + laddr: Tuple[str, int] + + +@pytest.mark.parametrize("port", COMMON_PORTS) +def test_get_free_tcp_port__checks_common_ports(port: int, monkeypatch): + unavailable_ports = [Connection(("", p)) for p in COMMON_PORTS if p is not port] + + monkeypatch.setattr( + "infection_monkey.network.info.psutil.net_connections", lambda: unavailable_ports + ) + assert get_free_tcp_port() is port + + +def test_get_free_tcp_port__checks_other_ports_if_common_ports_unavailable(monkeypatch): + unavailable_ports = [Connection(("", p)) for p in COMMON_PORTS] + monkeypatch.setattr( + "infection_monkey.network.info.psutil.net_connections", lambda: unavailable_ports + ) + + assert get_free_tcp_port() is not None + + +def test_get_free_tcp_port__none_if_no_available_ports(monkeypatch): + unavailable_ports = [Connection(("", p)) for p in range(65535)] + monkeypatch.setattr( + "infection_monkey.network.info.psutil.net_connections", lambda: unavailable_ports + ) + + assert get_free_tcp_port() is None From e2736a9273ac04161dee3e1cb4d35349b9408430 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 7 Sep 2022 00:02:23 -0400 Subject: [PATCH 121/175] Agent: Lock RelayUserHandler.has_*_users() --- .../network/relay/relay_user_handler.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/monkey/infection_monkey/network/relay/relay_user_handler.py b/monkey/infection_monkey/network/relay/relay_user_handler.py index 2c83f0b13..741045a29 100644 --- a/monkey/infection_monkey/network/relay/relay_user_handler.py +++ b/monkey/infection_monkey/network/relay/relay_user_handler.py @@ -76,18 +76,20 @@ class RelayUserHandler: """ Return whether or not we have any potential users. """ - self._potential_users = dict( - filter(lambda ru: not ru[1].timer.is_expired(), self._potential_users.items()) - ) + with self._lock: + self._potential_users = dict( + filter(lambda ru: not ru[1].timer.is_expired(), self._potential_users.items()) + ) - return len(self._potential_users) > 0 + return len(self._potential_users) > 0 def has_connected_users(self) -> bool: """ Return whether or not we have any relay users. """ - self._relay_users = dict( - filter(lambda ru: not ru[1].timer.is_expired(), self._relay_users.items()) - ) + with self._lock: + self._relay_users = dict( + filter(lambda ru: not ru[1].timer.is_expired(), self._relay_users.items()) + ) - return len(self._relay_users) > 0 + return len(self._relay_users) > 0 From 451d2d069473736c0cb2d5068b9e1dcf4052d1d7 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Thu, 8 Sep 2022 13:43:38 +0530 Subject: [PATCH 122/175] UT: Use RELAY_CONTROL_MESSAGE_REMOVE_FROM_WAITLIST in place of RELAY_CONTROL_MESSAGE --- .../network/relay/test_relay_connection_handler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/network/relay/test_relay_connection_handler.py b/monkey/tests/unit_tests/infection_monkey/network/relay/test_relay_connection_handler.py index 92aff10fc..48f8b6bf5 100644 --- a/monkey/tests/unit_tests/infection_monkey/network/relay/test_relay_connection_handler.py +++ b/monkey/tests/unit_tests/infection_monkey/network/relay/test_relay_connection_handler.py @@ -5,7 +5,7 @@ from unittest.mock import MagicMock import pytest from monkey.infection_monkey.network.relay import ( - RELAY_CONTROL_MESSAGE, + RELAY_CONTROL_MESSAGE_REMOVE_FROM_WAITLIST, RelayConnectionHandler, RelayUserHandler, TCPPipeSpawner, @@ -27,7 +27,7 @@ def relay_user_handler(): @pytest.fixture def close_socket(): sock = MagicMock(spec=socket.socket) - sock.recv.return_value = RELAY_CONTROL_MESSAGE + sock.recv.return_value = RELAY_CONTROL_MESSAGE_REMOVE_FROM_WAITLIST sock.getpeername.return_value = (USER_ADDRESS, 12345) return sock From b74c42ff0704d968582de606d2b10963bb81998a Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Wed, 7 Sep 2022 18:01:54 +0000 Subject: [PATCH 123/175] Agent: Add join timeout for the relay thread --- monkey/infection_monkey/monkey.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 3d895d5a1..9ba8e772a 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -397,7 +397,7 @@ class InfectionMonkey: if self._relay and self._relay.is_alive(): self._relay.stop() - self._relay.join() + self._relay.join(timeout=60) if firewall.is_enabled(): firewall.remove_firewall_rule() From 5a83401a0267703a793c7221e2a86249dc0950e7 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 7 Sep 2022 00:16:35 -0400 Subject: [PATCH 124/175] Agent: Add RelayUserHandler._remove_expired_users() --- .../network/relay/relay_user_handler.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/monkey/infection_monkey/network/relay/relay_user_handler.py b/monkey/infection_monkey/network/relay/relay_user_handler.py index 741045a29..5755d2794 100644 --- a/monkey/infection_monkey/network/relay/relay_user_handler.py +++ b/monkey/infection_monkey/network/relay/relay_user_handler.py @@ -77,9 +77,7 @@ class RelayUserHandler: Return whether or not we have any potential users. """ with self._lock: - self._potential_users = dict( - filter(lambda ru: not ru[1].timer.is_expired(), self._potential_users.items()) - ) + self._potential_users = RelayUserHandler._remove_expired_users(self._potential_users) return len(self._potential_users) > 0 @@ -88,8 +86,12 @@ class RelayUserHandler: Return whether or not we have any relay users. """ with self._lock: - self._relay_users = dict( - filter(lambda ru: not ru[1].timer.is_expired(), self._relay_users.items()) - ) + self._relay_users = RelayUserHandler._remove_expired_users(self._relay_users) return len(self._relay_users) > 0 + + @staticmethod + def _remove_expired_users( + user_list: Dict[IPv4Address, RelayUser] + ) -> Dict[IPv4Address, RelayUser]: + return dict(filter(lambda ru: not ru[1].timer.is_expired(), user_list.items())) From 45d1cc78c1814e95ed74b780f6ed2e665a6545a7 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Thu, 8 Sep 2022 14:38:19 +0530 Subject: [PATCH 125/175] UT: Use requests_mock in test_find_server --- .../network/relay/test_utils.py | 30 +++++++------------ 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/network/relay/test_utils.py b/monkey/tests/unit_tests/infection_monkey/network/relay/test_utils.py index bf4d70056..cae3cacbd 100644 --- a/monkey/tests/unit_tests/infection_monkey/network/relay/test_utils.py +++ b/monkey/tests/unit_tests/infection_monkey/network/relay/test_utils.py @@ -1,5 +1,6 @@ import pytest import requests +import requests_mock from infection_monkey.network.relay.utils import find_server @@ -9,27 +10,18 @@ SERVER_3 = "3.3.3.3:3142" SERVER_4 = "4.4.4.4:5000" -class MockConnectionError: - def __init__(self, *args, **kwargs): - raise requests.exceptions.ConnectionError - - -class MockRequestsGetResponsePerServerArgument: - def __init__(self, *args, **kwargs): - if SERVER_1 in args[0]: - MockConnectionError() - - -@pytest.fixture -def servers(): - return [SERVER_1, SERVER_2, SERVER_3, SERVER_4] +servers = [SERVER_1, SERVER_2, SERVER_3, SERVER_4] @pytest.mark.parametrize( - "mock_requests_get, expected", - [(MockConnectionError, None), (MockRequestsGetResponsePerServerArgument, SERVER_2)], + "expected_server,connection_error_servers,do_nothing_servers", + [(None, servers, []), (SERVER_2, [SERVER_1], [SERVER_2, SERVER_3, SERVER_4])], ) -def test_find_server(monkeypatch, servers, mock_requests_get, expected): - monkeypatch.setattr("infection_monkey.control.requests.get", mock_requests_get) +def test_find_server(expected_server, connection_error_servers, do_nothing_servers): + with requests_mock.Mocker() as mock: + for server in connection_error_servers: + mock.get(f"https://{server}/api?action=is-up", exc=requests.exceptions.ConnectionError) + for server in do_nothing_servers: + mock.get(f"https://{server}/api?action=is-up", text="") - assert find_server(servers) is expected + assert find_server(servers) is expected_server From 33da1214654568419f45070be65066f28a8ab84d Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Wed, 7 Sep 2022 18:10:37 +0000 Subject: [PATCH 126/175] Agent: Use 'relay_port' for relay local port --- monkey/infection_monkey/monkey.py | 4 ++-- monkey/infection_monkey/network/relay/tcp_relay.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 9ba8e772a..63e0b71d1 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -181,9 +181,9 @@ class InfectionMonkey: config = control_channel.get_config() - local_port = get_free_tcp_port() + relay_port = get_free_tcp_port() self._relay = TCPRelay( - local_port, + relay_port, IPv4Address(self._cmd_island_ip), self._cmd_island_port, client_disconnect_timeout=config.keep_tunnel_open_time, diff --git a/monkey/infection_monkey/network/relay/tcp_relay.py b/monkey/infection_monkey/network/relay/tcp_relay.py index a8793d91f..cb02999ae 100644 --- a/monkey/infection_monkey/network/relay/tcp_relay.py +++ b/monkey/infection_monkey/network/relay/tcp_relay.py @@ -18,7 +18,7 @@ class TCPRelay(Thread, InterruptableThreadMixin): def __init__( self, - local_port: int, + relay_port: int, dest_addr: IPv4Address, dest_port: int, client_disconnect_timeout: float, @@ -28,7 +28,7 @@ class TCPRelay(Thread, InterruptableThreadMixin): relay_filter = RelayConnectionHandler(self._pipe_spawner, self._user_handler) self._connection_handler = TCPConnectionHandler( bind_host="", - bind_port=local_port, + bind_port=relay_port, client_connected=[ relay_filter.handle_new_connection, ], From fac179bbda165bb1a62527be1aec111f0ec440d9 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Thu, 8 Sep 2022 14:48:03 +0530 Subject: [PATCH 127/175] UT: Simplify test logic in test_find_server() --- .../network/relay/test_utils.py | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/network/relay/test_utils.py b/monkey/tests/unit_tests/infection_monkey/network/relay/test_utils.py index cae3cacbd..fa63d34cb 100644 --- a/monkey/tests/unit_tests/infection_monkey/network/relay/test_utils.py +++ b/monkey/tests/unit_tests/infection_monkey/network/relay/test_utils.py @@ -14,14 +14,31 @@ servers = [SERVER_1, SERVER_2, SERVER_3, SERVER_4] @pytest.mark.parametrize( - "expected_server,connection_error_servers,do_nothing_servers", - [(None, servers, []), (SERVER_2, [SERVER_1], [SERVER_2, SERVER_3, SERVER_4])], + "expected_server,server_response_pairs", + [ + ( + None, + [ + (SERVER_1, {"exc": requests.exceptions.ConnectionError}), + (SERVER_2, {"exc": requests.exceptions.ConnectionError}), + (SERVER_3, {"exc": requests.exceptions.ConnectionError}), + (SERVER_4, {"exc": requests.exceptions.ConnectionError}), + ], + ), + ( + SERVER_2, + [ + (SERVER_1, {"exc": requests.exceptions.ConnectionError}), + (SERVER_2, {"text": ""}), + (SERVER_3, {"text": ""}), + (SERVER_4, {"text": ""}), + ], + ), + ], ) -def test_find_server(expected_server, connection_error_servers, do_nothing_servers): +def test_find_server(expected_server, server_response_pairs): with requests_mock.Mocker() as mock: - for server in connection_error_servers: - mock.get(f"https://{server}/api?action=is-up", exc=requests.exceptions.ConnectionError) - for server in do_nothing_servers: - mock.get(f"https://{server}/api?action=is-up", text="") + for server, response in server_response_pairs: + mock.get(f"https://{server}/api?action=is-up", **response) assert find_server(servers) is expected_server From 0398b31ece969837a77fe93ff1e9fb868ec783da Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Wed, 7 Sep 2022 19:46:22 +0000 Subject: [PATCH 128/175] Agent: Use set in get_free_tcp_port() --- monkey/common/utils/code_utils.py | 15 +-------------- monkey/infection_monkey/network/info.py | 7 +++---- .../unit_tests/common/utils/test_code_utils.py | 10 +--------- 3 files changed, 5 insertions(+), 27 deletions(-) diff --git a/monkey/common/utils/code_utils.py b/monkey/common/utils/code_utils.py index 2a2804d54..db87903bc 100644 --- a/monkey/common/utils/code_utils.py +++ b/monkey/common/utils/code_utils.py @@ -1,6 +1,5 @@ import queue -from bisect import bisect_left -from typing import Any, Dict, List, MutableMapping, Sequence, Type, TypeVar +from typing import Any, Dict, List, MutableMapping, Type, TypeVar T = TypeVar("T") @@ -49,15 +48,3 @@ def del_key(mapping: MutableMapping[T, Any], key: T): del mapping[key] except KeyError: pass - - -def in_sorted_sequence(item: Any, seq: Sequence[Any]) -> bool: - """ - Provides fast search in the case that the sequence is sorted. - - :param item: The item to search in the list. - :param seq: The sorted sequence in which to search the item. - :return: True if the item was found in the list, otherwise false. - """ - i = bisect_left(seq, item) - return i != len(seq) and seq[i] == item diff --git a/monkey/infection_monkey/network/info.py b/monkey/infection_monkey/network/info.py index 9d3347e79..6fdef3597 100644 --- a/monkey/infection_monkey/network/info.py +++ b/monkey/infection_monkey/network/info.py @@ -9,7 +9,6 @@ from typing import List import netifaces import psutil -from common.utils.code_utils import in_sorted_sequence from infection_monkey.utils.environment import is_windows_os from .ports import COMMON_PORTS @@ -123,10 +122,10 @@ else: def get_free_tcp_port(min_range=1024, max_range=65535): - in_use = sorted([conn.laddr[1] for conn in psutil.net_connections()]) + in_use = {conn.laddr[1] for conn in psutil.net_connections()} for port in COMMON_PORTS: - if not in_sorted_sequence(port, in_use): + if port not in in_use: return port min_range = max(1, min_range) @@ -134,7 +133,7 @@ def get_free_tcp_port(min_range=1024, max_range=65535): ports = list(range(min_range, max_range)) shuffle(ports) for port in ports: - if not in_sorted_sequence(port, in_use): + if port not in in_use: return port return None diff --git a/monkey/tests/unit_tests/common/utils/test_code_utils.py b/monkey/tests/unit_tests/common/utils/test_code_utils.py index 676b8cb88..e5980723d 100644 --- a/monkey/tests/unit_tests/common/utils/test_code_utils.py +++ b/monkey/tests/unit_tests/common/utils/test_code_utils.py @@ -1,6 +1,6 @@ from queue import Queue -from common.utils.code_utils import del_key, in_sorted_sequence, queue_to_list +from common.utils.code_utils import del_key, queue_to_list def test_empty_queue_to_empty_list(): @@ -40,11 +40,3 @@ def test_del_key__nonexistant_key(): # This test passes if the following call does not raise an error del_key(my_dict, key_to_delete) - - -def test_in_sorted_sequence__finds_item(): - assert in_sorted_sequence(99, range(100)) - - -def test_in_sorted_sequence__does_not_find_nonexistent_item(): - assert not in_sorted_sequence(101, range(100)) From 9f5aa5a055c2543606b05b2800e5f06a9c4089ec Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 7 Sep 2022 12:22:55 -0400 Subject: [PATCH 129/175] Agent: Drop DEFAULT_DISCONNECT_TIMEOUT to 2 minutes Agents should be sending data back to the island very frequently. 2 minutes should be plenty. Consider adding a heartbeat and reducing this timeout further. --- monkey/infection_monkey/network/relay/relay_user_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/network/relay/relay_user_handler.py b/monkey/infection_monkey/network/relay/relay_user_handler.py index 5755d2794..63c4000e0 100644 --- a/monkey/infection_monkey/network/relay/relay_user_handler.py +++ b/monkey/infection_monkey/network/relay/relay_user_handler.py @@ -10,7 +10,7 @@ from common.utils.code_utils import del_key # Wait for potential new clients to connect DEFAULT_NEW_CLIENT_TIMEOUT = 2.5 * MEDIUM_REQUEST_TIMEOUT -DEFAULT_DISCONNECT_TIMEOUT = 60 * 10 # Wait up to 10 minutes for clients to disconnect +DEFAULT_DISCONNECT_TIMEOUT = 60 * 2 # Wait up to 2 minutes for clients to disconnect @dataclass From e1759a7906d243e1a8a92bdb6474688d8d1693e0 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Thu, 8 Sep 2022 14:53:01 +0530 Subject: [PATCH 130/175] UT: Simplify parametrize logic in test_find_server() --- .../network/relay/test_utils.py | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/network/relay/test_utils.py b/monkey/tests/unit_tests/infection_monkey/network/relay/test_utils.py index fa63d34cb..1acd08012 100644 --- a/monkey/tests/unit_tests/infection_monkey/network/relay/test_utils.py +++ b/monkey/tests/unit_tests/infection_monkey/network/relay/test_utils.py @@ -16,23 +16,11 @@ servers = [SERVER_1, SERVER_2, SERVER_3, SERVER_4] @pytest.mark.parametrize( "expected_server,server_response_pairs", [ - ( - None, - [ - (SERVER_1, {"exc": requests.exceptions.ConnectionError}), - (SERVER_2, {"exc": requests.exceptions.ConnectionError}), - (SERVER_3, {"exc": requests.exceptions.ConnectionError}), - (SERVER_4, {"exc": requests.exceptions.ConnectionError}), - ], - ), + (None, [(server, {"exc": requests.exceptions.ConnectionError}) for server in servers]), ( SERVER_2, - [ - (SERVER_1, {"exc": requests.exceptions.ConnectionError}), - (SERVER_2, {"text": ""}), - (SERVER_3, {"text": ""}), - (SERVER_4, {"text": ""}), - ], + [(SERVER_1, {"exc": requests.exceptions.ConnectionError})] + + [(server, {"text": ""}) for server in servers[1:]], ), ], ) From baeb0c9460a28e1c1de45ab92f6c2f51cf534c17 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Fri, 2 Sep 2022 15:02:45 +0000 Subject: [PATCH 131/175] Agent: Add function to disconnect from relay --- .../infection_monkey/network/relay/utils.py | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/monkey/infection_monkey/network/relay/utils.py b/monkey/infection_monkey/network/relay/utils.py index 096500c5b..7ab38581b 100644 --- a/monkey/infection_monkey/network/relay/utils.py +++ b/monkey/infection_monkey/network/relay/utils.py @@ -1,5 +1,6 @@ import logging import socket +from ipaddress import IPv4Address from typing import Iterable, Optional import requests @@ -49,14 +50,21 @@ def send_remove_from_waitlist_control_message_to_relays(servers: Iterable[str]): def _send_remove_from_waitlist_control_message_to_relay(server: str): + ip, port = address_to_ip_port(server) + notify_disconnect(IPv4Address(ip), int(port)) + + +def notify_disconnect(server_ip: IPv4Address, server_port: int): + """ + Tell upstream relay that we no longer need the relay. + + :param server_ip: The IP address of the server to notify. + :param server_port: The port of the server to notify. + """ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as d_socket: - d_socket.settimeout(MEDIUM_REQUEST_TIMEOUT) - - ip, port = address_to_ip_port(server) - logger.info(f"Control message was sent to the server/relay {server}") - try: - d_socket.connect((ip, int(port))) - d_socket.send(RELAY_CONTROL_MESSAGE_REMOVE_FROM_WAITLIST) + d_socket.connect((server_ip, server_port)) + d_socket.sendall(RELAY_CONTROL_MESSAGE_REMOVE_FROM_WAITLIST) + logger.info(f"Control message was sent to the server/relay {server_ip}:{server_port}") except OSError as err: - logger.error(f"Error connecting to socket {server}: {err}") + logger.error(f"Error connecting to socket {server_ip}:{server_port}: {err}") From 6c2cab99505cbf01109e12a79854be6d9d79f47a Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Wed, 7 Sep 2022 18:46:17 +0000 Subject: [PATCH 132/175] Agent: Disconnect from upstream relay --- monkey/infection_monkey/monkey.py | 10 +++------- monkey/infection_monkey/network/relay/__init__.py | 1 + 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 918f5240c..311ccd9c0 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -9,7 +9,6 @@ from typing import List from pubsub.core import Publisher -import infection_monkey.tunnel as tunnel from common.event_queue import IAgentEventQueue, PyPubSubAgentEventQueue from common.events import CredentialsStolenEvent from common.network.network_utils import address_to_ip_port @@ -45,6 +44,7 @@ from infection_monkey.network.info import get_free_tcp_port, get_network_interfa from infection_monkey.network.relay import TCPRelay from infection_monkey.network.relay.utils import ( find_server, + notify_disconnect, send_remove_from_waitlist_control_message_to_relays, ) from infection_monkey.network_scanning.elasticsearch_fingerprinter import ElasticSearchFingerprinter @@ -424,12 +424,8 @@ class InfectionMonkey: logger.info("Monkey is shutting down") def _close_tunnel(self): - tunnel_address = ( - self._control_client.proxies.get("https", "").replace("http://", "").split(":")[0] - ) - if tunnel_address: - logger.info("Quitting tunnel %s", tunnel_address) - tunnel.quit_tunnel(tunnel_address) + logger.info(f"Quitting tunnel {self._cmd_island_ip}") + notify_disconnect(self._cmd_island_ip, self._cmd_island_port) def _send_log(self): monkey_log_path = get_agent_log_path() diff --git a/monkey/infection_monkey/network/relay/__init__.py b/monkey/infection_monkey/network/relay/__init__.py index b9eb8a009..50ee96438 100644 --- a/monkey/infection_monkey/network/relay/__init__.py +++ b/monkey/infection_monkey/network/relay/__init__.py @@ -7,3 +7,4 @@ from .sockets_pipe import SocketsPipe from .tcp_connection_handler import TCPConnectionHandler from .tcp_pipe_spawner import TCPPipeSpawner from .tcp_relay import TCPRelay +from .utils import notify_disconnect From dcb77d6285b0e78c50ec45bc86a49239e1b92a65 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Wed, 7 Sep 2022 19:10:08 +0000 Subject: [PATCH 133/175] Agent: Remove MonkeyTunnel --- monkey/infection_monkey/control.py | 53 +--- .../model/victim_host_factory.py | 9 +- monkey/infection_monkey/monkey.py | 2 +- monkey/infection_monkey/tunnel.py | 230 ------------------ .../model/test_victim_host_factory.py | 43 +--- .../infection_monkey/test_control.py | 16 -- vulture_allowlist.py | 3 - 7 files changed, 13 insertions(+), 343 deletions(-) delete mode 100644 monkey/infection_monkey/tunnel.py delete mode 100644 monkey/tests/unit_tests/infection_monkey/test_control.py diff --git a/monkey/infection_monkey/control.py b/monkey/infection_monkey/control.py index 3aedeb272..c3c5e58a9 100644 --- a/monkey/infection_monkey/control.py +++ b/monkey/infection_monkey/control.py @@ -2,20 +2,17 @@ import json import logging import platform from socket import gethostname -from typing import Mapping, Optional +from typing import MutableMapping, Optional import requests +from urllib3 import disable_warnings -import infection_monkey.tunnel as tunnel from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT from infection_monkey.config import GUID from infection_monkey.network.info import get_host_subnets, local_ips -from infection_monkey.transport.http import HTTPConnectProxy -from infection_monkey.transport.tcp import TcpProxy from infection_monkey.utils import agent_process -from infection_monkey.utils.environment import is_windows_os -requests.packages.urllib3.disable_warnings() +disable_warnings() # noqa DUO131 logger = logging.getLogger(__name__) @@ -28,7 +25,7 @@ class ControlClient: # https://github.com/guardicore/monkey/blob/133f7f5da131b481561141171827d1f9943f6aec/monkey/infection_monkey/telemetry/base_telem.py control_client_object = None - def __init__(self, server_address: str, proxies: Optional[Mapping[str, str]] = None): + def __init__(self, server_address: str, proxies: Optional[MutableMapping[str, str]] = None): self.proxies = {} if not proxies else proxies self.server_address = server_address @@ -62,25 +59,6 @@ class ControlClient: timeout=MEDIUM_REQUEST_TIMEOUT, ) - def set_proxies(self, proxy_find): - """ - Note: The proxy schema changes between different versions of requests and urllib3, - which causes the machine to not open a tunnel back. - If we get "ValueError: check_hostname requires server_hostname" or - "Proxy URL had not schema, should start with http:// or https://" errors, - the proxy schema needs to be changed. - Keep this in mind when upgrading to newer python version or when urllib3 and - requests are updated there is possibility that the proxy schema is changed. - https://github.com/psf/requests/issues/5297 - https://github.com/psf/requests/issues/5855 - """ - proxy_address, proxy_port = proxy_find - logger.info("Found tunnel at %s:%s" % (proxy_address, proxy_port)) - if is_windows_os(): - self.proxies["https"] = f"http://{proxy_address}:{proxy_port}" - else: - self.proxies["https"] = f"{proxy_address}:{proxy_port}" - def send_telemetry(self, telem_category, json_data: str): if not self.server_address: logger.error( @@ -117,29 +95,6 @@ class ControlClient: except Exception as exc: logger.warning(f"Error connecting to control server {self.server_address}: {exc}") - def create_control_tunnel(self, keep_tunnel_open_time: int): - if not self.server_address: - return None - - my_proxy = self.proxies.get("https", "").replace("https://", "") - if my_proxy: - proxy_class = TcpProxy - try: - target_addr, target_port = my_proxy.split(":", 1) - target_port = int(target_port) - except ValueError: - return None - else: - proxy_class = HTTPConnectProxy - target_addr, target_port = None, None - - return tunnel.MonkeyTunnel( - proxy_class, - keep_tunnel_open_time=keep_tunnel_open_time, - target_addr=target_addr, - target_port=target_port, - ) - def get_pba_file(self, filename): try: return requests.get( # noqa: DUO123 diff --git a/monkey/infection_monkey/model/victim_host_factory.py b/monkey/infection_monkey/model/victim_host_factory.py index a6b56532e..dc22425bb 100644 --- a/monkey/infection_monkey/model/victim_host_factory.py +++ b/monkey/infection_monkey/model/victim_host_factory.py @@ -4,7 +4,6 @@ from typing import Optional, Tuple from infection_monkey.model import VictimHost from infection_monkey.network import NetworkAddress from infection_monkey.network.tools import get_interface_to_target -from infection_monkey.tunnel import MonkeyTunnel logger = logging.getLogger(__name__) @@ -12,12 +11,10 @@ logger = logging.getLogger(__name__) class VictimHostFactory: def __init__( self, - tunnel: Optional[MonkeyTunnel], island_ip: Optional[str], island_port: Optional[str], on_island: bool, ): - self.tunnel = tunnel self.island_ip = island_ip self.island_port = island_port self.on_island = on_island @@ -26,19 +23,15 @@ class VictimHostFactory: domain = network_address.domain or "" victim_host = VictimHost(network_address.ip, domain) - if self.tunnel: - victim_host.default_tunnel = self.tunnel.get_tunnel_for_ip(victim_host.ip_addr) - if self.island_ip: ip, port = self._choose_island_address(victim_host.ip_addr) victim_host.set_island_address(ip, port) - logger.debug(f"Default tunnel for {victim_host} set to {victim_host.default_tunnel}") logger.debug(f"Default server for {victim_host} set to {victim_host.default_server}") return victim_host - def _choose_island_address(self, victim_ip: str) -> Tuple[str, Optional[str]]: + def _choose_island_address(self, victim_ip: str) -> Tuple[Optional[str], Optional[str]]: # Victims need to connect back to the interface they can reach # On island, choose the right interface to pass to children monkeys if self.on_island: diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index c96b2f159..0fe77c87a 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -381,7 +381,7 @@ class InfectionMonkey: on_island = self._running_on_island(local_network_interfaces) logger.debug(f"This agent is running on the island: {on_island}") - return VictimHostFactory(None, self._cmd_island_ip, self._cmd_island_port, on_island) + return VictimHostFactory(self._cmd_island_ip, self._cmd_island_port, on_island) def _running_on_island(self, local_network_interfaces: List[IPv4Interface]) -> bool: server_ip, _ = address_to_ip_port(self._control_client.server_address) diff --git a/monkey/infection_monkey/tunnel.py b/monkey/infection_monkey/tunnel.py deleted file mode 100644 index b4ca3b517..000000000 --- a/monkey/infection_monkey/tunnel.py +++ /dev/null @@ -1,230 +0,0 @@ -import logging -import socket -import struct -import time -from threading import Event, Thread - -from common.utils import Timer -from infection_monkey.network.firewall import app as firewall -from infection_monkey.network.info import get_free_tcp_port, local_ips -from infection_monkey.network.tools import check_tcp_port, get_interface_to_target -from infection_monkey.transport.base import get_last_serve_time - -logger = logging.getLogger(__name__) - -MCAST_GROUP = "224.1.1.1" -MCAST_PORT = 5007 -BUFFER_READ = 1024 -DEFAULT_TIMEOUT = 10 -QUIT_TIMEOUT = 60 * 10 # 10 minutes - - -def _set_multicast_socket(timeout=DEFAULT_TIMEOUT, adapter=""): - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) - sock.settimeout(timeout) - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - sock.bind((adapter, MCAST_PORT)) - sock.setsockopt( - socket.IPPROTO_IP, - socket.IP_ADD_MEMBERSHIP, - struct.pack("4sl", socket.inet_aton(MCAST_GROUP), socket.INADDR_ANY), - ) - return sock - - -def _check_tunnel(address, port, existing_sock=None): - if not existing_sock: - sock = _set_multicast_socket() - else: - sock = existing_sock - - logger.debug("Checking tunnel %s:%s", address, port) - is_open, _ = check_tcp_port(address, int(port)) - if not is_open: - logger.debug("Could not connect to %s:%s", address, port) - if not existing_sock: - sock.close() - return False - - try: - sock.sendto(b"+", (address, MCAST_PORT)) - except Exception as exc: - logger.debug("Caught exception in tunnel registration: %s", exc) - - if not existing_sock: - sock.close() - return True - - -def find_tunnel(default=None, attempts=3, timeout=DEFAULT_TIMEOUT): - l_ips = local_ips() - - if default: - if default.find(":") != -1: - address, port = default.split(":", 1) - if _check_tunnel(address, port): - return address, port - - for adapter in l_ips: - for attempt in range(0, attempts): - try: - logger.info("Trying to find using adapter %s", adapter) - sock = _set_multicast_socket(timeout, adapter) - sock.sendto(b"?", (MCAST_GROUP, MCAST_PORT)) - tunnels = [] - - while True: - try: - answer, address = sock.recvfrom(BUFFER_READ) - if answer not in [b"?", b"+", b"-"]: - tunnels.append(answer) - except socket.timeout: - break - - for tunnel in tunnels: - if tunnel.find(":") != -1: - address, port = tunnel.split(":", 1) - if address in l_ips: - continue - - if _check_tunnel(address, port, sock): - sock.close() - return address, port - - except Exception as exc: - logger.debug("Caught exception in tunnel lookup: %s", exc) - continue - - return None - - -def quit_tunnel(address, timeout=DEFAULT_TIMEOUT): - try: - sock = _set_multicast_socket(timeout) - sock.sendto(b"-", (address, MCAST_PORT)) - sock.close() - logger.debug("Success quitting tunnel") - except Exception as exc: - logger.debug("Exception quitting tunnel: %s", exc) - return - - -class MonkeyTunnel(Thread): - def __init__( - self, - proxy_class, - keep_tunnel_open_time, - target_addr=None, - target_port=None, - timeout=DEFAULT_TIMEOUT, - ): - self._target_addr = target_addr - self._target_port = target_port - self._proxy_class = proxy_class - self._keep_tunnel_open_time = keep_tunnel_open_time - self._broad_sock = None - self._timeout = timeout - self._stopped = Event() - self._clients = [] - self.local_port = None - super(MonkeyTunnel, self).__init__(name="MonkeyTunnelThread") - self.daemon = True - self.l_ips = None - self._wait_for_exploited_machines = Event() - - def run(self): - self._broad_sock = _set_multicast_socket(self._timeout) - self.l_ips = local_ips() - self.local_port = get_free_tcp_port() - - if not self.local_port: - return - - if not firewall.listen_allowed(localport=self.local_port): - logger.info("Machine firewalled, listen not allowed, not running tunnel.") - return - - proxy = self._proxy_class( - local_port=self.local_port, dest_host=self._target_addr, dest_port=self._target_port - ) - logger.info( - "Running tunnel using proxy class: %s, listening on port %s, routing to: %s:%s", - proxy.__class__.__name__, - self.local_port, - self._target_addr, - self._target_port, - ) - proxy.start() - - while not self._stopped.is_set(): - try: - search, address = self._broad_sock.recvfrom(BUFFER_READ) - if b"?" == search: - ip_match = get_interface_to_target(address[0]) - if ip_match: - answer = "%s:%d" % (ip_match, self.local_port) - logger.debug( - "Got tunnel request from %s, answering with %s", address[0], answer - ) - self._broad_sock.sendto(answer.encode(), (address[0], MCAST_PORT)) - elif b"+" == search: - if not address[0] in self._clients: - logger.debug("Tunnel control: Added %s to watchlist", address[0]) - self._clients.append(address[0]) - elif b"-" == search: - logger.debug("Tunnel control: Removed %s from watchlist", address[0]) - self._clients = [client for client in self._clients if client != address[0]] - - except socket.timeout: - continue - - logger.info("Stopping tunnel, waiting for clients: %s" % repr(self._clients)) - - # wait till all of the tunnel clients has been disconnected, or no one used the tunnel in - # QUIT_TIMEOUT seconds - timer = Timer() - timer.set(self._calculate_timeout()) - while self._clients and not timer.is_expired(): - try: - search, address = self._broad_sock.recvfrom(BUFFER_READ) - if b"-" == search: - logger.debug("Tunnel control: Removed %s from watchlist", address[0]) - self._clients = [client for client in self._clients if client != address[0]] - except socket.timeout: - continue - - timer.set(self._calculate_timeout()) - - logger.info("Closing tunnel") - self._broad_sock.close() - proxy.stop() - proxy.join() - - def _calculate_timeout(self) -> float: - try: - return QUIT_TIMEOUT - (time.time() - get_last_serve_time()) - except TypeError: # get_last_serve_time() may return None - return 0.0 - - def get_tunnel_for_ip(self, ip: str): - - if not self.local_port: - return - - ip_match = get_interface_to_target(ip) - return "%s:%d" % (ip_match, self.local_port) - - def set_wait_for_exploited_machines(self): - self._wait_for_exploited_machines.set() - - def stop(self): - self._wait_for_exploited_machine_connection() - self._stopped.set() - - def _wait_for_exploited_machine_connection(self): - if self._wait_for_exploited_machines.is_set(): - logger.info( - f"Waiting {self._keep_tunnel_open_time} seconds for exploited machines to connect " - "to the tunnel." - ) - time.sleep(self._keep_tunnel_open_time) diff --git a/monkey/tests/unit_tests/infection_monkey/model/test_victim_host_factory.py b/monkey/tests/unit_tests/infection_monkey/model/test_victim_host_factory.py index 766ef0392..3ec67438c 100644 --- a/monkey/tests/unit_tests/infection_monkey/model/test_victim_host_factory.py +++ b/monkey/tests/unit_tests/infection_monkey/model/test_victim_host_factory.py @@ -1,18 +1,9 @@ -from unittest.mock import MagicMock - import pytest from infection_monkey.model import VictimHostFactory from infection_monkey.network import NetworkAddress -@pytest.fixture -def mock_tunnel(): - tunnel = MagicMock() - tunnel.get_tunnel_for_ip = lambda _: "1.2.3.4:1234" - return tunnel - - @pytest.fixture(autouse=True) def mock_get_interface_to_target(monkeypatch): monkeypatch.setattr( @@ -21,9 +12,7 @@ def mock_get_interface_to_target(monkeypatch): def test_factory_no_tunnel(): - factory = VictimHostFactory( - tunnel=None, island_ip="192.168.56.1", island_port="5000", on_island=False - ) + factory = VictimHostFactory(island_ip="192.168.56.1", island_port="5000", on_island=False) network_address = NetworkAddress("192.168.56.2", None) victim = factory.build_victim_host(network_address) @@ -34,24 +23,8 @@ def test_factory_no_tunnel(): assert victim.domain_name == "" -def test_factory_with_tunnel(mock_tunnel): - factory = VictimHostFactory( - tunnel=mock_tunnel, island_ip="192.168.56.1", island_port="5000", on_island=False - ) - network_address = NetworkAddress("192.168.56.2", None) - - victim = factory.build_victim_host(network_address) - - assert victim.default_server == "192.168.56.1:5000" - assert victim.ip_addr == "192.168.56.2" - assert victim.default_tunnel == "1.2.3.4:1234" - assert victim.domain_name == "" - - -def test_factory_on_island(mock_tunnel): - factory = VictimHostFactory( - tunnel=mock_tunnel, island_ip="192.168.56.1", island_port="99", on_island=True - ) +def test_factory_on_island(): + factory = VictimHostFactory(island_ip="192.168.56.1", island_port="99", on_island=True) network_address = NetworkAddress("192.168.56.2", "www.bogus.monkey") victim = factory.build_victim_host(network_address) @@ -63,10 +36,8 @@ def test_factory_on_island(mock_tunnel): @pytest.mark.parametrize("default_port", ["", None]) -def test_factory_no_port(mock_tunnel, default_port): - factory = VictimHostFactory( - tunnel=mock_tunnel, island_ip="192.168.56.1", island_port=default_port, on_island=True - ) +def test_factory_no_port(default_port): + factory = VictimHostFactory(island_ip="192.168.56.1", island_port=default_port, on_island=True) network_address = NetworkAddress("192.168.56.2", "www.bogus.monkey") victim = factory.build_victim_host(network_address) @@ -74,8 +45,8 @@ def test_factory_no_port(mock_tunnel, default_port): assert victim.default_server == "1.1.1.1" -def test_factory_no_default_server(mock_tunnel): - factory = VictimHostFactory(tunnel=mock_tunnel, island_ip=None, island_port="", on_island=True) +def test_factory_no_default_server(): + factory = VictimHostFactory(island_ip=None, island_port="", on_island=True) network_address = NetworkAddress("192.168.56.2", "www.bogus.monkey") victim = factory.build_victim_host(network_address) diff --git a/monkey/tests/unit_tests/infection_monkey/test_control.py b/monkey/tests/unit_tests/infection_monkey/test_control.py deleted file mode 100644 index b90087ebf..000000000 --- a/monkey/tests/unit_tests/infection_monkey/test_control.py +++ /dev/null @@ -1,16 +0,0 @@ -import pytest - -from monkey.infection_monkey.control import ControlClient - - -@pytest.mark.parametrize( - "is_windows_os,expected_proxy_string", - [(True, "http://8.8.8.8:45455"), (False, "8.8.8.8:45455")], -) -def test_control_set_proxies(monkeypatch, is_windows_os, expected_proxy_string): - monkeypatch.setattr("monkey.infection_monkey.control.is_windows_os", lambda: is_windows_os) - control_client = ControlClient("8.8.8.8:5000") - - control_client.set_proxies(("8.8.8.8", "45455")) - - assert control_client.proxies["https"] == expected_proxy_string diff --git a/vulture_allowlist.py b/vulture_allowlist.py index 65756c95f..3a45ca674 100644 --- a/vulture_allowlist.py +++ b/vulture_allowlist.py @@ -299,9 +299,6 @@ event deserialize serialized_event -# TODO: Remove when removing Tunnel code -create_control_tunnel -set_wait_for_exploited_machines # pydantic base models underscore_attrs_are_private From 1d58ce36e7ee84f78d30569ba2469b6884c34a70 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 7 Sep 2022 19:45:12 -0400 Subject: [PATCH 134/175] Agent: Use a finally clause to ensure singleton is always unlocked --- monkey/infection_monkey/monkey.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 311ccd9c0..b242ad7d2 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -415,11 +415,12 @@ class InfectionMonkey: ).send() # Signal the server (before closing the tunnel) self._close_tunnel() - self._singleton.unlock() except Exception as e: logger.error(f"An error occurred while cleaning up the monkey agent: {e}") if deleted is None: InfectionMonkey._self_delete() + finally: + self._singleton.unlock() logger.info("Monkey is shutting down") From d7cabc2f5e3f8f0510074ea3430d32aa85f07221 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Wed, 7 Sep 2022 19:33:53 +0000 Subject: [PATCH 135/175] Agent: Get rid of default_tunnel --- monkey/infection_monkey/model/host.py | 7 +++---- .../infection_monkey/model/test_victim_host_factory.py | 2 -- .../infection_monkey/telemetry/test_exploit_telem.py | 1 - .../infection_monkey/telemetry/test_scan_telem.py | 1 - .../cc/services/edge/test_displayed_edge_service.py | 1 - 5 files changed, 3 insertions(+), 9 deletions(-) diff --git a/monkey/infection_monkey/model/host.py b/monkey/infection_monkey/model/host.py index bcfcf2f16..170e6fea4 100644 --- a/monkey/infection_monkey/model/host.py +++ b/monkey/infection_monkey/model/host.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Any, Dict, Optional from common import OperatingSystem @@ -7,10 +7,9 @@ class VictimHost(object): def __init__(self, ip_addr: str, domain_name: str = ""): self.ip_addr = ip_addr self.domain_name = str(domain_name) - self.os = {} - self.services = {} + self.os: Dict[str, Any] = {} + self.services: Dict[str, Any] = {} self.icmp = False - self.default_tunnel = None self.default_server = None def as_dict(self): diff --git a/monkey/tests/unit_tests/infection_monkey/model/test_victim_host_factory.py b/monkey/tests/unit_tests/infection_monkey/model/test_victim_host_factory.py index 3ec67438c..a584dca17 100644 --- a/monkey/tests/unit_tests/infection_monkey/model/test_victim_host_factory.py +++ b/monkey/tests/unit_tests/infection_monkey/model/test_victim_host_factory.py @@ -19,7 +19,6 @@ def test_factory_no_tunnel(): assert victim.default_server == "192.168.56.1:5000" assert victim.ip_addr == "192.168.56.2" - assert victim.default_tunnel is None assert victim.domain_name == "" @@ -32,7 +31,6 @@ def test_factory_on_island(): assert victim.default_server == "1.1.1.1:99" assert victim.domain_name == "www.bogus.monkey" assert victim.ip_addr == "192.168.56.2" - assert victim.default_tunnel == "1.2.3.4:1234" @pytest.mark.parametrize("default_port", ["", None]) diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/test_exploit_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/test_exploit_telem.py index 3255cc7b7..c38c1d130 100644 --- a/monkey/tests/unit_tests/infection_monkey/telemetry/test_exploit_telem.py +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/test_exploit_telem.py @@ -16,7 +16,6 @@ HOST_AS_DICT = { "os": {}, "services": {}, "icmp": False, - "default_tunnel": None, "default_server": None, } EXPLOITER_NAME = "SSHExploiter" diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/test_scan_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/test_scan_telem.py index a369fe4cf..837b7d782 100644 --- a/monkey/tests/unit_tests/infection_monkey/telemetry/test_scan_telem.py +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/test_scan_telem.py @@ -14,7 +14,6 @@ HOST_AS_DICT = { "os": {}, "services": {}, "icmp": False, - "default_tunnel": None, "default_server": None, } HOST_SERVICES = {} diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/edge/test_displayed_edge_service.py b/monkey/tests/unit_tests/monkey_island/cc/services/edge/test_displayed_edge_service.py index aadd13f60..90195f980 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/edge/test_displayed_edge_service.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/edge/test_displayed_edge_service.py @@ -18,7 +18,6 @@ SCAN_DATA_MOCK = [ }, }, "monkey_exe": None, - "default_tunnel": None, "default_server": None, }, } From 1c805184fe2dc9c6b7613ace1c43c449d8eb636a Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Wed, 7 Sep 2022 19:58:21 +0000 Subject: [PATCH 136/175] Agent: Remove unused proxy classes --- monkey/infection_monkey/transport/base.py | 31 -------- monkey/infection_monkey/transport/http.py | 66 ----------------- monkey/infection_monkey/transport/tcp.py | 88 ----------------------- 3 files changed, 185 deletions(-) delete mode 100644 monkey/infection_monkey/transport/base.py delete mode 100644 monkey/infection_monkey/transport/tcp.py diff --git a/monkey/infection_monkey/transport/base.py b/monkey/infection_monkey/transport/base.py deleted file mode 100644 index f61f7b115..000000000 --- a/monkey/infection_monkey/transport/base.py +++ /dev/null @@ -1,31 +0,0 @@ -import time -from threading import Thread - -g_last_served = None -PROXY_TIMEOUT = 2.5 - - -class TransportProxyBase(Thread): - def __init__(self, local_port, dest_host=None, dest_port=None, local_host=""): - global g_last_served - - self.local_host = local_host - self.local_port = local_port - self.dest_host = dest_host - self.dest_port = dest_port - self._stopped = False - super(TransportProxyBase, self).__init__() - self.daemon = True - - def stop(self): - self._stopped = True - - -def update_last_serve_time(): - global g_last_served - g_last_served = time.time() - - -def get_last_serve_time(): - global g_last_served - return g_last_served diff --git a/monkey/infection_monkey/transport/http.py b/monkey/infection_monkey/transport/http.py index 46dad8c52..293d86496 100644 --- a/monkey/infection_monkey/transport/http.py +++ b/monkey/infection_monkey/transport/http.py @@ -1,17 +1,9 @@ import http.server -import select -import socket import threading import urllib from logging import getLogger -from urllib.parse import urlsplit from infection_monkey.network.tools import get_interface_to_target -from infection_monkey.transport.base import ( - PROXY_TIMEOUT, - TransportProxyBase, - update_last_serve_time, -) logger = getLogger(__name__) @@ -110,56 +102,6 @@ class FileServHTTPRequestHandler(http.server.BaseHTTPRequestHandler): ) -class HTTPConnectProxyHandler(http.server.BaseHTTPRequestHandler): - timeout = 30 # timeout with clients, set to None not to make persistent connection - - def version_string(self): - return "" - - def do_CONNECT(self): - logger.info("Received a connect request!") - # just provide a tunnel, transfer the data with no modification - req = self - req.path = "https://%s/" % req.path.replace(":443", "") - - u = urlsplit(req.path) - address = (u.hostname, u.port or 443) - try: - conn = socket.create_connection(address) - except socket.error as e: - logger.debug( - "HTTPConnectProxyHandler: Got exception while trying to connect to %s: %s" - % (repr(address), e) - ) - self.send_error(504) # 504 Gateway Timeout - return - self.send_response(200, "Connection Established") - self.send_header("Connection", "close") - self.end_headers() - - conns = [self.connection, conn] - keep_connection = True - while keep_connection: - keep_connection = False - rlist, wlist, xlist = select.select(conns, [], conns, self.timeout) - if xlist: - break - for r in rlist: - other = conns[1] if r is conns[0] else conns[0] - data = r.recv(8192) - if data: - other.sendall(data) - keep_connection = True - update_last_serve_time() - conn.close() - - def log_message(self, format_string, *args): - logger.debug( - "HTTPConnectProxyHandler: %s - [%s] %s" - % (self.address_string(), self.log_date_time_string(), format_string % args) - ) - - class LockedHTTPServer(threading.Thread): """ Same as HTTPServer used for file downloads just with locks to avoid racing conditions. @@ -226,11 +168,3 @@ class LockedHTTPServer(threading.Thread): def stop(self, timeout=STOP_TIMEOUT): self._stopped = True self.join(timeout) - - -class HTTPConnectProxy(TransportProxyBase): - def run(self): - httpd = http.server.HTTPServer((self.local_host, self.local_port), HTTPConnectProxyHandler) - httpd.timeout = PROXY_TIMEOUT - while not self._stopped: - httpd.handle_request() diff --git a/monkey/infection_monkey/transport/tcp.py b/monkey/infection_monkey/transport/tcp.py deleted file mode 100644 index 83c631c3b..000000000 --- a/monkey/infection_monkey/transport/tcp.py +++ /dev/null @@ -1,88 +0,0 @@ -import select -import socket -from logging import getLogger -from threading import Thread - -from infection_monkey.transport.base import ( - PROXY_TIMEOUT, - TransportProxyBase, - update_last_serve_time, -) - -READ_BUFFER_SIZE = 8192 -SOCKET_READ_TIMEOUT = 10 - -logger = getLogger(__name__) - - -class SocketsPipe(Thread): - def __init__(self, source, dest, timeout=SOCKET_READ_TIMEOUT): - Thread.__init__(self) - self.source = source - self.dest = dest - self.timeout = timeout - self._keep_connection = True - super(SocketsPipe, self).__init__() - self.daemon = True - - def run(self): - sockets = [self.source, self.dest] - while self._keep_connection: - self._keep_connection = False - rlist, wlist, xlist = select.select(sockets, [], sockets, self.timeout) - if xlist: - break - for r in rlist: - other = self.dest if r is self.source else self.source - try: - data = r.recv(READ_BUFFER_SIZE) - except Exception: - break - if data: - try: - other.sendall(data) - update_last_serve_time() - except Exception: - break - self._keep_connection = True - - self.source.close() - self.dest.close() - - -class TcpProxy(TransportProxyBase): - def run(self): - pipes = [] - l_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - l_socket.bind((self.local_host, self.local_port)) - l_socket.settimeout(PROXY_TIMEOUT) - l_socket.listen(5) - - while not self._stopped: - try: - source, address = l_socket.accept() - except socket.timeout: - continue - - dest = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - try: - dest.connect((self.dest_host, self.dest_port)) - except socket.error: - source.close() - dest.close() - continue - - pipe = SocketsPipe(source, dest) - pipes.append(pipe) - logger.debug( - "piping sockets %s:%s->%s:%s", - address[0], - address[1], - self.dest_host, - self.dest_port, - ) - pipe.start() - - l_socket.close() - for pipe in pipes: - pipe.join() From 6051cfa29aa4a4e63d5d6bb20eb95cdee8eb872e Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Thu, 8 Sep 2022 15:55:50 +0000 Subject: [PATCH 137/175] Agent: Fix log call exhausting iterator --- monkey/infection_monkey/monkey.py | 1 + monkey/infection_monkey/network/relay/utils.py | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index b242ad7d2..eea2d6af7 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -126,6 +126,7 @@ class InfectionMonkey: return opts def _get_server(self): + logger.debug(f"Trying to wake up with servers: {', '.join(self._opts.servers)}") servers_iterator = (s for s in self._opts.servers) server = find_server(servers_iterator) if server: diff --git a/monkey/infection_monkey/network/relay/utils.py b/monkey/infection_monkey/network/relay/utils.py index 7ab38581b..4a5532c30 100644 --- a/monkey/infection_monkey/network/relay/utils.py +++ b/monkey/infection_monkey/network/relay/utils.py @@ -14,8 +14,6 @@ logger = logging.getLogger(__name__) def find_server(servers: Iterable[str]) -> Optional[str]: - logger.debug(f"Trying to wake up with servers: {', '.join(servers)}") - for server in servers: logger.debug(f"Trying to connect to server: {server}") From 7530a8915259874aae2cf67e345c1956dcb3d842 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Thu, 8 Sep 2022 16:36:20 +0530 Subject: [PATCH 138/175] Agent: Remove usage of proxies --- monkey/infection_monkey/control.py | 11 +---------- .../exploit/caching_agent_binary_repository.py | 5 +---- .../infection_monkey/master/control_channel.py | 9 ++------- monkey/infection_monkey/monkey.py | 16 ++++------------ 4 files changed, 8 insertions(+), 33 deletions(-) diff --git a/monkey/infection_monkey/control.py b/monkey/infection_monkey/control.py index c3c5e58a9..7f161ecbd 100644 --- a/monkey/infection_monkey/control.py +++ b/monkey/infection_monkey/control.py @@ -2,7 +2,6 @@ import json import logging import platform from socket import gethostname -from typing import MutableMapping, Optional import requests from urllib3 import disable_warnings @@ -25,8 +24,7 @@ class ControlClient: # https://github.com/guardicore/monkey/blob/133f7f5da131b481561141171827d1f9943f6aec/monkey/infection_monkey/telemetry/base_telem.py control_client_object = None - def __init__(self, server_address: str, proxies: Optional[MutableMapping[str, str]] = None): - self.proxies = {} if not proxies else proxies + def __init__(self, server_address: str): self.server_address = server_address def wakeup(self, parent=None): @@ -47,15 +45,11 @@ class ControlClient: "launch_time": agent_process.get_start_time(), } - if self.proxies: - monkey["tunnel"] = self.proxies.get("https") - requests.post( # noqa: DUO123 f"https://{self.server_address}/api/agent", data=json.dumps(monkey), headers={"content-type": "application/json"}, verify=False, - proxies=self.proxies, timeout=MEDIUM_REQUEST_TIMEOUT, ) @@ -73,7 +67,6 @@ class ControlClient: data=json.dumps(telemetry), headers={"content-type": "application/json"}, verify=False, - proxies=self.proxies, timeout=MEDIUM_REQUEST_TIMEOUT, ) except Exception as exc: @@ -89,7 +82,6 @@ class ControlClient: data=json.dumps(telemetry), headers={"content-type": "application/json"}, verify=False, - proxies=self.proxies, timeout=MEDIUM_REQUEST_TIMEOUT, ) except Exception as exc: @@ -100,7 +92,6 @@ class ControlClient: return requests.get( # noqa: DUO123 PBA_FILE_DOWNLOAD % (self.server_address, filename), verify=False, - proxies=self.proxies, timeout=LONG_REQUEST_TIMEOUT, ) except requests.exceptions.RequestException: diff --git a/monkey/infection_monkey/exploit/caching_agent_binary_repository.py b/monkey/infection_monkey/exploit/caching_agent_binary_repository.py index f3d4dc73a..745aae112 100644 --- a/monkey/infection_monkey/exploit/caching_agent_binary_repository.py +++ b/monkey/infection_monkey/exploit/caching_agent_binary_repository.py @@ -1,7 +1,6 @@ import io import threading from functools import lru_cache -from typing import Mapping import requests @@ -18,9 +17,8 @@ class CachingAgentBinaryRepository(IAgentBinaryRepository): request is actually sent to the island for each requested binary. """ - def __init__(self, island_url: str, proxies: Mapping[str, str]): + def __init__(self, island_url: str): self._island_url = island_url - self._proxies = proxies self._lock = threading.Lock() def get_agent_binary( @@ -40,7 +38,6 @@ class CachingAgentBinaryRepository(IAgentBinaryRepository): response = requests.get( # noqa: DUO123 f"{self._island_url}/api/agent-binaries/{os_name}", verify=False, - proxies=self._proxies, timeout=MEDIUM_REQUEST_TIMEOUT, ) diff --git a/monkey/infection_monkey/master/control_channel.py b/monkey/infection_monkey/master/control_channel.py index 8c6653573..b1b4bae81 100644 --- a/monkey/infection_monkey/master/control_channel.py +++ b/monkey/infection_monkey/master/control_channel.py @@ -1,7 +1,7 @@ import json import logging from pprint import pformat -from typing import MutableMapping, Optional, Sequence +from typing import Optional, Sequence from uuid import UUID import requests @@ -22,10 +22,9 @@ logger = logging.getLogger(__name__) class ControlChannel(IControlChannel): - def __init__(self, server: str, agent_id: str, proxies: MutableMapping[str, str]): + def __init__(self, server: str, agent_id: str): self._agent_id = agent_id self._control_channel_server = server - self._proxies = proxies def register_agent(self, parent: Optional[UUID] = None): agent_registration_data = AgentRegistrationData( @@ -44,7 +43,6 @@ class ControlChannel(IControlChannel): url, json=agent_registration_data.dict(simplify=True), verify=False, - proxies=self._proxies, timeout=SHORT_REQUEST_TIMEOUT, ) response.raise_for_status() @@ -68,7 +66,6 @@ class ControlChannel(IControlChannel): response = requests.get( # noqa: DUO123 url, verify=False, - proxies=self._proxies, timeout=SHORT_REQUEST_TIMEOUT, ) response.raise_for_status() @@ -89,7 +86,6 @@ class ControlChannel(IControlChannel): response = requests.get( # noqa: DUO123 f"https://{self._control_channel_server}/api/agent-configuration", verify=False, - proxies=self._proxies, timeout=SHORT_REQUEST_TIMEOUT, ) response.raise_for_status() @@ -116,7 +112,6 @@ class ControlChannel(IControlChannel): response = requests.get( # noqa: DUO123 propagation_credentials_url, verify=False, - proxies=self._proxies, timeout=SHORT_REQUEST_TIMEOUT, ) response.raise_for_status() diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 0fe77c87a..ca2886b5b 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -78,7 +78,6 @@ from infection_monkey.telemetry.messengers.legacy_telemetry_messenger_adapter im LegacyTelemetryMessengerAdapter, ) from infection_monkey.telemetry.state_telem import StateTelem -from infection_monkey.telemetry.tunnel_telem import TunnelTelem from infection_monkey.utils.aws_environment_check import run_aws_environment_check from infection_monkey.utils.environment import is_windows_os from infection_monkey.utils.file_utils import mark_file_for_deletion_on_windows @@ -160,9 +159,7 @@ class InfectionMonkey: run_aws_environment_check(self._telemetry_messenger) - should_stop = ControlChannel( - self._control_client.server_address, GUID, self._control_client.proxies - ).should_agent_stop() + should_stop = ControlChannel(self._control_client.server_address, GUID).should_agent_stop() if should_stop: logger.info("The Monkey Island has instructed this agent to stop") return @@ -178,9 +175,7 @@ class InfectionMonkey: if firewall.is_enabled(): firewall.add_firewall_rule() - control_channel = ControlChannel( - self._control_client.server_address, GUID, self._control_client.proxies - ) + control_channel = ControlChannel(self._control_client.server_address, GUID) control_channel.register_agent(self._opts.parent) config = control_channel.get_config() @@ -197,7 +192,6 @@ class InfectionMonkey: self._relay.start() StateTelem(is_done=False, version=get_version()).send() - TunnelTelem(self._control_client.proxies).send() self._build_master() @@ -207,9 +201,7 @@ class InfectionMonkey: local_network_interfaces = InfectionMonkey._get_local_network_interfaces() # TODO control_channel and control_client have same responsibilities, merge them - control_channel = ControlChannel( - self._control_client.server_address, GUID, self._control_client.proxies - ) + control_channel = ControlChannel(self._control_client.server_address, GUID) propagation_credentials_repository = AggregatingPropagationCredentialsRepository( control_channel ) @@ -281,7 +273,7 @@ class InfectionMonkey: puppet.load_plugin("ssh", SSHFingerprinter(), PluginType.FINGERPRINTER) agent_binary_repository = CachingAgentBinaryRepository( - f"https://{self._control_client.server_address}", self._control_client.proxies + f"https://{self._control_client.server_address}" ) exploit_wrapper = ExploiterWrapper( self._telemetry_messenger, event_queue, agent_binary_repository From d335c4081a42925417d858ffc748112ff5ede8ef Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Thu, 8 Sep 2022 15:56:51 +0000 Subject: [PATCH 139/175] Agent: Fix TCPRelay missing _interrupted member --- monkey/infection_monkey/network/relay/tcp_relay.py | 1 + 1 file changed, 1 insertion(+) diff --git a/monkey/infection_monkey/network/relay/tcp_relay.py b/monkey/infection_monkey/network/relay/tcp_relay.py index cb02999ae..8e62a2169 100644 --- a/monkey/infection_monkey/network/relay/tcp_relay.py +++ b/monkey/infection_monkey/network/relay/tcp_relay.py @@ -34,6 +34,7 @@ class TCPRelay(Thread, InterruptableThreadMixin): ], ) super().__init__(name="MonkeyTcpRelayThread", daemon=True) + InterruptableThreadMixin.__init__(self) self._lock = Lock() def run(self): From cb45cd88739602bac1ed6be9addfe4420bb0304c Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Thu, 8 Sep 2022 14:30:03 +0000 Subject: [PATCH 140/175] Agent: Remove TunnelTelem --- .../telemetry/tunnel_telem.py | 18 ------------------ .../telemetry/test_tunnel_telem.py | 19 ------------------- 2 files changed, 37 deletions(-) delete mode 100644 monkey/infection_monkey/telemetry/tunnel_telem.py delete mode 100644 monkey/tests/unit_tests/infection_monkey/telemetry/test_tunnel_telem.py diff --git a/monkey/infection_monkey/telemetry/tunnel_telem.py b/monkey/infection_monkey/telemetry/tunnel_telem.py deleted file mode 100644 index efe917643..000000000 --- a/monkey/infection_monkey/telemetry/tunnel_telem.py +++ /dev/null @@ -1,18 +0,0 @@ -from typing import Mapping - -from common.common_consts.telem_categories import TelemCategoryEnum -from infection_monkey.telemetry.base_telem import BaseTelem - - -class TunnelTelem(BaseTelem): - def __init__(self, proxy: Mapping[str, str]): - """ - Default tunnel telemetry constructor - """ - super(TunnelTelem, self).__init__() - self.proxy = proxy.get("https") - - telem_category = TelemCategoryEnum.TUNNEL - - def get_data(self): - return {"proxy": self.proxy} diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/test_tunnel_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/test_tunnel_telem.py deleted file mode 100644 index eb18307ce..000000000 --- a/monkey/tests/unit_tests/infection_monkey/telemetry/test_tunnel_telem.py +++ /dev/null @@ -1,19 +0,0 @@ -import json - -import pytest - -from infection_monkey.telemetry.tunnel_telem import TunnelTelem - - -@pytest.fixture -def tunnel_telem_test_instance(): - return TunnelTelem({}) - - -def test_tunnel_telem_send(tunnel_telem_test_instance, spy_send_telemetry): - tunnel_telem_test_instance.send() - expected_data = {"proxy": None} - expected_data = json.dumps(expected_data, cls=tunnel_telem_test_instance.json_encoder) - - assert spy_send_telemetry.data == expected_data - assert spy_send_telemetry.telem_category == "tunnel" From 3516fa1fec6d2845cc4b2962367a09889cec090f Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Thu, 8 Sep 2022 16:16:31 +0000 Subject: [PATCH 141/175] Agent: Fix: Pass port as int --- monkey/infection_monkey/monkey.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index eea2d6af7..c96b2f159 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -189,7 +189,7 @@ class InfectionMonkey: self._relay = TCPRelay( relay_port, IPv4Address(self._cmd_island_ip), - self._cmd_island_port, + int(self._cmd_island_port), client_disconnect_timeout=config.keep_tunnel_open_time, ) @@ -427,7 +427,7 @@ class InfectionMonkey: def _close_tunnel(self): logger.info(f"Quitting tunnel {self._cmd_island_ip}") - notify_disconnect(self._cmd_island_ip, self._cmd_island_port) + notify_disconnect(self._cmd_island_ip, int(self._cmd_island_port)) def _send_log(self): monkey_log_path = get_agent_log_path() From 4a2297b0976e3c1368a1fdee30aea5e9555fc46c Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Thu, 8 Sep 2022 14:30:58 +0000 Subject: [PATCH 142/175] Island: Remove TunnelTelem --- .../common/common_consts/telem_categories.py | 1 - .../cc/resources/telemetry_feed.py | 11 ------- .../telemetry/processing/processing.py | 2 -- .../services/telemetry/processing/tunnel.py | 15 --------- .../cc/services/telemetry/processing/utils.py | 5 --- .../telemetry/zero_trust_checks/tunneling.py | 32 ------------------- 6 files changed, 66 deletions(-) delete mode 100644 monkey/monkey_island/cc/services/telemetry/processing/tunnel.py delete mode 100644 monkey/monkey_island/cc/services/telemetry/zero_trust_checks/tunneling.py diff --git a/monkey/common/common_consts/telem_categories.py b/monkey/common/common_consts/telem_categories.py index 669b2379c..0697fd4f7 100644 --- a/monkey/common/common_consts/telem_categories.py +++ b/monkey/common/common_consts/telem_categories.py @@ -8,4 +8,3 @@ class TelemCategoryEnum: SCAN = "scan" STATE = "state" TRACE = "trace" - TUNNEL = "tunnel" diff --git a/monkey/monkey_island/cc/resources/telemetry_feed.py b/monkey/monkey_island/cc/resources/telemetry_feed.py index 9087e2b97..feb408ae0 100644 --- a/monkey/monkey_island/cc/resources/telemetry_feed.py +++ b/monkey/monkey_island/cc/resources/telemetry_feed.py @@ -64,16 +64,6 @@ class TelemetryFeed(AbstractResource): def get_telem_brief_parser_by_category(telem_category): return TELEM_PROCESS_DICT[telem_category] - @staticmethod - def get_tunnel_telem_brief(telem): - tunnel = telem["data"]["proxy"] - if tunnel is None: - return "No tunnel is used." - else: - tunnel_host_ip = tunnel.split(":")[-2].replace("//", "") - tunnel_host = NodeService.get_monkey_by_ip(tunnel_host_ip)["hostname"] - return "Tunnel set up to machine: %s." % tunnel_host - @staticmethod def get_state_telem_brief(telem): if telem["data"]["done"]: @@ -132,7 +122,6 @@ TELEM_PROCESS_DICT = { TelemCategoryEnum.SCAN: TelemetryFeed.get_scan_telem_brief, TelemCategoryEnum.STATE: TelemetryFeed.get_state_telem_brief, TelemCategoryEnum.TRACE: TelemetryFeed.get_trace_telem_brief, - TelemCategoryEnum.TUNNEL: TelemetryFeed.get_tunnel_telem_brief, } diff --git a/monkey/monkey_island/cc/services/telemetry/processing/processing.py b/monkey/monkey_island/cc/services/telemetry/processing/processing.py index 557dbff7f..f3550077f 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/processing.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/processing.py @@ -8,7 +8,6 @@ from monkey_island.cc.services.telemetry.processing.exploit import process_explo from monkey_island.cc.services.telemetry.processing.post_breach import process_post_breach_telemetry from monkey_island.cc.services.telemetry.processing.scan import process_scan_telemetry from monkey_island.cc.services.telemetry.processing.state import process_state_telemetry -from monkey_island.cc.services.telemetry.processing.tunnel import process_tunnel_telemetry logger = logging.getLogger(__name__) @@ -22,7 +21,6 @@ TELEMETRY_CATEGORY_TO_PROCESSING_FUNC = { TelemCategoryEnum.SCAN: process_scan_telemetry, TelemCategoryEnum.STATE: process_state_telemetry, TelemCategoryEnum.TRACE: lambda *args, **kwargs: None, - TelemCategoryEnum.TUNNEL: process_tunnel_telemetry, } # Don't save credential telemetries in telemetries collection. diff --git a/monkey/monkey_island/cc/services/telemetry/processing/tunnel.py b/monkey/monkey_island/cc/services/telemetry/processing/tunnel.py deleted file mode 100644 index 6bd1fd711..000000000 --- a/monkey/monkey_island/cc/services/telemetry/processing/tunnel.py +++ /dev/null @@ -1,15 +0,0 @@ -from monkey_island.cc.services.node import NodeService -from monkey_island.cc.services.telemetry.processing.utils import get_tunnel_host_ip_from_proxy_field -from monkey_island.cc.services.telemetry.zero_trust_checks.tunneling import ( - check_tunneling_violation, -) - - -def process_tunnel_telemetry(telemetry_json, _): - check_tunneling_violation(telemetry_json) - monkey_id = NodeService.get_monkey_by_guid(telemetry_json["monkey_guid"])["_id"] - if telemetry_json["data"]["proxy"] is not None: - tunnel_host_ip = get_tunnel_host_ip_from_proxy_field(telemetry_json) - NodeService.set_monkey_tunnel(monkey_id, tunnel_host_ip) - else: - NodeService.unset_all_monkey_tunnels(monkey_id) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/utils.py b/monkey/monkey_island/cc/services/telemetry/processing/utils.py index ffa6960f6..30487593a 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/utils.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/utils.py @@ -14,8 +14,3 @@ def get_edge_by_scan_or_exploit_telemetry(telemetry_json): dst_label = NodeService.get_label_for_endpoint(dst_node["_id"]) return EdgeService.get_or_create_edge(src_monkey["_id"], dst_node["_id"], src_label, dst_label) - - -def get_tunnel_host_ip_from_proxy_field(telemetry_json): - tunnel_host_ip = telemetry_json["data"]["proxy"].split(":")[-2].replace("//", "") - return tunnel_host_ip diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/tunneling.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/tunneling.py deleted file mode 100644 index 092fd67e2..000000000 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/tunneling.py +++ /dev/null @@ -1,32 +0,0 @@ -import common.common_consts.zero_trust_consts as zero_trust_consts -from monkey_island.cc.models import Monkey -from monkey_island.cc.models.zero_trust.event import Event -from monkey_island.cc.services.telemetry.processing.utils import get_tunnel_host_ip_from_proxy_field -from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import ( - MonkeyZTFindingService, -) - - -def check_tunneling_violation(tunnel_telemetry_json): - if tunnel_telemetry_json["data"]["proxy"] is not None: - # Monkey is tunneling, create findings - tunnel_host_ip = get_tunnel_host_ip_from_proxy_field(tunnel_telemetry_json) - current_monkey = Monkey.get_single_monkey_by_guid(tunnel_telemetry_json["monkey_guid"]) - tunneling_events = [ - Event.create_event( - title="Tunneling event", - message="Monkey on {hostname} tunneled traffic through {proxy}.".format( - hostname=current_monkey.hostname, proxy=tunnel_host_ip - ), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, - timestamp=tunnel_telemetry_json["timestamp"], - ) - ] - - MonkeyZTFindingService.create_or_add_to_existing( - test=zero_trust_consts.TEST_TUNNELING, - status=zero_trust_consts.STATUS_FAILED, - events=tunneling_events, - ) - - MonkeyZTFindingService.add_malicious_activity_to_timeline(tunneling_events) From 0246f6b981a16695a6f074073d5e7d28405d86ae Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 9 Sep 2022 07:52:12 -0400 Subject: [PATCH 143/175] Agent: Convert _cmd_island_port to int in one place --- monkey/common/network/network_utils.py | 1 + monkey/infection_monkey/monkey.py | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/monkey/common/network/network_utils.py b/monkey/common/network/network_utils.py index 13a54dce1..d211474a5 100644 --- a/monkey/common/network/network_utils.py +++ b/monkey/common/network/network_utils.py @@ -1,6 +1,7 @@ from typing import Optional, Tuple +# TODO: `address_to_port()` should return the port as an integer. def address_to_ip_port(address: str) -> Tuple[str, Optional[str]]: """ Split a string containing an IP address (and optionally a port) into IP and Port components. diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index c96b2f159..005fad62e 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -103,7 +103,9 @@ class InfectionMonkey: # TODO: Revisit variable names server = self._get_server() + # TODO: `address_to_port()` should return the port as an integer. self._cmd_island_ip, self._cmd_island_port = address_to_ip_port(server) + self._cmd_island_ip = int(self._cmd_island_ip) self._control_client = ControlClient(server_address=server) # TODO Refactor the telemetry messengers to accept control client @@ -189,7 +191,7 @@ class InfectionMonkey: self._relay = TCPRelay( relay_port, IPv4Address(self._cmd_island_ip), - int(self._cmd_island_port), + self._cmd_island_port, client_disconnect_timeout=config.keep_tunnel_open_time, ) @@ -427,7 +429,7 @@ class InfectionMonkey: def _close_tunnel(self): logger.info(f"Quitting tunnel {self._cmd_island_ip}") - notify_disconnect(self._cmd_island_ip, int(self._cmd_island_port)) + notify_disconnect(self._cmd_island_ip, self._cmd_island_port) def _send_log(self): monkey_log_path = get_agent_log_path() From 093e2c79dc3030baeec33aabd37f6c6ee2f90b6c Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Thu, 8 Sep 2022 20:42:19 +0000 Subject: [PATCH 144/175] Agent: Add relay to servers list for exploited hosts --- monkey/infection_monkey/monkey.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 9015d5c7f..5deb5e966 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -40,7 +40,7 @@ from infection_monkey.master import AutomatedMaster from infection_monkey.master.control_channel import ControlChannel from infection_monkey.model import VictimHostFactory from infection_monkey.network.firewall import app as firewall -from infection_monkey.network.info import get_free_tcp_port, get_network_interfaces +from infection_monkey.network.info import get_free_tcp_port, get_network_interfaces, local_ips from infection_monkey.network.relay import TCPRelay from infection_monkey.network.relay.utils import ( find_server, @@ -189,17 +189,18 @@ class InfectionMonkey: self._cmd_island_port, client_disconnect_timeout=config.keep_tunnel_open_time, ) + relay_servers = [f"{ip}:{relay_port}" for ip in local_ips()] if not maximum_depth_reached(config.propagation.maximum_depth, self._current_depth): self._relay.start() StateTelem(is_done=False, version=get_version()).send() - self._build_master() + self._build_master(relay_servers) register_signal_handlers(self._master) - def _build_master(self): + def _build_master(self, relay_servers: List[str]): local_network_interfaces = InfectionMonkey._get_local_network_interfaces() # TODO control_channel and control_client have same responsibilities, merge them @@ -221,7 +222,7 @@ class InfectionMonkey: self._master = AutomatedMaster( self._current_depth, - self._opts.servers, + self._opts.servers + relay_servers, puppet, telemetry_messenger, victim_host_factory, From 6171b66282e139b50a1d1e2a993f7e422894c20e Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Fri, 9 Sep 2022 13:01:51 +0200 Subject: [PATCH 145/175] Agent: Fix SSHCollector to publish list of Credentials Previously it was publish list of lists of Credentials --- .../credential_collectors/ssh_collector/ssh_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/credential_collectors/ssh_collector/ssh_handler.py b/monkey/infection_monkey/credential_collectors/ssh_collector/ssh_handler.py index 8c1129455..0212e18a4 100644 --- a/monkey/infection_monkey/credential_collectors/ssh_collector/ssh_handler.py +++ b/monkey/infection_monkey/credential_collectors/ssh_collector/ssh_handler.py @@ -173,7 +173,7 @@ def _publish_credentials_stolen_event( ): credentials_stolen_event = CredentialsStolenEvent( tags=SSH_COLLECTOR_EVENT_TAGS, - stolen_credentials=[collected_credentials], + stolen_credentials=collected_credentials, ) event_queue.publish(credentials_stolen_event) From 867a3a83e7e770b14b8899e2121b405924d58835 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Fri, 9 Sep 2022 13:03:16 +0200 Subject: [PATCH 146/175] Agent: Use get_plaintext in SSHExploiter login with password --- monkey/infection_monkey/exploit/sshexec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index 3ff128203..7de9b26ce 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -119,7 +119,7 @@ class SSHExploiter(HostExploiter): ssh.connect( self.host.ip_addr, username=user, - password=current_password, + password=get_plaintext(current_password), port=port, timeout=SSH_CONNECT_TIMEOUT, auth_timeout=SSH_AUTH_TIMEOUT, From 2b4b462c1a8ded87b3403707f767bc2c53b8afb1 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Fri, 9 Sep 2022 13:03:56 +0200 Subject: [PATCH 147/175] Agent: Relock Pipfile --- monkey/infection_monkey/Pipfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/Pipfile.lock b/monkey/infection_monkey/Pipfile.lock index cb73432a9..10bd542e7 100644 --- a/monkey/infection_monkey/Pipfile.lock +++ b/monkey/infection_monkey/Pipfile.lock @@ -243,7 +243,7 @@ "sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e", "sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f" ], - "markers": "python_version >= '3.6' and python_version < '4'", + "markers": "python_version >= '3.6' and python_version < '4.0'", "version": "==2.2.1" }, "egg-timer": { From a4b6e2157ba1ac27e7c92c9a02d08262c156c34c Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Fri, 9 Sep 2022 14:28:14 +0200 Subject: [PATCH 148/175] Agent: Cast cmd_island_port to int --- monkey/infection_monkey/monkey.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 5deb5e966..4ddc8655c 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -104,7 +104,7 @@ class InfectionMonkey: server = self._get_server() # TODO: `address_to_port()` should return the port as an integer. self._cmd_island_ip, self._cmd_island_port = address_to_ip_port(server) - self._cmd_island_ip = int(self._cmd_island_ip) + self._cmd_island_port = int(self._cmd_island_port) self._control_client = ControlClient(server_address=server) # TODO Refactor the telemetry messengers to accept control client From 46152c0cd269bea87365d09b1c78ecaaf194a277 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Fri, 9 Sep 2022 17:04:47 +0300 Subject: [PATCH 149/175] Island, Agent: Update pydantic to 1.10 Without this update SecretStr is of an unhashable type --- monkey/infection_monkey/Pipfile.lock | 363 +++++++++++++-------------- monkey/monkey_island/Pipfile.lock | 59 ++++- 2 files changed, 219 insertions(+), 203 deletions(-) diff --git a/monkey/infection_monkey/Pipfile.lock b/monkey/infection_monkey/Pipfile.lock index 10bd542e7..335ad117f 100644 --- a/monkey/infection_monkey/Pipfile.lock +++ b/monkey/infection_monkey/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "4035f0b638bdef6971dcf5c52a0aa0f2efc76310ad5a1ea853b6d21495ffa2b0" + "sha256": "cb55ba9c2d5b1763315473d9e766aade31137f61839644c1bcdf6ce0e7601ddb" }, "pipfile-spec": 6, "requires": { @@ -18,11 +18,11 @@ "default": { "aiosmb": { "hashes": [ - "sha256:0afa901093f0ad91d0b8421dec66c80bd2e9cb237a8da405984413a5d7475398", - "sha256:0e98390ba00fdc4190e698f184dfcf72b02b592cdfe9274e03cc7316ac4ee368" + "sha256:2668d63ae6e6ca30a999696c444d4afc349f85a872a204994394aa6abbf5673d", + "sha256:b0b0e7068e757b8d3a8e4be2ebf2b499933d9fa7853bc3a8e198d6a57dc77131" ], "markers": "python_version >= '3.7'", - "version": "==0.3.8" + "version": "==0.4.0" }, "aiowinreg": { "hashes": [ @@ -56,11 +56,11 @@ }, "asysocks": { "hashes": [ - "sha256:23d5fcfae71a75826c3ed787bd9b1bc3b189ec37658961bce83c9e99455e354c", - "sha256:731eda25d41783c5243153d3cb4f9357fef337c7317135488afab9ecd6b7f1a1" + "sha256:8f4516088ebec7f08d8c549e5e75e7a86b41e6043af920ba4895cf3f6c654150", + "sha256:d4c619c9d2e8be0cbdd21fa635a7eaf5886809edc948e2f76625b33b3cccfc47" ], "markers": "python_version >= '3.6'", - "version": "==0.1.7" + "version": "==0.2.1" }, "attrs": { "hashes": [ @@ -79,28 +79,29 @@ }, "bcrypt": { "hashes": [ - "sha256:2b02d6bfc6336d1094276f3f588aa1225a598e27f8e3388f4db9948cb707b521", - "sha256:433c410c2177057705da2a9f2cd01dd157493b2a7ac14c8593a16b3dab6b6bfb", - "sha256:4e029cef560967fb0cf4a802bcf4d562d3d6b4b1bf81de5ec1abbe0f1adb027e", - "sha256:61bae49580dce88095d669226d5076d0b9d927754cedbdf76c6c9f5099ad6f26", - "sha256:6d2cb9d969bfca5bc08e45864137276e4c3d3d7de2b162171def3d188bf9d34a", - "sha256:7180d98a96f00b1050e93f5b0f556e658605dd9f524d0b0e68ae7944673f525e", - "sha256:7d9ba2e41e330d2af4af6b1b6ec9e6128e91343d0b4afb9282e54e5508f31baa", - "sha256:7ff2069240c6bbe49109fe84ca80508773a904f5a8cb960e02a977f7f519b129", - "sha256:88273d806ab3a50d06bc6a2fc7c87d737dd669b76ad955f449c43095389bc8fb", - "sha256:a2c46100e315c3a5b90fdc53e429c006c5f962529bc27e1dfd656292c20ccc40", - "sha256:cd43303d6b8a165c29ec6756afd169faba9396a9472cdff753fe9f19b96ce2fa" + "sha256:0b0f0c7141622a31e9734b7f649451147c04ebb5122327ac0bd23744df84be90", + "sha256:1c3334446fac200499e8bc04a530ce3cf0b3d7151e0e4ac5c0dddd3d95e97843", + "sha256:2d0dd19aad87e4ab882ef1d12df505f4c52b28b69666ce83c528f42c07379227", + "sha256:594780b364fb45f2634c46ec8d3e61c1c0f1811c4f2da60e8eb15594ecbf93ed", + "sha256:7c7dd6c1f05bf89e65261d97ac3a6520f34c2acb369afb57e3ea4449be6ff8fd", + "sha256:845b1daf4df2dd94d2fdbc9454953ca9dd0e12970a0bfc9f3dcc6faea3fa96e4", + "sha256:8780e69f9deec9d60f947b169507d2c9816e4f11548f1f7ebee2af38b9b22ae4", + "sha256:bf413f2a9b0a2950fc750998899013f2e718d20fa4a58b85ca50b6df5ed1bbf9", + "sha256:bfb67f6a6c72dfb0a02f3df51550aa1862708e55128b22543e2b42c74f3620d7", + "sha256:c59c170fc9225faad04dde1ba61d85b413946e8ce2e5f5f5ff30dfd67283f319", + "sha256:dc6ec3dc19b1c193b2f7cf279d3e32e7caf447532fbcb7af0906fe4398900c33", + "sha256:ede0f506554571c8eda80db22b83c139303ec6b595b8f60c4c8157bdd0bdee36" ], "markers": "python_version >= '3.6'", - "version": "==3.2.2" + "version": "==4.0.0" }, "certifi": { "hashes": [ - "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d", - "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412" + "sha256:43dadad18a7f168740e66944e4fa82c6611848ff9056ad910f8f7a3e46ab89e0", + "sha256:cffdcd380919da6137f76633531a5817e3a9f268575c128249fb637e4f9e73fb" ], "markers": "python_version >= '3.6'", - "version": "==2022.6.15" + "version": "==2022.6.15.1" }, "cffi": { "hashes": [ @@ -181,11 +182,11 @@ }, "charset-normalizer": { "hashes": [ - "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5", - "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413" + "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845", + "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" ], "markers": "python_version >= '3.6'", - "version": "==2.1.0" + "version": "==2.1.1" }, "click": { "hashes": [ @@ -212,38 +213,42 @@ }, "cryptography": { "hashes": [ - "sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59", - "sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596", - "sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3", - "sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5", - "sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab", - "sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884", - "sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82", - "sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b", - "sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441", - "sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa", - "sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d", - "sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b", - "sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a", - "sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6", - "sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157", - "sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280", - "sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282", - "sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67", - "sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8", - "sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046", - "sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327", - "sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9" + "sha256:0297ffc478bdd237f5ca3a7dc96fc0d315670bfa099c04dc3a4a2172008a405a", + "sha256:10d1f29d6292fc95acb597bacefd5b9e812099d75a6469004fd38ba5471a977f", + "sha256:16fa61e7481f4b77ef53991075de29fc5bacb582a1244046d2e8b4bb72ef66d0", + "sha256:194044c6b89a2f9f169df475cc167f6157eb9151cc69af8a2a163481d45cc407", + "sha256:1db3d807a14931fa317f96435695d9ec386be7b84b618cc61cfa5d08b0ae33d7", + "sha256:3261725c0ef84e7592597606f6583385fed2a5ec3909f43bc475ade9729a41d6", + "sha256:3b72c360427889b40f36dc214630e688c2fe03e16c162ef0aa41da7ab1455153", + "sha256:3e3a2599e640927089f932295a9a247fc40a5bdf69b0484532f530471a382750", + "sha256:3fc26e22840b77326a764ceb5f02ca2d342305fba08f002a8c1f139540cdfaad", + "sha256:5067ee7f2bce36b11d0e334abcd1ccf8c541fc0bbdaf57cdd511fdee53e879b6", + "sha256:52e7bee800ec869b4031093875279f1ff2ed12c1e2f74923e8f49c916afd1d3b", + "sha256:64760ba5331e3f1794d0bcaabc0d0c39e8c60bf67d09c93dc0e54189dfd7cfe5", + "sha256:765fa194a0f3372d83005ab83ab35d7c5526c4e22951e46059b8ac678b44fa5a", + "sha256:79473cf8a5cbc471979bd9378c9f425384980fcf2ab6534b18ed7d0d9843987d", + "sha256:896dd3a66959d3a5ddcfc140a53391f69ff1e8f25d93f0e2e7830c6de90ceb9d", + "sha256:89ed49784ba88c221756ff4d4755dbc03b3c8d2c5103f6d6b4f83a0fb1e85294", + "sha256:ac7e48f7e7261207d750fa7e55eac2d45f720027d5703cd9007e9b37bbb59ac0", + "sha256:ad7353f6ddf285aeadfaf79e5a6829110106ff8189391704c1d8801aa0bae45a", + "sha256:b0163a849b6f315bf52815e238bc2b2346604413fa7c1601eea84bcddb5fb9ac", + "sha256:b6c9b706316d7b5a137c35e14f4103e2115b088c412140fdbd5f87c73284df61", + "sha256:c2e5856248a416767322c8668ef1845ad46ee62629266f84a8f007a317141013", + "sha256:ca9f6784ea96b55ff41708b92c3f6aeaebde4c560308e5fbbd3173fbc466e94e", + "sha256:d1a5bd52d684e49a36582193e0b89ff267704cd4025abefb9e26803adeb3e5fb", + "sha256:d3971e2749a723e9084dd507584e2a2761f78ad2c638aa31e80bc7a15c9db4f9", + "sha256:d4ef6cc305394ed669d4d9eebf10d3a101059bdcf2669c366ec1d14e4fb227bd", + "sha256:d9e69ae01f99abe6ad646947bba8941e896cb3aa805be2597a0400e0764b5818" ], "markers": "python_version >= '3.6'", - "version": "==37.0.4" + "version": "==38.0.1" }, "dnspython": { "hashes": [ "sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e", "sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f" ], - "markers": "python_version >= '3.6' and python_version < '4.0'", + "markers": "python_version >= '3.6' and python_version < '4'", "version": "==2.2.1" }, "egg-timer": { @@ -256,11 +261,11 @@ }, "flask": { "hashes": [ - "sha256:3c604c48c3d5b4c63e72134044c0b4fe90ff01ef65280b9fe2d38c8860d99fe5", - "sha256:9c2b81b9b1edcc835af72d600f1955e713a065e7cb41d7e51ee762b449d9c65d" + "sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b", + "sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526" ], "markers": "python_version >= '3.7'", - "version": "==2.2.1" + "version": "==2.2.2" }, "future": { "hashes": [ @@ -402,22 +407,6 @@ "markers": "python_version >= '3.7'", "version": "==2.1.1" }, - "marshmallow": { - "hashes": [ - "sha256:00040ab5ea0c608e8787137627a8efae97fabd60552a05dc889c888f814e75eb", - "sha256:635fb65a3285a31a30f276f30e958070f5214c7196202caa5c7ecf28f5274bc7" - ], - "index": "pypi", - "version": "==3.17.0" - }, - "marshmallow-enum": { - "hashes": [ - "sha256:38e697e11f45a8e64b4a1e664000897c659b60aa57bfa18d44e226a9920b6e58", - "sha256:57161ab3dbfde4f57adeb12090f39592e992b9c86d206d02f6bd03ebec60f072" - ], - "index": "pypi", - "version": "==1.5.1" - }, "minidump": { "hashes": [ "sha256:6a9d2152f76ae633c609e09b48b42f55bd5a6b65f920dbbec756e5d9134a6201", @@ -428,19 +417,19 @@ }, "minikerberos": { "hashes": [ - "sha256:3c383f67ebcf6f28105ed54f623a6a5c677a24e3f0c9ad69ed453f77e569d714", - "sha256:789f802263fa1882f701b123f6eec048b45cd731bf1b528870005daf07402047" + "sha256:c05cfcd846b1973b2b0d501e9e9fa2a263543d7762b052fc803fc1de849286a3", + "sha256:e912eb4bea899e1875707e7998001ed1047e1b32d5d7bf74d8b6137acf9614d3" ], "markers": "python_version >= '3.6'", - "version": "==0.2.20" + "version": "==0.3.1" }, "msldap": { "hashes": [ - "sha256:ac8174ed7e0162eb64b3e9dfeff13b2e1021612d0a4b2cfc6b8e5bed7c00ffe0", - "sha256:e2c22a6e396b4d7d65d73863ed44612120e8e2570ff895b5421ddf6a350085bb" + "sha256:236eacd04b0d2886e71b2890ec6c67fc626e1b9812c93a0fe21e998697415927", + "sha256:ccb5c1f40de165141931659eb71d4bbad326665aaff7bf23dd0dccb410dfa78c" ], "markers": "python_version >= '3.7'", - "version": "==0.3.40" + "version": "==0.4.0" }, "netifaces": { "hashes": [ @@ -492,14 +481,6 @@ ], "version": "==1.3.0" }, - "packaging": { - "hashes": [ - "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", - "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" - ], - "markers": "python_version >= '3.6'", - "version": "==21.3" - }, "paramiko": { "editable": true, "git": "https://github.com/VakarisZ/paramiko.git", @@ -522,49 +503,49 @@ }, "prompt-toolkit": { "hashes": [ - "sha256:859b283c50bde45f5f97829f77a4674d1c1fcd88539364f1b28a37805cfd89c0", - "sha256:d8916d3f62a7b67ab353a952ce4ced6a1d2587dfe9ef8ebc30dd7c386751f289" + "sha256:9696f386133df0fc8ca5af4895afe5d78f5fcfe5258111c2a79a1c3e41ffa96d", + "sha256:9ada952c9d1787f52ff6d5f3484d0b4df8952787c087edf6a1f7c2cb1ea88148" ], "markers": "python_full_version >= '3.6.2'", - "version": "==3.0.30" + "version": "==3.0.31" }, "psutil": { "hashes": [ - "sha256:068935df39055bf27a29824b95c801c7a5130f118b806eee663cad28dca97685", - "sha256:0904727e0b0a038830b019551cf3204dd48ef5c6868adc776e06e93d615fc5fc", - "sha256:0f15a19a05f39a09327345bc279c1ba4a8cfb0172cc0d3c7f7d16c813b2e7d36", - "sha256:19f36c16012ba9cfc742604df189f2f28d2720e23ff7d1e81602dbe066be9fd1", - "sha256:20b27771b077dcaa0de1de3ad52d22538fe101f9946d6dc7869e6f694f079329", - "sha256:28976df6c64ddd6320d281128817f32c29b539a52bdae5e192537bc338a9ec81", - "sha256:29a442e25fab1f4d05e2655bb1b8ab6887981838d22effa2396d584b740194de", - "sha256:3054e923204b8e9c23a55b23b6df73a8089ae1d075cb0bf711d3e9da1724ded4", - "sha256:32c52611756096ae91f5d1499fe6c53b86f4a9ada147ee42db4991ba1520e574", - "sha256:3a76ad658641172d9c6e593de6fe248ddde825b5866464c3b2ee26c35da9d237", - "sha256:44d1826150d49ffd62035785a9e2c56afcea66e55b43b8b630d7706276e87f22", - "sha256:4b6750a73a9c4a4e689490ccb862d53c7b976a2a35c4e1846d049dcc3f17d83b", - "sha256:56960b9e8edcca1456f8c86a196f0c3d8e3e361320071c93378d41445ffd28b0", - "sha256:57f1819b5d9e95cdfb0c881a8a5b7d542ed0b7c522d575706a80bedc848c8954", - "sha256:58678bbadae12e0db55186dc58f2888839228ac9f41cc7848853539b70490021", - "sha256:645bd4f7bb5b8633803e0b6746ff1628724668681a434482546887d22c7a9537", - "sha256:799759d809c31aab5fe4579e50addf84565e71c1dc9f1c31258f159ff70d3f87", - "sha256:79c9108d9aa7fa6fba6e668b61b82facc067a6b81517cab34d07a84aa89f3df0", - "sha256:91c7ff2a40c373d0cc9121d54bc5f31c4fa09c346528e6a08d1845bce5771ffc", - "sha256:9272167b5f5fbfe16945be3db475b3ce8d792386907e673a209da686176552af", - "sha256:944c4b4b82dc4a1b805329c980f270f170fdc9945464223f2ec8e57563139cf4", - "sha256:a6a11e48cb93a5fa606306493f439b4aa7c56cb03fc9ace7f6bfa21aaf07c453", - "sha256:a8746bfe4e8f659528c5c7e9af5090c5a7d252f32b2e859c584ef7d8efb1e689", - "sha256:abd9246e4cdd5b554a2ddd97c157e292ac11ef3e7af25ac56b08b455c829dca8", - "sha256:b14ee12da9338f5e5b3a3ef7ca58b3cba30f5b66f7662159762932e6d0b8f680", - "sha256:b88f75005586131276634027f4219d06e0561292be8bd6bc7f2f00bdabd63c4e", - "sha256:c7be9d7f5b0d206f0bbc3794b8e16fb7dbc53ec9e40bbe8787c6f2d38efcf6c9", - "sha256:d2d006286fbcb60f0b391741f520862e9b69f4019b4d738a2a45728c7e952f1b", - "sha256:db417f0865f90bdc07fa30e1aadc69b6f4cad7f86324b02aa842034efe8d8c4d", - "sha256:e7e10454cb1ab62cc6ce776e1c135a64045a11ec4c6d254d3f7689c16eb3efd2", - "sha256:f65f9a46d984b8cd9b3750c2bdb419b2996895b005aefa6cbaba9a143b1ce2c5", - "sha256:fea896b54f3a4ae6f790ac1d017101252c93f6fe075d0e7571543510f11d2676" + "sha256:14b29f581b5edab1f133563272a6011925401804d52d603c5c606936b49c8b97", + "sha256:256098b4f6ffea6441eb54ab3eb64db9ecef18f6a80d7ba91549195d55420f84", + "sha256:39ec06dc6c934fb53df10c1672e299145ce609ff0611b569e75a88f313634969", + "sha256:404f4816c16a2fcc4eaa36d7eb49a66df2d083e829d3e39ee8759a411dbc9ecf", + "sha256:42638876b7f5ef43cef8dcf640d3401b27a51ee3fa137cb2aa2e72e188414c32", + "sha256:4642fd93785a29353d6917a23e2ac6177308ef5e8be5cc17008d885cb9f70f12", + "sha256:4fb54941aac044a61db9d8eb56fc5bee207db3bc58645d657249030e15ba3727", + "sha256:561dec454853846d1dd0247b44c2e66a0a0c490f937086930ec4b8f83bf44f06", + "sha256:5d39e3a2d5c40efa977c9a8dd4f679763c43c6c255b1340a56489955dbca767c", + "sha256:614337922702e9be37a39954d67fdb9e855981624d8011a9927b8f2d3c9625d9", + "sha256:67b33f27fc0427483b61563a16c90d9f3b547eeb7af0ef1b9fe024cdc9b3a6ea", + "sha256:68b35cbff92d1f7103d8f1db77c977e72f49fcefae3d3d2b91c76b0e7aef48b8", + "sha256:7cbb795dcd8ed8fd238bc9e9f64ab188f3f4096d2e811b5a82da53d164b84c3f", + "sha256:8f024fbb26c8daf5d70287bb3edfafa22283c255287cf523c5d81721e8e5d82c", + "sha256:91aa0dac0c64688667b4285fa29354acfb3e834e1fd98b535b9986c883c2ce1d", + "sha256:94e621c6a4ddb2573d4d30cba074f6d1aa0186645917df42c811c473dd22b339", + "sha256:9770c1d25aee91417eba7869139d629d6328a9422ce1cdd112bd56377ca98444", + "sha256:b1928b9bf478d31fdffdb57101d18f9b70ed4e9b0e41af751851813547b2a9ab", + "sha256:b2f248ffc346f4f4f0d747ee1947963613216b06688be0be2e393986fe20dbbb", + "sha256:b315febaebae813326296872fdb4be92ad3ce10d1d742a6b0c49fb619481ed0b", + "sha256:b3591616fa07b15050b2f87e1cdefd06a554382e72866fcc0ab2be9d116486c8", + "sha256:b4018d5f9b6651f9896c7a7c2c9f4652e4eea53f10751c4e7d08a9093ab587ec", + "sha256:d75291912b945a7351d45df682f9644540d564d62115d4a20d45fa17dc2d48f8", + "sha256:dc9bda7d5ced744622f157cc8d8bdd51735dafcecff807e928ff26bdb0ff097d", + "sha256:e3ac2c0375ef498e74b9b4ec56df3c88be43fe56cac465627572dbfb21c4be34", + "sha256:e4c4a7636ffc47b7141864f1c5e7d649f42c54e49da2dd3cceb1c5f5d29bfc85", + "sha256:ed29ea0b9a372c5188cdb2ad39f937900a10fb5478dc077283bf86eeac678ef1", + "sha256:f40ba362fefc11d6bea4403f070078d60053ed422255bd838cd86a40674364c9", + "sha256:f4cb67215c10d4657e320037109939b1c1d2fd70ca3d76301992f89fe2edb1f1", + "sha256:f7929a516125f62399d6e8e026129c8835f6c5a3aab88c3fff1a05ee8feb840d", + "sha256:fd331866628d18223a4265371fd255774affd86244fc307ef66eaf00de0633d5", + "sha256:feb861a10b6c3bb00701063b37e4afc754f8217f0f09c42280586bd6ac712b5c" ], "index": "pypi", - "version": "==5.9.1" + "version": "==5.9.2" }, "pyasn1": { "hashes": [ @@ -648,44 +629,45 @@ }, "pydantic": { "hashes": [ - "sha256:1061c6ee6204f4f5a27133126854948e3b3d51fcc16ead2e5d04378c199b2f44", - "sha256:19b5686387ea0d1ea52ecc4cffb71abb21702c5e5b2ac626fd4dbaa0834aa49d", - "sha256:2bd446bdb7755c3a94e56d7bdfd3ee92396070efa8ef3a34fab9579fe6aa1d84", - "sha256:328558c9f2eed77bd8fffad3cef39dbbe3edc7044517f4625a769d45d4cf7555", - "sha256:32e0b4fb13ad4db4058a7c3c80e2569adbd810c25e6ca3bbd8b2a9cc2cc871d7", - "sha256:3ee0d69b2a5b341fc7927e92cae7ddcfd95e624dfc4870b32a85568bd65e6131", - "sha256:4aafd4e55e8ad5bd1b19572ea2df546ccace7945853832bb99422a79c70ce9b8", - "sha256:4b3946f87e5cef3ba2e7bd3a4eb5a20385fe36521d6cc1ebf3c08a6697c6cfb3", - "sha256:4de71c718c9756d679420c69f216776c2e977459f77e8f679a4a961dc7304a56", - "sha256:5565a49effe38d51882cb7bac18bda013cdb34d80ac336428e8908f0b72499b0", - "sha256:5803ad846cdd1ed0d97eb00292b870c29c1f03732a010e66908ff48a762f20e4", - "sha256:5da164119602212a3fe7e3bc08911a89db4710ae51444b4224c2382fd09ad453", - "sha256:615661bfc37e82ac677543704437ff737418e4ea04bef9cf11c6d27346606044", - "sha256:78a4d6bdfd116a559aeec9a4cfe77dda62acc6233f8b56a716edad2651023e5e", - "sha256:7d0f183b305629765910eaad707800d2f47c6ac5bcfb8c6397abdc30b69eeb15", - "sha256:7ead3cd020d526f75b4188e0a8d71c0dbbe1b4b6b5dc0ea775a93aca16256aeb", - "sha256:84d76ecc908d917f4684b354a39fd885d69dd0491be175f3465fe4b59811c001", - "sha256:8cb0bc509bfb71305d7a59d00163d5f9fc4530f0881ea32c74ff4f74c85f3d3d", - "sha256:91089b2e281713f3893cd01d8e576771cd5bfdfbff5d0ed95969f47ef6d676c3", - "sha256:9c9e04a6cdb7a363d7cb3ccf0efea51e0abb48e180c0d31dca8d247967d85c6e", - "sha256:a8c5360a0297a713b4123608a7909e6869e1b56d0e96eb0d792c27585d40757f", - "sha256:afacf6d2a41ed91fc631bade88b1d319c51ab5418870802cedb590b709c5ae3c", - "sha256:b34ba24f3e2d0b39b43f0ca62008f7ba962cff51efa56e64ee25c4af6eed987b", - "sha256:bd67cb2c2d9602ad159389c29e4ca964b86fa2f35c2faef54c3eb28b4efd36c8", - "sha256:c0f5e142ef8217019e3eef6ae1b6b55f09a7a15972958d44fbd228214cede567", - "sha256:cdb4272678db803ddf94caa4f94f8672e9a46bae4a44f167095e4d06fec12979", - "sha256:d70916235d478404a3fa8c997b003b5f33aeac4686ac1baa767234a0f8ac2326", - "sha256:d8ce3fb0841763a89322ea0432f1f59a2d3feae07a63ea2c958b2315e1ae8adb", - "sha256:e0b214e57623a535936005797567231a12d0da0c29711eb3514bc2b3cd008d0f", - "sha256:e631c70c9280e3129f071635b81207cad85e6c08e253539467e4ead0e5b219aa", - "sha256:e78578f0c7481c850d1c969aca9a65405887003484d24f6110458fb02cca7747", - "sha256:f0ca86b525264daa5f6b192f216a0d1e860b7383e3da1c65a1908f9c02f42801", - "sha256:f1a68f4f65a9ee64b6ccccb5bf7e17db07caebd2730109cb8a95863cfa9c4e55", - "sha256:fafe841be1103f340a24977f61dee76172e4ae5f647ab9e7fd1e1fca51524f08", - "sha256:ff68fc85355532ea77559ede81f35fff79a6a5543477e168ab3a381887caea76" + "sha256:05e00dbebbe810b33c7a7362f231893183bcc4251f3f2ff991c31d5c08240c42", + "sha256:06094d18dd5e6f2bbf93efa54991c3240964bb663b87729ac340eb5014310624", + "sha256:0b959f4d8211fc964772b595ebb25f7652da3f22322c007b6fed26846a40685e", + "sha256:19b3b9ccf97af2b7519c42032441a891a5e05c68368f40865a90eb88833c2559", + "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709", + "sha256:1ee433e274268a4b0c8fde7ad9d58ecba12b069a033ecc4645bb6303c062d2e9", + "sha256:216f3bcbf19c726b1cc22b099dd409aa371f55c08800bcea4c44c8f74b73478d", + "sha256:2d0567e60eb01bccda3a4df01df677adf6b437958d35c12a3ac3e0f078b0ee52", + "sha256:2e05aed07fa02231dbf03d0adb1be1d79cabb09025dd45aa094aa8b4e7b9dcda", + "sha256:352aedb1d71b8b0736c6d56ad2bd34c6982720644b0624462059ab29bd6e5912", + "sha256:355639d9afc76bcb9b0c3000ddcd08472ae75318a6eb67a15866b87e2efa168c", + "sha256:37c90345ec7dd2f1bcef82ce49b6235b40f282b94d3eec47e801baf864d15525", + "sha256:4b8795290deaae348c4eba0cebb196e1c6b98bdbe7f50b2d0d9a4a99716342fe", + "sha256:5760e164b807a48a8f25f8aa1a6d857e6ce62e7ec83ea5d5c5a802eac81bad41", + "sha256:6eb843dcc411b6a2237a694f5e1d649fc66c6064d02b204a7e9d194dff81eb4b", + "sha256:7b5ba54d026c2bd2cb769d3468885f23f43710f651688e91f5fb1edcf0ee9283", + "sha256:7c2abc4393dea97a4ccbb4ec7d8658d4e22c4765b7b9b9445588f16c71ad9965", + "sha256:81a7b66c3f499108b448f3f004801fcd7d7165fb4200acb03f1c2402da73ce4c", + "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410", + "sha256:9300fcbebf85f6339a02c6994b2eb3ff1b9c8c14f502058b5bf349d42447dcf5", + "sha256:9cabf4a7f05a776e7793e72793cd92cc865ea0e83a819f9ae4ecccb1b8aa6116", + "sha256:a1f5a63a6dfe19d719b1b6e6106561869d2efaca6167f84f5ab9347887d78b98", + "sha256:a4c805731c33a8db4b6ace45ce440c4ef5336e712508b4d9e1aafa617dc9907f", + "sha256:ae544c47bec47a86bc7d350f965d8b15540e27e5aa4f55170ac6a75e5f73b644", + "sha256:b97890e56a694486f772d36efd2ba31612739bc6f3caeee50e9e7e3ebd2fdd13", + "sha256:bb6ad4489af1bac6955d38ebcb95079a836af31e4c4f74aba1ca05bb9f6027bd", + "sha256:bedf309630209e78582ffacda64a21f96f3ed2e51fbf3962d4d488e503420254", + "sha256:c1ba1afb396148bbc70e9eaa8c06c1716fdddabaf86e7027c5988bae2a829ab6", + "sha256:c33602f93bfb67779f9c507e4d69451664524389546bacfe1bee13cae6dc7488", + "sha256:c4aac8e7103bf598373208f6299fa9a5cfd1fc571f2d40bf1dd1955a63d6eeb5", + "sha256:c6f981882aea41e021f72779ce2a4e87267458cc4d39ea990729e21ef18f0f8c", + "sha256:cc78cc83110d2f275ec1970e7a831f4e371ee92405332ebfe9860a715f8336e1", + "sha256:d49f3db871575e0426b12e2f32fdb25e579dea16486a26e5a0474af87cb1ab0a", + "sha256:dd3f9a40c16daf323cf913593083698caee97df2804aa36c4b3175d5ac1b92a2", + "sha256:e0bedafe4bc165ad0a56ac0bd7695df25c50f76961da29c050712596cf092d6d", + "sha256:e9069e1b01525a96e6ff49e25876d90d5a563bc31c658289a8772ae186552236" ], "index": "pypi", - "version": "==1.9.2" + "version": "==1.10.2" }, "pyinstaller": { "hashes": [ @@ -706,11 +688,11 @@ }, "pyinstaller-hooks-contrib": { "hashes": [ - "sha256:c4210fc50282c9c6a918e485e0bfae9405592390508e3be9fde19acc2213da56", - "sha256:e46f099934dd4577fb1ddcf37a99fa04027c92f8f5291c8802f326345988d001" + "sha256:d1dd6ea059dc30e77813cc12a5efa8b1d228e7da8f5b884fe11775f946db1784", + "sha256:e5edd4094175e78c178ef987b61be19efff6caa23d266ade456fc753e847f62e" ], "markers": "python_version >= '3.7'", - "version": "==2022.8" + "version": "==2022.10" }, "pymssql": { "hashes": [ @@ -811,21 +793,19 @@ }, "pyspnego": { "hashes": [ - "sha256:1ee612f20c843365fbc6cf7f95c526b4ee8795281641a9bb87083622a2f87939", - "sha256:284ca7a6218344bb90aeae02fb1d2ed73e5c991d6e4c16c0df404aeab5eb58a3", - "sha256:416fd2d67e82b44ba3d2d9062485056e4dde3c141630170e9190379d6b19972a", - "sha256:4c1be83e0aca12d64f5eec638259c77eaa8bf552c89ac69f0af2322a3be9afeb", - "sha256:4d1ea987b9c2539457235793014e0d9c5e4766da9e4e028d4b6b596cfbe53828", - "sha256:725df2030e5d1b1155bb696eca3d684f403164da8e6a6b0bee3eb02f8748f72b", - "sha256:7320539f45da463029e12f3742102085d2a0343bfe77ac550c11d2fdac1d34f5", - "sha256:77b86002082f278c3f5935d8b428a0d0659ea709e305537294ba95fc49907339", - "sha256:aa93d94775d01bf70d16840426d1ddd58c11a6a71c4d0d1d7e529ad541fa0a60", - "sha256:c2abca03b6d3c71d7ec9678c7b2220d99d9a29ef204b4c52549080169e586310", - "sha256:e6645107f200fb7bf6698512d04ea0790b292028861ce169eb97e5ad8eba14ed", - "sha256:f4784d9f8e9c50a36109d715a140150add1990fce16805a56217e8ccaf69d234" + "sha256:15cd6d3fc4d18b4b7f80259bfab1580c87dc9677d47e7cf801dad71dc23d1afc", + "sha256:210a2248060a2d789a333f7553a1a478d21812f675c507393169143cbf038d9b", + "sha256:4e967f29c099c196cbf4622587cd27e8c61f20adf78b1d3007b72596e60c9f23", + "sha256:4fab51afb757be21d543ddf78aaeb83db600a7e7daec773568db90d4b7499a2c", + "sha256:53d30afbef1255cb1a8930c14604184b07f989b6ac295a1397eac8c27fd59d8b", + "sha256:838f875ee55004a274f6470460e62b7713237ae8b66a02680a2f31e43b3b5387", + "sha256:b78a3370ace76209a52dc7816636a8c8437e323637eefe86a2193cc4ec352b3b", + "sha256:e08709c4e0838bf37d4ef8ceff2163a51abe2b071e285bb5774de5b73eab214f", + "sha256:ea8570c5363e5dd359aaf599eac6f70116e0ada734ebe557e17cc608c8bb93fc", + "sha256:fa2946ba5059f79d13cb8c47e83474de55569c16ed8f953cc47a24dda6f38f57" ], - "markers": "python_version >= '3.6'", - "version": "==0.5.3" + "markers": "python_version >= '3.7'", + "version": "==0.6.0" }, "pywin32": { "hashes": [ @@ -874,11 +854,11 @@ }, "setuptools": { "hashes": [ - "sha256:7c7854ee1429a240090297628dc9f75b35318d193537968e2dc14010ee2f5bca", - "sha256:dc2662692f47d99cb8ae15a784529adeed535bcd7c277fee0beccf961522baf6" + "sha256:2e24e0bec025f035a2e72cdd1961119f557d78ad331bb00ff82efb2ab8da8e82", + "sha256:7732871f4f7fa58fb6bdcaeadb0161b2bd046c85905dbaa066bdcbcc81953b57" ], "markers": "python_version >= '3.7'", - "version": "==63.4.1" + "version": "==65.3.0" }, "six": { "hashes": [ @@ -890,22 +870,22 @@ }, "tqdm": { "hashes": [ - "sha256:40be55d30e200777a307a7585aee69e4eabb46b4ec6a4b4a5f2d9f11e7d5408d", - "sha256:74a2cdefe14d11442cedf3ba4e21a3b84ff9a2dbdc6cfae2c34addb2a14a5ea6" + "sha256:5f4f682a004951c1b450bc753c710e9280c5746ce6ffedee253ddbcbf54cf1e4", + "sha256:6fee160d6ffcd1b1c68c65f14c829c22832bc401726335ce92c52d395944a6a1" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==4.64.0" + "version": "==4.64.1" }, "twisted": { "extras": [ "tls" ], "hashes": [ - "sha256:a047990f57dfae1e0bd2b7df2526d4f16dcdc843774dc108b78c52f2a5f13680", - "sha256:f9f7a91f94932477a9fc3b169d57f54f96c6e74a23d78d9ce54039a7f48928a2" + "sha256:8d4718d1e48dcc28933f8beb48dc71cfe77a125e37ad1eb7a3d0acc49baf6c99", + "sha256:e5b60de39f2d1da153fbe1874d885fe3fcbdb21fcc446fa759a53e8fc3513bed" ], - "markers": "python_full_version >= '3.6.7'", - "version": "==22.4.0" + "markers": "python_full_version >= '3.7.1'", + "version": "==22.8.0" }, "twisted-iocpsupport": { "hashes": [ @@ -935,11 +915,11 @@ }, "unicrypto": { "hashes": [ - "sha256:0487f9dd9009c326ee9531a79412ae18ad673425a1c800d64947b96fdeb04cdf", - "sha256:fab49ee41926bb31be49552aa135f7aedc04436b49c8fe326d7b6a823810575e" + "sha256:4d1de0f0a379bb4c213302ae61d927eb8f98179bde9a0ffb8e120998a0c882a6", + "sha256:9d5dd858ad5ad608fa524987b17e8855d64d6d2450ca0ca11638f4d92fc6c80b" ], "markers": "python_version >= '3.6'", - "version": "==0.0.8" + "version": "==0.0.9" }, "urllib3": { "hashes": [ @@ -958,11 +938,11 @@ }, "werkzeug": { "hashes": [ - "sha256:4d7013ef96fd197d1cdeb03e066c6c5a491ccb44758a5b2b91137319383e5a5a", - "sha256:7e1db6a5ba6b9a8be061e47e900456355b8714c0f238b0313f53afce1a55a79a" + "sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f", + "sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5" ], "markers": "python_version >= '3.7'", - "version": "==2.2.1" + "version": "==2.2.2" }, "winacl": { "hashes": [ @@ -972,14 +952,6 @@ "markers": "python_version >= '3.6'", "version": "==0.1.3" }, - "winsspi": { - "hashes": [ - "sha256:2f5a8d2c4b9f459144426909e26a74e550512e23b6cf9af52c2a00003c7c3fdb", - "sha256:59b7c7595f91414528cfd80c6cfc77ec6f5e4e28185ebd6418f8368ddc7aca82" - ], - "markers": "python_version >= '3.6'", - "version": "==0.0.10" - }, "winsys-3.x": { "hashes": [ "sha256:cef3df1dce2a5a71efa46446e6007ad9f7dbae31e83ffcc2ea3485c00c914cc3" @@ -992,6 +964,7 @@ "sha256:1d6b085e5c445141c475476000b661f60fff1aaa19f76bf82b7abb92e0ff4942", "sha256:b6a6be5711b1b6c8d55bda7a8befd75c48c12b770b9d227d31c1737dbf0d40a6" ], + "index": "pypi", "markers": "sys_platform == 'win32'", "version": "==1.5.1" }, diff --git a/monkey/monkey_island/Pipfile.lock b/monkey/monkey_island/Pipfile.lock index 5140b93b2..16908ab87 100644 --- a/monkey/monkey_island/Pipfile.lock +++ b/monkey/monkey_island/Pipfile.lock @@ -72,11 +72,11 @@ }, "certifi": { "hashes": [ - "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d", - "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412" + "sha256:43dadad18a7f168740e66944e4fa82c6611848ff9056ad910f8f7a3e46ab89e0", + "sha256:cffdcd380919da6137f76633531a5817e3a9f268575c128249fb637e4f9e73fb" ], "markers": "python_version >= '3.6'", - "version": "==2022.6.15" + "version": "==2022.6.15.1" }, "cffi": { "hashes": [ @@ -164,6 +164,14 @@ "markers": "python_version >= '3.7'", "version": "==8.1.3" }, + "colorama": { + "hashes": [ + "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da", + "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4" + ], + "markers": "platform_system == 'Windows'", + "version": "==0.4.5" + }, "cryptography": { "hashes": [ "sha256:0297ffc478bdd237f5ca3a7dc96fc0d315670bfa099c04dc3a4a2172008a405a", @@ -236,6 +244,13 @@ "index": "pypi", "version": "==0.3.9" }, + "future": { + "hashes": [ + "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.18.2" + }, "gevent": { "hashes": [ "sha256:0082d8a5d23c35812ce0e716a91ede597f6dd2c5ff508a02a998f73598c59397", @@ -485,6 +500,7 @@ "hashes": [ "sha256:a5488a3dd1fd021ce33f969780b88fe0f7eebb76eb20996d7318f307612a045b" ], + "index": "pypi", "markers": "sys_platform == 'win32'", "version": "==2022.5.30" }, @@ -641,6 +657,7 @@ "sha256:81a3ebc33b1367f301d1c8eda57eec4868e951504986d5d3fe437479dcdac5b2", "sha256:8455176fd1b86de97d859fed4ae0ef867bf998581f584c7a1a591246dfec330f", "sha256:845b178bd127bb074835d2eac635b980c58ec5e700ebadc8355062df708d5a71", + "sha256:858af7c2ab98f21ed06b642578b769ecfcabe4754648b033168a91536f7beef9", "sha256:87e18f29bac4a6be76a30e74de9c9005475e27100acf0830679420ce1fd9a6fd", "sha256:89d7baa847383b9814de640c6f1a8553d125ec65e2761ad146ea2e75a7ad197c", "sha256:8c7ad5cab282f53b9d78d51504330d1c88c83fbe187e472c07e6908a0293142e", @@ -741,14 +758,32 @@ "version": "==2022.2.1" }, "pywin32": { - "sys_platform": "== 'win32'", - "version": "*" + "hashes": [ + "sha256:25746d841201fd9f96b648a248f731c1dec851c9a08b8e33da8b56148e4c65cc", + "sha256:30c53d6ce44c12a316a06c153ea74152d3b1342610f1b99d40ba2795e5af0269", + "sha256:3c7bacf5e24298c86314f03fa20e16558a4e4138fc34615d7de4070c23e65af3", + "sha256:4f32145913a2447736dad62495199a8e280a77a0ca662daa2332acf849f0be48", + "sha256:7ffa0c0fa4ae4077e8b8aa73800540ef8c24530057768c3ac57c609f99a14fd4", + "sha256:94037b5259701988954931333aafd39cf897e990852115656b014ce72e052e96", + "sha256:bb2ea2aa81e96eee6a6b79d87e1d1648d3f8b87f9a64499e0b92b30d141e76df", + "sha256:be253e7b14bc601718f014d2832e4c18a5b023cbe72db826da63df76b77507a1", + "sha256:cbbe34dad39bdbaa2889a424d28752f1b4971939b14b1bb48cbf0182a3bcfc43", + "sha256:d24a3382f013b21aa24a5cfbfad5a2cd9926610c0affde3e8ab5b3d7dbcf4ac9", + "sha256:d3ee45adff48e0551d1aa60d2ec066fec006083b791f5c3527c40cd8aefac71f", + "sha256:de9827c23321dcf43d2f288f09f3b6d772fee11e809015bdae9e69fe13213988", + "sha256:ead865a2e179b30fb717831f73cf4373401fc62fbc3455a0889a7ddac848f83e", + "sha256:f64c0377cf01b61bd5e76c25e1480ca8ab3b73f0c4add50538d332afdf8f69c5" + ], + "index": "pypi", + "markers": "sys_platform == 'win32'", + "version": "==304" }, "pywin32-ctypes": { "hashes": [ "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942", "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98" ], + "index": "pypi", "markers": "sys_platform == 'win32'", "version": "==0.2.0" }, @@ -965,11 +1000,11 @@ }, "certifi": { "hashes": [ - "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d", - "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412" + "sha256:43dadad18a7f168740e66944e4fa82c6611848ff9056ad910f8f7a3e46ab89e0", + "sha256:cffdcd380919da6137f76633531a5817e3a9f268575c128249fb637e4f9e73fb" ], "markers": "python_version >= '3.6'", - "version": "==2022.6.15" + "version": "==2022.6.15.1" }, "charset-normalizer": { "hashes": [ @@ -987,6 +1022,14 @@ "markers": "python_version >= '3.7'", "version": "==8.1.3" }, + "colorama": { + "hashes": [ + "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da", + "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4" + ], + "markers": "platform_system == 'Windows'", + "version": "==0.4.5" + }, "coverage": { "hashes": [ "sha256:01778769097dbd705a24e221f42be885c544bb91251747a8a3efdec6eb4788f2", From c756c7ace2b1983bdb6a5aba112270a0a9fce28c Mon Sep 17 00:00:00 2001 From: vakarisz Date: Fri, 9 Sep 2022 17:51:37 +0300 Subject: [PATCH 150/175] BB: Add tunneling test diagrams --- .../docs/images/tunneling_diagram.png | Bin 0 -> 22821 bytes envs/monkey_zoo/docs/tunneling_diagram.drawio | 1 + 2 files changed, 1 insertion(+) create mode 100644 envs/monkey_zoo/docs/images/tunneling_diagram.png create mode 100644 envs/monkey_zoo/docs/tunneling_diagram.drawio diff --git a/envs/monkey_zoo/docs/images/tunneling_diagram.png b/envs/monkey_zoo/docs/images/tunneling_diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..fdd63e96886597d81ecce644da4bfd0f45c09b53 GIT binary patch literal 22821 zcmeFZ2T+vjwkAxHpb`WT2||;_05nZ%kqk}F8Ob>{sfkT&K?Omg5+z6uA_^!-7EwSN zkt6~Nh=3$Xl9Tg(=)U*NIs445zv{1scMGt*JzLlIbJ?0Rg3o zvVtxF0Wlc?0iiKDDfotzEnEToMTpT=k|TKXo@JhZfXv%R(bxwa;Na|LPr!|k|NV(u zNYKdx1wb{_s1@KIAA`~epPUnAteC-6WN zOagrj5EbSFf601!y4f4q+o(B%)lnh{F@A(7`1+!nvc85ow~##e?B?uh5B^fJw{`V^ zhR8d4d!WHLD7c_7KbZP>O+9M|Yj5X&m;yS9y^poy?^8I63p)9z`)E0e`Z*h!V#JXm z7>EGi9Y1?-jI)RPUx(#KK+{1Fd;&e~e}8Ld@8@g_78K$}C;~$Mp%{3@`45c@Foqai zA7yt-V@J!t02Og}T^pC*lek-hogUL#8<6QL{P!echTb+dz8XS`+Je^FNNXKkO@z3w zx-v%JMA1jdS=K|)#UJIOp=T(hW~k^WswysOXK(DI=wyR}+Z!1nee^Jj%7G>tq8h5o z>RMX1+PbDX>S$316K`-jSIqzqCk<~U4QmCAoWHGJpuVPWpsJs`gR`==TY#ybsiz(a zt%XvR(-qgi0&+QFJUv8}Fmfg$I>wHEu5NN_T80Qi7d>?i4JVrbw4*RqS6?52(F!z% zdx?rTx}dz2L;~dXeEbYhx>$ctZyOO8Wi26Xc@2A2KW|k}HAQhXI~&Kps8l>;OdRA71njNb=SAoLu(s>rA5_|SlIw47j=6Pe_3~Xd2Kl{B>37gHX_YAPtQ1M2ACz{rJ8md@5w342lx(66wBqyk+rtYc`=&fR4 zql{JpuNZiH=P?0DG+>8N4X$WlKoex z>MH07iD2EdP;xekZYW1TFJS|$oRNXMn~0}hpth&3k+&z@M#I3!6J_HshxD;iQ}Gjq z8=1Ol`0L0jXxq7i#k_>U4(t^Sv1k(`4`F*bq_3AI(%DncUdU5c&{R&@P9B3o2_x)m z#S{_3CZdL3ej4Jgs)FjWy6zZjJwrV~J$q%OD3E3ggDsky+FusQ|^7a=tjU3YANr?9)IyD2nW)eo+TG|?7x*A>HH z>`X=UZ3Xp>9gW=lRg5vpSOpb(ZGEReag2t8vx1njps$h$+{w#P$;DXT0fkoeR#eo} zl=D(oLK}JOC>w+6Y^)t+MSajHQx{`zUvLd_HiCxQ3ihT30opbS-Ux(;n~yz0M@-yA z0kDN!AW{zlU5>7hi;=ge10abS#s`kF0UtE<>^;R*;C{NkDp*%xKN}$@Asrzl8+{co z2U|mLxUoB26{GE`X5tfokaM^8cGGr|w{cSU7XrjZsmj9LjRQr60bk0=%fp5I5#UQL zF(DB}thkMhi;JD6p`RDR0f}-o5_Pb%!-@r}`)JCEVwF{ewG>eHZtf=FeM3hvN2Hv- zwU!pvTiaVpLlf)buc;-c2_7j4D~TBEs3^GkJ7fIpwH=Iv23lR-!$Ct+M-=7egpm^y z(Z!j*0Z3JII2%A2^j3oF~0>U$e$xWYY=J|=!nUJl+) zO7dR%28NEd#zJ~dMnK!j*@*bVolRshntsC8NI$r^gDu#DxQ#f_qbM&qQ(28bCvj6G zQW*SO&Jld)EhHyw=K!pWoC){?rS7L7rr_W#C#)l?0bQn#jjIc|OF6i(Dgxu6XYA(U z>T9oN2ZRmfZ6jx71C0^&^|w}3_tn%C^_I1E^|RIXQFd~)b2sn{aB|hq4*;WG3{8X( z8g_o*BSyhl*&ov6(EmU`q5iT0$0h;%`;U1=D3-jXup=OV5vVB0>IYc-$O?LFI2>4< zO8NC1^Yw7}$rnR3N+r(p94@8K)FpL&B;Rwm%E~`beRy~GESC&TtbjP&zr)Xln3zb3 ztLzF{KCbM=^uY5fGA-C{3o^aHn{pg!(0d;P%lcpM!VhnlM)+VH%a{0>HdlcBJp zBBH4-+9xg(dq~jW?d0B?KSiJN!5e@l$ zf?1fh_1rn=C}1(0|BDwh>Y96E-Z1#gwqvl&p#1T^e!)#jX3>&Tz2dU{l@`ki^9Doo z*b5DfG{Hy8^yw=__bTqf8~Qmh+KqtJGP!pE;Uy!r257=@Nes>QWDd>sV5ajDi<4rd-a-__Ej8rRW zWQuB-*ZZhkwQM%4UGopy_TS6mR!i|2jn2DYuk}#;kpn*Mk!JSw5jdH1mq%@=MHkie{DTMdMmL4~}WEdPRh$iX}|&*vTjT z_I%i4k!wTf2F>BfDS<}Mfo$S*B+UR!rD;i8uE-} zfav6tTTwA_^G4tZNHP?18D?cfX}XtWKC>u@o$ctsJ>HL(!p!3PRDQ(>G^aUHB{}e) zyJ}i6MsG_iM&{ADcH6WG``n$wIZU?SVe!(INnL-w3jCch5Up7tufke-C=x<);mGiE zyPB~oj@@$;_;U(kGz@^MyzGTnFRKcX;aP;aoES-g$ORFlMU1L-0&RsWqAhvHa>b(2 z*n=o#Ms6M56s*Vgg;U!O?C!EsDvRPaX8{;I_OmrRq6j7Q?Ir=qf&#b{-sf|dNnjr? z5k0})#xonlbUB(CmleHdkza<(hkFiBIt7p=plkG zCu4XxtM$CB2OyCPxVN&esuj=5@N;QsMv!`%>}FBPRiFp?3xgJo1XZ; z7yGZs_>^O{m6*EZaDV&8knC3nn!3Z)!%;pMrv;r!gLi}tzS4^t8JsBn+Yk^6XKpvy#ah|AZuTP{Cmdt5f=`o!Sl zPpu;$Njus{VrEOs8~jQs=y(cT*rlcYmeNe0yBHWX^^bSo7`GSP`S{v?q7-5m84Ivt zV?h8l5i3PPWI*HvPd%w`t!d%g6JeH+3e1d9OXV+}=}s*-sdjLVfRV~%2gprKOjM2s ze7pC4qPWRXTg^r~>?rto=;7XzS=f)J#u;<9MLp`_kYXuv!0v&79_vX{lH>tgGPF}? z{8}}3??!Mc#g)9od@u7|{8;*>!QbD3Y6?MNL&UY#n?XOfm3?2@Ybr7WZhQM$;JRHI zQaMP#WA~EXW}y;&NU`R>nKUkp}>5bhtjSbIwhWCpAawme@xOe}7>`+$tO2O0R#*ERh39keZy@U>y=|D2fhruX& zGe9FcAVnFu9JWp@gcB;(-v~tQWSYOV`pvdp(Hd^b&OD#$>JfqgW6)hwr( zQ*HIvjbgE;UA;i&g-F=0$!k;t#s*Gi{g$N(C@@$!5=IK6fkFESC!b5wedo_|_T_h2 zy-DE8c+;L!|D5gewe#S=s?w20Ix(`fo?hn_`@@h2xNkS&FLlU$ffPYMI8GELIf-&O z7nh6<_5=4+F4>0$;pl%+8c#W7Xl-s)!f!~{Onx2sl~*J@UnR~PAL`5(+k zr(9BEhi;@9Tyrp8u_XC4Ohz}2Sof3#JJpiHIn3%;flwQx=!ky*gE!I_@i^iHeL$iB z_Hg9t&2$t@JC;Dj8g8Kxk}3XF?;OUeBfcNdTLMfrS}KuG^$;yPgppQ2Qu4%}@V!*(2kNgfk1M(m% zE;Oq-hCk?U%z`k8U2R2oWV(c+| z;+~VO8fv%S4So+YNlc44^{ZKH1(D!5C^;~+`pkb`l5G9g%*)^d&qq;hEN9!^bb||x z2N#OHLeL`*i-w3q4~Qf|Rna%CTIdXCCNeMom(ZEX>6f|q{c4P6W85346?e&igCqA9Rz|`*Ivx(tk zI(i?_ue~!?myn)Ymt_?}1VKiWMogXHOhlwev7>o1W-EUCkOt@h#bsm^|C!MS|4J!u z;k~p5DpFF?jd`W97w7?AhQcox8cT1xg>^I)*KX>)jy}4Wx4jkx@0Y@Kde2)?9kfe9 zK1UO0ICAbnkm+riOF(n5>qe@Ci$5JGoYN`t)-SzI#};fRaJAlVhT5gQp!f5qgoBtI zr`J{~j9p7|<9E`3n+FP0Q{ep_l|89aG(SIGNuyT_)fFsR0KlRjfi5#z2_VW|qeEi`L7E*9#1%XmPui zGH`d=BBJ1X6I<`%l48P)qEJx6cR67^E-UUnaG(!>jP1;5xYBegh8GAj6uoUxVELr- zaF`{nP)>+WaF;n#m-#`=2hy!j%U_M1>m2{(xX!gg$MMfk94S#>LcX1wuHn5F1goa> z&h-AcL^#{3siX=Vuu#CW2kS*5OP9 zlG+_5`|fqEKS}p(WwA^A_8mxxIbflMyt9XH^ngT$U0K(Rr&{@`s_?kaj_ViRWe0$Sg^x{4p>DEy$rURFD7va(T zY4bfXUY5tu0vHn_j&LNo^67daLizB51Y;VacAPnj4}z?!Kw0!`cM?ZP$E&{3kl6VO zPU(^7ebg3~6%!AX7V7q1HDD)j$f^73#1cIhDGA9pvVq#!X8J%({#gM`l-tVksY50S znJ^H2J3jLW5Se`8Z+@^p;O`Ro3=8hfLAgK8h9?O$@6oQ@2%6uqdHWg}!4NITnV zA?5VtHaRkJ8BVjNw8hl96#nuX ztkluDBg2Mj$)`Y=1U8vUFxx8NVHzRBPsDZWrJ%?WcQkSKI!Q{%?!dyB<G;eEaI3cD&c&z~u3ga*N01VbaL264#MOA_~h@=F0M zm_ooug}Ve;xGYyStC^zn@hcEfmp(CjnTLvFDb>unHsiYm`*BeZ5dr)4KuP^3GFu-X zaXy0Uo0aI*?h{T`kxuv!M8mSIU?ZHYnKOP1L?z7N>X@BoAJF&=BCO=^i zCK&@YVoX#ZPErQksFItPeD}C$vP1-b>}5-Kx>A+R-b9vU2yOz==Le#1)?dI+E)JO6 zlO)Lni-KKqwYv5-XbW%^V%PaR2*}_d_jB(}{$dYzIMV6K3-tMPJd=8wpe#s?sNZ8m zpt6t{)UVyCH!&`rp4wSbA*0>n5p%O&EihX35|4&M@2 z8U9dnV^B}4^m}%Y=G7Z7)YAjTtL$S;UwRi}_vVW?=M}F*9vqB@6|5eR@qv@vCy7s6 zCYORlut7sR5e}wA13T#y(;r)HNTOWi!p)G?1CzN<#{msn08ywmTg9An40(e3bLFLT zrym5k<2p~DA);|Q4JNcLu!CuE$OsK_i7=j8Q$G!6qbZ>V4?=44imyTsIG)`Q93*26 zkZIbT&AfV^Pd*ZCMmr3|5PPnjQM&X+8$ji%b=w^>VpgsmmcNW=WpaWvTt0GlD|Vh;-UmLD$zCP$qa2& zc4jL!F~B|kW9(V5A8D|kLlHX?N-nUU|6c6B$@u>$gV0g9eXi4y+#J+fc=2-r9-KpsYK}6F1a>)Y_~uUIUUW0j3&NLEXtzo$TVx(a#h` z>l#&B{5b9PN7?H1O6|Mp-RY?_1*yglM2UEH&2&&P#zEUIvSqb}2fJ$$-VCH#m@4QB9<1?Z#c( zb{1hvTNF76!0&(skfuUOw`meaBJQw-8aHk9t1XE*MHQjhNN00@oN3(ww7PbtPLa>I zN%Mlcyk(`OQc~45HQF}1bA(eb8(G^ifqn)LxU$PR)SAGaE|SzBC)pabr?s=XE8FIE zny_tR_pRq|vHD?Kd*gBz`{%x9T-*ePkR6y9lA@DrQhqo8Jg`4;S{m z7SUOVARv*0?1ehLRPYiB?$3j`iw8?PFZu7WK8e<40|tTvs7b4`)vp8QG{|4SZcOYM zE}Y3(J#{&^@3kiN&d3_ZoJrD*t`OvCV8BC)Xx0Dvy0`(BuM}^{7IyHg8MY)bvHM;(x!{^iamkz$QBu?D2R&a)Oh4FO|Ku*tH0S1xaKb>zSiWm z5{c$Zv{z?Y{QWg*`O9LxdS5{qh{005A&_-wRO~KPOWjaWKXvYkc2%s)kJ0BYAe>Vb zwQUI4+ut9;rz3m~PBHN7XNq|k+BBL;w^v`#VK}g(7*FC>^EnO55So!C5>}=0ar=B${xQ6F{l2k z%Hi4E%F13U)yFklXefm+R_kG;TN|sD4H6Ydw9=(=IqL6Or|aYx4&(&Mo~WbEF4+=U z1kOB&3!HfFntIK1GF+774%LPJwX0#@^QieZ$i+X;F+sI1;5E}2!&blA@V87V~9}ZTQpo+p~gWj@G*c%Gr)q?_g1?Qvxn5k#Tr_t~aIa>ZYF+ z$D-c)_2XY5daviNaI5S|M*rgPT%Lbo0?*Jel8`}L5j zj}xTeB?|ff; zCE2q7Wo59xP;KaA#f_n+?eQ`A`L}NEsr1pvgc%tdeo#s33$#qinL8c6vAFv^N6Sn% z(T+-;%}hXoJ-eZ!I$ly(huF&IN)JoHo6=z79NuNF7kOSDDX-J*72mRO9cFi~BX_wTXgvu0_}D}<sBkV5(NV$YnfH1wRcqN?IZ$bqVNb?HF& zJuNBWcuDCN^}w(AWT+xuwswZeLEh^#OPakJDgGf6GbnvHAU&TRUc=Sx1`|~o6>DC* z_x^Nb@IIRc`LEraU%N$xnte06Lit_?J6^@i**(T(AM*PN7G!4(CU(r*K}o7v>6<=Y z)`!pZGfSuLi#blU)#a97N138rN*W%)CIgkBo8?bXBUid&mIV?-(~)zsANsj~8FPZv z;52u768d%sVKX0%tJPSq+A z^J!qoADmf#yW&18X%q_v&d5l;Q04*2s;9JY*f1I?R2#OgG5Kcd{e6Fq1aDxLDl4T`WzzFa@xO zz~I6Jkt#(?B&9cVOm5nyAkx0k+3&_5tbo*KVGnM^(Q26`VXr_kx#l}-xY^8KSO9ZF zE)~;_nmnLzN-dApQ%fDklv3%_^&TvB@}@qKi@&rtQhqJu^A6hK(KvRof}MGqnoa6) zo|o=NmUgYnLM^v%UNcK!nE~qj8VJluiq1j$tLA~_!Pt#q zvDYaEg%#N88G}tpvcUZ_Z9xtyNqDDDy_udb_8n^1Q@NFpK=o-J8`L-d{}@zTUh+Sd z)=%N<-80o^qc2dn@yag6PkK3p#&qj5{=G{ybQUYWsWR^MBsQz**s6%?}h0 z0kbp+?Eg@W^8l^MUlsGK#!qEHPS)h-ebMhdnZAe^{E{la5dV_EQan+zy?1*^LWQ~z{b zFlCIO*Hk;ZT5S9I&YxmoKgBL!U@KS5>-EuJ>J4}g*WZJ>(jbIAcqD(P<{3@3pzYh! z<__=f^No_YeR?XJCGIs&$LciFr^`CP~GQqR3E*3$|q(tLE zt&-48PGIf1KaM8dzpVk$6d)54yAzA5N{{QDLgTmnD$lLy_0sD=qk z6o#*eUQXD`hEsXh)LeH)kbN1CdCz6{#oO{N%jYd#%pl0HmzA@zH1TQjNbN*2ecC^4 zTZ=$mjE#kA*3`hS$C9&QCRv*+?JkvmUsJd=-`m%K>_>TIhI6M!T>nvTBwy-|eJCAf zaX6t@p{E>%Ci7}{p`@roiN2F846Kod#(4OUH| z=tvE!!aKfpG&D{Sz{^T^Uz3!Gx!Knr+a=&mIu(8Y;QD2i-k)(Y3tJY!CW&0Zz0$ve zzo&S*cAmJT+%K`jWge=Y$oe^KhARW7>K3%y>z4JZr5%VzuA)!5@@gNt%6VLZQ1 zY|n>~uE*9p)-y?M@i%>!Icm9{8qdamwkj<`t}cL)h-MDTGsd5Y5?hgABzN2S_+_43 zdMrI6KsSe!vp2}$ux@belWlw{i0u6}e^uQ!%e`5``u1#Rw9Rm(hi`pe?0@5?EiJTCyN? zZy={EUTW{65=BeppGa}SBmcyWWRr82TAx{_B`Yqj=;qCdzsray)4H6LeAf=KeyetS ze)*k>qOH_eG5=AKC6tmslEC9CqY5s7EiiyBH06#L_E_-L;vQWb%CcC$?A#%HCK zdBJ@6wrk^R+o|Yc+BZG2878YCy*N;z(Y4!Ri30lXraN=Wz7p=vg40PNdfj z^unjriUwk72w!N`i@TsXH43+bAZ$^VGojQfBQ*(9;$k^5>NBb9_^omWcy*^EeS5Tg#uG zaUy2%+@W`<*$e|U``5ip6CY~(%=Y}iezY3=BIYiu8I)H|CNu`C<#X&|wW3-IrG~Cz z2Y9)w-4fLzn*Lo1XA6*m6ur}3$r7nDGe8Dtcg4m7m|De(~EDiXq^m3-6_ddL6_koc`pKtfK zy=>x<$l%SvhWcf#t991y^^$*Anl#bhMS|=QEl?`TE*A^yvrhHis~WM{RWDNaz*oHa zgCj76ip#aX&OtbWDt87|l`5a9b_x;T+xP+vu0VXI27fNNQg;L zjDzArUr@0iSLah9t<(9ac{WGv9V&=PD>&(>(Pv)wx$mMO06oNKR&zK77aJ2Fj>+zphE;R(gw zImFCJq%Q!3QuO__TaURUdnNiEY9-0m>=oCSfA&gOZY_-r4BekrDXOCP{7Kq1NEz4s z4asWcm7meU;uBP?9`eQEWc^j^cXYirRHVJxgS<5KaI&h$%3F+P*Y_|XkDXIWd9uG8 zG{{^9(!vI$MfxG8wkUG4ktz7Y6 zF!UH7FkjMt$FCtoe;avmPou@eCe=Y9<|FUfvWI`FxOJ&9y>CAcr!qdQlD4*5p}ywN z5>+bxb2i3&0gdwVAZ6w<)APOFl{xKY_{0NPAxymv45!v3zrJ6nQd7Ty#oo=CQz!I zJF&@+_U^@H!N4{ei|J>~RnKxKKY#25{C;ea6+R8_vb#Rx0umirlvxI8#42(8Y`=VY zos&Fld%R;F+p?+@_0z9i=v}LO7gVpEJ%bW@*SM(|-QI)y;jHL=t#8#Ud1%rE{&fGE z$`Per(7!1zx1Xg0Ih5N_4rLBxe`Hrg&i4~fbjet~u3gp?*rf*=es6iaDQM_m_vfA1 zFQ0t{1qE}}^VpiUVcG56A)KxYcuY#8b0fZr19P^3OJ&V*DniTW5GA7F+3egT`}U8K zsDX?`M#jE~MZniv?8TG-;E~{ygBzUL%Z{j_Y`5|38OH^sHfHa6==dPKH@KsH`Eu-N zq8t~)MoU48Wff#*C(rn07X|YY2kz+?`%gL+1C#sVN6k4nRTW7-NU3+}d*E^A8<9rW z__7D43>=8sVW+bXn^dpysr^;dcCGXL|Vu3#dRK!2mL80{zG__Ln^rmu5tOfw*i-HC=OV_oIP(^aC!J z{^8+8X*F{P;MjUyT~AQArn=j%W&v;`jpvQ3ABmTm)!KVk#q)$RR{<=*f@%X_dVt>} z9$!$qYz9&socMl(stc{D4NRjpUtxC!F`t;GuBi~G)OVBqysf?dUJPp=oW{s!>^cYU2QD)8Dhhz-&H3k1@_>~FOL+$ z$wp!Wh(YlJR90XtS9gj&;LdUB`O(Di=v9rIW=B&?JxR4TYgi`}vFUeocT+xkqvhl6 zkiuAq{;0kH2RiXmd-X)X3)ibn!fR&qCJO*N{ksNVrkKS=d~Q{_XlauEplHZ#`Tpjx zkRDA~`;|^~)w<7(j(bSu)O%snOLEt)W!f7J-0OE8uF!KGe#~cJ(Eo*^@KPQPNIB!~ z0uH@IYF2|y!sWH!Zl(rNyrKgY*Vj~y4)3ZM7V7kk#_ex`S$bw%$fS{o>kN5o8jF+} z?bYVj8~(Lo@;kxv$F-pP8`mNn`7~zQMfK;3+V9>E@)$ssE0TaJy3|sQKL}<;$h-@_ zrHj=rnndXMYpskr`E1EJFi(rlJRTv&1Aq~KQaN8wi@-4sSO#_lcv(+305oCY)t%Z4 zt=DH;Kf^9x4RLuDhbQg5fUUrsYeb$Da>WSE0yqkzv19-)%o0nG#RH%}E5JJ)PxSXl zp#Gh65{O3%DkZ-QDG5%FpM#Q>{ zZq-SmgMG6T1`DA2FUR58?R!*sN`-ol2$xOUnSrFi2nK8wNa0GNkO_wrRlSo_eL0J^ z8r5-W?JPSK5g<@6|Engqur=mU`BdYNXXv4sb{^mPtJEzkx`*7~KC2w(1@icDtSsA5Cr!?U@G_ z#Feu(mTzB3*s%TT`g%LK{NnpB8y1 zllzDy36FcIDIEOS4uT!}e*F=W#bp`WHu*9cXt9sQ7B_hWy03QG!0`J*5isAoT?sQP zX%XWj$#^EW(Ha%=Cal4|^Kd!I^-j(QkOzs)hgX2hqPYmcHf}T7!#-#e{S$^spQEp* zvo95jk@k|^2p*Y|_C zNjZ)>Er`=EB=mGTYmGiD0@!n6J^*wvE%OFI7wwAdK?t4P$_mz@Akf=(_(RR@W-eAGs(*XK{QGH3a?r88I*-w1+CjiMBF|Jihvxp z?<}72@Oun{K-sHTvc3S+{yhi;+C%dyJrFj<44^@d>z^fXTpcojBox?|Ys`0(Z#%fF zix!_zoJhRay*54ZIqAwu;g|cd$P)gjX`j5}Y^lHV;zaOwUAR5+7^$c1uwlIRw67c#lvHf00>OI{!{ zNzF}X;O=Ad?Pb_t697KT!eiWj=xkcBffI%Qb_44f5poYw0vIvcM5bLFQndbT==&^E zZ=hZd85jyIp$wL|{q!H zmjjUV%aJEDoI@TQPgY~0OaNw}@dPb1ljK}O(B~3PLbm9Jc+rK! z*-wikm}^;TbTu-@0osHxgy&nMMaV$XH~;&Yj4kvJ8c(Ph<~HZ^<0-j+Pw7IDqj@|f zNHoD@bmky}6I%Y6@=c82@swF$%K4Ls{j`ElnT(<2neCp0e5lTQxJoi(jM<5KV|iTzp41oNX1D|She;oX&a{rqi?%v z;ybeQn&m!;CLE%K2JCnZ6kW~J3&ao66!ljpL?uT>Eo%Fwnnjr<1<95-b5YQX&)Kbh z5+?a4NR4)mrSi0peSbQL&SXV1dl>imVLaS`<%)d5e+W{?qeHHV*#)4isEJGupgv<&o}vv7>Lz#LJMo9uV(C@X$@*M;$PgWRZtGNYLicf zAhRgUJKm!8nWwu1T@tix=;Ll_muooaQO6v=y=ift-`LASI8^A z$8Rzl)_^KqJ?jR*XN1e3$#-^Sw);@%aHCH;;zs1P3Pb3DeDt@gXJ{>AKFz(t7 zJRmaJZz3}jNlASOsnKDcr1v}f9^9*r?=g|pA|r=6M6nGt;ls6*nk~fB2PM5UK1_tKy~Tt?J`K+C#)@2yM6ge9X|o=#mZ7ZY8(gY3-f?u8Z>PJ@nRapjo5>s!Qt$%0Ik zWb7GE)M+?{ZAY8|$fZ{9(=ZSG;(05%W|XrEHV#0MK1Icl{6D0FxO6NXf<;2ul$FIs zql8vosWpntp~_H$roDyow1}HaWx?j5wPsS)j=fat+fh51(yE4fwX@ak4IfVdMnhvM30GXzL!`j$&&Cd>&X!Q1o9`}HI$>b8hs*vg<;~koEAx@+u{RsPU6OSjT{*YrxUHph{fOVZz5qn# z`OUjM9xj~xUjCDI$WlL0*K`xc2Z<1c&PY|aurABL;s~ukBYQ% zo>+wL+HbyQJJN=ViD8+nB2+iEn&;U7OeEBj^=Cx`*N6}?tQXYye9eQlmij2PYwkIv z7oavCG?`kg0n6!j8f%dltpxM{saApM%H!;mPU9^xjFb}kM3MQ;YJ16X! z<5`7&_{Z#&iyq~*5c8j|(m1fDa@ir2O@(-*jsxhxsWR%plGy`@2M|bD@a|4GD!o?Yft29F)H;ll7Z8|)Vx~))@;x$u_ICZmxX3! zLV46;3oL#%U-5uPH#)y=97gl$2Ae3|Z{%_W?REw?YTbRS3d)>YZnRrIU+cijHiwY~ z9yfQAT;lmqLsDd8CTp}J*Ud2t%TQL!^Vp(Lre!nF=2Yp0YK3`-EMg{s^fPdx;<}oyb={c46>OvkL7nOQseIN{nGVR$AJL=ZM?TH*0 zs0$b%Ei5Tba?p4j{H#N1@`$C?s9FZ-W|0t5OEDvfhPtI+8(rXkJN9-5u@_nMm=ZI& zSbO`}*pn!?z9GYgzjm=tHFFxdij3iJIMWIWC!;FLU$$7d(us`_WIXK2qIe3_lbap4 zSfA`o<+Cz;L*F=1^!1VqvdRcg~w3)beQMWCy|&rIn&P8j$_MhiSWoJ2s7DoFe9)TE1M45wxO~kzA6WBn)iAl zvO-x?KI!mu)iC>_d*obI-W=mko#`jb*E}C7b!pG3z`+6Z4x*LU&il{@>DJz3O^e7r zIN5g*5=lYGctygAu-4=?Cf##<%8wKjj`e=N*T))Q*CW>}$q#`#f_)$+LLbSj5H71` zx+SNqd6+Tqo24OLwfPPr-;EXA`5`vc{QMcZavrF_r}t>yL7oZlxnFP#ilkhsUUW*2 z?p!=|Y{oX3)VZ0e`-p*yDXs`Ak>)e;`K|B30R2T;7!%JL1 zR&=dvOX^7oYpxJhv~e82SRv^1P#+ClOEii8u~U@|DeVR1)a(}1)z|$m{?3#Tu+2SB zyi}e4ue*v!kDzVkJD!IRnkSV7R;qW5_eP5qZ*q(9Q#X^uk`ca zkeR$l_>coSe})_|WCeVuGlZyWPOEkOtpyLx9Y&Sm9-M)=&<$+b5&<(X$MnoM`h)?pTN*MEJRQV&3mPnHjyYhpjTGRari z&Jw&6lk>@7DtF^T*c$ao%p?Jq0dxT+mkFglES7!lIYf0ot%+T*R(}N{D?pw7IXnQg zr3_WscegFnD0)Omp9%o^ya1>-4Dh8t$&QSFN7b;%Bl}AF^P|mkUdx}6l6qF^uZGHv zHq{^yG%+!;weP1YvG$w`GMVWSAWYTEUp{KA_nk(&tbX2KXd9Eh{Dl$FK0hA@(cXb3 zU=l|*cyzd*2|tf|)be0!a|wV@hJcfO=K$ZxrRJO> zVEM!kn66(?WA?Q`R!QuJhIy}^WzC0wK!0vnZf|dwrz0HwERX@KxuARa=S}6LnLDgl z<-?6)X#=*9jl@*Z!o-nk$L%Hmj2&jCu)duvgkxON1S9;mik1mR@Cf&2VSUZAJVt5X z_xbA%O+(vXOYBYR^H6K*H>y9~qQIjhAOfAlyFC?LNRV}5vmpQo*otIt_$h<~{sXBM zlrUHLqMcl|-r4Z>B3Dytt(0En`3OL?awCgC#o?NcpWHX^CZ-Bqewer zXOhmvYGwP>o%6G~fCb! z?ZGQt+!}$42vS=BtEqoChnLkaEg4yb2Z7&pa0%5~U1G#U%}-FK%mcY)*N*UZ?nuhGO6w62iK+m+Kj$Y5!B zmR>fg8bEW)DsI#+czBnGidU4FwyoKt?WYv6bC-{*@ZKi>i>f^r$!snSzr}B^B#%_f z9iD|{A56mmx`9v6G6OmHBDnOD;>+*3m#V}T?qx;@txa+MF}nJ-k^=VswifU5E#Wwk z1BmvDcJ4VS^z>9TLM{bB`DV9!tjBzGY(5buaQQC3#NA#^eZC?O$ubtT?D~-SVZYf{ zqXR}ilkU-X|B(A0njG04g4(6{StUOup2j5bVgt#JF9iJJ2T_ye`}O5Bf)ji1Xbapk zBVq;}r(=6r(jwhn-8>T!o>Kd-MEX z^#Mr6U(vvj)yZ;roNI*DaQ>shH=yy?jFz(e2?yv33?1>gqLluwiBzRGNIH9XBnH+U zW48tCg<1@sPThol{0KUjD#&f!M-`s}os2s`*&nO}B$mFi0})S{t)5H1_nFbw@s-6K z1`~_OKZX5cT3C@MJRDKMA)kIzIv82&cBPu$=|_0D4_A}sFE@Lg@*btjl1vyOeW`(T z1qsDJ{i^@DWg@8If2aCeY6nXv&ot7BpWfe?CIq5aR>_UXj` zS^FKxoNzzTEw&9@mIPt=@4jD%8aLTxyd8KIEAg+MN=O&GGa?Nso3{;z2|Y~{vzi~J zf+pakXB)o}3uM~L&*abyi9@&^4N(j?y=GtJEGWF8{y$-GSWt-}YbTr>+dR z$6h>PdPwnoZzFctgekB90j`}Xom6AU)%@IVm1lhP{3^~_dvvy|bT=BjKh2i9GT5h0 z$N!LwiTOMUNl4ao08TkIDfC-TXaVjljpQ@=^UgjzZGXd-KM(8Yud*&UzP(@c9S5*_ z*mO)3xBxIyy5s5X`pxAJ?y0U{zNsV6t#6;n$Aju6KUJC!Y+7`zRg4*QA_ha_Heev% z(7X>^ar19==qus*Ki7MuabA);b5Qpcuxc(-+OnerxYER!F-d^!{J*%)#t!>U*B#Q> z&&aZSMqKVQJu0)sf&;Ys3Dm*`PURf%I}cpRJ&o(6kK=asVqh3|9sp*fTNBTo6yE=7 z$0XsNw*KAT=C$x$yVl!Ejx728MXqHD_eBGCGp!&oNI?pkQ*WFr0c@z$0h=BBkq5Q2 z%z;ZF;+lRaOHY4%azAk1@}BQ*O-bAt1i0vG(h2V-{m%V4c78?-mloj|v4-qI2IUb2 z2_}{FvLIljg0nMG!`0QWee>WV${AEdah++DwSRQJFaDZp#tVb6feA@L0Jz)T*jf|Vz5mkzoQFf3K?JD?;0C$P2$&YYZUd^{(Cbe^4nAY+o2Nzs@~2r zUHWRzQ!DeZDNK+vNI>a$mb1cn-jjP}a$h`KFg+*Zwk|LZqUT)!4X=R`x&tp~%H0Vz zZ_e4jvs%Ud|I__1=bs)jS=x11I|5xk%ea{>F`nb#$gB4k~fSpa>*2T9$rh)=w zhw_1W%;h>OSKb4TxPHCXe-AvQ3zU1%08BHCE~_$FfMdzZDZpieP#rgboAGX3Se!E% zn73vvQA-Am4ui`t5uifN9FZ*0z%;XKss~Ji2=JH&jbq*0XM;Ac$K*;va}5KNC2;nt zsOizxW1ty`xFj~`qr|)-9d#rK!P=sc{B;ZK8ibWS_R1f6X3o=ZV`i{GSS^t&_&;H-% zt^bR1&1imh#ObzslVwIVi$NX4J_eSMxZ(*dUS|#+U;eNE-^CaEqn5Va|LZ!nG&5?8 zTpAz8feAprIt4ZFgBB|a7lfJ2o++$7Z0>XD`IbO~|Grh?#TSH6W}37zJZrcB4Jd~N zUs+Zeuote_-Xmk3;yFp8E&8}$;Ed*L?ePH&2JDU}9n--92XyTVRz~?}3YjrU5-IY{ zk<+G_m6@r0SJJw5Q|;M;xx0+BVD406sY>Ph)YV{l!n^Bx4lwv~ID_OMo&}B=1B3BO zoD$>>#RA7{bG)?sa&kz8e zvqBgsG55-%^`XErD8zI6F1Q41V}IY@^$&jk>|7bowXeTq!#|dP^1^fHo2F_-2msGL OX7F_Nb6Mw<&;$SwAo(`{ literal 0 HcmV?d00001 diff --git a/envs/monkey_zoo/docs/tunneling_diagram.drawio b/envs/monkey_zoo/docs/tunneling_diagram.drawio new file mode 100644 index 000000000..7b1ae9561 --- /dev/null +++ b/envs/monkey_zoo/docs/tunneling_diagram.drawio @@ -0,0 +1 @@ +7VrbbuM2EP0aP9aQRN38uLHsTYEtGiAo0jwFjMSV2KVEg6J86deXtEhbF1eWW9tSsEHyYM6MJHLOOcPRZQLm6fYrg6vkNxohMrGMaDsBwcSyTNuyJvLfiHalxZvZpSFmOFJBR8Mz/hspo6GsBY5QXgvklBKOV3VjSLMMhbxmg4zRTT3sOyX1q65gjFqG5xCStvUFRzwprb5jHO2PCMeJvrJpKE8KdbAy5AmM6KZiAosJmDNKefkr3c4RkcnTeSmPW/6L9zAxhjLe5wAneCPB79/gSxC+PT4t053Jlr94am58pxeMIrF+NaSMJzSmGSSLo/WB0SKLkDyrIUbHmG+UroTRFMa/EOc7BSYsOBWmhKdEedszV4vJacFC1DFdzQDIYsQ74hTp5FoqF1B5+YpoijjbiQCGCOR4XccaKsrEhzh16BfG4K4SsKI443nlzE/SIAIU+w/QK+6bbgOhy+LFj3IGelRZytG0R/0CBqhFryEpVBp+zQnMohYx6rBvEszR8wru4doI7feEeI0YR9tOUJTXbyRDS25zlOFBbElFgvq4UzBWUnl5pvyPpRWrp1bMUWpFbwB9tdKIv41WZkMwQADPdn+q4/eDVzmYWo4eB9uqN9ip0e2pA0ZBHdCsFF43dc7E34Y6VqvM8kJ0LARnsaSVS0SeH96Z+BXzPXZNiykQF38nYvcec9om570rtuWOrWTrLvKj1GzQU3j2KIRnzxp4n6nZZ+Jv1N9YP0nR7ssdZ5Tc0TXgWlwIXsHzDIePThy8sMfiD7i0uG51hqgGjXTfFmJrEESNBqLGmWrQHX+bagA6tmFZjXruw+b0VPDeZUxPbDr33olte3Q78YDa+y9Ssy+R2jWr6f/Kst1Fb/MSep8I1vRuIzk4vb2h2Q2GbTIqfcVr1Xn1JsPpKYtx3BnarlPjCbhLw2n/JA1nXy64o+CC12w3/O725Ez8bbjjdNVvq3f9FkW6fddz7yLtNB6zHJ4ODFelnY/Vg7g9BTaOOzrPrOMNzjwNOBN/G4G5XQIDlwisvePfW2CePTqBea30trKU/0A8TFSS9pxCbLFGklplZg7vSWVABPPkkNJK+nLO6A80p4QyYcloJqX6HROiTRMLBEt/bov8PRD4jsgTzTHHNBO+EMmLCofMNQ6FlhsB75RzmlYCvhAcSweXmn+AanQ4Dy24YJCYjn4HbZTrWMk1p9tYvhqfrhGCqTUVgcX2ShW2+b5w1iYAsNv4a9v18fc/8b8j/qBZAMyh8Z994j+g/r2B4dfT+YT/LvD7DfiB4Q6Mf/tbkjb+14J3sfSXljkueKdp/rbBWbT/6uwaELuNN6rAakvcPtHhgdnUvxhkMTx+jVY23Mdv+sDiHw== From 756bfe488b383c2067fa759018e556843321d00a Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Fri, 9 Sep 2022 17:08:16 +0000 Subject: [PATCH 151/175] Agent: Provide address as string to connect() --- .../network/relay/relay_connection_handler.py | 8 +++++++- monkey/infection_monkey/network/relay/sockets_pipe.py | 8 ++++---- monkey/infection_monkey/network/relay/tcp_pipe_spawner.py | 6 +++--- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/monkey/infection_monkey/network/relay/relay_connection_handler.py b/monkey/infection_monkey/network/relay/relay_connection_handler.py index 4b4475e52..9d081cf65 100644 --- a/monkey/infection_monkey/network/relay/relay_connection_handler.py +++ b/monkey/infection_monkey/network/relay/relay_connection_handler.py @@ -1,11 +1,14 @@ import socket from ipaddress import IPv4Address +from logging import getLogger from .relay_user_handler import RelayUserHandler from .tcp_pipe_spawner import TCPPipeSpawner RELAY_CONTROL_MESSAGE_REMOVE_FROM_WAITLIST = b"infection-monkey-relay-control-message: -" +logger = getLogger(__name__) + class RelayConnectionHandler: """Handles new relay connections.""" @@ -29,4 +32,7 @@ class RelayConnectionHandler: self._relay_user_handler.disconnect_user(addr) else: self._relay_user_handler.add_relay_user(addr) - self._pipe_spawner.spawn_pipe(sock) + try: + self._pipe_spawner.spawn_pipe(sock) + except OSError as err: + logger.debug(f"Failed to spawn pipe: {err}") diff --git a/monkey/infection_monkey/network/relay/sockets_pipe.py b/monkey/infection_monkey/network/relay/sockets_pipe.py index e31dfaaf4..9ec400d0d 100644 --- a/monkey/infection_monkey/network/relay/sockets_pipe.py +++ b/monkey/infection_monkey/network/relay/sockets_pipe.py @@ -32,7 +32,7 @@ class SocketsPipe(Thread): while True: read_list, _, except_list = select.select(sockets, [], sockets, self.timeout) if except_list: - raise Exception("select() failed on sockets {except_list}") + raise OSError("select() failed on sockets {except_list}") if not read_list: raise TimeoutError("pipe did not receive data for {self.timeout} seconds") @@ -46,17 +46,17 @@ class SocketsPipe(Thread): def run(self): try: self._pipe() - except Exception as err: + except OSError as err: logger.debug(err) try: self.source.close() - except Exception as err: + except OSError as err: logger.debug(f"Error while closing source socket: {err}") try: self.dest.close() - except Exception as err: + except OSError as err: logger.debug(f"Error while closing destination socket: {err}") self._pipe_closed(self) diff --git a/monkey/infection_monkey/network/relay/tcp_pipe_spawner.py b/monkey/infection_monkey/network/relay/tcp_pipe_spawner.py index 5ffebaaea..8e702d374 100644 --- a/monkey/infection_monkey/network/relay/tcp_pipe_spawner.py +++ b/monkey/infection_monkey/network/relay/tcp_pipe_spawner.py @@ -22,12 +22,12 @@ class TCPPipeSpawner: Attempt to create a pipe on between the configured client and the provided socket :param source: A socket to the connecting client. - :raises socket.error: If a socket to the configured client could not be created. + :raises OSError: If a socket to the configured client could not be created. """ dest = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: - dest.connect((self._target_addr, self._target_port)) - except socket.error as err: + dest.connect((str(self._target_addr), self._target_port)) + except OSError as err: source.close() dest.close() raise err From 9ff92d2128a43bb1a5385ef4816320504044eb23 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Fri, 9 Sep 2022 21:03:25 +0000 Subject: [PATCH 152/175] Agent: Fix ident not available in constructor --- monkey/infection_monkey/network/relay/sockets_pipe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/network/relay/sockets_pipe.py b/monkey/infection_monkey/network/relay/sockets_pipe.py index 9ec400d0d..394adef52 100644 --- a/monkey/infection_monkey/network/relay/sockets_pipe.py +++ b/monkey/infection_monkey/network/relay/sockets_pipe.py @@ -24,7 +24,7 @@ class SocketsPipe(Thread): self.source = source self.dest = dest self.timeout = timeout - super().__init__(name=f"SocketsPipeThread-{self.ident}", daemon=True) + super().__init__(name="SocketsPipeThread", daemon=True) self._pipe_closed = pipe_closed def _pipe(self): From 7ff61423dca494b5a8dcfabd4a80076de9206576 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Mon, 12 Sep 2022 12:15:07 +0300 Subject: [PATCH 153/175] Agent: Log uncaught errors in tcp_connection_handler.py thread If there's an uncaught error in the thread, the error won't propagate to the caller and we wouldn't know what went wrong from the logs. This fixes things and all uncaught errors get logged --- .../network/relay/tcp_connection_handler.py | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/monkey/infection_monkey/network/relay/tcp_connection_handler.py b/monkey/infection_monkey/network/relay/tcp_connection_handler.py index b70a133bf..12d029d0f 100644 --- a/monkey/infection_monkey/network/relay/tcp_connection_handler.py +++ b/monkey/infection_monkey/network/relay/tcp_connection_handler.py @@ -1,3 +1,4 @@ +import logging import socket from threading import Thread from typing import Callable, List @@ -6,6 +7,8 @@ from infection_monkey.utils.threading import InterruptableThreadMixin PROXY_TIMEOUT = 2.5 +logger = logging.getLogger(__name__) + class TCPConnectionHandler(Thread, InterruptableThreadMixin): """Accepts connections on a TCP socket.""" @@ -24,18 +27,21 @@ class TCPConnectionHandler(Thread, InterruptableThreadMixin): InterruptableThreadMixin.__init__(self) def run(self): - l_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - l_socket.bind((self.bind_host, self.bind_port)) - l_socket.settimeout(PROXY_TIMEOUT) - l_socket.listen(5) + try: + l_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + l_socket.bind((self.bind_host, self.bind_port)) + l_socket.settimeout(PROXY_TIMEOUT) + l_socket.listen(5) - while not self._interrupted.is_set(): - try: - source, _ = l_socket.accept() - except socket.timeout: - continue + while not self._interrupted.is_set(): + try: + source, _ = l_socket.accept() + except socket.timeout: + continue - for notify_client_connected in self._client_connected: - notify_client_connected(source) - - l_socket.close() + for notify_client_connected in self._client_connected: + notify_client_connected(source) + except: + logging.exception("Uncaught error in TCPConnectionHandler thread") + finally: + l_socket.close() From 191ee1a5f9bce114763d9c42901af843a286a904 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 12 Sep 2022 08:12:17 -0400 Subject: [PATCH 154/175] Agent: Change order of spawn pipe and add relay user If we were unsuccessful in starting the pipe, we don't want to sit around and wait for a relay user to connect. --- .../infection_monkey/network/relay/relay_connection_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/network/relay/relay_connection_handler.py b/monkey/infection_monkey/network/relay/relay_connection_handler.py index 9d081cf65..964a1121a 100644 --- a/monkey/infection_monkey/network/relay/relay_connection_handler.py +++ b/monkey/infection_monkey/network/relay/relay_connection_handler.py @@ -31,8 +31,8 @@ class RelayConnectionHandler: if control_message.startswith(RELAY_CONTROL_MESSAGE_REMOVE_FROM_WAITLIST): self._relay_user_handler.disconnect_user(addr) else: - self._relay_user_handler.add_relay_user(addr) try: self._pipe_spawner.spawn_pipe(sock) + self._relay_user_handler.add_relay_user(addr) except OSError as err: logger.debug(f"Failed to spawn pipe: {err}") From a9edbb2874891cd0d1204ef1da9abce682e7c4b1 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 12 Sep 2022 09:55:04 -0400 Subject: [PATCH 155/175] Agent: Add ThreadSafeIterator --- monkey/infection_monkey/utils/threading.py | 20 +++++++++++++++++-- .../infection_monkey/utils/test_threading.py | 10 ++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/utils/threading.py b/monkey/infection_monkey/utils/threading.py index be28aa0b1..6d8b28253 100644 --- a/monkey/infection_monkey/utils/threading.py +++ b/monkey/infection_monkey/utils/threading.py @@ -1,8 +1,8 @@ import logging from functools import wraps from itertools import count -from threading import Event, Thread -from typing import Any, Callable, Iterable, Optional, Tuple +from threading import Event, Lock, Thread +from typing import Any, Callable, Iterable, Iterator, Optional, Tuple, TypeVar logger = logging.getLogger(__name__) @@ -116,3 +116,19 @@ class InterruptableThreadMixin: def stop(self): """Stop a running thread.""" self._interrupted.set() + + +T = TypeVar("T") + + +class ThreadSafeIterator(Iterator[T]): + """Provides a thread-safe iterator that wraps another iterator""" + + def __init__(self, iterator: Iterator[T]): + self._lock = Lock() + self._iterator = iterator + + def __next__(self) -> T: + while True: + with self._lock: + return next(self._iterator) diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_threading.py b/monkey/tests/unit_tests/infection_monkey/utils/test_threading.py index 96a289096..05b813b66 100644 --- a/monkey/tests/unit_tests/infection_monkey/utils/test_threading.py +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_threading.py @@ -1,8 +1,10 @@ import logging +from itertools import zip_longest from threading import Event, current_thread from typing import Any from infection_monkey.utils.threading import ( + ThreadSafeIterator, create_daemon_thread, interruptible_function, interruptible_iter, @@ -127,3 +129,11 @@ def test_interruptible_decorator_returns_default_value_on_interrupt(): assert return_value == 777 assert fn.call_count == 0 + + +def test_thread_safe_iterator(): + test_list = [1, 2, 3, 4, 5] + tsi = ThreadSafeIterator(test_list.__iter__()) + + for actual, expected in zip_longest(tsi, test_list): + assert actual == expected From 67893b7825409783681e8dd2896bf849635ca537 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 12 Sep 2022 10:01:13 -0400 Subject: [PATCH 156/175] Agent: Find/check island servers concurrently --- .../infection_monkey/network/relay/utils.py | 69 ++++++++++++++----- .../network/relay/test_utils.py | 12 +++- 2 files changed, 62 insertions(+), 19 deletions(-) diff --git a/monkey/infection_monkey/network/relay/utils.py b/monkey/infection_monkey/network/relay/utils.py index 4a5532c30..a19f316db 100644 --- a/monkey/infection_monkey/network/relay/utils.py +++ b/monkey/infection_monkey/network/relay/utils.py @@ -1,42 +1,75 @@ import logging import socket +from contextlib import suppress from ipaddress import IPv4Address -from typing import Iterable, Optional +from typing import Dict, Iterable, Iterator, MutableMapping, Optional import requests from common.common_consts.timeouts import MEDIUM_REQUEST_TIMEOUT from common.network.network_utils import address_to_ip_port from infection_monkey.network.relay import RELAY_CONTROL_MESSAGE_REMOVE_FROM_WAITLIST -from infection_monkey.utils.threading import create_daemon_thread +from infection_monkey.utils.threading import ( + ThreadSafeIterator, + create_daemon_thread, + run_worker_threads, +) logger = logging.getLogger(__name__) +# The number of Island servers to test simultaneously. 32 threads seems large enough for all +# practical purposes. Revisit this if it's not. +NUM_FIND_SERVER_WORKERS = 32 + def find_server(servers: Iterable[str]) -> Optional[str]: - for server in servers: - logger.debug(f"Trying to connect to server: {server}") + server_list = list(servers) + server_iterator = ThreadSafeIterator(server_list.__iter__()) + server_results: Dict[str, bool] = {} - try: - requests.get( # noqa: DUO123 - f"https://{server}/api?action=is-up", - verify=False, - timeout=MEDIUM_REQUEST_TIMEOUT, - ) + run_worker_threads( + _find_island_server, + "FindIslandServer", + args=(server_iterator, server_results), + num_workers=NUM_FIND_SERVER_WORKERS, + ) + for server in server_list: + if server_results[server]: return server - except requests.exceptions.ConnectionError as err: - logger.error(f"Unable to connect to server/relay {server}: {err}") - except TimeoutError as err: - logger.error(f"Timed out while connecting to server/relay {server}: {err}") - except Exception as err: - logger.error( - f"Exception encountered when trying to connect to server/relay {server}: {err}" - ) return None +def _find_island_server(servers: Iterator[str], server_status: MutableMapping[str, bool]): + with suppress(StopIteration): + server = next(servers) + server_status[server] = _check_if_island_server(server) + + +def _check_if_island_server(server: str) -> bool: + logger.debug(f"Trying to connect to server: {server}") + + try: + requests.get( # noqa: DUO123 + f"https://{server}/api?action=is-up", + verify=False, + timeout=MEDIUM_REQUEST_TIMEOUT, + ) + + return True + except requests.exceptions.ConnectionError as err: + logger.error(f"Unable to connect to server/relay {server}: {err}") + except TimeoutError as err: + logger.error(f"Timed out while connecting to server/relay {server}: {err}") + except Exception as err: + logger.error( + f"Exception encountered when trying to connect to server/relay {server}: {err}" + ) + + return False + + def send_remove_from_waitlist_control_message_to_relays(servers: Iterable[str]): for server in servers: t = create_daemon_thread( diff --git a/monkey/tests/unit_tests/infection_monkey/network/relay/test_utils.py b/monkey/tests/unit_tests/infection_monkey/network/relay/test_utils.py index 1acd08012..ac7eb1b16 100644 --- a/monkey/tests/unit_tests/infection_monkey/network/relay/test_utils.py +++ b/monkey/tests/unit_tests/infection_monkey/network/relay/test_utils.py @@ -20,7 +20,7 @@ servers = [SERVER_1, SERVER_2, SERVER_3, SERVER_4] ( SERVER_2, [(SERVER_1, {"exc": requests.exceptions.ConnectionError})] - + [(server, {"text": ""}) for server in servers[1:]], + + [(server, {"text": ""}) for server in servers[1:]], # type: ignore[dict-item] ), ], ) @@ -30,3 +30,13 @@ def test_find_server(expected_server, server_response_pairs): mock.get(f"https://{server}/api?action=is-up", **response) assert find_server(servers) is expected_server + + +def test_find_server__multiple_successes(): + with requests_mock.Mocker() as mock: + mock.get(f"https://{SERVER_1}/api?action=is-up", exc=requests.exceptions.ConnectionError) + mock.get(f"https://{SERVER_2}/api?action=is-up", text="") + mock.get(f"https://{SERVER_3}/api?action=is-up", text="") + mock.get(f"https://{SERVER_4}/api?action=is-up", text="") + + assert find_server(servers) == SERVER_2 From a01785838d5a9ec39a103371b08c45a3b96c8154 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 12 Sep 2022 10:24:26 -0400 Subject: [PATCH 157/175] Agent: Pass keep_tunnel_open timeout as new_client_timeout --- monkey/infection_monkey/network/relay/tcp_relay.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/network/relay/tcp_relay.py b/monkey/infection_monkey/network/relay/tcp_relay.py index 8e62a2169..5fac87ecc 100644 --- a/monkey/infection_monkey/network/relay/tcp_relay.py +++ b/monkey/infection_monkey/network/relay/tcp_relay.py @@ -23,7 +23,10 @@ class TCPRelay(Thread, InterruptableThreadMixin): dest_port: int, client_disconnect_timeout: float, ): - self._user_handler = RelayUserHandler(client_disconnect_timeout=client_disconnect_timeout) + self._user_handler = RelayUserHandler( + new_client_timeout=client_disconnect_timeout, + client_disconnect_timeout=client_disconnect_timeout, + ) self._pipe_spawner = TCPPipeSpawner(dest_addr, dest_port) relay_filter = RelayConnectionHandler(self._pipe_spawner, self._user_handler) self._connection_handler = TCPConnectionHandler( From c2c2993ff79dbd4cc57f7cc067db14153f8179bf Mon Sep 17 00:00:00 2001 From: vakaris_zilius Date: Mon, 12 Sep 2022 15:01:21 +0000 Subject: [PATCH 158/175] Agent: Fix a bug(peeking socket consumes data) sock.recv takes two parameters instead of one --- .../network/relay/relay_connection_handler.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/network/relay/relay_connection_handler.py b/monkey/infection_monkey/network/relay/relay_connection_handler.py index 964a1121a..91f37b520 100644 --- a/monkey/infection_monkey/network/relay/relay_connection_handler.py +++ b/monkey/infection_monkey/network/relay/relay_connection_handler.py @@ -26,7 +26,9 @@ class RelayConnectionHandler: addr, _ = sock.getpeername() addr = IPv4Address(addr) - control_message = sock.recv(socket.MSG_PEEK) + control_message = sock.recv( + len(RELAY_CONTROL_MESSAGE_REMOVE_FROM_WAITLIST), socket.MSG_PEEK + ) if control_message.startswith(RELAY_CONTROL_MESSAGE_REMOVE_FROM_WAITLIST): self._relay_user_handler.disconnect_user(addr) From 27cb2009fce9e0b9214d8e64dadf1d72abfc3b16 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 12 Sep 2022 14:46:42 -0400 Subject: [PATCH 159/175] Agent: Let relay run indefinitely if user hasn't forced a stop --- monkey/infection_monkey/monkey.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 4ddc8655c..b4d9897ce 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -110,6 +110,7 @@ class InfectionMonkey: # TODO Refactor the telemetry messengers to accept control client # and remove control_client_object ControlClient.control_client_object = self._control_client + self._control_channel = None self._telemetry_messenger = LegacyTelemetryMessengerAdapter() self._current_depth = self._opts.depth self._master = None @@ -177,10 +178,10 @@ class InfectionMonkey: if firewall.is_enabled(): firewall.add_firewall_rule() - control_channel = ControlChannel(self._control_client.server_address, GUID) - control_channel.register_agent(self._opts.parent) + self._control_channel = ControlChannel(self._control_client.server_address, GUID) + self._control_channel.register_agent(self._opts.parent) - config = control_channel.get_config() + config = self._control_channel.get_config() relay_port = get_free_tcp_port() self._relay = TCPRelay( @@ -204,9 +205,8 @@ class InfectionMonkey: local_network_interfaces = InfectionMonkey._get_local_network_interfaces() # TODO control_channel and control_client have same responsibilities, merge them - control_channel = ControlChannel(self._control_client.server_address, GUID) propagation_credentials_repository = AggregatingPropagationCredentialsRepository( - control_channel + self._control_channel ) event_queue = PyPubSubAgentEventQueue(Publisher()) @@ -226,7 +226,7 @@ class InfectionMonkey: puppet, telemetry_messenger, victim_host_factory, - control_channel, + self._control_channel, local_network_interfaces, propagation_credentials_repository, ) @@ -396,7 +396,12 @@ class InfectionMonkey: if self._relay and self._relay.is_alive(): self._relay.stop() - self._relay.join(timeout=60) + + while self._relay.is_alive() and not self._control_channel.should_agent_stop(): + self._relay.join(timeout=5) + + if self._control_channel.should_agent_stop(): + self._relay.join(timeout=60) if firewall.is_enabled(): firewall.remove_firewall_rule() From 77c97062eb8e00d0922f33648d29d5cf0cdbb488 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 12 Sep 2022 14:53:19 -0400 Subject: [PATCH 160/175] Agent: Wrap relay stop logic in a method --- monkey/infection_monkey/monkey.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index b4d9897ce..9effa91b0 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -393,15 +393,7 @@ class InfectionMonkey: self._master.cleanup() reset_signal_handlers() - - if self._relay and self._relay.is_alive(): - self._relay.stop() - - while self._relay.is_alive() and not self._control_channel.should_agent_stop(): - self._relay.join(timeout=5) - - if self._control_channel.should_agent_stop(): - self._relay.join(timeout=60) + self._stop_relay() if firewall.is_enabled(): firewall.remove_firewall_rule() @@ -425,6 +417,16 @@ class InfectionMonkey: logger.info("Monkey is shutting down") + def _stop_relay(self): + if self._relay and self._relay.is_alive(): + self._relay.stop() + + while self._relay.is_alive() and not self._control_channel.should_agent_stop(): + self._relay.join(timeout=5) + + if self._control_channel.should_agent_stop(): + self._relay.join(timeout=60) + def _close_tunnel(self): logger.info(f"Quitting tunnel {self._cmd_island_ip}") notify_disconnect(self._cmd_island_ip, self._cmd_island_port) From 9002c47d5601bfb373529878c816ce73c0f679ca Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 12 Sep 2022 16:29:19 -0400 Subject: [PATCH 161/175] Agent: Add a timeout on TCPPipeSpawner dest socket --- monkey/infection_monkey/network/relay/consts.py | 1 + monkey/infection_monkey/network/relay/sockets_pipe.py | 5 +++-- monkey/infection_monkey/network/relay/tcp_pipe_spawner.py | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 monkey/infection_monkey/network/relay/consts.py diff --git a/monkey/infection_monkey/network/relay/consts.py b/monkey/infection_monkey/network/relay/consts.py new file mode 100644 index 000000000..eb5d3f75a --- /dev/null +++ b/monkey/infection_monkey/network/relay/consts.py @@ -0,0 +1 @@ +SOCKET_TIMEOUT = 10 diff --git a/monkey/infection_monkey/network/relay/sockets_pipe.py b/monkey/infection_monkey/network/relay/sockets_pipe.py index 394adef52..ad1307a05 100644 --- a/monkey/infection_monkey/network/relay/sockets_pipe.py +++ b/monkey/infection_monkey/network/relay/sockets_pipe.py @@ -5,8 +5,9 @@ from logging import getLogger from threading import Thread from typing import Callable +from .consts import SOCKET_TIMEOUT + READ_BUFFER_SIZE = 8192 -SOCKET_READ_TIMEOUT = 10 logger = getLogger(__name__) @@ -19,7 +20,7 @@ class SocketsPipe(Thread): source, dest, pipe_closed: Callable[[SocketsPipe], None], - timeout=SOCKET_READ_TIMEOUT, + timeout=SOCKET_TIMEOUT, ): self.source = source self.dest = dest diff --git a/monkey/infection_monkey/network/relay/tcp_pipe_spawner.py b/monkey/infection_monkey/network/relay/tcp_pipe_spawner.py index 8e702d374..e4c48b77f 100644 --- a/monkey/infection_monkey/network/relay/tcp_pipe_spawner.py +++ b/monkey/infection_monkey/network/relay/tcp_pipe_spawner.py @@ -3,6 +3,7 @@ from ipaddress import IPv4Address from threading import Lock from typing import Set +from .consts import SOCKET_TIMEOUT from .sockets_pipe import SocketsPipe @@ -25,6 +26,7 @@ class TCPPipeSpawner: :raises OSError: If a socket to the configured client could not be created. """ dest = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + dest.settimeout(SOCKET_TIMEOUT) try: dest.connect((str(self._target_addr), self._target_port)) except OSError as err: From fe954bb659430cfb1ac549fc89305dcbcbb8863b Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 12 Sep 2022 16:31:03 -0400 Subject: [PATCH 162/175] Agent: Call pipe.start(), not run() run() blocks, start() starts the run method on a different thread. --- monkey/infection_monkey/network/relay/tcp_pipe_spawner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/network/relay/tcp_pipe_spawner.py b/monkey/infection_monkey/network/relay/tcp_pipe_spawner.py index e4c48b77f..dab5721b8 100644 --- a/monkey/infection_monkey/network/relay/tcp_pipe_spawner.py +++ b/monkey/infection_monkey/network/relay/tcp_pipe_spawner.py @@ -37,7 +37,7 @@ class TCPPipeSpawner: pipe = SocketsPipe(source, dest, self._handle_pipe_closed) with self._lock: self._pipes.add(pipe) - pipe.run() + pipe.start() def has_open_pipes(self) -> bool: """Return whether or not the TCPPipeSpawner has any open pipes.""" From c532cdec72649c3e290d6ba3daee4e21227941fc Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 12 Sep 2022 16:35:36 -0400 Subject: [PATCH 163/175] Agent: Detect closed socket in SocketsPipe When a socket is closed, select.select() returns the socket in the read_list. A closed socket can be detected by attempting to read from it. If 0 data is read, then the socket is closed. See below for more details: > If a socket is in the output readable list, you can be as-close-to-certain-as-we-ever-get-in-this-business that a recv on that socket will return something. https://docs.python.org/3/howto/sockets.html#non-blocking-sockets https://stackoverflow.com/questions/17386487/python-detect-when-a-socket-disconnects-for-any-reason https://stackoverflow.com/questions/17705239/is-there-a-way-to-detect-that-tcp-socket-has-been-closed-by-the-remote-peer-wit --- monkey/infection_monkey/network/relay/sockets_pipe.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/network/relay/sockets_pipe.py b/monkey/infection_monkey/network/relay/sockets_pipe.py index ad1307a05..ca60a7c22 100644 --- a/monkey/infection_monkey/network/relay/sockets_pipe.py +++ b/monkey/infection_monkey/network/relay/sockets_pipe.py @@ -30,7 +30,9 @@ class SocketsPipe(Thread): def _pipe(self): sockets = [self.source, self.dest] - while True: + socket_closed = False + + while not socket_closed: read_list, _, except_list = select.select(sockets, [], sockets, self.timeout) if except_list: raise OSError("select() failed on sockets {except_list}") @@ -43,6 +45,9 @@ class SocketsPipe(Thread): data = r.recv(READ_BUFFER_SIZE) if data: other.sendall(data) + else: + socket_closed = True + break def run(self): try: From 3dd2052dc5cb19da36bee1d7bca83f02696cef76 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Mon, 12 Sep 2022 20:45:37 +0000 Subject: [PATCH 164/175] Agent: Add debug logging to TCPRelay --- .../network/relay/relay_user_handler.py | 13 +++++++++++-- .../network/relay/tcp_connection_handler.py | 5 ++++- .../network/relay/tcp_pipe_spawner.py | 5 +++++ monkey/infection_monkey/network/relay/tcp_relay.py | 9 ++++++++- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/monkey/infection_monkey/network/relay/relay_user_handler.py b/monkey/infection_monkey/network/relay/relay_user_handler.py index 63c4000e0..390f1d31f 100644 --- a/monkey/infection_monkey/network/relay/relay_user_handler.py +++ b/monkey/infection_monkey/network/relay/relay_user_handler.py @@ -1,5 +1,6 @@ from dataclasses import dataclass from ipaddress import IPv4Address +from logging import getLogger from threading import Lock from typing import Dict @@ -13,6 +14,9 @@ DEFAULT_NEW_CLIENT_TIMEOUT = 2.5 * MEDIUM_REQUEST_TIMEOUT DEFAULT_DISCONNECT_TIMEOUT = 60 * 2 # Wait up to 2 minutes for clients to disconnect +logger = getLogger(__name__) + + @dataclass class RelayUser: address: IPv4Address @@ -48,7 +52,9 @@ class RelayUserHandler: timer = EggTimer() timer.set(self._client_disconnect_timeout) - self._relay_users[user_address] = RelayUser(user_address, timer) + user = RelayUser(user_address, timer) + self._relay_users[user_address] = user + logger.debug(f"Added relay user {user}") def add_potential_user(self, user_address: IPv4Address): """ @@ -60,7 +66,9 @@ class RelayUserHandler: with self._lock: timer = EggTimer() timer.set(self._new_client_timeout) - self._potential_users[user_address] = RelayUser(user_address, timer) + user = RelayUser(user_address, timer) + self._potential_users[user_address] = user + logger.debug(f"Added potential relay user {user}") def disconnect_user(self, user_address: IPv4Address): """ @@ -70,6 +78,7 @@ class RelayUserHandler: """ with self._lock: if user_address in self._relay_users: + logger.debug(f"Disconnected user {user_address}") del_key(self._relay_users, user_address) def has_potential_users(self) -> bool: diff --git a/monkey/infection_monkey/network/relay/tcp_connection_handler.py b/monkey/infection_monkey/network/relay/tcp_connection_handler.py index 12d029d0f..f61bc2d3a 100644 --- a/monkey/infection_monkey/network/relay/tcp_connection_handler.py +++ b/monkey/infection_monkey/network/relay/tcp_connection_handler.py @@ -39,9 +39,12 @@ class TCPConnectionHandler(Thread, InterruptableThreadMixin): except socket.timeout: continue + logging.debug(f"New connection received from: {source.getpeername()}") for notify_client_connected in self._client_connected: notify_client_connected(source) - except: + except OSError: logging.exception("Uncaught error in TCPConnectionHandler thread") finally: l_socket.close() + + logging.info("Exiting connection handler.") diff --git a/monkey/infection_monkey/network/relay/tcp_pipe_spawner.py b/monkey/infection_monkey/network/relay/tcp_pipe_spawner.py index dab5721b8..0c23f0380 100644 --- a/monkey/infection_monkey/network/relay/tcp_pipe_spawner.py +++ b/monkey/infection_monkey/network/relay/tcp_pipe_spawner.py @@ -1,11 +1,14 @@ import socket from ipaddress import IPv4Address +from logging import getLogger from threading import Lock from typing import Set from .consts import SOCKET_TIMEOUT from .sockets_pipe import SocketsPipe +logger = getLogger(__name__) + class TCPPipeSpawner: """ @@ -37,6 +40,7 @@ class TCPPipeSpawner: pipe = SocketsPipe(source, dest, self._handle_pipe_closed) with self._lock: self._pipes.add(pipe) + pipe.start() def has_open_pipes(self) -> bool: @@ -50,4 +54,5 @@ class TCPPipeSpawner: def _handle_pipe_closed(self, pipe: SocketsPipe): with self._lock: + logger.debug(f"Closing pipe {pipe}") self._pipes.discard(pipe) diff --git a/monkey/infection_monkey/network/relay/tcp_relay.py b/monkey/infection_monkey/network/relay/tcp_relay.py index 8e62a2169..c2663cb1d 100644 --- a/monkey/infection_monkey/network/relay/tcp_relay.py +++ b/monkey/infection_monkey/network/relay/tcp_relay.py @@ -1,4 +1,5 @@ from ipaddress import IPv4Address +from logging import getLogger from threading import Lock, Thread from time import sleep @@ -10,6 +11,8 @@ from infection_monkey.network.relay import ( ) from infection_monkey.utils.threading import InterruptableThreadMixin +logger = getLogger(__name__) + class TCPRelay(Thread, InterruptableThreadMixin): """ @@ -23,7 +26,10 @@ class TCPRelay(Thread, InterruptableThreadMixin): dest_port: int, client_disconnect_timeout: float, ): - self._user_handler = RelayUserHandler(client_disconnect_timeout=client_disconnect_timeout) + self._user_handler = RelayUserHandler( + new_client_timeout=client_disconnect_timeout, + client_disconnect_timeout=client_disconnect_timeout, + ) self._pipe_spawner = TCPPipeSpawner(dest_addr, dest_port) relay_filter = RelayConnectionHandler(self._pipe_spawner, self._user_handler) self._connection_handler = TCPConnectionHandler( @@ -46,6 +52,7 @@ class TCPRelay(Thread, InterruptableThreadMixin): self._connection_handler.stop() self._connection_handler.join() self._wait_for_pipes_to_close() + logger.info("TCP Relay closed.") def add_potential_user(self, user_address: IPv4Address): """ From 4ba4cb583aa46a514155c372713ccca353e05170 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Tue, 13 Sep 2022 15:32:15 +0000 Subject: [PATCH 165/175] Agent: Add thread ID to SocketsPipe thread name --- .../infection_monkey/network/relay/sockets_pipe.py | 9 ++++++++- .../network/relay/test_sockets_pipe.py | 14 ++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 monkey/tests/unit_tests/infection_monkey/network/relay/test_sockets_pipe.py diff --git a/monkey/infection_monkey/network/relay/sockets_pipe.py b/monkey/infection_monkey/network/relay/sockets_pipe.py index ca60a7c22..b4d59416a 100644 --- a/monkey/infection_monkey/network/relay/sockets_pipe.py +++ b/monkey/infection_monkey/network/relay/sockets_pipe.py @@ -15,6 +15,8 @@ logger = getLogger(__name__) class SocketsPipe(Thread): """Manages a pipe between two sockets.""" + _thread_count: int = 0 + def __init__( self, source, @@ -25,9 +27,14 @@ class SocketsPipe(Thread): self.source = source self.dest = dest self.timeout = timeout - super().__init__(name="SocketsPipeThread", daemon=True) + super().__init__(name=f"SocketsPipeThread-{self._next_thread_num()}", daemon=True) self._pipe_closed = pipe_closed + @classmethod + def _next_thread_num(cls): + cls._thread_count += 1 + return cls._thread_count + def _pipe(self): sockets = [self.source, self.dest] socket_closed = False diff --git a/monkey/tests/unit_tests/infection_monkey/network/relay/test_sockets_pipe.py b/monkey/tests/unit_tests/infection_monkey/network/relay/test_sockets_pipe.py new file mode 100644 index 000000000..0a98c0247 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/network/relay/test_sockets_pipe.py @@ -0,0 +1,14 @@ +from unittest.mock import MagicMock + +from monkey.infection_monkey.network.relay import SocketsPipe + + +def test_sockets_pipe__name_increments(): + sock_in = MagicMock() + sock_out = MagicMock() + + pipe1 = SocketsPipe(sock_in, sock_out, None) + assert pipe1.name.endswith("1") + + pipe2 = SocketsPipe(sock_in, sock_out, None) + assert pipe2.name.endswith("2") From 69063de6274cdb1ffafb6f9e0669977c17d6a0a4 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Tue, 13 Sep 2022 15:38:19 +0000 Subject: [PATCH 166/175] Agent: Add note about unintentional disconnect --- monkey/infection_monkey/monkey.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 9effa91b0..440ab7213 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -137,6 +137,10 @@ class InfectionMonkey: raise Exception( f"Failed to connect to the island via any known servers: {self._opts.servers}" ) + + # Note: Since we pass the address for each of our interfaces to the exploited + # machines, is it possible for a machine to unintentionally unregister itself from the + # relay if it is able to connect to the relay over multiple interfaces? send_remove_from_waitlist_control_message_to_relays(servers_iterator) return server From 04d79a0a3594c86c1dbad73e8c4f9904835277bc Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 13 Sep 2022 12:17:45 -0400 Subject: [PATCH 167/175] Agent: Add TCPPortSelector --- monkey/infection_monkey/network/info.py | 103 +++++++++++++++--- .../infection_monkey/network/test_info.py | 34 ++++-- 2 files changed, 116 insertions(+), 21 deletions(-) diff --git a/monkey/infection_monkey/network/info.py b/monkey/infection_monkey/network/info.py index 6fdef3597..fdcc45234 100644 --- a/monkey/infection_monkey/network/info.py +++ b/monkey/infection_monkey/network/info.py @@ -4,10 +4,12 @@ import struct from dataclasses import dataclass from ipaddress import IPv4Interface from random import shuffle # noqa: DUO102 -from typing import List +from threading import Lock +from typing import Dict, List, Set import netifaces import psutil +from egg_timer import EggTimer from infection_monkey.utils.environment import is_windows_os @@ -120,20 +122,93 @@ else: return routes -def get_free_tcp_port(min_range=1024, max_range=65535): +class TCPPortSelector: + """ + Select an available TCP port that a new server can listen on - in_use = {conn.laddr[1] for conn in psutil.net_connections()} + Examines the system to find which ports are not in use and makes an intelligent decision + regarding what port can be used to host a server. In multithreaded applications, a race occurs + between the time when the OS reports that a port is free and when the port is actually used. In + other words, two threads which request a free port simultaneously may be handed the same port, + as the OS will report that the port is not in use. To combat this, the TCPPortSelector will + reserve a port for a period of time to give the requester ample time to start their server. Once + the requester's server is listening on the port, the OS will report the port as "LISTEN". + """ - for port in COMMON_PORTS: - if port not in in_use: - return port + def __init__(self): + self._leases: Dict[int, EggTimer] = {} + self._lock = Lock() - min_range = max(1, min_range) - max_range = min(65535, max_range) - ports = list(range(min_range, max_range)) - shuffle(ports) - for port in ports: - if port not in in_use: - return port + def get_free_tcp_port( + self, min_range: int = 1024, max_range: int = 65535, lease_time_sec: float = 30 + ): + """ + Get a free TCP port that a new server can listen on - return None + This function will attempt to provide a well-known port that the caller can listen on. If no + well-known ports are available, a random port will be selected. + + :param min_range: The smallest port number a random port can be chosen from, defaults to + 1024 + :param max_range: The largest port number a random port can be chosen from, defaults to + 65535 + :lease_time_sec: The amount of time a port should be reserved for if the OS does not report + it as in use, defaults to 30 seconds + :return: A TCP port number + """ + with self._lock: + ports_in_use = {conn.laddr[1] for conn in psutil.net_connections()} + + common_port = self._get_free_common_port(ports_in_use, lease_time_sec) + if common_port is not None: + return common_port + + return self._get_free_random_port(ports_in_use, min_range, max_range, lease_time_sec) + + def _get_free_common_port(self, ports_in_use: Set[int], lease_time_sec): + for port in COMMON_PORTS: + if self._port_is_available(port, ports_in_use): + self._reserve_port(port, lease_time_sec) + return port + + return None + + def _get_free_random_port( + self, ports_in_use: Set[int], min_range: int, max_range: int, lease_time_sec: float + ): + min_range = max(1, min_range) + max_range = min(65535, max_range) + ports = list(range(min_range, max_range)) + shuffle(ports) + for port in ports: + if self._port_is_available(port, ports_in_use): + self._reserve_port(port, lease_time_sec) + return port + + return None + + def _port_is_available(self, port: int, ports_in_use: Set[int]): + if port in ports_in_use: + return False + + if port not in self._leases: + return True + + if self._leases[port].is_expired(): + return True + + return False + + def _reserve_port(self, port: int, lease_time_sec: float): + timer = EggTimer() + timer.set(lease_time_sec) + self._leases[port] = timer + + +# TODO: This function is here because existing components rely on it. Refactor these components to +# accept a TCPPortSelector instance and use that instead. +def get_free_tcp_port(min_range=1024, max_range=65535, lease_time_sec=30): + return get_free_tcp_port.port_selector.get_free_tcp_port(min_range, max_range, lease_time_sec) + + +get_free_tcp_port.port_selector = TCPPortSelector() # type: ignore[attr-defined] diff --git a/monkey/tests/unit_tests/infection_monkey/network/test_info.py b/monkey/tests/unit_tests/infection_monkey/network/test_info.py index 8dab89e4b..f0508ce98 100644 --- a/monkey/tests/unit_tests/infection_monkey/network/test_info.py +++ b/monkey/tests/unit_tests/infection_monkey/network/test_info.py @@ -3,7 +3,7 @@ from typing import Tuple import pytest -from infection_monkey.network.info import get_free_tcp_port +from infection_monkey.network.info import TCPPortSelector from infection_monkey.network.ports import COMMON_PORTS @@ -13,28 +13,48 @@ class Connection: @pytest.mark.parametrize("port", COMMON_PORTS) -def test_get_free_tcp_port__checks_common_ports(port: int, monkeypatch): +def test_tcp_port_selector__checks_common_ports(port: int, monkeypatch): + tcp_port_selector = TCPPortSelector() unavailable_ports = [Connection(("", p)) for p in COMMON_PORTS if p is not port] monkeypatch.setattr( "infection_monkey.network.info.psutil.net_connections", lambda: unavailable_ports ) - assert get_free_tcp_port() is port + assert tcp_port_selector.get_free_tcp_port() is port -def test_get_free_tcp_port__checks_other_ports_if_common_ports_unavailable(monkeypatch): +def test_tcp_port_selector__checks_other_ports_if_common_ports_unavailable(monkeypatch): + tcp_port_selector = TCPPortSelector() unavailable_ports = [Connection(("", p)) for p in COMMON_PORTS] monkeypatch.setattr( "infection_monkey.network.info.psutil.net_connections", lambda: unavailable_ports ) - assert get_free_tcp_port() is not None + assert tcp_port_selector.get_free_tcp_port() is not None -def test_get_free_tcp_port__none_if_no_available_ports(monkeypatch): +def test_tcp_port_selector__none_if_no_available_ports(monkeypatch): + tcp_port_selector = TCPPortSelector() unavailable_ports = [Connection(("", p)) for p in range(65535)] monkeypatch.setattr( "infection_monkey.network.info.psutil.net_connections", lambda: unavailable_ports ) - assert get_free_tcp_port() is None + assert tcp_port_selector.get_free_tcp_port() is None + + +@pytest.mark.parametrize("common_port", COMMON_PORTS) +def test_tcp_port_selector__checks_common_ports_leases(common_port: int, monkeypatch): + tcp_port_selector = TCPPortSelector() + unavailable_ports = [Connection(("", p)) for p in COMMON_PORTS if p is not common_port] + monkeypatch.setattr( + "infection_monkey.network.info.psutil.net_connections", lambda: unavailable_ports + ) + + free_port_1 = tcp_port_selector.get_free_tcp_port() + free_port_2 = tcp_port_selector.get_free_tcp_port() + + assert free_port_1 == common_port + assert free_port_2 != common_port + assert free_port_2 is not None + assert free_port_2 not in COMMON_PORTS From 0f0f50208da99386514d20fe0e8f48f3c68a8a0f Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 13 Sep 2022 15:07:25 -0400 Subject: [PATCH 168/175] Agent: Add missing "param" in docstring Co-authored-by: Kekoa Kaaikala --- monkey/infection_monkey/network/info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/network/info.py b/monkey/infection_monkey/network/info.py index fdcc45234..6efcf3cd2 100644 --- a/monkey/infection_monkey/network/info.py +++ b/monkey/infection_monkey/network/info.py @@ -152,7 +152,7 @@ class TCPPortSelector: 1024 :param max_range: The largest port number a random port can be chosen from, defaults to 65535 - :lease_time_sec: The amount of time a port should be reserved for if the OS does not report + :param lease_time_sec: The amount of time a port should be reserved for if the OS does not report it as in use, defaults to 30 seconds :return: A TCP port number """ From b865c13a3b1cc192a0a64cd37ff94634ca64be2b Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Tue, 13 Sep 2022 19:45:57 +0000 Subject: [PATCH 169/175] BB: Revert depth_3_a test --- .../blackbox/test_configurations/depth_3_a.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/envs/monkey_zoo/blackbox/test_configurations/depth_3_a.py b/envs/monkey_zoo/blackbox/test_configurations/depth_3_a.py index 0b971d2d5..0a39a5e59 100644 --- a/envs/monkey_zoo/blackbox/test_configurations/depth_3_a.py +++ b/envs/monkey_zoo/blackbox/test_configurations/depth_3_a.py @@ -16,12 +16,14 @@ from .utils import ( # Tests: # Powershell (10.2.3.45, 10.2.3.46, 10.2.3.47, 10.2.3.48) +# Tunneling (SSH brute force) (10.2.2.9, 10.2.1.10, 10.2.0.12, 10.2.0.11) # WMI pass the hash (10.2.2.15) def _add_exploiters(agent_configuration: AgentConfiguration) -> AgentConfiguration: brute_force = [ PluginConfiguration(name="PowerShellExploiter", options={}), + PluginConfiguration(name="SSHExploiter", options={}), PluginConfiguration(name="WmiExploiter", options={"smb_download_timeout": 30}), ] @@ -30,17 +32,21 @@ def _add_exploiters(agent_configuration: AgentConfiguration) -> AgentConfigurati def _add_subnets(agent_configuration: AgentConfiguration) -> AgentConfiguration: subnets = [ + "10.2.2.9", "10.2.3.45", "10.2.3.46", "10.2.3.47", "10.2.3.48", + "10.2.1.10", + "10.2.0.12", + "10.2.0.11", "10.2.2.15", ] return add_subnets(agent_configuration, subnets) def _add_tcp_ports(agent_configuration: AgentConfiguration) -> AgentConfiguration: - ports = [135, 5985, 5986] + ports = [22, 135, 5985, 5986] return add_tcp_ports(agent_configuration, ports) @@ -54,6 +60,9 @@ CREDENTIALS = ( Credentials(Username("m0nk3y"), None), Credentials(Username("m0nk3y-user"), None), Credentials(None, Password("Passw0rd!")), + Credentials(None, Password("3Q=(Ge(+&w]*")), + Credentials(None, Password("`))jU7L(w}")), + Credentials(None, Password("t67TC5ZDmz")), Credentials(None, NTHash("d0f0132b308a0c4e5d1029cc06f48692")), Credentials(None, NTHash("5da0889ea2081aa79f6852294cba4a5e")), Credentials(None, NTHash("50c9987a6bf1ac59398df9f911122c9b")), From e7bb5ce535cf0b79c3e0eefe7e2c1068d8054a75 Mon Sep 17 00:00:00 2001 From: Kekoa Kaaikala Date: Tue, 13 Sep 2022 19:52:04 +0000 Subject: [PATCH 170/175] BB: Add interface to tunneling-11 --- envs/monkey_zoo/terraform/monkey_zoo.tf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/envs/monkey_zoo/terraform/monkey_zoo.tf b/envs/monkey_zoo/terraform/monkey_zoo.tf index 2d98e4eaa..a15e6b9f4 100644 --- a/envs/monkey_zoo/terraform/monkey_zoo.tf +++ b/envs/monkey_zoo/terraform/monkey_zoo.tf @@ -127,6 +127,10 @@ resource "google_compute_instance_from_template" "tunneling-11" { subnetwork="${local.resource_prefix}tunneling2-main" network_ip="10.2.0.11" } + network_interface{ + subnetwork="${local.resource_prefix}tunneling-main" + network_ip="10.2.1.11" + } } resource "google_compute_instance_from_template" "tunneling-12" { From 7a33802b7cb969d8dc97aa21c4ef7b6ac5f849e6 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Wed, 14 Sep 2022 12:06:46 +0300 Subject: [PATCH 171/175] BB: Add tunneling machines to the list of depth 3 test --- envs/monkey_zoo/blackbox/gcp_test_machine_list.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/envs/monkey_zoo/blackbox/gcp_test_machine_list.py b/envs/monkey_zoo/blackbox/gcp_test_machine_list.py index f00af692a..ffbd21c90 100644 --- a/envs/monkey_zoo/blackbox/gcp_test_machine_list.py +++ b/envs/monkey_zoo/blackbox/gcp_test_machine_list.py @@ -50,6 +50,9 @@ DEPTH_1_A = { DEPTH_3_A = { "europe-west3-a": [ + "tunneling-9", + "tunneling-10", + "tunneling-11", "mimikatz-15", ], "europe-west1-b": [ From c2816665047dc5134e556e06d2d92902d6add403 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Wed, 14 Sep 2022 12:10:06 +0300 Subject: [PATCH 172/175] BB: Remove tunneling-12 from depth-3 --- envs/monkey_zoo/blackbox/test_configurations/depth_3_a.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/envs/monkey_zoo/blackbox/test_configurations/depth_3_a.py b/envs/monkey_zoo/blackbox/test_configurations/depth_3_a.py index 0a39a5e59..049521858 100644 --- a/envs/monkey_zoo/blackbox/test_configurations/depth_3_a.py +++ b/envs/monkey_zoo/blackbox/test_configurations/depth_3_a.py @@ -16,7 +16,7 @@ from .utils import ( # Tests: # Powershell (10.2.3.45, 10.2.3.46, 10.2.3.47, 10.2.3.48) -# Tunneling (SSH brute force) (10.2.2.9, 10.2.1.10, 10.2.0.12, 10.2.0.11) +# Tunneling through grandparent agent (SSH brute force) (10.2.2.9, 10.2.1.10, 10.2.0.11) # WMI pass the hash (10.2.2.15) @@ -38,7 +38,6 @@ def _add_subnets(agent_configuration: AgentConfiguration) -> AgentConfiguration: "10.2.3.47", "10.2.3.48", "10.2.1.10", - "10.2.0.12", "10.2.0.11", "10.2.2.15", ] @@ -62,7 +61,6 @@ CREDENTIALS = ( Credentials(None, Password("Passw0rd!")), Credentials(None, Password("3Q=(Ge(+&w]*")), Credentials(None, Password("`))jU7L(w}")), - Credentials(None, Password("t67TC5ZDmz")), Credentials(None, NTHash("d0f0132b308a0c4e5d1029cc06f48692")), Credentials(None, NTHash("5da0889ea2081aa79f6852294cba4a5e")), Credentials(None, NTHash("50c9987a6bf1ac59398df9f911122c9b")), From bc6da3d4023fa5035f9a76a322149b5e6b2ef5fb Mon Sep 17 00:00:00 2001 From: vakarisz Date: Wed, 14 Sep 2022 12:13:36 +0300 Subject: [PATCH 173/175] BB: Add documentation about changes to tunneling-11 --- envs/monkey_zoo/docs/fullDocs.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/envs/monkey_zoo/docs/fullDocs.md b/envs/monkey_zoo/docs/fullDocs.md index 9a52d5687..b08a89bfc 100644 --- a/envs/monkey_zoo/docs/fullDocs.md +++ b/envs/monkey_zoo/docs/fullDocs.md @@ -303,7 +303,7 @@ Update all requirements using deployment script:
- + @@ -343,7 +343,10 @@ Update all requirements using deployment script:
- + From d509ee7216debba94a2825d45b5dac703ff1b462 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 14 Sep 2022 07:22:44 -0400 Subject: [PATCH 174/175] Build: Pull Python AppImage v3.7.14 --- build_scripts/appimage/appimage.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build_scripts/appimage/appimage.sh b/build_scripts/appimage/appimage.sh index 9666374d5..f4269ea2c 100755 --- a/build_scripts/appimage/appimage.sh +++ b/build_scripts/appimage/appimage.sh @@ -1,7 +1,7 @@ #!/bin/bash LINUXDEPLOY_URL="https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage" -PYTHON_VERSION="3.7.13" +PYTHON_VERSION="3.7.14" PYTHON_APPIMAGE_URL="https://github.com/niess/python-appimage/releases/download/python3.7/python${PYTHON_VERSION}-cp37-cp37m-manylinux1_x86_64.AppImage" APPIMAGE_DIR=$(realpath "$(dirname "${BASH_SOURCE[0]}")") APPDIR="$APPIMAGE_DIR/squashfs-root" From f3fd2fca8be5399f2f0a98fdc5be9fb68d06ecdf Mon Sep 17 00:00:00 2001 From: vakarisz Date: Wed, 14 Sep 2022 17:08:52 +0300 Subject: [PATCH 175/175] Agent: Fix SSH exploiter to be able to exploit unknown OS Previously if the agent couldn't detect the OS, agent failed with errors --- monkey/infection_monkey/exploit/sshexec.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index 7de9b26ce..1b317b2ac 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -4,6 +4,7 @@ from pathlib import PurePath import paramiko +from common import OperatingSystem from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT from common.credentials.credentials import get_plaintext from common.utils import Timer @@ -181,7 +182,8 @@ class SSHExploiter(HostExploiter): _, stdout, _ = ssh.exec_command("uname -o", timeout=SSH_EXEC_TIMEOUT) uname_os = stdout.read().lower().strip().decode() if "linux" in uname_os: - self.exploit_result.os = "linux" + self.exploit_result.os = OperatingSystem.LINUX + self.host.os["type"] = OperatingSystem.LINUX else: self.exploit_result.error_message = f"SSH Skipping unknown os: {uname_os}"
Root password:3Q=(Ge(+&w]*3Q=(Ge(+&w]*
Server’s config:
Server’s config:DefaultContains firewall rules to block everything from 10.2.1.10 except ssh. +This prevents tunneling communication, but allows ssh exploitation. +Contains firewall rules to allow everything from 10.2.1.9 except ssh. +This prevents ssh exploitation, but allows tunneling.
Notes: