Skip to main content

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 user interaction in the form of client-side JavaScript, which permits functionality to be included inside the web 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 a hacker could use XSS vulnerabilities to execute their 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 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 XSS

DOM-based XSS is the hardest form of XSS to detect. Since it is often device- or browser-specific, it won’t appear 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 capable of storing a script, and the sink is capable of script execution.

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

DOM XSS is dangerous because it can often bypass sanitizers on the client. For example, if you validate every input from a user against an XSS sanitizer, you may be able to ensure no script tags are 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 simplest way to protect your users from cross-site scripting attacks is to let LWC manipulate the DOM whenever possible and avoid manual DOM manipulation.

LWC and its security wrapper, Lightning Web Security, 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, avoid using the lwc:dom=”manual” annotation wherever possible.

Another best practice to follow is when you need to bind JavaScript data to your UI, use LWC’s annotations like @api and @track. When you need to iterate through a list of items, use 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.

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

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})`;
         el.appendChild(li);
      })
   }
}

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 the 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 want to compromise information on credit cards or bank accounts. 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 the goal of accessing your users’ data as a form of corporate espionage.

XSS can be used by attackers to accomplish many different things. Here are some common types of XSS.

  • 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 a 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, the application throws an error if the user enters anything besides numbers in a phone number field.

Allowlisting is considered the more secure approach of the two input filtering methods. For allowlisting to be effective, the developer needs to know only the expected input values.Blocklisting requires the developer to maintain a list of all potential malicious entries in all forms of encoding, which is an impossible task.

Two blocks of obscured code; in front of each is a hand holding a magnifying glass.

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 secure 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.

Resources

Keep learning for
free!
Sign up for an account to continue.
What’s in it for you?
  • Get personalized recommendations for your career goals
  • Practice your skills with hands-on challenges and quizzes
  • Track and share your progress with employers
  • Connect to mentorship and career opportunities