Linux Privilege Escalation: Basics & Exploitation
Kill chains for sudo abuse, SUID/capabilities, PATH hijacking, cron exploitation, NFS no_root_squash, and runtime process hunting with pspy. Built for Red Teamers and CTF players who skip the theory.
::NOPASSWD → Root Shell
Why it works: The binary executes with root's effective UID, giving you root-level file access regardless of your own privileges.
Every NOPASSWD binary is a potential root shell. Cross-reference immediately at GTFOBins → sudo.
Don't stop at the obvious ones. cat, less, cp, tee, dd — anything that can read or
write arbitrary files as root can be used to compromise /etc/shadow, /etc/passwd, or deploy
SSH keys. nmap, vim, find, perl, python, awk each have direct shell escape entries.
::Application Function Abuse
When a binary has no GTFOBins entry, look for flags that accept a file path as input. Apache2 is the canonical example:
Why it works: Apache's -f flag loads an alternate config file. When pointed at /etc/shadow, it fails to parse — but the error message prints the first line of the file, which is the root hash.
::LD_PRELOAD Kill Chain
I've written about this vector repeatedly on LinkedIn because it keeps surfacing in real
engagements. env_keep+=LD_PRELOAD in production sudoers files is far more common than
defenders realize — and it's trivially exploitable. Do not skip this check.
Precondition: sudo -l output shows env_keep+=LD_PRELOAD alongside at least one NOPASSWD binary.
Why it works: env_keep preserves LD_PRELOAD across the privilege boundary. The shared library is loaded into the sudo-launched process before main() runs, so _init() executes as root.
Phase 1 — Write the Payload
unsetenv("LD_PRELOAD") is critical — without it, the shell it spawns will also try to preload your library and loop or crash.
Phase 2 — Compile as Shared Object
- ▹
-fPIC— position-independent code (required for shared objects) - ▹
-shared— produce a.soinstead of an executable - ▹
-nostartfiles— skip the standard C startup routines (since we're using_init)
Phase 3 — Execute
Any NOPASSWD binary works as the trigger — find, vim, iftop. The binary's function is irrelevant; it just needs to reach sudo's exec path.
SUID & Capabilities
These are two expressions of the same problem: a binary that executes with elevated privileges without requiring the calling user to have them. SUID sets the effective UID at the filesystem level; capabilities grant specific kernel privileges to the binary itself. Both are invisible to sudo -l and both bypass standard privilege controls.
::SUID — Discovery & Exploitation
Key output to scan — flag any non-standard binaries immediately:
Cross-reference every result: GTFOBins → SUID
Custom binaries not in GTFOBins are the real prize. Anything in /usr/local/, /opt/, or a
user home directory with the SUID bit set is non-standard and warrants manual analysis (strings,
ltrace, strace, or full reverse engineering with ghidra).
::SUID + Writable /etc/passwd → Backdoor User
If /etc/passwd is writable directly (or you have a SUID binary like nano that can write to it), inject a root-equivalent user — no password cracking needed.
Phase 1 — Generate Password Hash
Phase 2 — Append Backdoor Entry to /etc/passwd
Phase 3 — Switch User
Why it works: When /etc/passwd contains a password hash in the second field (instead of x), the system uses it directly — bypassing /etc/shadow entirely. UID 0 makes the account root-equivalent regardless of the username.
::Capabilities — Discovery & Exploitation
Capabilities have no s bit — they are invisible to SUID scans. find -perm -04000 will not find them. getcap -r / 2>/dev/null is the only way. A binary with cap_setuid+ep is functionally equivalent to a SUID root binary.
Cross-reference all hits: GTFOBins → Capabilities
cap_setuid+ep — Vim exploit:
cap_setuid+ep — Python exploit:
cap_dac_read_search+ep — tar file read:
PATH Hijacking
Precondition: A SUID binary (or root-executed script) calls another program without a full path (e.g., system("thm") instead of system("/usr/bin/thm")), and you control a directory that appears earlier in $PATH than the real binary's location.
Phase 1 — Identify the Vulnerable Binary
Phase 2 — Find a Writable Directory
Phase 3 — Prepend Writable Directory to PATH
Phase 4 — Plant the Malicious Payload
Phase 5 — Trigger
Why it works: system("thm") inherits the calling process's $PATH. When the SUID binary runs with root's effective UID and searches for thm, it finds your /tmp/thm first. The shell it spawns inherits the elevated privileges.
Spot the pattern in any binary. Run strings ./suspicious_suid_binary and look for short
names without /. Any unqualified command call (backup, cleanup, service, python) is a
candidate. Confirm with ltrace ./binary to see the exact system() call at runtime.
Cron Job Exploitation
Enumerate first. cat /etc/crontab, ls /etc/cron.d/, and cat /var/spool/cron/crontabs/*.
The technique you use depends entirely on what you find.
Precondition: A cron job owned by root calls a script you can write to.
Payload — Reverse Shell:
Alternative — Backdoor Root Password:
All cron directories to check:
NFS — no_root_squash
Why it works: no_root_squash tells the NFS server to trust the root identity of connecting clients. When you mount the share as root on your attacker machine, files you create there are owned by root on the target. That includes SUID binaries.
Phase 1 — Identify Vulnerable Exports (from Target)
Any share with no_root_squash and rw is exploitable.
Phase 2 — Enumerate Shares & Mount (from Attacker)
Phase 3 — Write SUID Payload to the Share (from Attacker)
The compiled binary is now sitting in /backups on the target, owned by root with SUID set — because you wrote it as root on your machine via the no_root_squash share.
Phase 4 — Execute on Target
-static is important. The target and attacker machines may have different glibc versions. A
dynamically linked binary compiled on Kali can fail with "No such file or directory" on an older
target. Static linking bundles everything the binary needs.
pspy — Catching What Automated Tools Miss
pspy is your best weapon for discovering undocumented cron jobs, scripts run by root in real time, and any process that fires and exits before automated enumeration tools can see it.
LinPeas, LinEnum, and every other static scanner take a snapshot at the moment they run. A cron job that fires every 5 minutes and completes in 2 seconds is completely invisible to them. pspy catches it every time.
GitHub: github.com/DominicBreuker/pspy
Why it works: Instead of polling /proc on an interval (slow, misses fast processes), pspy uses inotify watches on key directories (/etc, /tmp, /usr, /var, /bin). When filesystem activity fires, it immediately scans /proc to capture the new process — even short-lived ones.
Sample output — catching a writable root script:
Identify, Inspect, Exploit:
World-writable (rwxrwxrwx) and running as root on a loop. Append your payload:
pspy use cases beyond cron: - Catch scripts called by systemd services at startup - Observe
what happens when a web application triggers a system command - Identify credentials passed as
command-line arguments (e.g., mysql -uroot -pSECRET) - Spot SUID binaries being called by other
processes — reveals how they're being invoked
Public Exploits & Kernel CVEs
The exploit workflow — do not skip steps:
Step 1 — Enumerate
Step 2 — Research
Step 3 — Evaluate Before Running
- ▹Read the exploit source — understand what it does and what it modifies
- ▹Check requirements:
gcc, kernel config options (CONFIG_*), specific versions - ▹Assess crash risk — kernel exploits can panic the machine; never run without reading first
Step 4 — Transfer & Compile
Step 5 — Verify
::Linux Exploit Suggester — Automate CVE Matching
Sample output (condensed):
"Less probable" doesn't mean impossible. LES rates exposure based on version-tag matching, not actual system configuration. A "less probable" CVE may still apply if the matching config options are present. Always cross-check with the exploit's specific requirements before dismissing.
Automation Tools
Run these after manual enumeration — they confirm what you find and catch things you miss. No single tool covers every vector.
| Tool | GitHub | When to use |
|---|---|---|
| LinPeas | carlospolop/PEASS-ng | First run — full system sweep |
| LinEnum | rebootuser/LinEnum | Quick structured info dump |
| LES | The-Z-Labs/linux-exploit-suggester | Kernel CVE matching only |
| LSE | diego-treitos/linux-smart-enumeration | Verbose, adjustable detail levels |
| Linux Priv Checker | linted/linuxprivchecker | Inline Python-based flag detection |
| pspy | DominicBreuker/pspy | Runtime process & cron hunting |
Tooling is environment-dependent. No gcc on target? Precompile. No Python? Use the bash variant of LinPeas. No wget or curl? Transfer via nc, SCP, or base64-encode and paste directly. Always have a fallback.
Automated tools can and do miss things — custom SUID binaries, unusual cron paths, no_root_squash shares on non-standard ports, capabilities on non-standard binaries. Treat tool output as a starting point, not a conclusion.
Linux Privilege Escalation: Enumeration Cheatsheet
Copy-paste-ready enumeration commands covering every critical Linux privesc vector — OS, users, network, files, capabilities, and scheduled tasks. Built for Red Teamers, pentesters, and CTF players.
Shell Upgrade Cheatsheet
Complete guide to upgrading dumb shells to fully interactive TTYs during penetration testing
File Transfer Techniques
Cheatsheet for moving files across Linux, Windows, restricted shells, and inspected networks. Built for CTF players, bug bounty hunters, and internal pentesters.
