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.
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.
+ep means the capability is permitted and effective.
The following is a basic overview on how to use capabilities.
cap_setuid capability on the specified file:
setcap cap_setuid+ep /path/to/file
-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
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
/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:
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:
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
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 -eallows 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.
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.
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.