**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
Ruby on Rails Security Guide
============================
This manual describes common security problems in web applications and how to avoid them with Rails.
After reading this guide, you will know:
* All countermeasures _that are highlighted_.
* The concept of sessions in Rails, what to put in there and popular attack methods.
* How just visiting a site can be a security problem (with CSRF).
* What you have to pay attention to when working with files or providing an administration interface.
* How to manage users: Logging in and out and attack methods on all layers.
* And the most popular injection attack methods.
--------------------------------------------------------------------------------
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 that 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](#additional-resources) chapter). It is done manually because that's how you find the nasty logical security problems.
Sessions
--------
A good place to start looking at security is with sessions, which can be vulnerable to particular attacks.
### What are Sessions?
NOTE: _HTTP is a stateless protocol. Sessions make it stateful._
Most applications need to keep track of certain state of a particular user. This could be the contents of a shopping basket or the user id of the currently logged in user. Without the idea of sessions, the user would have to identify, and probably authenticate, on every request.
Rails will create a new session automatically if a new user accesses the application. It will load an existing session if the user has already used the application.
A session usually consists of a hash of values and a session ID, usually a 32-character string, to identify the hash. Every cookie sent to the client's browser includes the session ID. And the other way round: the browser will send it to the server on every request from the client. In Rails you can save and retrieve values using the session method:
```ruby
session[:user_id] = @current_user.id
User.find(session[:user_id])
```
### Session ID
NOTE: _The session ID is a 32-character random hex string._
The session ID is generated using `SecureRandom.hex` which generates a random hex string using platform specific methods (such as OpenSSL, /dev/urandom or Win32) for generating cryptographically secure random numbers. Currently it is not feasible to brute-force Rails' session IDs.
### 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 user name 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:
* Sniff the cookie in an insecure network. A wireless LAN can be an example of such a network. In an unencrypted wireless LAN, it is especially easy to listen to the traffic of all connected clients. For the web application builder this means to _provide a secure connection over SSL_. In Rails 3.1 and later, this could be accomplished by always forcing SSL connection in your application config file:
```ruby
config.force_ssl = true
```
* Most people don't clear out the cookies after working at a public terminal. So if the last user didn't log out of a web application, you would be able to use it as this user. Provide the user with a _log-out button_ in the web application, and _make it prominent_.
* Many cross-site scripting (XSS) exploits aim at obtaining the user's cookie. You'll read [more about XSS](#cross-site-scripting-xss) later.
* Instead of stealing a cookie unknown to the attacker, they fix a user's session identifier (in the cookie) known to them. Read more about this so-called session fixation later.
The main objective of most attackers is to make money. The underground prices for stolen bank login accounts range from $10-$1000 (depending on the available amount of funds), $0.40-$20 for credit card numbers, $1-$8 for online auction site accounts and $4-$30 for email passwords, according to the [Symantec Global Internet Security Threat Report](http://eval.symantec.com/mktginfo/enterprise/white_papers/b-whitepaper_internet_security_threat_report_xiii_04-2008.en-us.pdf).
### Session Guidelines
Here are some general guidelines on sessions.
* _Do not store large objects in a session_. Instead you should store them in the database and save their id in the session. This will eliminate synchronization headaches and it won't fill up your session storage space (depending on what session storage you chose, see below).
This will also be a good idea, if you modify the structure of an object and old versions of it are still in some user's cookies. With server-side session storages you can clear out the sessions, but with client-side storages, this is hard to mitigate.
* _Critical data should not be stored in session_. If the user clears their cookies or closes the browser, they will be lost. And with a client-side session storage, the user can read the data.
### Encrypted Session Storage
NOTE: _Rails provides several storage mechanisms for the session hashes. The most important is `ActionDispatch::Session::CookieStore`._
The `CookieStore` saves the session hash directly 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:
* Cookies imply a strict size limit of 4kB. This is fine as you should
not store large amounts of data in a session anyway, as described
before. Storing the current user's database id in a session is common
practice.
* Session cookies do not invalidate themselves and can be maliciously
reused. It may be a good idea to have your application invalidate old
session cookies using a stored timestamp.
The `CookieStore` uses the
[encrypted](http://api.rubyonrails.org/classes/ActionDispatch/Cookies/ChainedCookieJars.html#method-i-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](http://api.rubyonrails.org/classes/ActionDispatch/Cookies/ChainedCookieJars.html#method-i-signed)
cookies, is derived from the `secret_key_base` configuration value.
As of Rails 5.2 encrypted cookies and sessions are protected using AES
GCM encryption. This form of encryption is a type of Authenticated
Encryption and couples authentication and encryption in single step
while also producing shorter ciphertexts as compared to other
algorithms previously used. The key for cookies encrypted with AES GCM
are derived using a salt value defined by the
`config.action_dispatch.authenticated_encrypted_cookie_salt`
configuration value.
Prior to this version, encrypted cookies were secured using AES in CBC
mode with HMAC using SHA1 for authentication. The keys for this type of
encryption and for HMAC verification were derived via the salts defined
by `config.action_dispatch.encrypted_cookie_salt` and
`config.action_dispatch.encrypted_signed_cookie_salt` respectively.
Prior to Rails version 4 in both versions 2 and 3, session cookies were
protected using only HMAC verification. As such, these session cookies
only provided integrity to their content because the actual session data
was stored in plaintext encoded as base64. This is how `signed` cookies
work in the current version of Rails. These kinds of cookies are still
useful for protecting the integrity of certain client-stored data and
information.
__Do not use a trivial secret for the `secret_key_base`, i.e. a word
from a dictionary, or one which is shorter than 30 characters! Instead
use `rails secret` to generate secret keys!__
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...
If you have received an application where the secret was exposed (e.g. an application whose source was shared), strongly consider changing the secret.
### Rotating Keys for Encrypted and Signed Cookies
It is possible to rotate the `secret_key_base` as well as the salts,
ciphers, and digests used for both encrypted and signed cookies. Rotating
the `secret_key_base` is necessary if the value was exposed or leaked.
It is also useful to rotate this value for other more benign reasons,
such as an employee leaving your organization or changing hosting
environments.
For example to rotate out an old `secret_key_base`, we can define signed and
encrypted rotations as follows:
```ruby
Rails.application.config.action_dispatch.cookies_rotations.tap do |cookies|
cookies.rotate :encrypted, secret: Rails.application.credentials.old_secret_key_base
cookies.rotate :signed, secret: Rails.application.credentials.old_secret_key_base
end
```
It's also possible to set up multiple rotations. For instance to use `SHA512`
for signed cookies while rotating out SHA256 and SHA1 digests, we'd do:
```ruby
Rails.application.config.action_dispatch.signed_cookie_digest = "SHA512"
Rails.application.config.action_dispatch.cookies_rotations.tap do |cookies|
cookies.rotate :signed, digest: "SHA256"
cookies.rotate :signed, digest: "SHA1"
end
```
While you can setup 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](api.rubyonrails.org/classes/ActiveSupport/MessageEncryptor.html)
and
[MessageVerifier API](api.rubyonrails.org/classes/ActiveSupport/MessageVerifier.html)
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:
* A user receives credits, the amount is stored in a session (which is a bad idea anyway, but we'll do this for demonstration purposes).
* The user buys something.
* The new adjusted credit value is stored in the session.
* The user takes the cookie from the first step (which they previously copied) and replaces the current cookie in the browser.
* The user has their original credit back.
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](images/session_fixation.png)
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:
* The attacker creates a valid session ID: They load the login page of the web application where they want to fix the session, and take the session ID in the cookie from the response (see number 1 and 2 in the image).
* They maintain the session by accessing the web application periodically in order to keep an expiring session alive.
* The attacker forces the user's browser into using this session ID (see number 3 in the image). As you may not change a cookie of another domain (because of the same origin policy), the attacker has to run a JavaScript from the domain of the target web application. Injecting the JavaScript code into the application by XSS accomplishes this attack. Here is an example: ``. Read more about XSS and injection later on.
* The attacker lures the victim to the infected page with the JavaScript code. By viewing the page, the victim's browser will change the session ID to the trap session ID.
* As the new trap session is unused, the web application will require the user to authenticate.
* From now on, the victim and the attacker will co-use the web application with the same session: The session became valid and the victim didn't notice the attack.
### 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:
```ruby
reset_session
```
If you use the popular [Devise](https://rubygems.org/gems/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.
```ruby
class Session < ApplicationRecord
def self.sweep(time = 1.hour)
if time.is_a?(String)
time = time.split.inject { |count, unit| count.to_i.send(unit) }
end
delete_all "updated_at < '#{time.ago.to_s(:db)}'"
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:
```ruby
delete_all "updated_at < '#{time.ago.to_s(:db)}' OR
created_at < '#{2.days.ago.to_s(:db)}'"
```
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.
![](images/csrf.png)
In the [session chapter](#sessions) 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:
* Bob browses a message board and views a post from a hacker where there is a crafted HTML image element. The element references a command in Bob's project management application, rather than an image file: ``
* Bob's session at `www.webapp.com` is still alive, because he didn't log out a few minutes ago.
* By viewing the post, the browser finds an image tag. It tries to load the suspected image from `www.webapp.com`. As explained before, it will also send along the cookie with the valid session ID.
* The web application at `www.webapp.com` verifies the user information in the corresponding session hash and destroys the project with the ID 1. It then returns a result page which is an unexpected result for the browser, so it will not display the image.
* Bob doesn't notice the attack - but a few days later he finds out that project number one is gone.
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._
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:**
* The interaction is more _like a question_ (i.e., it is a safe operation such as a query, read operation, or lookup).
**Use POST if:**
* The interaction is more _like an order_, or
* The interaction _changes the state_ of the resource in a way that the user would perceive (e.g., a subscription to a service), or
* The user is _held accountable for the results_ of the interaction.
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.
```html
To the harmless survey
```
Or the attacker places the code into the onmouseover event handler of an image:
```html
```
There are many other possibilities, like using a `
```
This JavaScript code will simply display an alert box. The next examples do exactly the same, only in very uncommon places:
```html