Finding and removing setuid binaries, the secure way
Every setuid-root binary is a small door into root that the system leaves unlocked on purpose, trusting the program to check who is knocking. Most hosts have a dozen of these doors, and nobody remembers why half of them are there.
The short answer
Inventory with find / -xdev -type f \( -perm -4000 -o -perm -2000 \) and getcap -r /. Remove the setuid bit from programs no one on the host needs, such as chfn, chsh and newgrp on servers. On Debian and Ubuntu use dpkg-statoverride --update so package upgrades do not put the bit back. Review the list after every install.
On this page
What goes wrong
A program with the setuid bit runs with the rights of the file's owner, not the rights of the user who started it. When the owner is root, any bug in that program, a buffer overflow or a bad environment variable, is a path to root for every local user.
Some of these programs are needed: passwd has to write /etc/shadow, su
has to switch users. Many are not needed on a server: chfn changes the
"full name" field, chsh changes a login shell, newgrp switches groups for
people who still do that by hand.
Two things make this worse over time. New packages add new setuid files
quietly. And removing the bit with chmod u-s is undone at the next package
upgrade, because the package puts its files back exactly as they shipped.
What the docs say
If the set-user-ID bit is set on the program file referred to by path, then the effective user ID of the calling process is changed to that of the owner of the program file.
Source: execve(2)
This can be used to force programs that are normally setuid to be install without a setuid flag, or only executable by a certain group.
Source: dpkg-statoverride(1)
The Debian tool is documented, but most hardening guides show only chmod.
The chmod change looks right until the next security update reinstalls the
package.
The secure configuration
Inventory first. Include setgid files and file capabilities, which give part of root's power without the setuid bit:
# Setuid (4000) and setgid (2000) files on the root filesystem.
find / -xdev -type f \( -perm -4000 -o -perm -2000 \) -exec ls -l {} + 2>/dev/null
# Repeat -xdev per mounted filesystem you care about (/home, /var, /opt).
# File capabilities: e.g. cap_net_raw on ping.
getcap -r / 2>/dev/null
# Who owns each file (Debian, Ubuntu). After /usr merge, some are known as /bin/...
dpkg -S /usr/bin/chfn /bin/suRemove the bit in a way that survives upgrades:
# Debian, Ubuntu: record an override; --update applies it now.
dpkg-statoverride --update --add root root 0755 /usr/bin/chfn
dpkg-statoverride --update --add root root 0755 /usr/bin/chsh
dpkg-statoverride --update --add root root 0755 /usr/bin/newgrp
dpkg-statoverride --list# Or keep the bit but limit who can run it: only group "admins".
dpkg-statoverride --update --add root admins 4750 /usr/bin/suOn RPM systems (Fedora, RHEL, Rocky) there is no statoverride. Apply the
chmod from configuration management on every run, or remove the package if
nothing needs it. rpm -V <package> shows M for a file whose mode no longer
matches the package, which is also a useful audit.
Keep the list as a baseline and compare it after each change:
find / -xdev -type f \( -perm -4000 -o -perm -2000 \) 2>/dev/null | sort > /root/suid-baseline.txt
# later:
find / -xdev -type f \( -perm -4000 -o -perm -2000 \) 2>/dev/null | sort | diff /root/suid-baseline.txt -Prove it
The inventory on a stock Debian 12 image, with ping added to show a file
capability:
find / -xdev -type f \( -perm -4000 -o -perm -2000 \) -exec ls -l {} + 2>/dev/null | awk '{print $1, $3, $4, $9}'-rwxr-sr-x root shadow /usr/bin/chage
-rwsr-xr-x root root /usr/bin/chfn
-rwsr-xr-x root root /usr/bin/chsh
-rwxr-sr-x root shadow /usr/bin/expiry
-rwsr-xr-x root root /usr/bin/gpasswd
-rwsr-xr-x root root /usr/bin/mount
-rwsr-xr-x root root /usr/bin/newgrp
-rwsr-xr-x root root /usr/bin/passwd
-rwsr-xr-x root root /usr/bin/su
-rwsr-xr-x root root /usr/bin/umount
-rwxr-sr-x root shadow /usr/sbin/unix_chkpwdgetcap -r / 2>/dev/null/usr/bin/ping cap_net_raw=epchmod alone, then a package reinstall. The bit comes back:
chmod u-s /usr/bin/chfn; stat -c "%A %n" /usr/bin/chfn
apt-get install -y --reinstall passwd
stat -c "%A %n" /usr/bin/chfn-rwxr-xr-x /usr/bin/chfn
-rwsr-xr-x /usr/bin/chfnWith dpkg-statoverride, the change survives the reinstall of both packages:
for f in /usr/bin/chfn /usr/bin/chsh /usr/bin/newgrp; do dpkg-statoverride --update --add root root 0755 "$f"; done
dpkg-statoverride --list
apt-get install -y --reinstall passwd login
stat -c "%A %n" /usr/bin/chfn /usr/bin/chsh /usr/bin/newgrp
find / -xdev -type f -perm -4000 2>/dev/null | wc -lroot root 755 /usr/bin/newgrp
root root 755 /usr/bin/chsh
root root 755 /usr/bin/chfn
-rwxr-xr-x /usr/bin/chfn
-rwxr-xr-x /usr/bin/chsh
-rwxr-xr-x /usr/bin/newgrp
5For comparison, the same search on alpine:3.22:
find / -xdev -type f \( -perm -4000 -o -perm -2000 \) 2>/dev/null | wc -l0The test script is secure-tests/finding-removing-setuid-binaries/run.sh.
Mistakes people make
chmod and walk away
The next apt upgrade or dnf update of that package restores the original
mode. Use dpkg-statoverride on Debian and Ubuntu, and configuration
management everywhere else.
Forgetting setgid and capabilities
A setgid shadow program can read password hashes. A binary with
cap_dac_read_search can read any file. Neither shows up in a search for
-perm -4000 alone.
Removing the bit from passwd or su
Users can no longer change their own password, and su stops working. Decide
per program: if nobody on the host needs it, remove it; if admins need it,
limit it to an admin group with mode 4750.
Searching only the root filesystem
-xdev stays on one filesystem, which keeps the search fast and skips
/proc. Run it again for each other mounted filesystem, or mount the ones
users can write to with nosuid so the question does not arise.
Checklist
- A list of every setuid and setgid file exists, with the owning package for each.
getcap -r /has been reviewed.chfn,chshandnewgrphave no setuid bit on servers.- Every removal on Debian or Ubuntu is recorded with
dpkg-statoverride. suis limited to an admin group or kept only where needed.- User-writable filesystems are mounted with
nosuid. - The list is compared to a baseline after each package change.
You cannot audit a door you do not know exists. Make the list, shorten it, and make sure the package manager does not quietly reopen what you closed.
FND
Learn it on a live range
Linux 1: the command line to a working, secure host, in Foundation: a real host in your browser, and every objective checked on the machine.
Start freeThe Secure Way
More on linux hosts
SSH, sudo, firewalls, mount options, updates and the host basics every engineer should get right.
All linux hosts guides