Have you ever wondered how it is that systems and platforms handle user access and permission while preventing attackers and bad actors from having access to the server file system and restricted files? Well, that is a great question. Understanding the fundamentals of access control and authorization is a great asset for any developer. For those who work on sensitive applications that require a high level of security, or even the enthusiast who wants to learn a bit more, we'll be addressing your questions.
Our goal with this article is to explore the subject of access control and how to ensure adequate security for web applications:
Firstly, we will briefly address what broken access control is.
Secondly, we will illustrate what broken access control looks like and what vulnerabilities it targets.
Finally, we will offer some mitigating solutions for said vulnerabilities.
This article is specifically targeted at the Ruby on Rails stack and its technologies. If you're not a Ruby developer or haven't yet familiarized yourself with this development stack, please follow this link and put some hours into it as we'll be talking about some concepts for which you might need some background with Rails websites to grasp. This isn't mandatory since many of the explanations in this article could apply to any technology, but it'll help you understand better.
Now, let's get into it.
Defining Access Control
If you've ever interacted with a login page, then you've already interacted with some form of access control. As its name states, access control is a set of policies and mechanisms that a system implements and enforces to regulate and control who has access to what. It's commonly known as authorization. Typically, once the server has determined who you are through some login or authentication mechanism, it then grants or restricts what functions and resources you have access to in the system. Additionally, this also serves as the backbone to track user interactions and monitor resources.
At face value, this explanation may convey a level of simplicity to the logistics of implementing access control. Yet, adequately implementing a robust and secure access control system is deceptively complex and tricky. An application's access control, and a web application in specific, is intimately bound to its architecture and the function it provides. More importantly, users of most platforms commonly fall into more than one role, which means the restriction complexity can increase exponentially.
Developing proper access control in our applications can be a real challenge, even for experienced engineers. Depending on the scale and complexity of the system, an adequate solution could be implementing a simple authentication solution, a third-party library, or even customizing a combination of both. In Ruby on Rails, there are popular and robust solutions like Devise, Cancan, and AuthLogic, to name a few.
What Is Broken Access Control?
All right—so now that we understand what access control is in our system, we can talk about how attackers try to exploit vulnerabilities to breach it.
Broken access control essentially encompasses a set of exploits or vulnerabilities that are known and can represent a threat to our system's access control. Although many of the flaws aren't difficult to exploit when overlooked, we can address them relatively easily. Additionally, the consequences of a breach in Access Control can be disastrous since attackers can read, change, or even delete content and take over our system. Therefore, we must fix these issues adequately.
Broken Access Control Vulnerabilities
So, what are examples of access control vulnerabilities we can see out there? Well, there are many. But we'll focus here on insecure IDs, path traversal, file permission, and client caching.
Let's explore them and see what makes them work.
Insecure IDs' Vulnerability
If your application uses a database to store data, then chances are you use IDs to identify these resources. Most websites nowadays use some form of ID or index to quickly and efficiently handle data. And as we've already mentioned above, some resources aren't for every user to access. So, if your website allows ID inputting and these are easy to guess, then you might have a problem.
Think of it like this. Imagine we have a profile page section where we display the user profile. Then, the following URL retrieves the user profile:
Good, right? Well, not really.
If I manually change the number in the query string and there's no active Access Control implemented to validate who's requesting what, I can have access to any profiles.
Path Traversal Vulnerability
Path traversal involves navigating the directory tree of a file system. As you might imagine, this means that systems allowing the user to access resources in the server file system are vulnerable. This situation is particularly critical if we don't validate the path.
One example would be something like the following:
In this case, a bad actor could change user.png to something like ../../etc/passwd and attempt to gain access to the application's secrets. Yikes!
File Permission Vulnerability
Expanding on the previous vulnerability just explored, a file permission vulnerability relates to the server's file system permission mechanism. A server application contains many crucial configuration files and resources that shouldn't be modifiable and, in most cases, should be non-readable and non-executable to users. However, if proper configuration policies are lacking, an attacker can target these files, taking over the entire platform.
To illustrate this, here's an example of an attempt to access a file that should be unreadable:
Client Caching Vulnerability
The chances are that some of your clients share their computers with someone else. That's a factor that we, as developers, can't control, and it's this specific factor that breeds the client caching vulnerability. Client caching is tough to address because it involves a bad actor taking advantage of the session credentials or the cached pages or data in the browser already present in an authenticated client.
Of course, this also means that an attacker needs to have physical access to the victim's computer. Still, once this happens, a sufficiently determined attacker can wreak havoc on the user data.
Addressing Broken Access Control in Rails
The process of addressing access control vulnerabilities can be very simple or incredibly complex, depending on the complexity of your platform, architecture, and data sensitivity.
For Ruby on Rails, the first step is to implement proper authentication mechanisms. For the most part, implementing a robust and proven solution like the Devise gem would get you 80% there. However, for those who need more customization, Devise also offers extensive customizability and additional add-ons like Devise-Security, which extends the library's functionality.
A straightforward implementation of Devise involves adding the gem to your Gemfile.
# User authentication
gem 'devise', '~> 4.7.1'
Next, run the generator:
$ rails generate devise:install
This command will generate an initializer file where you can configure all the settings you need.
Next, you need to input the following command:
$ rails generate devise MODEL
This command will generate the model file representing the user in your application and to which Devise will apply all the authentication policies. Note that you must change the MODEL with the name of your user model file.
Finally, you can run the following command, which will then apply the changes in the database, adding all the fields necessary on the tables:
Congratulations! You've just implemented a solid authentication solution.
There are some additional configurations and tweaks needed to implement, but for that, we'll guide you to the Devise wiki page, which has an extensive guide on how to make the gem your own.
Tackling Broken Access Control Vulnerabilities
Now, to address the specific vulnerabilities mentioned before, we need to make a few changes. Do keep in mind that Rails already goes to great lengths to mitigate most of the vulnerabilities in access control. We will, however, illustrate some ways you can complement the built-in security measures in Devise and Rails.
We must address this issue early on in our system's development. IDs shouldn't be easily guessable. You must develop your system so that sensitive IDs are both obfuscated and unique. This solution is easily achievable by implementing GUIDs as IDs.
However, this isn't enough to protect our systems —not nearly. A robust solution would involve session validation of every request and proper access control of who is requesting what. We can achieve this by using Devise's already defined current_user variable. This variable contains the logged user object and can be used to determine access control.
Additionally, If you have configured roles for the users (Admin), then the current_user will allow you to confirm the privileges of that user by just using the .admin? method.
Mitigating path traversal attacks involves implementing proper validation on user input and restrictions on the mobility of the server directory and access control.
Fortunately, you don't need to do much at all to implement proper Path Traversal policies. However, make sure that if you enable functionalities that require accessing and modifying files in the server, like file upload, you implement robust and tested gems like Paperclip or CarrierWave.
For more on this, check our article focused on Path Traversal on Rails here.
Tackling File Permission vulnerabilities requires extensive knowledge of server configuration and maintenance. However, unless you need to tinker with server permissions, no changes are necessary. As before, Rails does most of the work for you. Nevertheless, consult with your server manager if you need peace of mind.
In terms of client caching, the most effective solution is also the most simple. Don't store sensitive user information on the client.
if you must venture into sophisticated features, implement proper HTML meta tags and async validations to confirm authority on display.
As you can see, achieving robust access control policies can be an overwhelming task. This is especially true as our platforms grow in size and complexity. Nevertheless, we must do what we can to close as many gaps as possible. It has been reported that broken access control is now the highest vulnerability in OWASP Top 10 2021. We must rise to the task and bring creative and sustainable solutions to these problems.
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.