mirror of
https://github.com/Pennyw0rth/NetExec.git
synced 2026-11-07 09:26:01 -10:00
Merge pull request #1495 from aas-n/feat/reauth-swap-module
Add reauth_swap module: check cross-user SMB re-auth swap (enforced signing bypass viability)
This commit is contained in:
2 files changed
+130
No files matched your search
@@ -0,0 +1,129 @@
|
||||
import binascii
|
||||
import contextlib
|
||||
|
||||
from impacket import ntlm
|
||||
from impacket.smb3 import SMB2SessionSetup, SMB2SessionSetup_Response
|
||||
from impacket.smb3structs import SMB2_SESSION_SETUP
|
||||
from impacket.spnego import SPNEGO_NegTokenInit, SPNEGO_NegTokenResp, TypesMech
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
from nxc.helpers.smb import get_smb_connection
|
||||
|
||||
|
||||
class NXCModule:
|
||||
"""
|
||||
Module made by: Lyderic Lefebvre (@aas_s3curity) - https://blackfield.ch
|
||||
|
||||
Check if an SMB server still allows a cross-user re-authentication swap on an existing session:
|
||||
log in as one account, then re-authenticate as a different one on the same connection. That swap
|
||||
is the primitive an auth relay uses to keep a privileged session under a signing key it controls,
|
||||
defeating enforced SMB signing. Recent Windows builds allow same-user re-auth only and refuse the
|
||||
swap with ACCESS_DENIED. Not derivable from the build number, so it is probed live.
|
||||
https://blackfield.ch/blog/defeating-enforced-smb-signing/
|
||||
"""
|
||||
name = "reauth_swap"
|
||||
description = "Check if the server allows a cross-user re-auth swap on an SMB session (used to defeat enforced signing); needs a 2nd account"
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.ENUMERATION
|
||||
|
||||
STATUS_SUCCESS = 0x00000000
|
||||
STATUS_MORE_PROCESSING_REQUIRED = 0xC0000016
|
||||
STATUS_ACCESS_DENIED = 0xC0000022
|
||||
|
||||
def options(self, context, module_options):
|
||||
"""
|
||||
PROBE_USER second account, must differ from the login account (required)
|
||||
PROBE_PASS second account password
|
||||
PROBE_NTHASH second account NT hash (instead of PROBE_PASS)
|
||||
|
||||
The second account is probed over NTLM; its privileges do not matter.
|
||||
"""
|
||||
self.probe_user = module_options.get("PROBE_USER")
|
||||
self.probe_pass = module_options.get("PROBE_PASS", "")
|
||||
self.probe_nthash = module_options.get("PROBE_NTHASH", "")
|
||||
if not self.probe_user:
|
||||
context.log.fail("reauth_swap requires a second account: -o PROBE_USER=<user> PROBE_PASS=<pass> (must differ from the account nxc logged in with)")
|
||||
return False
|
||||
if not self.probe_pass and not self.probe_nthash:
|
||||
context.log.fail("reauth_swap requires PROBE_PASS or PROBE_NTHASH for the second account")
|
||||
return False
|
||||
|
||||
def on_login(self, context, connection):
|
||||
if self.probe_user.lower() == (connection.username or "").lower():
|
||||
context.log.fail("PROBE_USER must differ from the account nxc logged in with: only a cross-user swap is refused, so the same account always succeeds and tests nothing")
|
||||
return
|
||||
|
||||
# without enforced signing there is nothing to bypass: a relayed auth is accepted directly
|
||||
if not connection.signing:
|
||||
context.log.info("SMB signing not enforced, nothing to bypass - skipping")
|
||||
return
|
||||
|
||||
try:
|
||||
# fresh session so nxc's own connection is left untouched
|
||||
new_conn = get_smb_connection(context, connection, timeout=connection.args.smb_timeout)
|
||||
try:
|
||||
status = self.reauth_as(new_conn.getSMBServer(), self.probe_user, self.probe_pass, connection.domain, self.probe_nthash)
|
||||
finally:
|
||||
with contextlib.suppress(Exception):
|
||||
new_conn.close()
|
||||
except Exception as e:
|
||||
context.log.fail(f"could not probe this host: {e.__class__.__name__}: {e}")
|
||||
return
|
||||
|
||||
if status == self.STATUS_SUCCESS:
|
||||
context.log.highlight("VULNERABLE - enforced SMB signing bypassable")
|
||||
context.log.highlight("Next step: https://blackfield.ch/blog/defeating-enforced-smb-signing/")
|
||||
elif status == self.STATUS_ACCESS_DENIED:
|
||||
context.log.display("not vulnerable - cross-user re-auth swap refused (ACCESS_DENIED), same-user re-auth only")
|
||||
else:
|
||||
context.log.display(f"inconclusive - second session setup returned 0x{status:08X}")
|
||||
|
||||
def reauth_as(self, smb, user, password, domain, nthash):
|
||||
"""Replay an NTLM (Type1/Type3) session setup as `user` on an EXISTING session; return the NTSTATUS."""
|
||||
# Create NTLM Type1 message (challenge)
|
||||
type1 = ntlm.getNTLMSSPType1("", domain)
|
||||
blob = SPNEGO_NegTokenInit()
|
||||
blob["MechTypes"] = [TypesMech["NTLMSSP - Microsoft NTLM Security Support Provider"]]
|
||||
blob["MechToken"] = type1.getData()
|
||||
|
||||
setup = SMB2SessionSetup()
|
||||
setup["Flags"] = 0
|
||||
setup["SecurityMode"] = 1
|
||||
setup["SecurityBufferLength"] = len(blob.getData())
|
||||
setup["Buffer"] = blob.getData()
|
||||
pkt = smb.SMB_PACKET()
|
||||
pkt["Command"] = SMB2_SESSION_SETUP
|
||||
pkt["Data"] = setup
|
||||
|
||||
ans = smb.recvSMB(smb.sendSMB(pkt))
|
||||
if ans["Status"] != self.STATUS_MORE_PROCESSING_REQUIRED:
|
||||
return ans["Status"]
|
||||
|
||||
resp = SMB2SessionSetup_Response(ans["Data"])
|
||||
try:
|
||||
type2 = SPNEGO_NegTokenResp(resp["Buffer"])["ResponseToken"]
|
||||
except Exception:
|
||||
raw = resp["Buffer"]
|
||||
type2 = raw[raw.index(b"NTLMSSP"):]
|
||||
|
||||
lm = nt = ""
|
||||
if nthash:
|
||||
lm = binascii.unhexlify("aad3b435b51404eeaad3b435b51404ee")
|
||||
nt = binascii.unhexlify(nthash)
|
||||
|
||||
# Create NTLM Type3 message (authentication)
|
||||
type3, _ = ntlm.getNTLMSSPType3(type1, type2, user, password, domain, lmhash=lm, nthash=nt)
|
||||
blob2 = SPNEGO_NegTokenResp()
|
||||
blob2["ResponseToken"] = type3.getData()
|
||||
|
||||
setup2 = SMB2SessionSetup()
|
||||
setup2["Flags"] = 0
|
||||
setup2["SecurityMode"] = 1
|
||||
setup2["SecurityBufferLength"] = len(blob2.getData())
|
||||
setup2["Buffer"] = blob2.getData()
|
||||
|
||||
pkt2 = smb.SMB_PACKET()
|
||||
pkt2["Command"] = SMB2_SESSION_SETUP
|
||||
pkt2["Data"] = setup2
|
||||
|
||||
ans2 = smb.recvSMB(smb.sendSMB(pkt2))
|
||||
return ans2["Status"]
|
||||
@@ -154,6 +154,7 @@ netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M putty
|
||||
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M rdcman
|
||||
#netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M rdp -o ACTION=enable
|
||||
#netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M rdp -o ACTION=disable
|
||||
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M reauth_swap -o PROBE_USER=SECOND_USERNAME PROBE_PASS=SECOND_PASSWORD
|
||||
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M reg-query -o PATH=HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion KEY=DevicePath
|
||||
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M runasppl
|
||||
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M scuffy -o SERVER=127.0.0.1 NAME=test
|
||||
|
||||
Reference in new issue
Block a user