Merge main into argcomplete
commit
a70f3e6ae3
|
@ -3,6 +3,7 @@ hash_spider_default.sqlite3
|
||||||
*.bak
|
*.bak
|
||||||
*.log
|
*.log
|
||||||
.venv
|
.venv
|
||||||
|
pyvenv.cfg
|
||||||
.vscode
|
.vscode
|
||||||
.idea
|
.idea
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
|
|
|
@ -13,6 +13,12 @@ a = Analysis(
|
||||||
('./nxc/modules', 'nxc/modules')
|
('./nxc/modules', 'nxc/modules')
|
||||||
],
|
],
|
||||||
hiddenimports=[
|
hiddenimports=[
|
||||||
|
'aardwolf',
|
||||||
|
'aardwolf.connection',
|
||||||
|
'aardwolf.commons.queuedata.constants',
|
||||||
|
'aardwolf.commons.iosettings',
|
||||||
|
'aardwolf.commons.target',
|
||||||
|
'aardwolf.protocol.x224.constants',
|
||||||
'impacket.examples.secretsdump',
|
'impacket.examples.secretsdump',
|
||||||
'impacket.dcerpc.v5.lsat',
|
'impacket.dcerpc.v5.lsat',
|
||||||
'impacket.dcerpc.v5.transport',
|
'impacket.dcerpc.v5.transport',
|
||||||
|
@ -65,6 +71,7 @@ a = Analysis(
|
||||||
'dploot.lib.smb',
|
'dploot.lib.smb',
|
||||||
'pyasn1_modules.rfc5652',
|
'pyasn1_modules.rfc5652',
|
||||||
'unicrypto.backends.pycryptodomex',
|
'unicrypto.backends.pycryptodomex',
|
||||||
|
'sspilib.raw._text',
|
||||||
],
|
],
|
||||||
hookspath=['./nxc/.hooks'],
|
hookspath=['./nxc/.hooks'],
|
||||||
runtime_hooks=[],
|
runtime_hooks=[],
|
||||||
|
|
|
@ -10,6 +10,7 @@ from nxc.config import pwned_label
|
||||||
from nxc.helpers.logger import highlight
|
from nxc.helpers.logger import highlight
|
||||||
from nxc.logger import nxc_logger, NXCAdapter
|
from nxc.logger import nxc_logger, NXCAdapter
|
||||||
from nxc.context import Context
|
from nxc.context import Context
|
||||||
|
from nxc.protocols.ldap.laps import laps_search
|
||||||
|
|
||||||
from impacket.dcerpc.v5 import transport
|
from impacket.dcerpc.v5 import transport
|
||||||
import sys
|
import sys
|
||||||
|
@ -459,6 +460,13 @@ class connection:
|
||||||
self.logger.info("Successfully authenticated using Kerberos cache")
|
self.logger.info("Successfully authenticated using Kerberos cache")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
if hasattr(self.args, "laps") and self.args.laps:
|
||||||
|
self.logger.debug("Trying to authenticate using LAPS")
|
||||||
|
username[0], secret[0], domain[0], ntlm_hash = laps_search(self, username, secret, cred_type, domain)
|
||||||
|
cred_type = ["plaintext"]
|
||||||
|
if not (username[0] or secret[0] or domain[0]):
|
||||||
|
return False
|
||||||
|
|
||||||
if not self.args.no_bruteforce:
|
if not self.args.no_bruteforce:
|
||||||
for secr_index, secr in enumerate(secret):
|
for secr_index, secr in enumerate(secret):
|
||||||
for user_index, user in enumerate(username):
|
for user_index, user in enumerate(username):
|
||||||
|
|
|
@ -4,7 +4,6 @@ from logging.handlers import RotatingFileHandler
|
||||||
import os.path
|
import os.path
|
||||||
import sys
|
import sys
|
||||||
import re
|
import re
|
||||||
from nxc.helpers.misc import called_from_cmd_args
|
|
||||||
from nxc.console import nxc_console
|
from nxc.console import nxc_console
|
||||||
from termcolor import colored
|
from termcolor import colored
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
@ -68,12 +67,6 @@ class NXCAdapter(logging.LoggerAdapter):
|
||||||
|
|
||||||
def display(self, msg, *args, **kwargs):
|
def display(self, msg, *args, **kwargs):
|
||||||
"""Display text to console, formatted for nxc"""
|
"""Display text to console, formatted for nxc"""
|
||||||
try:
|
|
||||||
if self.extra and "protocol" in self.extra and not called_from_cmd_args():
|
|
||||||
return
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
msg, kwargs = self.format(f"{colored('[*]', 'blue', attrs=['bold'])} {msg}", kwargs)
|
msg, kwargs = self.format(f"{colored('[*]', 'blue', attrs=['bold'])} {msg}", kwargs)
|
||||||
text = Text.from_ansi(msg)
|
text = Text.from_ansi(msg)
|
||||||
nxc_console.print(text, *args, **kwargs)
|
nxc_console.print(text, *args, **kwargs)
|
||||||
|
@ -81,12 +74,6 @@ class NXCAdapter(logging.LoggerAdapter):
|
||||||
|
|
||||||
def success(self, msg, color="green", *args, **kwargs):
|
def success(self, msg, color="green", *args, **kwargs):
|
||||||
"""Print some sort of success to the user"""
|
"""Print some sort of success to the user"""
|
||||||
try:
|
|
||||||
if self.extra and "protocol" in self.extra and not called_from_cmd_args():
|
|
||||||
return
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
msg, kwargs = self.format(f"{colored('[+]', color, attrs=['bold'])} {msg}", kwargs)
|
msg, kwargs = self.format(f"{colored('[+]', color, attrs=['bold'])} {msg}", kwargs)
|
||||||
text = Text.from_ansi(msg)
|
text = Text.from_ansi(msg)
|
||||||
nxc_console.print(text, *args, **kwargs)
|
nxc_console.print(text, *args, **kwargs)
|
||||||
|
@ -94,12 +81,6 @@ class NXCAdapter(logging.LoggerAdapter):
|
||||||
|
|
||||||
def highlight(self, msg, *args, **kwargs):
|
def highlight(self, msg, *args, **kwargs):
|
||||||
"""Prints a completely yellow highlighted message to the user"""
|
"""Prints a completely yellow highlighted message to the user"""
|
||||||
try:
|
|
||||||
if self.extra and "protocol" in self.extra and not called_from_cmd_args():
|
|
||||||
return
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
msg, kwargs = self.format(f"{colored(msg, 'yellow', attrs=['bold'])}", kwargs)
|
msg, kwargs = self.format(f"{colored(msg, 'yellow', attrs=['bold'])}", kwargs)
|
||||||
text = Text.from_ansi(msg)
|
text = Text.from_ansi(msg)
|
||||||
nxc_console.print(text, *args, **kwargs)
|
nxc_console.print(text, *args, **kwargs)
|
||||||
|
@ -107,11 +88,6 @@ class NXCAdapter(logging.LoggerAdapter):
|
||||||
|
|
||||||
def fail(self, msg, color="red", *args, **kwargs):
|
def fail(self, msg, color="red", *args, **kwargs):
|
||||||
"""Prints a failure (may or may not be an error) - e.g. login creds didn't work"""
|
"""Prints a failure (may or may not be an error) - e.g. login creds didn't work"""
|
||||||
try:
|
|
||||||
if self.extra and "protocol" in self.extra and not called_from_cmd_args():
|
|
||||||
return
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
msg, kwargs = self.format(f"{colored('[-]', color, attrs=['bold'])} {msg}", kwargs)
|
msg, kwargs = self.format(f"{colored('[-]', color, attrs=['bold'])} {msg}", kwargs)
|
||||||
text = Text.from_ansi(msg)
|
text = Text.from_ansi(msg)
|
||||||
nxc_console.print(text, *args, **kwargs)
|
nxc_console.print(text, *args, **kwargs)
|
||||||
|
|
|
@ -17,12 +17,11 @@ class NXCModule:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def on_login(self, context, connection):
|
def on_login(self, context, connection):
|
||||||
domain_dn = ",".join(["DC=" + dc for dc in connection.domain.split(".")])
|
|
||||||
search_filter = "(&(objectClass=trustedDomain))"
|
search_filter = "(&(objectClass=trustedDomain))"
|
||||||
attributes = ["flatName", "trustPartner", "trustDirection", "trustAttributes"]
|
attributes = ["flatName", "trustPartner", "trustDirection", "trustAttributes"]
|
||||||
|
|
||||||
context.log.debug(f"Search Filter={search_filter}")
|
context.log.debug(f"Search Filter={search_filter}")
|
||||||
resp = connection.ldapConnection.search(searchBase=domain_dn, searchFilter=search_filter, attributes=attributes, sizeLimit=0)
|
resp = connection.ldapConnection.search(searchFilter=search_filter, attributes=attributes, sizeLimit=0)
|
||||||
|
|
||||||
trusts = []
|
trusts = []
|
||||||
context.log.debug(f"Total of records returned {len(resp)}")
|
context.log.debug(f"Total of records returned {len(resp)}")
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
import binascii
|
||||||
|
import hashlib
|
||||||
|
from json import loads
|
||||||
from pyasn1.codec.der import decoder
|
from pyasn1.codec.der import decoder
|
||||||
from pyasn1_modules import rfc5652
|
from pyasn1_modules import rfc5652
|
||||||
|
|
||||||
|
@ -258,3 +261,106 @@ class LAPSv2Extract:
|
||||||
plaintext = decrypt_plaintext(cek, iv, remaining)
|
plaintext = decrypt_plaintext(cek, iv, remaining)
|
||||||
self.logger.info(plaintext[:-18].decode("utf-16le"))
|
self.logger.info(plaintext[:-18].decode("utf-16le"))
|
||||||
return plaintext[:-18].decode("utf-16le")
|
return plaintext[:-18].decode("utf-16le")
|
||||||
|
|
||||||
|
|
||||||
|
def laps_search(self, username, password, cred_type, domain):
|
||||||
|
prev_protocol = self.logger.extra["protocol"]
|
||||||
|
prev_port = self.logger.extra["port"]
|
||||||
|
self.logger.extra["protocol"] = "LDAP"
|
||||||
|
self.logger.extra["port"] = "389"
|
||||||
|
|
||||||
|
ldapco = LDAPConnect(self.domain, "389", self.domain)
|
||||||
|
|
||||||
|
if self.kerberos:
|
||||||
|
if self.kdcHost is None:
|
||||||
|
self.logger.fail("Add --kdcHost parameter to use laps with kerberos")
|
||||||
|
return None, None, None, None
|
||||||
|
|
||||||
|
connection = ldapco.kerberos_login(
|
||||||
|
domain[0],
|
||||||
|
username[0] if username else "",
|
||||||
|
password[0] if cred_type[0] == "plaintext" else "",
|
||||||
|
password[0] if cred_type[0] == "hash" else "",
|
||||||
|
kdcHost=self.kdcHost,
|
||||||
|
aesKey=self.aesKey,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
connection = ldapco.auth_login(
|
||||||
|
domain[0],
|
||||||
|
username[0] if username else "",
|
||||||
|
password[0] if cred_type[0] == "plaintext" else "",
|
||||||
|
password[0] if cred_type[0] == "hash" else "",
|
||||||
|
)
|
||||||
|
if not connection:
|
||||||
|
self.logger.fail(f"LDAP connection failed with account {username[0]}")
|
||||||
|
|
||||||
|
return None, None, None, None
|
||||||
|
|
||||||
|
search_filter = "(&(objectCategory=computer)(|(msLAPS-EncryptedPassword=*)(ms-MCS-AdmPwd=*)(msLAPS-Password=*))(name=" + self.hostname + "))"
|
||||||
|
attributes = [
|
||||||
|
"msLAPS-EncryptedPassword",
|
||||||
|
"msLAPS-Password",
|
||||||
|
"ms-MCS-AdmPwd",
|
||||||
|
"sAMAccountName",
|
||||||
|
]
|
||||||
|
results = connection.search(searchFilter=search_filter, attributes=attributes, sizeLimit=0)
|
||||||
|
|
||||||
|
msMCSAdmPwd = ""
|
||||||
|
sAMAccountName = ""
|
||||||
|
username_laps = ""
|
||||||
|
|
||||||
|
from impacket.ldap import ldapasn1 as ldapasn1_impacket
|
||||||
|
|
||||||
|
results = [r for r in results if isinstance(r, ldapasn1_impacket.SearchResultEntry)]
|
||||||
|
if len(results) != 0:
|
||||||
|
for host in results:
|
||||||
|
values = {str(attr["type"]).lower(): attr["vals"][0] for attr in host["attributes"]}
|
||||||
|
if "mslaps-encryptedpassword" in values:
|
||||||
|
msMCSAdmPwd = values["mslaps-encryptedpassword"]
|
||||||
|
d = LAPSv2Extract(
|
||||||
|
bytes(msMCSAdmPwd),
|
||||||
|
username[0] if username else "",
|
||||||
|
password[0] if cred_type[0] == "plaintext" else "",
|
||||||
|
domain[0],
|
||||||
|
password[0] if cred_type[0] == "hash" else "",
|
||||||
|
self.args.kerberos,
|
||||||
|
self.args.kdcHost,
|
||||||
|
339
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
data = d.run()
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.fail(str(e))
|
||||||
|
return None, None, None, None
|
||||||
|
r = loads(data)
|
||||||
|
msMCSAdmPwd = r["p"]
|
||||||
|
username_laps = r["n"]
|
||||||
|
elif "mslaps-password" in values:
|
||||||
|
r = loads(str(values["mslaps-password"]))
|
||||||
|
msMCSAdmPwd = r["p"]
|
||||||
|
username_laps = r["n"]
|
||||||
|
elif "ms-mcs-admpwd" in values:
|
||||||
|
msMCSAdmPwd = str(values["ms-mcs-admpwd"])
|
||||||
|
else:
|
||||||
|
self.logger.fail("No result found with attribute ms-MCS-AdmPwd or msLAPS-Password")
|
||||||
|
self.logger.debug(f"Host: {sAMAccountName:<20} Password: {msMCSAdmPwd} {self.hostname}")
|
||||||
|
else:
|
||||||
|
self.logger.fail(f"msMCSAdmPwd or msLAPS-Password is empty or account cannot read LAPS property for {self.hostname}")
|
||||||
|
return None, None, None, None
|
||||||
|
|
||||||
|
if msMCSAdmPwd == "":
|
||||||
|
self.logger.fail(f"msMCSAdmPwd or msLAPS-Password is empty or account cannot read LAPS property for {self.hostname}")
|
||||||
|
return None, None, None, None
|
||||||
|
|
||||||
|
hash_ntlm = None
|
||||||
|
if cred_type[0] == "hash":
|
||||||
|
hash_ntlm = hashlib.new("md4", msMCSAdmPwd.encode("utf-16le")).digest()
|
||||||
|
hash_ntlm = binascii.hexlify(hash_ntlm).decode()
|
||||||
|
|
||||||
|
username = username_laps if username_laps else self.args.laps
|
||||||
|
password = msMCSAdmPwd
|
||||||
|
domain = self.hostname
|
||||||
|
self.args.local_auth = True
|
||||||
|
self.logger.extra["protocol"] = prev_protocol
|
||||||
|
self.logger.extra["port"] = prev_port
|
||||||
|
return username, password, domain, hash_ntlm
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import ntpath
|
import ntpath
|
||||||
import hashlib
|
|
||||||
import binascii
|
import binascii
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
@ -44,7 +43,6 @@ from nxc.protocols.smb.smbspider import SMBSpider
|
||||||
from nxc.protocols.smb.passpol import PassPolDump
|
from nxc.protocols.smb.passpol import PassPolDump
|
||||||
from nxc.protocols.smb.samruser import UserSamrDump
|
from nxc.protocols.smb.samruser import UserSamrDump
|
||||||
from nxc.protocols.smb.samrfunc import SamrFunc
|
from nxc.protocols.smb.samrfunc import SamrFunc
|
||||||
from nxc.protocols.ldap.laps import LDAPConnect, LAPSv2Extract
|
|
||||||
from nxc.protocols.ldap.gmsa import MSDS_MANAGEDPASSWORD_BLOB
|
from nxc.protocols.ldap.gmsa import MSDS_MANAGEDPASSWORD_BLOB
|
||||||
from nxc.helpers.logger import highlight
|
from nxc.helpers.logger import highlight
|
||||||
from nxc.helpers.bloodhound import add_user_bh
|
from nxc.helpers.bloodhound import add_user_bh
|
||||||
|
@ -65,7 +63,6 @@ from datetime import datetime
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from traceback import format_exc
|
from traceback import format_exc
|
||||||
import logging
|
import logging
|
||||||
from json import loads
|
|
||||||
from termcolor import colored
|
from termcolor import colored
|
||||||
import contextlib
|
import contextlib
|
||||||
|
|
||||||
|
@ -253,104 +250,10 @@ class smb(connection):
|
||||||
if self.args.local_auth:
|
if self.args.local_auth:
|
||||||
self.domain = self.hostname
|
self.domain = self.hostname
|
||||||
|
|
||||||
def laps_search(self, username, password, ntlm_hash, domain):
|
|
||||||
self.logger.extra["protocol"] = "LDAP"
|
|
||||||
self.logger.extra["port"] = "389"
|
|
||||||
|
|
||||||
ldapco = LDAPConnect(self.domain, "389", self.domain)
|
|
||||||
|
|
||||||
if self.kerberos:
|
|
||||||
if self.kdcHost is None:
|
|
||||||
self.logger.fail("Add --kdcHost parameter to use laps with kerberos")
|
|
||||||
return False
|
|
||||||
|
|
||||||
connection = ldapco.kerberos_login(
|
|
||||||
domain,
|
|
||||||
username[0] if username else "",
|
|
||||||
password[0] if password else "",
|
|
||||||
ntlm_hash[0] if ntlm_hash else "",
|
|
||||||
kdcHost=self.kdcHost,
|
|
||||||
aesKey=self.aesKey,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
connection = ldapco.auth_login(
|
|
||||||
domain,
|
|
||||||
username[0] if username else "",
|
|
||||||
password[0] if password else "",
|
|
||||||
ntlm_hash[0] if ntlm_hash else "",
|
|
||||||
)
|
|
||||||
if not connection:
|
|
||||||
self.logger.fail(f"LDAP connection failed with account {username[0]}")
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
search_filter = "(&(objectCategory=computer)(|(msLAPS-EncryptedPassword=*)(ms-MCS-AdmPwd=*)(msLAPS-Password=*))(name=" + self.hostname + "))"
|
|
||||||
attributes = [
|
|
||||||
"msLAPS-EncryptedPassword",
|
|
||||||
"msLAPS-Password",
|
|
||||||
"ms-MCS-AdmPwd",
|
|
||||||
"sAMAccountName",
|
|
||||||
]
|
|
||||||
results = connection.search(searchFilter=search_filter, attributes=attributes, sizeLimit=0)
|
|
||||||
|
|
||||||
msMCSAdmPwd = ""
|
|
||||||
sAMAccountName = ""
|
|
||||||
username_laps = ""
|
|
||||||
|
|
||||||
from impacket.ldap import ldapasn1 as ldapasn1_impacket
|
|
||||||
|
|
||||||
results = [r for r in results if isinstance(r, ldapasn1_impacket.SearchResultEntry)]
|
|
||||||
if len(results) != 0:
|
|
||||||
for host in results:
|
|
||||||
values = {str(attr["type"]).lower(): attr["vals"][0] for attr in host["attributes"]}
|
|
||||||
if "mslaps-encryptedpassword" in values:
|
|
||||||
msMCSAdmPwd = values["mslaps-encryptedpassword"]
|
|
||||||
d = LAPSv2Extract(bytes(msMCSAdmPwd), username[0] if username else "", password[0] if password else "", domain, ntlm_hash[0] if ntlm_hash else "", self.args.kerberos, self.args.kdcHost, 339)
|
|
||||||
try:
|
|
||||||
data = d.run()
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.fail(str(e))
|
|
||||||
return None
|
|
||||||
r = loads(data)
|
|
||||||
msMCSAdmPwd = r["p"]
|
|
||||||
username_laps = r["n"]
|
|
||||||
elif "mslaps-password" in values:
|
|
||||||
r = loads(str(values["mslaps-password"]))
|
|
||||||
msMCSAdmPwd = r["p"]
|
|
||||||
username_laps = r["n"]
|
|
||||||
elif "ms-mcs-admpwd" in values:
|
|
||||||
msMCSAdmPwd = str(values["ms-mcs-admpwd"])
|
|
||||||
else:
|
|
||||||
self.logger.fail("No result found with attribute ms-MCS-AdmPwd or msLAPS-Password")
|
|
||||||
logging.debug(f"Host: {sAMAccountName:<20} Password: {msMCSAdmPwd} {self.hostname}")
|
|
||||||
else:
|
|
||||||
self.logger.fail(f"msMCSAdmPwd or msLAPS-Password is empty or account cannot read LAPS property for {self.hostname}")
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
self.username = username_laps if username_laps else self.args.laps
|
|
||||||
self.password = msMCSAdmPwd
|
|
||||||
|
|
||||||
if msMCSAdmPwd == "":
|
|
||||||
self.logger.fail(f"msMCSAdmPwd or msLAPS-Password is empty or account cannot read LAPS property for {self.hostname}")
|
|
||||||
|
|
||||||
return False
|
|
||||||
if ntlm_hash:
|
|
||||||
hash_ntlm = hashlib.new("md4", msMCSAdmPwd.encode("utf-16le")).digest()
|
|
||||||
self.hash = binascii.hexlify(hash_ntlm).decode()
|
|
||||||
|
|
||||||
self.args.local_auth = True
|
|
||||||
self.domain = self.hostname
|
|
||||||
self.logger.extra["protocol"] = "SMB"
|
|
||||||
self.logger.extra["port"] = "445"
|
|
||||||
return True
|
|
||||||
|
|
||||||
def print_host_info(self):
|
def print_host_info(self):
|
||||||
signing = colored(f"signing:{self.signing}", host_info_colors[0], attrs=["bold"]) if self.signing else colored(f"signing:{self.signing}", host_info_colors[1], attrs=["bold"])
|
signing = colored(f"signing:{self.signing}", host_info_colors[0], attrs=["bold"]) if self.signing else colored(f"signing:{self.signing}", host_info_colors[1], attrs=["bold"])
|
||||||
smbv1 = colored(f"SMBv1:{self.smbv1}", host_info_colors[2], attrs=["bold"]) if self.smbv1 else colored(f"SMBv1:{self.smbv1}", host_info_colors[3], attrs=["bold"])
|
smbv1 = colored(f"SMBv1:{self.smbv1}", host_info_colors[2], attrs=["bold"]) if self.smbv1 else colored(f"SMBv1:{self.smbv1}", host_info_colors[3], attrs=["bold"])
|
||||||
self.logger.display(f"{self.server_os}{f' x{self.os_arch}' if self.os_arch else ''} (name:{self.hostname}) (domain:{self.domain}) ({signing}) ({smbv1})")
|
self.logger.display(f"{self.server_os}{f' x{self.os_arch}' if self.os_arch else ''} (name:{self.hostname}) (domain:{self.domain}) ({signing}) ({smbv1})")
|
||||||
if self.args.laps:
|
|
||||||
return self.laps_search(self.args.username, self.args.password, self.args.hash, self.domain)
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def kerberos_login(self, domain, username, password="", ntlm_hash="", aesKey="", kdcHost="", useCache=False):
|
def kerberos_login(self, domain, username, password="", ntlm_hash="", aesKey="", kdcHost="", useCache=False):
|
||||||
|
@ -363,7 +266,6 @@ class smb(connection):
|
||||||
nthash = ""
|
nthash = ""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not self.args.laps:
|
|
||||||
self.password = password
|
self.password = password
|
||||||
self.username = username
|
self.username = username
|
||||||
# This checks to see if we didn't provide the LM Hash
|
# This checks to see if we didn't provide the LM Hash
|
||||||
|
@ -403,9 +305,6 @@ class smb(connection):
|
||||||
used_ccache = " from ccache" if useCache else f":{process_secret(kerb_pass)}"
|
used_ccache = " from ccache" if useCache else f":{process_secret(kerb_pass)}"
|
||||||
if self.args.delegate:
|
if self.args.delegate:
|
||||||
used_ccache = f" through S4U with {username}"
|
used_ccache = f" through S4U with {username}"
|
||||||
else:
|
|
||||||
self.plaintext_login(self.hostname, username, password)
|
|
||||||
return True
|
|
||||||
|
|
||||||
out = f"{self.domain}\\{self.username}{used_ccache} {self.mark_pwned()}"
|
out = f"{self.domain}\\{self.username}{used_ccache} {self.mark_pwned()}"
|
||||||
self.logger.success(out)
|
self.logger.success(out)
|
||||||
|
@ -456,17 +355,11 @@ class smb(connection):
|
||||||
# Re-connect since we logged off
|
# Re-connect since we logged off
|
||||||
self.create_conn_obj()
|
self.create_conn_obj()
|
||||||
try:
|
try:
|
||||||
if not self.args.laps:
|
|
||||||
self.password = password
|
self.password = password
|
||||||
self.username = username
|
self.username = username
|
||||||
self.domain = domain
|
self.domain = domain
|
||||||
|
|
||||||
try:
|
|
||||||
self.conn.login(self.username, self.password, domain)
|
self.conn.login(self.username, self.password, domain)
|
||||||
except UnicodeEncodeError:
|
|
||||||
self.logger.error(f"UnicodeEncodeError on: '{self.username}:{self.password}'. Trying again with a different encoding...")
|
|
||||||
self.create_conn_obj()
|
|
||||||
self.conn.login(self.username, self.password.encode().decode("latin-1"), domain)
|
|
||||||
|
|
||||||
self.check_if_admin()
|
self.check_if_admin()
|
||||||
self.logger.debug(f"Adding credential: {domain}/{self.username}:{self.password}")
|
self.logger.debug(f"Adding credential: {domain}/{self.username}:{self.password}")
|
||||||
|
@ -521,7 +414,7 @@ class smb(connection):
|
||||||
lmhash = ""
|
lmhash = ""
|
||||||
nthash = ""
|
nthash = ""
|
||||||
try:
|
try:
|
||||||
if not self.args.laps:
|
self.domain = domain
|
||||||
self.username = username
|
self.username = username
|
||||||
# This checks to see if we didn't provide the LM Hash
|
# This checks to see if we didn't provide the LM Hash
|
||||||
if ntlm_hash.find(":") != -1:
|
if ntlm_hash.find(":") != -1:
|
||||||
|
@ -534,10 +427,6 @@ class smb(connection):
|
||||||
self.lmhash = lmhash
|
self.lmhash = lmhash
|
||||||
if nthash:
|
if nthash:
|
||||||
self.nthash = nthash
|
self.nthash = nthash
|
||||||
else:
|
|
||||||
nthash = self.hash
|
|
||||||
|
|
||||||
self.domain = domain
|
|
||||||
|
|
||||||
self.conn.login(self.username, "", domain, lmhash, nthash)
|
self.conn.login(self.username, "", domain, lmhash, nthash)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import binascii
|
|
||||||
import hashlib
|
|
||||||
import os
|
import os
|
||||||
import requests
|
import requests
|
||||||
import urllib3
|
import urllib3
|
||||||
|
@ -19,12 +17,12 @@ from nxc.config import process_secret
|
||||||
from nxc.connection import connection
|
from nxc.connection import connection
|
||||||
from nxc.helpers.bloodhound import add_user_bh
|
from nxc.helpers.bloodhound import add_user_bh
|
||||||
from nxc.helpers.misc import gen_random_string
|
from nxc.helpers.misc import gen_random_string
|
||||||
from nxc.protocols.ldap.laps import LDAPConnect, LAPSv2Extract
|
|
||||||
from nxc.logger import NXCAdapter
|
from nxc.logger import NXCAdapter
|
||||||
|
|
||||||
|
|
||||||
urllib3.disable_warnings()
|
urllib3.disable_warnings()
|
||||||
|
|
||||||
|
|
||||||
class winrm(connection):
|
class winrm(connection):
|
||||||
def __init__(self, args, db, host):
|
def __init__(self, args, db, host):
|
||||||
self.domain = None
|
self.domain = None
|
||||||
|
@ -99,89 +97,6 @@ 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(":", "-"))
|
self.output_filename = os.path.expanduser(f"~/.nxc/logs/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}".replace(":", "-"))
|
||||||
|
|
||||||
def laps_search(self, username, password, ntlm_hash, domain):
|
|
||||||
ldapco = LDAPConnect(self.domain, "389", self.domain)
|
|
||||||
|
|
||||||
if self.kerberos:
|
|
||||||
if self.kdcHost is None:
|
|
||||||
self.logger.fail("Add --kdcHost parameter to use laps with kerberos")
|
|
||||||
return False
|
|
||||||
|
|
||||||
connection = ldapco.kerberos_login(
|
|
||||||
domain,
|
|
||||||
username[0] if username else "",
|
|
||||||
password[0] if password else "",
|
|
||||||
ntlm_hash[0] if ntlm_hash else "",
|
|
||||||
kdcHost=self.kdcHost,
|
|
||||||
aesKey=self.aesKey,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
connection = ldapco.auth_login(
|
|
||||||
domain,
|
|
||||||
username[0] if username else "",
|
|
||||||
password[0] if password else "",
|
|
||||||
ntlm_hash[0] if ntlm_hash else "",
|
|
||||||
)
|
|
||||||
if not connection:
|
|
||||||
self.logger.fail(f"LDAP connection failed with account {username[0]}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
search_filter = "(&(objectCategory=computer)(|(msLAPS-EncryptedPassword=*)(ms-MCS-AdmPwd=*)(msLAPS-Password=*))(name=" + self.hostname + "))"
|
|
||||||
attributes = [
|
|
||||||
"msLAPS-EncryptedPassword",
|
|
||||||
"msLAPS-Password",
|
|
||||||
"ms-MCS-AdmPwd",
|
|
||||||
"sAMAccountName",
|
|
||||||
]
|
|
||||||
results = connection.search(searchFilter=search_filter, attributes=attributes, sizeLimit=0)
|
|
||||||
|
|
||||||
msMCSAdmPwd = ""
|
|
||||||
sAMAccountName = ""
|
|
||||||
username_laps = ""
|
|
||||||
|
|
||||||
from impacket.ldap import ldapasn1 as ldapasn1_impacket
|
|
||||||
|
|
||||||
results = [r for r in results if isinstance(r, ldapasn1_impacket.SearchResultEntry)]
|
|
||||||
if len(results) != 0:
|
|
||||||
for host in results:
|
|
||||||
values = {str(attr["type"]).lower(): attr["vals"][0] for attr in host["attributes"]}
|
|
||||||
if "mslaps-encryptedpassword" in values:
|
|
||||||
from json import loads
|
|
||||||
|
|
||||||
msMCSAdmPwd = values["mslaps-encryptedpassword"]
|
|
||||||
d = LAPSv2Extract(bytes(msMCSAdmPwd), username[0] if username else "", password[0] if password else "", domain, ntlm_hash[0] if ntlm_hash else "", self.args.kerberos, self.args.kdcHost, 339)
|
|
||||||
data = d.run()
|
|
||||||
r = loads(data)
|
|
||||||
msMCSAdmPwd = r["p"]
|
|
||||||
username_laps = r["n"]
|
|
||||||
elif "mslaps-password" in values:
|
|
||||||
from json import loads
|
|
||||||
|
|
||||||
r = loads(str(values["mslaps-password"]))
|
|
||||||
msMCSAdmPwd = r["p"]
|
|
||||||
username_laps = r["n"]
|
|
||||||
elif "ms-mcs-admpwd" in values:
|
|
||||||
msMCSAdmPwd = str(values["ms-mcs-admpwd"])
|
|
||||||
else:
|
|
||||||
self.logger.fail("No result found with attribute ms-MCS-AdmPwd or msLAPS-Password")
|
|
||||||
self.logger.debug(f"Host: {sAMAccountName:<20} Password: {msMCSAdmPwd} {self.hostname}")
|
|
||||||
else:
|
|
||||||
self.logger.fail(f"msMCSAdmPwd or msLAPS-Password is empty or account cannot read LAPS property for {self.hostname}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
self.username = username_laps if username_laps else self.args.laps
|
|
||||||
self.password = msMCSAdmPwd
|
|
||||||
|
|
||||||
if msMCSAdmPwd == "":
|
|
||||||
self.logger.fail(f"msMCSAdmPwd or msLAPS-Password is empty or account cannot read LAPS property for {self.hostname}")
|
|
||||||
return False
|
|
||||||
if ntlm_hash:
|
|
||||||
hash_ntlm = hashlib.new("md4", msMCSAdmPwd.encode("utf-16le")).digest()
|
|
||||||
self.hash = binascii.hexlify(hash_ntlm).decode()
|
|
||||||
|
|
||||||
self.domain = self.hostname
|
|
||||||
return True
|
|
||||||
|
|
||||||
def print_host_info(self):
|
def print_host_info(self):
|
||||||
if self.args.no_smb:
|
if self.args.no_smb:
|
||||||
self.logger.extra["protocol"] = "WINRM-SSL" if self.ssl else "WINRM"
|
self.logger.extra["protocol"] = "WINRM-SSL" if self.ssl else "WINRM"
|
||||||
|
@ -193,9 +108,6 @@ class winrm(connection):
|
||||||
self.logger.extra["port"] = self.port
|
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.info(f"Connection information: {self.endpoint} (auth type:{self.auth_type}) (domain:{self.domain if self.args.domain else ''})")
|
||||||
|
|
||||||
if self.args.laps:
|
|
||||||
return self.laps_search(self.args.username, self.args.password, self.args.hash, self.domain)
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def create_conn_obj(self):
|
def create_conn_obj(self):
|
||||||
|
@ -249,7 +161,6 @@ class winrm(connection):
|
||||||
|
|
||||||
def plaintext_login(self, domain, username, password):
|
def plaintext_login(self, domain, username, password):
|
||||||
self.admin_privs = False
|
self.admin_privs = False
|
||||||
if not self.args.laps:
|
|
||||||
self.password = password
|
self.password = password
|
||||||
self.username = username
|
self.username = username
|
||||||
self.domain = domain
|
self.domain = domain
|
||||||
|
@ -290,15 +201,13 @@ class winrm(connection):
|
||||||
self.admin_privs = False
|
self.admin_privs = False
|
||||||
lmhash = "00000000000000000000000000000000"
|
lmhash = "00000000000000000000000000000000"
|
||||||
nthash = ""
|
nthash = ""
|
||||||
if not self.args.laps:
|
|
||||||
self.username = username
|
self.username = username
|
||||||
# This checks to see if we didn't provide the LM Hash
|
# This checks to see if we didn't provide the LM Hash
|
||||||
if ntlm_hash.find(":") != -1:
|
if ntlm_hash.find(":") != -1:
|
||||||
lmhash, nthash = ntlm_hash.split(":")
|
lmhash, nthash = ntlm_hash.split(":")
|
||||||
else:
|
else:
|
||||||
nthash = ntlm_hash
|
nthash = ntlm_hash
|
||||||
else:
|
|
||||||
nthash = self.hash
|
|
||||||
self.lmhash = lmhash
|
self.lmhash = lmhash
|
||||||
self.nthash = nthash
|
self.nthash = nthash
|
||||||
self.domain = domain
|
self.domain = domain
|
||||||
|
|
Loading…
Reference in New Issue