Ruby on Rails Security Cheatsheet

I’m back from the Rails Conference Europe in Berlin. I realize that a 45 minute talk is hardly enough to tell everything which is important to say about Rails security. Moreover, you will never get the level right in a talk about security (or generally): There are Rails newbies, everyday-programmers and even security experts.

Anyway, I was referring to this web site quite often as I wanted to provide further reading about more advanced security problems in Rails and their countermeasures. Therefore, I created a Rails security cheatsheet with all the information in one place.

Go to the Ruby on Rails Security cheatsheet.

PS: My slides will be available here.
PS 2: You can find the links to the plugins I mentioned in the blogroll on the => :right.

OpenID security issues

As many Rails projects use the OpenID service to authenticate its users, I want to bring some of its security issues to your attention which were announced recently. Gareth Heyes found a cross-site request forgery attack vector with MyOpenID, one of the bigger OpenID providers. MyOpenID reacted promptly, but other providers have the same problem. Here's what he writes:

When developing a OpenID system is very important to include form tokens in order to prevent CSRF attacks. The MyOpenID.com site had included form tokens but the token was stored in the URL of the site.

A more general article about OpenID security issues can be found on GNUCITIZEN.

RedCloth security thoughts

Often times RedCloth is used to prevent Cross Site Scripting, because it uses a markup other than HTML to format text. “RedCloth is a module for using Textile in Ruby. Textile is a text format. A very simple text format. Another stab at making readable text that can be converted to HTML.” For example *a phrase* becomes a phrase in RedCloth. However, there are a few things you have to know:

Note: the original attributes href and src were replaced by the blog software with xhref and xsrc in the following.

Using RedCloth without any options is still vulnerable to XSS:

>> RedCloth.new(‘<script>alert(1)</script>’).to_html
=> “<script>alert(1)</script>”

Use the :filter_html option to remove or escape HTML which wasn’t 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, for example <a>:

>> RedCloth.new(“<a xhref=’javascript:alert(1)’>hello</a>”, [:filter_html]).to_html=> “<p><a _href=\”javascript:alert(1)\”>hello</a></p>”

According to the source code, the image tag (<img xsrc=””>) is allowed, as well, which should allow attacks like these:

<IMG xsrc=javascript:alert(‘XSS’)> 

However, my setup (version 3.0.4) gives me errors when passing image tags. I recommend to use a combination of RedCloth and the good old white_list filter, as in:

>> RedCloth.new(‘”ha”:javascript:alert(1);’).to_html
=> “<p><a xhref=\”javascript:alert(1);\”>ha</a></p>”

>> white_list(RedCloth.new(‘”ha”:javascript:alert(1);’).to_html)
=> “<p><a>ha</a></p>”

As you can see, the Textile syntax allows you to create JavaScript links, as well. And if you think a JavaScript link looks suspicious to the users you can hide links in the data: protocol, as in Thou art so tolerant.

Another attack vector could be built with the Textile ability to include CSS. As described earlier, an attacker may deface your site and display his own elements (links, buttons, maybe login forms) on top of your original ones and lure the victim on one of his pages:

>> RedCloth.new(‘p{position:absolute; top:50px; left:10px; width:150px; height:150px}. Spacey blue’).to_html
=> “<p style=\”position:absolute; top:50px; left:10px; width:150px; height:150px;\”>Spacey blue</p>”

>> RedCloth.new(‘p{position:absolute; top:50px; left:10px; width:150px; height:150px}. No spacey blue’, [:filter_styles]).to_html
=> “<p>No spacey blue</p>”

So a good combination of RedCloth and the WhiteListHelper should be a secure solution.

Don’t use strip_tags, strip_links and sanitize

Update: This is about earlier releases, Rails 2.0 provides a new sanitize method which uses a white list. Also, strip_tags and strip_links have been updated, the attack vectors below do not work anymore.
 
Rails includes several insecure text helpers, especially strip_tags, strip_links and sanitize. Do not rely on the these as they do not fulfill what the name promises. Here are two examples:
 
Note: the original attributes href and src were replaced by the blog software with xhref and xsrc in the following.

>> strip_tags("sdfasdf<<b>script>alert('hello')<</b>/script>")
=> "sdfasdf<script>alert('hello')</script>"

>> strip_links("<a xhref='http://www.holy-angel.com/'><a xhref='http://www.attacker.com/'>Test</a></a>")
=> "<a xhref='http://www.attacker.com/'>Test</a>"

I've posted a bug ticket at http://dev.rubyonrails.org/ticket/8864 which was followed by http://dev.rubyonrails.org/ticket/8877, but it won't be fixed until Rails 2.0, so I recommend to use Rick's white_list plugin to remove all but some safe tags.

Thou art so tolerant

Web browsers are quite helpful: If you are a web-designer and you don't produce (X)HTML compliant pages, they will be rendered correctly anyway, because they quite fault-tolerant. But there are some features in some browsers that are questionable, here are some advanced examples:

  • Even though this file has a strange extension, IE will interpret the JavaScript inside: http://kleinerfeigling.kl.ohost.de/hubi.istdick
  • IE guesses the protocol you wanted to use, so try to enter this link: somescript:alert("hi");
    See here for more examples of that. It seems that a simple link to this doesn't work, but with a few tricks you can execute it:
    <a ONclicK=` ;; morescript:alert(String.fromCharCode(88,83,83))` xhref='#'>TEST</a>
  • The last example actually contains another trick to hide malicious code: the onclick event is used with backticks `
  • Do you know the data protocol of Firefox, you can hide JavaScript in it, you can even base64 encode it, try this link: data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K

Older tricks can be found in the XSS Cheat Sheet

sanitize() and blacklists

This is Rails' sanitize method:

sanitize(html)
Sanitizes the html by converting <form> and <script> tags into regular text, and removing all "onxxx" attributes (so that arbitrary Javascript cannot be executed). It also removes xhref= and xsrc= attributes that start with "javascript:".

This is a blacklist method which removes potential harmful JavaScript. As I said before, blacklist filter are never complete and filter only the most basic cross-site scripting attacks, there will always be special code which works fine in some browsers, even though you have used a filter. Examples are here, here and here (list != infinite). Here are some other examples that pass through sanitize and execute in IE (mostly v6) or Firefox:

  • <IMG _src="javascript:alert(String.fromCharCode(88,83,83));">
  • <DIV STYLE="background-image: url(javascript:alert(1))">
  • <div style="width: expression(alert(1))">hello</div>
  • <INPUT TYPE="IMAGE" _src="javascript:alert(1);">

I will not post these examples as a ticket, because I think fixing a blacklist is rather useless. As DHH said in one of the tickets, sanitize in that form is kind of deprecated, a whitelist filter is definitely better. I recommend not to use sanitize until it is being converted to a whitelist filter.

This is RSnake's famous XSS Cheat Sheet.

Everything You Always Wanted to Know About Web Application Security – But Were Afraid to Ask

Here is an interesting general document about web application security, a list with frequently asked questions: http://www.owasp.org/index.php/OWASP_AppSec_FAQ

It gives good advice on how to design the login process. It describes a best practice for the "Lost Password" feature, which is especially important these days where there are an increasing number of attacks based on this feature. One thing I have to add in this context: You should not return a meaningful error message if the user name for the lost password existed. An attacker can use this to find valid user names, and did you know that a password cracker can check 30,000 passwords a minute over the Internet?

BTW, I'm glad that LoginSugar, the popular login system generator, has seen a security update after I published several security issues here. If you use it, please download the new version which is now also compatible with Rails 1.2.

Tour Dates

The Ruby On Rails Security Project is now also touring in real life, spreading the word of secure programming. I am doing a conference session for the two main upcoming Ruby On Rails events in Europe. The first one is in German on June, 22nd, the second in English on September, 19th:

I see you there!

Ajax Security

Several sources, for example this, state that Ajax applications are more complex due to their asynchronous nature, or that Ajax might cause more entry points for attackers, whileother sources claim the opposite. However, the classes of attacks stay largely the same,so the advices given herein apply to Ajax applications, as well, especially input and output validation.
 
But there is one exception, output validation, as described in the User Agent Injection, cannot be done solely in Rails' view anymore. In a situation where the attacker sends malicious input through an Ajax function and the server does not filter it and returnsa string and not a Rails view, the input will be displayed without validation. For example, Rails provides a function called in_place_editor() which makes string elements on a website editable and sends the new string to the server to save it and return the string again. If this string contains an injection, it will be injected in the result.
 
 
The solution is to, at first, determine which data format the Ajax result will be returned in. In Rails applications it is quite common to return plain text or HTML code, but itcould be other formats, such as XML or JSON (JavaScript Object Notation, a lightweight data-interchange format).
 
In addition to input validation, the user input has to be filtereda ccording to that data format, as well. And it is important to keep in mind that an attacker can bypass client-side validation, use it for performance reasons only, but not as a replacement for server-side validation.
 
Secondly, you have to move the output validation for Ajax actions that do not render a view from Rails' view to the controller. The h() function works in a Rails controller, as well, and the input validation framework described before (see https://rorsecurity.info/files/transforms.rb) has a data type "htmlescape" which performs output validation, for example:
 
name = parseparam( params[:name], "empty", "htmlescape")

However, before you perform any action for an Ajax call, you should check for a valid session. Ajax requests in Rails also contain a session identifier. And you should checkwhether the logged in user has appropriate privileges to perform that action. Moreover, you can make sure that the request really is an Ajax request by using the verify method at the top of your controller.

It is typical for Ajax applications to store parts of the state on the client side (the name of the current project, for example), and sometimes parts of the application logic resides inJavaScript code on the client side, as well. As with all input, you should always distrust the state that comes from the client. You should minimize the amount of application logic on the client.

Use good passwords

You might have heard of the MySpace phishing attack at the end of last year. Bruce Schneier has analyzed 34,000 real-world user names and passwords and it turns out, as expected, that most of the passwords people use are quite easy to crack. The most common passwords are:

Common Passwords: The top 20 passwords are (in order): password1, abc123, myspace1, password, blink182, qwerty1, ****you, 123abc, baseball1, football1, 123456, soccer, monkey1, liverpool1, princess1, jordan23, slipknot1, superman1, iloveyou1 and monkey.

But also:

I'm impressed that less than 4 percent were dictionary words and that the great majority were at least alphanumeric.

 

A good password would be a long alphanumeric combination of mixed cases. As this is quite hard to remember I advice you to use the first letters of a sentence you can easily remember, for example "The quick brown fox jumps over the lazy dog" will be "Tqbfjotld". (Note: This is just an example, you should not use well known phrases like these, as they might appear in cracker dictionaries.) Use these passwords for MySQL users, Rails database access and in your web application. It is also good advice to check the password when a user is signing up to you application. The problem is that users need many user names and passwords, so they use the same for different applications. OpenID might be a solution.