Cross-site scripting (XSS) attack - Part 1

As frontend developers we are constantly adding and releasing new features or fixing bugs as per business requirements, and it's hard to keep vigilance on the security side of things. It has become a secondary concern and we are far behind the backend and Devops engineers for whom this is a primary and regular part of their thinking in development process. Today security attacks are on the rise and we need to take measures from both server side and client side before its too late.

With this post I am starting a new series called 'What a frontend developer should know about web security'. Here I will cover about Cross-site scripting (XSS) attacks and how to defend your client-side code from this attack as a frontend developer.

What is this Cross-site scripting (XSS) ?

In simple terms, this is a type of code injection attack where an attacker finds a way to inject malicious javascript into the user's browser while the user views your website.

As you very well know javascript can absolutely do anything with a web page, alter page content, steal cookies, hijack user sessions, trick them into logging and thereby passing the attacker the user's login information unknowingly and so on.

There are different types of Cross site scripting attacks :

1. Stored XSS attacks

Websites generate content in the HTML using the stored data from the database. For example we are able to display the users address on the profile settings page by making an api call and fetching the response to display the address details that we need.

Let's say an attacker injected a malicious script within the comment section and submitted it on a web page. The comment with the javascript gets stored in the database and gets executed in the browser when an unsuspecting victim views it on the site. This type of attack is called stored cross-site scripting attack .

Any user controlled content can be used for this type of attack.

Let's look at how we can defend against such attacks.

Defence # 1 - Escape..

Sorry, not that escape, I mean Escape HTML characters.

To prevent stored XSS attack, we could escape all dynamic content coming from the database so that the browser will treat the escaped characters (entity encodings) as content within HTML tags.

<div class="comment">
  &lt;script&gt;alert(&quot;HAXXED&quot;)&lt;/script&gt;
</div>

In javascript one can escape characters on a string (used mainly for URI) using the function encodeURI().

const data = {
  comment: "Hi, there <script>console.log('you are hacked')</script>"
}
console.log(encodeURI(data.comment)); // => Hi,%20there%20%3Cscript%3Econsole.log('you%20are%20hacked')%3C/script%3E

Thankfully, most of the modern frameworks take care of this automatically. But if you want to unescape for some reason, then sanitise the data before it is put into HTML.

Note: The following examples are ways to unescape in different frameworks.

  • React - return <div dangerouslySetInnerHTML={createMarkup()} />;
  • Angular - <div [innerHTML]='<a href="#">Unescaped link</a>'</div>
  • Vue - <div v-html="htmlData"></div>

The above doesn't mean they are unprotected from script injection, for example in Vue.js, if you try to put a script tag into v-html it does not execute. Measures may be already taken by these frameworks, but be careful, there may be still some exceptions, make sure you read the security recommendation provided in the documentation.

Edit: Try to use a library such as node-esapi for encoding content in different contexts with functions like encodeForHTML,
encodeForCSS, encodeForJS, encodeForURL, encodeForHTMLAttribute etc.

Defence # 2 - Content Security Policy (CSP)

Content Security Policy or CSP is a security layer that can be set on modern browsers, telling the browser which scripts should be trusted.

XSS attacks rely on injecting script somewhere with the <html> tag of a web page (known as inline script). CSP tells the browser to never execute inline scripts unless it is imported via srcattribute in the <script> tag.

CSP can be either set in your HTTP response headers..

Content-Security-Policy: script-src 'self' https://api.foo.com

or in a <meta> tag in the <head> element of the HTML.

<meta http-equiv="Content-Security-Policy" content="script-src 'self' https://api.foo.com">

In the above example the browser will load script only from the domain api.foo.com even if say the website's domain is https://foowebsite.com.

Unsafe inline script

Inline script can be permitted if the policy includes the keyword unsafe-inline.

Content-Security-Policy: script-src 'unsafe-inline';

In case you want to include a specific inline-script like google tag manager and still want to block any other suspicious inline scripts being added, cryptographic nonce can be added

<script nonce=E9h3sdfn3f03nce9DNnIOefn3fa>
        (function googleTagFunction(){....})();
      !</script>
Content-Security-Policy: script-src 'nonce-E9h3sdfn3f03nce9DNnIOefn3fa'

To know more about ways of adding unsafe-inline read #unsafe inline script documentation.

Reporting CSP violations

If you want to block the scripts and also report when scripts are attempted to inject, you can add a report-uri directive ( multiple directives are separated by semi-colon) and include a URI where the report should be sent.

Content-Security-Policy: default-src 'self'; report-uri http://reportcollector.example.com/collector.cgi

If you would only collect violation reports and not block the scripts being executed.

Content-Security-Policy-Report-Only: default-src 'self'; report-uri http://reportcollector.example.com/collector.cgi

To know more about other directives check #Content-Security-Policy documentation

In the coming post we will look into another type of XSS attack - Reflected Cross-site Scripting attack. Stay tuned!

27