Cross-Site Scripting (XSS) stands as a prevalent and dangerous vulnerability in web applications. It occurs when a malicious actor can inject client-side scripts (typically JavaScript) into web pages viewed by other users. This vulnerability is fundamentally an exception to the browser’s Same-Origin Policy (SOP), which is a crucial security mechanism adopted by every modern browser.
The Same-Origin Policy and the XSS Vector
The Same-Origin Policy dictates that a document or script loaded from one origin (like example.com) cannot interact with resources from another origin (like anothersite.com). This rule is in place to prevent malicious documents from reading or writing sensitive data from other sites.
However, for a modern, functional webpage, exceptions must exist to allow the loading of third-party assets—such as external JavaScript, Cascading Style Sheets (CSS), or images—and this necessity is the vector through which XSS attacks occur.
When a browser processes the src attribute on an HTML tag (like ), it is designed to execute the code that attribute points to. Crucially, the code doesn’t have to be a separate file; it can simply be code included directly in the attribute string. Furthermore, it is not just the src attribute that can execute JavaScript; many other attributes and HTML tags are susceptible.
Demonstrating an XSS Snippet
A classic method for testing for XSS is to use an event handler that triggers a simple JavaScript function, like alert(). This is often referred to as an XSS canary (an initial test payload to confirm a vulnerability exists).
The following is a common example of an XSS testing snippet, utilizing the onmouseover attribute:
snippet text
If this payload successfully executes, hovering over the snippet text will trigger a JavaScript alert() box. The inclusion of document.location within the alert is a simple, effective way of immediately referencing the exact URL where the XSS is taking place, making it easy to document the vulnerability.
Many Varieties of XSS
XSS attacks can be broadly categorized in two distinct frameworks, which help in understanding how the malicious script is delivered and executed.
I. Categorization by Delivery Mechanism
The traditional method of grouping XSS varieties is based on how the malicious payload is delivered to and processed by the browser.
Stored or Persistent XSS
This is considered the most dangerous type of XSS. It occurs when the malicious script is permanently stored on the target server (e.g., in a web application’s database, file system, or other data storage) via a user input, such as a comment, forum post, or profile field.
In the example above, the tag with the malicious JavaScript would be inserted via a form input and then stored in the web app’s database. When any user subsequently views that page, the stored, malicious HTML element is retrieved and rendered by their browser. When that user then performs the necessary action—such as hovering over the element—the onmouseover event triggers the execution of the XSS code.
Reflected XSS
In this scenario, the injected script is reflected off of the target server. This happens when a user’s input, containing the malicious payload, is immediately included and displayed on a response page, such as a search results page, an error message, or another message that incorporates the user’s data.
The payload is not permanently stored. Instead, an attacker typically tricks a user into clicking a specially crafted link (e.g., in an email or message) containing the XSS payload in the URL’s query parameters. The server receives the request, includes the payload in the response (reflecting it), and the victim’s browser then executes the script. Reflected XSS can be very damaging because it leverages the trust of the server the code is being reflected from, making the script appear legitimate.
DOM-based XSS
This is a more specialized and modern type of attack. DOM-based XSS (Document Object Model-based XSS) occurs entirely on the client side. It relies on a client-side script in the legitimate page handling user-supplied data (often from a URL fragment or query string) in an unsafe manner.
The attack relies on a user being supplied an attacker-generated link containing an XSS payload. When the user’s browser opens the link, the legitimate page’s code constructs the DOM (the structural representation of the page) and executes the code based on the attacker’s input, effectively echoing back the payload and executing the malicious script. The key distinction is that the server often does not even see the payload.
II. Categorization by Execution Location
Another way of grouping XSS is by dividing the bug into client XSS and server XSS.
- Server XSS: This occurs when unverified user data is supplied by the server. This includes both reflected XSS (data supplied through a request) and stored XSS (data supplied from stored locations). The server is responsible for including the malicious data in the page content sent to the client.
- Client XSS: This refers to the execution of unverified code in the client (the browser), from the same locations (stored or reflected). This category often encompasses DOM-based XSS, as the vulnerability resides in the client-side code’s insecure handling of data.
Understanding these categorizations is essential, as the detection and mitigation techniques can be specific to each variety.
Testing for XSS – Where to Find It, How to Verify It
Discovering XSS vulnerabilities requires systematic testing. The process often involves leveraging specialized security tools to automate or semi-automate the injection of test payloads.
Burp Suite and XSS Validator
One problem with automated and semi-automated solutions for XSS is distinguishing signal from noise—determining which injected payloads actually resulted in successful script execution, and which were simply filtered or rendered inert.
A useful Burp Suite Professional plugin, XSS Validator, helps address this. It runs a PhantomJS-powered web server to receive the results of Burp queries. The extension works by injecting snippets containing an alert() call with a unique, injected string (a canary). The PhantomJS server then monitors network traffic and looks for this unique string, providing a clean way of culling the results of XSS submissions down to absolute confirmed vulnerabilities.
Installation and Setup
- Installing the Burp Extension: The easiest way to download the XSS Validator Burp extension is through the Bapp store (Burp App Store). This is accessible by navigating to the Extension tab within Burp Suite and selecting the extension from the marketplace. (It is free.) It is also possible to install the extension manually by following the instructions in the XSS Validator GitHub documentation.
- Running the Server: During the actual testing, one must run the server that parses incoming Burp requests. If one clones the XSS Validator git repo (repository), navigation to the
xss-validatordirectory allows the starting of thexss.jsscript. The server can then be bootstrapped and set to run as a detached background process using a simple command line:phantomjs xss.js &This command executes the PhantomJS (a headless WebKit scriptable with JavaScript) utility, running thexss.jsscript in the background.
Executing the XSS Attack with Burp Intruder
With the XSS Validator server and Burp Suite running, the next step is to test a specific form input for XSS susceptibility. This process can be demonstrated on a proven testing ground, like the Web Scanner Test Site (webscantest.com), which has been designed to be susceptible to XSS.
- Initial Submission: Arrive on the target page with the Burp Proxy Intercept feature initially turned off to navigate without manual forwarding of traffic. Enter something easily recognizable into the form fields being tested.
- Intercepting the Request: Navigate back to the Burp Suite GUI and turn the Intercept feature on before submitting the form. Upon submission, the browser favicon may indicate a submission without any visible change on the form. Returning to Burp, the form’s POST request will have been intercepted (note: be prepared to forward other intercepted requests if multiple tabs are open).
- Sending to Intruder: Right-click on the intercepted request and select Send to Intruder. This feature allows for the automated manipulation and submission of the request data.
- Configuring Payloads: Within the Intruder window, navigate to the Positions tab. The POST request parameters and cookie IDs will likely already be selected as Payload Positions. These defaults can be maintained. The critical changes are made in the Payloads tab to integrate with the XSS Validator extension:
- Payload Sets: For the second drop-down, Payload Type, select the Extension-generated option.
- Payload Options: Click Select generator…. This will open a modal where XSS Validator Payloads must be chosen as the selected generator.
- Payload Processing: Add a new rule here. Choose Invoke Burp extension as the rule type, and then select XSS Validator as the processor.
- Setting the Grep Phrase: Head over to the xssValidator tab. A random string is generated in the Grep Phrase field. This phrase acts as the success indicator. The key is to add this grep phrase into the Grep – Match section, located in the Options tab of the Intruder configuration. This ensures that when the attack results are viewed, a checkbox will indicate whether the phrase turned up in an attack response, signifying a confirmed vulnerability.
- Starting the Attack: Once all configurations are complete, click the start attack button. An attack window will pop up and begin to self-populate with the results of the multiple XSS snippet submissions. A successful test is evidenced by the presence of the grep phrase for several of the tag/attribute combinations generated by the XSS Validator, meaning the submissions were successful.
XSS – An End-To-End Example (Google Gruyere)
To provide an example of a vulnerability in a real-world scenario, the process can be demonstrated on a deliberately-vulnerable teaching site.
XSS in Google Gruyere
The following example takes place on Google Gruyere, an XSS laboratory operated by Google that explains different aspects of XSS alongside appropriately vulnerable form input. Google Gruyere is based loosely on a social network where users can share public snippets.
Beyond the explicit advertising of the service as being susceptible to XSS, there are often small pieces of text, similar to what is found in real applications, hinting at areas of vulnerability. Limited support for HTML in a specific form is always a potential risk, as it suggests the filters put in place to allow formatting markup (such as , , and ) may fail to sanitize (clean or filter out dangerous code) a specially-crafted snippet, such as a script tag.
Probing and Testing
- Initial Attempt (Slow Pitch): After setting up an account and navigating to the submission form to create a New Snippet, an attempt is made to probe the sanitizing process using a simple, un-obfuscated script that even the most naive filter should capture:
- Result of Initial Attempt: When the result of this submission is viewed, no
alert()window is displayed, and there is nothing to trigger the execution of the code. The filter has functioned at the most basic level by stripping out thetags. - Second Attempt (Trojan-Horse): To bypass this basic filter, a snippet is chosen from XSS snippet lists in repositories like Seclists. The chosen payload ensures the HTML tag is one that is likely to be included in a form input meant to allow formatting code:
xxs linkThe payload usesdocument.cookieas a simple piece of data to surface viaalert(), providing a glimpse of a potential attack scenario. - Result of Second Attempt (Success): Going through the submission process again yields a different response: Success! The strategy of using a common, formatting-based tag (
) to Trojan-horse a malicious payload contained in its attribute (onmouseover) worked. This confirms a vulnerability.
Gathering Report Information
Once a vulnerability has been discovered, it is crucial to document all necessary and useful information for a comprehensive report. This information will be vital across various submission platforms and styles (e.g., Bug Bounty programs).
Category
This is the classification of the bug. In the example of Google Gruyere, the bug is Persistent / Stored XSS.
Timestamps
If using an automated or code-based solution, taking accurate timestamps is a necessity. If the malicious snippet was manually entered, simply recording the time after the discovery is sufficient. Giving the time of discovery in UTC (Coordinated Universal Time) will save the developer fielding the report from having to perform a mental timezone conversion before analyzing logs, usage charts, and other monitoring tools.
URL
This is the specific URL where the vulnerability exists. When executing test code such as alert(), it can be useful to include a location reference (e.g., alert(document.location)). This way, a single screenshot can convey both preliminary proof of the bug and its precise location in the application.
Payload
The XSS snippet used to successfully execute JavaScript must be included here. In the case of other payload-based attacks (like SQLi or a successful password attack), that data would be required as well. If multiple payload types illustrate the general sanitation rules being misapplied, all should be mentioned.
Example Payload: xxs link
Methodology
The method used to discover the bug must be stated clearly. If a particular tool was used, it should be named (unless the bug bounty program explicitly prohibited the use of scanners). This information can help the team fielding the report validate the finding. In the Gruyere example, the methodology would be: XSS payload submitted manually.
It is also useful to list basic info about the discovery environment: the operating system, browser type and version (plus any relevant add-ons or extensions), and any miscellaneous information considered relevant (e.g., was it discovered in an incognito window? Were any particular headers used if using a tool like curl or Postman?).
Instructions to Reproduce
Making the instructions clear enough for the person evaluating the report is, along with the actual payload, the most important information provided. A screenshot of the vulnerability (e.g., the alert() window) is great evidence, but could easily fall short of winning a payout if the issue cannot be reproduced.
Attack Scenario
Coming up with a high-impact attack scenario is not as necessary as the previous data points, but it can be a great method for increasing the bug’s severity and boosting the payout.
For the Persistent XSS attack found in Gruyere, the severity is highlighted by detailing the extent of the damage beyond just the Gruyere application. If an attacker could execute arbitrary JavaScript from a stored XSS bug, they could exfiltrate sensitive cookies, such as those for authenticating financial apps (banks, brokers, and crypto traders) or social networks (Twitter, Facebook, Instagram). This stolen data could then be used for identity theft, credit card fraud, and other cyber crimes.
Sample Submission Report
| Field | Detail |
|---|---|
| CATEGORY | Persistent / Stored XSS |
| TIME | 1:12 AM (1:12) UTC |
| URL | https://google-gruyere.appspot.com/09809809887686765654654/newsnippet.gtl |
| PAYLOAD | xxs link |
| METHODOLOGY | XSS payload submitted manually |
| INSTRUCTIONS TO REPRODUCE | 1. Navigate to “New Snippet” submission page. 2. Enter the XSS payload into the “New Snippet” form. 3. Click “Submit” and create a new snippet. 4. The malicious XSS contained in the payload is executed whenever someone hovers over the snippet with that link. |
| ATTACK SCENARIO | With a persistent XSS vulnerability to exploit, a malicious actor could exfiltrate sensitive cookies to steal the identity of Gruyere’s users, impersonating them both in the app and in whatever other accounts they are logged into at the time of the XSS script’s execution. |
Summary of XSS Concepts
This article covered the different types of XSS attacks, establishing an understanding of the anatomy of an XSS snippet, and demonstrating how to extend Burp Suite with XSS Validator to confirm successful injection attempts. The usage of Google Gruyere as a teaching aide and testing ground was examined, and the steps for reporting an XSS vulnerability from start to finish—including how to document the report and a sample submission—were provided.
Frequently Asked Questions (Q\&A)
- What are the different principle types of XSS?
The principle types are Stored (Persistent) XSS, Reflected XSS, and DOM-based XSS. Another framework groups them as Server XSS and Client XSS. - Which XSS varieties are most dangerous/impactful?
Stored (Persistent) XSS is generally considered the most dangerous because the malicious payload is permanently embedded in the application, affecting all users who view the compromised page without requiring them to click a special link. - What’s the value of XSS Validator as an extension?
XSS Validator provides a crucial function by running a dedicated server to receive the results of Burp queries. It uses a grep phrase within analert()call to filter through noise and provide a clean, definitive confirmation of a successful XSS vulnerability. - What does the phantomjs server do?
The PhantomJS-powered web server receives the results of XSS snippets submitted through Burp. It executes the injected code and checks for the unique grep phrase (the canary string) that was included in the XSS payload’salert()call, confirming a successful injection attempt. - How do you select payloads for fuzzing (automated testing) in Burp Intruder?
To use an extension like XSS Validator, one must first send the intercepted request to Burp Intruder. In the Payloads tab, select Extension-generated as the Payload Type, and then choose the XSS Validator Payloads as the generator. - What are the most important things to include about XSS in your submission report?
The most important information is the URL of the vulnerability, the exact Payload used to successfully execute the script, and clear, reproducible Instructions to Reproduce the issue. - What’s a worst-case attack scenario for a hacker who’s found an XSS bug to exploit?
A worst-case scenario involves a malicious actor exploiting a persistent XSS bug to execute arbitrary JavaScript to exfiltrate sensitive session cookies (e.g., authentication tokens) for financial or social media accounts. This can lead to the attacker stealing the identity of the user and performing cyber crimes like fraud or unauthorized access. - Why is including an attack scenario in your report submission important?
Including an attack scenario is important because it highlights the potential business impact and severity of the vulnerability, which is a key factor in how the security team prioritizes the fix and determines any potential bug bounty payout.
Further Reading
To deepen your understanding of these topics, explore the following resources:
- XSS Filter Evasion Cheat Sheet:
https://cheatsheetseries.owasp.org/cheatsheets/XSS_Filter_Evasion_Cheat_Sheet.html - XSS Challenges:
https://xss-quiz.int21h.jp/ - XSS Game:
https://xss-game.appspot.com/