123456789_123456789_123456789_123456789_123456789_

DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.

Securing Rails Applications

This guide describes common security problems in web applications and how to avoid them with Rails.

After reading this guide, you will know:


Introduction

Web application frameworks are made to help developers build web applications. Some of them also help you with securing the web application. In fact one framework is not more secure than another: If you use it correctly, you will be able to build secure apps with many frameworks. Ruby on Rails has some clever helper methods, for example against SQL injection, so this is hardly a problem.

In general there is no such thing as plug-n-play security. Security depends on the people using the framework, and sometimes on the development method. And it depends on all layers of a web application environment: The back-end storage, the web server, and the web application itself (and possibly other layers or applications).

The Gartner Group, however, estimates that 75% of attacks are at the web application layer, and found out "that out of 300 audited sites, 97% are vulnerable to attack". This is because web applications are relatively easy to attack, as they are simple to understand and manipulate, even by the lay person.

The threats against web applications include user account hijacking, bypass of access control, reading or modifying sensitive data, or presenting fraudulent content. Or an attacker might be able to install a Trojan horse program or unsolicited e-mail sending software, aim at financial enrichment, or cause brand name damage by modifying company resources. In order to prevent attacks, minimize their impact and remove points of attack, first of all, you have to fully understand the attack methods in order to find the correct countermeasures. That is what this guide aims at.

In order to develop secure web applications you have to keep up to date on all layers and know your enemies. To keep up to date subscribe to security mailing lists, read security blogs, and make updating and security checks a habit (check the Additional Resources chapter). It is done manually because that's how you find the nasty logical security problems.

Authentication

Authentication is often one of the first features implemented in a web application. It serves as the foundation for securing user data and is part of most modern web applications.

Starting with version 8.0, Rails comes with a default authentication generator, which provides a solid starting point for securing your application by only allowing access to verified users.

The authentication generator adds all of the relevant models, controllers, views, routes, and migrations needed for basic authentication and password reset functionality.

To use this feature in your application, you can run bin/rails generate authentication. Here are all of the files the generator modifies and new files it adds:

$ bin/rails generate authentication
      invoke  erb
      create    app/views/passwords/new.html.erb
      create    app/views/passwords/edit.html.erb
      create    app/views/sessions/new.html.erb
      create  app/models/session.rb
      create  app/models/user.rb
      create  app/models/current.rb
      create  app/controllers/sessions_controller.rb
      create  app/controllers/concerns/authentication.rb
      create  app/controllers/passwords_controller.rb
      create  app/mailers/passwords_mailer.rb
      create  app/views/passwords_mailer/reset.html.erb
      create  app/views/passwords_mailer/reset.text.erb
      create  test/mailers/previews/passwords_mailer_preview.rb
        gsub  app/controllers/application_controller.rb
       route  resources :passwords, param: :token
       route  resource :session
        gsub  Gemfile
      bundle  install --quiet
    generate  migration CreateUsers email_address:string!:uniq password_digest:string! --force
       rails  generate migration CreateUsers email_address:string!:uniq password_digest:string! --force
      invoke  active_record
      create    db/migrate/20241010215312_create_users.rb
    generate  migration CreateSessions user:references ip_address:string user_agent:string --force
       rails  generate migration CreateSessions user:references ip_address:string user_agent:string --force
      invoke  active_record
      create    db/migrate/20241010215314_create_sessions.rb

As shown above, the authentication generator modifies the Gemfile to add the bcrypt gem. The generator uses the bcrypt gem to create a hash of the password, which is then stored in the database (instead of the plain-text password). As this process is not reversible, there's no way to go from the hash back to the password. The hashing algorithm is deterministic though, so the stored password is able to be compared with the hash of the user-inputted password during authentication.

The generator adds two migration files for creating user and session tables. Next step is to run the migrations:

$ bin/rails db:migrate

Then, if you visit /session/new in your browser (you will see this route has been added in routes.rb), you'll see a form that accepts an email and a password with "sign in" button. This form routes to the SessionsController which was added by the generator. If you provide an email/password for a user that exists in the database, you will be able to successfully authenticate with those credentials and log in to the application.

NOTE: After running the Authentication generator, you do need to implement your own sign up flow and add the necessary views, routes, and controller actions. There is no code generated that creates new user records and allows users to "sign up" in the first place. This is something you'll need to wire up based on the requirements of your application.

Here is a list of modified files:

On branch main
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
  modified:   Gemfile
  modified:   Gemfile.lock
  modified:   app/controllers/application_controller.rb
  modified:   config/routes.rb

Untracked files:
  (use "git add <file>..." to include in what will be committed)
  app/controllers/concerns/authentication.rb
  app/controllers/passwords_controller.rb
  app/controllers/sessions_controller.rb
  app/mailers/passwords_mailer.rb
  app/models/current.rb
  app/models/session.rb
  app/models/user.rb
  app/views/passwords/
  app/views/passwords_mailer/
  app/views/sessions/
  db/migrate/
  db/schema.rb
  test/mailers/previews/

Reset Password

The authentication generator also adds reset password functionality. You can see a "forgot password?" link on the "sign in" page. Clicking that link navigates to the /passwords/new path and routes to the passwords controller. The new method of the PasswordsController class runs through the flow for sending a password reset email.

The link is valid for 15 minutes by default, but this can be configured with has_secure_password.

The mailers for reset password are also set up by the generator at app/mailers/password_mailer.rb and render the following email to send to the user:

# app/views/passwords_mailer/reset.html.erb
<p>
  You can reset your password within the next 15 minutes on
  <%= link_to "this password reset page", edit_password_url(@user.password_reset_token) %>.
</p>

Implementation Details

This section covers some of the implementation details around the authentication flow added by the authentication generator: The has_secure_password method, the authenticate_by method, and the Authentication concern.

has_secure_password

The has_secure_password</a> method is added to the user model and takes care of storing a hashed password using the bcrypt algorithm:

class User < ApplicationRecord
  has_secure_password
  has_many :sessions, dependent: :destroy

  normalizes :email_address, with: -> e { e.strip.downcase }
end

NOTE: has_secure_password adds the following validations automatically:

  • Password must be present on creation
  • Password length should be less than or equal to 72 bytes
  • Confirmation of password (using a XXX_confirmation attribute)

    However it doesn't validate the minimum length or the complexity of the password, you need to define validation for those yourself.

authenticate_by

The authenticate_by</a> method is used in the SessionsController while creating a new session to validate that the credentials provided by the user match the credentials stored in the database (e.g. password) for that user:

class SessionsController < ApplicationController
  def create
    if user = User.authenticate_by(params.permit(:email_address, :password))
      start_new_session_for user
      redirect_to after_authentication_url
    else
      redirect_to new_session_url, alert: "Try another email address or password."
    end
  end

  # ...
end

If the credentials are valid, a new Session is created for that user.

Session Management

The core functionality around session management is implemented in the Authentication controller concern, which is included by the ApplicationController in your application. You can explore details of the authentication concern in the source code.

One method to note in the Authentication concern is authenticated?, a helper method available in view templates. You can use this method to conditionally display links/buttons depending on whether a user is currently authenticated. For example:

<% if authenticated? %>
  <%= button_to "Sign Out", session_path, method: :delete  %>
<% else %>
  <%= link_to "Sign In", new_session_path %>
<% end %>

TIP: You can find all of the details for the Authentication generator in the Rails source code. You are encouraged to explore the implementation details and not treat authentication as a black box.

With the authentication generator configured as above, your application is ready for a more secure user authentication and password recovery process in just a few steps.

Sessions

This chapter describes some particular attacks related to sessions, and security measures to protect your session data.

What are Sessions?

INFO: Sessions enable the application to maintain user-specific state, while users interact with the application. For example, sessions allow users to authenticate once and remain signed in for future requests.

Most applications need to keep track of state for users that interact with the application. This could be the contents of a shopping basket, or the user id of the currently logged in user. This kind of user-specific state can be stored in the session.

Rails provides a session object for each user that accesses the application. If the user already has an active session, Rails uses the existing session. Otherwise a new session is created.

NOTE: Read more about sessions and how to use them in Action Controller Overview Guide.

Session Hijacking

WARNING: Stealing a user's session ID lets an attacker use the web application in the victim's name.

Many web applications have an authentication system: a user provides a username and password, the web application checks them and stores the corresponding user id in the session hash. From now on, the session is valid. On every request the application will load the user, identified by the user id in the session, without the need for new authentication. The session ID in the cookie identifies the session.

Hence, the cookie serves as temporary authentication for the web application. Anyone who seizes a cookie from someone else, may use the web application as this user - with possibly severe consequences. Here are some ways to hijack a session, and their countermeasures:

Session Storage

NOTE: Rails uses ::ActionDispatch::Session::CookieStore as the default session storage.

TIP: Learn more about other session storages in Action Controller Overview Guide.

Rails CookieStore saves the session hash in a cookie on the client-side. The server retrieves the session hash from the cookie and eliminates the need for a session ID. That will greatly increase the speed of the application, but it is a controversial storage option and you have to think about the security implications and storage limitations of it:

The CookieStore uses the encrypted cookie jar to provide a secure, encrypted location to store session data. Cookie-based sessions thus provide both integrity as well as confidentiality to their contents. The encryption key, as well as the verification key used for signed cookies, is derived from the secret_key_base configuration value.

TIP: Secrets must be long and random. Use bin/rails secret to get new unique secrets.

INFO: Learn more about managing credentials later in this guide

It is also important to use different salt values for encrypted and signed cookies. Using the same value for different salt configuration values may lead to the same derived key being used for different security features which in turn may weaken the strength of the key.

In test and development applications get a secret_key_base derived from the app name. Other environments must use a random key present in config/credentials.yml.enc, shown here in its decrypted state:

secret_key_base: 492f...

WARNING: If your application's secrets may have been exposed, strongly consider changing them. Note that changing secret_key_base will expire currently active sessions and require all users to log in again. In addition to session data: encrypted cookies, signed cookies, and Active Storage files may also be affected.

Rotating Encrypted and Signed Cookies Configurations

Rotation is ideal for changing cookie configurations and ensuring old cookies aren't immediately invalid. Your users then have a chance to visit your site, get their cookie read with an old configuration and have it rewritten with the new change. The rotation can then be removed once you're comfortable enough users have had their chance to get their cookies upgraded.

It's possible to rotate the ciphers and digests used for encrypted and signed cookies.

For instance to change the digest used for signed cookies from SHA1 to SHA256, you would first assign the new configuration value:

Rails.application.config.action_dispatch.signed_cookie_digest = "SHA256"

Now add a rotation for the old SHA1 digest so existing cookies are seamlessly upgraded to the new SHA256 digest.

Rails.application.config.action_dispatch.cookies_rotations.tap do |cookies|
  cookies.rotate :signed, digest: "SHA1"
end

Then any written signed cookies will be digested with SHA256. Old cookies that were written with SHA1 can still be read, and if accessed will be written with the new digest so they're upgraded and won't be invalid when you remove the rotation.

Once users with SHA1 digested signed cookies should no longer have a chance to have their cookies rewritten, remove the rotation.

While you can set up as many rotations as you'd like it's not common to have many rotations going at any one time.

For more details on key rotation with encrypted and signed messages as well as the various options the rotate method accepts, please refer to the MessageEncryptor API and MessageVerifier API documentation.

Replay Attacks for CookieStore Sessions

TIP: Another sort of attack you have to be aware of when using CookieStore is the replay attack.

It works like this:

Including a nonce (a random value) in the session solves replay attacks. A nonce is valid only once, and the server has to keep track of all the valid nonces. It gets even more complicated if you have several application servers. Storing nonces in a database table would defeat the entire purpose of CookieStore (avoiding accessing the database).

The best solution against it is not to store this kind of data in a session, but in the database. In this case store the credit in the database and the logged_in_user_id in the session.

Session Fixation

NOTE: Apart from stealing a user's session ID, the attacker may fix a session ID known to them. This is called session fixation.

!Session fixation

This attack focuses on fixing a user's session ID known to the attacker, and forcing the user's browser into using this ID. It is therefore not necessary for the attacker to steal the session ID afterwards. Here is how this attack works:

Session Fixation - Countermeasures

TIP: One line of code will protect you from session fixation.

The most effective countermeasure is to issue a new session identifier and declare the old one invalid after a successful login. That way, an attacker cannot use the fixed session identifier. This is a good countermeasure against session hijacking, as well. Here is how to create a new session in Rails:

reset_session

If you use the popular Devise gem for user management, it will automatically expire sessions on sign in and sign out for you. If you roll your own, remember to expire the session after your sign in action (when the session is created). This will remove values from the session, therefore you will have to transfer them to the new session.

Another countermeasure is to save user-specific properties in the session, verify them every time a request comes in, and deny access, if the information does not match. Such properties could be the remote IP address or the user agent (the web browser name), though the latter is less user-specific. When saving the IP address, you have to bear in mind that there are Internet service providers or large organizations that put their users behind proxies. These might change over the course of a session, so these users will not be able to use your application, or only in a limited way.

Session Expiry

NOTE: Sessions that never expire extend the time-frame for attacks such as cross-site request forgery (CSRF), session hijacking, and session fixation.

One possibility is to set the expiry time-stamp of the cookie with the session ID. However the client can edit cookies that are stored in the web browser so expiring sessions on the server is safer. Here is an example of how to expire sessions in a database table. Call Session.sweep(20.minutes) to expire sessions that were used longer than 20 minutes ago.

class Session < ApplicationRecord
  def self.sweep(time = 1.hour)
    where(updated_at: ...time.ago).delete_all
  end
end

The section about session fixation introduced the problem of maintained sessions. An attacker maintaining a session every five minutes can keep the session alive forever, although you are expiring sessions. A simple solution for this would be to add a created_at column to the sessions table. Now you can delete sessions that were created a long time ago. Use this line in the sweep method above:

where(updated_at: ...time.ago).or(where(created_at: ...2.days.ago)).delete_all

Cross-Site Request Forgery (CSRF)

This attack method works by including malicious code or a link in a page that accesses a web application that the user is believed to have authenticated. If the session for that web application has not timed out, an attacker may execute unauthorized commands.

!Cross-Site Request Forgery

In the session chapter you have learned that most Rails applications use cookie-based sessions. Either they store the session ID in the cookie and have a server-side session hash, or the entire session hash is on the client-side. In either case the browser will automatically send along the cookie on every request to a domain, if it can find a cookie for that domain. The controversial point is that if the request comes from a site of a different domain, it will also send the cookie. Let's start with an example:

It is important to notice that the actual crafted image or link doesn't necessarily have to be situated in the web application's domain, it can be anywhere - in a forum, blog post, or email.

CSRF appears very rarely in CVE (Common Vulnerabilities and Exposures) - less than 0.1% in 2006 - but it really is a 'sleeping giant' [Grossman]. This is in stark contrast to the results in many security contract works - CSRF is an important security issue.

CSRF Countermeasures

NOTE: _First, as is required by the W3C, use GET and POST appropriately. Secondly, a security token in non-GET requests will protect your application from CSRF._

Use GET and POST Appropriately

The HTTP protocol basically provides two main types of requests - GET and POST (DELETE, PUT, and PATCH should be used like POST). The World Wide Web Consortium (W3C) provides a checklist for choosing HTTP GET or POST:

Use GET if:

Use POST if:

If your web application is RESTful, you might be used to additional HTTP verbs, such as PATCH, PUT, or DELETE. Some legacy web browsers, however, do not support them - only GET and POST. Rails uses a hidden _method field to handle these cases.

POST requests can be sent automatically, too. In this example, the link www.harmless.com is shown as the destination in the browser's status bar. But it has actually dynamically created a new form that sends a POST request.

<a href="http://www.harmless.com/" onclick="
  var f = document.createElement('form');
  f.style.display = 'none';
  this.parentNode.appendChild(f);
  f.method = 'POST';
  f.action = 'http://www.example.com/account/destroy';
  f.submit();
  return false;">To the harmless survey</a>

Or the attacker places the code into the onmouseover event handler of an image:

<img src="http://www.harmless.com/img" width="400" height="400" onmouseover="..." />

There are many other possibilities, like using a ", which makes an attack work. That's why a permitted list approach is better, using the updated Rails 2 method sanitize():

tags = %w(a acronym b strong i em li ul ol h1 h2 h3 h4 h5 h6 blockquote br cite sub sup ins p)
s = sanitize(user_input, tags: tags, attributes: %w(href title))

This allows only the given tags and does a good job, even against all kinds of tricks and malformed tags.

Both Action View and Action Text build their sanitization helpers](https://api.rubyonrails.org/classes/ActionView/Helpers/SanitizeHelper.html) on top of the [rails-html-sanitizer gem.

As a second step, it is good practice to escape all output of the application, especially when re-displaying user input, which hasn't been input-filtered (as in the search form example earlier on). Use html_escape() (or its alias h()) method to replace the HTML input characters &, ", <, and > by their uninterpreted representations in HTML (&amp;, ", <, and &gt;).

Obfuscation and Encoding Injection

Network traffic is mostly based on the limited Western alphabet, so new character encodings, such as Unicode, emerged, to transmit characters in other languages. But, this is also a threat to web applications, as malicious code can be hidden in different encodings that the web browser might be able to process, but the web application might not. Here is an attack vector in UTF-8 encoding:

<img src=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;
  &#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;>

This example pops up a message box. It will be recognized by the above sanitize() filter, though. A great tool to obfuscate and encode strings, and thus "get to know your enemy", is the Hackvertor. Rails' sanitize() method does a good job to fend off encoding attacks.

Examples from the Underground

In order to understand today's attacks on web applications, it's best to take a look at some real-world attack vectors.

The following is an excerpt from the Js.Yamanner@m Yahoo! Mail worm. It appeared on June 11, 2006 and was the first webmail interface worm:

<img src='http://us.i1.yimg.com/us.yimg.com/i/us/nt/ma/ma_mail_1.gif'
  target=""onload="var http_request = false;    var Email = '';
  var IDList = '';   var CRumb = '';   function makeRequest(url, Func, Method,Param) { ...

The worms exploit a hole in Yahoo's HTML/JavaScript filter, which usually filters all targets and onload attributes from tags (because there can be JavaScript). The filter is applied only once, however, so the onload attribute with the worm code stays in place. This is a good example why restricted list filters are never complete and why it is hard to allow HTML/JavaScript in a web application.

Another proof-of-concept webmail worm is Nduja, a cross-domain worm for four Italian webmail services. Find more details on Rosario Valotta's paper. Both webmail worms have the goal to harvest email addresses, something a criminal hacker could make money with.

In December 2006, 34,000 actual usernames and passwords were stolen in a MySpace phishing attack. The idea of the attack was to create a profile page named "login_home_index_html", so the URL looked very convincing. Specially-crafted HTML and CSS were used to hide the genuine MySpace content from the page and instead display its own login form.

CSS Injection

INFO: CSS Injection is actually JavaScript injection, because some browsers (IE, some versions of Safari, and others) allow JavaScript in CSS. Think twice about allowing custom CSS in your web application.

CSS Injection is explained best by the well-known MySpace Samy worm. This worm automatically sent a friend request to Samy (the attacker) simply by visiting his profile. Within several hours he had over 1 million friend requests, which created so much traffic that MySpace went offline. The following is a technical explanation of that worm.

MySpace blocked many tags, but allowed CSS. So the worm's author put JavaScript into CSS like this:

<div style="background:url('javascript:alert(1)')">

So the payload is in the style attribute. But there are no quotes allowed in the payload, because single and double quotes have already been used. But JavaScript has a handy eval() function which executes any string as code.

<div id="mycode" expr="alert('hah!')" style="background:url('javascript:eval(document.all.mycode.expr)')">

The eval() function is a nightmare for restricted list input filters, as it allows the style attribute to hide the word "innerHTML":

alert(eval('document.body.inne' + 'rHTML'));

The next problem was MySpace filtering the word "javascript", so the author used "javascript" to get around this:

<div id="mycode" expr="alert('hah!')" style="background:url('java↵script:eval(document.all.mycode.expr)')">

Another problem for the worm's author was the CSRF security tokens. Without them he couldn't send a friend request over POST. He got around it by sending a GET to the page right before adding a user and parsing the result for the CSRF token.

In the end, he got a 4 KB worm, which he injected into his profile page.

The moz-binding CSS property proved to be another way to introduce JavaScript in CSS in Gecko-based browsers (Firefox, for example).

Countermeasures

This example, again, showed that a restricted list filter is never complete. However, as custom CSS in web applications is a quite rare feature, it may be hard to find a good permitted CSS filter. If you want to allow custom colors or images, you can allow the user to choose them and build the CSS in the web application. Use Rails' sanitize() method as a model for a permitted CSS filter, if you really need one.

Textile Injection

If you want to provide text formatting other than HTML (due to security), use a mark-up language which is converted to HTML on the server-side. RedCloth is such a language for Ruby, but without precautions, it is also vulnerable to XSS.

For example, RedCloth translates {_test_} to test, which makes the text italic. However, RedCloth doesn’t filter unsafe html tags by default:

RedCloth.new("<script>alert(1)</script>").to_html
# => "<script>alert(1)</script>"

Use the :filter_html option to remove HTML which was not created by the Textile processor.

RedCloth.new("<script>alert(1)</script>", [:filter_html]).to_html
# => "alert(1)"

However, this does not filter all HTML, a few tags will be left (by design), for example <a>:

RedCloth.new("<a href='javascript:alert(1)'>hello</a>", [:filter_html]).to_html
# => "<p><a href="javascript:alert(1)">hello</a></p>"

Countermeasures

It is recommended to use RedCloth in combination with a permitted input filter, as described in the countermeasures against XSS section.

Ajax Injection

NOTE: The same security precautions have to be taken for Ajax actions as for "normal" ones. There is at least one exception, however: The output has to be escaped in the controller already, if the action doesn't render a view.

If you use the in_place_editor plugin, or actions that return a string, rather than rendering a view, you have to escape the return value in the action. Otherwise, if the return value contains a XSS string, the malicious code will be executed upon return to the browser. Escape any input value using the h() method.

Command Line Injection

NOTE: Use user-supplied command line parameters with caution.

If your application has to execute commands in the underlying operating system, there are several methods in Ruby: system(command), exec(command), spawn(command) and command. You will have to be especially careful with these functions if the user may enter the whole command, or a part of it. This is because in most shells, you can execute another command at the end of the first one, concatenating them with a semicolon (;) or a vertical bar (|).

user_input = "hello; rm *"
system("/bin/echo #{user_input}")
# prints "hello", and deletes files in the current directory

A countermeasure is to use the system(command, parameters) method which passes command line parameters safely.

system("/bin/echo", "hello; rm *")
# prints "hello; rm *" and does not delete files

{Kernel#open's} Vulnerability

{Kernel#open} executes OS command if the argument starts with a vertical bar (|).

open("| ls") { |file| file.read }
# returns file list as a String via `ls` command

Countermeasures are to use {File.open}, {IO.open} or {URI#open} instead. They don't execute an OS command.

File.open("| ls") { |file| file.read }
# doesn't execute `ls` command, just opens `| ls` file if it exists

IO.open(0) { |file| file.read }
# opens stdin. doesn't accept a String as the argument

require "open-uri"
URI("https://example.com").open { |file| file.read }
# opens the URI. `URI()` doesn't accept `| ls`

Header Injection

WARNING: HTTP headers are dynamically generated and under certain circumstances user input may be injected. This can lead to false redirection, XSS, or HTTP response splitting.

HTTP request headers have a Referer, User-Agent (client software), and Cookie field, among others. Response headers for example have a status code, Cookie, and Location (redirection target URL) field. All of them are user-supplied and may be manipulated with more or less effort. Remember to escape these header fields, too. For example when you display the user agent in an administration area.

Besides that, it is important to know what you are doing when building response headers partly based on user input. For example you want to redirect the user back to a specific page. To do that you introduced a "referer" field in a form to redirect to the given address:

redirect_to params[:referer]

What happens is that Rails puts the string into the {Location} header field and sends a 302 (redirect) status to the browser. The first thing a malicious user would do, is this:

http://www.yourapplication.com/controller/action?referer=http://www.malicious.tld

And due to a bug in (Ruby and) Rails up to version 2.1.2 (excluding it), a hacker may inject arbitrary header fields; for example like this:

http://www.yourapplication.com/controller/action?referer=http://www.malicious.tld%0d%0aX-Header:+Hi!
http://www.yourapplication.com/controller/action?referer=path/at/your/app%0d%0aLocation:+http://www.malicious.tld

Note that %0d%0a is URL-encoded for \r\n which is a carriage-return and line-feed (CRLF) in Ruby. So the resulting HTTP header for the second example will be the following because the second Location header field overwrites the first.

HTTP/1.1 302 Moved Temporarily
(...)
Location: http://www.malicious.tld

So attack vectors for Header Injection are based on the injection of CRLF characters in a header field. And what could an attacker do with a false redirection? They could redirect to a phishing site that looks the same as yours, but ask to login again (and sends the login credentials to the attacker). Or they could install malicious software through browser security holes on that site. Rails 2.1.2 escapes these characters for the Location field in the {redirect_to} method. Make sure you do it yourself when you build other header fields with user input.

DNS Rebinding and Host Header Attacks

DNS rebinding is a method of manipulating resolution of domain names that is commonly used as a form of computer attack. DNS rebinding circumvents the same-origin policy by abusing the Domain Name System (DNS) instead. It rebinds a domain to a different IP address and then compromises the system by executing random code against your Rails app from the changed IP address.

It is recommended to use the {ActionDispatch::HostAuthorization} middleware to guard against DNS rebinding and other Host header attacks. It is enabled by default in the development environment, you have to activate it in production and other environments by setting the list of allowed hosts. You can also configure exceptions and set your own response app.

Rails.application.config.hosts << "product.com"

Rails.application.config.host_authorization = {
  # Exclude requests for the /healthcheck/ path from host checking
  exclude: ->(request) { request.path.include?("healthcheck") },
  # Add custom Rack application for the response
  response_app: -> env do
    [400, { "Content-Type" => "text/plain" }, ["Bad Request"]]
  end
}

You can read more about it in the ActionDispatch::HostAuthorization middleware documentation

Response Splitting

If Header Injection was possible, Response Splitting might be, too. In HTTP, the header block is followed by two CRLFs and the actual data (usually HTML). The idea of Response Splitting is to inject two CRLFs into a header field, followed by another response with malicious HTML. The response will be:

HTTP/1.1 302 Found [First standard 302 response]
Date: Tue, 12 Apr 2005 22:09:07 GMT
Location:Content-Type: text/html


HTTP/1.1 200 OK [Second New response created by attacker begins]
Content-Type: text/html


&lt;html&gt;&lt;font color=red&gt;hey&lt;/font&gt;&lt;/html&gt; [Arbitrary malicious input is
Keep-Alive: timeout=15, max=100         shown as the redirected page]
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: text/html

Under certain circumstances this would present the malicious HTML to the victim. However, this only seems to work with Keep-Alive connections (and many browsers are using one-time connections). But you can't rely on this. In any case this is a serious bug, and you should update your Rails to version 2.0.5 or 2.1.2 to eliminate Header Injection (and thus response splitting) risks.

Unsafe Query Generation

Due to the way Active Record interprets parameters in combination with the way that Rack parses query parameters it was possible to issue unexpected database queries with IS NULL where clauses. As a response to that security issue (CVE-2012-2660, CVE-2012-2694 and CVE-2013-0155) {deep_munge} method was introduced as a solution to keep Rails secure by default.

Example of vulnerable code that could be used by attacker, if {deep_munge} wasn't performed is:

unless params[:token].nil?
  user = User.find_by_token(params[:token])
  user.reset_password!
end

When params is one of: [nil], [nil, nil, ...] or ['foo', nil] it will bypass the test for {nil}, but IS NULL or IN ('foo', NULL) where clauses still will be added to the SQL query.

To keep Rails secure by default, {deep_munge} replaces some of the values with {nil}. Below table shows what the parameters look like based on {JSON} sent in request:

JSON Parameters
{ "person": null } { :person => nil }
{ "person": [] } { :person => [] }
{ "person": [null] } { :person => [] }
{ "person": [null, null, ...] } { :person => [] }
{ "person": ["foo", null] } { :person => ["foo"] }

It is possible to return to old behavior and disable {deep_munge} configuring your application if you are aware of the risk and know how to handle it:

config.action_dispatch.perform_deep_munge = false

HTTP Security Headers

To improve the security of your application, Rails can be configured to return HTTP security headers. Some headers are configured by default; others need to be explicitly configured.

Default Security Headers

By default Rails is configured to return the following response headers. Your application returns these headers for every HTTP response.

X-Frame-Options

The [X-Frame-Options][] header indicates if a browser can render the page in a ,