As your application gets larger and larger, the surface area for security issues expands accordingly, and security bugs become more and more problematic.
Here are a few tips to avoid some common pitfalls regarding security for Rails apps.
It is quite common to want to mix I18n translation keys with HTML tags. I’d recommend against doing that as much as possible, but sometimes you can’t really avoid it.
Let’s take the following example:
We have a problem here, because this will produce the following output:
Welcome <strong>John</strong>!
Oops! Indeed, our string was never marked as html safe, therefore rails will escape html entities.
One (bad) way to fix it would be to do the following:
While it works, we just exposed ourselves to a nasty XSS. Indeed, our user can now change their name with some pesky JavaScript in it and the JavaScript will be executed.
XSSes are often underrated as benign security issues, but they can be fatal if exploited properly.
Fortunately, Rails has a nice solution for us: if an I18n key ends up with _html
, it will automatically be marked as html safe while the key interpolations will be escaped!
Note that this is pretty much the same as doing this:
One good way to avoid XSSes is to really try to avoid using html_safe
(or raw
) as much as possible, and when forced, double check that you have full control of the content displayed.
You can’t trust user params; you most likely already know that. But there are different ways to implement sanitization of user params.
Let’s pretend we have a form, and we want to use one of two different Form Objects depending on a param:
Here, we get the good form class by constantizing a string that is controllable by the user. This is very bad practice and can lead to terrible side effects (imagine sending make_user_admin
instead of foo
or bar
)
One solution could be to do this:
Here we are ‘safe’. We check that the params are one of the two expected values and only constantize if needed. While this works fine, we haven’t corrected the root security issue (which is the use of constantize
over user input).
Code grows old and evolves, developers copy and paste parts constantly, and at some point your offending line could end up outside of its guard.
Now let’s have a look at this alternative:
We have the same behaviour as above, except this time we don’t use constantize
.
By being defensive and keeping a close eye on user input, we can avoid many basic security issues.
Consider the following code:
This code is voluntarily weird-looking in its structure to be vulnerable to the security issue, but trust me, I’ve seen it in the wild ;)
Everything works ok here until we start messing a bit with the params.
Let’s imagine we send the following request:
POST /delete_user?id[]=42&id[]=43&id[]=44&id[]=45..
Rails will parse params[:id]
as an array: [42, 43, 44, 45]
Thanks to some weirdness in find_by
and messing with the params, here we managed to act on records we may not have access to.
It’s always good to remember that params can also be arrays (or hashes!) as that can pose some security risks.
Most of us are very wary when dealing with user params, or values coming from the database.
However, there are some attack vectors that can be forgotten, including (but not limited to) the following:
Referer
, User-Agent
, etc.X-Forwarded-For
)It’s always good to think about where any given input comes from and wonder if it can be tampered.