Preventing XSS Vulnerabilities When Developing Ruby on Rails Apps, Part 1

DZone 's Guide to

Preventing XSS Vulnerabilities When Developing Ruby on Rails Apps, Part 1

In Part 1 of this two-part article, we cover several different ways to transfer data in a Rails application, and how they make your app vulnerable to XSS.

· Security Zone ·
Free Resource

Cross-site scripting is a very common injection type of web application vulnerability. It allows attackers to inject any type of client-side script which is then executed by the victims' browsers in their browsing contexts. Typically, cross-site scripting attacks are used to bypass access controls and to steal web sessions. Read What is Cross-site Scripting and How to Prevent It for more technical details on this notorious vulnerability.

Ruby on Rails has a built-in XSS protection mechanism which automatically HTML escapes all the data being transferred from Rails to HTML. While this is a big plus for Rails framework security, it is not enough to solve all XSS problems, which is why, every day, new cross-site scripting vulnerabilities are still being discovered on Ruby on Rails web applications.

This article explains several different techniques which you can use to develop Ruby on Rails web applications that are not vulnerable to cross-site scripting (XSS).

What Is HTML Escaping?

In programming/scripting languages, there are some special characters that help interpreters/parsers to differentiate data from actual code. In HTML, tags start and finish with the < > characters. In the tags, we differentiate attributes from data by putting the latter between single or double quotes.

Those special characters might be a part of the data that is needed to be displayed within an HTML page. In this case, those characters must be substituted with innocuous ones to make sure they are not being considered as regular HTML tags.

Rails uses the ERB::Util#html_escape function to escape HTML entities. This particular function does the substitution based on the following hash (more about ERB::Util).

{ '&' => '&amp;', '>' => '&gt;', '<' => '&lt;', '"' => '&quot;', "'" => '&#39;' }

Here is an example:

Hello <%= params[:name] %> 

If the above is attacked with ?name=<script>alert(1)</script>, the output will be as follows:

<b> Hello &lt;script&gt;alert(1)&lt;/script&gt;gt; </b>

As can be seen in the generated HTML code, all the special characters have been escaped.

html_safe and Introduction to Safe Buffers

Sometimes, Rails's built-in auto-escaping feature might not be desirable because of some business need, and, actually, we may intentionally want to output data that contains legit HTML through views. There are several ways to disable this automatic escaping. For example, the data can be marked as html_safe as shown in the below example:

Hello <%=params[:name].html_safe%> 

If the above is attacked with the ?name=<script>alert(1)</script> query string, the result will be:

Hello <script>alert(1)</script>

What's happening here is that when html_safe is being called on a String and as a response, an ActiveSupport::SafeBuffer object is being created and returned (ref). SafeBuffer objects in the views are not escaped as they are already considered "safe." Since it is really easy to introduce XSS vulnerabilities through the use of html_safe, it must only be used for trusted/validated data with the utmost care.

In this following Rails view,  params[:input] will be HTML-encoded before appending into other SafeBuffer, therefore it would be safe.

Hello <%= '<b>'.html_safe + params[:name] + '</b>'.html_safe%>

For the following example, the input - params[:name] - will be a part of the string that is being converted to a SafeBuffer, therefore it won't be escaped later and, as a result, a Cross-Site Scripting vulnerability will be introduced.

Hello <%= "<b>#{params[:name]}</b>".html_safe%>

Transferring Data From Rails to HTML

HTML code consists of elements, attributes, and values.

Passing Data to HTML Elements

The following style of data output is considered mostly safe for the sake of Rails's built-in auto-escaping, as discussed previously.

Hello <%=params[:name]%>

When a malicious input hits this code location, Rail's auto HTML escaping will take care of it before being sent back to the client. On this example, the data was sent within the <b> tag. While using nearly all of the HTML tags wouldn't make a difference there, a couple would have introduced security vulnerabilities. Some HTML tags change the context and open up another layer of parsing and execution. Those are: <script> and <style>. There should not be any untrusted data going there directly, except with very precise methods which will be discussed in the upcoming sections.

Passing Data to Attributes or Tag Names

HTML tag and attribute names should never be created from untrusted data, even after escaping or encoding, as those are parts of HTML code itself. By not doing so it would mean giving the ability to write HTML code to the outside parties.

Passing Data to Attribute Values

In HTML, there are some special attributes that either change execution context or have some special abilities, for example, style, src, and href attributes. The same in this case, untrusted data should not be directly passed inside the values for these.

Attributes that help define event handlers like onmouseover will be discussed in the JavaScript section. For other attributes, it can be considered safe with appropriate escaping. Other than that, there is only one caveat to be noted here, attribute values must always be placed within quotes. Here is an example:

<b name=”<%=params[:name]%>”>Hey All!</b>

Transferring Data From Rails to JavaScript

Modern web applications often need to pass data from the backend to JavaScript. That is a perfectly valid need but should be done with precaution to avoid introducing any security vulnerabilities. Rails's auto-HTML escaping features will help us a lot, but, certainly, it is not a silver bullet solution, and depending on the usage, there might be some details that need to be taken care of.

There are different encoding considerations that need to be made when transferring data from Rails to a different context than HTML, and if this is JavaScript, it can be quite tricky.

General Principles

First of all, we should know what we're expecting to pass to JavaScript. It could be a string, an email address, an integer or something else. Depending on the case, we need to employ a different strategy.

Second, JavaScript can be considered as a sub-context under HTML, since it is living in an HTML document. Because of that, we should not forget to take care of HTML Injections together with our JavaScript.

Third, as a general principle, there must never be any type of input or untrusted data structure that is sent to eval or to related JavaScript functions, even after encoding or escaping. Since these functions are actually evaluating the incoming string, it is impossible to prevent code execution if any input is passed to:

  • eval
  • setInterval
  • setTimeOut
  • execScript
  • write
  • writeLn
  • body.innerHtml

Alternatively, eval and related functions can be disabled by Content Security Policy restrictions.

Passing Numbers to JavaScript

A simple implementation of passing numbers to JavaScript can be something like:

<script>current_user_id = <%=param[:id]%></script>

If the above is attacked with a parameter such as ?id=alert(document.cookie) the output will be:

<script>current_user_id = alert(document.cookie)</script>

In this case, none of the characters are being escaped because there were not any HTML special characters, yet it was possible to execute some simple JavaScript in the target web application's browser context. This is happening because the user input is being put to JavaScript execution context without any precaution.

There is only one way to secure this design, we should make sure that we are passing just an integer, nothing else. This can be done a couple of ways, for example by employing whitelists. But for the Ruby ecosystem, there is an easy way:

<script>current_user_id = <%=param[:id].to_i%></script>

Do not worry, for if a string is sent to the to_i function, it will basically cast everything to 0 if there are no valid integers.

Passing Text to JavaScript

JavaScript is a really interesting language with lots of special characters. Though it is really hard to transform all the special characters into harmless data, which is why the proper way to transfer text to JavaScript context is only possible by locking up the data inside single or double quotes, and by making sure all the quotes and special HTML characters are properly escaped.

Here is an example:

<script>current_user_id = ‘<%=param[:id] %>’ </script>

In this particular case, the built-in Rails protection will be enough to help defend the situation. Since, in order to inject JavaScript code, the malicious input should be going outside of the single quotes, which is not possible since all the single quotes and tag opening/closing characters are going to be escaped by ERB::Util#html_escape.

If we attack the above with the following parameter, ?id='+alert(1)+'we get:

<script>current_user_id = '&#39;+alert(1)+&#39;'</script>

As you can see, we could not get out of the string and therefore could not inject any code to the execution context. If we re-iterate our thinking and decide to attack through getting out of <script> tag first, we can do this with ?id="><script>alert(1)</script>:

<script>current_user_id = '&quot;/&gt;&lt;script&gt;alert(1)&lt;/script&gt;'</script>

But again, the attack was not successful since special HTML characters are being escaped.

Tune in next time when we'll cover transferring JSON Data to HTML/JavaScript! 

cross-site scripting vulnerability, ruby on rails, security, web application security, xss

Published at DZone with permission of Ferruh Mavituna , DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}