Cross-Site Scripting (XSS)
CAP, ACID vs BASE, latency numbers, back-of-envelope estimation, single points of failure — the vocabulary every system designer thinks in.
Core Philosophy: Cross-Site Scripting is injection again — but the injected code is JavaScript, and the place it runs is not the server but the browser of an innocent victim. XSS turns a trusted website into a delivery mechanism for the attacker’s code, executing with all the trust the victim’s browser places in that site. Because so much identity and power lives in the browser, XSS is one of the most impactful web vulnerabilities there is.
Part 1: The Problem
Web pages are built from HTML and JavaScript. When an application takes user input and places it into a page without properly handling it, an attacker can supply input that isn’t ordinary text but is JavaScript code. The browser, unable to tell the difference, executes it.
The crucial twist that makes XSS different from server-side injection: the code runs in the browser of whoever views that page — potentially thousands of innocent users. The website becomes the attacker’s accomplice, delivering malicious code under its own trusted name.
Part 2: The Concept — Injecting Into the Page
It’s the same data-vs-code confusion from 2.4, in a new context. An application takes user input and builds a web page with it:
A page greets the user by name. It builds HTML:
"<p>Welcome, " + INPUT + "</p>"
NORMAL — input is data:
Input: "Alice"
Page becomes: <p>Welcome, Alice</p> ✓ harmless
ATTACK — input is code:
Input: "<script>alert('XSS')</script>"
Page becomes:
<p>Welcome, <script>alert('XSS')</script></p>
└──────────────┬──────────────┘
the browser EXECUTES this as JavaScript
The attacker’s input wasn’t displayed as text — it was injected into the page’s HTML, and the browser ran it as code. alert('XSS') is the harmless classic used to prove the vulnerability. Real attacker payloads do far more (Part 4).
Part 3: The Three Types of XSS
XSS is categorized by how the malicious script reaches the victim.
1. Reflected XSS — the script bounces off the server. The malicious script is part of a request (often in a URL parameter), and the server “reflects” it straight back in the response. The attacker must get the victim to click a crafted link.
Attacker crafts a URL containing the script
│
Victim clicks it (phishing email, message, malicious page)
│
Server reflects the script into the response page
│
Script runs in the victim's browser
2. Stored XSS — the script is saved on the server. The malicious script is saved by the application — in a comment, a profile field, a forum post, a product review. Then it’s served to everyone who views that content. No clicking a special link needed; just viewing the page triggers it. This is the most dangerous type because it hits every visitor automatically.
Attacker posts a comment containing the script
│
Server STORES it in the database
│
EVERY user who views that page receives the script
│
Script runs in every one of their browsers
3. DOM-based XSS — the injection happens entirely in the browser. The vulnerability is in the page’s own JavaScript, which takes attacker-influenced data and unsafely writes it into the page — the server may never even see the malicious part. It’s a client-side-only variant.
| Type | Where the payload lives | Victim must… |
|---|---|---|
| Reflected | In the request, reflected back immediately | Click a crafted link |
| Stored | Saved on the server, served to all | Just view the page |
| DOM-based | Handled unsafely by the page’s own JavaScript | View / interact with the page |
Part 4: Why XSS Is Dangerous — What an Attacker’s Script Can Do
“It just runs some JavaScript” sounds minor. It isn’t. JavaScript running in the victim’s browser, in the context of the trusted site, can do almost anything the user can — and more:
- Steal session cookies / tokens. Recall from 0.3 and 1.4: the session cookie is the user’s identity. A script that reads it and sends it to the attacker hands over the victim’s account — this is session hijacking via XSS, and it’s why XSS and session theft are tightly linked.
- Perform actions as the victim. The script can make requests as the logged-in user — change their email, transfer funds, post content — all silently.
- Capture keystrokes and input. Including passwords typed into the page.
- Deface the page or show fake content (e.g. a fake login prompt to phish credentials).
- Spread itself. A stored XSS in a social feature can be made to re-post itself — a self-propagating “XSS worm.”
The severity comes from trust: the browser grants the script all the trust it grants the legitimate site. To the browser, the attacker’s injected script and the site’s real code are indistinguishable.
🔑 One important defensive concept worth knowing now: a cookie can be marked HttpOnly, which makes it unreadable by JavaScript. This single flag blocks the most direct cookie-stealing XSS payloads. It doesn’t fix XSS — the attacker can still act as the victim — but it’s a key piece of defense in depth you’ll apply in Phase 4.
Part 5: Finding XSS
The testing mindset mirrors 2.4 — find inputs, probe, observe — but here you’re watching whether your input lands in the page in a way the browser will execute.
1. FIND INPUTS Every input that ends up DISPLAYED somewhere:
search boxes, comment fields, profile fields,
URL parameters reflected on the page, names,
messages — anything shown back to a user.
2. PROBE Submit a harmless, unique marker, then a test
payload (the alert() classic). Use something
distinctive so you can find it in the page.
3. INSPECT View the resulting page SOURCE. Did your input
appear as plain text (safe) or as live HTML/
script (vulnerable)?
4. CLASSIFY Determine which type — reflected, stored, or
DOM-based — based on how it got there.
5. ASSESS Confirm it executes. Then assess real impact
responsibly (see ethics note).
⚖️ Ethics of XSS testing. The alert() popup is the universal harmless proof — it demonstrates script execution without harming anyone. That is what you use to confirm and report XSS. Never test XSS with a payload that actually steals real users’ cookies or data, even on an in-scope bug bounty target — that harms real people and is a crime. Prove execution; do not weaponize it.
Part 6: A Glance at the Fix
XSS, like all injection, is fixed by correctly separating data from code. The defenses (built fully in Phase 4.1):
- Output encoding / escaping — the primary fix. When user input is placed into a page, encode it so the browser always treats it as text to display, never as code to run. The characters that have special meaning in HTML (like
<and>) get converted to harmless display equivalents. Encoded input shows up as the literal text the user typed — it cannot become a<script>tag. - Context-aware encoding. Input going into HTML, into a JavaScript block, into an attribute, or into a URL each needs a different encoding. Modern frameworks often handle this automatically — a major reason to use a well-built framework rather than gluing HTML by hand.
- Content Security Policy (CSP). A browser security feature: the site declares rules for what scripts are allowed to run. A strong CSP can block injected scripts even if an XSS slips through — defense in depth.
HttpOnlycookies (Part 4) and input validation as supporting layers.
VULNERABLE: page = "<p>Welcome, " + input + "</p>"
input "<script>...</script>" → browser RUNS it
SAFE (output encoding): input is encoded before display
"<script>" becomes harmless display text
→ browser SHOWS "<script>", never runs it
You’ll implement these in Phase 4.1 and then re-attack your own fix. The principle to carry forward: encode on output, treat all user input as untrusted, and let a good framework and CSP back you up.
📓 Key Terms
| Term | Plain meaning |
|---|---|
| XSS (Cross-Site Scripting) | Injecting malicious script that runs in a victim’s browser. |
| Reflected XSS | Script in a request, reflected back immediately; needs a crafted link. |
| Stored XSS | Script saved on the server and served to all viewers. |
| DOM-based XSS | XSS arising from the page’s own JavaScript handling input unsafely. |
| Payload | The attacker’s malicious input/script. |
| Session hijacking | Taking over a user’s session — a common goal of XSS. |
| Output encoding / escaping | Converting input so the browser treats it as text, not code — the main fix. |
| Content Security Policy (CSP) | Browser rules restricting which scripts may run — defense in depth. |
| HttpOnly | A cookie flag making the cookie unreadable by JavaScript. |
🧪 Hands-On Lab
Only on your own lab or deliberately vulnerable practice apps. Use only harmless proof payloads like alert() — never real cookie/data-stealing payloads.
Task 1 — Reflected XSS. On a deliberately vulnerable app, find an input reflected in the response (a search box is classic). Inject the alert() payload. See the popup. View the page source and find your script in the HTML — confirm the browser treated it as code.
Task 2 — Stored XSS. Find a feature that saves your input and shows it later (a comment, a profile name, a guestbook). Store an alert() payload. Reload the page — the script runs. Now grasp the danger: it would run for every visitor, automatically.
Task 3 — DOM-based XSS. Find a DOM-XSS challenge. Use browser dev tools to trace how the page’s own JavaScript takes input and writes it unsafely into the page. Notice the server may never see the payload.
Task 4 — Read the source, every time. For each XSS you find, view the page source and locate exactly where and how your input landed. Building the habit of reading how input is reflected is what makes you good at finding XSS.
Task 5 — Understand the impact (conceptually). Read about what real XSS payloads do — cookie theft, acting as the user. Do not run such payloads. Understanding the impact is what lets you explain severity in a report; demonstrating it harmfully is off-limits.
Task 6 — See a defense work. Find an input in the app that is not vulnerable — where your payload appears as harmless text. View the source: your <script> shows up encoded. You’ve just seen output encoding (Part 6) doing its job. This previews Phase 4.1.
⚠️ Common Mistakes
- Using harmful payloads to “demonstrate” XSS.
alert()proves execution harmlessly. Cookie-stealing payloads against real users cause real harm and are criminal — even in an in-scope program. - Thinking XSS is “just a popup.” The popup is the proof; the impact is session hijacking, acting as the victim, credential theft. Communicate the real severity.
- Only checking obvious inputs. XSS hides in profile fields, error messages, file names, HTTP headers reflected on a page, and more — not just search boxes.
- Believing blacklisting bad characters is the fix. Filters get bypassed endlessly. The real fix is correct, context-aware output encoding.
- Forgetting DOM-based XSS. If you only watch server responses, you’ll miss XSS that happens purely in client-side JavaScript.
✅ Recap & What’s Next
- XSS is injection of malicious JavaScript that executes in an innocent victim’s browser, with all the trust the browser grants the legitimate site.
- It comes in three types — reflected (crafted link), stored (saved server-side, hits everyone), and DOM-based (client-side JavaScript) — and enables session hijacking, acting as the victim, and credential theft.
- The primary fix is output encoding, backed by CSP and
HttpOnlycookies — implemented in Phase 4.1; always prove XSS with harmless payloads only.
Next (2.6): We turn to the machinery from page 1.4 and attack it directly — broken authentication and session handling, the flaws that let an attacker simply become another user.
⁂ Back to all modules