CS 161, Lesson 40, in 52 slides, on cross-site scripting. It shows how an attacker injects JavaScript that the victim's browser then RUNS with the target site's origin, defeating the same-origin policy, in section 22, and covers stored XSS and reflected XSS in sections 22.1 and 22.2. It then gives the defenses: sanitizing input by HTML-encoding it, in section 22.3, and Content Security Policy, in section 22.4. It is anchored to textbook sections 22.1 to 22.4.
Subject: Computer Security · 78 slides · applied lesson
Open the interactive version of this deck · Homework for this lesson
Title
CS 161 · Lesson 40 of 45
How an attacker injects JavaScript that YOUR browser runs with the target site's origin — defeating the same-origin policy from the inside
Objectives
<script> is not sanitizing and why HTML-encoding dangerous characters is the robust fix (§22.3).Warm-up
Discussion prompt
Before we open L40 · Cross-Site Scripting (XSS): without looking back, what was the main idea of L39 · Cross-Site Request Forgery (CSRF), and what could you do by the end of it that you could not do before?
Hint: One sentence for the idea, one for the skill. If the second one is blank, that is the part to revisit.
Answer:
CS 161, Lesson 39, in 50 slides, on cross-site request forgery. It shows how an attacker rides the victim's automatically attached session cookie to forge state-changing requests, in section 21.1, covers CSRF based on GET and on POST, and explains why the same-origin policy does not stop it. It then gives the three defenses - CSRF tokens, Referer validation, and the SameSite cookie attribute - in sections 21.2 to 21.4. It is anchored to textbook sections 21.1 to 21.4.
Concept
In L39 CSRF rode your cookie from the OUTSIDE and never read the response. XSS goes one level deeper: the attacker's code runs INSIDE the target's origin — with full read access to everything you can see and do there.
Matching
Match the pairs
From Why a whole lesson on XSS — match each one to what it actually does. The descriptions have been shuffled.
Why: The attack, Two flavors, The defenses are easy to tell apart while they are sitting next to their descriptions and much harder afterwards, which is what this checks.
Section
Part 1 · §22 injecting JS that runs with the site's origin
Concept
Concrete scenario: a site takes some input from you (a search term, a comment) and puts it back into a page. If it doesn't neutralize that input, the attacker can slip in <script> and the browser runs it when the page loads.
Cross-Site Scripting (XSS) — An attack where the attacker injects malicious JavaScript onto a webpage; when the victim loads the page, their browser RUNS the attacker's JavaScript — with the target site's origin.
Concept
Here's the crux. Normally the attacker's JavaScript only runs on sites THEY control — evil.com — with evil.com's origin, which can't touch your Google data. XSS changes which origin the script runs with.
If the attacker injects JS into google.com, the victim's browser runs it with google.com's origin. Recall L37: a script runs with the origin of the page that INCLUDES it. So the injected script can do anything you can do on google.com — read your data, steal your cookies — and exfiltrate it to the attacker.
Intuition
Think of each origin as a secure building. Normally the attacker stands outside in their own clothes (evil.com) — the guards never let them touch google.com's files.
XSS smuggles the attacker's instructions INTO google.com's building, where they get carried out by a worker wearing google.com's uniform. Now everything the worker is trusted to do — open the vault, read the files, copy the keys (your cookies) — the attacker's instructions can do too. The origin IS the uniform.
Ask yourself: whose origin does the injected script run with? (The victim SITE's — google.com's — not evil.com's. That's the whole power of XSS.)
Counterexample
Discussion prompt
Think of each origin as a secure building. Normally the attacker stands outside in their own clothes (evil.com) — the guards never let them touch google.com's files.
That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.
Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.
Answer:
Ask yourself: whose origin does the injected script run with? (The victim SITE's — google.com's — not evil.com's. That's the whole power of XSS.)
Ranking
Put in order
Put the moves of §22 Walk the XSS flow end to end into the order they have to happen.
Why: These are the moves of the worked example in the order it makes them, and each one is set up by the one before it. Any spot where input from a request or a database lands in the HTML is a candidate injection point.
Worked example
The attacker finds a page on google.com that echoes user input without neutralizing it
Why: Any spot where input from a request or a database lands in the HTML is a candidate injection point.
The attacker arranges for their JavaScript to be placed in that page
Why: Either by storing it server-side (stored XSS) or by crafting a URL the victim loads (reflected XSS).
The victim loads the page; the browser parses the attacker's <script> and RUNS it
Why: The browser can't tell injected script from the site's own script — both arrived in google.com's HTML, so both run with google.com's origin.
| step | who acts | whose origin runs the JS? |
|---|---|---|
| inject | attacker | — |
| victim loads page | victim's browser | google.com (the including page) |
| script reads secrets | attacker's JS | google.com — full access |
| script exfiltrates | attacker's JS | sends cookies/data to evil.com |
Verify: the injected script ran with google.com's origin, so the SOP was no help
Why: §22: the script is part of the google.com page, so it has google.com's origin and full read access to the user's Google secrets — the SOP only isolates DIFFERENT origins, and here the script IS google.com's origin.
Discrimination
Sort into buckets
Sort these by who acts, from memory, without looking back at §22 Walk the XSS flow end to end. Telling them apart on the spot is the skill; the table is only where the answer happens to be written down.
Concept
Contrast with CSRF (L39): CSRF blindly RIDES the cookie to fire one request and never reads the response. XSS runs INSIDE the origin with full read access — it can read your data, read your cookies, and send them anywhere.
That's why XSS is strictly more powerful: CSRF forges specific requests from the outside; XSS owns the page from the inside. Everything the user can do on the site, the injected script can do.
Anomaly
Predict first
A student writes this, and it looks reasonable:
A student: 'The Same-Origin Policy isolates origins, so an attacker's injected script can't touch google.com's data — the SOP stops XSS.'
It is wrong. Say what breaks — and say it before you turn the page.
Correct: XSS DEFEATS the SOP. Recall L37: a script runs with the origin of the page that INCLUDES it.
A student: which origin does the injected script actually get?
Why: XSS DEFEATS the SOP. Recall L37: a script runs with the origin of the page that INCLUDES it. The injected script is included in the google.com page, so it runs WITH google.com's origin — not the attacker's — and has full access to google.com's secrets.
Trap
A student: 'The Same-Origin Policy isolates origins, so an attacker's injected script can't touch google.com's data — the SOP stops XSS.'
Assume the SOP isolates the injected script from google.com's data
Why: Wrong. XSS DEFEATS the SOP. Recall L37: a script runs with the origin of the page that INCLUDES it. The injected script is included in the google.com page, so it runs WITH google.com's origin — not the attacker's — and has full access to google.com's secrets.
A student: which origin does the injected script actually get?
It gets the INCLUDING page's origin — google.com — so the SOP doesn't isolate it
Why: §22 / L37: the SOP only separates DIFFERENT origins. The injected script is part of google.com's origin, so the SOP treats it as trusted google.com code. XSS works precisely by making attacker code run with the victim site's origin.
Section
Part 2 · §22.1 the payload persists on the server
Concept
In stored XSS the attacker persistently stores the malicious JavaScript on the server. From then on the server serves that script to everyone who loads the page — no per-victim trickery needed.
Stored XSS — An XSS attack where the attacker's malicious JavaScript is saved on the server (e.g. in a post, comment, or profile) and then served to every user who loads that page, running with the site's origin in each victim's browser.
Definition probe
Sort into buckets
Every line below is part of the definition of Cross-Site Scripting (XSS) or of Stored XSS — one or the other, never both. Put each where it belongs.
Concept
The book's example: the attacker writes a Facebook post whose text is a <script> tag. Facebook stores the post on its servers. Every friend who views the post has that script served to them — and their browser runs it.
<!-- the attacker's Facebook post -->
<script>alert("XSS attack!")</script>Here the payload just pops an alert, but the same slot could hold a script that steals each viewer's Facebook cookies. Stored once, it fires in every viewer's browser with facebook.com's origin.
Intuition
Stored XSS is like graffiti the attacker spray-paints on a billboard everyone passes. They paint it ONCE, and every person who walks by reads it. No need to hand each victim a custom note.
That's why stored XSS is higher-impact: there's no victim-specific link to send, and it hits ALL viewers automatically. One injection, every visitor.
Ask yourself: does the attacker need each victim to click something special? (No — the script is already baked into the page the server hands to everyone.)
Analogy
Discussion prompt
Explain §22.1 A poisoned billboard, not a poisoned letter by analogy to something with no Computer Security in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.
Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.
Answer:
Stored XSS is like graffiti the attacker spray-paints on a billboard everyone passes. They paint it ONCE, and every person who walks by reads it. No need to hand each victim a custom note.
Step zero
Discussion prompt
§22.1 Trace a stored XSS — before any calculation: what is the plan? Name the moves in order, in plain English, without doing the arithmetic.
Hint: It starts with: The attacker submits a post containing <script>alert("XSS…
Answer:
Worked example
The attacker submits a post containing <script>alert("XSS attack!")</script>
Why: The site saves the post text verbatim, without neutralizing the <script> tag.
The site stores the post in its database and serves it on the attacker's profile/feed
Why: The payload now lives server-side; nothing more is needed from the attacker.
Every friend who loads the page receives the post — including its <script>
Why: The server emits the stored text into the HTML, so each viewer's browser parses and runs the script.
| event | who | script runs? | with whose origin? |
|---|---|---|---|
| attacker posts | attacker → server | no (just stored) | — |
| friend A loads page | friend A's browser | yes | facebook.com |
| friend B loads page | friend B's browser | yes | facebook.com |
| every later viewer | their browser | yes | facebook.com |
Verify: one injection runs in many browsers, each with facebook.com's origin
Why: §22.1: stored XSS is served to all visitors, so the script executes in every viewer's session with the site's origin — no victim-specific link required, which is what makes its impact so broad.
Comparison
Comparison matrix
From §22.1 Trace a stored XSS: refill the script runs? column from what you know. The rest of the table is as it appeared.
| event | who | script runs? | with whose origin? |
|---|---|---|---|
| attacker posts | attacker → server | no (just stored) | — |
| friend A loads page | friend A's browser | yes | facebook.com |
| friend B loads page | friend B's browser | yes | facebook.com |
| every later viewer | their browser | yes | facebook.com |
Concept
Two properties make stored XSS dangerous: it needs no victim-specific link (the attacker doesn't have to phish anyone), and it affects every viewer of the page automatically. The attacker just posts and waits.
Anywhere a site stores user content and shows it to others — comments, reviews, profiles, posts — is a candidate. If that content isn't neutralized, one stored payload reaches the whole audience.
Explain it
Discussion prompt
Explain §22.1 Why stored XSS is high-impact to a student a year behind you. No notation, no jargon they have not met — and it still has to be true.
Hint: If your explanation needs a symbol they have never seen, you are describing the notation rather than the idea.
Answer:
Anywhere a site stores user content and shows it to others — comments, reviews, profiles, posts — is a candidate. If that content isn't neutralized, one stored payload reaches the whole audience.
Anomaly
Predict first
A student writes this, and it looks reasonable:
A student: 'For stored XSS to fire, the attacker still has to trick the victim into clicking a special crafted link.'
It is wrong. Say what breaks — and say it before you turn the page.
Correct: Wrong — that describes REFLECTED XSS.
A student: how does a victim get hit by stored XSS?
Why: Wrong — that describes REFLECTED XSS. Stored XSS lives on the server and is served to ANYONE who loads the normal page. No crafted link, no click bait: a friend simply visiting the post is enough.
Trap
A student: 'For stored XSS to fire, the attacker still has to trick the victim into clicking a special crafted link.'
Assume stored XSS requires a victim-specific link/click
Why: Wrong — that describes REFLECTED XSS. Stored XSS lives on the server and is served to ANYONE who loads the normal page. No crafted link, no click bait: a friend simply visiting the post is enough.
A student: how does a victim get hit by stored XSS?
By loading the ordinary page; the server serves the stored script to everyone
Why: §22.1: the payload is persisted server-side and served to all visitors. That's exactly why stored XSS is higher-impact than reflected XSS — it needs no per-victim link and reaches the whole audience.
Section
Part 3 · §22.2 the payload bounces off the request
Concept
In reflected XSS the server takes input straight from a request and reflects it back into the response — unneutralized. If the input is a script, the response embeds and runs it.
Reflected XSS — An XSS attack where the server reflects user input from a request directly into its response. The attacker crafts a URL whose input is a script; when the victim loads that URL, the script is embedded in the response and runs.
Concept
The book's example: a Google search for cs161 via https://www.google.com/search?q=cs161 returns a page reading 'You searched for: cs161'. The server reflected the q value into the page.
https://www.google.com/search?q=cs161
-> response: "You searched for: cs161"
https://www.google.com/search?q=<script>alert("XSS attack!")</script>
-> response embeds the <script> and runs itBy putting a <script> in the q parameter, the attacker makes the reflected response contain — and run — their JavaScript with google.com's origin.
Intuition
Reflected XSS is an echo: whatever you put in the request bounces back in the response. But an echo only happens if SOMEONE shouts. The attacker has to get the VICTIM to load the crafted URL — typically via a phishing link.
So unlike stored XSS (graffiti everyone passes), reflected XSS needs a victim to load the attacker's specific URL. The payload lives in the link, not on the server.
Ask yourself: where does the reflected payload live? (In the request/URL — so the attacker must deliver that URL to the victim and get them to load it.)
Sorting
Sort into buckets
These are the pieces of L40 · Cross-Site Scripting (XSS), out of order. Put each one back under the part of the lesson it belongs to.
Ranking
Put in order
Put the moves of §22.2 Trace a reflected XSS into the order they have to happen.
Why: These are the moves of the worked example in the order it makes them, and each one is set up by the one before it. The q parameter holds the payload; the server reflects q into the response unneutralized.
Worked example
The attacker crafts a URL: google.com/search?q=<script>alert("XSS attack!")</script>
Why: The q parameter holds the payload; the server reflects q into the response unneutralized.
The attacker delivers the URL to the victim (e.g. a phishing email or link)
Why: Reflected XSS requires the victim to LOAD the attacker's crafted URL — the payload isn't stored on the server.
The victim clicks; the server reflects q into 'You searched for: ...' and the browser runs the script
Why: The <script> is now part of google.com's response, so it runs with google.com's origin in the victim's browser.
| step | where the payload is | who must act | result |
|---|---|---|---|
| craft URL | in the q parameter | attacker | weaponized link |
| deliver | in the link | attacker → victim | phishing |
| victim loads URL | reflected into response | victim's browser | script runs (google.com origin) |
| server side | nothing stored | — | no persistence |
Verify: the script ran only because the victim loaded the attacker's crafted URL
Why: §22.2: reflected XSS lives in the request and needs the victim to load that specific URL — contrast stored XSS, which is saved server-side and served to everyone without any link.
Trade off
Comparison matrix
From §22.2 Trace a reflected XSS: every row here is a choice with a cost. Fill the where the payload is column, then say which row you would actually pick and what you give up for it.
| step | where the payload is | who must act | result |
|---|---|---|---|
| craft URL | in the q parameter | attacker | weaponized link |
| deliver | in the link | attacker → victim | phishing |
| victim loads URL | reflected into response | victim's browser | script runs (google.com origin) |
| server side | nothing stored | — | no persistence |
Concept
The two flavors differ in WHERE the payload lives and WHO it reaches. Stored sits on the server and hits everyone; reflected sits in a crafted URL and hits whoever loads it.
| Stored XSS (§22.1) | Reflected XSS (§22.2) | |
|---|---|---|
| payload lives | on the server (DB) | in the request/URL |
| needs a crafted link? | no | yes — victim must load it |
| who's affected | all viewers of the page | whoever loads the link |
| typical delivery | post/comment/profile | phishing link |
Comparison
Comparison matrix
From §22.2 Reflected vs stored, side by side: refill the Reflected XSS (§22.2) column from what you know. The rest of the table is as it appeared.
| Stored XSS (§22.1) | Reflected XSS (§22.2) | |
|---|---|---|
| payload lives | on the server (DB) | in the request/URL |
| needs a crafted link? | no | yes — victim must load it |
| who's affected | all viewers of the page | whoever loads the link |
| typical delivery | post/comment/profile | phishing link |
Anomaly
Predict first
A student writes this, and it looks reasonable:
A student: 'Reflected and stored XSS are basically the same — the script gets saved on the server either way.'
It is wrong. Say what breaks — and say it before you turn the page.
Correct: In REFLECTED XSS the payload lives in the URL/request and is bounced back in the response — nothing is stored, and the victim must LOAD the crafted URL.
A student: what's the actual distinction?
Why: In REFLECTED XSS the payload lives in the URL/request and is bounced back in the response — nothing is stored, and the victim must LOAD the crafted URL. Only STORED XSS persists the payload server-side and serves it to all visitors.
Trap
A student: 'Reflected and stored XSS are basically the same — the script gets saved on the server either way.'
Treat reflected XSS as if the payload is stored server-side
Why: Wrong. In REFLECTED XSS the payload lives in the URL/request and is bounced back in the response — nothing is stored, and the victim must LOAD the crafted URL. Only STORED XSS persists the payload server-side and serves it to all visitors.
A student: what's the actual distinction?
Stored = saved server-side, served to all; reflected = in the URL, needs a click
Why: §22.1–22.2: stored XSS persists on the server and reaches every viewer with no link; reflected XSS lives in a crafted request and only fires when the victim loads that specific URL. WHERE the payload lives is the key difference.
Section
Part 4 · §22.3 and why it's hard
Concept
The natural defense: detect and neutralize any input that could run JavaScript — for instance, strip out <script> tags before putting user input into a page.
It sounds simple. But catching ALL the ways input can run JavaScript is very hard — and a naive filter is easy to defeat. The rest of this part shows why, then gives the robust fix.
Concept
First problem: <script> isn't the only way to run JavaScript. Many HTML attributes are event handlers that execute JS — for example an <img> with a broken src fires its onerror handler.
<img src=1 onerror="alert('XSS')">The src=1 fails to load, so the browser runs the onerror JavaScript — no <script> tag anywhere. A filter that only looks for <script> lets this straight through.
Concept
Second problem: suppose the filter removes every <script> it finds. The attacker NESTS the tags so that removing the inner <script> makes the leftovers re-form a fresh <script>.
<scr<script>ipt>alert('XSS')</scr<script>ipt>
-- filter deletes the inner <script> -->
<script>alert('XSS')</script>After the filter strips the inner <script>, the surrounding <scr + ipt> snap together into a working <script> tag. A single strip-and-stop pass is defeated by its own deletion.
Intuition
Trying to list every dangerous pattern is a losing game — there are too many ways to spell 'run this JS' (onerror, nested tags, and dozens more). Every blocklist you write, an attacker finds the gap you missed.
The robust move flips the strategy: instead of hunting for code to delete, change how the input is interpreted so the browser DISPLAYS it as text instead of PARSING it as a tag. Don't try to recognize all attacks — make attacks structurally impossible to parse.
Ask yourself: is the goal to spot every bad pattern, or to make the browser never treat input as a tag? (The latter — that's what HTML-encoding does.)
Concept
The robust approach is to HTML-encode the dangerous characters so the browser shows them as text rather than parsing them as markup. The two key ones: < becomes < and > becomes >.
<script>alert('XSS')</script>
-- HTML-encode < and > -->
<script>alert('XSS')</script>
-- the browser DISPLAYS the characters; no tag is parsed -->Now the browser literally prints the text <script>alert('XSS')</script> on the page instead of executing it — there's no < to start a tag. The user sees the characters; the parser sees no markup.
Concept
Because the corner cases are so numerous (event handlers, nested tags, encodings, context differences), you should use a standardized, known-robust sanitizer — never hand-write your own filter.
A purpose-built, widely-tested library has already handled the bypasses you'd never think of. The lesson of the nested-tag and onerror tricks is exactly why a homemade blocklist is a trap. ⊕ In practice, libraries like DOMPurify are the go-to.
Step zero
Discussion prompt
§22.3 Defeat the filter, then fix it by encoding — before any calculation: what is the plan? Name the moves in order, in plain English, without doing the arithmetic.
Hint: It starts with: Start with a naive filter that strips every <script> substring
Answer:
Worked example
Start with a naive filter that strips every <script> substring
Why: It looks safe: any literal <script> the attacker submits gets deleted.
Attacker submits <scr<script>ipt>alert('XSS')</scr<script>ipt>
Why: The payload hides a real <script> across the boundary of the one the filter will delete.
| stage | what the string looks like | runs? |
|---|---|---|
| submitted | <scr<script>ipt>alert('XSS')</scr<script>ipt> | — |
| after stripping inner <script> | <script>alert('XSS')</script> | YES — filter bypassed |
| also try | <img src=1 onerror="alert('XSS')"> | YES — no <script> at all |
| HTML-encode < and > instead | <script>...</script> | no — shown as text |
Verify: stripping fails (nesting + onerror), but encoding < and > makes every payload display as inert text
Why: §22.3: a blocklist that deletes <script> is defeated by reconstruction and by non-<script> handlers; HTML-encoding the dangerous characters neutralizes ALL of them by ensuring the browser never parses input as a tag. Use a standard sanitizer, not your own.
Trade off
Comparison matrix
From §22.3 Defeat the filter, then fix it by encoding: every row here is a choice with a cost. Fill the runs? column, then say which row you would actually pick and what you give up for it.
| stage | what the string looks like | runs? |
|---|---|---|
| submitted | <scr<script>ipt>alert('XSS')</scr<script>ipt> | — |
| after stripping inner <script> | <script>alert('XSS')</script> | YES — filter bypassed |
| also try | <img src=1 onerror="alert('XSS')"> | YES — no <script> at all |
| HTML-encode < and > instead | <script>...</script> | no — shown as text |
Anomaly
Predict first
A student writes this, and it looks reasonable:
A student: 'I remove any <script> tags from user input before displaying it, so it's sanitized and safe.'
It is wrong. Say what breaks — and say it before you turn the page.
Correct: Wrong on two counts. (1) JS runs without <script> — e.g.
A student: what actually neutralizes the input?
Why: Wrong on two counts. (1) JS runs without <script> — e.g. <img src=1 onerror="alert('XSS')"> fires on the onerror handler. (2) A naive strip is self-defeating: <scr<script>ipt>...</scr<script>ipt> re-forms a real <script> once the inner one is removed.
Trap
A student: 'I remove any <script> tags from user input before displaying it, so it's sanitized and safe.'
Treat 'delete every <script>' as sufficient sanitizing
Why: Wrong on two counts. (1) JS runs without <script> — e.g. <img src=1 onerror="alert('XSS')"> fires on the onerror handler. (2) A naive strip is self-defeating: <scr<script>ipt>...</scr<script>ipt> re-forms a real <script> once the inner one is removed.
A student: what actually neutralizes the input?
HTML-encode dangerous characters (< -> <, > -> >) with a standard sanitizer
Why: §22.3: encoding makes the browser DISPLAY the characters instead of parsing a tag, so no payload executes — regardless of onerror tricks or nesting. Use a known-robust sanitizer; never roll your own blocklist.
Section
Part 5 · §22.4 a browser-enforced script allowlist
Concept
A Content Security Policy is an allowlist of domains that scripts may load from. The server sends it in a Content-Security-Policy HTTP response header, and the browser enforces it.
Content Security Policy (CSP) — An allowlist of script sources a server sends in the Content-Security-Policy HTTP response header. The browser enforces it: scripts may load only from allowlisted domains, and — crucially — inline scripts injected into the HTML are blocked.
Explain it
Discussion prompt
Explain §22.4 What a CSP is to a student a year behind you. No notation, no jargon they have not met — and it still has to be true.
Hint: If your explanation needs a symbol they have never seen, you are describing the notation rather than the idea.
Answer:
A Content Security Policy is an allowlist of domains that scripts may load from. The server sends it in a Content-Security-Policy HTTP response header, and the browser enforces it.
Concept
Example: cs161.org sends a CSP allowing scripts only from *.cs161.org or *.google.com, and blocking everything else. The browser then refuses to run any script from a non-allowlisted source.
Content-Security-Policy: script-src *.cs161.org *.google.comCrucially, this includes blocking INLINE scripts injected by an attacker. With this CSP you can't run a script embedded directly in the HTML — only EXTERNAL scripts loaded from an allowlisted URL.
Analogy
Discussion prompt
Explain §22.4 What a CSP allows and blocks by analogy to something with no Computer Security in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.
Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.
Answer:
Example: cs161.org sends a CSP allowing scripts only from *.cs161.org or *.google.com, and blocking everything else. The browser then refuses to run any script from a non-allowlisted source.
Intuition
A CSP is a guest list the browser checks at the door. Scripts get in only if they come from a domain on the list. An attacker's injected <script> is written right into the page — it has no external domain to vouch for it, so it's not on the list.
That's the key: even if the attacker manages to inject an inline <script>, the browser won't run it, because inline scripts aren't on the allowlist. And they can't point the page at evil.com's script either — evil.com isn't allowlisted. The injection gets in; it just never executes.
Ask yourself: with CSP on, what happens to an injected inline <script>? (It's blocked — inline scripts aren't allowlisted, so the browser refuses to run it.)
Counterexample
Discussion prompt
Ask yourself: with CSP on, what happens to an injected inline <script>? (It's blocked — inline scripts aren't allowlisted, so the browser refuses to run it.)
That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.
Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.
Ranking
Put in order
Put the moves of §22.4 Run injected scripts past a CSP into the order they have to happen.
Why: These are the moves of the worked example in the order it makes them, and each one is set up by the one before it. The browser will now only run scripts whose source matches the allowlist.
Worked example
cs161.org sends Content-Security-Policy: script-src *.cs161.org *.google.com
Why: The browser will now only run scripts whose source matches the allowlist.
An attacker injects an inline <script> and also tries pointing at evil.com
Why: Both are the usual XSS payloads — an embedded script and an external one from the attacker's domain.
| script the page contains | source | CSP verdict |
|---|---|---|
| <script src=https://js.cs161.org/app.js> | *.cs161.org | ALLOWED |
| <script src=https://apis.google.com/x.js> | *.google.com | ALLOWED |
| <script>steal(document.cookie)</script> | inline | BLOCKED |
| <script src=https://evil.com/x.js> | evil.com | BLOCKED — not allowlisted |
Verify: the attacker can neither inject an inline script nor point the page at their own domain
Why: §22.4: the CSP blocks inline scripts and any non-allowlisted source, so even a successful injection fails to execute — the browser enforces the allowlist before running anything.
Discrimination
Sort into buckets
Sort these by CSP verdict, from memory, without looking back at §22.4 Run injected scripts past a CSP. Telling them apart on the spot is the skill; the table is only where the answer happens to be written down.
Concept
⊕ Supplemental (beyond the core §22.4): a few things worth knowing exist but aren't required here.
innerHTML); the SERVER never sees the payload, so server-side filtering misses it.nonce; only scripts carrying the matching nonce run, so an attacker (who can't guess it) still can't execute inline code.Anomaly
Predict first
A student writes this, and it looks reasonable:
A student: 'A CSP just restricts which external domains scripts come from — inline <script> written into the page still runs fine.'
It is wrong. Say what breaks — and say it before you turn the page.
Correct: A KEY point of CSP is that it BLOCKS inline scripts — scripts embedded directly in the HTML.
A student: what does CSP do to an injected inline <script>?
Why: A KEY point of CSP is that it BLOCKS inline scripts — scripts embedded directly in the HTML. That's precisely what defangs an injected XSS payload: even if the attacker gets an inline <script> into the page, the browser won't run it.
Trap
A student: 'A CSP just restricts which external domains scripts come from — inline <script> written into the page still runs fine.'
Assume a CSP leaves inline scripts running
Why: Wrong. A KEY point of CSP is that it BLOCKS inline scripts — scripts embedded directly in the HTML. That's precisely what defangs an injected XSS payload: even if the attacker gets an inline <script> into the page, the browser won't run it.
A student: what does CSP do to an injected inline <script>?
It blocks it — only allowlisted EXTERNAL scripts may run
Why: §22.4: with CSP enabled you can't run scripts embedded directly in the HTML; only external scripts from allowlisted URLs run. So an attacker can't inject an inline <script> and can't point the page at their own domain.
Two truths and a lie
Sort into buckets
Some of these hold up and some are the exact mistakes this lesson is built to prevent. Sort them.
<script> in the q parameter, the attacker makes the reflected response contain — and run — their JavaScript with google.com's origin.Concept
Students conflate XSS and CSRF, but they differ on one axis: does the attacker get to run code inside the target origin? XSS does — with full read access. CSRF does not — it only triggers a request from outside and never reads the response.
| XSS (L40) | CSRF (L39) | |
|---|---|---|
| attacker code runs in target origin? | YES | no |
| reads the response / DOM? | yes — full access | no — fire and forget |
| defeats the SOP? | yes (runs with site's origin) | no (rides the cookie out) |
| severity | owns the page — far worse | forge specific requests |
XSS breaks INTO the origin and runs script there; CSRF rides the cookie from outside. That's why XSS is strictly more powerful.
Comparison
Comparison matrix
From §22.1–22.4 XSS vs CSRF — a sharp line: refill the XSS (L40) column from what you know. The rest of the table is as it appeared.
| XSS (L40) | CSRF (L39) | |
|---|---|---|
| attacker code runs in target origin? | YES | no |
| reads the response / DOM? | yes — full access | no — fire and forget |
| defeats the SOP? | yes (runs with site's origin) | no (rides the cookie out) |
| severity | owns the page — far worse | forge specific requests |
Pattern
<script> (defeated by onerror and nested-tag reconstruction) — HTML-ENCODE dangerous characters (<→<, >→>) so they DISPLAY instead of parse, using a standardized sanitizer.Content-Security-Policy script-source allowlist the browser enforces — it blocks INLINE scripts and any non-allowlisted source, so an injected payload can't execute.Edge cases
Discussion prompt
The XSS playbook works on the cases you have just seen. Push it to the edge: what is the most degenerate input it still handles — empty, zero, one item, everything equal — and what is the first case where it stops being true? Name the case, not just "it breaks".
Hint: Try the smallest legal input, then the largest, then the one where two things collide. Methods are specified at their edges; the middle takes care of itself.
Answer:
<script> (defeated by onerror and nested-tag reconstruction) — HTML-ENCODE dangerous characters…Content-Security-Policy script-source allowlist the browser enforces — it blocks INLINE scripts and any…Elimination
Eliminate the wrong options
Which statement about this XSS attack is TRUE?
3 of these 4 are wrong. Strike them one at a time, and say what rules each one out before you strike the next. The survivor is the answer.
<script> tag from the input would have fully sanitized it and stopped the attack.</> (or a CSP blocking inline scripts) is what actually stops it.Survives elimination: D
Why: §22 / L37: the injected script is included in the google.com page, so it runs WITH google.com's origin — defeating the SOP — and has full read access to the user's Google secrets. The robust fix is to HTML-encode dangerous characters (< → <, > → >) so the browser displays the input as text instead of parsing a tag, and/or to send a Content Security Policy, which the browser enforces to block inline scripts. The other options each rest on a common XSS misconception.
Check
An attacker injects JavaScript into a page on google.com (say, via a reflected search parameter). A victim loads the page and the script runs. Think through which origin the script gets, what the SOP does here, and what actually stops the attack before choosing.
Check your understanding
Which statement about this XSS attack is TRUE?
<script> tag from the input would have fully sanitized it and stopped the attack.</> (or a CSP blocking inline scripts) is what actually stops it. (correct)Answer: D
Why: §22 / L37: the injected script is included in the google.com page, so it runs WITH google.com's origin — defeating the SOP — and has full read access to the user's Google secrets. The robust fix is to HTML-encode dangerous characters (< → <, > → >) so the browser displays the input as text instead of parsing a tag, and/or to send a Content Security Policy, which the browser enforces to block inline scripts. The other options each rest on a common XSS misconception.
Concept
<script> sanitizes input — no: onerror handlers and nested-tag reconstruction defeat it; HTML-ENCODE the dangerous characters.Concept
Concept
<script> fails (onerror, nesting) and why HTML-encoding with a standard sanitizer is robust.Connect it up
Draw it
One page, no notation unless you need it: draw how these connect — What XSS Is · Stored XSS · Reflected XSS · Defense 1 — Sanitizing Input · Defense 2 — Content Security Policy. Put an arrow wherever one of them is what makes another possible, and label the arrow with why.
Recap
You can now define XSS as injected JavaScript that the victim's browser runs with the TARGET site's origin — defeating the same-origin policy and giving full read access to that site's secrets; build it as stored XSS (saved server-side, served to all) and reflected XSS (in a crafted URL, needs a load); explain why stripping <script> fails and why HTML-encoding is the robust fix; describe a CSP as a browser-enforced allowlist that blocks inline scripts; and distinguish XSS from CSRF.
| Idea | § | The one-line version |
|---|---|---|
| What XSS is | 22 | injected JS the browser RUNS with the site's origin |
| Why devastating | 22 | defeats the SOP; full read access to the site's secrets |
| Stored XSS | 22.1 | saved server-side; served to ALL viewers; no link |
| Reflected XSS | 22.2 | in a crafted URL; victim must load it |
| Sanitizing is hard | 22.3 | onerror + nested tags defeat a <script> strip |
| The robust fix | 22.3 | HTML-encode < and > with a standard sanitizer |
| CSP | 22.4 | browser-enforced allowlist; blocks INLINE scripts |
| vs CSRF | — | XSS runs IN the origin; CSRF rides the cookie out |
Want this taught 1-on-1? Alexander tutors Computer Security — $55/session, free consultation.