How to do a quick check for access control vulnerabilities in Rails applications
By far the most common security vulnerabilities I see in Rails applications revolve around access control (a.k.a. authorization). Here's how to quickly scan your codebase for the most obvious forms of these vulnerabilities.
Access control is the part of the code that answers the question Is this user allowed to do what he's trying to do? It's common to find Rails apps where this code is either broken or non-existant.
Quickly grep the codebase for a smoking gun
The offending code will most likely be updating a model without checking if the user should be allowed to. So a quick way to search for that would be:
$ cd project_root $ grep -E 'create|update_attributes|delete' app/controllers/*
This should give you a rough idea of where models are getting created, modified or deleted in your controllers. Most Rails apps will have a few entries for each of the method calls listed in the above regex.
Check access control around each method call
For each of these method calls, (we'll call them 'updates') ask yourself the following:
- Should all users be allowed to update the model in this way?
- If not, can you point out the code that stops users who shouldn't be allowed to do the update?
- Should users be allowed to set all the fields on the model in question?
- If not, can you point out the code that stops users from updating all disallowed fields?
If you can't point out the code that enforces your applications access control rules, your code is vulnerable. Here are some things an attacker may be able to do if you have this sort of vulnerabilty in your application:
- View other users data
- Upgrade priveleges to an 'admin' on his own or another users account
- Delete entire tables worth of data from your application
That's just a small sample.
Most applications suffer from this class of vulnerability in some form or another. While fixing access control won't make your web application bullet-proof, it's one of the low-hanging fruit that security researchers will try first when attacking your application.
In this code sample, I haven't implemented access control at all:
class BananasController < ApplicationController def show @banana = Banana.find(params[:id]) end def update @banana = Banana.find(params[:id]) @banana.update_attributes(params[:banana]) end def destroy @banana = Banana.find(params[:id]) @banana.delete end def create @banana = Banana.create(params[:id]) end end
This code allows anonymous users to create, read, update and delete bananas at will. Not good!
Here's the same example with access control implemented:
class BananasController < ApplicationController def show @banana = current_user.bananas.find(params[:id]) end def update @banana = current_user.bananas.find(params[:id]) @banana.update_attributes(params[:banana]) end def destroy @banana = current_user.bananas.find(params[:id]) @banana.delete end def create @banana = current_user.bananas.create(params[:id]) end end
Here we've made the following assumptions:
- That we have a
current_user, probably provided by devise or a library like it.
current_user.bananasgives us an
ActiveRelationthat scopes banas to
current_user. When we call
current_user.bananas.find, we're searching within the current users bananas.
For this code to work:
current_usercan't be nil (a user must be logged in)
- The banana that the user is trying to create, read, update or delete 'belongs to' the logged in user.
If either of those two criteria aren't met, the code will throw an error. This is exactly what we want it to do as it means someone is trying to modify a banana they're not allowed to.
This effectively implements access control for us. While there is still the potential for security vulnerabilties in this code (you'll notice that the parameters haven't been whitelisted) this covers off the absolute basic requirements for access control around a resource.