Support for ambient capabilities has been added to the most recent libcap-ng
release (0.8+). Ambient capabilities are designed to let a privileged process bestow
capabilities to a non-privileged child process. Normally when you hit
execve in a non-privilged process, all capabilities are stripped unless
you do a very specific sequence of instructions. This is nicely wrapped up in
capng_change_id(). But what if you want to execute a helper process that also
needs capabilities?
Ambient capabilities allows this to happen. If
you are the privileged process, you can grant yourself ambient capabilities
and then capng_change_id() and then calling helper processes is not problem.
Systemd takes advantage of this to allow services to be started as non-root
and with capabilities. All you have to do is add a line with
AmbientCapabilities= to your service file and assign yourself the capabilities
you want.
But there is a problem. Ambient capabilities are leaky.
You may pass them to a child, the child can pass them to a child, and so on.
Whole families of processes could leak capabilities to everything they do. And
if one of those applications are exploitable, then the attacker has a way to
escalate some privileges. One of the updates to libcap-ng is to also allow
netcap and pscap display processes that have ambient capabilities so that we
can hunt down the source of leaks.
Let's test this and ways to
solve it. The test scenario is like this, we have a privileged process as root
with all capabilities. It selects a set of capabilities, including ambient, so
that its child can use the capabilities and changes to uid 99 (nobody). But
then suppose that program is exploitable and the attacker pops a shell, the
third process? What are the possible mitigations?
In this first
run, we have the privileged root process starting off the second as follows:
capng_updatev(CAPNG_ADD,
CAPNG_EFFECTIVE|CAPNG_PERMITTED|CAPNG_INHERITABLE|
CAPNG_BOUNDING_SET|CAPNG_AMBIENT, CAP_KILL, CAP_IPC_LOCK, -1);
capng_change_id(99, 99, CAPNG_DROP_SUPP_GRP);
It drops supplemental groups as a precaution.
first
Effective: 00000000, 00004020
Permitted: 00000000, 00004020
Inheritable: 00000000, 00004020
Bounding Set: 00000000, 00004020
Ambient : 00000000, 00004020
second
Effective: 00000000, 00004020
Permitted: 00000000, 00004020
Inheritable: 00000000, 00004020
Bounding Set: 000000FF, FFFFFFFF
Ambient : 00000000, 00004020
third
Effective: 00000000, 00004020
Permitted: 00000000, 00004020
Inheritable: 00000000, 00004020
Bounding Set: 000000FF, FFFFFFFF
Ambient : 00000000, 00004020
As we can see, the attacker has inherited privileges because of the ambient capabilities. We can see that Effective and Permitted have values.
Does clearing the bounding set in the privileged process have any effect?
The code to do that looks like this:
Now running our test program we see the evolution of permissions as follows:
first
Effective: 00000000, 00004020
Permitted: 00000000, 00004020
Inheritable: 00000000, 00004020
Bounding Set: 00000000, 00000000
Ambient : 00000000, 00004020
second
Effective: 00000000, 00004020
Permitted: 00000000, 00004020
Inheritable: 00000000, 00004020
Bounding Set: 00000000, 00000000
Ambient : 00000000, 00004020
third
Effective: 00000000, 00004020
Permitted: 00000000, 00004020
Inheritable: 00000000, 00004020
Bounding Set: 00000000, 00000000
Ambient : 00000000, 00004020
Nope. That changes nothing. The attacker still has privileges in Effective and Permitted capabilities.
Next let's see what happens if we drop the inheritable capabilities
capng_updatev(CAPNG_DROP, CAPNG_INHERITABLE, CAP_KILL, CAP_IPC_LOCK, -1);
capng_apply(CAPNG_SELECT_CAPS);
The results are as follows:
first
Effective: 00000000, 00004020
Permitted: 00000000, 00004020
Inheritable: 00000000, 00004020
Bounding Set: 00000000, 00004020
Ambient : 00000000, 00004020
second
Effective: 00000000, 00004020
Permitted: 00000000, 00004020
Inheritable: 00000000, 00000000
Bounding Set: 000000FF, FFFFFFFF
Ambient : 00000000, 00000000
third
Effective: 00000000, 00000000
Permitted: 00000000, 00000000
Inheritable: 00000000, 00000000
Bounding Set: 000000FF, FFFFFFFF
Ambient : 00000000, 00000000
attempting to regain ambient capabilities
Effective: 00000000, 00000000
Permitted: 00000000, 00000000
Inheritable: 00000000, 00000000
Bounding Set: 000000FF, FFFFFFFF
Ambient : 00000000, 00000000
This works but that means the program needs to be capabilities aware. If the program was capabilities aware it would not need ambient capabilities because it could start as root and set itself up and change uid.
Maybe just clearing the ambient capabilities in the second process works?
This can be done with the following code:
The test results are as follows:
first
Effective: 00000000, 00004020
Permitted: 00000000, 00004020
Inheritable: 00000000, 00004020
Bounding Set: 00000000, 00004020
Ambient : 00000000, 00004020
second
Effective: 00000000, 00004020
Permitted: 00000000, 00004020
Inheritable: 00000000, 00004020
Bounding Set: 000000FF, FFFFFFFF
Ambient : 00000000, 00000000
third
Effective: 00000000, 00000000
Permitted: 00000000, 00000000
Inheritable: 00000000, 00004020
Bounding Set: 000000FF, FFFFFFFF
Ambient : 00000000, 00000000
attempting to regain ambient capabilities
Effective: 00000000, 00000000
Permitted: 00000000, 00000000
Inheritable: 00000000, 00004020
Bounding Set: 000000FF, FFFFFFFF
Ambient : 00000000, 00000000
This works. This is probably the best solution for programs that have ambient capabilities and do not want to become capabilities aware. All that's needed is to just make a 1 line call to prctl. You do not need to link against any new library.