Ruby on Rails Security

Photo by Matthew Henry

We talk to a lot of folks about Rails, from VCs looking to staff their next company to new coding bootcamp grads. And one thing we see from newcomers and veterans alike is a lack of knowledge of web application and Rails security concerns.

Over and over again we see plans for a new web app with the assumption some “security contractor” is going to come review any and all security problems introduced by the team doing the actual building of the application. Rarely does that security contractor get hired. And so security bugs linger. And even worse, the development team is taught that they don’t have to be vigilant about security.

So, here are just a few of the big patterns we spot constantly (there are plenty more I’ll share if you keep watching this blog). These will be useful for folks who aren’t paying attention to Rails security and a good refresher for those who might be.

Query string interpolation

“String interpolation.” Fancy schmancy phrase right there. From Wikipedia:

In computer programming, string interpolation (or variable interpolation, variable substitution, or variable expansion) is the process of evaluating a string literal containing one or more placeholders, yielding a result in which the placeholders are replaced with their corresponding values.

String interpolation makes it easy for a string to get some other values added to it. Here’s an example:

ruby_variable = "cool"
string = "Nate is #{ruby_variable}"

Doesn’t look that useful here, but when that ruby variable is calculated or the string gets more complicated:

string = "Nate is #{ruby_variable}. And super smart."

You can start seeing the power. So, many Rails developers immediately reach for it to create queries:

query = "category = '#{params[:category]}'"
projects = Project.where(query).limit(10)

Makes sense. Find 10 projects based on a user submitted search of a project’s category. But, it’s a “you just blew up the company” kind of mistake.

You’ve given attackers a way to inject whatever SQL they want into params[:category]. And they might stick in:

') AND private=true'

And now your backend query returns projects that were meant to be private. Or a:

'); DROP TABLE USERS;'

And now parts of your database go missing. So never, ever use string interpolation for your queries in Rails. Instead make sure you use Rails’ ability to escape SQL snippets. You should have written your project query this way:

projects = Project.where("category = ?", params[:category]).limit(10)

Simply adding that question mark to your string and passing in params[:category] as an additional parameter to where() tells Rails to make params[:category] safe for SQL.

No scopes

The above examples should have made your skin crawl for another reason.

projects = Project.where

When would anyone need to search across all Projects? Rarely. Instead those projects are probably scoped to some kind of user.

projects = current_user.projects.where

Or account:

projects = current_account.projects.where

Something. It is a very rare occurrence where you’ll ever need to find something across an entire table in your database. But we see this mistake constantly — finds start with that Top level model when they should be scoped. You might still get the query wrong, hopefully you don’t, but if you did, at least the damage is contained to just a user or account rather than leaking one customer’s information to another.

When I’m doing code reviews that’s the first thing I look for: model names being used with a capital letter. Brakeman, a security scanner for Rails, even looks for it.

Scope those queries. There are very few reasons where you don’t need to.

Trusted input

Here’s some client side JS I spotted recently:

$.ajax({  
url: "/selected_answer",
type: "POST",
dataType: "json",
data: {user_id: current_user.id, question_id: question_id}
});

user_id passed in of the current user.

Anything from your JS code can be tampered with. So user_id in this case is something that should absolutely not be trusted passed in from the client using your app. What’s stopping someone from putting in whatever user_id they want there?

This is a perfect use of Sessions.

(We also find a lot of Rails and web developer newcomers don’t realize they even have Sessions available. Sessions allow you to save things about your user/app during multiple requests of your application.)

In Rails, Sessions are tamperproof. So once you stick user_id in a Rails session after you validate their username+password, a user can’t change that user_id by messing with their cookies.

But there’s another thing in the example above. question_id. You shouldn’t trust that either. Similar to point #2 above, scope a query to make sure that question is indeed something this user has access to.

So instead of doing:

@question = Question.find(params[:question_id))

Maybe you could do:

@question = current_user.questions.find(params[:question_id))

Handle the case where this finds nothing as you see fit, but it’s a starting point to dealing with question_ids users shouldn’t be using. Never trust input from a user.


These are just a few brief highlights of mistakes we see newcomers and veterans make with Rails a bit too frequently still today.

Users shouldn’t be blindly trusted. Validate everything. Make sure every input is as long/short/numbered/whatever as it’s supposed to be. Make sure every input belongs to who it’s meant for. Just assume everything will be tampered with unless it’s tamperproof and react accordingly. In other words…

Whether you have “security” in your title at work or not, invoke the X-files principle during your web application development: Trust No One.

P.S. Need some Rails help or even a pair of extra eyes to help with things like security? Please reach out. We’d love to help.

Originally published at www.rockstarcoders.com on October 30, 2018.