Skip to main content

Insecure Direct Object References (IDOR)

Setup #

There is nothing to download or install for this lab. You can access the lab environment by visiting astarml.com.

Introduction #

Insecure Direct Object References are vulnerabilities that stem from failures in access control in functionality where user-supplied input provides access to objects (data, pages, functions, etc) directly.

Direct Reference to Database Objects #

Once we log into our lab site, we’ll see the page url follows the format of /user/<id>/profile

idor-url-format-profile.png

And this page contains information about the user that is currently logged in.

idor-profile-page-alice.png

If we’re thinking like an adversary, it’s awfully suspicious that the ID of the account we’re logged into is 1, meaning we’re probably the first user account. So what if we just change the ID to 2 within the URL?

We’re now seeing the account information for the second user, bob

idor-profile-page-bob.png

If we go look at the JWT cookie for our currently logged-in user, we’ll see that it is in fact the cookie for alice. So why are we able to see bob’s information?

idor-cookie-alice.png

Let’s look at what’s happening the under the hood when we request the user’s profile…

We see that the /user/<id>/profile endpoint does call the requireAuth function.

idor-profile-page-server-code-1.png

However, that /requireAuth function only validates that the token exists and is valid.

idor-requireAuth-function.png

There’s no code in the requireAuth function or the /user/<id>/profile function that validates the user is authorized to call that endpoint or view the data.

We can validate this authentication does work, but calling the page https://astarml.com/user/1/profile from within a private/incognito browser. You’ll see that we were immediately redirected to the login page.

idor-unautheticate-profile-attempt.png

What this ultimately means is that once a user is logged-in, they could access any other user in the system. Because the user ID’s are incremental, this becomes even more trivial to accomplish.

Direct Reference to Static Files #

While the root cause is ultimate the same, a lack of authorization checks on who can access what files. Another very common avenue of IDOR is when uploaded files are available automatically available to all users.

If we look at the documents page. We see that it returns a list of files that belong to our user alice. We’ll notice that the documents are named incrementally. While IDOR doesn’t technically require items to increment, it is one of the classic traits that should ring alarm bells when looking for it.

Clicking into one of the document, we’ll see it follows the format of /document/<file name>.

idor-document-link.png

Incrementing that document number, we’ll start to see that we can also view other users documents.

idor-document-link-bob.png

Much like the user profile above, the documents are protected against unauthenticated access, but once a user is logged in, they have free rein over all documents, aided by the fact the documents are incrementally named.

idor-unauthenticated-document-attempt.png

Securing against IDOR #

Enforce Access Control Checks #

Every endpoint should have code (preferably a centralized function) that validates that the user making a request is authorized to interact with the resource being accessed. This does require authorization information to be stored and maintained, but this is why we covered access models such as RBAC/DAC/MAC early on in the course. The pseudocode below is an example of simple the check can be.

// Pseudocode
if (request.user.id !== targetObject.owner_id) {
    return res.status(403).send('Forbidden');
}

Non-predictable ID for object references #

While not a replacement for proper access controls, it’s also best practice to utilize non-predictable ID’s for object references rather than incrementing a resource ID. As an example, UUID’s, also known as Universally Unique IDentifiers, also known as GUID’s (Globally unique…), are 128 bit long strings that guarantee unique identifiers in the system. This prevents collisions as well ensures that bad actors can’t guess the next object.

Other options for identifying objects are random tokens (using secure random number generators) or hashing (for example, hashing a file and then using that hash as the identifier). An example of hashing being used is how github identifies commits being made to the system. Because they are unique, they can use these hashes as identifies and then think them throughout Github knowing they will remain unique.

Avoid taking in user input as a way to retrieve information. #

In our example app, the profile page is loaded by visiting the page /user/<id>/profile. Realistically, this is unnecessary. Because the cookie contains any information we require to authenticate the user, we could instead write the app so when we visit /user/profile, the server reads our cookie, identifies us, and then returns the profile information required to populate the page. This removes user supplied data from the data processing flow.