In Linux environments a superuser can do practically anything and is not bounded by normal security checks. In other words, the superuser has a number of privileges which allow him to change the system as he pleases.
Linux divides these privileges into distinct units, known as capabilities. These capabilities can be added to an executable, which will give any user running that executable the specific superuser privilege defined by the capability.
Why have capabilities?
Before capabilities, your process was either superuser-privileged or non-privileged, with no in-between. Either your process could do everything or it was restricted to the capabilities of a standard user.
Certain executables, which needed to be run by standard users, also needed to make privileged kernel calls. These would have the suid bit set, effectively granting them privileged access. Consequently, they were also prime targets for hackers. If a bug could be exploited in them, then the hacker would gain superuser privileges on the system.
It was possible for the program to drop superuser privileges once the program had completed the part of its execution, which demanded heightened privileges. However, it was an inelegant solution which gave unnecessarily high privileges to the program for a part of its execution lifecycle.
This wasn’t a great situation, so the kernel developers came up with a more nuanced solution: capabilities.
The idea is simple: just split all the possible privileged kernel calls up into groups of related functionality. Then, only the necessary subset of capabilities can be assigned to an executable. That way, a breach in such an application’s security would only result in the attacker gaining the assigned subset of capabilities, not access to the entire system.
Capability format
When searching for capabilities on your system, you might see something like:
/usr/bin/mtr-packet = cap_net_raw+ep
This means that the executable /usr/bin/mtr-packet
has the cap_net_raw
capability. As mentioned in the man page about capabilities, this allows the program to:
- use RAW and PACKET sockets;
- bind to any address for transparent proxying.
The +ep
means the capability is permitted and effective.
Capability usage
The following is a basic overview on how to use capabilities.
Assigning capabilities
Assigning the cap_setuid
capability on the specified file:
setcap cap_setuid+ep /path/to/file
Removing capabilities
Specifying the -r
flag removes all capabilities from a file.
setcap -r /path/to/program
Searching for capabilities
Recursively searching the whole filesystem for any files with capabilities:
getcap -r / 2>/dev/null
In case you’re not familiar with 2>/dev/null
, 2
is stderr
, meaning “standard error”. Redirecting it to /dev/null
discards all error messages.
Zip arbitrary file read
Let’s say there is a zip
executable with the cap_dac_read_search
capability. Reading the man page once more, we can see this allows zip to bypass read permission checks, meaning we can read any file on the filesystem.
Making use of this, there are multiple files an attacker may read. The easiest example is /etc/shadow
, where hashed user passwords are stored. Another common thing to read would be the users’ ssh private keys.
The process is easy. First, we archive the file or folder we wish to read:
/path/to/zip /tmp/shadow.zip /etc/shadow
Where /path/to/
is the directory of the zip file with the added capability.
Next, we extract that archive:
unzip /tmp/shadow.zip -d /tmp
Then, we can simply read the file:
cat /tmp/etc/shadow
The reason we weren’t able to read the original /etc/shadow
is because root
is the owner of it. However, if we create a backup by archiving and extracting it with zip, then we become the owner of the backup file. And as the owner of the file, we are able to read it.
Empty file capabilities
It is also possible to assign an empty capability set to a file. When viewing such a file with getcap
, it looks like this: /path/to/some-file =ep
.
Counterintuitively, an empty capability set means that all the capabilities are enabled and the executable runs as the superuser. Therefore, the above example is the equivalent of /path/to/some-file all=ep
.
So when you come across a file with an empty capability set, make sure that it’s like that for a good reason and it isn’t exploitable.
Perl privilege escalation
Let’s say there is a perl
executable with the an empty capability set. In that case, escalating our privileges to root is trivial. All we have to do is change our UID to root and run bash.
Therefore, running the following command will give us root privileges:
perl -e 'use POSIX (setuid); POSIX::setuid(0); exec "/bin/bash";'
Let’s break it down:
-
perl -e
allows us to execute perl code. -
use POSIX (setuid);
imports the required module. -
POSIX::setuid(0);
sets the UID to 0, which is root. -
exec "/bin/bash";
executes bash as root.
And that’s how easy it can be to get root privileges like this.
Technical details
If you’re interested in how file capabilities are implemented in Linux, then this part is for you.
Linux file capabilities are specified in the security.capability
extended attribute. Writing to this extended attribute requires the CAP_SETFCAP
capability, meaning non-privileged users are unable to write file capabilities.
How extended attributes themselves are stored is filesystem specific. In ext3, for example, the extended attributes are stored directly in inodes (on file systems with inodes bigger than 128 bytes) and on additional disk blocks.
When creating backups, you must ensure the backup creation process preserves extended attributes. For example, the cp
command does not preserve extended attributes by default. If you wish to preserve extended attributes, you must use the --preserve=xattr
flag. Note that writing capabilities requires superuser privileges.
Conclusion
Linux file capabilities are great because they allow you to manage superuser privileges on a more granular level. However, it is still great power to give to an executable. And with that power, there needs to be the responsibility to verify that the executable’s capabilities cannot be abused in any way.