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.
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.
Follow these best practices to prevent the vast majority of cross-site scripting attacks:
(Note that filtering is deliberately not listed here—read on to learn why you can’t trust XSS filtering.)
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:
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.
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.
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.
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.
Â