Learn Ruby on Rails Book

Preventing XSS when cookies is used

XSS attack happens when a hacker is able to execute JavaScript code and is able to steal cookies.

To prevent this we can do the following things.

  1. Do not allow JavaScript to read cookies information.
  2. Do not allow hacker to execute hacker's JavaScript code.

As discussed in cookie section if a cookie is marked as HttpOnly then JavaScript can't read cookie information and XSS attack can't take place.

Open config/initializers/session_store.rb and we will write something like this.

1Rails.application.config.session_store :cookie_store, key: "xxxxxxxx"

Let's add httponly option to this application.

1Rails.application.config.session_store :cookie_store, key: "xxxxxxxx",
2                                                      httponly: true

Now the cookies can't be read by any JavaScript code.

Do not allow hacker to execute JavaScript

If a hacker is able to execute custom JavaScript code then hacker can steal cookies. There is only one way to prevent it and that is to not allow users of the application to 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 by the users of the applications.

Rails default behavior is to keep things secure.

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

So the code without html_safe would look like this.

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

If we execute this code then hacker's address would look like this.

1John 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.

1John Smith&lt;br /&gt;&lt;script&gt;alert(document.cookie)&lt;/script&gt;&lt;
2br /&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 escaped the content by using method html_escape.

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

In this case 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.

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

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

Using content_tag

Rails also provides content_tag to wrap content with any html tag. In the process of wrapping the content Rails also escapes the content which makes the output of content_tag safe.

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

simple_format for simple formatting

If we want to format the text a little then we can use 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. Note that simple_format will remove script tag while solutions like html_escape will preserve script tag in escaped format.

sanitize for sanitizing html content

sanitize remove href and src attributes with unsafe protocols like javascript:. sanitize takes scrubber as an option which provides a lot of flexibility in terms of what to sanitize.

1class CommentScrubber < Rails::Html::PermitScrubber
2  def initialize
3    super
4    self.tags = %w( form script comment blockquote )
5    self.attributes = %w( style )
6  end
8  def skip_node?(node)
9    node.text?
10  end
13<%= sanitize @comment.body, scrubber: CommentScrubber.new %>