Last active
October 13, 2023 17:49
-
-
Save marcogrcr/2be05cb35196e63e46b066119fbadb8e to your computer and use it in GitHub Desktop.
Content-Security-Policy: `child-src data:` for allowing `data:` URLs in workers but mitigate malicious `<iframe>`/`<frame>` elements
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { createServer } from "node:http"; | |
const CSP = | |
"default-src 'none'; child-src data:; frame-src https://www.example.com; script-src 'nonce-abc'; worker-src data:"; | |
const INDEX_PAGE = ` | |
<!doctype html> | |
<html> | |
<head> | |
<meta charset="utf-8"/> | |
</head> | |
<body> | |
<h1>CSP proof of concept</h1> | |
<p>This proof of concept tests the implications of using the following CSP:</p> | |
<code>${CSP}</code> | |
<p> | |
In particular, we want to evalute the implications of setting the | |
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/child-src">child-src</a> | |
directive to <code>data:</code> for allowing web workers in old browsers where the | |
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/worker-src">worker-src</a> | |
directive is not supported and how the risk of <code><frame></code> and <code><iframe></code> elements with | |
<code>data:</code> URLs is mitigated by the use of the | |
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-src">frame-src</a> | |
directive. | |
</p> | |
<p>Use the following links below to test the different scenarios:</p> | |
<table> | |
<tr> | |
<th>URL</th> | |
<th>Description</th> | |
</tr> | |
<tr> | |
<td><a href="/iframe-csp">/iframe-csp</a></td> | |
<td>Tests <code><iframe></code> with CSP enabled.</td> | |
</tr> | |
<tr> | |
<td><a href="/iframe-no-csp">/iframe-no-csp</a></td> | |
<td>Tests <code><iframe></code> with CSP disabled.</td> | |
</tr> | |
<tr> | |
<td><a href="/frame-csp">/frame-csp</a></td> | |
<td>Tests <code><frame></code> with CSP enabled</td> | |
</tr> | |
<tr> | |
<td><a href="/frame-no-csp">/frame-no-csp</a></td> | |
<td>Tests <code><frame></code> with CSP disabled.</td> | |
</tr> | |
</table> | |
</body> | |
</html> | |
`; | |
const ATTACKER_PAGE = ` | |
<!doctype html> | |
<html> | |
<head> | |
<meta charset="utf-8"/> | |
</head> | |
<body> | |
<p>If you're seeing this, the attack succeeded.</p> | |
</body> | |
</html> | |
`; | |
const WORKER_CODE = ` | |
globalThis.postMessage("Hello from the worker!"); | |
`; | |
const IFRAME_PAGE = ` | |
<!doctype html> | |
<html> | |
<head> | |
<meta charset="utf-8"/> | |
</head> | |
<body> | |
<iframe src="https://www.example.com"></iframe> | |
<iframe src="data:text/html;base64,${btoa(ATTACKER_PAGE)}"></iframe> | |
<script nonce="abc"> | |
const worker = new Worker("data:text/javascript;base64,${btoa(WORKER_CODE)}"); | |
worker.addEventListener( | |
"message", | |
({ data }) => alert("If you're seeing this, loading the worker using a data: url worked. Got the following from the worker: " + data), | |
{ once: true } | |
); | |
</script> | |
</body> | |
</html> | |
`; | |
const FRAME_PAGE = ` | |
<!doctype html> | |
<html> | |
<head> | |
<meta charset="utf-8"/> | |
<frameset cols="50%,50%"> | |
<frame src="https://www.example.com"/> | |
<frame src="data:text/html;base64,${btoa(ATTACKER_PAGE)}"/> | |
</frameset> | |
</head> | |
</html> | |
`; | |
const REGEX = /^\/(?<iframe>i)?frame(?<cspDisabled>-no)?-csp$/; | |
const server = createServer(async (req, res) => { | |
let regexResult; | |
if (req.url === "/") { | |
res.setHeader("content-type", "text/html"); | |
res.write(INDEX_PAGE); | |
} else if ((regexResult = REGEX.exec(req.url))) { | |
res.setHeader("content-type", "text/html"); | |
if (!regexResult.groups["cspDisabled"]) { | |
res.setHeader("content-security-policy", CSP); | |
} | |
if (regexResult.groups["iframe"]) { | |
res.write(IFRAME_PAGE); | |
} else { | |
res.write(FRAME_PAGE); | |
} | |
} else { | |
res.statusCode = 404; | |
} | |
res.end(); | |
}); | |
server.listen(8080); | |
console.log("Server started on: http://localhost:8080"); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment