Mitigate Cross-Site Scripting

Learning Objectives

After completing this unit, you'll be able to:

  • Explain XSS and how it can affect your applications.
  • Define three types of XSS.
  • Write XSS-safe Lightning Web Components code.

What Is XSS?

Modern web applications allow for significant amounts of user interaction in the form of client-side JavaScript, which permits functionality to be included inside of the internet browser itself. Unfortunately, browser execution of JavaScript presents itself as a risk when the JavaScript that is executed does not originate from the application developer but comes instead from another user. We call this cross-site scripting, or XSS for short.

XSS is an injection vulnerability that occurs when an attacker inserts unauthorized JavaScript, VBScript, HTML, or other active content into a web page. When subsequent users view the page, the malicious code executes or attacks the user. For example, a malicious script can hijack the user’s session, submit unauthorized transactions, and even steal confidential information from the user.

Three Forms of XSS

XSS takes several forms, each of which is enacted slightly differently on the application. Let’s review three ways that a hacker could use XSS vulnerabilities to execute their own script on another user’s device. For more information on XSS vulnerabilities, see the OWASP website. 

Stored XSS
Stored XSS is one of the most common forms of XSS, as many applications take, store, and display user input to other users. Stored XSS occurs when malicious input is permanently stored on a server and reflected back to the user in a vulnerable web application. This often occurs when a malicious value can be stored in a database and retrieved, such as with a message board post or data in a user profile.

Reflected XSS
Reflected XSS operates very similarly to stored XSS, but the server does not store the script. Reflected XSS occurs when malicious input is redirected to a website and then reflected back to the user on the response page. This can happen by convincing users to click links, fill out web forms, or search input boxes that contain malicious input.

DOM-based XSS is the hardest form of XSS to detect. Since it is often device or browser specific, it won’t show up in routine integration tests as developers usually perform those on the latest browser versions.

DOM XSS vulnerabilities include both a source and a sink. Think about sources as our inputs and sinks as our outputs. The source is the part of the DOM that is capable of storing a script and the sink is capable of script execution. 

An example of DOM XSS would be storing script nodes in the hash of a URL that can be accessed via window.location.hash and executing it via DOMParser.parseFromString().

DOM XSS is dangerous because it is often capable of bypassing sanitizers on the client. For example, if you validate every input from a user against an XSS sanitizer, you may be able to ensure there are no script tags in the input. But if a user references window.location.hash and sets it equal to script, then the reference is used as a source and will bypass sanitization. This can lead to script execution.

Use LWC to Protect Against XSS Attacks

The most simple way to protect your users from cross-site scripting attacks is to ensure that you let LWC manipulate the DOM whenever possible and stay away from manual DOM manipulation.

LWC and its security wrapper, Lightning Locker, provide the best DOM sanitization in the industry. However, this means you must leave LWC to manipulate its own DOM.

Relying on LWC for DOM manipulation is simple. First off, avoid the use of the lwc:dom=”manual” annotation wherever possible.

Another best practice to follow is when you need to bind JavaScript data to your UI, make use of LWC’s annotations like @api and @track. When you need to iterate through a list of items, make use of LWC’s iterators instead of generating the DOM nodes in JavaScript.

LWC makes it easy to use annotations.

This iteration makes use of LWC’s built-in for:Each iterator, offloading DOM manipulation to LWC’s engine.

   <div class="app slds-p-around_x-large">
      <h1>Template Demo</h1>
      <h1>LWC Iteration</h1><br>
         <template for:each={demographics} for:item="demographic">
            <li key={demographic.state}>
               {demographic.state} (pop: {demographic.population})
      <h1>DOM Iteration</h1><br>
      <ul class="myList">

This manipulation generates new list elements and appends them manually to the DOM.

import { LightningElement, track } from 'lwc';
export default class App extends LightningElement {
   demographics = [
         state: 'Washington',
         population: 7100000
         state: 'California',
         population: 270000000
         state: 'Florida',
         population: 21000000
   renderedCallback() {
      const el = this.template.querySelector('.myList');
      this.demographics.forEach((d) => {
         const li = document.createElement('li');
         li.innerHTML = `${d.state} (pop: ${d.population})`;

The first code sample shows the LWC option. The second code option is simple and significantly reduces XSS risk by sanitizing the inputs of the iteration for you prior to appending them to the DOM.

If you must use the DOM manually, then do your best to avoid common script execution sinks. These include but are not limited to:

  • SetTimeout
  • SetInterval
  • Eval
  • DOMParser.parseFromString
  • Document.implementation.createHTMLDocument

XSS Impact on Your Application

Failing to protect your application from XSS puts all of your users at serious risk. Because XSS allows script execution against another user’s browser, any data currently in that user’s Locker sandbox or DOM can potentially be exfiltrated. This means a chart app being used to display financial data can have that data stolen and sent to an attacker.

Additionally, script execution can result in untended operations being performed on behalf of the victim. A script can fill out a form on behalf of the attacked user and submit it—appearing as if the form was signed by a legitimate employee.

Let’s consider how an external attacker utilizes this attack. Some attackers are financially motivated and look to compromise credit card or bank account information. Some attackers are politically or socially motivated and look to deface a page, impacting a company’s reputation as a form of protest. Or attackers can be hired by a competitor to target user accounts with a goal of accessing your users’ data as a form of corporate espionage. Here are some other common XSS attack types.

  • Arbitrary requests: An attacker can use XSS to send requests that appear to be from the victim to the web server.
  • Malware download: XSS can prompt the user to download malware. Since the prompt looks like a legitimate request from the site, the user may be more likely to trust the request and actually install the malware.
  • Log keystrokes: The attacker can monitor keyboard entries, possibly finding usernames and passwords to access accounts at later dates and steal information.

Common XSS Mitigations

Now that you’ve seen the dangers that XSS poses to your users, we’ll arm you with defense mechanisms that you can use in your application to protect against these attacks. As we mentioned previously, XSS is caused by weak separation between code context (the actual underlying application) and user data (any information submitted by the user). An attacker can submit malicious code and convince the application to execute it. To fortify your defenses, you can strengthen the barrier between these two components.

There are two basic techniques to accomplish this: input filtering and output encoding.

Input Filtering

Input filtering works on the idea that malicious attacks are best caught at the point of user input. If the user inputs <b>duck</b> and the page strips or blocks the code, then no unauthorized code runs. There are two types of input filtering.

Blocklisting: Specific “bad” characters or combinations of characters are banned, meaning they can’t be entered or stored. The developer creates a list of known bad characters—such as HTML or script tags—and throws an error if any bad characters are in the input.

Allowlisting: Only characters or words from a known list of entries are permitted, preventing malicious input. For example, if the user enters anything besides numbers in a phone number field, the application throws an error.

Of the two input filtering methods, allow listing is considered the more secure approach. For allowlisting to be effective, the developer needs to know only the expected input values, while blocklisting requires the developer to maintain a list of all potential malicious entries, often an impossible task.

Two blocks of obscured code; in front of each is a hand holding a magnifying glass. The block on the left displays blocked sites, designated by a magnifying glass showing a red x. The block on the right displays allowed sites, designated by a magnifying glass showing a green check.

Output Encoding

While input filtering techniques work by preventing malicious data from entering the system, output encoding techniques take the opposite approach by preventing malicious payloads already in the system from executing. In fact, output encoding is often considered more necessary than input encoding because it doesn’t rely on any upstream or downstream protections, and it can’t be bypassed by alternative input pathways.

In output encoding, a server takes all characters that are meaningful in a specific context—such as in HTML, JavaScript, or a URL—and replaces them with characters that represent their text versions. For example, consider the < character. In HTML, this character signifies the start of a tag—like <b>, the HTML tag specifying bold text—but in mathematical functions, the character means less than.

So how does your browser tell the difference? Encoding! Encoding enables a meaningful character to be included in a block of text via a controlled substitution that removes the meaning. In the case of the < character, you can replace it with the characters &lt; and your browser will understand that you want the text version of <, not the HTML version.

As a developer, you can use this same concept to mitigate XSS, because characters that can act as code aren’t represented in their meaningful version inside a block of code. Only the characters’ text equivalents appear.