As we isolate functionality into services distributed across networks, we increasingly strain the concept of trust boundaries. Hosts are no longer simply trusted or untrusted, and each host comes with a new foothold for attackers. This risk is called the Confused Deputy Problem, and it’s part of a growing number of attacks, including the one on Equifax. We need to stop assuming trusted services -- especially huge ones like those in traditional web stacks -- remain trustworthy. Capability-based models offer hope, but we need a few more patterns to use them with modern microservices.
The Confused Deputy Problem has a history going back to early Unix. Attacks on passwd are a classic case. The architecture of passwd is simple: allow unprivileged users to invoke a setuid executable that performs a high-privilege operation under defined preconditions. This means passwd has been deputized to alter the passwords for all users. However, if you can confuse passwd into misusing its ambient authority, you can gain privileges. As a rule, a compromised service can do anything the service itself is allowed to do.
One of the answers has been Mandatory Access Control (MAC) like selinux. Firewalls are analogous for networked services. These systems operate by confining which principals can access which files or services. This helps somewhat; passwd has no business updating /boot (for example). However, in cases like Equifax, principals were only talking to the expected systems; the attacker used the web server to exfiltrate data from the database backing the web application, even accessing tables the web application would be expected to access.
Just as with passwd, Equifax’s problem was trusting an application to both perform its functionality and enforce authorization. Capability-based models are one answer: rather than trusting a complex service to enforce authorization against deeper systems, we provide the client with proof that it’s allowed to access its own records and have the service forward this proof to deeper systems in order to read or manipulate corresponding records. It’s now harder to turn a compromised service into a system-wide attack.
Systems using capabilities are in widespread use: Kerberos, Google’s Firebase, and concert tickets all use the concept of a token providing proof-of-authorization in a way decoupled from principals. However, things get weird when interaction is no longer directly between the primary principal (say, a web client) and nested services. Because possessing a capability is sufficient to exercise it, how do we handle cases where deeper services should have access to fewer (or more, or different) objects than the ones in front of them?
In this presentation, we’ll look at state-of-the-art methods for delegating capabilities, including “sealing” (reducing the authorization of shallow services) and original techniques to forward only a subset of the caller’s capabilities (reducing the authorization of deep services), all designed for use with distributed services across networks.
Note: The capabilities here are unrelated to Linux kernel capabilities. However, Linux does have some capabilities like the ones mentioned, including the idea of handing off a file descriptor from one application to another. This allows one app to open the FD and hand it to another that cannot (but can use the FD once in possession). Socket activating a web server on port 80 despite starting the web server as an unprivileged user is one example of handing off a capability via FD.