Eliminating Authorization Vulnerabilities with Dacquiri

d0nut
6 min readJan 29, 2022

--

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.

Dacquiri catching our authorization 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).

Dacquiri stopping us from letting users not in the enabled state from receiving money.

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!

--

--

d0nut

Security Engineer, developer, and part-time bug hunter