Skip to content

Instantly share code, notes, and snippets.

@marcogrcr
Last active October 13, 2023 17:49
Show Gist options
  • Save marcogrcr/2be05cb35196e63e46b066119fbadb8e to your computer and use it in GitHub Desktop.
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
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>&lt;frame&gt;</code> and <code>&lt;iframe&gt;</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>&lt;iframe&gt;</code> with CSP enabled.</td>
</tr>
<tr>
<td><a href="/iframe-no-csp">/iframe-no-csp</a></td>
<td>Tests <code>&lt;iframe&gt;</code> with CSP disabled.</td>
</tr>
<tr>
<td><a href="/frame-csp">/frame-csp</a></td>
<td>Tests <code>&lt;frame&gt;</code> with CSP enabled</td>
</tr>
<tr>
<td><a href="/frame-no-csp">/frame-no-csp</a></td>
<td>Tests <code>&lt;frame&gt;</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