Cross-Site Scripting (XSS) is a type of security vulnerability typically found in web applications. It allows attackers to inject malicious scripts into content from otherwise trusted websites. These scripts can be executed in the context of the user’s browser, leading to potential consequences like stealing cookies, session tokens, or other sensitive information, defacing websites, or redirecting users to malicious sites.
XSS attacks exploit the trust that users have in a particular website. They are classified into three main types:
- Stored XSS (Persistent XSS): The malicious script is permanently stored on the target server, such as in a database, comment field, or forum post. When a user retrieves the stored information, the script is delivered to their browser and executed.
- Reflected XSS (Non-Persistent XSS): The malicious script is reflected off a web server, such as in an error message or search result. The script is then sent to the victim’s browser, where it is executed. This type of XSS is often delivered through phishing emails or other social engineering techniques.
- DOM-based XSS: This occurs when the vulnerability exists in the client-side code rather than the server-side code. The malicious script is executed as a result of modifying the DOM environment in the victim’s browser.
Basically with XSS the thing we want to find is an input we can control and somehow will be inserted somewhere on the page, with this we can insert some malicious code that will then be executed in some part of the page.
PortSwigger’s XSS Labs
PortSwigger, the creators of the popular web vulnerability scanner Burp Suite, offer a comprehensive range of labs specifically designed to help security enthusiasts and professionals understand and mitigate XSS vulnerabilities. These labs cover various aspects of XSS, from basic to advanced levels, and provide hands-on experience in a controlled and educational environment.
In this blog post, we will guide you through the various XSS labs offered by PortSwigger, providing an overview of each lab, tips for solving them, and key takeaways to enhance your web security expertise. Let’s dive in and explore the fascinating world of Cross-Site Scripting through the lens of PortSwigger’s interactive labs.
Reflected XSS into HTML context with nothing encoded
Lab Access: https://portswigger.net/web-security/cross-site-scripting/reflected/lab-html-context-nothing-encoded
In this lab we have a simple XSS case, here your input is unsanitized and directly placed on the website code, so to complete the lab we just need to pop an alert, to do this we can use this simple payload: <script>alert(1);</script>
:
Stored XSS into HTML context with nothing encoded
Lab Access: https://portswigger.net/web-security/cross-site-scripting/stored/lab-html-context-nothing-encoded
In this lab we have a stored XSS, this means we can insert some malicious code in some portion of the site where it will be stores in a database, and then replicated when any user accesses the page where we left the malicious code. In this case we need to access any post on the blog and try to place the Javascript alert on a comment, that way the code will be stored in a database, and then replicated when the comment is loaded in the page:
Again, we need to use a simple payload to finish the lab: <script>alert(1);</script>
DOM XSS in document.write
sink using source location.search
Lab Access: https://portswigger.net/web-security/cross-site-scripting/dom-based/lab-document-write-sink
Here we can just place a random string on the search bar and check where does this string appears on the page, in that case we can find it on a path to a img
HTML tag:
That way we can use a payload that closes the tag and start a new one, in this case we can open an svg
tag and use the onload
parameter to make your XSS, like the following payload "><svg onload=alert(1)>
.
DOM XSS in innerHTML
sink using source location.search
Lab Access: https://portswigger.net/web-security/cross-site-scripting/dom-based/lab-innerhtml-sink
In this lab, we can use a similar payload as the previous one, <img src=1 onerror=alert(1)>
, we can insert this one on the search bar or on the parameter inside the URL, since this is a DOM XSS.
DOM XSS in jQuery anchor href
attribute sink using location.search
source
Lab Access: https://portswigger.net/web-security/cross-site-scripting/dom-based/lab-jquery-href-attribute-sink
In this lab on the submit feedback page we can see a parameter on the URL called returnPath
, this parameter is used on the back link on this same page, that means, if this parameter is being reflected to the page we can insert some JavaScript there and complete this lab.
Here we can use this simple payload javascript:alert(document.cookie)
, and place this on the returnPath
parameter on the URL, once we refresh the page, the back link on the page have our code.
DOM XSS in jQuery selector sink using a hashchange event
Lab Access: https://portswigger.net/web-security/cross-site-scripting/dom-based/lab-jquery-selector-hash-change-event
Here we need to first understand what is a hashchange event, using the tips provided we can find this piece of code in the page source:
<script>
$(window).on('hashchange', function(){
var post = $('section.blog-list h2:contains(' + decodeURIComponent(window.location.hash.slice(1)) + ')');
if (post) post.get(0).scrollIntoView();
});
</script>
We have here a JavaScript code that gets executed when the hashchange event is fired.
The
hashchange
event is fired when the fragment identifier of the URL has changed (the part of the URL beginning with and following the#
symbol).
https://developer.mozilla.org/en-US/docs/Web/API/Window/hashchange_event
Basically when we insert a #
followed by a blog title on the page, this function will scroll the view to the post, we can check this by inserting a #BLOG-POST-TITLE
, in my case I have a post called “Finding Inspiration”, so I can try this link: https://0a89004e03d4e96182c70b2700ff0027.web-security-academy.net/#Finding%20Inspiration
, when we press enter the page will be automatically scrolled to the post.
This is being accomplished by storing an JQuery post search on the variable post, but there is a problem, the logic below that query is checking if the post is true to then use the scrollIntoView()
function, but, we can manually check what happens when we insert manually an HTML tag on query. For that we can use this payload, #<img src=0 onerror=alert()>
, we can see that the alert()
was executed, that means that the <img>
was inserted, but this was place on a JQuery, how exactlly was this inserted on the page?
That happened because this is a vulnerable JQuery version, when we pass an object that doesn’t exist on the page, instead of returning nothing the query is returning the object we passed to it. But this is not enough, this code is only going to be executed on a hashchange event, that means we need a way to craft an exploit the will set the hash to something, and then reload the page to include our payload.
We can do this by using a <iframe>
element:
<iframe src="https://0ad900ff04b36a2781117a22006a006e.web-security-academy.net/#" onload="this.src+='<img src=1 onerror=print()>'"></iframe>
We can use this on the exploit server to deliver this exploit to the victim, basically this will simulate an “external” server that will have our iframe
, once the page is loaded we will add our payload with the onload
functionality of the iframe
.
Reflected XSS into attribute with angle brackets HTML-encoded
Lab Access: https://portswigger.net/web-security/cross-site-scripting/contexts/lab-attribute-angle-brackets-html-encoded
In this lab we can see a search bar on the home page, immediately we can check if the input of that bar ins being reflected somewhere on the page source, in this case, on a form
tag we can see our input on a value
attribute of the input
tag:
<form action=[/](view-source:https://0aca00b70451c670803f71d000c10053.web-security-academy.net/) method=GET>
<input type=text placeholder='Search the blog...' name=search value="test">
<button type=submit class=button>Search</button>
</form>
Our input is being placed inside double quotation marks on the code, that means we can create a XSS payload to escape those quotations marks as follows: "onmouseover="alert(1)
. In this lab we need to make more than just finding one payload that works, in this case we can use other payloads to trigger the lab completion, like:
test" onmouseover="alert(1)
: This will make thevalue
attribute not empty, therefore, the actual payload will not be shown in the search bar.test" onclick="alert(1)
: This will make thevalue
attribute not empty, and thealert()
function will be triggered on a click on the search bar.
There are a lot of HTML events we can use to trigger the XSS in this case.
Stored XSS into anchor href
attribute with double quotes HTML-encoded
Lab Access: https://portswigger.net/web-security/cross-site-scripting/contexts/lab-href-attribute-double-quotes-html-encoded
Here in this lab, our input is being reflected on a href
attribute on the page source code, on the comment section. We can try to input a comment and see how the site will handle it, down below we can see an example of a comment:
<section class="comment">
<p>
<img src="/resources/images/avatarDefault.svg" class="avatar">
<a id="author" href="site.com">name1234</a>
| 17 June 2024
</p>
<p>comment1234</p>
<p></p>
</section>
With this we can use this payload: javascript:alert()
on the Website input, once we click on the comment the alert()
function will be executed
Reflected XSS into a JavaScript string with angle brackets HTML encoded
Lab Access: https://portswigger.net/web-security/cross-site-scripting/contexts/lab-javascript-string-angle-brackets-html-encoded
As the other labs, we can start by placing a random string on the input and analyzing where the site will place it, in this case we can find our string (in this case it is test1234
) on an inline script:
<script>
var searchTerms = 'test1234';
qdocument.write('<img src="/resources/images/tracker.gif?searchTerms='+encodeURIComponent(searchTerms)+'">');
</script>
Here our string is placed on a JavaScript variable, so, we can craft a payload to exit out of this variable and execute an alert()
function. To do this, we need to understand that the payload will be place as follows: var searchTerms = 'PAYLOAD';
. So, we first need to use single quote to get out of the string declaration, than we use a semicolon to pass another JavaScript function, like alert()
than we can create another empty variable to use to finish the closing single quote. That will give us a payload that looks like this: test1234'; alert(); var str ='
, that way we will have a script section that will look like this:
<script>
var searchTerms = 'test1234'; alert(); var str ='';
qdocument.write('<img src="/resources/images/tracker.gif?searchTerms='+encodeURIComponent(searchTerms)+'">');
</script>
That will break out of the original variable, then execute another function and then create another variable just to use the last single quote.
DOM XSS in document.write
sink using source location.search
inside a select element
Lab Access: https://portswigger.net/web-security/cross-site-scripting/dom-based/lab-document-write-sink-inside-select-element
Inside this lab we can find an interesting script section within the code:
<form id="stockCheckForm" action="/product/stock" method="POST">
<input required type="hidden" name="productId" value="3">
<script>
var stores = ["London","Paris","Milan"];
var store = (new URLSearchParams(window.location.search)).get('storeId');
document.write('<select name="storeId">');
if(store) {
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>');
</script>
<button type="submit" class="button">Check stock</button>
</form>
This code is responsible to add the options to the select HTML tag, but there is a bonus feature, on the top of the code we can see the declaration of the variable store
this variable is looking to a URL parameter called storeId
, this parameter is not present at the moment we enter on the page, but, just looking on the code we can predict what will happen when we place it. As we can see, if the storeId
parameter is available, it will be added directly added to a document.write()
function. This means we can use this to insert an arbitrary piece of code to the page using a parameter on the DOM.
So to start our payload we need to add another parameter to the URL, in this case the storeId
parameter, to do this we can simply write &storeId=
, then we will take advantage of the document.write() function, in this case we will first close the select tag with a </select>
and the last part we can add a standard <img>
tag with an invalid source to trigger an error. We will end up with a payload that looks like this: &storeId=</select><img src=1 onerror=alert(1)>
.
DOM XSS in AngularJS expression with angle brackets and double quotes HTML-encoded
Lab Access: https://portswigger.net/web-security/cross-site-scripting/dom-based/lab-angularjs-expression
The description of this lab, as well as the head portion of the HTML code in the page, tells us that the page is using AngularJS.
<head>
<link href=/resources/labheader/css/academyLabHeader.css rel=stylesheet>
<link href=/resources/css/labsBlog.css rel=stylesheet>
<script type="text/javascript" src="/resources/js/angular_1-7-7.js"></script>
<title>DOM XSS in AngularJS expression with angle brackets and double quotes HTML-encoded</title>
</head>
AngularJS is a JavaScript framework used on web development. Usually, in this kind of scenario, we can use double curly brackets to insert JavaScript code, so, we can try a basic payload and see if it works. We can try {{alert()}}
, but this will not work by default, I’m no AngularJS expert, but, I guess this doesn’t work because of SCE.
Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain contexts to require a value that is marked as safe to use for that context.
https://docs.angularjs.org/guide/security
So we will need to use some kind o “evasion” to make this work. A very popular technique to dealing with those cases is using a constructor. A constructor is used to create functions, in this case we can also create and call those functions dynamically. So, the payload we can use to execute an alert()
here is {{constructor.constructor('alert(1)')()}}
, with this payload we are creating a new function that simply executes the alert() function from JavaScript, after that we call that just create function (with the pretenses on the end). This payload and other similar ones can be found on this GitHub repository.
A more detailed explanation of this payload can be found on this video: https://www.youtube.com/watch?v=QpQp2JLn6JA. In this video, z3nsh3ll will recreate from scratch the command we used and explain in more details how and why it works. But in other terms, this XSS is similar to template injection like in Jinja, of course, the difference would be the core programming language, but the process is very similar. Another thing we can use to test if this type of payload will work is by using a simple math operation like {{1+1}}
, if the result of the operation is returned to the page this will be an indicator that there is a payload that might work.