---
title: Building secure web applications with Ruby on Rails
teaser: 'Ruby on Rails makes it easy to build web apps with security in mind.

  '
tags: rails,ruby,security,new bamboo,web
author: Murtaza Gulamali
published_on: 2015-03-02
---

_This post was originally published on the New Bamboo blog, before [New Bamboo
joined thoughtbot in London][new-bamboo-thoughtbot]._

---

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][official_guide] and [unofficially][unofficial_guide].  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][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][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,

```erb
<%= "<script>document.alert('Mwahahaha!')</script>" %>
```

becomes,

```html
<script>document.alert('Mwahahaha!')</script>
```

Furthermore, the [`sanitize`][sanitize] helper can be used to strip unwanted
HTML tags in your data,

```ruby
sanitize("<script>document.alert('Mwahahaha!')</script>")
=> ""
```

You can also specify which tags you want to allow through,

```ruby
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`][gsub] method can be used to remove `<script>` tags from a string but it
won't completely sanitise the input,

```ruby
"<sc<script>ript>".gsub("<script>", "")
=> "<script>"
```

but `sanitize` will,

```ruby
sanitize("<sc<script>ript>")
=> "ript>"
```

An extreme example of code injection is highlighted in the XKCD cartoon,
[_Exploits of a mom_][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][xkcd_explanation].  Fortunately, the
[`find`][find] and [`find_by`][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`][where] and
[`find_by_sql`][find_by_sql], and manually sanitize them.

Another way to attack a web app is through the [session cookie][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`][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][session_hijacking] and can be mitigated by forcing the user to access
the app via HTTPS instead of HTTP by setting,

```ruby
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][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`][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,

```ruby
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,

```ruby
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][rails_security] is a great
place to start.  Also, the [Open Web Application Security Project][owasp]
(OWASP) have created a [cheatsheet for Rails][rails_cheatsheet], which is
updated regularly, and the [Ruby on Rails security mailing
list][rails_security_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][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][secure_headers] gem can automatically apply several security
related headers to your app requests and responses.  Also, [Cloud
Flare][cloud_flare] provides an easy way to protect your websites from online
threats such as SQL injection or [denial of service attacks][dos_attack].

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][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][web_security] but it is a considerably
easier job when you're using Ruby on Rails.

[brakeman]: http://brakemanscanner.org/
[challenges]: http://cryptopals.com/
[cloud_flare]: https://www.cloudflare.com/
[code_injection]: https://www.owasp.org/index.php/Code_Injection
[dos_attack]: http://en.wikipedia.org/wiki/Denial-of-service_attack
[exploits_of_a_mom]: http://xkcd.com/327/
[find_by_sql]: http://api.rubyonrails.org/classes/ActiveRecord/Querying.html#method-i-find_by_sql
[find_by]: http://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-find_by
[find]: http://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-find
[gsub]: http://ruby-doc.org//core-2.2.0/String.html#method-i-gsub
[najaf_ali]: http://www.happybearsoftware.com/
[new-bamboo-thoughtbot]: https://thoughtbot.com/blog/new-bamboo-joins-thoughtbot-in-london
[official_guide]: http://guides.rubyonrails.org/
[owasp]: https://www.owasp.org/
[rails_cheatsheet]: https://www.owasp.org/index.php/Ruby_on_Rails_Cheatsheet
[rails_security_list]: https://groups.google.com/forum/#!forum/rubyonrails-security
[rails_security]: http://guides.rubyonrails.org/security.html
[reset_session]: http://api.rubyonrails.org/classes/ActionDispatch/Request.html#method-i-reset_session
[sanitize]: http://api.rubyonrails.org/classes/ActionView/Helpers/SanitizeHelper.html#method-i-sanitize
[secrets_yml]: http://guides.rubyonrails.org/4_1_release_notes.html#config-secrets-yml
[secure_headers]: https://github.com/twitter/secureheaders
[session_cookie]: http://en.wikipedia.org/wiki/HTTP_cookie#Session_cookie
[session_hijacking]: https://www.owasp.org/index.php/Session_hijacking_attack
[sniff]: http://en.wikipedia.org/wiki/Packet_analyzer
[unofficial_guide]: http://stackoverflow.com/questions/tagged/ruby-on-rails
[web_security]: https://thoughtbot.com/blog/whos_responsible_for_web_app_security
[where]: http://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-where
[xkcd_explanation]: http://www.explainxkcd.com/wiki/index.php/327:_Exploits_of_a_Mom
