mirror of
https://github.com/Pennyw0rth/NetExec
synced 2026-06-06 16:34:30 +00:00
Merge branch 'main' into presence
This commit is contained in:
@@ -1,24 +1,20 @@
|
||||
## Description
|
||||
|
||||
Please include a summary of the change and which issue is fixed, or what the enhancement does.
|
||||
Please also include relevant motivation and context.
|
||||
List any dependencies that are required for this change.
|
||||
|
||||
## Type of change
|
||||
Please delete options that are not relevant.
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||
- [ ] This change requires a documentation update
|
||||
- [ ] This requires a third party update (such as Impacket, Dploot, lsassy, etc)
|
||||
|
||||
## How Has This Been Tested?
|
||||
Please describe the tests that you ran to verify your changes (e2e, single commands, etc)
|
||||
Please also list any relevant details for your test configuration, such as your locally running machine Python version & OS, as well as the target(s) you tested against, including software versions
|
||||
|
||||
If you are using poetry, you can easily run tests via:
|
||||
`poetry run python tests/e2e_tests.py -t $TARGET -u $USER -p $PASSWORD`
|
||||
There are additional options like `--errors` to display ALL errors (some may not be failures), `--poetry` (output will include the poetry run prepended), `--line-num $START-$END $SINGLE` for only running a subset
|
||||
## Setup guide for the review
|
||||
Please provide guidance on what setup is needed to test the introduced changes, such as your locally running machine Python version & OS, as well as the target(s) you tested against, including software versions.
|
||||
In particular:
|
||||
- Bug Fix: Please provide a short description on how to trigger the bug, to make the bug reproducable for the reviewer.
|
||||
- Added Feature/Enhancement: Please specify what setup is needed in order to test the changes. E.g. is additional software needed? GPO changes required? Specific registry settings that need to be changed?
|
||||
|
||||
## Screenshots (if appropriate):
|
||||
Screenshots are always nice to have and can give a visual representation of the change.
|
||||
@@ -29,8 +25,7 @@ If appropriate include before and after screenshot(s) to show which results are
|
||||
- [ ] I have ran Ruff against my changes (via poetry: `poetry run python -m ruff check . --preview`, use `--fix` to automatically fix what it can)
|
||||
- [ ] I have added or updated the tests/e2e_commands.txt file if necessary
|
||||
- [ ] New and existing e2e tests pass locally with my changes
|
||||
- [ ] My code follows the style guidelines of this project (should be covered by Ruff above)
|
||||
- [ ] If reliant on third party dependencies, such as Impacket, dploot, lsassy, etc, I have linked the relevant PRs in those projects
|
||||
- [ ] If reliant on changes of third party dependencies, such as Impacket, dploot, lsassy, etc, I have linked the relevant PRs in those projects
|
||||
- [ ] I have performed a self-review of my own code
|
||||
- [ ] I have commented my code, particularly in hard-to-understand areas
|
||||
- [ ] I have made corresponding changes to the documentation (PR here: https://github.com/Pennyw0rth/NetExec-Wiki)
|
||||
|
||||
@@ -18,6 +18,11 @@ if "nxc" not in nxc_config.sections():
|
||||
|
||||
# Check if there are any missing options in the config file
|
||||
for section in nxc_default_config.sections():
|
||||
if not nxc_config.has_section(section):
|
||||
nxc_logger.display(f"Adding missing section '{section}' to nxc.conf")
|
||||
nxc_config.add_section(section)
|
||||
with open(path_join(NXC_PATH, "nxc.conf"), "w") as config_file:
|
||||
nxc_config.write(config_file)
|
||||
for option in nxc_default_config.options(section):
|
||||
if not nxc_config.has_option(section, option):
|
||||
nxc_logger.display(f"Adding missing option '{option}' in config section '{section}' to nxc.conf")
|
||||
|
||||
+4
-6
@@ -275,7 +275,7 @@ class connection:
|
||||
extra={
|
||||
"module_name": module.name.upper(),
|
||||
"host": self.host,
|
||||
"port": self.args.port,
|
||||
"port": self.port,
|
||||
"hostname": self.hostname,
|
||||
},
|
||||
)
|
||||
@@ -293,8 +293,7 @@ class connection:
|
||||
module.on_admin_login(context, self)
|
||||
|
||||
def inc_failed_login(self, username):
|
||||
global global_failed_logins
|
||||
global user_failed_logins
|
||||
global global_failed_logins, user_failed_logins
|
||||
|
||||
if username not in user_failed_logins:
|
||||
user_failed_logins[username] = 0
|
||||
@@ -304,8 +303,7 @@ class connection:
|
||||
self.failed_logins += 1
|
||||
|
||||
def over_fail_limit(self, username):
|
||||
global global_failed_logins
|
||||
global user_failed_logins
|
||||
global global_failed_logins, user_failed_logins
|
||||
|
||||
if global_failed_logins == self.args.gfail_limit:
|
||||
return True
|
||||
@@ -313,7 +311,7 @@ class connection:
|
||||
if self.failed_logins == self.args.fail_limit:
|
||||
return True
|
||||
|
||||
if username in user_failed_logins and self.args.ufail_limit == user_failed_logins[username]:
|
||||
if username in user_failed_logins and self.args.ufail_limit == user_failed_logins[username]: # noqa: SIM103
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@@ -15,6 +15,9 @@ bh_port = 7687
|
||||
bh_user = neo4j
|
||||
bh_pass = bloodhoundcommunityedition
|
||||
|
||||
[BloodHound-CE]
|
||||
bhce_enabled = True
|
||||
|
||||
[Empire]
|
||||
api_host = 127.0.0.1
|
||||
api_port = 1337
|
||||
|
||||
@@ -111,6 +111,7 @@ def initialize_db():
|
||||
# Even if the default workspace exists, we still need to check if every protocol has a database (in case of a new protocol)
|
||||
init_protocol_dbs("default")
|
||||
|
||||
|
||||
def format_host_query(q, filter_term, HostsTable):
|
||||
"""One annoying thing is that if you search for an ip such as '10.10.10.5',
|
||||
it will return 10.10.10.5 and 10.10.10.52, so we have to check if its an ip address first
|
||||
@@ -141,6 +142,7 @@ def format_host_query(q, filter_term, HostsTable):
|
||||
|
||||
return q
|
||||
|
||||
|
||||
class BaseDB:
|
||||
def __init__(self, db_engine):
|
||||
self.db_engine = db_engine
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from argparse import ArgumentDefaultsHelpFormatter, SUPPRESS, OPTIONAL, ZERO_OR_MORE
|
||||
from argparse import Action
|
||||
|
||||
|
||||
class DisplayDefaultsNotNone(ArgumentDefaultsHelpFormatter):
|
||||
def _get_help_string(self, action):
|
||||
help_string = action.help
|
||||
|
||||
@@ -5,6 +5,7 @@ import uuid
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class Substitution:
|
||||
def __init__(self, buf, offset):
|
||||
(sub_token, sub_id, sub_type) = struct.unpack_from("<BHB", buf, offset)
|
||||
@@ -46,6 +47,7 @@ class Substitution:
|
||||
else:
|
||||
print("Unknown value type", hex(value.type))
|
||||
|
||||
|
||||
class Value:
|
||||
def __init__(self, buf, offset):
|
||||
token, string_type, length = struct.unpack_from("<BBH", buf, offset)
|
||||
@@ -56,6 +58,7 @@ class Value:
|
||||
def xml(self, template=None):
|
||||
return self._val
|
||||
|
||||
|
||||
class Attribute:
|
||||
def __init__(self, buf, offset):
|
||||
struct.unpack_from("<B", buf, offset)
|
||||
@@ -75,6 +78,7 @@ class Attribute:
|
||||
val = self._value.xml(template)
|
||||
return None if val is None else f'{self._name.val}="{val}"'
|
||||
|
||||
|
||||
class Name:
|
||||
def __init__(self, buf, offset):
|
||||
hashs, length = struct.unpack_from("<HH", buf, offset)
|
||||
@@ -82,6 +86,7 @@ class Name:
|
||||
self.val = buf[offset + 4:offset + 4 + length * 2].decode("utf16")
|
||||
self.length = 4 + (length + 1) * 2
|
||||
|
||||
|
||||
class Element:
|
||||
def __init__(self, buf, offset):
|
||||
token, dependency_id, length = struct.unpack_from("<BHI", buf, offset)
|
||||
@@ -151,6 +156,7 @@ class Element:
|
||||
children = (x.xml(template) for x in self._children)
|
||||
return "<{}{}>{}</{}>".format(self._name.val, attrs, "".join(children), self._name.val)
|
||||
|
||||
|
||||
class ValueSpec:
|
||||
def __init__(self, buf, offset, value_offset):
|
||||
self.length, self.type, value_eof = struct.unpack_from("<HBB", buf, offset)
|
||||
@@ -159,6 +165,7 @@ class ValueSpec:
|
||||
if self.type == 0x21:
|
||||
self.template = BinXML(buf, value_offset)
|
||||
|
||||
|
||||
class TemplateInstance:
|
||||
def __init__(self, buf, offset):
|
||||
token, unknown0, guid, length, next_token = struct.unpack_from("<BB16sIB", buf, offset)
|
||||
@@ -179,6 +186,7 @@ class TemplateInstance:
|
||||
def xml(self, template=None):
|
||||
return self._xml.xml(self)
|
||||
|
||||
|
||||
class BinXML:
|
||||
def __init__(self, buf, offset):
|
||||
header_token, major_version, minor_version, flags, next_token = struct.unpack_from("<BBBBB", buf, offset)
|
||||
@@ -195,6 +203,7 @@ class BinXML:
|
||||
def xml(self, template=None):
|
||||
return self._element.xml(template)
|
||||
|
||||
|
||||
class ResultSet:
|
||||
def __init__(self, buf):
|
||||
total_size, header_size, event_offset, bookmark_offset, binxml_size = struct.unpack_from("<IIIII", buf)
|
||||
|
||||
+71
-2
@@ -4,6 +4,8 @@ import re
|
||||
import inspect
|
||||
import os
|
||||
|
||||
from ipaddress import ip_address
|
||||
|
||||
|
||||
def identify_target_file(target_file):
|
||||
with open(target_file) as target_file_handle:
|
||||
@@ -22,7 +24,7 @@ def gen_random_string(length=10):
|
||||
|
||||
|
||||
def validate_ntlm(data):
|
||||
allowed = re.compile("^[0-9a-f]{32}", re.IGNORECASE)
|
||||
allowed = re.compile(r"^[0-9a-f]{32}", re.IGNORECASE)
|
||||
return bool(allowed.match(data))
|
||||
|
||||
|
||||
@@ -40,7 +42,7 @@ def called_from_cmd_args():
|
||||
# Stolen from https://github.com/pydanny/whichcraft/
|
||||
def which(cmd, mode=os.F_OK | os.X_OK, path=None):
|
||||
"""Find the path which conforms to the given mode on the PATH for a command.
|
||||
|
||||
|
||||
Given a command, mode, and a PATH string, return the path which conforms to the given mode on the PATH, or None if there is no such file.
|
||||
`mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result of os.environ.get("PATH"), or can be overridden with a custom search path.
|
||||
Note: This function was backported from the Python 3 source code.
|
||||
@@ -77,3 +79,70 @@ def which(cmd, mode=os.F_OK | os.X_OK, path=None):
|
||||
name = os.path.join(p, thefile)
|
||||
if _access_check(name, mode):
|
||||
return name
|
||||
|
||||
|
||||
def get_bloodhound_info():
|
||||
"""
|
||||
Detect which BloodHound package is installed (regular or CE) and its version.
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple: (package_name, version, is_ce)
|
||||
- package_name: Name of the installed package ('bloodhound', 'bloodhound-ce', or None)
|
||||
- version: Version string of the installed package (or None if not installed)
|
||||
- is_ce: Boolean indicating if it's the Community Edition
|
||||
"""
|
||||
import importlib.metadata
|
||||
import importlib.util
|
||||
|
||||
# First check if any BloodHound package is available to import
|
||||
if importlib.util.find_spec("bloodhound") is None:
|
||||
return None, None, False
|
||||
|
||||
# Try to get version info from both possible packages
|
||||
version = None
|
||||
package_name = None
|
||||
is_ce = False
|
||||
|
||||
# Check for bloodhound-ce first
|
||||
try:
|
||||
version = importlib.metadata.version("bloodhound-ce")
|
||||
package_name = "bloodhound-ce"
|
||||
is_ce = True
|
||||
except importlib.metadata.PackageNotFoundError:
|
||||
# Check for regular bloodhound
|
||||
try:
|
||||
version = importlib.metadata.version("bloodhound")
|
||||
package_name = "bloodhound"
|
||||
|
||||
# Even when installed as 'bloodhound', check if it's actually the CE version
|
||||
if version and ("ce" in version.lower() or "community" in version.lower()):
|
||||
is_ce = True
|
||||
except importlib.metadata.PackageNotFoundError:
|
||||
# No bloodhound package found via metadata
|
||||
pass
|
||||
|
||||
# In case we can import it but metadata is not working, check the module itself
|
||||
if not version:
|
||||
try:
|
||||
import bloodhound
|
||||
version = getattr(bloodhound, "__version__", "unknown")
|
||||
package_name = "bloodhound"
|
||||
|
||||
# Check if it's CE based on version string
|
||||
if "ce" in version.lower() or "community" in version.lower():
|
||||
is_ce = True
|
||||
package_name = "bloodhound-ce"
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
return package_name, version, is_ce
|
||||
|
||||
|
||||
def detect_if_ip(target):
|
||||
try:
|
||||
ip_address(target)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
+15
-30
@@ -30,7 +30,6 @@ import os
|
||||
import secrets
|
||||
import hashlib
|
||||
import datetime
|
||||
import logging
|
||||
import random
|
||||
import base64
|
||||
|
||||
@@ -47,8 +46,7 @@ from asn1crypto import keys
|
||||
from minikerberos.pkinit import PKINIT, DirtyDH
|
||||
from minikerberos.protocol.constants import NAME_TYPE, PaDataType
|
||||
from minikerberos.protocol.encryption import Enctype, _enctype_table, Key
|
||||
from minikerberos.protocol.asn1_structs import KDC_REQ_BODY, PrincipalName, KDCOptions, EncASRepPart, AS_REQ, PADATA_TYPE, \
|
||||
PA_PAC_REQUEST
|
||||
from minikerberos.protocol.asn1_structs import KDC_REQ_BODY, PrincipalName, KDCOptions, EncASRepPart, AS_REQ, PADATA_TYPE, PA_PAC_REQUEST
|
||||
from minikerberos.protocol.rfc4556 import PKAuthenticator, AuthPack, PA_PK_AS_REP, KDCDHKeyInfo, PA_PK_AS_REQ
|
||||
|
||||
from pyasn1.codec.der import decoder, encoder
|
||||
@@ -70,6 +68,7 @@ from minikerberos.common.ccache import CCACHE
|
||||
from impacket.krb5.ccache import CCache as impacket_CCache
|
||||
|
||||
from nxc.paths import NXC_PATH
|
||||
from nxc.logger import nxc_logger
|
||||
|
||||
|
||||
class myPKINIT(PKINIT):
|
||||
@@ -304,8 +303,8 @@ class myPKINIT(PKINIT):
|
||||
|
||||
key = Key(cipher.enctype, t_key)
|
||||
enc_data = as_rep["enc-part"]["cipher"]
|
||||
logging.info("AS-REP encryption key (you might need this later):")
|
||||
logging.info(hexlify(t_key).decode("utf-8"))
|
||||
nxc_logger.info("AS-REP encryption key (you might need this later):")
|
||||
nxc_logger.info(hexlify(t_key).decode("utf-8"))
|
||||
dec_data = cipher.decrypt(key, 3, enc_data)
|
||||
encasrep = EncASRepPart.load(dec_data).native
|
||||
cipher = _enctype_table[int(encasrep["key"]["keytype"])]
|
||||
@@ -327,34 +326,27 @@ class GETPAC:
|
||||
for _bufferN in range(pacType["cBuffers"]):
|
||||
infoBuffer = PAC_INFO_BUFFER(buff)
|
||||
data = pacType["Buffers"][infoBuffer["Offset"] - 8:][:infoBuffer["cbBufferSize"]]
|
||||
if logging.getLogger().level == logging.DEBUG:
|
||||
print("TYPE 0x%x" % infoBuffer["ulType"])
|
||||
nxc_logger.debug(f"TYPE 0x{infoBuffer['ulType']}")
|
||||
if infoBuffer["ulType"] == 2:
|
||||
found = True
|
||||
credinfo = PAC_CREDENTIAL_INFO(data)
|
||||
if logging.getLogger().level == logging.DEBUG:
|
||||
credinfo.dump()
|
||||
newCipher = _enctype_table[credinfo["EncryptionType"]]
|
||||
out = newCipher.decrypt(key, 16, credinfo["SerializedData"])
|
||||
type1 = TypeSerialization1(out)
|
||||
# I'm skipping here 4 bytes with its the ReferentID for the pointer
|
||||
newdata = out[len(type1) + 4:]
|
||||
pcc = PAC_CREDENTIAL_DATA(newdata)
|
||||
if logging.getLogger().level == logging.DEBUG:
|
||||
pcc.dump()
|
||||
for cred in pcc["Credentials"]:
|
||||
credstruct = NTLM_SUPPLEMENTAL_CREDENTIAL(b"".join(cred["Credentials"]))
|
||||
if logging.getLogger().level == logging.DEBUG:
|
||||
credstruct.dump()
|
||||
|
||||
logging.info("Recovered NT Hash")
|
||||
logging.info(hexlify(credstruct["NtPassword"]).decode("utf-8"))
|
||||
nxc_logger.info("Recovered NT Hash")
|
||||
nxc_logger.info(hexlify(credstruct["NtPassword"]).decode("utf-8"))
|
||||
nthash = hexlify(credstruct["NtPassword"]).decode("utf-8")
|
||||
|
||||
buff = buff[len(infoBuffer):]
|
||||
|
||||
if not found:
|
||||
logging.info("Did not find the PAC_CREDENTIAL_INFO in the PAC. Are you sure your TGT originated from a PKINIT operation?")
|
||||
nxc_logger.info("Did not find the PAC_CREDENTIAL_INFO in the PAC. Are you sure your TGT originated from a PKINIT operation?")
|
||||
return nthash
|
||||
|
||||
def __init__(self, username, domain, kdcHost, key, tgt):
|
||||
@@ -399,10 +391,8 @@ class GETPAC:
|
||||
authenticator["cusec"] = now.microsecond
|
||||
authenticator["ctime"] = KerberosTime.to_asn1(now)
|
||||
|
||||
if logging.getLogger().level == logging.DEBUG:
|
||||
logging.debug("AUTHENTICATOR")
|
||||
print(authenticator.prettyPrint())
|
||||
print("\n")
|
||||
nxc_logger.debug("AUTHENTICATOR")
|
||||
nxc_logger.debug(authenticator.prettyPrint() + "\n")
|
||||
|
||||
encodedAuthenticator = encoder.encode(authenticator)
|
||||
|
||||
@@ -452,23 +442,18 @@ class GETPAC:
|
||||
|
||||
myTicket = ticket.to_asn1(TicketAsn1())
|
||||
seq_set_iter(reqBody, "additional-tickets", (myTicket,))
|
||||
if logging.getLogger().level == logging.DEBUG:
|
||||
logging.debug("Final TGS")
|
||||
print(tgsReq.prettyPrint())
|
||||
if logging.getLogger().level == logging.DEBUG:
|
||||
logging.debug("Final TGS")
|
||||
print(tgsReq.prettyPrint())
|
||||
nxc_logger.debug("Final TGS")
|
||||
nxc_logger.debug(tgsReq.prettyPrint())
|
||||
|
||||
message = encoder.encode(tgsReq)
|
||||
logging.info("Requesting ticket to self with PAC")
|
||||
nxc_logger.info("Requesting ticket to self with PAC")
|
||||
|
||||
r = sendReceive(message, self.__domain, self.__kdcHost)
|
||||
|
||||
tgs = decoder.decode(r, asn1Spec=TGS_REP())[0]
|
||||
|
||||
if logging.getLogger().level == logging.DEBUG:
|
||||
logging.debug("TGS_REP")
|
||||
print(tgs.prettyPrint())
|
||||
nxc_logger.debug("TGS_REP")
|
||||
nxc_logger.debug(tgs.prettyPrint())
|
||||
|
||||
cipherText = tgs["ticket"]["enc-part"]["cipher"]
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import random
|
||||
|
||||
obfuscate_ps_scripts = False
|
||||
|
||||
|
||||
def replace_singles(s):
|
||||
"""Replaces single quotes with a double quote
|
||||
We do this because quoting is very important in PowerShell, and we are doing multiple layers:
|
||||
@@ -27,6 +28,7 @@ def replace_singles(s):
|
||||
"""
|
||||
return s.replace("'", r"\"")
|
||||
|
||||
|
||||
def get_ps_script(path):
|
||||
"""Generates a full path to a PowerShell script given a relative path.
|
||||
|
||||
@@ -56,19 +58,6 @@ def encode_ps_command(command):
|
||||
return b64encode(command.encode("UTF-16LE")).decode()
|
||||
|
||||
|
||||
def is_powershell_installed():
|
||||
"""
|
||||
Check if PowerShell is installed.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool: True if PowerShell is installed, False otherwise.
|
||||
"""
|
||||
if which("powershell"):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def obfs_ps_script(path_to_script):
|
||||
"""
|
||||
Obfuscates a PowerShell script.
|
||||
@@ -90,7 +79,7 @@ def obfs_ps_script(path_to_script):
|
||||
obfs_script_dir = os.path.join(NXC_PATH, "obfuscated_scripts")
|
||||
obfs_ps_script = os.path.join(obfs_script_dir, ps_script)
|
||||
|
||||
if is_powershell_installed() and obfuscate_ps_scripts:
|
||||
if bool(which("powershell")) and obfuscate_ps_scripts:
|
||||
if os.path.exists(obfs_ps_script):
|
||||
nxc_logger.display("Using cached obfuscated Powershell script")
|
||||
with open(obfs_ps_script) as script:
|
||||
@@ -116,12 +105,11 @@ def obfs_ps_script(path_to_script):
|
||||
and debug statements from a PowerShell source file.
|
||||
"""
|
||||
# strip block comments
|
||||
stripped_code = re.sub(re.compile("<#.*?#>", re.DOTALL), "", script.read())
|
||||
stripped_code = re.sub(re.compile(r"<#.*?#>", re.DOTALL), "", script.read())
|
||||
# strip blank lines, lines starting with #, and verbose/debug statements
|
||||
return "\n".join([line for line in stripped_code.split("\n") if ((line.strip() != "") and (not line.strip().startswith("#")) and (not line.strip().lower().startswith("write-verbose ")) and (not line.strip().lower().startswith("write-debug ")))])
|
||||
|
||||
|
||||
|
||||
def create_ps_command(ps_command, force_ps32=False, obfs=False, custom_amsi=None, encode=True):
|
||||
"""
|
||||
Generates a PowerShell command based on the provided `ps_command` parameter.
|
||||
@@ -139,7 +127,7 @@ def create_ps_command(ps_command, force_ps32=False, obfs=False, custom_amsi=None
|
||||
str: The generated PowerShell command.
|
||||
"""
|
||||
nxc_logger.debug(f"Creating PS command parameters: {ps_command=}, {force_ps32=}, {obfs=}, {custom_amsi=}, {encode=}")
|
||||
|
||||
|
||||
if custom_amsi:
|
||||
nxc_logger.debug(f"Using custom AMSI bypass script: {custom_amsi}")
|
||||
with open(custom_amsi) as file_in:
|
||||
@@ -154,7 +142,7 @@ def create_ps_command(ps_command, force_ps32=False, obfs=False, custom_amsi=None
|
||||
command = amsi_bypass + f"$functions = {{function Command-ToExecute{{{amsi_bypass + ps_command}}}}}; if ($Env:PROCESSOR_ARCHITECTURE -eq 'AMD64'){{$job = Start-Job -InitializationScript $functions -ScriptBlock {{Command-ToExecute}} -RunAs32; $job | Wait-Job | Receive-Job }} else {{IEX '$functions'; Command-ToExecute}}"
|
||||
else:
|
||||
command = f"{amsi_bypass} {ps_command}"
|
||||
|
||||
|
||||
nxc_logger.debug(f"Generated PS command:\n {command}\n")
|
||||
|
||||
if obfs:
|
||||
@@ -163,7 +151,7 @@ def create_ps_command(ps_command, force_ps32=False, obfs=False, custom_amsi=None
|
||||
while True:
|
||||
nxc_logger.debug(f"Obfuscation attempt: {obfs_attempts + 1}")
|
||||
obfs_command = invoke_obfuscation(command)
|
||||
|
||||
|
||||
command = f'powershell.exe -exec bypass -noni -nop -w 1 -C "{replace_singles(obfs_command)}"'
|
||||
if len(command) <= 8191:
|
||||
break
|
||||
@@ -176,11 +164,11 @@ def create_ps_command(ps_command, force_ps32=False, obfs=False, custom_amsi=None
|
||||
# if we arent encoding or obfuscating anything, we quote the entire powershell in double quotes, otherwise the final powershell command will syntax error
|
||||
command = f"-enc {encode_ps_command(command)}" if encode else f'"{command}"'
|
||||
command = f"powershell.exe -noni -nop -w 1 {command}"
|
||||
|
||||
|
||||
if len(command) > 8191:
|
||||
nxc_logger.error(f"Command exceeds maximum length of 8191 chars (was {len(command)}). exiting.")
|
||||
exit(1)
|
||||
|
||||
|
||||
nxc_logger.debug(f"Final command: {command}")
|
||||
return command
|
||||
|
||||
@@ -429,4 +417,3 @@ def invoke_obfuscation(script_string):
|
||||
obfuscated_script = choice(invoke_options)
|
||||
nxc_logger.debug(f"Script after obfuscation: {obfuscated_script}")
|
||||
return obfuscated_script
|
||||
|
||||
|
||||
@@ -46,9 +46,7 @@ class ModuleLoader:
|
||||
self.logger.fail(f"{module_path} missing the on_login/on_admin_login function(s)")
|
||||
module_error = True
|
||||
|
||||
if module_error:
|
||||
return False
|
||||
return True
|
||||
return not module_error
|
||||
|
||||
def load_module(self, module_path):
|
||||
"""Load a module, initializing it and checking that it has the proper attributes"""
|
||||
|
||||
+1
-1
@@ -103,7 +103,7 @@ class NXCAdapter(logging.LoggerAdapter):
|
||||
logging.getLogger("dploot").disabled = True
|
||||
logging.getLogger("neo4j").setLevel(logging.ERROR)
|
||||
|
||||
def format(self, msg, *args, **kwargs): # noqa: A003
|
||||
def format(self, msg, *args, **kwargs):
|
||||
"""Format msg for output
|
||||
|
||||
This is used instead of process() since process() applies to _all_ messages, including debug calls
|
||||
|
||||
+5
-2
@@ -27,7 +27,7 @@ class NXCModule:
|
||||
SERVER PKI Enrollment Server to enumerate templates for. Default is None, use CN name
|
||||
BASE_DN The base domain name for the LDAP query
|
||||
"""
|
||||
self.regex = re.compile("(https?://.+)")
|
||||
self.regex = re.compile(r"(https?://.+)")
|
||||
|
||||
self.server = None
|
||||
self.base_dn = None
|
||||
@@ -70,7 +70,10 @@ class NXCModule:
|
||||
searchBase="CN=Configuration," + base_dn_root,
|
||||
)
|
||||
except LDAPSearchError as e:
|
||||
context.log.fail(f"Obtained unexpected exception: {e}")
|
||||
if "noSuchObject" in str(e):
|
||||
context.log.fail("No ADCS infrastructure found.")
|
||||
else:
|
||||
context.log.fail(f"Obtained unexpected exception: {e}")
|
||||
|
||||
def process_servers(self, item):
|
||||
"""Function that is called to process the items obtain by the LDAP search when listing PKI Enrollment Servers."""
|
||||
|
||||
@@ -4,6 +4,7 @@ import sys
|
||||
from impacket.dcerpc.v5 import samr, epm, transport
|
||||
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
|
||||
|
||||
|
||||
class NXCModule:
|
||||
"""
|
||||
Module by CyberCelt: @Cyb3rC3lt
|
||||
@@ -88,7 +89,6 @@ class NXCModule:
|
||||
if not self.noLDAPRequired:
|
||||
self.do_ldaps_add(connection, context)
|
||||
|
||||
|
||||
def do_samr_add(self, context):
|
||||
"""
|
||||
Connects to a target server and performs various operations related to adding or deleting machine accounts.
|
||||
|
||||
@@ -9,6 +9,7 @@ from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
|
||||
|
||||
from nxc.paths import NXC_PATH
|
||||
|
||||
|
||||
class NXCModule:
|
||||
name = "backup_operator"
|
||||
description = "Exploit user in backup operator group to dump NTDS @mpgn_x64"
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
from impacket.ldap import ldaptypes
|
||||
from nxc.parsers.ldap_results import parse_result_attributes
|
||||
from ldap3.protocol.microsoft import security_descriptor_control
|
||||
|
||||
RELEVANT_OBJECT_TYPES = {
|
||||
"00000000-0000-0000-0000-000000000000": "All Objects",
|
||||
"0feb936f-47b3-49f2-9386-1dedc2c23765": "msDS-DelegatedManagedServiceAccount",
|
||||
}
|
||||
|
||||
EXCLUDED_SIDS_SUFFIXES = ["-512", "-519"] # Domain Admins, Enterprise Admins
|
||||
EXCLUDED_SIDS = ["S-1-5-32-544", "S-1-5-18"] # Builtin Administrators, Local SYSTEM
|
||||
|
||||
# Define all access rights
|
||||
ACCESS_RIGHTS = {
|
||||
# Generic Rights
|
||||
"GenericRead": 0x80000000, # ADS_RIGHT_GENERIC_READ
|
||||
"GenericWrite": 0x40000000, # ADS_RIGHT_GENERIC_WRITE
|
||||
"GenericExecute": 0x20000000, # ADS_RIGHT_GENERIC_EXECUTE
|
||||
"GenericAll": 0x10000000, # ADS_RIGHT_GENERIC_ALL
|
||||
|
||||
# Maximum Allowed access type
|
||||
"MaximumAllowed": 0x02000000,
|
||||
|
||||
# Access System Acl access type
|
||||
"AccessSystemSecurity": 0x01000000, # ADS_RIGHT_ACCESS_SYSTEM_SECURITY
|
||||
|
||||
# Standard access types
|
||||
"Synchronize": 0x00100000, # ADS_RIGHT_SYNCHRONIZE
|
||||
"WriteOwner": 0x00080000, # ADS_RIGHT_WRITE_OWNER
|
||||
"WriteDACL": 0x00040000, # ADS_RIGHT_WRITE_DAC
|
||||
"ReadControl": 0x00020000, # ADS_RIGHT_READ_CONTROL
|
||||
"Delete": 0x00010000, # ADS_RIGHT_DELETE
|
||||
|
||||
# Specific rights
|
||||
"AllExtendedRights": 0x00000100, # ADS_RIGHT_DS_CONTROL_ACCESS
|
||||
"ListObject": 0x00000080, # ADS_RIGHT_DS_LIST_OBJECT
|
||||
"DeleteTree": 0x00000040, # ADS_RIGHT_DS_DELETE_TREE
|
||||
"WriteProperties": 0x00000020, # ADS_RIGHT_DS_WRITE_PROP
|
||||
"ReadProperties": 0x00000010, # ADS_RIGHT_DS_READ_PROP
|
||||
"Self": 0x00000008, # ADS_RIGHT_DS_SELF
|
||||
"ListChildObjects": 0x00000004, # ADS_RIGHT_ACTRL_DS_LIST
|
||||
"DeleteChild": 0x00000002, # ADS_RIGHT_DS_DELETE_CHILD
|
||||
"CreateChild": 0x00000001, # ADS_RIGHT_DS_CREATE_CHILD
|
||||
}
|
||||
|
||||
# Define which rights are considered relevant for potential abuse
|
||||
RELEVANT_RIGHTS = {
|
||||
"GenericAll": ACCESS_RIGHTS["GenericAll"],
|
||||
"GenericWrite": ACCESS_RIGHTS["GenericWrite"],
|
||||
"WriteOwner": ACCESS_RIGHTS["WriteOwner"],
|
||||
"WriteDACL": ACCESS_RIGHTS["WriteDACL"],
|
||||
"CreateChild": ACCESS_RIGHTS["CreateChild"],
|
||||
"WriteProperties": ACCESS_RIGHTS["WriteProperties"],
|
||||
"AllExtendedRights": ACCESS_RIGHTS["AllExtendedRights"]
|
||||
}
|
||||
|
||||
FUNCTIONAL_LEVELS = {
|
||||
"Windows 2000": 0,
|
||||
"Windows Server 2003": 1,
|
||||
"Windows Server 2003 R2": 2,
|
||||
"Windows Server 2008": 3,
|
||||
"Windows Server 2008 R2": 4,
|
||||
"Windows Server 2012": 5,
|
||||
"Windows Server 2012 R2": 6,
|
||||
"Windows Server 2016": 7,
|
||||
"Windows Server 2019": 8,
|
||||
"Windows Server 2022": 9,
|
||||
"Windows Server 2025": 10,
|
||||
}
|
||||
|
||||
|
||||
class NXCModule:
|
||||
"""
|
||||
-------
|
||||
Module by @mpgn based on https://www.akamai.com/blog/security-research/abusing-dmsa-for-privilege-escalation-in-active-directory#credentials
|
||||
and https://raw.githubusercontent.com/akamai/BadSuccessor/refs/heads/main/Get-BadSuccessorOUPermissions.ps1
|
||||
"""
|
||||
|
||||
name = "badsuccessor"
|
||||
description = "Check if vulnerable to bad successor attack (DMSA)"
|
||||
supported_protocols = ["ldap"]
|
||||
opsec_safe = True
|
||||
multiple_hosts = True
|
||||
|
||||
def __init__(self):
|
||||
self.context = None
|
||||
self.module_options = None
|
||||
|
||||
def options(self, context, module_options):
|
||||
"""No options available"""
|
||||
|
||||
def is_excluded_sid(self, sid, domain_sid):
|
||||
if sid in EXCLUDED_SIDS:
|
||||
return True
|
||||
return any(sid.startswith(domain_sid) and sid.endswith(suffix) for suffix in EXCLUDED_SIDS_SUFFIXES)
|
||||
|
||||
def get_domain_sid(self, ldap_session, base_dn):
|
||||
"""Retrieve the domain SID from the domain object in LDAP"""
|
||||
r = ldap_session.search(
|
||||
searchBase=base_dn,
|
||||
searchFilter="(objectClass=domain)",
|
||||
attributes=["objectSid"]
|
||||
)
|
||||
parsed = parse_result_attributes(r)
|
||||
if parsed and "objectSid" in parsed[0]:
|
||||
return parsed[0]["objectSid"]
|
||||
|
||||
def find_bad_successor_ous(self, ldap_session, entries, base_dn):
|
||||
domain_sid = self.get_domain_sid(ldap_session, base_dn)
|
||||
results = {}
|
||||
parsed = parse_result_attributes(entries)
|
||||
for entry in parsed:
|
||||
dn = entry["distinguishedName"]
|
||||
sd_data = entry["nTSecurityDescriptor"]
|
||||
sd = ldaptypes.SR_SECURITY_DESCRIPTOR(data=sd_data)
|
||||
|
||||
for ace in sd["Dacl"]["Data"]:
|
||||
if ace["AceType"] != ldaptypes.ACCESS_ALLOWED_ACE.ACE_TYPE:
|
||||
continue
|
||||
|
||||
has_relevant_right = False
|
||||
mask = int(ace["Ace"]["Mask"]["Mask"])
|
||||
for right_value in RELEVANT_RIGHTS.values():
|
||||
if mask & right_value:
|
||||
has_relevant_right = True
|
||||
break
|
||||
|
||||
if not has_relevant_right:
|
||||
continue # Skip this ACE if it doesn't have any relevant rights
|
||||
|
||||
object_type = getattr(ace, "ObjectType", None)
|
||||
if object_type:
|
||||
object_guid = ldaptypes.bin_to_string(object_type).lower()
|
||||
if object_guid not in RELEVANT_OBJECT_TYPES:
|
||||
continue
|
||||
|
||||
sid = ace["Ace"]["Sid"].formatCanonical()
|
||||
if self.is_excluded_sid(sid, domain_sid):
|
||||
continue
|
||||
|
||||
results.setdefault(sid, []).append(dn)
|
||||
|
||||
if hasattr(sd, "OwnerSid"):
|
||||
owner_sid = str(sd["OwnerSid"])
|
||||
if not self.is_excluded_sid(owner_sid, domain_sid):
|
||||
results.setdefault(owner_sid, []).append(dn)
|
||||
return results
|
||||
|
||||
def resolve_sid_to_name(self, ldap_session, sid, base_dn):
|
||||
"""
|
||||
Resolves a SID to a samAccountName using LDAP
|
||||
|
||||
Args:
|
||||
----
|
||||
ldap_session: The LDAP connection
|
||||
sid: The SID to resolve
|
||||
base_dn: The base DN for the LDAP search
|
||||
|
||||
Returns:
|
||||
-------
|
||||
str: The samAccountName if found, otherwise the original SID
|
||||
"""
|
||||
try:
|
||||
search_filter = f"(objectSid={sid})"
|
||||
response = ldap_session.search(
|
||||
searchBase=base_dn,
|
||||
searchFilter=search_filter,
|
||||
attributes=["sAMAccountName"]
|
||||
)
|
||||
|
||||
parsed = parse_result_attributes(response)
|
||||
if parsed and "sAMAccountName" in parsed[0]:
|
||||
return parsed[0]["sAMAccountName"]
|
||||
return sid
|
||||
except Exception:
|
||||
return sid
|
||||
|
||||
def on_login(self, context, connection):
|
||||
# Check for a domain controller with Windows Server 2025
|
||||
resp = connection.ldap_connection.search(
|
||||
searchBase=connection.ldap_connection._baseDN,
|
||||
searchFilter="(&(objectCategory=computer)(primaryGroupId=516))",
|
||||
attributes=["operatingSystem", "dNSHostName"]
|
||||
)
|
||||
parsed_resp = parse_result_attributes(resp)
|
||||
|
||||
for dc in parsed_resp:
|
||||
if "2025" in dc["operatingSystem"]:
|
||||
out = connection.resolver(dc["dNSHostName"])
|
||||
dc_ip = out[0] if out else "Unknown IP"
|
||||
context.log.success(f"Found domain controller with operating system Windows Server 2025: {dc_ip} ({dc['dNSHostName']})")
|
||||
else:
|
||||
context.log.fail("No domain controller with operating system Windows Server 2025 found, attack not possible. Enumerate dMSA objects anyway.")
|
||||
|
||||
# Enumerate dMSA objects
|
||||
controls = security_descriptor_control(sdflags=0x07) # OWNER_SECURITY_INFORMATION
|
||||
resp = connection.ldap_connection.search(
|
||||
searchBase=connection.ldap_connection._baseDN,
|
||||
searchFilter="(objectClass=organizationalUnit)",
|
||||
attributes=["distinguishedName", "nTSecurityDescriptor"],
|
||||
searchControls=controls) # Fixed parameter name
|
||||
|
||||
context.log.debug(f"Found {len(resp)} entries")
|
||||
|
||||
results = self.find_bad_successor_ous(connection.ldap_connection, resp, connection.ldap_connection._baseDN)
|
||||
|
||||
if results:
|
||||
context.log.success(f"Found {len(results)} results")
|
||||
else:
|
||||
context.log.highlight("No account found")
|
||||
|
||||
for sid, ous in results.items():
|
||||
samaccountname = self.resolve_sid_to_name(
|
||||
connection.ldap_connection,
|
||||
sid,
|
||||
connection.ldap_connection._baseDN
|
||||
)
|
||||
|
||||
for ou in ous:
|
||||
if sid == samaccountname:
|
||||
context.log.highlight(f"{sid}, {ou}")
|
||||
else:
|
||||
context.log.highlight(f"{samaccountname} ({sid}), {ou}")
|
||||
@@ -4,6 +4,7 @@ from impacket.dcerpc.v5.dtypes import NULL
|
||||
from impacket.dcerpc.v5.dcomrt import DCOMConnection
|
||||
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_PKT_PRIVACY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
name = "bitlocker"
|
||||
description = "Enumerating BitLocker Status on target(s) If it is enabled or disabled."
|
||||
|
||||
+16
-22
@@ -221,7 +221,7 @@ class ShadowCoerceTrigger:
|
||||
def connect(self, username, password, domain, lmhash, nthash, aesKey, target, doKerberos, dcHost, pipe):
|
||||
binding_params = {
|
||||
"Fssagentrpc": {
|
||||
"stringBinding": r"ncacn_np:%s[\PIPE\Fssagentrpc]" % target,
|
||||
"stringBinding": rf"ncacn_np:{target}[\PIPE\Fssagentrpc]",
|
||||
"MSRPC_UUID_FSRVP": ("a8e0653c-2744-4389-a61d-7373df8b2292", "3.0"),
|
||||
},
|
||||
}
|
||||
@@ -338,7 +338,7 @@ class DFSCoerceTrigger:
|
||||
def connect(self, username, password, domain, lmhash, nthash, aesKey, target, doKerberos, dcHost, pipe):
|
||||
binding_params = {
|
||||
"netdfs": {
|
||||
"stringBinding": r"ncacn_np:%s[\PIPE\netdfs]" % target,
|
||||
"stringBinding": rf"ncacn_np:{target}[\PIPE\netdfs]",
|
||||
"MSRPC_UUID_DFSNM": ("4fc742e0-4a10-11cf-8273-00aa004ae673", "3.0"),
|
||||
},
|
||||
}
|
||||
@@ -509,23 +509,23 @@ class PetitPotamtTrigger:
|
||||
def connect(self, username, password, domain, lmhash, nthash, aesKey, target, doKerberos, dcHost, pipe):
|
||||
binding_params = {
|
||||
"lsarpc": {
|
||||
"stringBinding": r"ncacn_np:%s[\PIPE\lsarpc]" % target,
|
||||
"stringBinding": rf"ncacn_np:{target}[\PIPE\lsarpc]",
|
||||
"MSRPC_UUID_EFSR": ("c681d488-d850-11d0-8c52-00c04fd90f7e", "1.0"),
|
||||
},
|
||||
"efsrpc": {
|
||||
"stringBinding": r"ncacn_np:%s[\PIPE\efsrpc]" % target,
|
||||
"stringBinding": rf"ncacn_np:{target}[\PIPE\efsrpc]",
|
||||
"MSRPC_UUID_EFSR": ("df1941c5-fe89-4e79-bf10-463657acf44d", "1.0"),
|
||||
},
|
||||
"samr": {
|
||||
"stringBinding": r"ncacn_np:%s[\PIPE\samr]" % target,
|
||||
"stringBinding": rf"ncacn_np:{target}[\PIPE\samr]",
|
||||
"MSRPC_UUID_EFSR": ("c681d488-d850-11d0-8c52-00c04fd90f7e", "1.0"),
|
||||
},
|
||||
"lsass": {
|
||||
"stringBinding": r"ncacn_np:%s[\PIPE\lsass]" % target,
|
||||
"stringBinding": rf"ncacn_np:{target}[\PIPE\lsass]",
|
||||
"MSRPC_UUID_EFSR": ("c681d488-d850-11d0-8c52-00c04fd90f7e", "1.0"),
|
||||
},
|
||||
"netlogon": {
|
||||
"stringBinding": r"ncacn_np:%s[\PIPE\netlogon]" % target,
|
||||
"stringBinding": rf"ncacn_np:{target}[\PIPE\netlogon]",
|
||||
"MSRPC_UUID_EFSR": ("c681d488-d850-11d0-8c52-00c04fd90f7e", "1.0"),
|
||||
},
|
||||
}
|
||||
@@ -758,17 +758,15 @@ class PrinterBugTrigger:
|
||||
self.context = context
|
||||
|
||||
def get_dynamic_endpoint(self, interface: bytes, target: str, timeout: int = 5) -> str:
|
||||
string_binding = r"ncacn_ip_tcp:%s[135]" % target
|
||||
string_binding = rf"ncacn_ip_tcp:{target}[135]"
|
||||
rpctransport = transport.DCERPCTransportFactory(string_binding)
|
||||
rpctransport.set_connect_timeout(timeout)
|
||||
dce = rpctransport.get_dce_rpc()
|
||||
self.context.log.debug(
|
||||
"Trying to resolve dynamic endpoint %s" % repr(uuid.bin_to_string(interface))
|
||||
)
|
||||
self.context.log.debug(f"Trying to resolve dynamic endpoint {uuid.bin_to_string(interface)!r}")
|
||||
try:
|
||||
dce.connect()
|
||||
except Exception as e:
|
||||
self.context.log.warning("Failed to connect to endpoint mapper: %s" % e)
|
||||
self.context.log.warning(f"Failed to connect to endpoint mapper: {e}")
|
||||
raise e
|
||||
try:
|
||||
endpoint = epm.hept_map(target, interface, protocol="ncacn_ip_tcp", dce=dce)
|
||||
@@ -777,17 +775,13 @@ class PrinterBugTrigger:
|
||||
)
|
||||
return endpoint
|
||||
except Exception as e:
|
||||
self.context.log.debug(
|
||||
"Failed to resolve dynamic endpoint %s"
|
||||
% repr(uuid.bin_to_string(interface))
|
||||
)
|
||||
self.context.log.debug(f"Failed to resolve dynamic endpoint {uuid.bin_to_string(interface)!r}")
|
||||
raise e
|
||||
|
||||
|
||||
def connect(self, username, password, domain, lmhash, nthash, aesKey, target, doKerberos, dcHost, pipe):
|
||||
binding_params = {
|
||||
"spoolss": {
|
||||
"stringBinding": r"ncacn_np:%s[\PIPE\spoolss]" % target,
|
||||
"stringBinding": rf"ncacn_np:{target}[\PIPE\spoolss]",
|
||||
"MSRPC_UUID_RPRN": ("12345678-1234-abcd-ef00-0123456789ab", "1.0"),
|
||||
"port": 445
|
||||
},
|
||||
@@ -835,7 +829,7 @@ class PrinterBugTrigger:
|
||||
|
||||
def exploit(self, dce, listener, target, always_continue, pipe):
|
||||
try:
|
||||
resp = rprn.hRpcOpenPrinter(dce, "\\\\%s\x00" % target)
|
||||
resp = rprn.hRpcOpenPrinter(dce, f"\\\\{target}\x00")
|
||||
except Exception as e:
|
||||
if str(e).find("Broken pipe") >= 0:
|
||||
# The connection timed-out. Let's try to bring it back next round
|
||||
@@ -853,7 +847,7 @@ class PrinterBugTrigger:
|
||||
request = rprn.RpcRemoteFindFirstPrinterChangeNotificationEx()
|
||||
request["hPrinter"] = resp["pHandle"]
|
||||
request["fdwFlags"] = rprn.PRINTER_CHANGE_ADD_JOB
|
||||
request["pszLocalMachine"] = "\\\\%s\x00" % listener
|
||||
request["pszLocalMachine"] = f"\\\\{listener}\x00"
|
||||
request["fdwOptions"] = 0x00000000
|
||||
request["dwPrinterLocal"] = 0
|
||||
dce.request(request)
|
||||
@@ -885,7 +879,7 @@ class PrinterBugTrigger:
|
||||
request = RpcRemoteFindFirstPrinterChangeNotification()
|
||||
request["hPrinter"] = resp["pHandle"]
|
||||
request["fdwFlags"] = rprn.PRINTER_CHANGE_ADD_JOB
|
||||
request["pszLocalMachine"] = "\\\\%s\x00" % listener
|
||||
request["pszLocalMachine"] = f"\\\\{listener}\x00"
|
||||
request["fdwOptions"] = 0x00000000
|
||||
request["dwPrinterLocal"] = 0
|
||||
request["cbBuffer"] = NULL
|
||||
@@ -908,7 +902,7 @@ class MSEvenTrigger:
|
||||
def connect(self, username, password, domain, lmhash, nthash, aesKey, target, doKerberos, dcHost, pipe):
|
||||
binding_params = {
|
||||
"eventlog": {
|
||||
"stringBinding": r"ncacn_np:%s[\PIPE\eventlog]" % target,
|
||||
"stringBinding": rf"ncacn_np:{target}[\PIPE\eventlog]",
|
||||
"MSRPC_UUID_EVEN": ("82273fdc-e32a-18c3-3f78-827929dc23ea", "0.0"),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -429,11 +429,9 @@ class NXCModule:
|
||||
def parse_dacl(self, context, dacl):
|
||||
parsed_dacl = []
|
||||
context.log.debug("Parsing DACL")
|
||||
i = 0
|
||||
for ace in dacl["Data"]:
|
||||
parsed_ace = self.parse_ace(context, ace)
|
||||
parsed_dacl.append(parsed_ace)
|
||||
i += 1
|
||||
return parsed_dacl
|
||||
|
||||
# Parses an access mask to extract the different values from a simple permission
|
||||
@@ -509,11 +507,10 @@ class NXCModule:
|
||||
parsed_dacl : a parsed DACL from parse_dacl()
|
||||
"""
|
||||
context.log.debug("Printing parsed DACL")
|
||||
i = 0
|
||||
# If a specific right or a specific GUID has been specified, only the ACE with this right will be printed
|
||||
# If an ACE type has been specified, only the ACE with this type will be specified
|
||||
# If a principal has been specified, only the ACE where he is the trustee will be printed
|
||||
for parsed_ace in parsed_dacl:
|
||||
for i, parsed_ace in enumerate(parsed_dacl):
|
||||
print_ace = True
|
||||
context.log.debug(f"{parsed_ace=}, {self.rights=}, {self.rights_guid=}, {self.ace_type=}, {self.principal_sid=}")
|
||||
|
||||
@@ -561,16 +558,15 @@ class NXCModule:
|
||||
except Exception as 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.context.log.highlight(f"ACE[{i}] info")
|
||||
self.print_parsed_ace(parsed_ace)
|
||||
i += 1
|
||||
|
||||
# Prints properly a parsed ACE
|
||||
# - parsed_ace : a parsed ACE from parse_ace()
|
||||
def print_parsed_ace(self, parsed_ace):
|
||||
elements_name = list(parsed_ace.keys())
|
||||
for attribute in elements_name:
|
||||
self.context.log.highlight(" %-26s: %s" % (attribute, parsed_ace[attribute]))
|
||||
self.context.log.highlight(f"\t{attribute:<26}: {parsed_ace[attribute]}")
|
||||
|
||||
# Retrieves the GUIDs for the specified rights
|
||||
def build_guids_for_rights(self):
|
||||
|
||||
@@ -5,6 +5,7 @@ from nxc.protocols.smb.dpapi import upgrade_to_dploot_connection
|
||||
|
||||
# Based on dpapimk2john, original work by @fist0urs
|
||||
|
||||
|
||||
class NXCModule:
|
||||
name = "dpapi_hash"
|
||||
description = "Remotely dump Dpapi hash based on masterkeys"
|
||||
|
||||
@@ -33,7 +33,7 @@ class NXCModule:
|
||||
obfuscate = "OBFUSCATE" in module_options
|
||||
# we can use commands instead of backslashes - this is because Linux and OSX treat them differently
|
||||
default_obfuscation = "Token,All,1"
|
||||
obfuscate_cmd = module_options["OBFUSCATE_CMD"] if "OBFUSCATE_CMD" in module_options else default_obfuscation
|
||||
obfuscate_cmd = module_options.get("OBFUSCATE_CMD", default_obfuscation)
|
||||
context.log.debug(f"Obfuscate: {obfuscate} - Obfuscate_cmd: {obfuscate_cmd}")
|
||||
|
||||
# Pull the host and port from the config file
|
||||
|
||||
@@ -61,7 +61,7 @@ class NXCModule:
|
||||
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]:
|
||||
elif self.__port == 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)
|
||||
@@ -86,7 +86,7 @@ class NXCModule:
|
||||
|
||||
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)
|
||||
context.log.debug(f"EXEs {exename}")
|
||||
if exename == "certsrv.exe":
|
||||
context.log.highlight("Active Directory Certificate Services Found.")
|
||||
url = f"http://{connection.host}/certsrv/certfnsh.asp"
|
||||
|
||||
@@ -50,7 +50,7 @@ class NXCModule:
|
||||
rname = text.split(" ")[0]
|
||||
rtype = text.split(" ")[2]
|
||||
rvalue = " ".join(text.split(" ")[3:])
|
||||
if domain_data.get(rtype, False):
|
||||
if domain_data.get(rtype):
|
||||
domain_data[rtype].append(f"{rname}: {rvalue}")
|
||||
else:
|
||||
domain_data[rtype] = [f"{rname}: {rvalue}"]
|
||||
|
||||
@@ -42,5 +42,6 @@ class NXCModule:
|
||||
"""
|
||||
res = self.mssql_conn.sql_query(query)
|
||||
return [user["name"] for user in res] if res else []
|
||||
|
||||
def options(self, context, module_options):
|
||||
pass
|
||||
|
||||
@@ -36,5 +36,6 @@ class NXCModule:
|
||||
query = "SELECT name FROM sys.server_principals WHERE type_desc = 'SQL_LOGIN';"
|
||||
res = self.mssql_conn.sql_query(query)
|
||||
return [login["name"] for login in res] if res else []
|
||||
|
||||
def options(self, context, module_options):
|
||||
pass
|
||||
|
||||
@@ -20,13 +20,13 @@ class NXCModule:
|
||||
self.context = None
|
||||
self.module_options = None
|
||||
self.method = "execute"
|
||||
self.limit = 1000
|
||||
self.limit = None
|
||||
|
||||
def options(self, context, module_options):
|
||||
"""
|
||||
METHOD EventLog method (Execute or RPCCALL)
|
||||
METHOD EventLog method (Execute or RPCCALL), default: execute
|
||||
M Alias for METHOD
|
||||
LIMIT Limit of the number of records to be fetched
|
||||
LIMIT Limit of the number of records to be fetched, default: unlimited
|
||||
L Alias for LIMIT
|
||||
"""
|
||||
if "METHOD" in module_options:
|
||||
@@ -41,8 +41,6 @@ class NXCModule:
|
||||
def find_credentials(self, content, context):
|
||||
# remove unnecessary words
|
||||
content = content.replace("\r\n", "\n")
|
||||
content = content.replace("/add", "")
|
||||
content = content.replace("/active:yes", "")
|
||||
|
||||
# sort and unique lines
|
||||
content = "\n".join(sorted(set(content.split("\n"))))
|
||||
@@ -66,9 +64,16 @@ class NXCModule:
|
||||
# Extracting credentials
|
||||
for line in content.split("\n"):
|
||||
for reg in regexps:
|
||||
# verbose context.log.debug("Line: " + line)
|
||||
# verbose context.log.debug("Reg: " + reg)
|
||||
match = re.search(reg, line, re.IGNORECASE)
|
||||
# Remove unnecessary words
|
||||
line_stripped = line.replace("/add", "") \
|
||||
.replace("/active:yes", "") \
|
||||
.replace("/delete", "") \
|
||||
.replace("/domain", "") \
|
||||
# Remove command lines that were executed with nxc
|
||||
line_stripped = re.sub(r"1> \\Windows\\Temp\\[\w]{6} 2>&1", "", line_stripped)
|
||||
|
||||
# Use regex to find credentials
|
||||
match = re.search(reg, line_stripped, re.IGNORECASE)
|
||||
if match:
|
||||
# eleminate false positives
|
||||
# C:\Windows\system32\svchost.exe -k DcomLaunch -p -s PlugPlay
|
||||
@@ -92,11 +97,12 @@ class NXCModule:
|
||||
|
||||
def on_admin_login(self, context, connection):
|
||||
content = ""
|
||||
if self.method[:1].lower() == "e":
|
||||
if self.method.lower().startswith("e"):
|
||||
limit_str = f"/c:{self.limit}" if self.limit is not None else ""
|
||||
# https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-10/security/threat-protection/auditing/event-4688
|
||||
commands = [
|
||||
f'wevtutil qe Security /c:{self.limit} /f:text /rd:true /q:"*[System[(EventID=4688)]]" |findstr "Command Line"',
|
||||
f'wevtutil qe Microsoft-Windows-Sysmon/Operational /c:{self.limit} /f:text /rd:true /q:"*[System[(EventID=1)]]" |findstr "ParentCommandLine"'
|
||||
f'wevtutil qe Microsoft-Windows-Sysmon/Operational {limit_str} /f:text /rd:true /q:"*[System[(EventID=1)]]" | findstr "ParentCommandLine"',
|
||||
f'wevtutil qe Security {limit_str} /f:text /rd:true /q:"*[System[(EventID=4688)]]" | findstr "Command Line"',
|
||||
]
|
||||
for command in commands:
|
||||
context.log.debug("Execute Command: " + command)
|
||||
@@ -127,7 +133,6 @@ class NXCModule:
|
||||
content += "CommandLine: " + match.group("CommandLine") + "\n"
|
||||
except Exception as e:
|
||||
context.log.error(f"Error: {e}")
|
||||
continue
|
||||
|
||||
self.find_credentials(content, context)
|
||||
|
||||
@@ -182,7 +187,7 @@ class MSEven6Trigger:
|
||||
|
||||
|
||||
class MSEven6Result:
|
||||
def __init__(self, conn, handle, limit):
|
||||
def __init__(self, conn, handle, limit=None):
|
||||
self._conn = conn
|
||||
self._handle = handle
|
||||
self._hardlimit = limit
|
||||
@@ -192,11 +197,12 @@ class MSEven6Result:
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
self._hardlimit -= 1
|
||||
if self._hardlimit < 0:
|
||||
raise StopIteration
|
||||
if self._hardlimit is not None:
|
||||
self._hardlimit -= 1
|
||||
if self._hardlimit < 0:
|
||||
raise StopIteration
|
||||
if self._resp is not None and self._resp["NumActualRecords"] == 0:
|
||||
return None
|
||||
raise StopIteration
|
||||
|
||||
if self._resp is None or self._index == self._resp["NumActualRecords"]:
|
||||
req = even6.EvtRpcQueryNext()
|
||||
|
||||
@@ -32,10 +32,8 @@ def get_dns_resolver(server, context):
|
||||
# Is our host an IP? In that case make sure the server IP is used
|
||||
# if not assume lookups are working already
|
||||
try:
|
||||
if server.startswith("ldap://"):
|
||||
server = server[7:]
|
||||
if server.startswith("ldaps://"):
|
||||
server = server[8:]
|
||||
server = server.removeprefix("ldap://")
|
||||
server = server.removeprefix("ldaps://")
|
||||
socket.inet_aton(server)
|
||||
dnsresolver.nameservers = [server]
|
||||
except OSError:
|
||||
@@ -44,7 +42,7 @@ def get_dns_resolver(server, context):
|
||||
|
||||
|
||||
def ldap2domain(ldap):
|
||||
return re.sub(",DC=", ".", ldap[ldap.lower().find("dc="):], flags=re.I)[3:]
|
||||
return re.sub(r",DC=", ".", ldap[ldap.lower().find("dc="):], flags=re.IGNORECASE)[3:]
|
||||
|
||||
|
||||
def new_record(rtype, serial):
|
||||
|
||||
@@ -4,7 +4,7 @@ from time import sleep
|
||||
from csv import reader
|
||||
from base64 import b64encode
|
||||
from io import BytesIO, StringIO
|
||||
from xml.etree import ElementTree
|
||||
from xml.etree import ElementTree as ET
|
||||
from nxc.helpers.powershell import get_ps_script
|
||||
|
||||
|
||||
@@ -358,7 +358,7 @@ class NXCModule:
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
keepass_config_xml_root = ElementTree.fromstring(buffer.getvalue())
|
||||
keepass_config_xml_root = ET.fromstring(buffer.getvalue())
|
||||
except Exception as e:
|
||||
context.log.fail(f"Error while parsing file '{self.keepass_config_path}', exiting: {e}")
|
||||
sys.exit(1)
|
||||
@@ -377,7 +377,7 @@ class NXCModule:
|
||||
|
||||
def extract_password(self, context):
|
||||
xml_doc_path = os.path.abspath(self.local_export_path + "/" + self.export_name)
|
||||
xml_tree = ElementTree.parse(xml_doc_path)
|
||||
xml_tree = ET.parse(xml_doc_path)
|
||||
root = xml_tree.getroot()
|
||||
|
||||
root_entries = root.find("./Root/Entry")
|
||||
|
||||
@@ -112,7 +112,6 @@ class NXCModule:
|
||||
context.log.fail(f"Exception in run_ldaps_withEPA: {e}")
|
||||
return None
|
||||
|
||||
|
||||
# Domain Controllers do not have a certificate setup for
|
||||
# LDAPS on port 636 by default. If this has not been setup,
|
||||
# the TLS handshake will hang and you will not be able to
|
||||
|
||||
@@ -98,6 +98,4 @@ class NXCModule:
|
||||
result = self.mssql_conn.sql_query(query)
|
||||
# Assuming the query returns a list of dictionaries with 'config_value' as the key
|
||||
self.context.log.debug(f"{option} check result: {result}")
|
||||
if result and result[0]["config_value"] == 1:
|
||||
return True
|
||||
return False
|
||||
return bool(result and result[0]["config_value"] == 1)
|
||||
|
||||
@@ -16,6 +16,7 @@ class MRemoteNgEncryptionAttributes:
|
||||
encryption_engine: str
|
||||
full_file_encryption: bool
|
||||
|
||||
|
||||
class NXCModule:
|
||||
"""
|
||||
Dump mRemoteNG Passwords
|
||||
@@ -181,7 +182,6 @@ class NXCModule:
|
||||
content = conn.readFile(self.context.share, new_path)
|
||||
self.handle_confCons_file(content)
|
||||
|
||||
|
||||
def extract_remoteng_passwords(self, encrypted_password, encryption_attributes: MRemoteNgEncryptionAttributes):
|
||||
encrypted_password = b64decode(encrypted_password)
|
||||
if encrypted_password == b"":
|
||||
|
||||
+13
-22
@@ -31,19 +31,19 @@ class SmbHeader(Structure):
|
||||
]
|
||||
|
||||
def __init__(self, buffer):
|
||||
nxc_logger.debug("server_component : %04x" % self.server_component)
|
||||
nxc_logger.debug("smb_command : %01x" % self.smb_command)
|
||||
nxc_logger.debug("error_class : %01x" % self.error_class)
|
||||
nxc_logger.debug("error_code : %02x" % self.error_code)
|
||||
nxc_logger.debug("flags : %01x" % self.flags)
|
||||
nxc_logger.debug("flags2 : %02x" % self.flags2)
|
||||
nxc_logger.debug("process_id_high : %02x" % self.process_id_high)
|
||||
nxc_logger.debug("signature : %08x" % self.signature)
|
||||
nxc_logger.debug("reserved2 : %02x" % self.reserved2)
|
||||
nxc_logger.debug("tree_id : %02x" % self.tree_id)
|
||||
nxc_logger.debug("process_id : %02x" % self.process_id)
|
||||
nxc_logger.debug("user_id : %02x" % self.user_id)
|
||||
nxc_logger.debug("multiplex_id : %02x" % self.multiplex_id)
|
||||
nxc_logger.debug(f"server_component : {self.server_component:04x}")
|
||||
nxc_logger.debug(f"smb_command : {self.smb_command:01x}")
|
||||
nxc_logger.debug(f"error_class : {self.error_class:01x}")
|
||||
nxc_logger.debug(f"error_code : {self.error_code:02x}")
|
||||
nxc_logger.debug(f"flags : {self.flags:01x}")
|
||||
nxc_logger.debug(f"flags2 : {self.flags2:02x}")
|
||||
nxc_logger.debug(f"process_id_high : {self.process_id_high:02x}")
|
||||
nxc_logger.debug(f"signature : {self.signature:08x}")
|
||||
nxc_logger.debug(f"reserved2 : {self.reserved2:02x}")
|
||||
nxc_logger.debug(f"tree_id : {self.tree_id:02x}")
|
||||
nxc_logger.debug(f"process_id : {self.process_id:02x}")
|
||||
nxc_logger.debug(f"user_id : {self.user_id:02x}")
|
||||
nxc_logger.debug(f"multiplex_id : {self.multiplex_id:02x}")
|
||||
|
||||
def __new__(self, buffer=None):
|
||||
nxc_logger.debug(f"Creating SMB_HEADER object from buffer: {buffer}")
|
||||
@@ -72,7 +72,6 @@ class NXCModule:
|
||||
if str(e) == "Buffer size too small (0 instead of at least 32 bytes)":
|
||||
context.log.debug("Buffer size too small, which means the response was not the expected size")
|
||||
|
||||
|
||||
def generate_smb_proto_payload(self, *protos):
|
||||
"""
|
||||
Flattens a nested list and merges all bytes objects into a single bytes object.
|
||||
@@ -98,7 +97,6 @@ class NXCModule:
|
||||
self.logger.debug(f"Packed proto data: {hex_data}")
|
||||
return hex_data
|
||||
|
||||
|
||||
def calculate_doublepulsar_xor_key(self, s):
|
||||
"""
|
||||
Calculate Doublepulsar Xor Key.
|
||||
@@ -115,8 +113,6 @@ class NXCModule:
|
||||
x = (2 * s ^ (((s & 0xff00 | (s << 16)) << 8) | (((s >> 16) | s & 0xff0000) >> 8)))
|
||||
return x & 0xffffffff # truncate to 32 bits
|
||||
|
||||
|
||||
|
||||
def negotiate_proto_request(self):
|
||||
"""Generate a negotiate_proto_request packet."""
|
||||
self.logger.debug("generate negotiate proto request")
|
||||
@@ -160,7 +156,6 @@ class NXCModule:
|
||||
# Return the generated SMB protocol payload
|
||||
return self.generate_smb_proto_payload(netbios, smb_header, negotiate_proto_request)
|
||||
|
||||
|
||||
def session_setup_andx_request(self):
|
||||
"""Generate session setup andx request."""
|
||||
self.logger.debug("generate session setup andx request"
|
||||
@@ -210,7 +205,6 @@ class NXCModule:
|
||||
|
||||
return self.generate_smb_proto_payload(netbios, smb_header, session_setup_andx_request)
|
||||
|
||||
|
||||
def tree_connect_andx_request(self, ip, userid):
|
||||
"""Generate tree connect andx request.
|
||||
|
||||
@@ -279,7 +273,6 @@ class NXCModule:
|
||||
# Generate the final SMB protocol payload
|
||||
return self.generate_smb_proto_payload(netbios, smb_header, tree_connect_andx_request)
|
||||
|
||||
|
||||
def peeknamedpipe_request(self, treeid, processid, userid, multiplex_id):
|
||||
"""
|
||||
Generate tran2 request.
|
||||
@@ -345,7 +338,6 @@ class NXCModule:
|
||||
|
||||
return self.generate_smb_proto_payload(netbios, smb_header, tran_request)
|
||||
|
||||
|
||||
def trans2_request(self, treeid, processid, userid, multiplex_id):
|
||||
"""Generate trans2 request.
|
||||
|
||||
@@ -409,7 +401,6 @@ class NXCModule:
|
||||
|
||||
return self.generate_smb_proto_payload(netbios, smb_header, trans2_request)
|
||||
|
||||
|
||||
def check(self, ip, port=445):
|
||||
"""Check if MS17_010 SMB Vulnerability exists.
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import sys
|
||||
|
||||
|
||||
class NXCModule:
|
||||
"""Execute arbitrary SQL commands on the target MSSQL server"""
|
||||
|
||||
|
||||
@@ -300,9 +300,7 @@ class NXCModule:
|
||||
WHERE rp.name = 'db_owner' AND mp.name = SYSTEM_USER
|
||||
"""
|
||||
res = self.query_and_get_output(exec_as + query)
|
||||
if res and "database_role" in res[0] and res[0]["database_role"] == "db_owner":
|
||||
return True
|
||||
return False
|
||||
return bool(res and "database_role" in res[0] and res[0]["database_role"] == "db_owner")
|
||||
|
||||
def find_dbowner_priv(self, databases, exec_as="") -> list:
|
||||
"""
|
||||
|
||||
@@ -142,6 +142,8 @@ class NXCModule:
|
||||
add_ntds_hash.ntds_hashes = 0
|
||||
add_ntds_hash.added_to_db = 0
|
||||
|
||||
connection.output_filename = connection.output_file_template.format(output_folder="ntds")
|
||||
|
||||
NTDS = NTDSHashes(
|
||||
f"{self.dir_result}/Active Directory/ntds.dit",
|
||||
boot_key,
|
||||
|
||||
@@ -40,7 +40,7 @@ class NXCModule:
|
||||
def on_login(self, context, connection):
|
||||
# Connect and bind to MS-RPRN (https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rprn/848b8334-134a-4d02-aea4-03b673d6c515)
|
||||
target = connection.host if not connection.kerberos else connection.hostname + "." + connection.domain
|
||||
stringbinding = r"ncacn_np:%s[\PIPE\spoolss]" % target
|
||||
stringbinding = rf"ncacn_np:{target}[\PIPE\spoolss]"
|
||||
|
||||
context.log.info(f"Binding to {stringbinding!r}")
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from impacket.dcerpc.v5 import rrp
|
||||
from impacket.examples.secretsdump import RemoteOperations
|
||||
|
||||
|
||||
class NXCModule:
|
||||
r"""
|
||||
WinLogon AutoLogon: extract the credential from the following registry hive
|
||||
|
||||
@@ -52,10 +52,11 @@ class NXCModule:
|
||||
else:
|
||||
context.log.highlight("Potentially vulnerable to CVE-2019-1040, next step: https://dirkjanm.io/exploiting-CVE-2019-1040-relay-vulnerabilities-for-rce-and-domain-admin/")
|
||||
|
||||
|
||||
class Modify_Func:
|
||||
# Slightly modified version of impackets computeResponseNTLMv2
|
||||
def mod_computeResponseNTLMv2(flags, serverChallenge, clientChallenge, serverName, domain, user, password, lmhash="", nthash="",
|
||||
use_ntlmv2=ntlm.USE_NTLMv2, channel_binding_value=b""):
|
||||
use_ntlmv2=ntlm.USE_NTLMv2, channel_binding_value=b"", service="cifs"):
|
||||
|
||||
responseServerVersion = b"\x01"
|
||||
hiResponseServerVersion = b"\x01"
|
||||
@@ -162,7 +163,6 @@ class Modify_Func:
|
||||
if ntlmChallenge["flags"] & ntlm.NTLMSSP_NEGOTIATE_ALWAYS_SIGN == ntlm.NTLMSSP_NEGOTIATE_ALWAYS_SIGN:
|
||||
responseFlags ^= ntlm.NTLMSSP_NEGOTIATE_ALWAYS_SIGN
|
||||
|
||||
|
||||
keyExchangeKey = ntlm.KXKEY(ntlmChallenge["flags"], sessionBaseKey, lmResponse, ntlmChallenge["challenge"], password,
|
||||
lmhash, nthash, use_ntlmv2)
|
||||
|
||||
@@ -170,7 +170,6 @@ class Modify_Func:
|
||||
if user == "" and password == "" and lmhash == "" and nthash == "":
|
||||
keyExchangeKey = b"\x00" * 16
|
||||
|
||||
|
||||
if ntlmChallenge["flags"] & ntlm.NTLMSSP_NEGOTIATE_KEY_EXCH:
|
||||
exportedSessionKey = ntlm.b("".join([random.choice(string.digits + string.ascii_letters) for _ in range(16)]))
|
||||
encryptedRandomSessionKey = ntlm.generateEncryptedSessionKey(keyExchangeKey, exportedSessionKey)
|
||||
|
||||
@@ -4,6 +4,7 @@ from impacket.dcerpc.v5.rpcrt import DCERPCException
|
||||
from json import loads
|
||||
from traceback import format_exc as traceback_format_exc
|
||||
|
||||
|
||||
class NXCModule:
|
||||
"""
|
||||
Module by Adamkadaban: @Adamkadaban
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from impacket.dcerpc.v5 import rrp
|
||||
from impacket.examples.secretsdump import RemoteOperations
|
||||
|
||||
|
||||
# Module by @Defte_
|
||||
# Enables or disables shadow RDP
|
||||
class NXCModule:
|
||||
|
||||
@@ -3,6 +3,7 @@ import ntpath
|
||||
from sys import exit
|
||||
from nxc.paths import TMP_PATH
|
||||
|
||||
|
||||
class NXCModule:
|
||||
"""
|
||||
Original idea and PoC by Justin Angel (@4rch4ngel86)
|
||||
@@ -61,7 +62,6 @@ class NXCModule:
|
||||
self.ico_uri = module_options["ICO_URI"]
|
||||
context.log.debug("Overriding")
|
||||
|
||||
|
||||
self.lnk_name = module_options["NAME"]
|
||||
self.local_lnk_path = f"{TMP_PATH}/{self.lnk_name}.lnk"
|
||||
self.remote_file_path = ntpath.join("\\", f"{self.lnk_name}.lnk")
|
||||
|
||||
@@ -10,6 +10,7 @@ MAX_ATTEMPTS = 2000 # False negative chance: 0.04%
|
||||
# SMBGhost Packet
|
||||
SMBGHOST_PKT = b'\x00\x00\x00\xc0\xfeSMB@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x00\x08\x00\x01\x00\x00\x00\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00x\x00\x00\x00\x02\x00\x00\x00\x02\x02\x10\x02"\x02$\x02\x00\x03\x02\x03\x10\x03\x11\x03\x00\x00\x00\x00\x01\x00&\x00\x00\x00\x00\x00\x01\x00 \x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\n\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00'
|
||||
|
||||
|
||||
class NXCModule:
|
||||
name = "smbghost"
|
||||
description = "Module to check for the SMB dialect 3.1.1 and compression capability of the host, which is an indicator for the SMBGhost vulnerability (CVE-2020-0796)."
|
||||
|
||||
@@ -22,8 +22,6 @@ class NXCModule:
|
||||
self.context = context
|
||||
self.users = [user.lower() for user in module_options["USERS"].split(",")] if "USERS" in module_options else None
|
||||
|
||||
|
||||
|
||||
def on_admin_login(self, context, connection):
|
||||
self.context = context
|
||||
self.connection = connection
|
||||
@@ -111,7 +109,6 @@ class NXCModule:
|
||||
if total_files_downloaded > 0 and host_output_path:
|
||||
context.log.success(f"{total_files_downloaded} file(s) downloaded from host {connection.host} to {host_output_path}.")
|
||||
|
||||
|
||||
def find_screenshots_folders(self, user_folder_name):
|
||||
"""
|
||||
Dynamically searches for all Screenshots folders in the user's home directory.
|
||||
|
||||
@@ -5,11 +5,11 @@ from socket import socket, AF_INET, SOCK_DGRAM
|
||||
from struct import pack, unpack
|
||||
|
||||
|
||||
|
||||
def hashcat_format(rid, hashval, salt):
|
||||
"""Encodes hash in Hashcat-compatible format (with username prefix)."""
|
||||
return f"{rid}:$sntp-ms${hexlify(hashval).decode()}${hexlify(salt).decode()}"
|
||||
|
||||
|
||||
class NXCModule:
|
||||
"""
|
||||
Module by Disgame: @Disgame
|
||||
@@ -33,7 +33,6 @@ class NXCModule:
|
||||
# Static NTP query prefix using the MD5 authenticator. Append 4-byte RID and dummy checksum to create a full query.
|
||||
self.ntp_prefix = unhexlify("db0011e9000000000001000000000000e1b8407debc7e50600000000000000000000000000000000e1b8428bffbfcd0a")
|
||||
|
||||
|
||||
def options(self, context, module_options):
|
||||
self.rids = range(1, 2**31)
|
||||
self.rate = 180
|
||||
@@ -81,7 +80,6 @@ class NXCModule:
|
||||
except PermissionError:
|
||||
context.log.exception(f"No permission to listen on port {src_port}. May need to run as root.")
|
||||
|
||||
|
||||
query_interval = 1 / rate
|
||||
last_ok_time = time()
|
||||
rids_received = set()
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import logging
|
||||
|
||||
from impacket.dcerpc.v5 import rrp
|
||||
from impacket.examples.secretsdump import RemoteOperations
|
||||
@@ -14,7 +13,6 @@ class NXCModule:
|
||||
def __init__(self, context=None, module_options=None):
|
||||
self.context = context
|
||||
self.module_options = module_options
|
||||
logging.debug("test")
|
||||
|
||||
def options(self, context, module_options):
|
||||
""" """
|
||||
|
||||
+11
-11
@@ -128,17 +128,17 @@ class NXCModule:
|
||||
self.context.log.debug(f"Error while RegQueryValues {registry_keys} from {user_registry_path}: {e}")
|
||||
continue
|
||||
else:
|
||||
fh = tempfile.NamedTemporaryFile()
|
||||
fh.write(ntuser_dat_bytes)
|
||||
fh.seek(0)
|
||||
reg = winregistry.Registry(fh.name, isRemote=False)
|
||||
parent_key = reg.findKey(registry_path)
|
||||
if parent_key is None:
|
||||
continue
|
||||
cred["user"] = reg.getValue(ntpath.join(registry_path, registry_keys[0]))[1].decode("latin-1")
|
||||
password = reg.getValue(ntpath.join(registry_path, registry_keys[1]))[1].decode("utf-16le").rstrip("\0").encode()
|
||||
cred["password"] = self.recover_vncpassword(unhexlify(password)).decode("latin-1")
|
||||
cred["server"] = reg.getValue(ntpath.join(registry_path, registry_keys[2]))[1].decode("latin-1")
|
||||
with tempfile.NamedTemporaryFile() as fh:
|
||||
fh.write(ntuser_dat_bytes)
|
||||
fh.seek(0)
|
||||
reg = winregistry.Registry(fh.name, isRemote=False)
|
||||
parent_key = reg.findKey(registry_path)
|
||||
if parent_key is None:
|
||||
continue
|
||||
cred["user"] = reg.getValue(ntpath.join(registry_path, registry_keys[0]))[1].decode("latin-1")
|
||||
password = reg.getValue(ntpath.join(registry_path, registry_keys[1]))[1].decode("utf-16le").rstrip("\0").encode()
|
||||
cred["password"] = self.recover_vncpassword(unhexlify(password)).decode("latin-1")
|
||||
cred["server"] = reg.getValue(ntpath.join(registry_path, registry_keys[2]))[1].decode("latin-1")
|
||||
|
||||
self.context.log.highlight(f"[{vnc_name}] {cred['user']}:{cred['password']}@{cred['server']}")
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ class NXCModule:
|
||||
|
||||
self.pvkbytes = get_domain_backup_key(connection)
|
||||
|
||||
|
||||
target = Target.create(
|
||||
domain=connection.domain,
|
||||
username=username,
|
||||
|
||||
+2
-2
@@ -306,7 +306,7 @@ class HostChecker:
|
||||
|
||||
value = self.reg_query_value(self.dce, self.connection, key, value_name)
|
||||
|
||||
if type(value) == DCERPCSessionError:
|
||||
if isinstance(value, DCERPCSessionError):
|
||||
if options["KOIfMissing"]:
|
||||
ok = False
|
||||
if value.error_code in (ERROR_NO_MORE_ITEMS, ERROR_FILE_NOT_FOUND):
|
||||
@@ -462,7 +462,7 @@ class HostChecker:
|
||||
nbtns_enabled = 0
|
||||
for subkey in subkeys:
|
||||
value = self.reg_query_value(self.dce, self.connection, key_name + "\\" + subkey, "NetbiosOptions")
|
||||
if type(value) == DCERPCSessionError:
|
||||
if isinstance(value, DCERPCSessionError):
|
||||
if value.error_code == ERROR_OBJECT_NOT_FOUND:
|
||||
missing += 1
|
||||
continue
|
||||
|
||||
@@ -15,7 +15,6 @@ import re
|
||||
import configparser
|
||||
|
||||
|
||||
|
||||
class NXCModule:
|
||||
"""Module by @NeffIsBack"""
|
||||
|
||||
|
||||
+4
-4
@@ -40,7 +40,7 @@ if platform.system() != "Windows":
|
||||
resource.setrlimit(resource.RLIMIT_NOFILE, file_limit)
|
||||
|
||||
|
||||
async def start_run(protocol_obj, args, db, targets):
|
||||
async def start_run(protocol_obj, args, db, targets): # noqa: RUF029
|
||||
futures = []
|
||||
nxc_logger.debug("Creating ThreadPoolExecutor")
|
||||
if args.no_progress or len(targets) == 1:
|
||||
@@ -58,7 +58,7 @@ async def start_run(protocol_obj, args, db, targets):
|
||||
nxc_logger.debug(f"Creating thread for {protocol_obj}")
|
||||
futures = [executor.submit(protocol_obj, args, db, target) for target in targets]
|
||||
for _ in as_completed(futures):
|
||||
current += 1
|
||||
current += 1 # noqa: SIM113
|
||||
progress.update(tasks, completed=current)
|
||||
for future in as_completed(futures):
|
||||
try:
|
||||
@@ -102,8 +102,8 @@ def main():
|
||||
start_id, end_id = cred_id.split("-")
|
||||
try:
|
||||
for n in range(int(start_id), int(end_id) + 1):
|
||||
args.cred_id.append(n)
|
||||
args.cred_id.remove(cred_id)
|
||||
args.cred_id.append(n) # noqa: B909
|
||||
args.cred_id.remove(cred_id) # noqa: B909
|
||||
except Exception as e:
|
||||
nxc_logger.error(f"Error parsing database credential id: {e}")
|
||||
exit(1)
|
||||
|
||||
+1
-3
@@ -47,8 +47,7 @@ def write_csv(filename, headers, entries):
|
||||
def write_list(filename, entries):
|
||||
"""Writes a file with a simple list"""
|
||||
with open(os.path.expanduser(filename), "w") as export_file:
|
||||
for line in entries:
|
||||
export_file.write(line + "\n")
|
||||
export_file.writelines(line + "\n" for line in entries)
|
||||
|
||||
|
||||
def complete_import(text, line):
|
||||
@@ -516,7 +515,6 @@ class NXCDBMenu(cmd.Cmd):
|
||||
def do_EOF(line):
|
||||
sys.exit()
|
||||
|
||||
|
||||
@staticmethod
|
||||
def help_exit():
|
||||
help_string = """
|
||||
|
||||
@@ -5,6 +5,7 @@ from nxc.helpers.logger import highlight
|
||||
from nxc.logger import NXCAdapter
|
||||
from ftplib import FTP, error_perm
|
||||
|
||||
|
||||
class ftp(connection):
|
||||
def __init__(self, args, db, host):
|
||||
self.protocol = "FTP"
|
||||
|
||||
+67
-23
@@ -6,7 +6,7 @@ import os
|
||||
from errno import EHOSTUNREACH, ETIMEDOUT, ENETUNREACH
|
||||
from binascii import hexlify
|
||||
from datetime import datetime
|
||||
from re import sub, I
|
||||
from re import sub, IGNORECASE
|
||||
from zipfile import ZipFile
|
||||
from termcolor import colored
|
||||
from dns import resolver
|
||||
@@ -43,6 +43,7 @@ from nxc.protocols.ldap.gmsa import MSDS_MANAGEDPASSWORD_BLOB
|
||||
from nxc.protocols.ldap.kerberos import KerberosAttacks
|
||||
from nxc.parsers.ldap_results import parse_result_attributes
|
||||
from nxc.helpers.ntlm_parser import parse_challenge
|
||||
from nxc.helpers.misc import get_bloodhound_info
|
||||
|
||||
ldap_error_status = {
|
||||
"1": "STATUS_NOT_SUPPORTED",
|
||||
@@ -151,6 +152,7 @@ class ldap(connection):
|
||||
self.admin_privs = False
|
||||
self.no_ntlm = False
|
||||
self.sid_domain = ""
|
||||
self.scope = None
|
||||
|
||||
connection.__init__(self, args, db, host)
|
||||
|
||||
@@ -173,7 +175,7 @@ class ldap(connection):
|
||||
ldap_url = f"{proto}://{self.host}"
|
||||
self.logger.info(f"Connecting to {ldap_url} with no baseDN")
|
||||
try:
|
||||
self.ldap_connection = ldap_impacket.LDAPConnection(ldap_url, dstIp=self.host, signing=False)
|
||||
self.ldap_connection = ldap_impacket.LDAPConnection(ldap_url, dstIp=self.host)
|
||||
if self.ldap_connection:
|
||||
self.logger.debug(f"ldap_connection: {self.ldap_connection}")
|
||||
except SysCallError as e:
|
||||
@@ -195,10 +197,10 @@ class ldap(connection):
|
||||
target = resp_parsed["dnsHostName"]
|
||||
base_dn = resp_parsed["defaultNamingContext"]
|
||||
target_domain = sub(
|
||||
",DC=",
|
||||
r",DC=",
|
||||
".",
|
||||
base_dn[base_dn.lower().find("dc="):],
|
||||
flags=I,
|
||||
flags=IGNORECASE,
|
||||
)[3:]
|
||||
except ConnectionRefusedError as e:
|
||||
self.logger.debug(f"{e} on host {self.host}")
|
||||
@@ -249,6 +251,8 @@ class ldap(connection):
|
||||
if ntlm_challenge:
|
||||
ntlm_info = parse_challenge(ntlm_challenge)
|
||||
self.server_os = ntlm_info["os_version"]
|
||||
else:
|
||||
self.no_ntlm = True
|
||||
|
||||
if self.args.domain:
|
||||
self.domain = self.args.domain
|
||||
@@ -322,7 +326,7 @@ class ldap(connection):
|
||||
proto = "ldaps" if self.port == 636 else "ldap"
|
||||
ldap_url = f"{proto}://{self.target}"
|
||||
self.logger.info(f"Connecting to {ldap_url} - {self.baseDN} - {self.host} [1]")
|
||||
self.ldap_connection = ldap_impacket.LDAPConnection(url=ldap_url, baseDN=self.baseDN, dstIp=self.host, signing=False)
|
||||
self.ldap_connection = ldap_impacket.LDAPConnection(url=ldap_url, baseDN=self.baseDN, dstIp=self.host)
|
||||
self.ldap_connection.kerberosLogin(username, password, domain, self.lmhash, self.nthash, aesKey, kdcHost=kdcHost, useCache=useCache)
|
||||
if self.username == "":
|
||||
self.username = self.get_ldap_username()
|
||||
@@ -361,7 +365,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' % (process_secret(kerb_pass))} {e!s}",
|
||||
f"{self.domain}\\{self.username}{' from ccache' if useCache else f':{process_secret(kerb_pass)}'} {e!s}",
|
||||
color="red",
|
||||
)
|
||||
return False
|
||||
@@ -372,9 +376,10 @@ class ldap(connection):
|
||||
# Connect to LDAPS
|
||||
self.logger.extra["protocol"] = "LDAPS"
|
||||
self.logger.extra["port"] = "636"
|
||||
self.port = 636
|
||||
ldaps_url = f"ldaps://{self.target}"
|
||||
self.logger.info(f"Connecting to {ldaps_url} - {self.baseDN} - {self.host} [2]")
|
||||
self.ldap_connection = ldap_impacket.LDAPConnection(url=ldaps_url, baseDN=self.baseDN, dstIp=self.host, signing=False)
|
||||
self.ldap_connection = ldap_impacket.LDAPConnection(url=ldaps_url, baseDN=self.baseDN, dstIp=self.host)
|
||||
self.ldap_connection.kerberosLogin(username, password, domain, self.lmhash, self.nthash, aesKey, kdcHost=kdcHost, useCache=useCache)
|
||||
if self.username == "":
|
||||
self.username = self.get_ldap_username()
|
||||
@@ -399,21 +404,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' % (process_secret(kerb_pass))} {error!s}",
|
||||
f"{self.domain}\\{self.username}{' from ccache' if useCache else f':{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}:{process_secret(self.password)} {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.get(error_code, '')}",
|
||||
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' % (process_secret(kerb_pass))} {error_code!s}",
|
||||
f"{self.domain}\\{self.username}{' from ccache' if useCache else f':{process_secret(kerb_pass)}'} {error_code!s}",
|
||||
color="magenta" if error_code in ldap_error_status else "red",
|
||||
)
|
||||
return False
|
||||
@@ -438,7 +443,7 @@ class ldap(connection):
|
||||
proto = "ldaps" if self.port == 636 else "ldap"
|
||||
ldap_url = f"{proto}://{self.target}"
|
||||
self.logger.info(f"Connecting to {ldap_url} - {self.baseDN} - {self.host} [3]")
|
||||
self.ldap_connection = ldap_impacket.LDAPConnection(url=ldap_url, baseDN=self.baseDN, dstIp=self.host, signing=False)
|
||||
self.ldap_connection = ldap_impacket.LDAPConnection(url=ldap_url, baseDN=self.baseDN, dstIp=self.host)
|
||||
self.ldap_connection.login(self.username, self.password, self.domain, self.lmhash, self.nthash)
|
||||
self.check_if_admin()
|
||||
self.logger.debug(f"Adding credential: {domain}/{self.username}:{self.password}")
|
||||
@@ -459,9 +464,10 @@ class ldap(connection):
|
||||
# Connect to LDAPS
|
||||
self.logger.extra["protocol"] = "LDAPS"
|
||||
self.logger.extra["port"] = "636"
|
||||
self.port = 636
|
||||
ldaps_url = f"ldaps://{self.target}"
|
||||
self.logger.info(f"Connecting to {ldaps_url} - {self.baseDN} - {self.host} [4]")
|
||||
self.ldap_connection = ldap_impacket.LDAPConnection(url=ldaps_url, baseDN=self.baseDN, dstIp=self.host, signing=False)
|
||||
self.ldap_connection = ldap_impacket.LDAPConnection(url=ldaps_url, baseDN=self.baseDN, dstIp=self.host)
|
||||
self.ldap_connection.login(self.username, self.password, self.domain, self.lmhash, self.nthash)
|
||||
self.check_if_admin()
|
||||
self.logger.debug(f"Adding credential: {domain}/{self.username}:{self.password}")
|
||||
@@ -478,13 +484,13 @@ class ldap(connection):
|
||||
except Exception as e:
|
||||
error_code = str(e).split()[-2][:-1]
|
||||
self.logger.fail(
|
||||
f"{self.domain}\\{self.username}:{process_secret(self.password)} {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.get(error_code, '')}",
|
||||
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}:{process_secret(self.password)} {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.get(error_code, '')}",
|
||||
color="magenta" if (error_code in ldap_error_status and error_code != 1) else "red",
|
||||
)
|
||||
return False
|
||||
@@ -528,7 +534,7 @@ class ldap(connection):
|
||||
proto = "ldaps" if self.port == 636 else "ldap"
|
||||
ldaps_url = f"{proto}://{self.target}"
|
||||
self.logger.info(f"Connecting to {ldaps_url} - {self.baseDN} - {self.host}")
|
||||
self.ldap_connection = ldap_impacket.LDAPConnection(url=ldaps_url, baseDN=self.baseDN, dstIp=self.host, signing=False)
|
||||
self.ldap_connection = ldap_impacket.LDAPConnection(url=ldaps_url, baseDN=self.baseDN, dstIp=self.host)
|
||||
self.ldap_connection.login(self.username, self.password, self.domain, self.lmhash, self.nthash)
|
||||
self.check_if_admin()
|
||||
self.logger.debug(f"Adding credential: {domain}/{self.username}:{self.hash}")
|
||||
@@ -549,9 +555,10 @@ class ldap(connection):
|
||||
# We need to try SSL
|
||||
self.logger.extra["protocol"] = "LDAPS"
|
||||
self.logger.extra["port"] = "636"
|
||||
self.port = 636
|
||||
ldaps_url = f"ldaps://{self.target}"
|
||||
self.logger.info(f"Connecting to {ldaps_url} - {self.baseDN} - {self.host}")
|
||||
self.ldap_connection = ldap_impacket.LDAPConnection(url=ldaps_url, baseDN=self.baseDN, dstIp=self.host, signing=False)
|
||||
self.ldap_connection = ldap_impacket.LDAPConnection(url=ldaps_url, baseDN=self.baseDN, dstIp=self.host)
|
||||
self.ldap_connection.login(self.username, self.password, self.domain, self.lmhash, self.nthash)
|
||||
self.check_if_admin()
|
||||
self.logger.debug(f"Adding credential: {domain}/{self.username}:{self.hash}")
|
||||
@@ -569,13 +576,13 @@ class ldap(connection):
|
||||
except ldap_impacket.LDAPSessionError as e:
|
||||
error_code = str(e).split()[-2][:-1]
|
||||
self.logger.fail(
|
||||
f"{self.domain}\\{self.username}:{process_secret(nthash)} {ldap_error_status[error_code] if error_code in ldap_error_status else ''}",
|
||||
f"{self.domain}\\{self.username}:{process_secret(nthash)} {ldap_error_status.get(error_code, '')}",
|
||||
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}:{process_secret(nthash)} {ldap_error_status[error_code] if error_code in ldap_error_status else ''}",
|
||||
f"{self.domain}\\{self.username}:{process_secret(nthash)} {ldap_error_status.get(error_code, '')}",
|
||||
color="magenta" if (error_code in ldap_error_status and error_code != 1) else "red",
|
||||
)
|
||||
return False
|
||||
@@ -623,7 +630,7 @@ class ldap(connection):
|
||||
return t
|
||||
|
||||
def search(self, searchFilter, attributes, sizeLimit=0, baseDN=None) -> list:
|
||||
if baseDN is None and self.args.base_dn:
|
||||
if baseDN is None and self.args.base_dn is not None:
|
||||
baseDN = self.args.base_dn
|
||||
elif baseDN is None:
|
||||
baseDN = self.baseDN
|
||||
@@ -633,19 +640,24 @@ class ldap(connection):
|
||||
self.logger.debug(f"Search Filter={searchFilter}")
|
||||
|
||||
# Microsoft Active Directory set an hard limit of 1000 entries returned by any search
|
||||
paged_search_control = ldapasn1_impacket.SimplePagedResultsControl(criticality=True, size=1000)
|
||||
paged_search_control = [ldapasn1_impacket.SimplePagedResultsControl(criticality=True, size=1000)] if not self.no_ntlm else ""
|
||||
return self.ldap_connection.search(
|
||||
scope=self.scope,
|
||||
searchBase=baseDN,
|
||||
searchFilter=searchFilter,
|
||||
attributes=attributes,
|
||||
sizeLimit=sizeLimit,
|
||||
searchControls=[paged_search_control],
|
||||
searchControls=paged_search_control,
|
||||
)
|
||||
except ldap_impacket.LDAPSearchError as e:
|
||||
if e.getErrorString().find("sizeLimitExceeded") >= 0:
|
||||
if "sizeLimitExceeded" in str(e):
|
||||
# We should never reach this code as we use paged search now
|
||||
self.logger.fail("sizeLimitExceeded exception caught, giving up and processing the data received")
|
||||
e.getAnswers()
|
||||
# if empty username and password is possible that we need to change the scope, we try with a baseObject before returning a fail
|
||||
elif "operationsError" in str(e) and self.scope is None and self.username == "" and self.password == "":
|
||||
self.scope = ldapasn1_impacket.Scope("baseObject")
|
||||
return self.search(searchFilter, attributes, sizeLimit, baseDN)
|
||||
else:
|
||||
self.logger.fail(e)
|
||||
return []
|
||||
@@ -1208,6 +1220,38 @@ class ldap(connection):
|
||||
self.logger.fail("No string provided :'(")
|
||||
|
||||
def bloodhound(self):
|
||||
# Check which version is desired
|
||||
use_bhce = self.config.getboolean("BloodHound-CE", "bhce_enabled", fallback=False)
|
||||
package_name, version, is_ce = get_bloodhound_info()
|
||||
|
||||
if use_bhce and not is_ce:
|
||||
self.logger.fail("⚠️ Configuration Issue Detected ⚠️")
|
||||
self.logger.fail("Your configuration has BloodHound-CE enabled, but the regular BloodHound package is installed. Modify your ~/.nxc/nxc.conf config file or follow the instructions:")
|
||||
self.logger.fail("Please run the following commands to fix this:")
|
||||
self.logger.fail("poetry remove bloodhound-ce # poetry falsely recognizes bloodhound-ce as a the old bloodhound package")
|
||||
self.logger.fail("poetry add bloodhound-ce")
|
||||
self.logger.fail("")
|
||||
|
||||
# If using pipx
|
||||
self.logger.fail("Or if you installed with pipx:")
|
||||
self.logger.fail("pipx runpip netexec uninstall -y bloodhound")
|
||||
self.logger.fail("pipx inject netexec bloodhound-ce --force")
|
||||
return False
|
||||
|
||||
elif not use_bhce and is_ce:
|
||||
self.logger.fail("⚠️ Configuration Issue Detected ⚠️")
|
||||
self.logger.fail("Your configuration has regular BloodHound enabled, but the BloodHound-CE package is installed.")
|
||||
self.logger.fail("Please run the following commands to fix this:")
|
||||
self.logger.fail("poetry remove bloodhound-ce")
|
||||
self.logger.fail("poetry add bloodhound")
|
||||
self.logger.fail("")
|
||||
|
||||
# If using pipx
|
||||
self.logger.fail("Or if you installed with pipx:")
|
||||
self.logger.fail("pipx runpip netexec uninstall -y bloodhound-ce")
|
||||
self.logger.fail("pipx inject netexec bloodhound --force")
|
||||
return False
|
||||
|
||||
auth = ADAuthentication(
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
@@ -1227,7 +1271,7 @@ class ldap(connection):
|
||||
)
|
||||
collect = resolve_collection_methods("Default" if not self.args.collection else self.args.collection)
|
||||
if not collect:
|
||||
return
|
||||
return None
|
||||
self.logger.highlight("Resolved collection methods: " + ", ".join(list(collect)))
|
||||
|
||||
self.logger.debug("Using DNS to retrieve domain information")
|
||||
|
||||
@@ -107,4 +107,4 @@ class BloodHound:
|
||||
computer_enum.enumerate_computers(self.ad.computers, num_workers=num_workers, timestamp=timestamp, fileNamePrefix=fileNamePrefix)
|
||||
end_time = time.time()
|
||||
minutes, seconds = divmod(int(end_time - start_time), 60)
|
||||
self.logger.highlight("Done in %02dM %02dS" % (minutes, seconds))
|
||||
self.logger.highlight(f"Done in {minutes}M {seconds}S")
|
||||
|
||||
@@ -64,7 +64,7 @@ class KerberosAttacks:
|
||||
# last 12 bytes of the encrypted ticket represent the checksum of the decrypted
|
||||
# ticket
|
||||
if decoded_tgs["ticket"]["enc-part"]["etype"] == constants.EncryptionTypes.rc4_hmac.value:
|
||||
entry = "$krb5tgs$%d$*%s$%s$%s*$%s$%s" % (
|
||||
entry = "$krb5tgs${}$*{}${}${}*${}${}".format(
|
||||
constants.EncryptionTypes.rc4_hmac.value,
|
||||
username,
|
||||
decoded_tgs["ticket"]["realm"],
|
||||
@@ -73,7 +73,7 @@ class KerberosAttacks:
|
||||
hexlify(decoded_tgs["ticket"]["enc-part"]["cipher"][16:].asOctets()).decode(),
|
||||
)
|
||||
elif decoded_tgs["ticket"]["enc-part"]["etype"] == constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value:
|
||||
entry = "$krb5tgs$%d$%s$%s$*%s*$%s$%s" % (
|
||||
entry = "$krb5tgs${}${}${}$*{}*${}${}".format(
|
||||
constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value,
|
||||
username,
|
||||
decoded_tgs["ticket"]["realm"],
|
||||
@@ -82,7 +82,7 @@ class KerberosAttacks:
|
||||
hexlify(decoded_tgs["ticket"]["enc-part"]["cipher"][:-12:].asOctets()).decode,
|
||||
)
|
||||
elif decoded_tgs["ticket"]["enc-part"]["etype"] == constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value:
|
||||
entry = "$krb5tgs$%d$%s$%s$*%s*$%s$%s" % (
|
||||
entry = "$krb5tgs${}${}${}$*{}*${}${}".format(
|
||||
constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value,
|
||||
username,
|
||||
decoded_tgs["ticket"]["realm"],
|
||||
@@ -91,7 +91,7 @@ class KerberosAttacks:
|
||||
hexlify(decoded_tgs["ticket"]["enc-part"]["cipher"][:-12:].asOctets()).decode(),
|
||||
)
|
||||
elif decoded_tgs["ticket"]["enc-part"]["etype"] == constants.EncryptionTypes.des_cbc_md5.value:
|
||||
entry = "$krb5tgs$%d$*%s$%s$%s*$%s$%s" % (
|
||||
entry = "$krb5tgs${}$*{}${}${}*${}${}".format(
|
||||
constants.EncryptionTypes.des_cbc_md5.value,
|
||||
username,
|
||||
decoded_tgs["ticket"]["realm"],
|
||||
|
||||
@@ -95,13 +95,13 @@ class LDAPConnect:
|
||||
except ldap_impacket.LDAPSessionError as e:
|
||||
error_code = str(e).split()[-2][:-1]
|
||||
self.logger.fail(
|
||||
f"{domain}\\{username}:{password if password else ntlm_hash} {ldap_error_status[error_code] if error_code in ldap_error_status else ''}",
|
||||
f"{domain}\\{username}:{password if password else ntlm_hash} {ldap_error_status.get(error_code, '')}",
|
||||
color="magenta" if error_code in ldap_error_status else "red",
|
||||
)
|
||||
else:
|
||||
error_code = str(e).split()[-2][:-1]
|
||||
self.logger.fail(
|
||||
f"{domain}\\{username}:{password if password else ntlm_hash} {ldap_error_status[error_code] if error_code in ldap_error_status else ''}",
|
||||
f"{domain}\\{username}:{password if password else ntlm_hash} {ldap_error_status.get(error_code, '')}",
|
||||
color="magenta" if error_code in ldap_error_status else "red",
|
||||
)
|
||||
return False
|
||||
@@ -152,13 +152,13 @@ class LDAPConnect:
|
||||
except ldap_impacket.LDAPSessionError as e:
|
||||
error_code = str(e).split()[-2][:-1]
|
||||
self.logger.fail(
|
||||
f"{domain}\\{username}:{password if password else ntlm_hash} {ldap_error_status[error_code] if error_code in ldap_error_status else ''}",
|
||||
f"{domain}\\{username}:{password if password else ntlm_hash} {ldap_error_status.get(error_code, '')}",
|
||||
color="magenta" if error_code in ldap_error_status else "red",
|
||||
)
|
||||
else:
|
||||
error_code = str(e).split()[-2][:-1]
|
||||
self.logger.fail(
|
||||
f"{domain}\\{username}:{password if password else ntlm_hash} {ldap_error_status[error_code] if error_code in ldap_error_status else ''}",
|
||||
f"{domain}\\{username}:{password if password else ntlm_hash} {ldap_error_status.get(error_code, '')}",
|
||||
color="magenta" if error_code in ldap_error_status else "red",
|
||||
)
|
||||
return False
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
from re import sub, I
|
||||
from errno import EHOSTUNREACH, ETIMEDOUT, ENETUNREACH
|
||||
from OpenSSL.SSL import SysCallError
|
||||
|
||||
from impacket.ldap import ldap as ldap_impacket
|
||||
from impacket.ldap import ldapasn1 as ldapasn1_impacket
|
||||
|
||||
from nxc.parsers.ldap_results import parse_result_attributes
|
||||
from nxc.logger import nxc_logger
|
||||
|
||||
|
||||
class LDAPResolution:
|
||||
|
||||
def __init__(self, host):
|
||||
self.host = host
|
||||
|
||||
def get_resolution(self):
|
||||
target = ""
|
||||
target_domain = ""
|
||||
base_dn = ""
|
||||
try:
|
||||
ldap_url = f"ldap://{self.host}"
|
||||
nxc_logger.info(f"Connecting to {ldap_url} with no baseDN")
|
||||
try:
|
||||
self.ldap_connection = ldap_impacket.LDAPConnection(ldap_url, dstIp=self.host)
|
||||
if self.ldap_connection:
|
||||
nxc_logger.debug(f"ldap_connection: {self.ldap_connection}")
|
||||
except SysCallError as e:
|
||||
nxc_logger.fail(f"LDAP connection to {ldap_url} failed: {e}")
|
||||
return False
|
||||
|
||||
resp = self.ldap_connection.search(
|
||||
scope=ldapasn1_impacket.Scope("baseObject"),
|
||||
attributes=["defaultNamingContext", "dnsHostName"],
|
||||
sizeLimit=0,
|
||||
)
|
||||
resp_parsed = parse_result_attributes(resp)[0]
|
||||
|
||||
target = resp_parsed["dnsHostName"]
|
||||
base_dn = resp_parsed["defaultNamingContext"]
|
||||
target_domain = sub(
|
||||
r",DC=",
|
||||
".",
|
||||
base_dn[base_dn.lower().find("dc="):],
|
||||
flags=I,
|
||||
)[3:]
|
||||
# Extract machine name from target (hostname part of FQDN)
|
||||
if target:
|
||||
machine_name = target.split(".")[0]
|
||||
nxc_logger.debug(f"Extracted machine name: {machine_name}")
|
||||
|
||||
self.ldap_connection.close()
|
||||
except ConnectionRefusedError as e:
|
||||
nxc_logger.debug(f"{e} on host {self.host}")
|
||||
return False
|
||||
except OSError as e:
|
||||
if e.errno in (EHOSTUNREACH, ENETUNREACH, ETIMEDOUT):
|
||||
nxc_logger.info(f"Error connecting to {self.host} - {e}")
|
||||
return False
|
||||
else:
|
||||
nxc_logger.error(f"Error getting ldap info {e}")
|
||||
|
||||
nxc_logger.debug(f"Target: {machine_name}.{target_domain}; target_domain: {target_domain}; base_dn: {base_dn}")
|
||||
return machine_name, target_domain
|
||||
@@ -65,9 +65,7 @@ class MSSQLEXEC:
|
||||
result = self.mssql_conn.sql_query(query)
|
||||
# Assuming the query returns a list of dictionaries with 'config_value' as the key
|
||||
self.logger.debug(f"{option} check result: {result}")
|
||||
if result and result[0]["config_value"] == 1:
|
||||
return True
|
||||
return False
|
||||
return bool(result and result[0]["config_value"] == 1)
|
||||
|
||||
def put_file(self, data, remote):
|
||||
try:
|
||||
|
||||
@@ -562,8 +562,8 @@ class nfs(connection):
|
||||
# Format for the file id see: https://elixir.bootlin.com/linux/v6.13.4/source/include/linux/exportfs.h#L25
|
||||
fh = bytearray(mount_fh)
|
||||
if filesystem in [FileID.ext, FileID.unknown]:
|
||||
root_handles.append(bytes(fh[:3] + b"\x02" + fh[4:4+fh_fsid_len] + b"\x02\x00\x00\x00" + b"\x00\x00\x00\x00" + b"\x02\x00\x00\x00")) # noqa: E226 FURB113
|
||||
root_handles.append(bytes(fh[:3] + b"\x02" + fh[4:4+fh_fsid_len] + b"\x80\x00\x00\x00" + b"\x00\x00\x00\x00" + b"\x80\x00\x00\x00")) # noqa: E226
|
||||
root_handles.append(bytes(fh[:3] + b"\x02" + fh[4:4+fh_fsid_len] + b"\x02\x00\x00\x00" + b"\x00\x00\x00\x00" + b"\x02\x00\x00\x00")) # noqa: E226
|
||||
root_handles.append(bytes(fh[:3] + b"\x02" + fh[4:4+fh_fsid_len] + b"\x80\x00\x00\x00" + b"\x00\x00\x00\x00" + b"\x80\x00\x00\x00")) # noqa: E226
|
||||
if filesystem in [FileID.btrfs, FileID.unknown]:
|
||||
# Iterate over btrfs subvolumes, use 16 as default similar to the guys from nfs-security-tooling
|
||||
for i in range(16):
|
||||
@@ -728,7 +728,7 @@ def convert_size(size_bytes):
|
||||
if size_bytes == 0:
|
||||
return "0B"
|
||||
size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
|
||||
i = int(math.floor(math.log(size_bytes, 1024)))
|
||||
i = math.floor(math.log(size_bytes, 1024))
|
||||
p = math.pow(1024, i)
|
||||
s = round(size_bytes / p, 1)
|
||||
return f"{s}{size_name[i]}"
|
||||
|
||||
+47
-31
@@ -55,6 +55,8 @@ from nxc.protocols.ldap.gmsa import MSDS_MANAGEDPASSWORD_BLOB
|
||||
from nxc.helpers.logger import highlight
|
||||
from nxc.helpers.bloodhound import add_user_bh
|
||||
from nxc.helpers.powershell import create_ps_command
|
||||
from nxc.helpers.misc import detect_if_ip
|
||||
from nxc.protocols.ldap.resolution import LDAPResolution
|
||||
|
||||
from dploot.triage.vaults import VaultsTriage
|
||||
from dploot.triage.browser import BrowserTriage, LoginData, GoogleRefreshToken, Cookie
|
||||
@@ -65,7 +67,6 @@ from dploot.triage.sccm import SCCMTriage, SCCMCred, SCCMSecret, SCCMCollection
|
||||
from time import time, ctime
|
||||
from datetime import datetime
|
||||
from traceback import format_exc
|
||||
import logging
|
||||
from termcolor import colored
|
||||
import contextlib
|
||||
|
||||
@@ -100,6 +101,7 @@ def get_error_string(exception):
|
||||
else:
|
||||
return str(exception)
|
||||
|
||||
|
||||
class smb(connection):
|
||||
def __init__(self, args, db, host):
|
||||
self.domain = None
|
||||
@@ -125,6 +127,7 @@ class smb(connection):
|
||||
self.no_ntlm = False
|
||||
self.protocol = "SMB"
|
||||
self.is_guest = None
|
||||
self.isdc = False
|
||||
|
||||
connection.__init__(self, args, db, host)
|
||||
|
||||
@@ -185,13 +188,19 @@ class smb(connection):
|
||||
if not self.targetDomain: # Not sure if that can even happen but now we are safe
|
||||
self.targetDomain = self.hostname
|
||||
else:
|
||||
# If we can't authenticate with NTLM and the target is supplied as a FQDN we must parse it
|
||||
try:
|
||||
import socket
|
||||
socket.inet_aton(self.host)
|
||||
self.logger.debug("NTLM authentication not available! Authentication will fail without a valid hostname and domain name")
|
||||
self.hostname = self.host
|
||||
self.targetDomain = self.host
|
||||
# If we know the host is a DC we can still get the hostname over LDAP if NTLM is not available
|
||||
if self.is_host_dc() and detect_if_ip(self.host):
|
||||
self.hostname, self.domain = LDAPResolution(self.host).get_resolution()
|
||||
self.targetDomain = self.domain
|
||||
# If we can't authenticate with NTLM and the target is supplied as a FQDN we must parse it
|
||||
else:
|
||||
# Check if the host is a valid IP address, if not we parse the FQDN in the Exception
|
||||
import socket
|
||||
socket.inet_aton(self.host)
|
||||
self.logger.debug("NTLM authentication not available! Authentication will fail without a valid hostname and domain name")
|
||||
self.hostname = self.host
|
||||
self.targetDomain = self.host
|
||||
except OSError:
|
||||
if self.host.count(".") >= 1:
|
||||
self.hostname = self.host.split(".")[0]
|
||||
@@ -199,6 +208,10 @@ class smb(connection):
|
||||
else:
|
||||
self.hostname = self.host
|
||||
self.targetDomain = self.host
|
||||
except Exception as e:
|
||||
self.logger.debug(f"Error getting hostname from LDAP: {e}")
|
||||
self.hostname = self.host
|
||||
self.targetDomain = self.host
|
||||
|
||||
if self.args.domain:
|
||||
self.domain = self.args.domain
|
||||
@@ -283,21 +296,12 @@ class smb(connection):
|
||||
self.logger.display(f"{self.server_os}{f' x{self.os_arch}' if self.os_arch else ''} (name:{self.hostname}) (domain:{self.targetDomain}) ({signing}) ({smbv1}) {ntlm}")
|
||||
|
||||
if self.args.generate_hosts_file or self.args.generate_krb5_file:
|
||||
from impacket.dcerpc.v5 import nrpc, epm
|
||||
self.logger.debug("Performing authentication attempts...")
|
||||
isdc = False
|
||||
try:
|
||||
epm.hept_map(self.host, nrpc.MSRPC_UUID_NRPC, protocol="ncacn_ip_tcp")
|
||||
isdc = True
|
||||
except DCERPCException:
|
||||
self.logger.debug("Error while connecting to host: DCERPCException, which means this is probably not a DC!")
|
||||
|
||||
if self.args.generate_hosts_file:
|
||||
with open(self.args.generate_hosts_file, "a+") as host_file:
|
||||
dc_part = f" {self.targetDomain}" if isdc else ""
|
||||
dc_part = f" {self.targetDomain}" if self.isdc else ""
|
||||
host_file.write(f"{self.host} {self.hostname}.{self.targetDomain}{dc_part} {self.hostname}\n")
|
||||
self.logger.debug(f"{self.host} {self.hostname}.{self.targetDomain}{dc_part} {self.hostname}")
|
||||
elif self.args.generate_krb5_file and isdc:
|
||||
self.logger.debug(f"Line added to {self.args.generate_hosts_file} {self.host} {self.hostname}.{self.targetDomain}{dc_part} {self.hostname}")
|
||||
elif self.args.generate_krb5_file and self.isdc:
|
||||
with open(self.args.generate_krb5_file, "w+") as host_file:
|
||||
data = f"""
|
||||
[libdefaults]
|
||||
@@ -658,6 +662,18 @@ class smb(connection):
|
||||
except Exception as e:
|
||||
self.logger.fail(f"Failed to get TGT: {e}")
|
||||
|
||||
def is_host_dc(self):
|
||||
from impacket.dcerpc.v5 import nrpc, epm
|
||||
self.logger.debug("Performing authentication attempts...")
|
||||
try:
|
||||
epm.hept_map(self.host, nrpc.MSRPC_UUID_NRPC, protocol="ncacn_ip_tcp")
|
||||
self.isdc = True
|
||||
return True
|
||||
except DCERPCException:
|
||||
self.logger.debug("Error while connecting to host: DCERPCException, which means this is probably not a DC!")
|
||||
self.isdc = False
|
||||
return False
|
||||
|
||||
@requires_admin
|
||||
def execute(self, payload=None, get_output=False, methods=None) -> str:
|
||||
"""
|
||||
@@ -920,19 +936,19 @@ class smb(connection):
|
||||
return
|
||||
self.enumerate_sessions_info(sessions)
|
||||
|
||||
maxSessionNameLen = max([len(sessions[i]["SessionName"]) + 1 for i in sessions])
|
||||
maxSessionNameLen = max(len(sessions[i]["SessionName"]) + 1 for i in sessions)
|
||||
maxSessionNameLen = maxSessionNameLen if len("SESSIONNAME") < maxSessionNameLen else len("SESSIONNAME") + 1
|
||||
maxUsernameLen = max([len(sessions[i]["Username"] + sessions[i]["Domain"]) + 1 for i in sessions]) + 1
|
||||
maxUsernameLen = max(len(sessions[i]["Username"] + sessions[i]["Domain"]) + 1 for i in sessions) + 1
|
||||
maxUsernameLen = maxUsernameLen if len("Username") < maxUsernameLen else len("Username") + 1
|
||||
maxIdLen = max([len(str(i)) for i in sessions])
|
||||
maxIdLen = max(len(str(i)) for i in sessions)
|
||||
maxIdLen = maxIdLen if len("ID") < maxIdLen else len("ID") + 1
|
||||
maxStateLen = max([len(sessions[i]["state"]) + 1 for i in sessions])
|
||||
maxStateLen = max(len(sessions[i]["state"]) + 1 for i in sessions)
|
||||
maxStateLen = maxStateLen if len("STATE") < maxStateLen else len("STATE") + 1
|
||||
maxRemoteIp = max([len(sessions[i]["RemoteIp"]) + 1 for i in sessions])
|
||||
maxRemoteIp = max(len(sessions[i]["RemoteIp"]) + 1 for i in sessions)
|
||||
maxRemoteIp = maxRemoteIp if len("RemoteAddress") < maxRemoteIp else len("RemoteAddress") + 1
|
||||
maxClientName = max([len(sessions[i]["ClientName"]) + 1 for i in sessions])
|
||||
maxClientName = max(len(sessions[i]["ClientName"]) + 1 for i in sessions)
|
||||
maxClientName = maxClientName if len("ClientName") < maxClientName else len("ClientName") + 1
|
||||
template = ("{SESSIONNAME: <%d} "
|
||||
template = ("{SESSIONNAME: <%d} " # noqa: UP031
|
||||
"{USERNAME: <%d} "
|
||||
"{ID: <%d} "
|
||||
"{IPv4: <16} "
|
||||
@@ -1001,9 +1017,9 @@ class smb(connection):
|
||||
if not res:
|
||||
return
|
||||
self.logger.success("Enumerated processes")
|
||||
maxImageNameLen = max([len(i["ImageName"]) for i in res])
|
||||
maxSidLen = max([len(i["pSid"]) for i in res])
|
||||
template = "{: <%d} {: <8} {: <11} {: <%d} {: >12}" % (maxImageNameLen, maxSidLen)
|
||||
maxImageNameLen = max(len(i["ImageName"]) for i in res)
|
||||
maxSidLen = max(len(i["pSid"]) for i in res)
|
||||
template = "{: <%d} {: <8} {: <11} {: <%d} {: >12}" % (maxImageNameLen, maxSidLen) # noqa: UP031
|
||||
self.logger.highlight(template.format("Image Name", "PID", "Session#", "SID", "Mem Usage"))
|
||||
self.logger.highlight(template.replace(": ", ":=").format("", "", "", "", ""))
|
||||
for procInfo in res:
|
||||
@@ -1136,7 +1152,7 @@ class smb(connection):
|
||||
self.logger.highlight(f"{name:<15} {','.join(perms):<15} {remark}")
|
||||
return permissions
|
||||
|
||||
def dir(self): # noqa: A003
|
||||
def dir(self):
|
||||
search_path = ntpath.join(self.args.dir, "*")
|
||||
try:
|
||||
contents = self.conn.listPath(self.args.share, search_path)
|
||||
@@ -1420,7 +1436,7 @@ class smb(connection):
|
||||
|
||||
try:
|
||||
string_binding = KNOWN_PROTOCOLS[self.port]["bindstr"]
|
||||
logging.debug(f"StringBinding {string_binding}")
|
||||
self.logger.debug(f"StringBinding {string_binding}")
|
||||
rpc_transport = transport.DCERPCTransportFactory(string_binding)
|
||||
rpc_transport.setRemoteHost(self.host)
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ class TSCH_EXEC:
|
||||
if self.__password is None:
|
||||
self.__password = ""
|
||||
|
||||
stringbinding = r"ncacn_np:%s[\pipe\atsvc]" % self.__target
|
||||
stringbinding = rf"ncacn_np:{self.__target}[\pipe\atsvc]"
|
||||
self.__rpctransport = transport.DCERPCTransportFactory(stringbinding)
|
||||
self.__rpctransport.setRemoteHost(self.__remoteHost)
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ def get_domain_backup_key(context):
|
||||
context.logger.fail(f"Could not get domain backupkey: {e}")
|
||||
return pvkbytes
|
||||
|
||||
|
||||
def collect_masterkeys_from_target(context, target, dploot_connection, user=True, system=True):
|
||||
masterkeys = []
|
||||
plaintexts = {}
|
||||
@@ -85,6 +86,7 @@ def collect_masterkeys_from_target(context, target, dploot_connection, user=True
|
||||
|
||||
return masterkeys
|
||||
|
||||
|
||||
def upgrade_to_dploot_connection(target, connection=None):
|
||||
conn = None
|
||||
try:
|
||||
|
||||
@@ -18,12 +18,13 @@ from nxc.protocols.smb.dpapi import upgrade_to_dploot_connection
|
||||
CKA_ID = unhexlify("f8000000000000000000000000000001")
|
||||
|
||||
|
||||
@dataclass
|
||||
class FirefoxData:
|
||||
def __init__(self, winuser: str, url: str, username: str, password: str):
|
||||
self.winuser = winuser
|
||||
self.url = url
|
||||
self.username = username
|
||||
self.password = password
|
||||
winuser: str
|
||||
url: str
|
||||
username: str
|
||||
password: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class FirefoxCookie:
|
||||
@@ -36,6 +37,7 @@ class FirefoxCookie:
|
||||
expires_utc: str
|
||||
last_access_utc: str
|
||||
|
||||
|
||||
class FirefoxTriage:
|
||||
"""
|
||||
Firefox by @zblurx
|
||||
@@ -110,11 +112,11 @@ class FirefoxTriage:
|
||||
password = self.decrypt(key=key, iv=pwd[1], ciphertext=pwd[2]).decode("utf-8")
|
||||
if password is not None and decoded_username is not None:
|
||||
data = FirefoxData(
|
||||
winuser=user,
|
||||
url=host,
|
||||
username=decoded_username,
|
||||
password=password,
|
||||
)
|
||||
winuser=user,
|
||||
url=host,
|
||||
username=decoded_username,
|
||||
password=password,
|
||||
)
|
||||
if self.per_secret_callback is not None:
|
||||
self.per_secret_callback(data)
|
||||
firefox_data.append(data)
|
||||
@@ -126,7 +128,7 @@ class FirefoxTriage:
|
||||
|
||||
def parse_cookie_data(self, windows_user, cookies_data):
|
||||
cookies = []
|
||||
fh = tempfile.NamedTemporaryFile(delete=False)
|
||||
fh = tempfile.NamedTemporaryFile(delete=False) # noqa: SIM115
|
||||
fh.write(cookies_data)
|
||||
fh.seek(0)
|
||||
db = sqlite3.connect(fh.name)
|
||||
@@ -134,15 +136,15 @@ class FirefoxTriage:
|
||||
cursor.execute("SELECT name, value, host, path, expiry, lastAccessed, creationTime FROM moz_cookies;")
|
||||
for name, value, host, path, expiry, lastAccessed, creationTime in cursor:
|
||||
cookie = FirefoxCookie(
|
||||
winuser=windows_user,
|
||||
host=host,
|
||||
path=path,
|
||||
cookie_name=name,
|
||||
cookie_value=value,
|
||||
creation_utc=creationTime,
|
||||
last_access_utc=lastAccessed,
|
||||
expires_utc=expiry,
|
||||
)
|
||||
winuser=windows_user,
|
||||
host=host,
|
||||
path=path,
|
||||
cookie_name=name,
|
||||
cookie_value=value,
|
||||
creation_utc=creationTime,
|
||||
last_access_utc=lastAccessed,
|
||||
expires_utc=expiry,
|
||||
)
|
||||
if self.per_secret_callback is not None:
|
||||
self.per_secret_callback(cookie)
|
||||
cookies.append(cookie)
|
||||
@@ -165,7 +167,7 @@ class FirefoxTriage:
|
||||
# Instead of disabling "delete" and removing the file manually,
|
||||
# in the future (py3.12) we could use "delete_on_close=False" as a cleaner solution
|
||||
# Related issue: #134
|
||||
fh = tempfile.NamedTemporaryFile(delete=False)
|
||||
fh = tempfile.NamedTemporaryFile(delete=False) # noqa: SIM115
|
||||
fh.write(key4_data)
|
||||
fh.seek(0)
|
||||
db = sqlite3.connect(fh.name)
|
||||
|
||||
@@ -23,7 +23,7 @@ def convert(low, high, lockout=False):
|
||||
time = ""
|
||||
tmp = 0
|
||||
|
||||
if low == 0 and high == -0x8000_0000 or low == 0 and high == -0x8000_0000_0000_0000:
|
||||
if (low == 0 and high == -0x8000_0000) or (low == 0 and high == -0x8000_0000_0000_0000):
|
||||
return "Not Set"
|
||||
if low == 0 and high == 0:
|
||||
return "None"
|
||||
|
||||
@@ -97,6 +97,7 @@ def proto_args(parser, parents):
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def get_conditional_action(baseAction):
|
||||
class ConditionalAction(baseAction):
|
||||
def __init__(self, option_strings, dest, **kwargs):
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
# Which in turn stole from Impacket :)
|
||||
# Code refactored and added to by @mjhallenbeck (Marshall-Hallenbeck on GitHub)
|
||||
|
||||
import logging
|
||||
|
||||
from impacket.dcerpc.v5 import transport, lsat, lsad, samr
|
||||
from impacket.dcerpc.v5.dtypes import MAXIMUM_ALLOWED
|
||||
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
|
||||
@@ -43,7 +41,7 @@ class SamrFunc:
|
||||
domains = self.samr_query.get_domains()
|
||||
members = {}
|
||||
if "Builtin" not in domains:
|
||||
logging.error("No Builtin group to query locally on")
|
||||
self.logger.error("No Builtin group to query locally on")
|
||||
return None
|
||||
|
||||
domain_handle = self.samr_query.get_domain_handle("Builtin")
|
||||
@@ -128,10 +126,10 @@ class SAMRQuery:
|
||||
dce.connect()
|
||||
dce.bind(samr.MSRPC_UUID_SAMR)
|
||||
except NetBIOSError as e:
|
||||
logging.error(f"NetBIOSError on Connection: {e}")
|
||||
self.logger.error(f"NetBIOSError on Connection: {e}")
|
||||
return None
|
||||
except SessionError as e:
|
||||
logging.error(f"SessionError on Connection: {e}")
|
||||
self.logger.error(f"SessionError on Connection: {e}")
|
||||
return None
|
||||
return dce
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ def proto_args(parser, parents):
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def get_conditional_action(baseAction):
|
||||
class ConditionalAction(baseAction):
|
||||
def __init__(self, option_strings, dest, **kwargs):
|
||||
|
||||
Generated
+29
-28
@@ -335,15 +335,15 @@ files = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bloodhound"
|
||||
name = "bloodhound-ce"
|
||||
version = "1.8.0"
|
||||
description = "Python based ingestor for BloodHound"
|
||||
description = "Python based ingestor for BloodHound Community Edition"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "bloodhound-1.8.0-py3-none-any.whl", hash = "sha256:97dcef77fa38dbab7219909c117eb9fd7263aff107cee0bf6fc7a0d0db9a61ac"},
|
||||
{file = "bloodhound-1.8.0.tar.gz", hash = "sha256:35ed0f1fdda2b1d79a4e9d891cabe2c55309a32743aeed16d885f3d809f409b3"},
|
||||
{file = "bloodhound_ce-1.8.0-py3-none-any.whl", hash = "sha256:0d5f39c2ab157448313f6c0ea8afdcf081238682f445c81e065684395ba5484b"},
|
||||
{file = "bloodhound_ce-1.8.0.tar.gz", hash = "sha256:f663d6181e2a1ab8de9d57948011662e2a47880d8caca9b85369a7efaed13a70"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -865,7 +865,7 @@ all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2
|
||||
|
||||
[[package]]
|
||||
name = "impacket"
|
||||
version = "0.13.0.dev0+20250422.104055.27bebb13"
|
||||
version = "0.13.0.dev0+20250513.162347.b7288f23"
|
||||
description = "Network protocols Constructors and Dissectors"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
@@ -888,9 +888,9 @@ six = "*"
|
||||
|
||||
[package.source]
|
||||
type = "git"
|
||||
url = "https://github.com/fortra/impacket.git"
|
||||
reference = "HEAD"
|
||||
resolved_reference = "27bebb1347569fa810e432326266acf17560f274"
|
||||
url = "https://github.com/zblurx/impacket.git"
|
||||
reference = "ldap_signing"
|
||||
resolved_reference = "b7288f233c154b6f73610797f8e0fa3a1541b3a9"
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
@@ -2018,29 +2018,30 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.292"
|
||||
description = "An extremely fast Python linter, written in Rust."
|
||||
version = "0.11.3"
|
||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "ruff-0.0.292-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:02f29db018c9d474270c704e6c6b13b18ed0ecac82761e4fcf0faa3728430c96"},
|
||||
{file = "ruff-0.0.292-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:69654e564342f507edfa09ee6897883ca76e331d4bbc3676d8a8403838e9fade"},
|
||||
{file = "ruff-0.0.292-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c3c91859a9b845c33778f11902e7b26440d64b9d5110edd4e4fa1726c41e0a4"},
|
||||
{file = "ruff-0.0.292-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f4476f1243af2d8c29da5f235c13dca52177117935e1f9393f9d90f9833f69e4"},
|
||||
{file = "ruff-0.0.292-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be8eb50eaf8648070b8e58ece8e69c9322d34afe367eec4210fdee9a555e4ca7"},
|
||||
{file = "ruff-0.0.292-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:9889bac18a0c07018aac75ef6c1e6511d8411724d67cb879103b01758e110a81"},
|
||||
{file = "ruff-0.0.292-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6bdfabd4334684a4418b99b3118793f2c13bb67bf1540a769d7816410402a205"},
|
||||
{file = "ruff-0.0.292-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa7c77c53bfcd75dbcd4d1f42d6cabf2485d2e1ee0678da850f08e1ab13081a8"},
|
||||
{file = "ruff-0.0.292-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e087b24d0d849c5c81516ec740bf4fd48bf363cfb104545464e0fca749b6af9"},
|
||||
{file = "ruff-0.0.292-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f160b5ec26be32362d0774964e218f3fcf0a7da299f7e220ef45ae9e3e67101a"},
|
||||
{file = "ruff-0.0.292-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ac153eee6dd4444501c4bb92bff866491d4bfb01ce26dd2fff7ca472c8df9ad0"},
|
||||
{file = "ruff-0.0.292-py3-none-musllinux_1_2_i686.whl", hash = "sha256:87616771e72820800b8faea82edd858324b29bb99a920d6aa3d3949dd3f88fb0"},
|
||||
{file = "ruff-0.0.292-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b76deb3bdbea2ef97db286cf953488745dd6424c122d275f05836c53f62d4016"},
|
||||
{file = "ruff-0.0.292-py3-none-win32.whl", hash = "sha256:e854b05408f7a8033a027e4b1c7f9889563dd2aca545d13d06711e5c39c3d003"},
|
||||
{file = "ruff-0.0.292-py3-none-win_amd64.whl", hash = "sha256:f27282bedfd04d4c3492e5c3398360c9d86a295be00eccc63914438b4ac8a83c"},
|
||||
{file = "ruff-0.0.292-py3-none-win_arm64.whl", hash = "sha256:7f67a69c8f12fbc8daf6ae6d36705037bde315abf8b82b6e1f4c9e74eb750f68"},
|
||||
{file = "ruff-0.0.292.tar.gz", hash = "sha256:1093449e37dd1e9b813798f6ad70932b57cf614e5c2b5c51005bf67d55db33ac"},
|
||||
{file = "ruff-0.11.3-py3-none-linux_armv6l.whl", hash = "sha256:cb893a5eedff45071d52565300a20cd4ac088869e156b25e0971cb98c06f5dd7"},
|
||||
{file = "ruff-0.11.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:58edd48af0e201e2f494789de80f5b2f2b46c9a2991a12ea031254865d5f6aa3"},
|
||||
{file = "ruff-0.11.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:520f6ade25cea98b2e5cb29eb0906f6a0339c6b8e28a024583b867f48295f1ed"},
|
||||
{file = "ruff-0.11.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1ca4405a93ebbc05e924358f872efceb1498c3d52a989ddf9476712a5480b16"},
|
||||
{file = "ruff-0.11.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f4341d38775a6be605ce7cd50e951b89de65cbd40acb0399f95b8e1524d604c8"},
|
||||
{file = "ruff-0.11.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72bf5b49e4b546f4bea6c05448ab71919b09cf75363adf5e3bf5276124afd31c"},
|
||||
{file = "ruff-0.11.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:9fa791ee6c3629ba7f9ba2c8f2e76178b03f3eaefb920e426302115259819237"},
|
||||
{file = "ruff-0.11.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c81d3fe718f4d303aaa4ccdcd0f43e23bb2127da3353635f718394ca9b26721"},
|
||||
{file = "ruff-0.11.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e4c38e9b6c01caaba46b6d8e732791f4c78389a9923319991d55b298017ce02"},
|
||||
{file = "ruff-0.11.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9686f5d1a2b4c918b5a6e9876bfe7f47498a990076624d41f57d17aadd02a4dd"},
|
||||
{file = "ruff-0.11.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4800ddc4764d42d8961ce4cb972bcf5cc2730d11cca3f11f240d9f7360460408"},
|
||||
{file = "ruff-0.11.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e63a2808879361aa9597d88d86380d8fb934953ef91f5ff3dafe18d9cb0b1e14"},
|
||||
{file = "ruff-0.11.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:8f8b1c4ae62638cc220df440140c21469232d8f2cb7f5059f395f7f48dcdb59e"},
|
||||
{file = "ruff-0.11.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3ea2026be50f6b1fbedd2d1757d004e1e58bd0f414efa2a6fa01235468d4c82a"},
|
||||
{file = "ruff-0.11.3-py3-none-win32.whl", hash = "sha256:73d8b90d12674a0c6e98cd9e235f2dcad09d1a80e559a585eac994bb536917a3"},
|
||||
{file = "ruff-0.11.3-py3-none-win_amd64.whl", hash = "sha256:faf1bfb0a51fb3a82aa1112cb03658796acef978e37c7f807d3ecc50b52ecbf6"},
|
||||
{file = "ruff-0.11.3-py3-none-win_arm64.whl", hash = "sha256:67f8b68d7ab909f08af1fb601696925a89d65083ae2bb3ab286e572b5dc456aa"},
|
||||
{file = "ruff-0.11.3.tar.gz", hash = "sha256:8d5fcdb3bb359adc12b757ed832ee743993e7474b9de714bb9ea13c4a8458bf9"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2462,4 +2463,4 @@ files = [
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = ">=3.10,<4.0"
|
||||
content-hash = "1b8bf07cb55b385df03716a6bd4a553579e1b325ccb53e2f77ce28605e6903c5"
|
||||
content-hash = "e748a99b7137fb81541ad5152dd98603856a2bfea56d7b9ff23913e950ea2f70"
|
||||
|
||||
+24
-22
@@ -21,7 +21,7 @@ dependencies = [
|
||||
"argcomplete>=3.1.4",
|
||||
"asyauth>=0.0.20",
|
||||
"beautifulsoup4>=4.11,<5",
|
||||
"bloodhound>=1.8.0",
|
||||
"bloodhound-ce>=1.8.0",
|
||||
"dploot>=3.1.0",
|
||||
"dsinternals>=1.2.4",
|
||||
"jwt>=1.3.1",
|
||||
@@ -44,7 +44,7 @@ dependencies = [
|
||||
"terminaltables>=3.1.0",
|
||||
"xmltodict>=0.13.0",
|
||||
# Git Dependencies
|
||||
"impacket @ git+https://github.com/fortra/impacket.git",
|
||||
"impacket @ git+https://github.com/zblurx/impacket.git@ldap_signing",
|
||||
"oscrypto @ git+https://github.com/wbond/oscrypto",
|
||||
"pynfsclient @ git+https://github.com/Pennyw0rth/NfsClient",
|
||||
]
|
||||
@@ -85,38 +85,40 @@ build-backend = "poetry_dynamic_versioning.backend"
|
||||
flake8 = "*"
|
||||
shiv = "*"
|
||||
pytest = "^7.2.2"
|
||||
ruff = "=0.0.292"
|
||||
ruff = "*"
|
||||
|
||||
[tool.ruff]
|
||||
select = [
|
||||
"E", "F", "D", "UP", "YTT", "ASYNC", "B", "A", "C4", "ISC", "ICN", "PIE", "PT",
|
||||
"Q", "RSE", "RET", "SIM", "TID", "ERA", "FLY", "PERF", "FURB", "LOG", "RUF"
|
||||
]
|
||||
ignore = [
|
||||
"E501", "F405", "D100", "D101", "D102", "D103", "D104", "D105", "D106",
|
||||
"D107", "D203", "D204", "D205", "D212", "D213", "D400", "D401", "D415",
|
||||
"D417", "D419", "RET503", "RET505", "RET506", "RET507", "RET508",
|
||||
"PERF203", "RUF012"
|
||||
]
|
||||
|
||||
# Allow autofix for all enabled rules (when `--fix`) is provided.
|
||||
fixable = ["ALL"]
|
||||
unfixable = []
|
||||
|
||||
target-version = "py310"
|
||||
exclude = [
|
||||
".bzr", ".direnv", ".eggs", ".git", ".git-rewrite", ".hg", ".mypy_cache",
|
||||
".nox", ".pants.d", ".pytype", ".ruff_cache", ".svn", ".tox", ".venv",
|
||||
"__pypackages__", "_build", "buck-out", "build", "dist", "node_modules", "venv"
|
||||
]
|
||||
per-file-ignores = {}
|
||||
line-length = 65000
|
||||
preview = true
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = [
|
||||
"E", "F", "D", "UP", "YTT", "ASYNC", "B", "A", "C4", "ISC", "ICN", "PIE", "PT",
|
||||
"Q", "RSE", "RET", "SIM", "TID", "ERA", "FLY", "PERF", "LOG", "RUF"
|
||||
]
|
||||
ignore = [
|
||||
"A004", "E501", "F405", "D100", "D101", "D102", "D103", "D104", "D105", "D106",
|
||||
"D107", "D203", "D204", "D205", "D212", "D213", "D400", "D401", "D413", "D415",
|
||||
"D417", "D419", "FURB", "RET503", "RET505", "RET506", "RET507", "RET508",
|
||||
"PERF203", "RUF012", "RUF052", "RUF059"
|
||||
]
|
||||
|
||||
# THE SETTINGS BELOW ARE DEFAULTS, left in here to override potential vs-code settings
|
||||
# Allow autofix for all enabled rules (when `--fix`) is provided.
|
||||
fixable = ["ALL"]
|
||||
unfixable = []
|
||||
per-file-ignores = {}
|
||||
|
||||
# Allow unused variables when underscore-prefixed.
|
||||
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
||||
|
||||
target-version = "py310"
|
||||
|
||||
[tool.ruff.flake8-quotes]
|
||||
[tool.ruff.lint.flake8-quotes]
|
||||
docstring-quotes = "double"
|
||||
inline-quotes = "double"
|
||||
multiline-quotes = "double"
|
||||
|
||||
@@ -152,8 +152,8 @@ netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M webdav -
|
||||
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M wifi
|
||||
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M winscp
|
||||
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M zerologon
|
||||
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M change-password -o NEWPASS=Password123
|
||||
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M change-password -o NEWNTHASH=58A478135A93AC3BF058A5EA0E8FDB71
|
||||
#netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M change-password -o NEWPASS=Password123
|
||||
#netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M change-password -o NEWNTHASH=58A478135A93AC3BF058A5EA0E8FDB71
|
||||
# test for multiple modules at once
|
||||
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M spooler -M petitpotam -M zerologon -M nopac -M enum_av -M enum_dns -M gpp_autologin -M gpp_password -M lsassy -M impersonate -M install_elevated -M ioxidresolver -M ms17-010 -M ntlmv1 -M runasppl -M uac -M webdav -M wifi -M coerce_plus
|
||||
##### SMB Anonymous Auth
|
||||
|
||||
@@ -38,7 +38,7 @@ def db_setup(db_engine):
|
||||
delete_workspace("test")
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def db(db_setup):
|
||||
yield db_setup
|
||||
db_setup.clear_database()
|
||||
|
||||
Reference in New Issue
Block a user