You have learned so many misconfigurations on the Linux system that allow attackers to gain privileged shells. This is because even if a program is supposed to perform a specific system-level task, it needs to have the EUID of the root user thus making it vulnerable for attackers to use perform privilege escalation. Let's call this binary system of permissions, which will start two types of process – privileged (EUID == 0) and unprivileged (EUID != 0)
To prevent this, Linux developers have thought and sorted all the privileged tasks into a set of ~40 (as of now) capabilities. So for example, if you want to read a privileged file, you as an administrator allow the running process or program file to allow CAP_DAC_READ_SEARCH privilege, bypassing file read permission checks and directory read and execute permission checks. In this, a non-privileged user can only perform the specific tasks of reading any file and perform a search in protected directories, malicious use like setting UID to 0 and then spawn shell is not possible.
In this post, I will touch upon the brilliant idea of capabilities introduced in the Linux kernel and in my upcoming posts, I will discuss how an attacker can exploit these capabilities when allowed on unusual programs like interpreters
What are the Linux capabilities anyway?
Well, the answer is pretty simple – A granular set of permissions assigned to a running program or thread or even a program file by root user to allow process use privileged (system-level tasks) like killing process owned by different users from a shell of a low privileged user. Each capability provides one or more sets of related privileges to the process. All of them are listed and well explained in capabilities(7) man page
Unlike the DAC / MAC permissions I have discussed in my earlier posts, capabilities can be set or unset in the running process if they are in the permitted set (discussed below). Only a particular set of capabilities are checked by the kernel
NOTE: If a thread is running with the Effective UID value 0, initially it will have all the capabilities enabled.
Where are Capabilities Records Stored?
Extended permissions such as access control lists set by
setfacl and capability flags set by
setcap which use
setxattr(2) are stored in the same place as traditional permissions and set[ug]id flags set by
chmod – in the file's inode.
I have a binary file with some capabilities set already. When I performed
getfattr on the file, I found that the information is stored in
security.capability section. The syscall used to get extra file attributes is
Type of Capabilities Sets
The capabilities actually come into action during the execution of the process. The kernel will only check for them when the program tries to perform special system calls.
Each process have 5 different sets of capabilities from the list of all capabilities
- Effective — Capabilities used by the kernel to perform permission checks for the thread. So if you perform any privileged task and its capability is not in this set, it will throw an "Operation not permitted" error. You can check using EPERM enum.
- Permitted — It is a superset for the effective capabilities that the process may assume. If the capability is available in this set, a process transitions it to an effective set and drops it later. But once a process has dropped capability from the permitted set, it can not re-aquire
- Inheritable — This set is reserved for execve() syscall. If the capability is set to inheritable, it will be added permitted set when the program is executed with execve() syscall
- Bounding — It is the superset of all the capabilities allowed to the thread. That means no process can have the capability from outside this set.
- Ambient — This set is also reserved for execve() syscall, but for non-privileged files. You can control them via prctl(), this can not be set via CLI utilities
A binary can be called capability aware if it can actively transition the permitted capabilities to effective using syscalls like capget, capset or prctl. On another hand, a capability unaware binary doesn't have this privilege, to make the capabilities set effective either it's inherited by the parent process or while loading the program in the memory
These sets are well explained on hacktricks gitbook. To know more, visit – https://book.hacktricks.xyz/linux-unix/privilege-escalation/linux-capabilities#capabilities-sets
Capabilities In Action
There are three CLI utilities to manage the capabilities in Linux
- capsh — print the capabilities of the current context or decode the hex-encoded capabilities in the running process status
grep Cap /proc/PID/status
- setcap — set or unset the capabilities to regular files
- getcap — get the decoded set of capabilities from the file or recursively in directories
The current running process is a shell with normal users, and for a normal user process, by default, there are no capabilities. You can verify it by executing
capsh --print. If the capability set show =ep, that means it has all the capabilities from the bounding set
All the capabilities for processes and threads are stored in the status file under the process/thread directory in the /proc file system. These properties start "Cap" name.
You can also do the above by grepping "Cap" from /proc/$$/status. In this case, $$ will give the current process ID, which is of course shell
In the current directory, I have only one file which is a python interpreter with group and owner both set to terabyte user. Since this binary has cap_setuid capability set in the effective set, I can perform setuid operation without having the effective uid set to 0
Alternatively, for a running process, you can get the hex-encoded capabilities and then later decode it with capsh.
In Linux the threads are located under /proc/PID/tasks and the status of each task is stored in their individual status file, /proc/PID/tasks/TID/status
I didn't check for the 60928 because that is the task id of the main thread. If there is no thread in the process, the kernel automatically kills the process. So to avoid that, a task of the main process is created with the same id as the process
Ping command is supposed to be working for every user to check network connectivity by pinging other live hosts on the network. At its core, the ping command uses special system privilege to handle raw packets directly from the kernel. In the first case, the ping command has CAP_NET_RAW so it went ahead and sent ICMP packet on localhost. Just below the successful execution, I dropped it from the bounding set. So it will not be available in either of permitted or effective set
Transitioning Permitted Capabilities to Effective Set
For a capability aware program it is easy to move a capability from a permitted set to an effective set. The capability should exist in the permitted set before moving into the effective set, otherwise, you will get an "Operation not permitted" error. In programming, it can be handled by comparing the return value of a function with
To make things simple, I will use python binary. The binary container cap_setuid capability in the permitted set
You can fix this by installing the prctl module. This module contains wrapper functions for calling prctl(2) syscall. In this, you can use cap_permitted and cap_effective sets to view or set the permissions
As you can see, the capabilities are indeed in the permitted set. The fix is pretty simple, you need to set the
prctl.cap_effective.setuid = True and it will transition the capability by calling the prctl function under the hood
To learn more about it, I would recommend you practice the following labs
Getting a good knowledge of capabilities takes time and dedication. I am new to this as well. I will cover more topics and tricks in my upcoming posts. I will there use programming languages like C and Python to transition capabilities and explain secure coding practices