h1. Securing your Rails application This manual describes common security problems in web applications and how to avoid them with Rails. If you have any questions or suggestions, please mail me at ror(at)andreas-s.net. h2. Common security problems h2. SQL Injection h3. The problem SQL Injection is the #1 security problem in many web applications. How does it work? If the web application includes strings from unreliable sources (usually form parameters) in SQL statements and doesn't correctly quote any SQL meta characters like backslashes or single quotes, an attacker can change WHERE conditions in SQL statements, create records with invalid data or even execute arbitrary SQL statements. h3. How to protect your application If you only use the predefined ActiveRecord functions (attributes, save, find) without writing any conditions, limits or SQL queries yourself, ActiveRecord takes care of quoting any dangerous characters in the data for you. If you don't, you have to make sure that strings for arguments that are directly used to build SQL queries (like the condition, limit and sort arguments for find_all) do not contain any SQL meta characters. h3. Using arbitrary strings in conditions and SQL statements h3. Wrong Imagine a webmail system where a user can select a list of all the emails with a certain subject. A query could look like this:
Email.find_all "owner_id = 123 AND subject = '#{@params['subject']}'"
This is dangerous. Imagine a user sending the string "' OR 1 --'" in the parameter "subject"; the resulting statement will look like this:
Email.find_all "owner_id = 123 AND subject = '' OR 1 --''"Because of "OR 1" the condition is always true. The part "--" starts a SQL comment; everything after it will be ignored. The result: the user will get a list of all the emails in the database. (Of course the owner_id would have to be inserted dynamically in a real application; this was omitted to keep the examples as simple as possible.) h3. Correct
subject = @params['subject'] Email.find_all [ "owner_id = 123 AND subject = ?", subject ]If the argument for find_all is an array instead of a string, ActiveRecord will insert the elements 2..n of the array for the "?" placeholders in the element 1, add quotation marks if the elements are strings, and quote all characters that have a special meaning for the database adapter used by the Email model. If you don't like the syntax of the array, you can take care of the quoting yourself by calling the quote() method of the model class. You have to do this when you use find_by_sql, as the Array argument doesn't work there:
subject = params['subject']
Email.find_by_sql "SELECT * FROM email WHERE owner_id = 123 AND subject = #{Email.quote(subject)}"
The quotation marks are added automatically by Email.quote() if the argument is a string.
h3. Extracting queries into model methods
If you need to execute a query with the similar options in several places in your code, you should create a model method for that query. Instead of
emails = Email.find_all ["subject = ?", subject]you could define the following class method in the model:
class Email < ActiveRecord::Base
def self.find_with_subject(subject)
Email.find_all ["subject = ?", subject]
end
end
and call it like this:
emails = Email.find_with_subject(subject)This has the advantage that you don't have to care about meta characters when using the function find_with_subject. Generally you should always make sure that this kind of model method can not break anything, even if it is called with untrusted arguments. h2. Cross Site Scripting (CSS/XSS) h3. The problem Many web applications use session cookies to track the requests of a user. The cookie is used to identify the request and connect it to the session data (@session in Rails). Usually the session contains a reference to the user that is currently logged in, e.g. the id of a User object. Cross Site Scripting is a technique for "stealing" the cookie from another visitor of the website, and thus stealing the login. Cookies are only available from the domain where the were originally created. The easiest way to get access to the cookie is to place a specially crafted piece of JavaScript code on the website; the script can read the cookie of a visitor and send it to the attacker, e.g. by transmitting the data as an URL parameter to another website. h3. Example of an attack A site with the following Eruby template is vulnerable to XSS:
<%= @params['text'] %>The "text" parameter is directly included in the page, so it is possible to insert JavaScript code by setting the parameter to something like
After url-encoding the script (e.g. with
CGI.encode) the attacker can put it into an URL and make his victim open the URL in the browser.
If you open the URL
http://website.domain/controller/action?text=%3Cscript%3Ealert%28document.cookie%29%3C%2Fscript%3Eyou will get a popup window with your session cookie. Instead of opening the popup the script could as well send the session id to another server. h3. How to protect your application Obviously the key to a successful attack is the possibility to place JavaScript on a website that can receive the session cookie. This can be a blog with user comments, a web forum or a Wiki. How can you protect against it? Strictly convert HTML meta characters ("<" and ">") to the equivalent HTML entities ("<" and ">") in every string that is rendered in the website. This will ensure that, no matter what kind of text an attacker enters in a form or attaches to an URL, the browser will always render it as plain text and never interpret any HTML tags. This is a good idea anyway, as a user can easily mess up your layout by leaving tags open. If you want the user to be able to format his texts, it is usually better to use a markup language like Textile, Markdown or RDoc. Rails provides the helper method h() for HTML meta character conversion in Views. Example:
<%=h post.subject %> <%=h post.text %>You should accustom to using h() for any variable that is rendered in the view, even if you think you can trust it to be from a reliable source. h3. XSS attacks using an echo service The echo service is a service running on TCP port 7 that returns back everything you send to it. On Debian it is active by default. Now how can this be a security problem for a web application? If the server of the target website "target.domain" is running an echo service, the attacker could create a form like the following on his own website:
If he makes someone who has a session cookie on the target website submit this form, the content of the hidden field is sent to the echo server at port 7 - and returned. If the browser decides to display the returned data as HTML (some versions of IE do), it will execute the JavaScript code; and because the domain is "target.domain" the session cookie is available for the script.
This is rather a client-side issue, but to reduce the probability of a successful attack you should deactivate any echo services on the web server. However this does not provide full security, because there are also some other services (e.g. FTP, POP3) that can be used instead of the echo server.
h2. Typical mistakes in Rails applications
h2. Creating records directly from form parameters
h3. The problem
Let's say you want to make a user registration system. Your users table looks like this:
CREATE TABLE users ( id INTEGER PRIMARY KEY, name VARCHAR(20) NOT NULL, -- the login name password VARCHAR(20) NOT NULL, role VARCHAR(20) NOT NULL DEFAULT "User", -- e.g. "Admin", "Moderator, "User" approved INTEGER NOT NULL DEFAULT 0 -- is the registered accound approved by the administrator? ); CREATE UNIQUE INDEX users_name_unique ON users(name);The registration form:
The easiest way to create a user object from the form data in the controller is:
User.create(@params['user'])But what happens if someone decides to save the registration form to his disk and play around with adding a few fields?
He can create an account, make himself admin and approve his own account with one click. h3. The solution Active Record provides two ways of securing sensitive attributes from being overwritten by malicious users that change the form. The first is "attr_protected":http://ar.rubyonrails.org/classes/ActiveRecord/Base.html#M000116 that denies mass-assignment the right to change the named parameters. Using attr_protected, we can secure the User models like this:
class User < ActiveRecord::Base attr_protected :approved, :role endThis will ensure that on doing
User.create(@params['user']) both @params['user']['approved'] and @params['user']['role'] will be ignored. You'll have to manually set them like this:
user = User.new(@params['user']) user.approved = sanitize_properly(@params['user']['approved']) user. role = sanitize_properly(@params['user']['role'])h2. Allowing instead of protecting If you're afraid you might forget to apply attr_protected to the right attributes before making your model available to the cruel world, you can also specify the protection in reverse. You simply allow access instead of deny it, so only the attributes named in "attr_accessible":http://ar.rubyonrails.org/classes/ActiveRecord/Base.html#M000117 are available for mass-assignment. Using attr_accessible, we can secure the User models like this:
class User < ActiveRecord::Base attr_accessible :name, :password endThis description has exactly the same affect as doing
attr_protected :approved, :role, but when you add new attrbutes to the User model, they'll be protected by default.