Secure Node Apps Against OWASP Top 10 - Cross Site Scripting

Secure Node Apps Against OWASP Top 10 - Cross Site Scripting

  • 2016-11-01
  • 867

In this multipart series, we will explore some of the the OWASP top web application security flaws including how they work and best practices to protect your application from them. The focus will be on Express web applications in Node, but the principles shown can be applied to any framework or environment.

Cross Site Scripting (XSS)

So what exactly is a cross site scripting vulnerability?

XSS, the most prevalent web application security flaw, occurs when an application includes user supplied data in a page sent to the browser without properly validating, escaping, or sanitizing that content.

An XSS attack occurs when an attacker sends text-based scripts that exploit the interpreter in the browser. Essentially, the attacker takes advantage of the user’s trust in the website. By this, I mean the end user viewing your web application trusts the content you are sending back. Because of this implicit trust, an attacker can exploit it by having your web application return malicious content.

There are two main types of XSS attacks:

  1. Non-persistent
  2. Persistent

Non-persistent

Non-persistent, also referred to as reflected, are attacks where injected scripts are sent to a user’s browser via the user’s own request to the web application. These types of attacks are done by tricking the victim into initiating the attack via email, some other website, social media, etc. The result is the user clicks a malicious link, submits a form, or makes a request to a malicious site. In doing so, the user’s browser is used as the medium to send the injected code to the web application through the request which is then reflected back to user in the response. The browser then executes the injected code because it came from the web application which it trusts.

The following diagram shows how this type of attack is done. We will go over it step by step.

XSS Non-persistent

In this example, Site (our web application) implements a search function at the endpoint /search?q=searchTerm. An attacker has found that when they do a normal search on the site, the results page shows the search term along with the results. After some testing, the attacker finds that the site is not escaping or sanitizing the search term before showing it in the results page. The attacker has just found an XSS vulnerability.

Here is one way the attacker could exploit this vulnerability.

GET /index.html

The attacker has a page on a site they control and shares a link on social media. Our user clicks the link to see what it is and their browser makes a request to /index.html on the attacker’s site.

HTTP/1.1 200 OK

Requests to /index.html returns the following HTML content. If you look at the anchor tag, you will see it links directly to the search page on our web application and supplies JavaScript as the search term.

<html>

  <a href="https://site.com/search?q=<script>alert('hacked')</script>">
    Click Here
  </a>

</html>

GET /search?q=…

If the user clicks the link, and many will, their browser will make a request to our search page along with the malicious JavaScript in the search term.

https://site.com/search?q=<script>alert('hacked')</script>

HTTP/1.1 200 OK

Because our site is not properly escaping the search term being supplied by the user, we will return a response to the user that includes the injected JavaScript. In this example, it would just create an alert, but the attacker could do anything including injecting script tags that fetch other JavaScript.

Here is the response in a normal case

<html>

  <div>
    You searched for: Puppies
  </div>

</html>

Here is the response in our exploited case

<html>

  <div>
    You searched for: <script>alert('hacked')</script>
  </div>

</html>

Send valuable data

The end user’s browser will now execute the script that was injected into the page. The attacker could send valuable information embedded on the page, include other scripts to further the exploit, and more.

In this example, the attacker setup a page that the user had to click a link on. This attack could be done easier by simply returning a 302 response with the Location header set to the search page on our web application. This takes out the step of the user needing to click a link and makes the attack more probable. The user’s browser would get redirected to the exploited link and the attack would occur.

Persistent

Persistent, also referred to as stored, are attacks where the injected script is permanently stored on the target web application’s database. Victims are then tricked into clicking a link to the web application that returns back the injected script in the response after retrieving it from the database or persistent storage.

The following diagram shows how this type of attack is done. We will go over it step by step.

XSS Persistent

In this example, Site (our web application) allows users to POST comments at the endpoint /comment. An attacker has found that when they POST comments on the site, the posted data is not escaped or sanitized before storing it and/or showing it. The attacker has just found an XSS vulnerability.

Here is one way the attacker could exploit this vulnerability.

POST /comment

The attacker knows they can POST comments to the site that allow scripts to be injected. The attacker will POST an exploited comment to the site with the following content.

<script>alert('hacked')</script>

This comment is now persisted because the content is stored in the database. Anytime a user views the comment, the injected script will be executed.

GET /comment?id=1

A user is browsing the site and views the exploited comment. They could get to this comment by just being on the site or could be directly to it via email, social share, etc.

HTTP/1.1 200 OK

Because our site is not properly escaping or sanitizing input or output, the resulting response to the user will include the injected script.

Send valuable data

The injected script will be executed on the end user’s browser which allows the attacker to get a hold of valuable information embedded on the page, include other scripts to further the exploit, and more.

Solution 1

The first solution is to always validate, escape, or sanitize user input. As a rule, all user input should be treated as malicious. If you build your application in a way that distrusts user input, you will end up with a more secure system.

This can be done by either sanitizing all user input as it enters the system or as it leaves. The following code is one way to do this in an Express application. What we are doing here is setting up middleware that will process all posted data by sanitizing it. I like this pattern because it avoids the case where newly added forms could be missed if you are sanitizing per input.

var express    = require('express');
var bodyParser = require('body-parser');
var validator  = require('express-validator');

var app = express();

app.use(bodyParser.urlencoded());

app.use(validator());
app.use(function(req, res, next) {
  for (var item in req.body) {
    req.sanitize(item).escape();
  }
  next();
});

app.listen(80);

With this code in place, our previous exploit examples of injecting JavaScript would turn

<script>alert('hacked')</script>

into the following

>script<alert('hacked')>/script<

One issue with sanitizing user input as it enters the system is if someone is able to get data into your database or persistent storage through another means. If this happens, your application can still be vulnerable to XSS attacks. In this case, you will want to consider sanitizing data as it leaves your application.

Solution 2

The second solution is to tell the web browser to allow content only from trusted sources.

This can be done by taking advantage of a powerful response header called Content Security Policy or CSP. CSP is a response header that tells the browser the domains it should consider as valid sources of content.

This can be used in two ways for our applications. First, we can tell the browser which domains it is allowed to trust and run JavaScript from. Second, we can tell the browser to not allow any in-line script. These two methods can protect many of our users (the ones with browsers that support CSP) from XSS attacks. If the attacker cannot get the browser to load or execute the injected JavaScript, then the attack is stopped.

The following code shows an example Express application using the helmet package to implement CSP.

var express = require('express');
var helmet  = require('helmet');

var app = express();

app.use(helmet.csp({
  defaultSrc: ["'self'"],
  scriptSrc: ['*.google-analytics.com'],
  styleSrc: ["'unsafe-inline'"],
  imgSrc: ['*.google-analytics.com'],
  connectSrc: ["'none'"],
  fontSrc: [],
  objectSrc: [],
  mediaSrc: [],
  frameSrc: []
}));

app.listen(80);

In this example, we are telling the browser to only allow scripts from our own domain as well as *.google-analytics.com.

Here is what the response headers look like with newlines added for readability.

Content-Security-Policy: 
  default-src 'self';
  script-src *.google-analytics.com;
  object-src ;
  img-src *.google-analytics.com;
  media-src ;
  frame-src ;
  font-src ;
  connect-src 'none';
  style-src 'unsafe-inline'

Solution 3

The final solution is to do a deep analysis of your application and understand all the ways in which you could/should protect yourself. This cheat sheet provided by OWASP does a great job of discussing all the rules you should follow.

Wrap up

I hope that by learning how XSS attacks are performed you have a better understanding of how to protect your applications. I have shown a few ways in which you can protect yourself but it is important to learn more on this subject using the links I shared above. Also, these solutions are not exclusive. You should implement many layers of protection for your application.

Suggest

The Complete Node.js Developer Course 2.0

A Simple Node.js/Mongo/Restify API in Less Than 3 Hours

The Complete Node JS Developer Course

Facebook Authorization with Node.JS

Rapid Node.js Training

Node Program: From 0 to Hero with Nodejs and MongoDB