Worksheet 5 — Cross-Site Scripting & Client-Side Risks (3 hrs)
← all weeks · readme · slides
Worksheet 5 — Cross-Site Scripting & Client-Side Risks (3 hrs)
Course: Software Security (KOSEN69) · Week 5 Aligned: OWASP 2025 A05 Injection · CWE-79 (XSS), CWE-352 (CSRF), CWE-1004 (cookie without HttpOnly) Signature game: ⛳ XSS Golf — fire
alert(1)in the fewest characters possible. Lower payload length = lower score = better. Par for reflected is the<img>vector; can you go under par?
⚠️ Ethics note: Use only the provided
vulnerable_app.pysandbox and your own Juice Shop container. Stealing real users' cookies or sessions is illegal. All "session theft" steps here target the sandbox cookiesession=abc123only.
Part 1 — Student Information
| Name | Student ID | Date | Group |
|---|---|---|---|
Part 2 — Lecture Questions
Answer in 2–4 sentences each.
- Distinguish reflected, stored, and DOM-based XSS by where the untrusted data is injected and when it executes. Which two does our
vulnerable_app.pyimplement, and at which routes? - How does contextual output encoding (
markupsafe.escape) stop<script>from executing? Why is HTML-context encoding different from JavaScript- or URL-context encoding? - Explain how a strict Content-Security-Policy (
script-src 'self') defeats an injected inline script even when encoding is missing. - What do the cookie flags HttpOnly, SameSite, and Secure each protect against? Map each to a concrete attack (cookie theft via XSS, CSRF, network sniffing).
- Why does CSRF (CWE-352) work even without any script injection, and how does
SameSite=Strictplus the same-origin policy blunt it?
Part 3 — Hands-on Lab (150 min)
Learning goals: land reflected + stored XSS, abuse a JS-readable cookie, build a CSRF PoC against the comment board, then prove fixed_app.py blocks all of it.
Prerequisites: Docker + Docker Compose, a browser with DevTools, a text editor. Working dir: labs/week05-xss-client-side/.
Environment setup
cd labs/week05-xss-client-side
docker compose up # python:3.12-slim + flask, runs vulnerable_app.py
# vulnerable app -> http://localhost:8080 (service name: xss-lab, port 8080)
Optional secondary target (for DOM XSS, which our app does not expose):
docker run --rm -p 3000:3000 bkimminich/juice-shop # -> http://localhost:3000
What to submit per task: the exact payload, a screenshot of the alert/effect, and a 2–3 sentence mitigation.
Task 0 — Onboarding (5 min). Browse http://localhost:8080/. Open DevTools → Application → Cookies and confirm session=abc123 is set with no HttpOnly / SameSite. Screenshot it. Deliverable: screenshot.
Task 1 — Reflected XSS + XSS Golf (30 min) ⛳.
- Goal: execute JS via
/hello, then minimize the payload. - Steps: visit
/hello?name=<script>alert(1)</script>, then the alternate/hello?name=<img src=x onerror=alert(1)>(useful when<script>tags specifically are filtered — note it's actually 3 characters longer, not shorter). Record each payload's character count for your golf score. - Deliverable: both payloads + char counts + screenshot of
alert(1)+ your lowest score.
Task 2 — Stored XSS (30 min) ⛳.
- Goal: persist a script that runs for every visitor of
/comments. - Steps: POST a comment with body
<script>alert(document.cookie)</script>(use the form orcurl -d 'body=...'). Reload/commentsand watch the cookie pop. - Deliverable: payload + screenshot of the alert showing
session=abc123+ why stored XSS is more dangerous than reflected.
Task 3 — Cookie theft via XSS (25 min).
- Goal: show the cookie is readable by injected JS because HttpOnly is missing (CWE-1004).
- Steps: store
<script>new Image().src='http://localhost:8080/hello?name='+document.cookie</script>(a beacon), or simply<img src=x onerror=alert(document.cookie)>. Observe the cookie value being exfiltrated/displayed. - Deliverable: payload + screenshot + 2–3 sentences on how HttpOnly would have stopped this.
Task 4 — CSRF PoC (30 min).
- Goal: make a third-party page force a state-changing POST to
/comments. - Steps: create a local
csrf.htmlwith an auto-submitting form targeting the board (no token exists, cookie has no SameSite, so the browser attachessessioncross-site):
<body onload="document.forms[0].submit()">
<form action="http://localhost:8080/comments" method="POST">
<input name="body" value="CSRF posted this comment">
</form>
</body>
Open the file and confirm the comment appears on /comments.
- Deliverable: the HTML + screenshot of the forged comment + why
SameSite=Strictblocks it.
Task 5 — Defend / fix it (30 min) 🛡️.
- Goal: prove
fixed_app.pyblocks Tasks 1–3, then show that Task 4's CSRF PoC still gets through and explain why. - Steps: stop the vulnerable container (
Ctrl-C), then:
docker compose run --rm --service-ports xss-lab bash -c "pip install --no-cache-dir flask && python fixed_app.py"
Re-fire each payload. Expected: /hello renders the script as text (escape, L21), stored comments render literally (Jinja autoescape, L30–33), a strict CSP header is now present as defense-in-depth (Content-Security-Policy: script-src 'self', L12 — check DevTools → Network → Response Headers; escaping already neutralizes these payloads, so no CSP violation fires in the console), and the cookie now has HttpOnly; SameSite=Strict; Secure (L42). Then re-run Task 4's csrf.html PoC against fixed_app.py: it still posts the forged comment — /comments (L25–28) never checks the session cookie or a CSRF token before accepting a POST, so hardening the cookie only stops the browser from attaching it cross-site; it doesn't stop the request itself from being processed.
- Deliverable: screenshots of escaped output + the CSP response header + the hardened cookie flags + the still-successful Task 4 forgery against
fixed_app.py, with 2–3 sentences on why cookie hardening alone doesn't close CSRF here (no server-side check tied to the cookie, and no CSRF token).
Part 4 — Reflection
- CWE/OWASP mapping: map your reflected/stored XSS to CWE-79 and your CSRF PoC to CWE-352, both under OWASP 2025 A05 Injection (CSRF historically A01/A05).
- Real breach: the 2018 British Airways breach (~380k payment records) used malicious JavaScript (Magecart) injected into the site to skim card data — a client-side script-injection failure. In 3–4 sentences relate it to this lab's XSS and CSP lessons.
- Best mitigation: between output encoding, a strict CSP, and HttpOnly+SameSite cookies, which gives the broadest defense-in-depth, and why is "encoding alone" still risky?
Grading rubric (100)
| Criterion | Points |
|---|---|
| Part 2 — Lecture questions (conceptual accuracy) | 20 |
| Part 3 — Exploitation + evidence (payloads + screenshots, Tasks 1–4) | 40 |
| Part 3 — Defense (Task 5: fixes proven, lines cited) | 25 |
| Part 4 — Reflection (CWE/OWASP mapping, breach, mitigation) | 15 |
| Total | 100 |
Evidence & Integrity (required)
- Identity proof: every screenshot/diagram must show your **
whoami/ login email / student ID and a timestamp**. Generic or borrowed evidence is not accepted. - Personalized flag (if this lab issues one): ____________________
Flags are unique per student — submitting another student's flag is a violation. See SUBMISSION.md.
- Explain in your own words (graded on your reasoning, not copied text):
- What did you do, and why did the vulnerability work?
- Why does your fix actually stop it — and what could still break it?
🤖 Audit the AI (required)
AI is a power tool you must distrust — you are graded on your critique, not the AI's answer.
- Ask an AI assistant to exploit or fix this week's vulnerability. Paste its full answer.
- Find what's wrong or risky in it — insecure code, a subtly incomplete fix, a hallucinated API/function/CVE, a missed edge case, or wrong reasoning. Quote the exact line(s).
- Produce the correct, verified version yourself and explain in 2–3 sentences why the AI's output was insufficient.
Disclose your AI use in the Part 1 table. This task counts toward your Defense + Reflection score.
🧠 Comprehension & Prompt (required)
A. Explain in Plain English (EiPE). In 2–3 sentences, in your own words, describe what this week's vulnerable code/endpoint actually does and why it is exploitable — explain the mechanism, don't dump jargon.
B. Prompt Problem. Write a single prompt that makes an AI produce a correct, secure fix for one finding. Run it: does the exploit now fail? If not, refine the prompt and try again. Submit the final prompt + the verified result. Graded on the prompt's precision and your verification — this trains problem decomposition and AI literacy (Denny et al. 2024).