Labs

SUID Binary Abuse: Enumerating and Escalating with GTFOBins

Docker lab Privilege Escalation

Updated August 4, 2026 · Written by PWNMI — see About.

Most privilege escalation on a real engagement doesn't come from an exploit — it comes from someone leaving a permission bit set after they were done with it. This lab is the first in a series of Linux privesc technique labs: no CVE, no specific product, just the misconfiguration classes that show up constantly in real assessments and CTF boxes alike. This one is SUID abuse: a binary that runs with its owner's privileges (root) regardless of who executes it, because the setuid bit is set.

What you'll practice: enumerating SUID binaries systematically instead of guessing, cross-referencing a hit against GTFOBins instead of memorizing exploit strings, and recognizing which SUID binaries are expected baseline noise versus an actual finding.

Set up the lab

New to Docker or git, or unsure what the commands below are actually doing? See Docker for Security Labs and Git for Security Work first.

Get the lab files from pwnmihq/resources:

git clone --filter=blob:none --sparse https://github.com/pwnmihq/resources
cd resources
git sparse-checkout set technique-labs/suid-binary-abuse
cd technique-labs/suid-binary-abuse
docker compose up -d --build

Drop into a shell as analyst, an ordinary unprivileged user — this is your starting point, the same as a foothold gained through some other means on a real engagement:

docker compose exec -u analyst app bash
id

Understanding the technique

The setuid bit tells the kernel to run a binary with its owner's privileges, not the caller's. It exists for a small, legitimate set of cases — passwd needs to write to /etc/shadow on behalf of any user, for instance — which is exactly why a handful of SUID-root binaries are normal to see on any Linux system. The problem is any binary that can be made to spawn a shell, read arbitrary files, or write arbitrary files becomes a direct root escalation the moment it's SUID-root and wasn't supposed to be.

This lab's misconfiguration: find was made SUID-root, most plausibly because someone needed a one-off way to search directories a normal user couldn't read, and never reverted the permission afterward. find supports an -exec flag that runs an arbitrary command — including a shell — and because find itself is running as root, so is whatever it execs.

Exploitation

First, find what's actually SUID on this box — not just this one binary, since on a real engagement you won't be told where to look:

find / -perm -4000 -type f 2>/dev/null
/usr/bin/mount
/usr/bin/umount
/usr/bin/find
/usr/bin/chfn
/usr/bin/chsh
/usr/bin/passwd
/usr/bin/newgrp
/usr/bin/gpasswd
/usr/bin/su
/usr/bin/sudo

Most of this list is exactly the baseline you'd expect on any Debian system — passwd, su, sudo, mount are supposed to be SUID-root. find is not. That's the finding: not "a SUID binary exists," but "a SUID binary exists that has no business being SUID." This is the actual skill — a real box will hand you a list like this with the interesting entry buried in it, not labeled.

Confirm it against GTFOBins' find entry, which documents exactly this SUID technique, then run it:

find . -exec /bin/sh -p -c 'id' \; -quit
uid=1000(analyst) gid=1000(analyst) euid=0(root) groups=1000(analyst)

euid=0 is the tell — your real uid is still 1000, but your effective uid, the one the kernel actually checks for permissions, is root. The -p flag matters here: without it, a shell spawned by a setuid process actively drops its elevated privileges as a safety measure, which is exactly the behavior GTFOBins' documented command works around.

Don't stop at id. Confirm it against something the unprivileged user genuinely can't touch:

find . -exec /bin/sh -p -c 'head -1 /etc/shadow' \; -quit
root:*:20647:0:99999:7:::

Run head -1 /etc/shadow directly as analyst (no exploit) first if you want the contrast — Permission denied. That gap is the actual proof, not the id output alone.

Cleanup

There's nothing to revert on the target itself — this lab doesn't create or modify anything beyond spawning a root shell in memory. The one thing worth remembering for a real engagement: if you used a SUID binary to write a backdoor, plant a key, or create a persistent root shell binary, that needs cleanup and documentation. A bare privilege check like this one doesn't.

Tear the lab down when you're done:

docker compose down -v

Lessons worth keeping

  • A SUID bit is a decision, not a default. Every SUID-root binary on a system is either part of the small, well-known baseline set (passwd, su, sudo, mount, and a handful of others) or it's a decision someone made, deliberately or by accident, that's now part of your attack surface.
  • GTFOBins is a reference, not a memorization exercise. The actual skill is enumerating what's SUID, recognizing what's non-standard, and knowing where to look up whether it's exploitable — not having every technique memorized.
  • -p (or the equivalent flag) is not optional. Most shells actively defend against exactly this kind of privilege inheritance by dropping elevated privileges the moment they're spawned by a setuid process, unless explicitly told not to. Half of "getting a SUID exploit to work" is knowing this.

Next step

This is the first in a short series on generic Linux privesc technique classes — see Sudo Misconfiguration next, which hits the same GTFOBins reference from a different angle. For the broader mental model both fit into, see Privilege Escalation Fundamentals and the Script Privilege Escalation checklist.