Over the past year VDA labs has conducted application security audits on several Ruby on Rails applications for both local businesses and very large corporations. This has been something of a treat for me personally because it was very interesting to be on the other side – I have a fair amount of experience developing in Ruby, but specifically looking for bugs is a different kind of fun!

In the past my (incorrect) assumption was that by using a ‘modern framework’, you were protected from having issues in your application like SQL Injection, and using a well regarded Auth plugin made your site secure by default. What we see, however, is that even though these frameworks offer protections, even by default in some cases, developers still find ways around them – intentionally or otherwise.

Based on this experience, we wanted to help spread awareness of some common issues we see in Rails applications. In order to do this I have set up a copy of the RailsGoat application. RailsGoat is an intentionally vulnerable application written in Ruby on Rails by OWASP that aims to highlight how and where vulnerabilities occur in Ruby on Rails applications. In this case I installed the application using Docker on a fresh Ubuntu virtual machine.

The RailsGoat application allows a tester to familiarize themselves with the OWASP Top 10 security vulnerabilities. If you’re not familiar with these, I recommend you visit the OWASP Top 10 PDF and read up before continuing on with this post. Basically these are the most common categories where issues are found with web applications. We are going to look into some of these specifically with the RailsGoat application.

SQL Injection

SQL Injection is one of those bugs that I thought had gone extinct due to modern frameworks protecting us from ourselves – that is not the case, however. On a recent application security test we conducted we found an SQL Injection entry point through static analysis that could have had disastrous consequences for our client. Complete deletion of data, possible exfiltration, or even compromise of their web server were all possible.

In the RailsGoat application there is an example of an insecure SQL query on the Users controller at “app/controllers/users_controller.rb” specifically on the query below:
user = User.where("id = '#{params[:user][:id]}'")[0]

In this case it becomes very easy to inject an SQL statement into the command above because the developer has directly passed HTTP parameters to the query, so a string like

') OR admin = 't' --'")

could be used to change the password of the first administrator of the system. Issues like these are somewhat tough to spot because the secure code, which uses the framework the right way, looks quite similar to the insecure version:

user = User.find(id: params[:user][:user_id])

The difference is that in this case, instead of concatenating the raw parameters into an SQL statement for processing, we are passing the hashed parameters DIRECTLY to the Find statement, which allows ActiveRecord to sanitize them before executing the SQL query. This effectively shuts down any potential for SQL injection. There may be times when this is not possible, and only a direct SQL statement will do what is needed, but extra care must be taken in those cases to protect the system against injection.

Cross Site Scripting (XSS)

Cross Site Scripting is, unfortunately, often treated as less of a threat than other types of vulnerabilities and therefore sometimes ignored. XSS occurs when untrusted data is allowed into a web page without proper validation or sanitization. In Rails, XSS protections have been in place by default since Rails 3.0. That means that developers have to go out of their way to bypass those protections. Fortunately that means these issues can be discovered in code easily sometimes. In this case we can search for ‘html_safe’ across the code and we find the following instance in “app/views/layouts/shared/_header.html.erb”:

Never mind that they have included a handy note pointing it out here! Despite the term, this setup is definitely NOT safe. This XSS vulnerability could be used to inject java script into the page, which then could be used to steal passwords for example. The fix is very easy in this case – simply removing ‘html_safe’ from this view will allow the built in ActiveController functionality to safely encode the characters so they can’t be misinterpreted by the browser.

IDOR – Insecure Direct Object Reference

IDOR errors are something we run into all too often in our app penetration tests. An insecure direct object reference (IDOR) occurs when an application allows access to or manipulation of a key that references a specific object in the system by a user that should not have permissions to access that object. It doesn’t matter what framework or language is being used, there’s usually a way for IDOR class vulnerabilities to work their way into the system.

Finding IDOR bugs can be either very easy or very hard. That is because often times references to an ID column in a database, for example, are intentionally exposed such as “/get/post/6”. An IDOR issue arises when the user is supposed to have access to url “/get/post/6” but not “/get/post/9” but the system does not properly check those permissions. Let’s find an IDOR issue in RailsGoat. It looks like visiting the “Work Info” page within RailsGoat shows some basic user info for our account:

Note the “6” in the URL – if we change that, what happens? We can see everyone else’s pay and the last four digits of their Social Security Number! That’s definitely not a feature you would want in a system like this. So how would we go about preventing this issue? Let’s first take a look at how this page generates it’s data by looking at how this data is generated in “app/controllers/work_info_controller.rb”:
@user = User.find_by(id: params[:user_id])

We can see here that the @user object is being set by the query “User.find_by(id: params[:user_id])” – which is basically getting the ID from the GET parameter in the URL. That is probably NOT what we want to do. Instead a more secure way of doing this is setting the @user parameter based on the “current_user” session variable like this:

@user = current_user
This way the user only has access to their own record. Those who are following along may note that fixing the RailsGoat application in this way would dis-able the admin user from seeing other user’s info, where it looks like they could intentionally before. This would be a good opportunity to review the design parameters to see if that was, indeed, an intentional decision and then work around it as necessary.

Wrap Up

It’s easy to see how issues like these come into even modern applications. That’s why it’s always a good idea to make sure your developers are aware of the basics like the OWASP Top 10, have a second set of eyes review your application, and to invest in application security training. We also hope you come back and learn some more about Rails security on the next installment of Rails Vulnerabilities and Where to Find Them where we will look at a few more common issues we see during our testing.