Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

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

DZone's Guide to

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

We round off this two-part series by looking at transferring JSON data to HTML/JS, and all the security concerns this brings about when creating a Rails app.

· Security Zone ·
Free Resource

Discover how to provide active runtime protection for your web applications from known and unknown vulnerabilities including Remote Code Execution Attacks.

Welcome back! If you missed Part 1, check it out here.

Transferring JSON Data to HTML/JavaScript

What Is JSON?

JSON is an open, standard data format that is used for transmitting data between different kinds of systems. Utilizing JSON is really easy and convenient as many languages offer JSON creation and parsing routines. For more information on JSON, refer to this Wikipedia entry.

This is a sample JSON object:

{"id":1,"name":”<great>”,"created_at":"2016-02-24T13:14:05.049Z","updated_at":"2016-02-24T13:14:05.049Z"}

As it is self-explanatory, JSON is built on a collection of name/value pairs separated by a comma. Each name is followed by a colon and a value. The value can be either a string in double quotes, a number, true/false, null, another object, or an array.

Creating JSON Objects in Ruby on Rails

Ruby on Rails developers can easily create JSON objects from Rails models and from some Ruby core classes by hitting to_json or as_json, such as (ref): Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass, Enumerable, Time, Date, DateTime, Process::Status. The proper way to generate JSON is using those functions. For example:

Loading development environment (Rails 4.2.5.1)

2.1.1 :001 > hash = {:name => 'jack', :age => "23"}

=> {:name=>"jack", :age=>"23"}

2.1.1 :002 > hash.to_json

=> "{\"name\":\"jack\",\"age\":\"23\"}"

For all of those classes, ActiveSupport's JSON encoder - ActiveSupport::JSON.encode - is being used. We must stick to these functions and avoid trying to craft our own JSON strings. For example, the following implementation is exactly the type of thing we should stay away from:

@json_crafted = '{"name":"jack","id":"'+ params[:id] +'"}'

The downsides of this approach will be explained further down, along with examples.

What Is JSON Escaping?

Rails uses ERB::Util#json_escape function to escape special JSON characters. This particular function does the substitution based on the following hash (ref).

{ '&' => '\u0026', '>' => '\u003e', '<' => '\u003c', "\u2028" => '\u2028', "\u2029" => '\u2029' }

Here is an example:

 There is some JSON: <%= json_escape (User.last.to_json) %> 

The response is:

There is some JSON: {"id":1,"name":"\u003cgreat\u003e","created_at":"2016-02-


24T13:14:05.049Z","updated_at":"2016-02-24T13:14:05.049Z"}

As can be seen in the generated JSON code, all the special characters are escaped. On Rails 4+, JSON strings generated through ActiveSupport's JSON encoder do not require json_escape to be called since config.active_support.escape_html_entities_in_json set to true is on by default, and this will effectively escape all the HTML entities in the JSON values. Hence, extra care should be taken manually if JSON.escape is used.

Even though with Rails 4 the default JSON escaping will be applied to the values, it is still a good idea to call ERB::Util#json_escape manually for JSON strings, as relying on auto protection might not be advisable for a few immediate reasons. First, such configurations are subject to change; second, to_json might have been overridden and third; the JSON data might not be created in the application but might from some other source.

For Rails versions before 4, ERB::Util#json_escape has a bug and, as a result, it does not return valid JSON objects. Old versions of Ruby on Rails are not getting new features, bug fixes, and security fixes unless it is a severe one, as explained in Ruby on Rails maintenance policies. Therefore upgrading to Rails 4 is a good idea. But if you cannot update at this time, you should either set config.active_support.escape_html_entities_in_json to true, backport patch, or override json_escape to fix the bug as explained in this example.

JSON to HTML

Passing JSON data to HTML is simple and should be fine as long as the JSON string is created in a meaningful way. When passing JSON data to HTML, it could be only placed in two locations: inside the HTML document and into an attribute. Both of those cases will be analyzed.

Passing Data to HTML Elements

Here is an example:

<%={:name => "jack", :id => params[:id]}.to_json%>

The JSON object is created through Hash#to_json, which is the proper way as discussed before. Also, the JSON string is automatically encoded. So when attacked with ?id=<script>alert(1)</script> we get:

<b>Here is some json data: 
&quot;name&quot;:&quot;jack&quot;,&quot;id&quot;:&quot;\u003cscript\u003ealert(1)\u003c/script\u003e&quot;}

</b>

The JSON string is also encoded by ERB::Util#html_escape. This is fine since we are just presenting this data through a web page, and looks fine since those HTML entities will be displayed as regular quotes on the screen. This was pretty straightforward, thanks to Rails!

Passing Data to Attribute Values

As it has been discussed before, stylesrchref, and other event handler attributes should be abstained from. The most likely use case like this will require the passing of a JSON object through an attribute, and parsing it in the JavaScript later. Example:

<b user-info="<%={:name => "jack", :id => params[:id]}.to_json%>"</b>

If the above is attacked with ?id=" onmouseover=alert(1) the below will be the result:

<b user-info="{&quot;name&quot;:&quot;jack&quot;,&quot;id&quot;:&quot;\&quot; onmouseover=alert(1)&quot;}"
</b>

Looks like all the quotes were being escaped and it was not possible to write some custom HTML attribute here. The onmouseover is treated as a string. Let's see if those multiple layers of encoding broke the data in any way.

> object = JSON.parse(document.getElementsByTagName("b")[0].getAttribute("user-info"))

< Object {name: "jack", id: "" onmouseover=alert(1)"}

> object.name

< "jack"

> object.id

< "" onmouseover=alert(1)"

Everything seems fine from a JavaScript perspective as well. Still, it is important not to send this user-controlled input to unsafe JavaScript dorks that call eval inside, and that are mentioned in the General Principles section.

JSON to JavaScript

Ruby-on-Rails developers can transfer JSON formatted data from Rails to JavaScript. The following case is a good example. The user's name and id are sent to JavaScript through a JSON object.

<script>user = <%={:name => "jack", :id => params[:id]}.to_json%></script>

If the particular controller is being hit by ?id=34, the following HTML would be generated:

<script>user = {&quot;name&quot;:&quot;jack&quot;,&quot;id&quot;:34}</script>

As can be seen from the generated HTML, the JSON object is not valid because it has been escaped by Rails' built-in protection, just as a regular string could have been. In order to create a valid JSON object, HTML escaping should be disabled.

<script>
	user = <%={:name => "jack", :id => params[:id]}.to_json.html_safe%>
</script>

That fixes the problem of creating valid JSON but opens up an XSS issue. So if attacked with ?id=/><script>alert(1)</script>, the result will be:

<script>
	user = {"name":"jack","email":"/><script>alert(1)</script>"}
</script>

Since HTML escaping was disabled, this is a perfectly fine un-encoded quotation mark, but this also made JavaScript injection possible. We should escape values being carried in the JSON object. There is a function called ERB::Util#json_escape at our disposal to accomplish that.

<script>
	user = <%=json_escape({:name => "jack", :id => params[:id]}.to_json.html_safe)%>
</script>

In the case the same attack mentioned above is executed, the following HTML would be generated:

<script>
	user = {"name":"jack","id":"\u003e\u003cscript\u003ealert(1)\u003c/script\u003e"}
</script>

This time a valid JSON object was created successfully without introducing any cross-site scripting issues. The special HTML characters were encoded in unicode to transform the harmless data.

Why JSON Creation Matters

If a JSON object is not created in a proper way, as explained above, the methodology defined above won't work. Let's examine the situation with an example:

@json_crafted = '{"name":"jack","id":”'+ params[:id] +'”}'
<script>
	user=<%= json_escape(@json_crafted.html_safe) %>
</script>

The JSON object is manually crafted in the controller and passed to the JavaScript after json_escape. If the above is attacked with ?id="+alert(1)+" the output will be:

<script>
	user={"name":"jack","id":""+alert(1)+""}
</script>

In this case, the web browser will execute alert(1) like any other JavaScript, and the return value from the alert function will be concatenated to the empty strings and assigned back to the id value. If you try to inspect the user variable in the JS console you will see:

>user

Object {name: "jack", id: "undefined"}

This was possible because no mechanism is taking care of the double quotes - which were used here to get out of the JSON attribute value and execute JavaScript code. In other words, there was a JSON Injection vulnerability.

Final Notes

Insecure Helpers

There are a bunch of insecure helper functions, such as content_tag, raw, link_to that can introduce XSS vulnerabilities if not used with care. We are not going to go through every single one here, but let's talk about the one which is frequently used.

link_to

There is a small helper utility in ActionView::Helpers::UrlHelper called link_to that can be used to generate hyperlinks within an HTML document. Here is an example (ref):

link_to "Visit Other Site", "http://www.rubyonrails.org/"

# => <a href="http://www.rubyonrails.org/" >Visit Other Site</a>

This utility is fine as long as it is used with hardcoded data. But care should be taken if any untrusted input is linked this way, as that utility could be used to do XSS attacks. Let's change our view to demonstrate the idea.

link_to "Great link", params[:link]

When the above is attacked with ?id=javascript:alert(document.domain) the result is:

<a href="javascript:alert(document.domain)">Great link</a>

The JavaScript code embedded will work when the victim clicks the link. In order to fix this issue, the acceptable link target addresses should be restricted to only the following schemes: HTTP and HTTPS. This can be easily done by URI#parse.

$ bundle exec rails c

Running via Spring preloader in process 39287

Loading development environment (Rails 4.2.5.1)

2.1.1 :001 > URI.parse("javascript:alert(1)")

 => #<URI::Generic:0x00000101bf1970 URL:javascript:alert(1)>

2.1.1 :002 > URI.parse("javascript:alert(1)").scheme

 => "javascript"

Content-Type Header

The Content-Type HTTP header indicates the MIME type of the body of the request that is created, to help the user agent (in most cases the browser) correctly present the incoming data. According to the RFC2616, every HTTP/1.1 message that contains a body should include a Content-Type header (ref).

When serving resources, it is recommended to make sure that the correct Content-Type header is set to appropriately match the type of the resource being served. Otherwise, as is specified in the RFC, the user agent may try to guess it through MIME sniffing and that may lead to Cross-Site Scripting issues. See the section X-Content-Type-Options for details.

By default, Ruby on Rails will serve its requests with MIME content-type of text/html unless any of the following options are passed to the render method.

Render option

Content-Type

inline

text/html

json

application/json

plain

text/plain

js

text/javascript

xml

application/xml

raw

text/html


It is important to choose the correct option in order to serve data with the correct Content-Type header.

XSS Related to Security Headers

With Rails 4, every HTTP response is sent with the following headers by default (ref).

config.action_dispatch.default_headers = {
	'X-Frame-Options' => 'SAMEORIGIN',
	'X-XSS-Protection' => '1; mode=block',
	'X-Content-Type-Options' => 'nosniff'
}

To prevent XSS vulnerabilities, both of the X-Content-Type-Options and X-XSS-Protection headers should be left with default settings.

X-Content-Type-Options

This is the header that can be configured to give a specific policy to the browser on MIME type preference. When this is set to nosniff, the browser doesn't try to sniff the HTML document to guess its content type, instead, it uses the one passed in the Content-Type header. MIME type sniffing is a standard functionality in browsers. This allows older versions of Internet Explorer and Chrome to perform MIME-sniffing on the response body, potentially causing the response body to be interpreted and displayed as a content type other than that that it is intended to be.

The problem arises once a website allows users to upload content which is then published on the web server. If an attacker can carry out an XSS (cross-site scripting) attack by manipulating the content in a way to be accepted by the web application and rendered as HTML by the browser, it is possible to inject code in, for example, an image file and make the victim execute it by viewing the image.

X-XSS-Protection

This particular header can be used to enable cross-site scripting protection on the browser. This setting is recognized by Internet Explorer and Chrome.

HTTPOnly Cookies

HTTPOnly cookies cannot be read by client-side scripts, therefore marking a cookie as HTTPOnly can provide an additional layer of protection against Cross-site scripting attacks. On Ruby on Rails, cookies are not flagged as httponly by default, therefore should be set explicitly as shown in the below example:

cookies[:user_name] = {:value => "jack", :httponly => true}

Vulnerability Classification and Severity Table

Classification ID / Severity
PCI v3.1 6.5.7
PCI v3.2 6.5.7
CAPEC 19
CWE 79
WASC 8
OWASP 2013 A3
HIPAA 164.308(a)
CVSS:3.0 CVSS:3.0/VA:N/AC:L/PR:N/UI:R/S:C/C:H/I:N/A:N
Netsparker

 High

Find out how Waratek’s award-winning application security platform can improve the security of your new and legacy applications and platforms with no false positives, code changes or slowing your application.

Topics:
security ,web application security ,ruby on rails ,javascript security ,cross-site scripting

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

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

{{ parent.tldr }}

{{ parent.urlSource.name }}