Process Privilege Escalation with SUID

When an executable with the suid bit is run, it will always run as the user who owns the file, irrespective of the current user. A familiar example is the ping utility. Ping must run as the root user because it opens raw IP sockets, so it has the suid bit set. When an ordinary user runs ping, the process is running as root because /bin/ping is owned by root.

Examining the permissions of /bin/ping

To view the permissions of a file, the stat -c "%a" command is used

root@b6386528aa87:/# stat -c %a /bin/ping
4755
root@b6386528aa87:/# stat -c %a /bin/bash
755
root@b6386528aa87:/#

Looking at the /bin/ping file, we can see it has an extra permission bit (4), which bash does not have. This is the suid bit.

Adding and removing the suid bit

The chmod utility can be used to add the suid bit to an executable file. Let's look at what happens when we remove suid from ping and add it to the whoami executable

root@b6386528aa87:/# chmod +s /usr/bin/whoami
root@b6386528aa87:/# chmod -s /bin/ping
root@b6386528aa87:/# sudo -u nobody ping -c1 127.0.0.1
ping: Lacking privilege for raw socket.
root@b6386528aa87:/# sudo -u nobody whoami
root
root@b6386528aa87:/#

After removing setuid from ping, we cannot ping as an unprivileged user. Conversely, after adding the suid bit, whoami reports root even when running as nobody.

Finding all SUID binaries

The find command can be used to search for all the executables with the SUID permission (-perm -4000). In the following example, find is passing its output to ls -ldb. The -user root can be used to restrict the search to files owned by root.

root@b6386528aa87:/# find / -perm -4000 -exec ls -ldb {} \; 2>/dev/null
-rwsr-xr-x. 1 root root 40000 Mar 29 2015 /bin/mount
-rwsr-xr-x. 1 root root 70576 Oct 28 2014 /bin/ping
-rwsr-xr-x. 1 root root 61392 Oct 28 2014 /bin/ping6
-rwsr-xr-x. 1 root root 40168 Feb 24 2017 /bin/su
-rwsr-xr-x. 1 root root 27416 Mar 29 2015 /bin/umount
-rwsr-xr-x. 1 root root 53616 Feb 24 2017 /usr/bin/chfn
-rwsr-xr-x. 1 root root 44464 Feb 24 2017 /usr/bin/chsh
-rwsr-xr-x. 1 root root 75376 Feb 24 2017 /usr/bin/gpasswd
-rwsr-xr-x. 1 root root 39912 Feb 24 2017 /usr/bin/newgrp
-rwsr-xr-x. 1 root root 54192 Feb 24 2017 /usr/bin/passwd

Disabling SUID Globally

Warning: This may break things
The SUID permission can be ignored globally by using the -o nosuid flag when mounting root. This will break things like sudo or su or services that drop privileges.

rohan@localhost:~$ sudo mount / -o remount,nosuid
rohan@localhost:~$ sudo whoami
sudo: effective uid is not 0, is /usr/bin/sudo on a file system with the 'nosuid' option set or an NFS file system without root privileges?

Disabling SUID in Containers

The SUID capability can be dropped in containers via the --cap-drop=setuid option. See this page for more information https://www.redhat.com/en/blog/secure-your-containers-one-weird-trick

Replacing SUID with Granular Capabilities

The SUID permission does not provide granular privilege escalation. When a binary (for instance, /bin/ping) is elevated to root, it can do anything and everything, such as writing to system directories, installing kernel modules, or messing with hardware. This is poor security practice as it violates the principle of least privilege.

Going back to the example of /bin/ping, it runs as root because it requires the cap_net_raw privileged capability. So rather than elevating it to root, we can tell the kernel to grant that capability to that executable, even if invoked by an unprivileged user.

[root@centos7 ~]# chmod u-s /bin/ping
[root@centos7 ~]# sudo -u nobody ping -c1 localhost
ping: socket: Operation not permitted
[root@centos7 ~]# setcap cap_net_raw+p /bin/ping
[root@centos7 ~]# sudo -u nobody ping -c1 localhost
PING localhost (127.0.0.1) 56(84) bytes of data.
64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.045 ms

That's all for now. Thanks for reading!