August 7, 2026
12 Real-World XSS Attacks: A Hands-On Walkthrough from WAF Bypass to Type Confusion
When I joined MeshaSec — a startup building the next generation of Dynamic Application Security Testing (DAST) tools — my first assignment…

By Tingrikar Kamal
5 min read
When I joined MeshaSec — a startup building the next generation of Dynamic Application Security Testing (DAST) tools — my first assignment wasn't to write code. It was to think like an attacker.
Before I could build tools that find vulnerabilities automatically, I needed to understand how manual exploitation actually works. So I was put through the WriteupDB Advanced XSS Lab — a hands-on platform with 12 real-world XSS scenarios covering everything from WAF evasion to prototype pollution. This blog documents my journey through each level, the exact payloads, and the security lessons every developer should know.
In this post, I'll walk through 12 real-world XSS scenarios I exploited in the WriteupDB Advanced XSS Lab during my training at MeshaSec.
Cross-Site Scripting (XSS) remains one of the most prevalent and dangerous vulnerabilities in modern web applications. According to OWASP, XSS has consistently ranked among the top security risks, enabling attackers to steal session cookies, perform unauthorized actions, and compromise user accounts.
While most developers understand basic XSS concepts like <script>alert(1)</script>, the real world demands much more sophisticated exploitation techniques. Modern applications deploy Web Application Firewalls (WAFs), Content Security Policies (CSP), and strict input validation — making simple payloads obsolete.
Recently, I had the opportunity to work through the Advanced XSS Lab on (xss-lab.writeup-db.com) — a hands-on platform featuring 12 progressively challenging levels, each designed to test a unique XSS vector. In this blog, I'll walk you through every level, sharing the exact payloads, exploitation steps, and key takeaways.
Level 1: WAF Evasion via Case Insensitivity
The Challenge: Bypass a server-side regex filter that removes <script> and onerror=.
The Payload:
<svg onload=alert(1)><svg onload=alert(1)>How It Works: The <svg> tag with an onload event handler executes JavaScript as soon as the SVG element loads in the DOM — completely bypassing both regex filters.
Mitigation: Use a proper HTML sanitization library like DOMPurify instead of custom regex. Validate against an allowlist, not a blocklist.
Level 2: CSP Bypass via Trusted JSONP Endpoint
The Challenge: A strict CSP header blocks all inline scripts. Find a way to execute JavaScript.
The Vulnerability: The CSP only allows scripts from the same origin. However, the application exposes a JSONP endpoint that reflects the callback parameter without sanitization:
The Payload:
<script src="/level2/api/jsonp?callback=alert(1)//"></script><script src="/level2/api/jsonp?callback=alert(1)//"></script>How It Works: The browser treats the JSONP response as a legitimate script from 'self'. The callback parameter injects alert(1) before the JSON data, and // comments out the rest.
Mitigation: Validate JSONP callbacks against a strict allowlist (e.g., alphanumeric only). Avoid JSONP entirely — use CORS-enabled APIs instead.
Level 3: Multi-Level DOM Clobbering
The Challenge: Overwrite window.appConfig.scriptUrl without executing JavaScript directly.
The Vulnerability: The application loads a script using a configuration object. When JavaScript accesses window.appConfig, the browser first checks for DOM elements with id="appConfig". By injecting two anchor tags, we create an HTMLCollection where the second element is accessible via its name attribute.
The Payload:
<a id="appConfig"></a>
<a id="appConfig" name="scriptUrl" href="data:text/javascript,alert(1)"></a><a id="appConfig"></a>
<a id="appConfig" name="scriptUrl" href="data:text/javascript,alert(1)"></a>How It Works: The browser creates window.appConfig as an HTMLCollection. When window.appConfig.scriptUrl is accessed, it resolves to the second anchor's href, which the browser coerces into a string (data:text/javascript,alert(1)).
Mitigation: Avoid using DOM elements as configuration sources. Use data-* attributes or secure configuration APIs.
Level 4: Insecure postMessage Handling
The Challenge: Exploit cross-origin messaging to inject HTML into a vulnerable iframe.
The Payload:
<iframe src="https://xss-lab.writeup-db.com/level4" id="target"></iframe>
<script>
setTimeout(() => {
document.getElementById('target').contentWindow.postMessage(
{ type: 'updateText', html: '<img src=x onerror=alert(1)>' },
'*'
);
}, 1000);
</script><iframe src="https://xss-lab.writeup-db.com/level4" id="target"></iframe>
<script>
setTimeout(() => {
document.getElementById('target').contentWindow.postMessage(
{ type: 'updateText', html: '<img src=x onerror=alert(1)>' },
'*'
);
}, 1000);
</script>How It Works: Any origin can send a message to the iframe. The malicious message injects an image with an onerror handler directly into the DOM via innerHTML.
Mitigation: Always validate e.origin against a strict allowlist. Use textContent instead of innerHTML for untrusted data.
Level 5: Prototype Pollution
The Challenge: Inject a JSON payload that pollutes the global Object.prototype.
The Vulnerability: A recursive merge() function fails to sanitize the __proto__ key.
The Payload:
{"__proto__": {"template": "<img src=x onerror=alert(1)>"}}{"__proto__": {"template": "<img src=x onerror=alert(1)>"}}How It Works: The merge() function recursively assigns properties to __proto__, polluting the global Object.prototype. When the app later accesses config.template, it falls back to the polluted prototype and executes the XSS payload.
Mitigation: Block __proto__, constructor, and prototype keys in all object merging operations. Use libraries like lodash with merge configured safely.
Level 6: Angular Client-Side Template Injection (CSTI)
The Challenge: Break out of the AngularJS 1.5.8 expression sandbox.
The Vulnerability: The page imports a vulnerable AngularJS version and uses ng-app Anything inside {{ }} is evaluated as an Angular expression.
The Payload:
{{x = {'y':''.constructor.prototype}; x['y'].charAt=[].join; $eval('x=alert(1)');}}{{x = {'y':''.constructor.prototype}; x['y'].charAt=[].join; $eval('x=alert(1)');}}How It Works: This payload manipulates the String prototype to escape Angular's expression sandbox, granting access to raw JavaScript execution via $eval().
Mitigation: Upgrade from AngularJS (1.x) to Angular (2+). Modern Angular does not evaluate expressions in templates. If stuck on 1.x, use $compileProvider to restrict expression evaluation.
Level 7: URI Scheme Bypass via Whitespace Stripping
The Challenge: The application blocks javascript: links. Find a bypass.
The Payload:
?payload=%09javascript:alert(1)?payload=%09javascript:alert(1)How It Works: %09 is a URL-encoded tab character. The startsWith('javascript:') check fails because the string begins with a tab. However, the browser's URL parser strips leading whitespace before navigation, effectively executing javascript:alert(1).
Mitigation: Use the URL() constructor to parse and validate the protocol properly.
Level 8: Script Context Breakout
The Challenge: Escape from a <script> tag despite JSON encoding.
The Payload:
</script><script>alert(1)</script></script><script>alert(1)</script>How It Works: The HTML parser processes the page before the JavaScript engine runs. When it encounters </script>, it closes the current script block. The subsequent <script>alert(1)</script> is parsed as a new, attacker-controlled script.
Mitigation: Never interpolate user input inside <script> tags. Pass data via data-* attributes and parse them with JSON.parse() in a separate script block.
Level 9: DOM Smuggling via window.name
The Challenge: Exploit window.name persistence across domains.
The Payload:
<script>
window.name = "theme_<img src=x onerror=alert(1)>";
window.location = "https://xss-lab.writeup-db.com/level9";
</script><script>
window.name = "theme_<img src=x onerror=alert(1)>";
window.location = "https://xss-lab.writeup-db.com/level9";
</script>How It Works: window.name persists across different origins in the same browser tab. The attacker sets a malicious window.name on their domain, then redirects to the victim site, which reads and renders the payload via innerHTML.
Mitigation: Never render window.name directly into the DOM. Treat it as untrusted data and sanitize it before use.
Level 10: Naive Markdown Parser
The Challenge: Abuse a custom Markdown link regex to execute JavaScript.
The Payload:
[Click Me](javascript:alert(1))[Click Me](javascript:alert(1))How It Works: The javascript: pseudo-protocol is injected directly into the href attribute. When the user clicks the rendered link, the browser executes the JavaScript.
Mitigation: Parse Markdown with a robust library like marked.js and sanitize the output with DOMPurify. Always whitelist allowed URL schemes.
Level 11: LocalStorage Poisoning
The Challenge: Achieve persistent XSS using the browser's Local Storage.
Step 1 — Inject the Payload:
?save_banner=<img src=x onerror=alert(1)>?save_banner=<img src=x onerror=alert(1)>Step 2 — Trigger Persistence: Refresh the page or visit /level11 without parameters.
How It Works: The payload is saved to LocalStorage in Step 1. On every subsequent visit, the page reads from LocalStorage and renders it via innerHTML — creating a persistent XSS that survives page reloads.
Mitigation: Never render LocalStorage data directly. Use textContent for display purposes. If HTML is required, sanitize it with DOMPurify first.
Level 12: Express Type Confusion
The Challenge: Bypass WAF sanitization by sending an Array instead of a String.
The Payload:
?payload[]=<script>alert(1)</script>?payload[]=<script>alert(1)</script>How It Works: Express.js parses duplicate or bracketed URL parameters into an Array. The typeof payload === 'string' check fails, so sanitization is skipped entirely. Express then concatenates the array directly into the response.
Mitigation: Always validate input types before processing. Use schema validation libraries like Joi or Zod to enforce expected data types.
A huge thank you to @touhidshaikh22 for the incredible WriteupDB XSS Lab environment.
If you are interested in automated vulnerability scanning, check out what we are building at MeshaSec or Connect with me on Linkedin.
(Disclaimer: All techniques documented here were performed on intentionally vulnerable lab environments (xss-lab.writeup-db.com) for educational purposes only. Never test on systems you do not own or have explicit permission to test.)