Integrating Security Into Your Rails Development

Security is becoming more important than ever and the expectation for developers to produce secure code continues to rise. As a security consultant that tends to focus on Rails assessments, I get to work with a good number of different Rails development teams. Often, these teams look for ways to integrate security into their development lifecycle and ask for ways they can be better at producing secure code. This post is meant to try and address some of those concerns.

Learning about Rails security

Read the Rails Security Guide

If you're a Rails developer and haven't read the Rails security guide, I would highly recommend doing this right now. The guide is a great introduction to common web application vulnerabilities. It also puts the explanations for those vulnerabilities in the context of Rails, giving examples of how to protect yourself in code and/or how Rails tries to protect you by default.

Sign up for security advisory notifications

The Ruby on Rails team publishes every Rails related security advisory to the rubyonrails-security Google group. This Google group is used only for posting advisories, so setting up e-mail notifications on every new post means you only get an e-mail when a security issue is announced.

Read Hacker One disclosures

The Rails project recently switched to using HackerOne for tracking vulnerability disclosures. HackerOne is a Vulnerability Management & Bug Bounty Platform. Security researchers and/or developers submit security issues they find through this system. The nice thing about this is that when an advisory is released, the Rails team makes the disclosure public if both the team and the submitter agree to it. These reports usually have detailed explanations of the vulnerability that may include steps to reproduce and proof of concept scripts. It's a good way to learn by example. Oh and if you ever find a bug that's particularly nasty, you can get paid up to $1,500.

More resources

Web Application Hacker's Handbook

If you're looking for a more in-depth look into web application security, check out Stuttard & Pinto's Web Application Hacker's Handbook. This is the de facto book about web application security.

Catching security bugs in the code you write

Integrating security into your Rails development is made a good deal easier by the great static analysis tool called Brakeman. Brakeman won't catch every security issue in your application but it will catch A LOT.

What is Brakeman?

From the Brakeman Scanner site:

Brakeman is an open source vulnerability scanner specifically designed for Ruby on Rails applications. It statically analyzes Rails application code to find security issues at any stage of development.

Brakeman is usually run as a standalone command line tool, but there are some alternative ways you can run Brakeman that might align a bit better with development processes. This includes integrations for Guard and continuous integration tools like Jenkins/Hudson.

When developing locally on my machine, I like using Guard for keeping tests running as I make changes to files. Brakeman integrates into Guard via a dedicated Guard plugin. With this, you'll have Brakeman scans run every time you make code changes to relevant files. If you aren't already using Guard for automatic running of your tests on code changes, you probably should. It's kind of neat.

Adding Guard + Brakeman to your project

  1. Add the guard and guard-brakeman gems to the development block of your Gemfile. This will ensure that guard and guard-brakeman are only used in the context of the development environment:

    group :development do ... gem 'guard' gem 'guard-brakeman' end

  2. Run bundle install to install the new gems.

  3. The following command will Initialize your Guardfile, placing it in your application's root:

    bundle exec guard init

  4. If you added guard-brakeman into your Gemfile, the guard init
    command should have added the necessary configuration into your
    Guardfile to automatically run Brakeman on file changes. Your Guardfile
    should now look like this:

    # A sample Guardfile
    # More info at https://github.com/guard/guard#readme

    ## Uncomment and set this to only include directories you want to watch
    # directories %w(app lib config test spec features) \
    #  .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}

    ## Note: if you are using the `directories` clause above and you are not
    ## watching the project directory ('.'), then you will want to move
    ## the Guardfile to a watched dir and symlink it back, e.g.
    #
    #  $ mkdir config
    #  $ mv Guardfile config/
    #  $ ln -s config/Guardfile .
    #
    # and, you'll have to watch "config/Guardfile" instead of "Guardfile"

    guard 'brakeman', :run_on_start => true do
      watch(%r{^app/.+\.(erb|haml|rhtml|rb)$})
      watch(%r{^config/.+\.rb$})
      watch(%r{^lib/.+\.rb$})
      watch('Gemfile')
    end
  1. You can now run bundle exec guard and see that Brakeman is now being
    run in your Guard REPL.

Did you have some security issues show up? Time to start fixing! If you're unfamiliar with the security issues that brakeman flags, you can generate an HTML report that includes links to descriptions of vulnerabilities. Navigate to the root of your Rails directory and run:

brakeman . -o report.html

This will output an HTML file with all of the results of the Brakeman scan. Open this file in your browser and click on the link in the Warning Type column. This will direct you to a page that looks like http://brakemanscanner.org/docs/warningtypes/sqlinjection/.

Continuous Integration

Brakeman has the ability to create a rake task that can be used with all Continuous Integration platforms that utilize the rake command and/or let you run commands. Generate the default rake task by running

brakeman --rake  

This will produce the following output:

➜  rails_app git:(master) brakeman --rake
Task created in lib/tasks/brakeman.rake  
Usage: rake brakeman:run[output_file]  

This will create a check task within lib/tasks/brakeman.rake that looks like this:

  desc "Check your code with Brakeman"
  task :check do
    require 'brakeman'
    result = Brakeman.run app_path: '.', print_report: true
    exit Brakeman::Warnings_Found_Exit_Code unless result.filtered_warnings.empty?
  end

The key here is that the check will execute with an exit code which should cause a build to break for any reasonable CI system.

Here's a sample of a simple .travis.yml file I use for one of my Rails applications:

language: ruby  
rvm:  
 - "2.2.2"

bundler_args: --without production

script:  
  - bundle exec rake brakeman:check

Handling false positives

Although Brakeman tries it's best to limit false-positives, it would rather throw a false positive than miss a vulnerability. If you find a false positive you should file an issue and report it. It might take some time for it to get pushed out into a release so in the meantime I modify Brakeman's check rake task. Here's some code I used to reject specific warnings that would cause the check task to exit with an error code:

  desc "Check your code with Brakeman"
  task :check do
    require 'brakeman'
    result = Brakeman.run app_path: '.', print_report: true, url_safe_methods: ["find"]

    # TODO: There are some false positives that are going to take a bit of time
    # to get pushed to master so I'm manually filtering them here. This should
    # be removed once the fixes are released. See the following PR/issues for
    # more info:
    #
    # https://github.com/presidentbeef/brakeman/pull/45#issuecomment-148863250
    # https://github.com/presidentbeef/brakeman/issues/744
    filtered_warnings = result.filtered_warnings.reject{|warning| warning.format_message =~ /current_user.bars\.find\(/}

    exit Brakeman::Warnings_Found_Exit_Code unless filtered_warnings.empty?
  end

Catching security bugs in the code other people write

Well if you're catching bugs in the code you wrote, surely other people have written security bugs into the code they wrote. Luckily there's a tool for checking known security vulnerabilities in the gems you pull into your application.

Introducing bundler-audit

When you run bundle install, bundler goes through your Gemfile and installs the gems and their dependencies for your application. The actual versions of all of the gems that were installed go into the Gemfile.lock file. At some point there's probably going to be/already is a security vulnerability in one of the gems you use. Check out the ruby-advisory-db for a nice list. bundler-audit is a tool that scans the versions inside of your Gemfile.lock file and flags gem versions with known security vulnerabilities. Using it is as simple as installing via:

gem install bundler-audit  

and navigating to the root of your Rails application and running:

bundle-audit  

Since you don't really add gems to your application as often as you change code, it doesn't make much sense to integrate this into your Guard workflow. It does make sense to build it into a similar Rake task as we did for Brakeman though.

bundler-audit + Rake

I found the following discussion in regards to a rake task for bundler-audit. I ended up using thoughtbot's rake task. This adds a task that you can run using the command rake bundler:audit. To utilize the bundler audit client, you'll need to add the bundler-audit gem into your Gemfile:

group :development, :test do  
  ...
  gem 'bundler-audit'
end  

Copy the following into lib/tasks/bundler_audit.rake

if Rails.env.development? || Rails.env.test?  
  require "bundler/audit/cli"

  namespace :bundler do
    desc "Updates the ruby-advisory-db and runs audit"
    task :audit do
      %w(update check).each do |command|
        Bundler::Audit::CLI.start [command]
      end
    end
  end
end  

If you've been following along with the Brakeman rake task, the .travis.yml file should now look something like:

language: ruby  
rvm:  
 - "2.2.2"

bundler_args: --without production

script:  
  - bundle exec rake brakeman:check
  - bundle exec rake bundler:audit
  - bundle exec rake

With that, you should now have Brakeman running locally with every code change via Guard and you also have continuous integration support for both Brakeman and bundler-audit!

Hope that helps,
Tomek