Unfortunately, I solved this challenge only 30 minutes after the CTF ended. Here is my write-up about the challenge.
This is a fixed challenge of disconnection
.
In this challenge, we are given source code like this:
import express from "express";
const html = `
<h1>XSS Playground</h1>
<script>eval(new URLSearchParams(location.search).get("xss"));</script>
`.trim();
express()
.use("/", (req, res, next) => {
res.setHeader(
"Content-Security-Policy",
"script-src 'unsafe-inline' 'unsafe-eval'; default-src 'none'"
);
next();
})
.get("/", (req, res) => res.type("html").send(html))
.all("/*", (req, res) => res.socket.destroy()) // disconnected
.use((err, req, res, next) => {
// revenge!
res.socket.destroy(); // disconnected
})
.listen(3000);
The interesting part about this source code is in the Content Security Policy (CSP):
res.setHeader(
"Content-Security-Policy",
"script-src 'unsafe-inline' 'unsafe-eval'; default-src 'none'"
);
The disconnect mechanism:
.all("/*", (req, res) => res.socket.destroy()) // disconnected
.use((err, req, res, next) => {
// revenge!
res.socket.destroy(); // disconnected
})
There's also a code injection vulnerability here that allows us to gain XSS:
const html = `
<h1>XSS Playground</h1>
<script>eval(new URLSearchParams(location.search).get("xss"));</script>
`.trim();
The flag is located in the /cookie
path, as shown in this snippet of the source code:
await page.setCookie({
name: "FLAG",
value: FLAG,
domain: APP_HOST,
path: "/cookie", // 🍪
});
The interesting part is that we can't get the flag easily by accessing /cookie
directly using XSS and get the cookie from that. The disconnect mechanism causes us to be instantly disconnected when accessing any URL except /
, resulting in a browser error. Therefore, when we try to access URLs like /cookie
, the browser will instantly error ERR_EMPTY_RESPONSE, and the origin will be null as shown in the image below.