Skip to content

Commit 42d48eb

Browse files
committed
Merge branch '3234-repeat-create-on-error' into develop
Issue #3234
2 parents 0076990 + 31fa4cc commit 42d48eb

File tree

3 files changed

+60
-16
lines changed

3 files changed

+60
-16
lines changed

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

+10-10
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ def _create_command(self, target_ip: IPv4Address, command_name: str, community_s
107107
),
108108
ContextData(),
109109
ObjectType(
110-
ObjectIdentity(SNMP_EXTEND_MIB, "nsExtendStatus", f'"{command_name}"'),
110+
ObjectIdentity(SNMP_EXTEND_MIB, "nsExtendStatus", command_name),
111111
"createAndWait",
112112
),
113113
lookupNames=True,
@@ -143,11 +143,11 @@ def _set_command(
143143
),
144144
ContextData(),
145145
ObjectType(
146-
ObjectIdentity(SNMP_EXTEND_MIB, "nsExtendCommand", f'"{command_name}"'),
146+
ObjectIdentity(SNMP_EXTEND_MIB, "nsExtendCommand", command_name),
147147
"/bin/sh",
148148
),
149149
ObjectType(
150-
ObjectIdentity(SNMP_EXTEND_MIB, "nsExtendArgs", f'"{command_name}"'),
150+
ObjectIdentity(SNMP_EXTEND_MIB, "nsExtendArgs", command_name),
151151
command,
152152
),
153153
lookupNames=True,
@@ -166,7 +166,7 @@ def _activate_command(self, target_ip: IPv4Address, command_name: str, community
166166
),
167167
ContextData(),
168168
ObjectType(
169-
ObjectIdentity(SNMP_EXTEND_MIB, "nsExtendStatus", f'"{command_name}"'),
169+
ObjectIdentity(SNMP_EXTEND_MIB, "nsExtendStatus", command_name),
170170
"active",
171171
),
172172
)
@@ -198,10 +198,10 @@ def execute_command(self, target_ip: IPv4Address, command_name: str, community_s
198198
),
199199
ContextData(),
200200
ObjectType(ObjectIdentity(SNMP_EXTEND_MIB, "nsExtendNumEntries", 0)),
201-
ObjectType(ObjectIdentity(SNMP_EXTEND_MIB, "nsExtendCommand", f'"{command_name}"')),
202-
ObjectType(ObjectIdentity(SNMP_EXTEND_MIB, "nsExtendArgs", f'"{command_name}"')),
203-
ObjectType(ObjectIdentity(SNMP_EXTEND_MIB, "nsExtendInput", f'"{command_name}"')),
204-
ObjectType(ObjectIdentity(SNMP_EXTEND_MIB, "nsExtendResult", f'"{command_name}"')),
201+
ObjectType(ObjectIdentity(SNMP_EXTEND_MIB, "nsExtendCommand", command_name)),
202+
ObjectType(ObjectIdentity(SNMP_EXTEND_MIB, "nsExtendArgs", command_name)),
203+
ObjectType(ObjectIdentity(SNMP_EXTEND_MIB, "nsExtendInput", command_name)),
204+
ObjectType(ObjectIdentity(SNMP_EXTEND_MIB, "nsExtendResult", command_name)),
205205
)
206206
self._dispatch(cmd)
207207

@@ -226,7 +226,7 @@ def clear_command(self, target_ip: IPv4Address, command_name: str, community_str
226226
),
227227
ContextData(),
228228
ObjectType(
229-
ObjectIdentity(SNMP_EXTEND_MIB, "nsExtendStatus", f'"{command_name}"'),
229+
ObjectIdentity(SNMP_EXTEND_MIB, "nsExtendStatus", command_name),
230230
"destroy",
231231
),
232232
)
@@ -244,7 +244,7 @@ def _get_rowstatus(
244244
retries=self._snmp_retries,
245245
),
246246
ContextData(),
247-
ObjectType(ObjectIdentity(SNMP_EXTEND_MIB, "nsExtendStatus", f'"{command_name}"')),
247+
ObjectType(ObjectIdentity(SNMP_EXTEND_MIB, "nsExtendStatus", command_name)),
248248
)
249249
result = self._dispatch(cmd)
250250
vars = [

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

+37-6
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
from functools import partial
1+
from functools import partial, wraps
22
from ipaddress import IPv4Address
33
from logging import getLogger
44
from time import time
5-
from typing import Callable, Tuple
5+
from typing import Callable, Tuple, Type
66

77
from common.agent_events import ExploitationEvent, PropagationEvent
88
from common.event_queue import IAgentEventPublisher
@@ -32,6 +32,31 @@
3232
PROPAGATION_TAGS = (SNMP_EXPLOITER_TAG, INGRESS_TOOL_TRANSFER_T1105_TAG)
3333

3434

35+
def repeat_on_error(max_times: int = 3, error_types: Tuple[Type] = (Exception,)):
36+
"""
37+
Decorator to repeat a command if it fails with an error
38+
39+
:param times: The maximum number of times to repeat the command
40+
:param error_types: The types of errors to catch
41+
"""
42+
43+
def decorator(func):
44+
@wraps(func)
45+
def inner(*args, **kwargs):
46+
for _ in range(max_times - 1):
47+
try:
48+
return func(*args, **kwargs)
49+
except error_types as err:
50+
logger.debug(f"Retrying due to error: {err}")
51+
52+
# Allow the exception on the last try to bubble up
53+
return func(*args, **kwargs)
54+
55+
return inner
56+
57+
return decorator
58+
59+
3560
class SNMPExploitClient:
3661
def __init__(
3762
self,
@@ -92,15 +117,21 @@ def exploit_host(
92117
return exploitation_success, propagation_success
93118

94119
def _exploit(self, target_ip: IPv4Address, community_string: str, command: str):
95-
command_name = self._generate_command_name()
96-
logger.debug(f"Executing SNMP command {command_name} on {target_ip}")
97-
98-
self._snmp_client.create_command(target_ip, command_name, community_string, command)
120+
command_name = self._create_command(target_ip, community_string, command)
99121
try:
100122
self._snmp_client.execute_command(target_ip, command_name, community_string)
101123
finally:
102124
self._snmp_client.clear_command(target_ip, command_name, community_string)
103125

126+
@repeat_on_error(max_times=3)
127+
def _create_command(self, target_ip: IPv4Address, community_string: str, command: str) -> str:
128+
command_name = self._generate_command_name()
129+
logger.debug(f"Creating SNMP command {command_name} on {target_ip}")
130+
131+
self._snmp_client.create_command(target_ip, command_name, community_string, command)
132+
133+
return command_name
134+
104135
def _publish_exploitation_event(
105136
self, host: TargetHost, timestamp: float, success: bool, message: str
106137
):

monkey/tests/unit_tests/agent_plugins/exploiters/snmp/test_snmp_exploit_client.py

+13
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,19 @@ def test_exploit__failure_on_create_exception(
204204
assert not propagation_success
205205

206206

207+
def test_exploit__retries_create_on_error(
208+
mock_snmp_client,
209+
exploit_host: Callable[[], Tuple[bool, bool]],
210+
mock_command_name_generator,
211+
):
212+
mock_snmp_client.create_command.side_effect = Exception("test")
213+
214+
exploit_host()
215+
216+
assert mock_snmp_client.create_command.call_count > 1
217+
assert mock_command_name_generator.call_count == mock_snmp_client.create_command.call_count
218+
219+
207220
@pytest.mark.parametrize("command_name", ["command0", "command1", "command2"])
208221
def test_exploit__uses_command_name_generator(
209222
command_name,

0 commit comments

Comments
 (0)