Cross-Site Scripting (XSS)
Prerequisites¶
- Local INSECURE-threat-intel-app stack running; signed in at http://localhost:8081.
- Burp Suite proxy configured (Burp basics) — default listener 8080 does not conflict with the lab app on 8081.
Likeliness meter¶
High — XSS appears wherever user content is rendered without encoding (comments, profiles, search reflections, SPA sinks).
Overview¶
Cross-site scripting (XSS) is a class of flaws where attacker-controlled data is treated as active content (HTML or JavaScript) in another user's browser. The browser cannot distinguish the site's legitimate script from the attacker's payload, so the payload runs with the origin's privileges — access to cookies (unless HttpOnly), DOM data, and the ability to perform actions as the victim.
| Type | What happens | Attacker goal |
|---|---|---|
| Reflected | Payload in the request is echoed once in the response (e.g. search error). | Trick a victim into clicking a crafted link. |
| Stored | Payload is saved server-side and shown to later visitors. | Compromise every user who views the page. |
| DOM-based | Client-side JavaScript reads attacker input and writes it to a dangerous sink (innerHTML, document.write). |
Bypass server-side filters that never saw the final DOM. |
Stored XSS lives on the Tools page (/tools) in Shift collaboration: shift notes are persisted and rendered with unsafe HTML (set:html) so analysts can use basic formatting and embed external report screenshots.
Learning objectives¶
- Find output contexts where user input is reflected or stored.
- Progress from harmless probes to a working script execution proof.
- Use Burp to replay and refine payloads without retyping in the browser.
- Document sink, payload, and impact in your lab report.
Attacker methodology¶
Work through the steps below in order. The goal is a short report you could hand to a developer: what is vulnerable, how you proved it, and what to fix.
Step 1 — Map the app; rule out non-XSS surfaces first¶
With Burp intercept off, browse:
- Dashboard — panel titles and IOC strings are server-rendered data, not your input.
Burp: Filter HTTP history by your lab host. Notice mostly GET requests — nothing yet where your text is echoed back as HTML.
Step 2 — Find a user-controlled persistence point¶
Open http://localhost:8081/tools and scroll to Shift collaboration.
The thread already shows realistic case updates (case numbers, callback IPs in <code> tags). The form lets you post a case reference and shift note — input that is stored and shown to everyone who loads the page. That is a prime stored-XSS candidate.
Step 3 — Establish a normal baseline¶
Post a benign note the way an analyst would:
| Field | Value |
|---|---|
| Case reference | 4471 |
| Shift note | Lookalike login page at projectx-secure[.]lab. Callback 203.0.113.44:443. Recommend domain block. |
Reload the page. Your note appears in the thread with a Case #4471 header — expected behavior.
Burp: Find POST /tools with form=discussion, case_ref=4471, and body=.... Send to Repeater and confirm the stored note renders in a follow-up GET /tools.
Step 4 — Harmless HTML probe (is output encoded?)¶
Analysts often use light HTML to highlight IOCs. In Repeater, post:
| Field | Value |
|---|---|
| Case reference | 4471 |
| Shift note | Callback observed: <code>203.0.113.44</code> — escalate if persistence confirmed. |
Send, then reload Tools.
- If the IP renders in a monospace/code style, HTML is interpreted — encoding is missing or disabled.
- If you see literal
<code>..., the app is encoding (try another field or a different encoding bypass later).
Step 5 — Escalate: weaponize a broken report embed¶
Internal tools often let analysts paste screenshot embeds from external intel reports. When the image 404s, a broken onerror handler is a classic stored-XSS path.
In Repeater, post a note that looks like a normal case write-up but smuggles a handler on a fake screenshot:
| Field | Value |
|---|---|
| Case reference | 4471 |
| Shift note | See payload below |
Lookalike login page at projectx-secure[.]lab. Callback 203.0.113.44:443.<br>
Mandiant report excerpt (screenshot):<br>
<img src="/vault/reports/case4471.png" alt="landing page capture" onerror="this.insertAdjacentHTML('afterend','<div style="position:fixed;inset:0;background:rgba(9,9,11,.9);display:flex;align-items:center;justify-content:center;z-index:50"><div style="background:#18181b;border:1px solid #3f3f46;border-radius:12px;padding:2rem;max-width:360px"><h3 style="margin:0 0 .5rem;color:#fafafa;font-size:18px">Session expired</h3><p style="margin:0 0 1rem;color:#a1a1aa;font-size:13px">Re-authenticate to view restricted IOC attachments.</p><input placeholder="Password" type="password" style="width:100%;padding:.5rem;margin-bottom:.75rem;border-radius:6px;border:1px solid #3f3f46;background:#09090b;color:#fafafa"><button style="width:100%;padding:.5rem;border-radius:6px;background:#0284c7;color:white;border:none">Continue</button></div></div>')">
Send, then GET /tools in Repeater (or refresh the browser).
Expected: A Session expired overlay appears over the page — proof of stored XSS that looks like a real re-authentication prompt, not a CTF-style alert() popup.
Step 6 — Confirm “stored” vs one-time reflection¶
- Close the tab, open a new tab (same browser profile).
- Reload Tools without reposting.
The overlay appears again → payload lives in the database, not just your last response. That is stored XSS.
Burp tidbit: Compare two GET responses in HTTP history (before and after posting). The thread HTML should contain your unencoded <img … onerror=…> inside the discussion list.
Step 7 — Think like an attacker: impact beyond a popup¶
You are not done when the overlay renders.
| Question | In this lab |
|---|---|
| Can scripts read the session cookie? | The session cookie is HttpOnly — document.cookie may not show it; still document that XSS can change the page, phish credentials, or call APIs as the user while the session is active. |
| Who is affected? | Anyone who views the shift thread. |
| What would you steal or do next? | Harvest re-entered passwords from the fake overlay, exfiltrate page content, auto-submit forms (ties to CSRF). |
Step 8 — Document for your report¶
| Field | Example |
|---|---|
| Type | Stored XSS |
| URL | /tools (Shift collaboration, POST) |
| Parameter | body (POST); optional case_ref |
| Payload | Broken screenshot embed with onerror injecting a fake session overlay (see Step 5) |
| Sink | Server renders shift note HTML without encoding (set:html) |
| Impact | Arbitrary DOM injection in victims' browsers; credential phishing; session riding on same-origin APIs |
| Remediation | Context-aware encoding; CSP; avoid set:html for user content |
Attach a Burp Repeater request/response pair as your evidence.
Remediation¶
- Context-aware output encoding (HTML, attribute, JS, URL).
- Content-Security-Policy (CSP) to limit inline script.
HttpOnly+Securecookies; prefer token-based APIs with CORS discipline.
Related¶
- Burp Suite basics
- OWASP: XSS