Cross-Site Scripting (XSS): Understanding XSS vulnerabilities

The ultimate guide in understanding, identifying and exploiting XSS vulnerabilities for higher bounties

The ultimate guide in understanding, identifying and exploiting XSS vulnerabilities for higher bounties

Today's estimated reading time is: 19,7 min

Hi mate! Glad to see you back again reading my newsletter! By the end of this post, you'll have a better understanding of what Cross-Site Scripting (XSS) vulnerabilities are and in what forms they often are or can be found!This week will be all about understanding XSS, next week I will show you how you can identify and exploit them using custom-crafted payloads! Throughout this post, I may give additional information about where & how I found some of the most unique XSS vulnerabilities, don't miss out on this and take notes ;)!

Table of Contents:

  • Cross-Site Scripting (XSS) introduction

  • Types of XSS vulnerabilities

    • Reflected Cross-Site Scripting

    • Stored Cross-Site Scripting

    • DOM-based Cross-Site Scripting

  • Testing Methodology

    • Reflected & Stored XSS

    • DOM-based XSS

  • Additional Resources

Cross-Site Scripting introduction:

Cross-Site Scripting (or XSS in short) is an injection vulnerability that can be present on the client side as well as on the server side.Simply put, XSS vulnerabilities allow attackers to execute client-side javascript code on behalf of their victim (usually, without their knowledge). Or on the server side (think of HTML to PDF generators, or just some code that renders your HTML and JS code on the server side, etc.)And as they can execute javascript in the victim's web browser, they basically can redress the whole UI, perform unwanted actions or in severe cases, get a hold of the victim's session cookies (more on this when we approach the exploitation part).XSS vulnerabilities exist due to bad user input validation. Developers often underestimate what parameters attackers can control. However, sometimes, they just forget about it. This is often the case for example when the application is super complex and has a very large code base (take notes...).Modern web browsers can't fully mitigate this vulnerability as it is hard to determine whether the script that is loaded or code that gets executed comes from a trusted source or not (think of a third-party script that gets injected after the DOM has loaded).However, over the past few years, new browser security headers and features (such as Content Security Policy) started to get introduced and can (partially) mitigate this.The only most reliable mitigation is to validate user input and always encode it wherever possible.One small note:Most make this mistake, but understand that XSS vulnerabilities can not be exploited in a server's redirect (whenever the Location response header is set and a 3XX status code is returned).Say we have the following PHP code:

Then even if we try to inject an XSS payload using a payload, which will give us the following response

The browser will just redirect you to the destination host.

And, as we've seen in the previous post, the response content type also plays a major role in how your browser will handle the server's response. XSS vulnerabilities can only be executed in script-based contexts. Even if your payload got reflected in the response without any encoding, it won't execute in responses with a content type of for example "text/plain", "application/json", "text/javascript", etc. (unless you managed to find a CRLF injection and forced the server to respond with your specified content type, crazy stuff I know, I'll explain this vulnerability type later).It will for example execute in responses with a MIME type of "text/html", "image/svg+xml", "text/xml", etc.

Types of XSS vulnerabilities:

XSS vulnerabilities can be present in various contexts and to further classify them, we further separate them into 3 classes: Reflected XSS, Stored XSS and DOM-based XSS.

Reflected Cross-Site Scripting:

Reflected, Reflective, Non-Persistent or Type-I XSS are XSS vulnerabilities that get executed because invalidated or unencoded user input gets reflected in the server's response whenever the payload is specified.The victim must click on a malicious link that is provided by the attacker before it executes on the victim's browser.It only executes once whenever the malicious link is visited.Sometimes, it is possible to elevate a reflected cross-site scripting vulnerability to a (temporarily) stored cross-site scripting vulnerability through for example getting the server's response get cached.You often find reflected XSS vulnerabilities in search functions (that return the query) or in other input fields that reflect the value in the server response.You should also test hidden input fields and try to use javascript variable names as a query parameter.Do remember that parameters often get re-used by the developer on other routes or endpoints (if you found a reflected XSS on the login page using the "redirectURL" parameter, try to also check for XSS on the register or forgot password page).It is also pretty common that developers read the parameter from the response body and/or URL query (depending on where the parameter got supplied). So make sure to test it for both POST-based as well as normal GET-based XSS.Test everything that accepts user input and reflects it somewhere else.There's also POST-based XSS, where an injection point only reflects POST parameters. It's the same as a reflected XSS except for how you send the payload. Instead of a link with a payload, you send a link to your site with a simple HTML POST form.Auto-submitting the HTML form (when the link is visited) will decrease the attack complexity.One last form the reflected XSS can come in is in the URL path. Simply put, everything in the URL path may get reflected in the page, I've encountered a few times where whenever you request a resource that doesn't exist, it returns a 404 status code along with the path unencoded. This allowed me several times to execute javascript code just make sure your payload is in the right format.

Use parameter brute-forcing tools to discover new or hidden parameters! ParamMiner, Arjun and ParamSpider are perfect tools for discovering hidden parameters!

TIP!

Stored Cross-Site Scripting:

Stored, Persistent or Type II XSS is another type of XSS, it's quite similar to reflected XSS.The only main difference is that it is stored (i.e. saved in a database or somewhere else) and later retrieved again (everything, even the payload).This means that any user who visits a particular page gets the malicious javascript payload executed in his/her browser (without the need of specifying the payload every single time)!By nature, stored XSSs are a high-severity vulnerability as they can cause severe damage to each user who visits the page.And as you might have guessed, stored XSS can be anywhere where data is stored and later retrieved and reflected in the response body!You can think of comment sections, review forms, contact forms, user details (when signing up for example), file uploads (when uploading an HTML or SVG file for example, I'll share the payloads in the next post), chats, (login) logs, online viewable emails, notification system (that is built in to the site, don't confuse this with the Notifications browser API), etc.

DOM-based Cross-Site Scripting:

DOM-based XSS is different from reflective and stored XSS as your input does not get reflected in the source code.DOM-based XSS vulnerabilities arise when unsafe data (user input from a query parameter for example) gets processed in javascript and gets passed without proper validation.There are a few ways where user data can originate from, such as "window.location" object property. Every property that reads user-supplied input is called a DOM source.While each function that processes data in an unsafe manner is called a DOM sink.When user input gets passed from a DOM source to a DOM sink, DOM-based (XSS) vulnerabilities may arise.This may still be a bit vague, I know, but don't worry! Lets take a look at the following code snippet that is vulnerable to a DOM-based XSS vulnerability:

DOM XSS example

User input is read from the "redirect_url" query parameter (from the "location.searchDOM source) and is later passed on to the "location.href" DOM sink. If you send a request to that document with the "redirect_url" query parameter with a payload, you may execute javascript on the user's behalf!There are a lot of sinks that can lead to DOM-based XSS, I recommend going over them one by one in this article from Portswigger Academy.

Be aware that Blind XSS also exists. Blind XSS is hard to detect as you can't see the injection point (as it is often on a higher privileged or internal web dashboard, think of an admin's moderation dashboard where all data of a user gets reflected in the response) and can be any type of XSS. It often is stored XSS but this can also be a blind reflected or blind DOM-based (although these are much much harder to find).That is why it is also advisable to put your payload in other parts of your request such as your User-AgentCookie header, X-Forwarded-For (depending on how and what request headers the application processes), or in the request body (as a parameter) etc. Your request will eventually get logged and displayed on the admin's dashboard.Another trick you can use is to cause an error and have the request contain a blind XSS payload as errors often get logged separately to get resolved.

The best way to validate blind XSS is to send a specially crafted payload that when executed, sends a callback to your end (to validate the blind execution).

XSSHunter.com is a great solution, however, it will soon be depreciated and only the self-hosted version will be available.

TIP!

You might also come across self-XSS in program scopes. Self-XSS can also come in the form of the preceding 3 different types. To put it simply, self-XSS means just what you might think it means, only you can get exploited and affected by it.It often can not be used to harm other users. This is why it is almost always out of scope for most programs out there.You can come across it in various components of a web application such as your private profile or settings, an input field (with no query parameters) where the whole payload needs to be typed first (think of an auto-complete feature or similar features), etc.To save yourself and the company time, do not report self-XSS vulnerabilities. Instead, try to seek impact by chaining multiple vulnerabilities. This may not always be possible, however, the end results are always rewarding (you either learn something new or earn a bounty).

Testing methodology:

If you want to properly test XSS, I recommend going through the following steps. These have worked for me and allowed me to determine whether a specific field is vulnerable or not and have helped me find a lot of XSS vulnerabilities (all types).I usually first try a simple XSS payload just to save myself time but if that does not work, I fall back to these steps.This is how I always find XSS:

XSS Process

Let me explain

Reflected & Stored XSS:

  1. Inject a simple non-malicious HTML tag followed by a unique string or int value (depending on what the website is expecting). The HTML tag can be a u-tag, s-tag or i-tag. In this step, you mainly only want to identify injection points without getting blocked by a web application firewall. If you found a reflection point where your HTML tag got rendered, proceed to the following step.

  2. This step involves you (manually) checking what tags are allowed. To do so, you can open the XSS cheat sheet provided by Portswigger Academy and copy all the HTML tags. Going over them manually can be a tedious task, I recommend using a fuzzer such as your Burpsuite Intruder or any other tool that does the job. Take note of each single allowed tag, this will help you a lot in the following step.

  3. Now it's time to identify any allowed event handler (if the script tag is one of the allowed tags, skip this step, you already got XSS ;)). Usually, what you want to do now instead of directly injecting an event handler is to first determine if the keyword "on" is allowed or not. If it is, add an "x" after the "on" keyword. Keep on adding an "x" character until you reach the length of one of the event handlers. All you do know is to start checking what event handlers of the same length are allowed. You are mainly looking for event handlers that execute code without requiring any additional user input (such as "onload", "onerror", etc.). Note down each allowed event handler.

  4. This step is the final step, this is where you check what payload gets accepted and craft the final payload (including the code that is going to be executed). The code is often an alert popup including the document's domain as 1) it proves code execution and 2) it indicates on what domain it got executed.

If you still couldn't find a payload, I recommend bruteforcing payloads (I never had to reach this step as I always found it after finishing the 4th step).

DOM-based XSS:

For DOM-based XSS the methodology differs as your payload does not get reflected. That's why I recommend reading and analyzing javascript files for DOM sinks and sources and injecting your payload accordingly.

There are tools that help identify DOM sinks & sources! Burpsuite's built-in Chromium browser has DOMInvador. Untrusted Types from @filedescriptor is another great alternative that helped me find a lot of DOM-based XSS vulnerabilities in web applications!

TIP!

Additional Resources:

Thank you for reading this far!

I hope you've enjoyed this post! In the next part, I will go through how you can craft your own custom XSS payloads while avoiding most web app firewalls! Stay tuned!By the way, congratulations on making it this far! You are one of the few who have come this far and you make much more chance to complete this guide and become a bug bounty hunter!

If you have any feedback, please do not hesitate to reach out! You can reply to this email or get in touch via Twitter DM!

Have a nice day and see you in the next post!

You can follow me on Twitter to receive upcoming updates on this newsletter:

Whenever you're ready, I can help you:

  • Get $200 in Digital Ocean credits to set up your Virtual Private Server: