Content-Security-Policy: script-src 'self'; object-src 'none'; img-src 'self' data:
No avatar yet — upload one below.
The uploader carries its own type check — a blacklist ("no HTML, no PHP"). Whether it covers every executable type is your homework, not your assumption.
<script>document.domain</script> → dead. Console names script-src (and the page's own theme bootstrap dies the same way — no 'unsafe-inline' here).<script src="https://nahamsec.com/one.js"></script> → console violation; the request never even leaves the tab — origin not in the allow-list.<script src="/csp/lab21/123.js"></script> → 404, but the network tab shows the request allowed. 'self' is origin-wide: any path, existing or not..html, .php) → rejected. Types on the list are dead ends.save as → evil.js → accepted. Blacklists miss JavaScript constantly — uploads are usually audited for "traversal and weird HTML", not for JS.uploads/evil.js — alert(1) alone is a working script body.<script src="uploads/evil.js"></script> — same origin = allowed by the policy, and the code was ours. Wrap-up: when directives can't be flipped and no gadget serves your JS, the next best thing is always a way to write onto an allowed origin.<script src="https://nahamsec.com/one.js"></script> (blocked: external origin)
<script src="/csp/lab21/123.js"></script> (404 — but policy-approved: 'self')
The second tag failing with 404 but passing CSP is the whole insight: origin matching has no idea what exists at the path. A 404 means "allowed but empty" — you need to give it something to serve.
file: anything (bytes don't matter yet) · save as: evil.js
alert(1)
<script src="uploads/evil.js"></script>
Upload once to confirm the path, then re-upload evil.js containing alert(1) — repeated uploads under one name work like versioned updates. The final script tag uses a relative src: same origin, so the policy approves it, and the planted file executes. Try alert(document.cookie||'no cookies here') as body #2 while you're at it.
rejected: evil.html · evil.php · evil.svg · evil.php.js?? → no: evil.php.* is caught, evil.js sails
The uploader rejects names ending in the classic dangerous list (html/htm/svg/php/phtml/phar/asp/jsp/cgi/exe/sh…) — including double extensions (shell.js.php) — and refuses dotfiles (.htaccess, .user.ini). It does NOT reject .js — the exact oversight the lesson dwells on: upload filters hunt HTML/PHP, and JavaScript slips underneath.
Uploads on an allowed origin
Content-Security-Policy