recon-and-host-discovery.md (31209B)
1 --- 2 title: "Stage 01 — Recon and Host Discovery" 3 description: "CPTS attack-flow reference for stage 01 — recon and host discovery in an authorised engagement." 4 category: pentest-workflow 5 subcategory: "CPTS Attack Flow" 6 order: 2 7 tags: ["htb", "cpts", "htb-attack-flow", "htb-attack-flow-stage-01", "pentest-workflow"] 8 tools: ["RustScan", "Nmap", "dig", "Kerberos"] 9 difficulty: intermediate 10 updated: "2026-09-17" 11 source: "vault:Pentest Attack Flow/02 - Stage 01 - Recon and Host Discovery.md" 12 --- 13 > [!dashboard] Attack-flow navigation 14 > **Dashboard:** [HTB Pentest Attack Flow](/sheets/pentest-workflow/attack-flow-dashboard) 15 > 16 > **Section:** 02 of 17 · **Focus:** Stage 01 — Recon and Host Discovery 17 > 18 > **Previous:** [Stage 00 — Passive External Recon](/sheets/pentest-workflow/passive-external-recon) · **Next:** [Stage 02 — Web Enumeration and Exploitation](/sheets/pentest-workflow/web-enumeration-and-exploitation) 19 20 --- 21 # 🛰️ STAGE 1 — Recon & Host Discovery 22 23 First contact. Get the box into `/etc/hosts` + `krb5.conf`, kill clock skew *before* anything Kerberos, then rustscan → nmap the whole surface. rustscan finds the ports in seconds, nmap tells me what they are. Everything downstream branches off this scan, so I scan wide first and read carefully. 24 25 > [!note] Env 26 > `$IP` `$DOMAIN` `$DC` `$LHOST` `$U` `$P` are already exported by the guide's setup block. On a fresh box I usually only know `$IP` at first — `$DOMAIN`/`$DC` get filled in from the scan (SMB/LDAP `ssl-cert`, `smb-os-discovery`) and from `/etc/hosts` below. 27 28 > [!abstract] MITRE ATT&CK — Recon / Discovery 29 > | Technique | Where it lands here | 30 > |---|---| 31 > | T1595 Active Scanning | nmap/rustscan/masscan against the perimeter | 32 > | T1595.001 Scanning IP Blocks | subnet sweeps, `-sn` discovery | 33 > | T1595.002 Vulnerability Scanning | `--script vuln`, nuclei later | 34 > | T1046 Network Service Discovery | *post-foothold* internal scanning (fscan section) | 35 > | T1018 Remote System Discovery | LAN host discovery (arp-scan/netdiscover) | 36 > | T1070.001 Timestomp-adjacent hygiene | clock sync avoids noisy Kerberos failures | 37 38 --- 39 40 ### 0. SETUP — hosts + realm + clock (do this first, every AD box) 41 42 **What to look for** → the DC's hostname/FQDN, the domain FQDN, and whether my clock matches the DC. Skip this and every Kerberos tool later throws `KRB_AP_ERR_SKEW`. 43 44 **Enumerate / prep** 45 ```bash 46 # Auto-populate /etc/hosts with DC + domain straight from SMB 47 sudo nxc smb $IP --generate-hosts-file /etc/hosts 48 49 # Manual fallback / add extra vhosts as I find them 50 echo "$IP $DC $DOMAIN dc01" | sudo tee -a /etc/hosts 51 52 # Generate a matching krb5.conf realm (needed for -k Kerberos auth later) 53 nxc smb $DC --generate-krb5-file krb5.conf 54 sudo cp krb5.conf /etc/krb5.conf 55 ``` 56 57 **Fix the clock** (measure the skew, then either sync or wrap) 58 ```bash 59 # Measure skew straight off AD services (DC-minus-you; positive = DC ahead) 60 nmap -p445,88 --script smb2-time,clock-skew -Pn $DC 61 ntpdate -q $DC # prints offset in seconds 62 nxc smb $IP | grep -i time # quick sanity check vs DC 63 64 # Option A — sync my whole host to the DC (simplest) 65 sudo ntpdate -u $DC 66 sudo apt install ntpdate -y # if missing 67 sudo rdate -n $DC # modern alt 68 sudo chronyd -q "server $DC iburst" # modern alt 69 70 # htpdate — sync over plain HTTP when NTP/UDP 123 is blocked by the VPN 71 sudo htpdate -s $DOMAIN # uses the web server's Date: header 72 73 # Option B — surgical: leave my clock alone, wrap only the tool that talks Kerberos 74 faketime -f '+7h30m' <kerberos-tool> # +offset if DC is ahead, - if behind 75 ``` 76 77 > [!warning] Watch out 78 > - **Clock skew is the #1 Kerberos killer.** Any `KRB_AP_ERR_SKEW` = re-run `ntpdate -u $DC`. Re-check if a box's time drifts mid-engagement. 79 > - `faketime` sign matters: nmap's `clock-skew` is **DC minus you** — positive means DC is *ahead*, push forward (`+`). Wrong sign *doubles* the skew. 80 > - Use `faketime -f` (follow) for anything Python/impacket — child procs don't inherit the fake clock without `-f`. 81 > - Writing `--generate-hosts-file /etc/hosts` needs `sudo`. `evil-winrm -r $DOMAIN` and all `-k` auth need the FQDN resolving here, so keep `/etc/hosts` current as vhosts surface. 82 > Deep dive: faketime-cheatsheet · tools: [faketime](https://github.com/wolfcw/libfaketime), [htpdate](https://github.com/angea/htpdate). 83 84 --- 85 86 ### 1. nmap flag reference (the whole arsenal) 87 88 > [!example] Host discovery 89 > | Flag | Meaning | Use | 90 > |---|---|---| 91 > | `-sn` | Ping sweep — no port scan | map a subnet without touching ports | 92 > | `-Pn` | Skip host discovery (treat all as up) | **HTB default** — ICMP usually blocked | 93 > | `-PS22,80,443` | TCP SYN ping on given ports | discovery when ICMP dead but TCP open | 94 > | `-PA80,443` | TCP ACK ping | slips past stateless ACLs that block SYN | 95 > | `-PU53,161` | UDP ping | rare; catches UDP-only hosts | 96 > | `-PE/-PP/-PM` | ICMP echo / timestamp / netmask | classic ping types | 97 > | `-n` | No reverse DNS | speed + silence; rDNS leaks your target in DNS logs anyway | 98 > | `--open` | Only show open ports | cuts `closed` noise on `-p-` sweeps | 99 > | `--reason` | Show *why* a port got its state | distinguishes RST vs timeout | 100 101 > [!example] Scan types 102 > | Flag | Meaning | Notes | 103 > |---|---|---| 104 > | `-sS` | SYN "half-open" scan | default with root; fast, classic | 105 > | `-sT` | Full TCP connect | no root / through proxies; **loudest** (full handshake logged) | 106 > | `-sU` | UDP scan | slow; pair with `--top-ports` | 107 > | `-sV` | Service/version detection | probes banners; enables version-intensity scripts | 108 > | `-sC` | Default NSE scripts (`--script=default`) | safe-ish, high signal | 109 > | `-A` | `-sV -sC -O` + traceroute | convenient, **noisy** | 110 > | `-O` | OS detection | needs open+closed port; `--osscan-guess` forces | 111 > | `-sN/-sF/-sX` | NULL/FIN/Xmas | FW evasion on *non-Windows* targets (Windows answers RST regardless) | 112 > | `-sA` | ACK scan | maps firewall *rules* (filtered vs unfiltered), not port state | 113 > | `--script <name>` | Run specific NSE script(s) | comma-sep, globs OK: `"smb-* and not smb-brute"` | 114 115 > [!example] Timing, ports, output 116 > | Flag | Meaning | 117 > |---|---| 118 > | `-T0` … `-T5` | paranoid / sneaky / polite / normal / aggressive / insane — `-T4` is the lab default, `-T0/-T1` for IDS dodging (very slow) | 119 > | `--min-rate 1000` / `--max-rate 500` | packets/sec floor/ceiling — the real speed knob | 120 > | `--min-parallelism` / `--max-retries 2` | concurrency / retry cap for flaky links | 121 > | `-p-` | all 65535 TCP ports | 122 > | `-p80,443,8000-9000` | explicit list/range | 123 > | `--top-ports 100` | nmap's statistically-most-common ports | 124 > | `-F` | fast mode (~100 most common) | 125 > | `-oA basename` | **all three** outputs: `.nmap` `.gnmap` `.xml` — always use it | 126 > | `-oX file.xml` | XML (for ndiff / xsltproc / imports) | 127 > | `-oG file.gnmap` | greppable (shell one-liners) | 128 > | `-v` / `-vv` | verbosity; `--stats-every 15s` progress lines | 129 > | `--resume file.nmap` | resume an interrupted scan | 130 131 > [!example] Evasion / spoofing 132 > | Flag | Meaning | Pitfall | 133 > |---|---|---| 134 > | `-f` | Fragment IP packets (8-byte) | modern IDS reassembles; breaks some services' replies | 135 > | `--mtu 16` | Custom fragmentation (multiple of 8) | controlled version of `-f` | 136 > | `-D RND:10,ME` | Decoy scan — target sees N fake sources + you | doesn't hide you, just dilutes; never use on client reports without ROE | 137 > | `-S <IP>` | Spoof source address | replies go to the spoofed IP — you won't see results unless you can sniff them | 138 > | `--source-port 53` | Fixed source port | sneaks past lazy "allow UDP/53 out" firewall rules (also `-g 53`) | 139 > | `--proxies http://...` | Route through proxies | forces `-sT`, slow | 140 > | `--data-length 24` | Append random payload | defeats "packet size signature" detection | 141 > | `--spoof-mac 0` | Random MAC | LAN/VMnet only | 142 > | `-sI zombie:port` | Idle scan (fully blind) | needs idle zombie with predictable IP-ID — rare in practice, exam-famous | 143 144 --- 145 146 ### 2. NSE — categories and the scripts I actually run 147 148 **Categories** (use with `--script <cat>`): `auth` · `broadcast` · `brute` (**lockout risk**) · `default` (= `-sC`) · `discovery` · `dos` (never in prod) · `exploit` · `external` (queries whois/virustotal) · `fuzzer` · `intrusive` · `malware` · `safe` · `version` · `vuln`. 149 150 > [!example] High-signal scripts by service 151 > | Port/Service | Scripts | Gets you | 152 > |---|---|---| 153 > | 21 FTP | `ftp-anon,ftp-syst` | anonymous login, OS hints | 154 > | 22 SSH | `ssh2-enum-algos,ssh-hostkey,ssh-auth-methods` | weak algos, password vs key auth | 155 > | 53 DNS | `dns-zone-transfer,dns-recursion,dns-nsid` | AXFR, open resolver | 156 > | 80/443 HTTP | `http-title,http-headers,http-server-header,http-methods,http-enum` | title, put/del methods, common paths | 157 > | 445 SMB | `smb-os-discovery,smb2-security-mode,smb-shares,smb-protocols` | OS/domain, signing, SMBv1 | 158 > | 389/636 LDAP | `ldap-rootdse,ldap-search` | naming contexts = domain FQDN | 159 > | 88 Kerberos | `krb5-enum-users` | pre-auth user enum (see §9) | 160 > | 1433 MSSQL | `ms-sql-info,ms-sql-ntlm-info` | version, NTLM leak | 161 > | 3389 RDP | `rdp-enum-encryption,rdp-ntlm-info` | NLA state, NTLM info | 162 > | 5985 WinRM | `http-title` + service banner | confirms WinRM vs web app | 163 > | any TLS | `ssl-cert,ssl-enum-ciphers` | **CN/SANs leak hostnames** → vhosts | 164 > | vuln sweep | `"vuln,vulners" --script-args mincvss=7.0` | high-sev hits without the noise | 165 166 ```bash 167 # target one host with a service menu 168 nmap -Pn -p445 --script "smb-* and not smb-brute and not smb-flood" $IP 169 # script args 170 nmap -Pn -p80 --script http-enum --script-args http-enum.basepath='/app/' $IP 171 ``` 172 173 --- 174 175 ### 3. rustscan → nmap handoff (the fast path) 176 177 **What to look for** → every open TCP port, then default-script + version detail on exactly those ports. This is my default first scan. 178 179 **Enumerate** 180 ```bash 181 # rustscan auto-feeds the open ports into nmap (-sCV = -sC -sV), saves all formats 182 rustscan -a $IP -u 50000 -r 1-65535 -- -sCV -oA ./recon/target 183 184 # Windows / no-ping / firewalled targets — force it through and give it more room 185 rustscan -a $IP -u 50000 -r 1-65535 -t 3000 -b 1000 -- -Pn -sCV --max-retries 3 -T4 186 187 # Quick port-only discovery when I just want the list (no nmap) 188 rustscan -a $IP -q 189 ``` 190 191 > [!warning] Watch out 192 > **The #1 rustscan mistake: forgetting `-Pn` on the *nmap* side.** rustscan already proved the port open via raw TCP connect — but if nmap's own ping then fails (ICMP blocked, normal on HTB), nmap reports "0 hosts up" and you lose every result. Always append `-Pn` after `--` on Windows/firewalled boxes. `-- -A` piped through rustscan is still fast because nmap only re-touches the *already-open* ports, never the full 65535. Second rustscan gotcha: on a busy VPN, the default `-b` (batch size) floods and *misses* ports — if results look thin, re-run with `-b 500`. 193 194 --- 195 196 ### 4. masscan & naabu — subnet-scale discovery 197 198 **masscan** — asynchronous raw-socket scanner, internet-scale speeds. Different engine from nmap; results differ. 199 ```bash 200 sudo masscan -p1-65535 $IP --rate=1000 -oG masscan-all.gnmap # whole box, capped rate 201 sudo masscan -p80,443,445,3389 10.10.110.0/24 --rate=2000 # subnet sweep 202 # gotchas: --rate is pps (10000+ melts HTB VPN and DROPS results); raw sockets bypass the 203 # OS stack → responses can be eaten by your own firewall unless you: sudo iptables -A INPUT -p tcp --dport 60000 -j DROP (and --source-port 60000) 204 # masscan finds ports only — hand the list to nmap for -sCV (same two-stage pattern as rustscan) 205 ``` 206 207 **naabu** — ProjectDiscovery's fast SYN scanner; designed to pipe into httpx/nuclei. 208 ```bash 209 sudo naabu -host $IP -p - -rate 1000 -o naabu.txt # full range 210 naabu -host $IP -top-ports 1000 # quick triage 211 # gotchas: needs root for SYN (else falls back to connect); -p - means all ports; 212 # pair with -verify to drop the false positives raw-SYN scans love 213 ``` 214 215 > [!tip] Which scanner when 216 > Single box, I want detail now → **rustscan → nmap**. A whole /24 → **masscan or naabu** for ports, then nmap `-sCV` on the hits. Exam boxes → plain two-stage nmap is plenty. Whatever finds the ports, **nmap owns the service/version truth**. 217 218 --- 219 220 ### 5. Two-stage nmap (when I want full manual control) 221 222 **What to look for** → same result as the handoff, but I own the exact nmap command (custom scripts, output paths, timing). Use when rustscan's auto-invocation gets in the way. 223 224 **Enumerate** 225 ```bash 226 # Stage 1: find open ports fast, comma-join them 227 ports=$(nmap -p- --min-rate=1000 -T4 --open -oG - $IP | grep -oP '\d+(?=/open)' | paste -sd,) 228 229 # Stage 2: deep scan only those ports 230 nmap -p $ports -sCV -T4 -Pn $IP -oA ./recon/detailed 231 232 # Same idea straight from rustscan's greppable output 233 ports=$(rustscan -a $IP -g | grep -oP '\[\K[^\]]+') 234 nmap -sC -sV -Pn -p $ports $IP -oA ./recon/detailed 235 ``` 236 237 > [!tip] Long `-p-` scan? Don't sit and stare. 238 > Tap **spacebar** any time for an instant progress line (`% complete` + ETA), or run with `--stats-every 15s`. If it dies (SSH drop, Ctrl+C): `nmap --resume ./recon/detailed.nmap` picks up where it stopped — no restart from zero. 239 240 --- 241 242 ### 6. Deep TCP + version + default/vuln scripts 243 244 **What to look for** → confirmed service/product/version per port, OS guess, and any obvious high-severity CVE. Treat a version match as a *lead*, not proof. 245 246 **Enumerate** 247 ```bash 248 # Privileged full-TCP SYN inventory with reasons (fast internal starting point) 249 sudo nmap -sS -Pn -n -p- --open --reason --min-rate 1000 --max-retries 2 $IP -oA ./recon/01-tcp-all 250 251 # Service + default scripts + OS on the ports that came back 252 sudo nmap -sS -Pn -n -sV -sC -O --reason -p $ports $IP -oA ./recon/02-services 253 254 # Exhaust probes when a banner stays unknown 255 nmap -Pn -n -sV --version-all -p <port> $IP 256 ``` 257 258 **Attack (surface for weak points)** 259 ```bash 260 # High-severity vuln scripts only (mincvss filters the noise) 261 nmap -sV --script "vuln,vulners" --script-args mincvss=7.0 -p $ports $IP -oA ./recon/vuln 262 ``` 263 264 > [!warning] Watch out 265 > `-A` is **not** `-p-` — it's OS + version + default NSE + traceroute on the ports you gave it, nothing more. Keep intent explicit with `-sV -sC -O`. `tcpwrapped` in the output = connection accepted then instantly closed (ACL/wrapper), not a real banner — re-test from the expected source. `filtered` describes *my vantage point*, not the target — add `--reason`. 266 267 --- 268 269 ### 7. Top-UDP pass (DNS / SNMP / NFS / TFTP hide here) 270 271 **What to look for** → UDP services TCP scans never show. On AD/Linux boxes DNS(53), SNMP(161), NFS-adjacent(111), NTP(123) matter. 272 273 **Enumerate** 274 ```bash 275 # Quick triage 276 sudo nmap -sU --top-ports 50 $IP 277 278 # Focused high-value UDP with version probes 279 sudo nmap -sU -Pn -n -sV --reason \ 280 -p53,67,68,69,111,123,137,161,162,500,514,623,1434,1900,4500,5353 $IP -oA ./recon/04-udp 281 ``` 282 283 | UDP port | Service | Why I care | 284 |---|---|---| 285 | 53 | DNS | zone transfer, recursion, version | 286 | 67/68 | DHCP | rogue-DHCP angle on the LAN | 287 | 69 | TFTP | anonymous config/firmware pulls | 288 | 111 | rpcbind | NFS export discovery (→ 2049) | 289 | 123 | NTP | clock sync + `ntp-monlist` amplification history | 290 | 137 | NetBIOS-NS | hostname/domain without SMB | 291 | 161/162 | SNMP | **community `public` = full device census** (→ [Stage 03](/sheets/pentest-workflow/service-enumeration)) | 292 | 500/4500 | IKE/IPsec | VPN endpoint, `ike-scan` | 293 | 1434 | MSSQL browser | instance names + ports without a login | 294 | 1900/5353 | SSDP/mDNS | device inventory, [Responder](https://github.com/lgandx/Responder) targets | 295 296 > [!warning] Watch out 297 > UDP silence reads as `open|filtered` — ambiguous by design. Add `-sV` so nmap sends protocol-aware payloads and can promote ambiguous ports to confirmed `open`. It's slow; that's why it's a separate pass, not bolted onto the TCP scan. `--top-ports 50` on UDP covers ~90% of what ever matters; a UDP `-p-` is an overnight job and almost never worth it. 298 299 --- 300 301 ### 8. IPv6 — the forgotten surface 302 303 **What to look for** → targets dual-stacked with an *unhardened* v6 service set (firewall rules often only cover v4). 304 305 ```bash 306 # v6 scanning is explicit — nmap will NOT scan v6 unless asked 307 ping6 -c2 $TARGET_V6 308 sudo nmap -6 -sS -Pn -n -p- --open $TARGET_V6 -oA ./recon/tcp6 309 # link-local discovery on a LAN you have a foothold in: 310 ping6 -c2 ff02::1%eth0 # all-nodes multicast → live v6 neighbors 311 ip -6 neigh # kernel's discovered v6 neighbors 312 ``` 313 314 > [!note] In AD, v6 is usually DHCP-managed but **DNS is v4** — that's the [mitm6](https://github.com/dirkjanm/mitm6) angle; see [Stage 10](/sheets/pentest-workflow/lateral-movement-pivoting-and-loot). Here in Stage 01 just note whether the target even answers on v6 — a `::` in `ip addr` on a foothold means check it. 315 316 --- 317 318 ### 9. Host discovery on the LAN (post-foothold / internal range) 319 320 **What to look for** → live neighbors before port-scanning them. ARP can't be filtered on the local segment — it's the ground truth. 321 322 ```bash 323 # ARP-based (local subnet only, undetectable-by-firewall, fast) 324 sudo arp-scan -l -I eth0 # auto subnet 325 sudo arp-scan 10.10.110.0/24 | tee arp.txt 326 sudo netdiscover -i eth0 -r 10.10.110.0/24 -P # passive-capable, live table 327 328 # ICMP/TCP fallback across routed segments 329 fping -asgq 10.10.110.0/24 # alive hosts, one line each 330 nmap -sn -PS22,80,135,445,3389 10.10.110.0/24 -oA ./recon/lan-discover 331 ``` 332 333 > [!tools] Internal all-in-one: fscan 334 > [fscan_windows_x64.exe](/downloads/pentest-workflow/fscan_windows_x64.exe) ([SHA-256](/downloads/pentest-workflow/fscan_windows_x64.exe.sha256) · [GPG signature](/downloads/pentest-workflow/fscan_windows_x64.exe.sha256.asc)) 335 > 336 > [fscan](https://github.com/shadow1ng/fscan) sweeps a segment for hosts, ports, and weak services in one shot from a compromised Windows box — MS17-010 check, SSH/SMB/RDP/MSSQL/Redis brute (careful), web title grab. 337 > ```powershell 338 > # upload to the foothold, then: 339 > .\fscan_windows_x64.exe -h 10.10.110.0/24 -np -no -nopoc -o fscan-result.txt 340 > # -np skip ping (if ICMP is filtered), -no don't save default log, -nopoc skip web poc checks (quieter) 341 > .\fscan_windows_x64.exe -h 10.10.110.5 -p 1-65535 # deep on one juicy host 342 > ``` 343 > **OPSEC:** fscan is loud and well-signatured (AV eats it on sight — expect to need exclusion or obfuscation). It's a *lab/internal* convenience, not a stealth tool. For monitored networks prefer native nmap through a [Stage 10](/sheets/pentest-workflow/lateral-movement-pivoting-and-loot) socks proxy. 344 345 --- 346 347 ### 10. Vhosts & DNS → feed `/etc/hosts` 348 349 **What to look for** → hostnames the IP scan hints at but doesn't route: cert CN/SANs, HTTP redirects/titles, DNS zone data, and reverse-PTR records. Every new name goes into `/etc/hosts` so web + Kerberos work. 350 351 **Enumerate** 352 ```bash 353 # Reverse PTR lookup — ask the box's own DNS server to resolve its own IP. 354 # On plenty of HTB/AD boxes the reverse zone is auto-populated for the DC, 355 # so this hands you $DOMAIN from nothing but $IP, before nmap even finishes. 356 dig -x $IP @$IP +noall +answer 357 # 166.11.10.10.in-addr.arpa. 604800 IN PTR trick.htb. 358 359 # Names leak from TLS certs and HTTP on web ports 360 nmap -p443,8443 --script=ssl-cert $IP 361 nmap -sV -p80,443,8080,8443 --script=http-title,http-headers,http-server-header $IP 362 363 # DNS zone transfer (AXFR) → instant subdomain dump when it works 364 nmap -p53 --script=dns-zone-transfer --script-args dns-zone-transfer.domain=$DOMAIN $IP 365 dig axfr $DOMAIN @$IP 366 367 # Add whatever surfaces 368 echo "$IP $DOMAIN www.$DOMAIN mail.$DOMAIN" | sudo tee -a /etc/hosts 369 ``` 370 371 > [!tip] Reading the PTR answer 372 > `dig -x` is shorthand for a `PTR` query against `<reversed-octets>.in-addr.arpa` — `@$IP` points it at the target's *own* resolver since that's usually the only DNS server that has the zone. The FQDN in the `ANSWER` section (`trick.htb.` above) is your `$DOMAIN` — export it and go straight to §0's `/etc/hosts`/krb5.conf setup. No PTR record / `NXDOMAIN` is common and means try TLS-cert CNs or AXFR instead, not that something's broken. 373 374 > [!note] If a web port serves a different site per `Host:` header, that's vhost routing — fuzz it in STAGE 2 (`ffuf ... -H "Host: FUZZ.$DOMAIN"`). Zone transfer denied is the *expected* result on a sane DNS server, not an error. Port→service reference: Common Ports and Services Cheatsheet 2026. 375 376 **SRV records — AD publishes its own service map, unauthenticated** 377 378 **What to look for** → every DC, the PDC emulator, and Global Catalog servers, straight from DNS — no LDAP bind or Kerberos ticket needed. 379 380 ```bash 381 # _msdcs is the AD-specific subzone; these four cover 95% of what you need 382 dig srv _ldap._tcp.dc._msdcs.$DOMAIN @$DC +short # every domain controller 383 dig srv _kerberos._tcp.dc._msdcs.$DOMAIN @$DC +short # every KDC (usually == DC list) 384 dig srv _ldap._tcp.pdc._msdcs.$DOMAIN @$DC +short # the PDC emulator specifically 385 dig srv _gc._tcp.$DOMAIN @$DC +short # Global Catalog (multi-DC forest only) 386 387 # nslookup form — same data, useful from a Windows foothold where dig isn't installed 388 nslookup -type=srv _ldap._tcp.dc._msdcs.$DOMAIN $DC 389 ``` 390 391 > [!tools] dnsrecon / adidnsdump 392 > [dnsrecon](https://github.com/darkoperator/dnsrecon) automates the whole DNS pass above in one command: 393 > ```bash 394 > dnsrecon -d $DOMAIN -n $DC -t std,srv,axfr # std = A/AAAA/MX/NS/TXT/SOA, srv = the AD SRV set, axfr = zone transfer attempt 395 > ``` 396 > [adidnsdump](https://github.com/dirkjanm/adidnsdump) is a different job — it needs **any** authenticated domain account (any tier) and dumps the *entire* AD-integrated DNS zone via LDAP, not just the public SRV/PTR subset. Revisit once creds land in [Stage 04](/sheets/pentest-workflow/active-directory-enumeration): 397 > ```bash 398 > adidnsdump -u $DOMAIN\$U -p $P $DC # writes records.csv — every internal name, including ones with no PTR 399 > ``` 400 401 > [!tip] Why bother when nmap already found the DC 402 > A single-DC lab box makes SRV records feel redundant — but on a multi-DC forest they're how you find the **PDC emulator** (the one DC that matters for password/lockout state and time sync) and the **Global Catalog** (needed for forest-wide LDAP searches in Stage 04) without guessing which of five DCs to point tools at. `_msdcs.$DOMAIN` is also queryable straight off an external resolver on internet-facing AD — same passive-recon value as the PTR trick in §8 of [Stage 00](/sheets/pentest-workflow/passive-external-recon). 403 404 --- 405 406 ### 11. Kerberos on 88 = it's AD (tell + first users) 407 408 **What to look for** → port 88 open means Domain Controller. I can enumerate valid domain users pre-auth without creds, straight off the scan — hands STAGE 2 a userlist. 409 410 **Enumerate** 411 ```bash 412 # Valid-user enum via Kerberos pre-auth (realm MUST be uppercase) 413 nmap -p88 --script=krb5-enum-users \ 414 --script-args krb5-enum-users.realm=${DOMAIN^^},userdb=/usr/share/seclists/Usernames/top-usernames-shortlist.txt $DC 415 416 # Re-confirm skew here too — 88 is exactly where KRB_AP_ERR_SKEW bites 417 nmap -p445,88 --script smb2-time,clock-skew -Pn $DC 418 ``` 419 420 > [!warning] Watch out 421 > The krb5 realm is **case-sensitive and uppercase** (`${DOMAIN^^}`) — lowercase silently returns nothing. Enum doesn't trigger account lockout (pre-auth only) but pre-auth failures do log (Event 4768/4771). For the heavier userlist sweep hand off to `kerbrute userenum` in STAGE 2 — see Kerbrute (binaries staged in [Stage 00 §12](/sheets/pentest-workflow/passive-external-recon)). NSE per-port script menus live in NSE Guide; the full nmap flag arsenal (timing knobs, evasion, idle scan) in Nmap Cheatsheet 2026. 422 423 --- 424 425 ### 12. SMB / LDAP / DNS quick wins (bridge to Stages 03–04) 426 427 First-auth probes that answer *"is this worth an hour?"* in 60 seconds: 428 429 ```bash 430 # SMB — domain, hostname, OS, signing, null session in one line each 431 nxc smb $IP 432 nxc smb $IP -u '' -p '' --shares # null session → shares? 433 smbclient -N -L //$IP/ 2>/dev/null 434 435 # LDAP — anonymous bind dumps the domain naming context (sometimes more) 436 ldapsearch -x -H ldap://$DC -s base namingcontexts 437 nxc ldap $DC -u '' -p '' --users # occasionally wins a full user list 438 439 # DNS — adidnsdump-style record pull if we have ANY creds later; for now just AXFR + version 440 dig axfr $DOMAIN @$DC; dig version.bind chaos txt @$DC 441 ``` 442 443 > [!tip] Handoff logic 444 > SMB signing **disabled** → relay potential ([Stage 10](/sheets/pentest-workflow/lateral-movement-pivoting-and-loot)). Null shares → [Stage 03](/sheets/pentest-workflow/service-enumeration). Any creds → full AD enum in [Stage 04](/sheets/pentest-workflow/active-directory-enumeration). 88/389/445 on one box → it *is* the DC: fix `/etc/hosts`, krb5.conf, and clock before anything else. 445 446 --- 447 448 ### 13. Web screenshot triage — see every web port at once 449 450 When the scan comes back with a dozen HTTP(S) ports, eyeball them all in one pass instead of curling each. 451 452 > [!tools] Stage this 453 > [gowitness_linux_amd64](/downloads/pentest-workflow/gowitness_linux_amd64) ([SHA-256](/downloads/pentest-workflow/gowitness_linux_amd64.sha256) · [GPG signature](/downloads/pentest-workflow/gowitness_linux_amd64.sha256.asc)) 454 > [gowitness_windows_amd64.exe](/downloads/pentest-workflow/gowitness_windows_amd64.exe) ([SHA-256](/downloads/pentest-workflow/gowitness_windows_amd64.exe.sha256) · [GPG signature](/downloads/pentest-workflow/gowitness_windows_amd64.exe.sha256.asc)) 455 > 456 > [gowitness](https://github.com/sensepost/gowitness) — headless-Chrome screenshotter. Alternative: [EyeWitness](https://github.com/RedSiege/EyeWitness) (Python, classifies login panels/default pages automatically). 457 > 458 > ```bash 459 > # scan-straight-from-nmap: gowitness reads the -oX XML and shoots every http service 460 > gowitness scan nmap -f ./recon/detailed.xml --open --service-contains http --screenshot-path ./shots 461 > # or from a URL list (v3 syntax — old `gowitness single/file` is deprecated) 462 > gowitness scan file -f urls.txt --screenshot-path ./shots -t 8 463 > # review everything in the built-in gallery (defaults to 127.0.0.1:7171) 464 > gowitness report server 465 > ``` 466 467 > [!tip] CPTS exam tip 468 > Screenshot *before* deep enumeration. The gallery instantly surfaces login portals, default pages (IIS welcome = unpatched vibe), directory listings, and camera/printer panels you'd waste 20 minutes identifying via `curl -I`. Log every interesting panel in the notes with its port — Stage 02 fuzzing targets come from this view. 469 470 --- 471 472 ### 14. Results hygiene — parse, convert, keep receipts 473 474 **What to look for** → durable, greppable, diffable scan output. Future-you (and the report) needs it. 475 476 ```bash 477 NMAP_FILE=./recon/detailed.gnmap 478 479 # Host + count of open ports 480 egrep -v "^#|Status: Up" $NMAP_FILE | cut -d' ' -f2,4- | \ 481 sed -n -e 's/Ignored.*//p' | \ 482 awk -F, '{split($0,a," "); printf "Host: %-20s Ports Open: %d\n" , a[1], NF}' | sort -k 5 -g 483 484 # Clean table: state / proto/port / service 485 egrep -v "^#|Status: Up" $NMAP_FILE | cut -d' ' -f2,4- | sed -n -e 's/Ignored.*//p' | \ 486 awk '{print "Host: " $1 " Ports: " NF-1; $1=""; for(i=2; i<=NF; i++) { a=a" "$i; }; split(a,s,","); for(e in s) { split(s[e],v,"/"); printf "%-8s %s/%-7s %s\n" , v[2], v[3], v[1], v[5]}; a="" }' 487 488 # Top service versions across the scan 489 egrep -v "^#|Status: Up" $NMAP_FILE | cut -d ' ' -f4- | tr ',' '\n' | \ 490 sed -e 's/^[ \t]*//' | awk -F '/' '{print $7}' | grep -v "^$" | sort | uniq -c | sort -k 1 -nr 491 492 # one-liner: just the open port numbers (feed to -p or other tools) 493 grep -oP '\d+/open' $NMAP_FILE | cut -d/ -f1 | sort -n | uniq | paste -sd, 494 495 # XML → readable HTML report for the evidence folder 496 xsltproc ./recon/detailed.xml -o ./recon/detailed.html 497 498 # diff two scans (long engagements — new ports = new surface) 499 ndiff ./recon/baseline.xml ./recon/current.xml 500 ``` 501 502 > [!tip] XML over greppable for anything durable — `-oX` retains full host/port/service/NSE structure and diffs across time with `ndiff baseline.xml current.xml` (catches new attack surface on long boxes). Greppable is for quick shell work in the moment. Full recipes: Awesome NMAP grep. 503 504 > [!warning] /etc/hosts discipline 505 > - One line per IP, FQDN **first**, then short names: `$IP dc01.$DOMAIN dc01 $DOMAIN` — Kerberos picks the first match. 506 > - Never duplicate an IP with conflicting names from two boxes; comment out old entries, don't delete (audit trail). 507 > - After every vhost discovery (§10), append immediately — "I'll remember it" is how you lose 30 minutes to `curl` returning the wrong vhost. 508 509 --- 510 511 ### 15. AutoRecon — the "do all of this" wrapper 512 513 [AutoRecon](https://github.com/Tib3rius/AutoRecon) chains everything above: full-port discovery → per-service deep scans → web screenshots → feroxbuster kicks, all into `results/<IP>/` folders. 514 ```bash 515 autorecon $IP --dirbuster.threads 50 -o ./autorecon-out 516 # --single-target for one box at a time; -p/-t/-ct to shape port/timing 517 ``` 518 > [!note] Use it to *check coverage*, not to think for you. On CPTS boxes AutoRecon's feroxbuster/vhost stages are exactly the Stage 02 work — great safety net, but know what every spawned command does; "AutoRecon didn't find it" is not a strategy. 519 520 --- 521 522 > [!failure] Detection & OPSEC — assume the scan is seen 523 > | What I run | What the blue team sees | 524 > |---|---| 525 > | `-sS` SYN scan | half-open connections; IDS signature on rate/sequencing — fast scans at `-T4`/high `--min-rate` trip thresholds instantly | 526 > | `-sT` connect scan | full handshakes + `SYN` + app-layer logs on every service — **double visibility**, use only when rootless | 527 > | rustscan/masscan/naabu | extreme pps → NetFlow/IDS alarm regardless of flags | 528 > | `-sC`/`vuln` scripts | real probes hitting app logs (404 storms, malformed requests) — far noisier than the port scan itself | 529 > | `-D` decoys | dilutes, doesn't anonymize — source list still includes you | 530 > | `-f`/`--mtu`, `-T0`, `--source-port 53` | classic evasions; help vs. naïve IDS, reassembled/caught by anything modern | 531 > 532 > On HTB/CPTS: noise is free — optimize for speed and completeness. On a monitored engagement: agree scan windows, throttle (`-T2`/`--max-rate`), scan from the expected source, and **log your source IP + timestamps** for SOC deconfliction (T1595 attribution). 533 534 --- 535 536 > [!tip] Where this feeds 537 > Open 445/139/389/88 → **STAGE 2 AD enum** (netexec, bloodhound). Open 80/443/8080 → **web** (ffuf/feroxbuster). Open 53/111/2049/6379 → **Linux service** detours. The full phased map: [Attack-Flow-Guide](/sheets/pentest-workflow/attack-flow-guide) · [Most-Used-Commands](/sheets/pentest-workflow/most-used-commands). 538 --- 539 540 > [!navigation] Continue the attack flow 541 > **Previous:** [Stage 00 — Passive External Recon](/sheets/pentest-workflow/passive-external-recon) 542 > 543 > **Dashboard:** [HTB Pentest Attack Flow](/sheets/pentest-workflow/attack-flow-dashboard) 544 > 545 > **Next:** [Stage 02 — Web Enumeration and Exploitation](/sheets/pentest-workflow/web-enumeration-and-exploitation)