Local privesc: SUID, sudo, capabilities
The misconfigurations attackers find first.
A lock on a front door only matters if the key is scarce. Local privilege escalation is what happens when an attacker who slipped through a side window finds a key lying on the floor that opens the master door. On Linux that master door is root, the administrative account with user ID 0 that every permission check waves through. The keys on the floor are rarely exotic kernel bugs. They are misconfigurations: a program carrying more power than its job needs, a rule that trusts a user too far, a permission bit set once and forgotten. You learn to find these keys first, because the attacker's opening move after landing on a host is to look for the same three.
Three surfaces produce most local root on a modern Linux box: SUID binaries, sudo rules, and file capabilities. Each is a real feature doing a real job, and each turns into a road to root the moment it is handed out too generously. We will take them one at a time: what the feature is, how an attacker flips it, and the exact command you run to see it on your own hosts before they do.
The SUID Bit, a Key That Works No Matter Who Turns It
Normally a program runs as you. Start passwd (the tool that changes your login password) as the user alice and the process is alice. But passwd has to write to /etc/shadow, the file that stores everyone's password hashes, and that file is owned by root and off-limits to ordinary users, who cannot read it or write it. So how does an ordinary user change their own password? The Set User ID bit, written SUID. It is one flag on an executable that tells the kernel (the core of the operating system, the part that enforces every permission check): when anyone runs this file, run it as the file's owner, not as the caller. passwd is owned by root, so it runs as root no matter who starts it, long enough to update /etc/shadow, then it hands control back.
Two identities are in play at once, like a visitor who keeps their own name badge on while holding a borrowed master key. Your real user ID (RUID, the badge, who you actually are) does not change. Your effective user ID (EUID, the key the kernel actually checks every permission against) becomes root for the life of that process. You can see the SUID flag in a plain directory listing: the owner's execute slot shows an s where it would normally show an x.
Most of that list is supposed to be there. mount, su, sudo, passwd, and the openssh and polkit helpers ship SUID by design. What you are hunting for is the line that does not belong, because a SUID binary is only as safe as what it lets you do while you hold root. If the program can read any file, write any file, or run a command of your choosing, then running it as root hands you root. find is the textbook case: it has a -exec flag that runs commands, so a SUID find runs your command as root.
That trick is not special to find. GTFOBins (a public catalog of everyday Unix programs and the tricks that turn each one into a way to climb higher or break out of a restricted shell) lists dozens of them. Some hand you a root shell the instant they run SUID, with vim, less, and awk among them, because each can spawn a subshell on demand. Others, like cp and tar, only read or write files, but a program that can write any file as root can overwrite a cron job or /etc/passwd, which walks you to root all the same. So the audit question is not "does this binary look dangerous," it is "does anything on this host carry the SUID bit that is not on my known-good baseline." Notice what is missing from the scan above: ping. It used to be SUID-root and no longer is, which is the perfect bridge to the third surface.
Sudo, the Guard With a Clipboard
sudo is a guard standing outside the manager's office with a clipboard. The clipboard lists, per person, exactly which tasks each is allowed to carry out as the manager, and usually asks you to prove who you are with your own password, not the manager's. Ask for something on your line and the guard waves you through. Ask for anything else and you are turned away. That clipboard is the sudoers file, and any user can read their own line of it with one command.
# managed by config-mgmt; edit the template, never the host# deploy service account: manage the app and edit its config filedeploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart app.servicedeploy ALL=(root) NOPASSWD: /opt/app/deploy.shdeploy ALL=(root) NOPASSWD: /usr/bin/vim /etc/app/config.yml # vim shells out -> use sudoedit
Read that the way an attacker does, hunting the one grant that is broader than it looks. Three patterns turn a sudo rule into a root shell. The first is a shell-capable program. Any editor, pager, or interpreter can run a command, so permission to run one as root is permission to run anything as root. The vim rule is that trap: from inside vim you type a colon command that shells out, and the shell is root.
The second pattern is an editable target. The deploy.sh rule looks tight until you ask who can change what is inside deploy.sh. Check the file's own permissions. The group deploy has write access, so the deploy user edits the script, drops in a line that starts a shell, runs it through the sudo rule, and lands as root. The third pattern is a wildcard. A rule like (root) /usr/bin/systemctl * lets the argument be status, whose long output pipes through a pager, and the pager shells out the same way vim did. Wildcards and shell-capable programs are one bug in two outfits.
The fix for the vim rule is sudoedit, also written sudo -e. It copies the file to a temporary path, lets the user edit it as themselves, and writes it back as root, so no root-owned editor process ever exists to break out of. NOPASSWD is a separate weakness: it drops the password check, so a phished or reused low-privilege credential escalates with no second gate. Keep NOPASSWD off anything that is not genuinely unattended automation, and keep the runas target as narrow as the task allows.
Capabilities, Root Split Into a Keyring
Root is one key that opens every door, which is exactly what makes handing it out risky. Linux capabilities break that single key into roughly forty separate keys on a ring, each opening one specific door. A tool that only needs to open raw network sockets can be given the one key for that job, cap_net_raw, and nothing else, so it never runs as full root. That is genuinely safer, and it is why ping no longer needs SUID. It also opens a quieter road to root, because a binary carrying the wrong key escalates without ever being SUID and without showing up in a SUID scan.
Three keys are the ones to watch. cap_setuid lets a program call setuid() and become any user, root included. cap_dac_read_search switches off the file-read and directory-search permission checks, so whoever holds it reads every file on the system, including /etc/shadow and every private key. cap_sys_admin is a grab bag so wide it is close to full root by itself. You list file capabilities with getcap, and unlike SUID there is no other place they show up.
ping carrying cap_net_raw is fine, and it is exactly why ping dropped its SUID bit. python3.11 carrying cap_setuid is not fine, and that one is game over. The =ep suffix means the capability is both permitted (the process is allowed to use it) and effective (it is switched on the moment the program starts). With that single key, one line of Python becomes root.
Nothing in a SUID scan would have shown that binary. Nothing in sudo -l would either. A defender who checks only SUID and sudo has a blind spot the exact size of the capabilities model, which is why this audit stands on three legs, not two. How does a stray capability land on a binary? A careless package, an automation that ran setcap to fix a networking tool and pointed at the wrong file, or an attacker who set it on purpose as a quiet way back to root. The command that plants it is setcap cap_setuid+ep /path, and getcap prints the result back with the =ep spelling you saw above.
The Audit That Runs First, and Keeps Running
All three checks share one shape. Enumerate the privileged surface, compare it against a known-good baseline, and treat every difference as a finding. The first time you run them you are hardening a host. Every time after, you are hunting, because a SUID binary, sudo grant, or capability that was not there last week is both a way up for an attacker and a sign that one has already been through.
Remediate by removing what should not be there. Strip a stray SUID bit with chmod u-s, and strip an unwanted capability with setcap -r. Replace an editor sudo rule with sudoedit. Then verify the way you found the problem: re-run the scans and confirm the finding is gone. In the check below, ls shows find back to a plain -rwxr-xr-x with the s removed, and getcap prints nothing for python, which is what a clean binary looks like.
A privileged binary that was not in last week's baseline is your earliest and cheapest intrusion signal. Put the three scans in a nightly job that diffs against the baseline and alerts on any addition, and the same line of output catches both the misconfiguration you made by accident and the backdoor someone else left on purpose.
Try this
Work through “The Audit That Runs First, and Keeps Running” yourself on a sandbox you can throw away, following the commands above in order. Then break one step deliberately and re-run, so you have seen the failure before it finds you.
Takeaway
The trap worth remembering here: strip the delta, not the baseline. Check that on your own systems before you need to, because it is cheaper to find on a quiet afternoon than during an incident.