The Dynamic Duo of Broken Access Control

There's a reason that Broken Access Control sits at the top of the OWASP Top 10.

When developers get access control wrong, the consequences are immediate and severe. Users can view data that isn't theirs, perform actions outside their role, or even escalate privileges to become administrators.

These aren't just theoretical risks.

They're the kinds of issues that consistently show up in penetration test reports time and time again.

Unlike obscure vulnerabilities buried deep in cryptography, access control flaws are often simple oversights that slip past code reviews and automated scanners, yet they lead directly to data breaches, reputational damage and becoming the next headline on the news.

Not a place any company wants to be in.

Now if you are unfamiliar with either Mass Assignment or Insecure Direct Object Reference (IDOR), prepare for a 101 in both classic vulnerabilities.

Mass Assignment

In short, mass assignment is when a client can add additional information in a request that is accepted by the server.

Not so nefarious on the surface, but what if that information is controlling the variable which determines if the user is an administrator or the owner of a tenancy.

Picture this.

Your application allows user to edit their profile.

The request looks something like:

POST /api/users
{
  "username": "alice",
  "email": "alice@example.com"
}

And it just so happens the backend contains a variable to identify super users amongst the pack.

Which leads us to exhibit A.

POST /api/users
{
  "username": "alice",
  "email": "alice@example.com",
  "is_admin": true
}

And now Alice is an admin…

Some might say that example is overly simplistic, and they would be right.

But the sad truth is that it is more common than you think.

Now that we know how mass assignment is exploited, we can look at how we can prevent it.

Most frameworks have a method for either allowlisting or blocklisting, and when it comes down to it, allowlisting prevails.

Looking at an example Cursor has graciously provided for us in Django, we have a profile of a user that is currently listed as an employee.

Image of a demo website employee profile page

With some good guess work they submit the request.

curl -X POST https://vanguardcyber.lab/employee/3/update/ -H "Content-Type: application/json" -d '{"is_admin": true}'

Which unfortunately works and updates their role to an administrator, giving them access they didn't previously have.

Image of a demo website employee profile page with administrator role

Now they are able to access the Employees panel and found themselves with control over the application.

Image of a demo website administrator employees panel

Now that might sound farfetched to exist in a modern web application.

Sadly, we see it time after time.

It's a privilege escalation here or a lateral movement there that can expose your entire web application.

You're probably thinking that is all well and good, but how do you fix the issue.

Well, when we look at the code, which in this instance we'll use Django as our test subject.

The request to the server that is responsible for updating user profiles, seems to blindly accept user input and apply it.

The code is only checking if the user "hasattr" or has the attribute, then it goes ahead and sets the attribute with "setattr".

Image of a demo website code with a vulnearbility to mass assign the administrator role

Now it's obviously clear that something is wrong with the code, and we shouldn't be automatically updating a user based on what they provide.

Previously, we said that allowlisting was generally preferred to applying a blocklist.

And to solve our issue we can do just that.

We create a list of all the user fields that we want to allow the user to modify.

In this instance, we've called it SAFE_USER_FIELDS.

Once in place, users will only be able to update the fields that you want them to, not the other way around.

Image of a demo website code with a list of safe user fields fixing the vulnerability

So that covers off how you can avoid mass assignment ruining your Friday afternoon.

Let's look at what happens when IDOR vulnerabilities go overlooked and start to plague a web application.

Insecure Direct Object Reference

IDOR allows a user to access resources like profiles and pages through modifying the reference to a page.

To break it down, a classic example is a URL that looks something like:

https://vanguardcyber.lab/employee/2/
or
https://vanguardcyber.lab/employee/550e8400-e29b-41d4-a716-446655440000/

Where the 2 and 550e8400-e29b-41d4-a716-446655440000 in this case will be the identifier for the user.

As much as it is harder to guess a UUID or Universally Unique Identifier, the addition of obscurity isn't the best defence against IDOR.

Protection comes when the user is validated prior to accessing a resource on the server.

For our test case though, we have made it bleedingly simple to see how if your application has IDOR a user will be able to have more access than you expected.

For us, we have John.

He's logged in and looking at his profile page.

But it turns out he's curious and noted that his URL says, https://vanguardcyber.lab/employee/2/ and if he's number 2, who is number 1.

Image of a demo website profile page for user 2

As there is no actual intentional functionality in the application that allows users to view other user's profiles, he's decided to modify the URL to be:

https://vanguardcyber.lab/employee/1/

And voila!

Image of a demo website profile page for user 1

Now that's not John's profile.

Instead, he's found himself the administrator of the site, with their username and details.

What he was meant to see was a clear message that he will curb his curiosity for now.

Image of a demo website example error message when a user tries to access a profile page that they are not authorised to see

A clear message that he will curb his curiosity for now.

So, what caused this and how did it get fixed.

Looking at the code there is the line in the view_profile function that will render the user's profile without first checking that the user requesting the page is authorised to see it.

Image of the vulnerable code which allowed the user to access a profile page that they were not authorised to see

A simple fix is to do just that.

Once there is a check in-place, it will look like this.

Image of the fixed code which now checks if the user is an admin and the request is sent from the correct user

So now, we have a redirect in place for any user that isn't looking for their own profile.

if not request.user.is_admin and request.user.id != user_id

The quick conditional check will ensure that they aren't an admin and the request is sent from the correct user.

Done.

A couple of quick code changes can prevent a major issue popping up in your application.

But of course, we haven't covered nearly enough of the broken access control issues to say that's all you need.

Yet for now it is a good start.