How to prevent XSS attacks

Cross-site scripting vulnerabilities and attacks are not going away any time soon, but with the right combination of security headers, secure coding practices, modern application frameworks, and regular vulnerability testing, you can dramatically reduce the risk of successful XSS attacks against your applications and APIs.

How to prevent XSS attacks

JavaScript has come a long way since being only lightly sprinkled on static HTML web pages to make them more dynamic. It is now a crucial building block of modern web applications, making cross-site scripting (XSS) a commonplace security vulnerability—and also making XSS attacks that much more impactful if they succeed.

No longer restricted to providing some additional client-side functionality via a handful of scripts, JavaScript code can now run across the entire application stack, up to and including the server side with Node.js. Add to that the plethora of external dependencies loaded at runtime by any self-respecting site and you’re dealing with a tangled web of interconnected scripts—some of which could be vulnerable or even malicious.

Cross-site scripting is a complex and messy area of web application security, which makes it all but impossible to prevent every single attack. (As a side note, while JavaScript is by far the most popular attack vehicle, XSS is also possible with other script types, even including XSS in CSS.) Fortunately, most XSS vulnerabilities and resulting attacks can be prevented by following a handful of security best practices in development and deployment. Let’s start with a cut-out-and-keep checklist before going deeper into selected aspects of XSS.

XSS attack prevention checklist

Follow these best practices to prevent the vast majority of cross-site scripting attacks:

  • Set HTTP security headers: Define the right Content Security Policy (CSP) HTTP response headers to stop malicious scripts from being loaded in the first place.
  • Treat all inputs as untrusted: Always sanitize user inputs (including API inputs and outputs), perform input validation, and use context-dependent output encoding.
  • Use secure coding practices and tools: Avoid inline scripts and correctly use any XSS-resistant features provided by your application framework, such as automatic encoding functions.
  • Run regular vulnerability testing: Periodically rescan your websites and applications with an up-to-date web vulnerability scanner to catch vulnerabilities in time.

(Note that filtering is deliberately not listed here—read on to learn why you can’t trust XSS filtering.)

The complex world of cross-site scripting vulnerabilities

At its core, XSS is a type of injection attack just like SQL injection, except you’re injecting JavaScript code rather than SQL instructions. But unlike SQL injection, where you’re always trying to mess with an SQL query, there are many different types of cross-site scripting, depending on how the malicious code is delivered and executed. The XSS section in Invicti Learn goes into far more detail, but broadly speaking, there are three main types of XSS attacks:

  • Reflected XSS attacks: The classic XSS vulnerability is to take a raw input parameter value from an HTTP request and directly use it in the output, thus reflecting any malicious code from the input and executing it in the victim’s browser. While this is the most common type of XSS, its effects are limited to a single user and browser.
  • Stored XSS attacks: To inject JavaScript into multiple browsers, a malicious hacker can try to slip an XSS payload into a backend resource that will be accessed by many users. If the payload is stored as-is and the web server doesn’t sanitize it upon loading, a single entry in a database or serialized file could result in XSS across thousands of browsers when they load that entry.
  • DOM-based XSS attacks: Instead of preloading all the page code at once, many web applications rewrite their internal document object model (DOM) as the app executes without reloading the page. If an attacker manages to inject malicious JavaScript code into the DOM and have it execute, that code will only ever exist in the user’s browser, making these attacks invisible (and impossible to prevent) on the server side.

The only thing all XSS vulnerabilities have in common is they allow JavaScript code to exist somewhere in the inputs or outputs of an application. So maybe you could simply look out for that code and block it? That’s what XSS filters tried to do—and it didn’t work, in the long run.

Why you can’t trust XSS filtering

Early approaches to XSS prevention relied on stripping out script tags from inputs, starting with DIY filtering functions that eventually grew into heavyweight filters built into the web browser. The problem was that while reliably identifying and blocking a specific payload is easy, creating more general patterns to stop malicious scripts without interfering with legitimate ones proved all but impossible. Our blog post on the rise and fall of XSS Auditor in Google Chrome tells the fascinating story of all the things that can go wrong with XSS filtering (including how it can create its own security vulnerabilities), so check that out for details.

Long story short—XSS filtering doesn’t work and can’t be trusted as your only line of defense against cross-site scripting attacks. While most web application firewalls (WAFs) do have built-in XSS filters that may stop basic probing attacks, XSS filter evasion to bypass WAF rules is the bread and butter of any serious attacker. So, while it doesn’t hurt to have that option enabled if your WAF provides it, you should never rely on any XSS filter to provide useful protection from attacks.

Cross-site scripting in APIs

The stereotypical XSS attack is someone typing <script>alert(1)</script> into a form field or URL parameter—but what about cross-site scripting in modern API-driven apps? With the backend now acting as a separate data provider for any number of frontends communicating with it via APIs, there’s no way to do centralized XSS prevention on the server. API requests that include sensitive data are a valid and attractive target for attackers, making XSS a very real threat even without a form field in sight.

 

Read more about why APIs make XSS prevention a frontend job.

Layered security best practices are the way to prevent XSS

There is no silver bullet to magically protect your apps from cross-site scripting. Especially with full-stack JavaScript applications and the ubiquity of APIs, there are simply too many avenues of attack and too many code interactions to catch them all. And yet, if you follow a handful of secure practices to build up multiple layers of resistance, you can make successful and impactful XSS attacks extremely unlikely.

The winning combination is to dramatically limit your attack surface with the right CSP headers while also using secure coding practices and tools to minimize the number of XSS vulnerabilities that make it to production. Top this off with regular vulnerability scanning using a quality DAST tool and you should have XSS well under control.

 

Frequently asked questions

What is the best way to prevent XSS attacks?

Preventing XSS requires a combination of secure configuration and secure coding practices. Configuring the right Content Security Policy (CSP) header values is the most effective way to quickly improve the security of your website or web application by blocking the loading of unexpected scripts. Input validation and sanitization are also a must, combined with context-sensitive output encoding.

 

Learn more about using Content Security Policy to secure web applications

Can you use filtering to stop XSS attacks?

XSS filtering is never completely effective because attackers have many ways of bypassing WAF rules and getting their XSS payloads to your application. Filtering in the browser or the application itself is also never watertight, requires constant maintenance, and can cause problems with legitimate scripts. While web application firewalls provide some basic XSS filters, they won’t stop more advanced attacks and shouldn’t be relied on as your only line of defense.

 

Learn more about the many possibilities for XSS filter evasion

Does using HttpOnly cookies prevent cross-site scripting?

Setting the HttpOnly flag in cookies is a security measure that makes those cookies inaccessible to client-side scripts but does not actually prevent XSS attacks. Even so, using HttpOnly cookies is a recommended cybersecurity practice to protect session tokens and similar user data from malicious scripts.

 

Learn more about cookie security and security-related cookie flags

Can you prevent cross-site scripting by using a framework like React, Angular, or Vue?

When used correctly, modern JavaScript frameworks can prevent the majority of XSS vulnerabilities by default. In some cases, though, cross-site scripting is still possible in framework-based applications, especially when developers deliberately or unknowingly use some of the available unsafe constructs and feed them unsanitized user inputs.

 

Learn more about cross-site scripting in React web applications

Zbigniew Banach

About the Author

Zbigniew Banach - Technical Content Lead & Managing Editor

Cybersecurity writer and blog managing editor at Invicti Security. Drawing on years of experience with security, software development, content creation, journalism, and technical translation, he does his best to bring web application security and cybersecurity in general to a wider audience.