/secure_rails

Rails security best practices

Creative Commons Zero v1.0 UniversalCC0-1.0

Secure Rails

Everyone writing code must be responsible for security. đź”’

Start with the Rails Security Guide to see how Rails protects you.

Also, check out this guide for securing sensitive data.

Best Practices

Secrets

  • Keep secret tokens out of your code - ENV variables are a good practice

    Why: You don’t want your version control host, CI provider, or any other service with access to your code to have access to your secrets. If one of these services is compromised, or a single developer’s account on one of these services is compromised, you don’t want to lose your secrets.

SQL Injection

  • Even with ActiveRecord, SQL injection is still possible if misused

    User.group(params[:column])

    is vulnerable to injection. Learn about other methods

    Why: This explains it well

Host Header Injection

  • Prevent host header injection - add the following to config/environments/production.rb

    config.action_controller.default_url_options = {host: "www.yoursite.com"}
    config.action_controller.asset_host = "www.yoursite.com"

    Why: An attacker can pass a bad host header. If your app uses caching, this bad host may be cached and served to other users (this can happen with *_url helpers).

Data in Transit

  • Protect all data in transit with HTTPS - you can get free SSL certificates from Let’s Encrypt

    Add the following to config/environments/production.rb

    config.force_ssl = true

    Why: So attackers can’t eavesdrop or modify pages

  • Add your domain to the HSTS Preload List

    config.ssl_options = {hsts: {subdomains: true, preload: true, expires: 1.year}}

    Why: If someone visits your website over HTTP, even if you have an HTTPS redirect, an attacker can perform a middleperson attack. sslstrip is a popular tool for this. The preload list ships with the browser and instructs it to always use HTTPS for specific domains.

Data at Rest

  • Protect sensitive database fields with application-level encryption - use a library like Lockbox or attr_encrypted and possibly KMS Encrypted

    Why: This protects sensitive data if the database or a database backup is compromised

  • Protect sensitive files with application-level encryption - use a library like Lockbox

    Why: This protects sensitive data if file storage is compromised, or if someone accidentally makes an S3 bucket public

  • Make sure sensitive request parameters aren’t logged

    Rails.application.config.filter_parameters += [:credit_card_number]

    Use Logstop as an additional line of defense

    Why: You don’t want sensitive data in your log files if they are compromised

Authentication

  • Use a trusted library like Devise for authentication (see Hardening Devise if applicable)

    Why: Secure authentication is hard. Use a library that’s battle-tested. Don’t roll your own.

  • Notify users of password changes

    Why: So users are aware if someone tries to hijack their account

  • Notify users of email address changes - send an email to the old address

    Why: So users can’t silently hijack the account by changing the email, then the password

  • Rate limit login attempts by IP with Rack Attack

    Why: To slow down credential stuffing attacks

  • Log all successful and failed login attempts and password reset attempts (check out Authtrail if you use Devise)

    Why: So you have an audit trail when accounts are compromised. You can also use this information to detect compromised accounts.

  • Rails has a number of gems for authorization - we like Pundit

    Why: To prevent users from accessing unauthorized data

Browser Caching

  • Set autocomplete="off" for sensitive form fields, like credit card number

    Why: So other users of the browser can’t access this saved information

  • Ask the browser not to cache pages with sensitive information

    response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate"
    response.headers["Pragma"] = "no-cache"
    response.headers["Expires"] = "Sat, 01 Jan 2000 00:00:00 GMT"

    Why: So other users of the browser can’t click the back button and view sensitive information

Data Leakage

  • Ask search engines not to index pages with secret tokens in the URL

    <meta name="robots" content="noindex, nofollow">

    Why: So search engines don’t index (and therefore expose) the tokens

Cross-Site Scripting (XSS)

  • Use json_escape when passing variables to JavaScript, or better yet, a library like Gon

    <script>
      var currentUser = <%= raw json_escape(current_user.to_json) %>;
    </script>

    Why: To prevent cross-site scripting (XSS)

  • Be careful with html_safe

    Why: It bypasses escaping

  • Don’t use assets from a public CDN, as this creates unnecessary availability and security risk

    Why: This adds another attack vector for an attacker

Open Source Tools

  • Brakeman is a great static analysis tool - it scans your code for vulnerabilities

  • bundler-audit checks for vulnerable versions of gems

    gem install bundler-audit
    bundle audit check --update

    To fix Insecure Source URI issues with the github option, add to the top of your Gemfile:

    git_source(:github) do |repo_name|
      repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
      "https://github.com/#{repo_name}.git"
    end

    And run bundle install.

  • npm audit checks for vulnerable versions of JavaScript packages (if you use package.json)

  • git-secrets prevents you from committing sensitive info

    brew install git-secrets
    git secrets --register-aws --global
    git secrets --install
    git secrets --scan

Mailing Lists

Subscribe to ruby-security-ann to get security announcements for Ruby, Rails, Rubygems, Bundler, and other Ruby ecosystem projects.

Services

  • Observatory scans your site for best practices
  • Hakiri monitors for dependency and code vulnerabilities
  • CodeClimate provides a hosted version of static analysis
  • HackerOne allows you to enlist hackers to surface vulnerabilities

Additional Reading

Contributing

Have other good practices? Know of more great tools? Help make this guide better for everyone.

Also check out Production Rails.