In this article, we'll discuss rails broken object level authorization vulnerabilities and how bad actors use them to exploit your systems.
Firstly, we will define what broken object-level authorization is and how it differs from other authorization attacks. Then, we will provide some examples of the most commonly used broken object-level authorization attacks. Finally, we will leave you with some actionable steps and guidance to mitigate this threat and protect your systems.
By the end of this article, you'll know what broken object-level authorization is and how it affects systems all over the net. Additionally, you will be able to spot vulnerable spots in your systems and have the knowledge and expertise to address them.
Before we start, we want to clarify that this article is aimed at Ruby on Rails developers. With that out of the way, let's jump in.
What Is Broken Object-Level Authorization?
Before we address the big question, I think it's essential that we understand what authorization is and the nuance behind it.
In the context of software security, authorization is the mechanism that provides or denies access to resources in a system. Its main job is to ensure that only the people and services authorized to access these resources (data, mainly) can do so. This means that without a proper and robust authorization mechanism, all data in a system is at high risk of falling into the wrong hands.
Authorization is sometimes confused with authentication since they serve a similar purpose in gatekeeping access. However, they are very different systems that are designed to fulfill roles beyond just access protection.
Authorization systems are basically the policy enforcement mechanism for your application. They provide the infrastructure to maintain confidence in the security of your system between users. On the other hand, authentication is mainly concerned with keeping non-users out.
Alright, now let's address the big question. What is broken object-level authorization?
Broken object-level authorization, or BOLA, is a specific attack that targets weak or poorly implemented authorization mechanisms. It exploits endpoints that allow user input to retrieve objects (data) and have no user authorization validation.
In essence, an attacker can exploit your application whenever it doesn't properly confirm that the user requesting a resource has ownership of the said object.
How It Differs from Other Authorization Vulnerabilities
This is different from other authorization attacks like, for example, broken authentication because the target is not to gain access to the system but to resources within the system.
In the case of BOLA attacks, the targets are the endpoints of APIs the attacker has access to.
Sadly, almost every business has APIs with some endpoints that are vulnerable to some form of broken object-level authorization. This is not unexpected given the sheer number of possible targets and configurations in the systems we depend on today.
That is why the OWASP named broken object-level authorization the most severe and most common API vulnerability today.
So far, we have mainly focused on addressing broken object-level authorization vulnerabilities in the context of APIs. And the reason is that these attacks primarily target back-ends and APIs accessible to users like mobile back-ends and services.
Examples of Broken Object-Level Authorization Vulnerabilities
Now that we have defined what broken object-level authorization is, let's see some examples.
Mainly, there are two types of BOLA attacks:
Targeting User ID
When we set a vulnerable API endpoint to receive input from the user, an attacker can take advantage of poor implementations to retrieve resources. In this case, the endpoint will receive the provided user ID and try to access the user object.
A simple example of this attack would be something like the following:
bigbank.com/myapi/statements/get_statements_for_user?user_id=666
Targeting Object ID
Following the same pattern, an endpoint that allows user input of objects to retrieve resources allows attackers another avenue to retrieve resources. However, in this case, the input is the object ID itself. The vulnerable API endpoint receives an object ID provided by an authenticated user and tries to access it.
An example of this attack would be the following:
bigbank.com/myapi/transactions/download_pdf?transaction_id=1234
It is pretty clear what kind of trouble would arise if a user accessed our system's transactions by mere guesswork. Therefore, although convenient, you should avoid this kind of practice at all costs.
As I have mentioned before in our NodeJS article on broken object-level authorization, "Many could argue that there are specific cases where data has no sensitive nature. And thus you could forgive the use of simpler, more lenient approaches. However, I would argue that this vulnerability is, in fact, representative of a fundamental gap in the infrastructure and design of any system, and accepting the gap as a feature is shortsighted and dangerous."
Mitigating Broken Object-Level Authorization Vulnerabilities
OK, so how do we address these vulnerabilities, then?
The good news is that the mitigation strategy is simple. However, eradicating the threat might prove quite challenging, depending on the scale of your platform.
First, make sure you have implemented a robust and battle-tested authentication and authorization framework like Devise.
To install Devise, all you have to do is add the gem on your Gemfile.
gem 'devise'
Once you've done this, you need to run the bundle install command to install the gem dependency in your project.
bundle install
Next, run the following command to generate the default resources the gem needs on your project.
$ rails generate devise:install
Now, add the following configuration in your config file to set up the emailing settings for user authentication and confirmation.
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
After doing this, run the following command to generate or update the models representing the users in your project. In this case, you can replace MODEL for whatever model name you're using for your users.
$ rails generate devise MODEL
Finally, run the migration command to update the database to reflect the revised model structure.
rails db:migrate
All you need to do now is add the following line on the controllers you want to enforce authentication on.
before_action :authenticate_user!
There are a few more steps that you might need to follow to have your application protected with Devise. Please visit the devise official GitHub page here for more information.
Now, let's see how we can implement the mitigation strategies in our project.
Vulnerable User ID Endpoints
To address this vulnerability, ensure that the user ID in the session object, which resides in current_user.id, matches the user-provided value. If there is no match, then deny access and redirect the user. That's it.
It's important to note that the implementation of this solution could get a bit more complex as you start introducing user roles and hierarchy. However, Devise provides excellent documentation and examples of doing just that on their extensive developer resources.
Vulnerable Object ID Endpoints
Now, this vulnerability might require a bit more work to address, given that multiple users might have access to resources, and there's an inherent complexity level in user hierarchy involved. However, the logic to address this issue is still the same.
Once you have identified a vulnerable endpoint, you can confirm that the user has access to this object by including it in the query string that retrieves it.
One example would be the following:
Statements.where({ id: params[:id], user_id: current_user.id })
This example assumes that the Statements table has a user_id column or that the Statements model has already defined the table relationship with it properly.
Final Thoughts
Unfortunately, rails broken object-level authorization vulnerabilities exist mainly due to poor implementation of authorization mechanisms and human error. Additionally, many development teams consider that some endpoints don't need strict authorization mechanisms. This is because they lack sensitive data in use. Given that the web comprises APIs that handle sensitive and non-sensitive data, it is easy to assume that some requests should have authorization checks and others shouldn't; therefore, it's easy to dismiss a check when writing your code.
Nevertheless, keep in mind that it is your responsibility to protect the data of your users by providing robust and secure platforms.
This post was written by Juan Reyes. Juan is an engineer by profession and a dreamer by heart who crossed the seas to reach Japan following the promise of opportunity and challenge. While trying to find himself and build a meaningful life in the east, Juan borrows wisdom from his experiences as an entrepreneur, artist, hustler, father figure, husband, and friend to start writing about passion, meaning, self-development, leadership, relationships, and mental health. His many years of struggle and self-discovery have inspired him and drive to embark on a journey for wisdom.