Over the last year I’ve taken a step away from my usual bug bounty work to focus more on building resync — my continuous reconnaissance platform. As part of that work, I’ve built, and made public, a high performance TCP SYN scanner named Armada and a unique authorization framework named Dacquiri.
Both Armada and Dacquiri were made to address problems I encountered while developing resync. With Armada, it was that I was tired of shelling out to nmap
or masscan
and parsing String
output from stdout
. I wanted a high performance TCP port scanner written as a library so it was easy for Rust applications to integrate with. Port scanner libraries existed for Rust beforehand, but none were remotely designed to work on any appreciable scale.
So that’s Armada, but what is Dacquiri?
What is Dacquiri
Resync has become a relatively intricate project at this point. As I’ve been building out the core web services, which allow users to interact with their resync instances, I’ve found myself narrowly avoiding authorization vulnerabilities in the flurry of development.
The process of checking user rights with if
statements and SQL queries everywhere felt archaic and was clearly error prone. After some inspiration from a talk by members of r2c, I decided to solve this problem by building Dacquiri. So what does Dacquiri do?
Dacquiri turns authorization vulnerabilities into compile-time errors.
It might sound absurd but, with proper application, Dacquiri is the ultimate security code reviewer. By gating privileged actions in your application behind Dacquiri’s entitlement system, it will ensure that every single codepath that interacts with the protected function has performed the required authorization checks ahead of time.
Dacquiri is broken down into two main components: attributes and entitlements.
Attributes
Attributes are true statements about a subject (e.g. a User). For example, it’s common in applications to have an enabled or disabled state for users, so a IsEnabled
attribute would make sense for many applications. Another example of a common, useful attribute might be IsMemberOfTeam
which would attest that a user is a member of a team.
What’s powerful about attributes is that they can optionally depend on resources, like the aforementioned IsMemberOfTeam
attribute. These resources are tracked so you can always retrieve the specific resource that a subject’s attribute was checked against. In the case of an IsMemberOfTeam
attribute, the tracked resource would be the Team
object. This is a powerful defense against IDORs.
Entitlements
Entitlements are where you define your authorization model. With entitlements, you define what combination of attributes (e.g. IsEnabled
) are required to use privileged functionality in your application.
For example, a bank may want to restrict who can perform the withdraw
and deposit
actions differently to support features like sending money or account managers. We could require that a user’s account must be enabled to perform a deposit, but the user must also be authenticated if they wanted to perform a withdraw.
Entitlements make it easy to identify what attributes are required to access your privileged functionality. In typical applications, your access control model may be defined by the application of various control-flow statements (e.g. if
, break
, return
) throughout your call-graph. With Dacquiri, your access control-model is presented clearly and succinctly to your developers enabling easy audits and a simple developer workflow.
Building a Bank
To explore how Dacquiri can help us secure our applications, let’s build a send money feature for a fictional bank. For this feature we want to allow enabled users to send and receive money with each other. They’ll submit a request to an API endpoint with a session in a cookie, an amount they want to send, and an email address of the user they want to send their money to.
We’ll start by defining a User
with withdraw and deposit methods.
Now let’s build an API endpoint for our users to call. This is what it might look like if we didn’t use Dacquiri.
We have two primary functions. One of them receives the requests from the web as a handler and its primary job is to take information from the web request and convert it into types and information we can use later to service the request.
The second function takes two User
objects — a sender and a recipient and transfers funds between the two.
Despite how simple this code is, we already have introduced a vulnerability! Recall that our bank application is supposed to only allow the transfer of funds for enabled users. Unfortunately, the above code doesn’t actually check the enabled state of the referenced users which means that this endpoint allows users that aren’t enabled to send or receive money. Not good!
Let’s begin integrating Dacquiri by defining two attributes IsEnabled
and IsAuthenticated
— the attributes we’ll build our authorization model against.
Additionally, let’s move the withdraw
and deposit
implementations into their own respective entitlements.
We define entitlements by creating a trait
and annotating them with a combination of attributes that we want Dacquiri to require before we can access any of the defined functionality. In this case, a user must be enabled if we wanted to deposit money into their account. On the other hand, a user must be both enabled and authenticated if we wanted to withdraw money.
Lastly, let’s update the send_money
function from earlier to use our new Withdraw
and Deposit
entitlements.
After updating the sender and recipient function arguments to use our newly defined entitlements, it introduces compiler errors in response to our vulnerability.
To make Dacquiri happy, we’ll need to prove to it that we’ve checked that the sender account is both enabled and authenticated using Dacquiri’s try_grant
and try_grant_with_resource
methods.
try_grant
is a method that Dacquiri defines allowing us to test if a subject has a particular attribute. Dacquiri will run the function we previously defined alongside our attribute and return a type that attests to the compiler that our subject has a particular attribute and any entitlements that these attributes may qualify for. try_grant_with_resources
is similar to try_grant
but it also allows us to pass in a resource (e.g. like a Team
,Session
, or even another User
).
We’re still left with compiler errors, but we’re making progress here. We’ve successfully proved to Dacquiri that our sender
must be authenticated and must be enabled. If they aren’t, our handler will return early (signified with the ?
at the end of both of the function calls).
While we’ve handled the case that the caller isn’t authenticated or enabled, we haven’t checked that the account we’re sending money to is enabled. In this state, if Dacquiri wasn’t helping us, we’d still be able to send money to disabled accounts (which our bank definitely doesn’t want).
We need to add in a check that enforces that the recipient
account is enabled (which is required by the Deposit
entitlement).
Now that we have all of the required checks in place, our code is able to compile 🎉
Dacquiri Going Forward
Dacquiri is a powerful framework for teasing out authorization vulnerabilities in Rust applications, and preventing them from being introduced in the future. I do think there’s a bigger message here about leveraging type systems to help write more secure code — there’s ample opportunity to identify and eradicate classes of vulnerabilities using approaches enabled by more advanced type systems. This gives me hope that Rust will be taken as a serious language for secure code beyond the protections it offers for memory safety and concurrency.
In the meantime, I’ll be integrating Dacquiri into resync further to help ensure that resync is as secure as possible. Dogfooding Dacquiri has helped fuel the development of new features like async
support, contexts
(e.g. database connections for attribute decisions), and the ability to access an attribute’s associated resources (preventing many common cases of IDOR).
My hope for Dacquiri is that you walk away with a novel idea on how to solve some of the security problems you face in your day-to-day by building secure-by-default frameworks and leveraging the type system in your language. If you’re using Rust or are just curious about Dacquiri, give it a look over at the Github repository below.
Thank you!