L40 · Cross-Site Scripting (XSS)

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

What this lesson covers

The lesson, slide by slide

1. Cross-Site Scripting

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

2. By the end of this lesson you can…

Objectives

  1. Define XSS: the attacker injects malicious JavaScript onto a page so the victim's browser runs it with the target site's origin.
  2. Explain why XSS is devastating — it SUBVERTS the same-origin policy (L37): the script runs with the victim site's origin and can steal that site's secrets.
  3. Construct stored XSS (payload persisted server-side, served to all viewers) and reflected XSS (payload reflected from the request, needs a crafted URL) against a toy target.
  4. Explain why stripping <script> is not sanitizing and why HTML-encoding dangerous characters is the robust fix (§22.3).
  5. Describe Content Security Policy as a browser-enforced script allowlist that blocks inline scripts, and distinguish XSS from CSRF.

3. What survived from L39 · Cross-Site Request Forgery (CSRF)?

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.

4. Why a whole lesson on XSS

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.

The attack
§22 inject JS that runs with the site's origin
Two flavors
§22.1–22.2 stored · reflected
The defenses
§22.3–22.4 encode input · CSP

5. Which is which: Why a whole lesson on XSS

Matching

Match the pairs

From Why a whole lesson on XSS — match each one to what it actually does. The descriptions have been shuffled.

  • c1. The attack
  • c2. Two flavors
  • c3. The defenses
  • b1. §22 inject JS that runs with the site's origin
  • b2. §22.1–22.2 stored · reflected
  • b3. §22.3–22.4 encode input · CSP

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.

6. What XSS Is

Section

Part 1 · §22 injecting JS that runs with the site's origin

7. §22 What XSS is

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.

8. §22 Why it's devastating: it runs with the 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.

9. §22 The impostor in the uniform

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.)

10. Break it if you can: §22 The impostor in the uniform

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.)

11. What has to happen first: §22 Walk the XSS flow end to end

Ranking

Put in order

Put the moves of §22 Walk the XSS flow end to end into the order they have to happen.

  1. The attacker finds a page on google.com that echoes user input without neutralizing it
  2. The attacker arranges for their JavaScript to be placed in that page
  3. The victim loads the page; the browser parses the attacker's <script> and RUNS it
  4. Verify: the injected script ran with google.com's origin, so the SOP was no help

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.

12. §22 Walk the XSS flow end to end

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.

stepwho actswhose origin runs the JS?
injectattacker—
victim loads pagevictim's browsergoogle.com (the including page)
script reads secretsattacker's JSgoogle.com — full access
script exfiltratesattacker's JSsends 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.

13. Which is which, by who acts

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.

attacker
inject
victim's browser
victim loads page
attacker's JS
script reads secrets; script exfiltrates
g1
who acts is "attacker" for inject — that is what the table on "§22 Walk the XSS flow end to end" records, and it is the single property separating this group from the rest.
g2
who acts is "victim's browser" for victim loads page — that is what the table on "§22 Walk the XSS flow end to end" records, and it is the single property separating this group from the rest.
g3
who acts is "attacker's JS" for script reads secrets, script exfiltrates — that is what the table on "§22 Walk the XSS flow end to end" records, and it is the single property separating this group from the rest.

14. §22 XSS vs CSRF — read access is the difference

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.

15. Something is wrong here: 'the same-origin policy stops injected scripts'

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.

16. Trap: 'the same-origin policy stops injected scripts'

Trap

The 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.

The fix

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.

17. Stored XSS

Section

Part 2 · §22.1 the payload persists on the server

18. §22.1 Stored XSS

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.

19. Take the definitions apart: Cross-Site Scripting… vs Stored XSS

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.

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.
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.
b1
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.
b2
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.

20. §22.1 The Facebook-post example

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.

21. §22.1 A poisoned billboard, not a poisoned letter

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.)

22. By analogy: §22.1 A poisoned billboard, not a poisoned letter

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.

23. Plan first: §22.1 Trace a stored XSS

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:

  1. The attacker submits a post containing <script>alert("XSS attack!")</script>
  2. The site stores the post in its database and serves it on the attacker's profile/feed
  3. Every friend who loads the page receives the post — including its <script>
  4. Verify: one injection runs in many browsers, each with facebook.com's origin

24. §22.1 Trace a stored XSS

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.

eventwhoscript runs?with whose origin?
attacker postsattacker → serverno (just stored)—
friend A loads pagefriend A's browseryesfacebook.com
friend B loads pagefriend B's browseryesfacebook.com
every later viewertheir browseryesfacebook.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.

25. Fill in: script runs? for §22.1 Trace a stored XSS

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.

eventwhoscript runs?with whose origin?
attacker postsattacker → serverno (just stored)—
friend A loads pagefriend A's browseryesfacebook.com
friend B loads pagefriend B's browseryesfacebook.com
every later viewertheir browseryesfacebook.com

26. §22.1 Why stored XSS is high-impact

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.

27. Teach it back: §22.1 Why stored XSS is high-impact

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.

28. Something is wrong here: 'stored XSS needs the victim to click an attacker link'

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.

29. Trap: 'stored XSS needs the victim to click an attacker link'

Trap

The 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.

The fix

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.

30. Reflected XSS

Section

Part 3 · §22.2 the payload bounces off the request

31. §22.2 Reflected XSS

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.

32. §22.2 The Google-search example

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 it

By putting a <script> in the q parameter, the attacker makes the reflected response contain — and run — their JavaScript with google.com's origin.

33. §22.2 An echo you have to shout into

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.)

34. Where does each piece belong: L40 · Cross-Site Scripting (XSS)

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.

What XSS Is
§22 What XSS is; §22 Why it's devastating: it runs with the site's origin; §22 The impostor in the uniform
Stored XSS
§22.1 Stored XSS; §22.1 The Facebook-post example; §22.1 A poisoned billboard, not a poisoned letter
Reflected XSS
§22.2 Reflected XSS; §22.2 The Google-search example; §22.2 An echo you have to shout into
s1
What XSS Is is where L40 · Cross-Site Scripting (XSS) puts §22 What XSS is, §22 Why it's devastating: it runs with the site's origin, §22 The impostor in the uniform. Knowing which part of the lesson a problem belongs to is most of knowing which method to reach for.
s2
Stored XSS is where L40 · Cross-Site Scripting (XSS) puts §22.1 Stored XSS, §22.1 The Facebook-post example, §22.1 A poisoned billboard, not a poisoned letter. Knowing which part of the lesson a problem belongs to is most of knowing which method to reach for.
s3
Reflected XSS is where L40 · Cross-Site Scripting (XSS) puts §22.2 Reflected XSS, §22.2 The Google-search example, §22.2 An echo you have to shout into. Knowing which part of the lesson a problem belongs to is most of knowing which method to reach for.

35. What has to happen first: §22.2 Trace a reflected XSS

Ranking

Put in order

Put the moves of §22.2 Trace a reflected XSS into the order they have to happen.

  1. The attacker crafts a URL: google.com/search?q=<script>alert("XSS attack!")</script>
  2. The attacker delivers the URL to the victim (e.g. a phishing email or link)
  3. The victim clicks; the server reflects q into 'You searched for: ...' and the browser runs the script
  4. Verify: the script ran only because the victim loaded the attacker's crafted URL

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.

36. §22.2 Trace a reflected XSS

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.

stepwhere the payload iswho must actresult
craft URLin the q parameterattackerweaponized link
deliverin the linkattacker → victimphishing
victim loads URLreflected into responsevictim's browserscript runs (google.com origin)
server sidenothing 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.

37. What each one costs: §22.2 Trace a reflected XSS

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.

stepwhere the payload iswho must actresult
craft URLin the q parameterattackerweaponized link
deliverin the linkattacker → victimphishing
victim loads URLreflected into responsevictim's browserscript runs (google.com origin)
server sidenothing stored—no persistence

38. §22.2 Reflected vs stored, side by side

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 liveson the server (DB)in the request/URL
needs a crafted link?noyes — victim must load it
who's affectedall viewers of the pagewhoever loads the link
typical deliverypost/comment/profilephishing link

39. Fill in: Reflected XSS (§22.2) for §22.2 Reflected vs stored, side by side

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 liveson the server (DB)in the request/URL
needs a crafted link?noyes — victim must load it
who's affectedall viewers of the pagewhoever loads the link
typical deliverypost/comment/profilephishing link

40. Something is wrong here: confusing reflected XSS with stored XSS

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.

41. Trap: confusing reflected XSS with stored XSS

Trap

The 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.

The fix

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.

42. Defense 1 — Sanitizing Input

Section

Part 4 · §22.3 and why it's hard

43. §22.3 The idea: sanitize the input

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.

44. §22.3 JS can run WITHOUT a <script> tag

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.

45. §22.3 A naive <script>-stripping filter is self-defeating

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.

46. §22.3 Blocklists lose; change the meaning instead

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.)

47. §22.3 The robust fix: HTML-encode dangerous characters

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 &lt; and > becomes &gt;.

<script>alert('XSS')</script>
  -- HTML-encode < and > -->
&lt;script&gt;alert('XSS')&lt;/script&gt;
  -- 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.

48. §22.3 Never roll your own — use a standard sanitizer

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.

49. Plan first: §22.3 Defeat the filter, then fix it by encoding

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:

  1. Start with a naive filter that strips every <script> substring
  2. Attacker submits <scr<script>ipt>alert('XSS')</scr<script>ipt>
  3. Verify: stripping fails (nesting + onerror), but encoding < and > makes every payload display as inert text

50. §22.3 Defeat the filter, then fix it by encoding

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.

stagewhat the string looks likeruns?
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&lt;script&gt;...&lt;/script&gt;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.

51. What each one costs: §22.3 Defeat the filter, then fix it by encoding

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.

stagewhat the string looks likeruns?
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&lt;script&gt;...&lt;/script&gt;no — shown as text

52. Something is wrong here: 'stripping <script> tags makes input safe'

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.

53. Trap: 'stripping <script> tags makes input safe'

Trap

The 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.

The fix

A student: what actually neutralizes the input?

HTML-encode dangerous characters (< -> &lt;, > -> &gt;) 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.

54. Defense 2 — Content Security Policy

Section

Part 5 · §22.4 a browser-enforced script allowlist

55. §22.4 What a CSP is

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.

56. Teach it back: §22.4 What a CSP is

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.

57. §22.4 What a CSP allows and blocks

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.com

Crucially, 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.

58. By analogy: §22.4 What a CSP allows and blocks

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.

59. §22.4 The guest list at the door

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.)

60. Break it if you can: §22.4 The guest list at the door

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.

61. What has to happen first: §22.4 Run injected scripts past a CSP

Ranking

Put in order

Put the moves of §22.4 Run injected scripts past a CSP into the order they have to happen.

  1. cs161.org sends Content-Security-Policy: script-src *.cs161.org *.google.com
  2. An attacker injects an inline <script> and also tries pointing at evil.com
  3. Verify: the attacker can neither inject an inline script nor point the page at their own domain

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.

62. §22.4 Run injected scripts past a CSP

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 containssourceCSP verdict
<script src=https://js.cs161.org/app.js>*.cs161.orgALLOWED
<script src=https://apis.google.com/x.js>*.google.comALLOWED
<script>steal(document.cookie)</script>inlineBLOCKED
<script src=https://evil.com/x.js>evil.comBLOCKED — 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.

63. Which is which, by CSP verdict

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.

ALLOWED
<script src=https://js.cs161.org/app.js>; <script src=https://apis.google.com/x.js>
BLOCKED
<script>steal(document.cookie)</script>
BLOCKED — not allowlisted
<script src=https://evil.com/x.js>
g1
CSP verdict is "ALLOWED" for <script src=https://js.cs161.org/app.js>, <script src=https://apis.google.com/x.js> — that is what the table on "§22.4 Run injected scripts past a CSP" records, and it is the single property separating this group from the rest.
g2
CSP verdict is "BLOCKED" for <script>steal(document.cookie)</script> — that is what the table on "§22.4 Run injected scripts past a CSP" records, and it is the single property separating this group from the rest.
g3
CSP verdict is "BLOCKED — not allowlisted" for <script src=https://evil.com/x.js> — that is what the table on "§22.4 Run injected scripts past a CSP" records, and it is the single property separating this group from the rest.

64. §22.4 ⊕ Beyond the basics: DOM-based XSS, nonces, DOMPurify

Concept

⊕ Supplemental (beyond the core §22.4): a few things worth knowing exist but aren't required here.

65. Something is wrong here: 'CSP still allows inline scripts'

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.

66. Trap: 'CSP still allows inline scripts'

Trap

The 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.

The fix

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.

67. Which of these survive contact with L40 · Cross-Site Scripting (XSS)?

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.

Holds up
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.; 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.); By putting a <script> in the q parameter, the attacker makes the reflected response contain — and run — their JavaScript with google.com's origin.
Breaks
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.'; A student: 'For stored XSS to fire, the attacker still has to trick the victim into clicking a special crafted link.'
sound
These are stated as this lesson states them — each one survives the edge cases L40 · Cross-Site Scripting (XSS) puts it through.
flawed
Each of these is lifted from a trap in this deck: reasonable-sounding, and wrong in a way that only shows up once you rely on it.

68. §22.1–22.4 XSS vs CSRF — a sharp line

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?YESno
reads the response / DOM?yes — full accessno — fire and forget
defeats the SOP?yes (runs with site's origin)no (rides the cookie out)
severityowns the page — far worseforge 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.

69. Fill in: XSS (L40) for §22.1–22.4 XSS vs CSRF — a sharp line

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?YESno
reads the response / DOM?yes — full accessno — fire and forget
defeats the SOP?yes (runs with site's origin)no (rides the cookie out)
severityowns the page — far worseforge specific requests

70. The XSS playbook

Pattern

  1. Spot the injection point: somewhere the site puts user input into a page without neutralizing it (a comment, a search reflection, a profile).
  2. Inject the script — two flavors: STORED (save the payload server-side so it's served to every viewer, no link needed) or REFLECTED (put the payload in a crafted URL the victim must load).
  3. Why it's devastating: the injected JS runs with the TARGET site's origin (L37: a script runs with the including page's origin), so it defeats the SOP and can read the user's secrets and cookies and exfiltrate them.
  4. Defend by neutralizing input (§22.3): don't blocklist <script> (defeated by onerror and nested-tag reconstruction) — HTML-ENCODE dangerous characters (<→&lt;, >→&gt;) so they DISPLAY instead of parse, using a standardized sanitizer.
  5. Defend in depth with CSP (§22.4): send a 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.
  6. Remember the contrast: XSS runs IN the origin with full read access; CSRF (L39) only rides the cookie from outside — XSS is strictly worse.

71. Where does it stop working: The XSS playbook

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:

  1. Spot the injection point: somewhere the site puts user input into a page without neutralizing it (a comment, a search reflection, a profile).
  2. Inject the script — two flavors: STORED (save the payload server-side so it's served to every viewer, no link needed) or REFLECTED (put the payload in a…
  3. Why it's devastating: the injected JS runs with the TARGET site's origin (L37: a script runs with the including page's origin), so it defeats the SOP and…
  4. Defend by neutralizing input (§22.3): don't blocklist <script> (defeated by onerror and nested-tag reconstruction) — HTML-ENCODE dangerous characters…
  5. Defend in depth with CSP (§22.4): send a Content-Security-Policy script-source allowlist the browser enforces — it blocks INLINE scripts and any…
  6. Remember the contrast: XSS runs IN the origin with full read access; CSRF (L39) only rides the cookie from outside — XSS is strictly worse.

72. Rule out three: Checkpoint — which statement is TRUE?

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.

  • A. The Same-Origin Policy isolates the injected script, so it can't read the victim's google.com data.
  • B. Stripping every <script> tag from the input would have fully sanitized it and stopped the attack.
  • C. If google.com sent a CSP, inline scripts would still run, so a CSP can't help against this.
  • D. The script runs with google.com's origin and can read the victim's google.com secrets; HTML-encoding </> (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 (< → &lt;, > → &gt;) 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.

73. Checkpoint — which statement is TRUE?

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?

  • A. The Same-Origin Policy isolates the injected script, so it can't read the victim's google.com data.
  • B. Stripping every <script> tag from the input would have fully sanitized it and stopped the attack.
  • C. If google.com sent a CSP, inline scripts would still run, so a CSP can't help against this.
  • D. The script runs with google.com's origin and can read the victim's google.com secrets; HTML-encoding </> (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 (< → &lt;, > → &gt;) 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.

Why A tempts people
The SOP does NOT isolate the injected script. Recall L37: a script runs with the origin of the page that includes it — here google.com — so it has full access to google.com's data. XSS works precisely by defeating the SOP this way.
Why B tempts people
Stripping <script> is not sufficient: JS runs without it (e.g. <img src=1 onerror="...">), and a naive strip is defeated by nesting (<scr<script>ipt>... re-forms <script>). HTML-encoding the dangerous characters is the robust fix.
Why C tempts people
A key point of CSP is that it BLOCKS inline scripts. With a CSP in place, an injected inline <script> won't run and the page can't load script from a non-allowlisted domain — so a CSP does help.

74. Misconceptions to retire

Concept

75. Synthesis — XSS is the payoff of the web unit

Concept

76. Primary sources & where to read more

Concept

77. Connect it up: L40 · Cross-Site Scripting (XSS)

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.

78. Recap — Lesson 40

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 is22injected JS the browser RUNS with the site's origin
Why devastating22defeats the SOP; full read access to the site's secrets
Stored XSS22.1saved server-side; served to ALL viewers; no link
Reflected XSS22.2in a crafted URL; victim must load it
Sanitizing is hard22.3onerror + nested tags defeat a <script> strip
The robust fix22.3HTML-encode < and > with a standard sanitizer
CSP22.4browser-enforced allowlist; blocks INLINE scripts
vs CSRF—XSS runs IN the origin; CSRF rides the cookie out

Sources

  1. CS 161 Computer Security Textbook §22.1–22.4 — Wagner, Weaver, Kao, Shakir, Law & Ngai, UC Berkeley — XSS injects JavaScript the victim's browser runs with the target site's origin, subverting the same-origin policy (§22); stored XSS (§22.1) and reflected XSS (§22.2); and the defenses: input sanitizing via HTML-encoding (§22.3) and Content Security Policy (§22.4)
  2. OWASP Cross Site Scripting Prevention Cheat Sheet — OWASP Foundation — the primary practitioner reference for XSS defenses: context-appropriate output encoding, using a known-good sanitizer rather than rolling your own, and Content Security Policy
  3. Content Security Policy Level 3 — M. West & A. Barth (eds.), W3C Working Draft — defines the Content-Security-Policy HTTP response header: a browser-enforced allowlist of script sources that, among other things, blocks inline scripts
  4. CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') — MITRE Common Weakness Enumeration — the canonical catalog entry for XSS, covering stored, reflected, and DOM-based variants and their mitigations
  5. DOMPurify — ⊕ supplemental — Cure53, a widely-used, battle-tested HTML sanitizer library; the 'use a standardized sanitizer, never roll your own' recommendation in practice

Want this taught 1-on-1? Alexander tutors Computer Security — $55/session, free consultation.

Book on Wyzant · Text (657) 465-8108