This post was originally published on the New Bamboo blog, before New Bamboo joined thoughtbot in London.
The advantage of using a mature framework like Ruby on Rails to build your web applications is that it is reasonably secure by default. Current versions (4.0 and above) have several built-in mechanisms to stop opportunist hackers; the Rails community actively works to identify and patch new vulnerabilities as they come along; and, the framework is well documented both officially and unofficially. However, if some of the security features are disabled, your code doesn’t conform to best practises, or you don’t appreciate web security in general, your Rails apps can be left open to attack.
To understand this in more detail, earlier this year we took a day off from working on client projects to learn about web security in a workshop hosted by former Bambino, Najaf Ali. We spent the day attempting to complete challenges that involved exploiting vulnerabilities in typical web apps. Each of the apps for the workshop had been specifically tailored to contain some “secret” information that we had to find without looking at the source code, consulting any databases associated with the app, or indeed examining the web server logs.
One common way to attack a web app is via code injection, where the attacker introduces their own malicious code or parameters into the web application. This usually takes place wherever there is a means for data to be added to the web app, such as in a form or a POST request. Sometimes this data is not sanitised before being executed by the web server, entered into the backend database or shown again by the web app. This creates an opportunity for the attacker to insert some code which will get executed within the secure context of the web app.
Fortunately, Rails gives you some help in defending against code injection. Firstly, plain strings in your ERB views are automatically escaped,
<%= "<script>document.alert('Mwahahaha!')</script>" %>
becomes,
<script>document.alert('Mwahahaha!')</script>
Furthermore, the sanitize
helper can be used to strip unwanted
HTML tags in your data,
sanitize("<script>document.alert('Mwahahaha!')</script>")
=> ""
You can also specify which tags you want to allow through,
sanitize("<strong>Safe</strong>\n<b>Dangerous</b>", tags: ['strong'])
=> "<strong>Safe</strong>\nDangerous"
And it’s clever enough to catch clever attackers. For example, the
gsub
method can be used to remove <script>
tags from a string but it
won’t completely sanitise the input,
"<sc<script>ript>".gsub("<script>", "")
=> "<script>"
but sanitize
will,
sanitize("<sc<script>ript>")
=> "ript>"
An extreme example of code injection is highlighted in the XKCD cartoon,
Exploits of a mom. Here the attacker (mom) named her son
“Robert'); DROP TABLE Students; --
” (Little Bobby Tables) for the school’s
records, knowing that if the unsuspecting school were to enter this in a query
in their SQL database system, they would delete all the student records. There’s
a more thorough explanation here. Fortunately, the
find
and find_by
ActiveRecord methods automatically
escape single quotes, double quotes, the NULL character and line breaks in SQL
queries, disabling mom’s exploit. However, developers still need to be wary of
any strings that are passed to query methods like where
and
find_by_sql
, and manually sanitize them.
Another way to attack a web app is through the session cookie.
The session cookie provides a useful way for the app to persist data across web
requests, so it is a common place to record information unique to a user, like
their ID. Fortunately Rails encrypts cookies by default so that session cookies
cannot be read or altered by attackers unless they have the value of the
secret_key_base
parameter in the secrets.yml
file.
However, an attacker who possesses a user’s encrypted session cookie may still potentially access the app posing as the user. This is known as session hijacking and can be mitigated by forcing the user to access the app via HTTPS instead of HTTP by setting,
config.force_ssl = true
in the application config file. Then the user’s session cookie (whether it is
encrypted or not) won’t be distinguishable in the contents of the request, and
subsequently won’t allow an attacker to “sniff” and copy it. In
addition to this it is also important to force the user to clear their session
cookie, in case they are accessing the app from a public terminal. The easiest
way of doing this is to prominently display a “Log out” button in the app, as
well as add reset_session
in the action associated with
logging the user out. An expiry time can also be allocated to session cookies
with the use of a session controller,
class SessionController < ApplicationController
def login
if login_successful?
reset_session
session[:expires_after] = 1.hour.from_now.to_i
redirect_to some_page_url and return
end
redirect_to new_login_url
end
def logout
reset_session
redirect_to root_url
end
private
def login_successful?
# check login is successful
end
end
and then check the session hasn’t yet expired upon every request,
class ApplicationController < ActionController::Base
before_action :check_session_validity
private
def check_session_validity
unless session[:expires_after] && session[:expires_after] > Time.now.to_i
redirect_to new_login_url
end
end
end
By understanding vulnerabilities like code injection and session hijacking, and knowing how Rails protects against them, we can have some confidence in the security of our web apps, and we can figure out how to fix them should they ever become compromised. Furthermore, it also teaches us why the safeguards are there, and ensure that if we ever disable them, we do so in a safe and controllable way.
I’ve only mentioned two common web security vulnerabilities in this post, but to learn about more the official Rails security guide is a great place to start. Also, the Open Web Application Security Project (OWASP) have created a cheatsheet for Rails, which is updated regularly, and the Ruby on Rails security mailing list is a good place to ask any questions.
There are additional tools that can be used in conjunction with Rails to ensure that your web app is not compromised. In particular, Brakeman is a static analysis tool that can identify vulnerabilities in Rails apps, and can be run as part of your continuous integration test suite. The SecureHeaders gem can automatically apply several security related headers to your app requests and responses. Also, Cloud Flare provides an easy way to protect your websites from online threats such as SQL injection or denial of service attacks.
If you want to understand and exploit vulnerabilities in web apps in a safe and instructive way, you should try the The Matasano Crypto Challenges. They are similar to the challenges we attempted with Najaf Ali but are not specific to Rails. Najaf’s workshop helped us realise that web security is everybody’s job but it is a considerably easier job when you’re using Ruby on Rails.