$ cat writeup.md…
$ cat writeup.md…
fortid2026
Task: static HTML page with a 'runaway' button gimmick; the flag is baked into inline client-side JavaScript. Solution: read page source, ignore the UI distraction, and reproduce the join -> reverse -> base64-decode transformation offline.
The flag is only one click away... if you can catch it! 🏃
A single static HTML page titled "Catch Me If You Can" is served at a static
uploads/.../index.html URL. The page shows a "runaway" button that moves away
whenever the mouse approaches. The goal is to recover the flag (format
FortID{...}).
The entire challenge is client-side — there is no server-side logic to attack. Viewing the page source reveals two independent pieces:
A runAway() handler attached to the button's mouseenter event. It merely
repositions the button to a random screen location. This is a pure UI
distraction: you never actually need to "catch" or click the button.
The flag itself is derived entirely inside the button's click handler — it is not fetched from any endpoint. The relevant logic:
scrambled string.atob([...scrambled].reverse().join("")), i.e. it
reverses the character order of the joined string and then base64-decodes
the result.Because the transformation uses only data already present in the page, the flag can be reconstructed offline without running the browser or interacting with the moving button.
curl the index.html URL, or use the browser's
"View Source" / DevTools).<script>. Note that the flag comes from the click
handler, not from a network request — the mouseenter/runAway behaviour is
a red herring.FortID{...} format.#!/usr/bin/env python3 # Reproduce the client-side flag derivation offline. import base64 # The three fragments exactly as they appear in the inline <script>. fragments = [ "=0XNx81Nx81auFDa38Vdwk3", "X1QzXkJHNo91N0g2NfdDMu9V", "Nx8lYzc3eElEdy9mR", ] scrambled = "".join(fragments) # JS: atob([...scrambled].reverse().join("")) -> reverse then base64-decode decoded = base64.b64decode(scrambled[::-1]).decode() print(decoded) # -> FortID{REDACTED}
Equivalent one-liner in the browser console (no clicking required):
atob([..."=0XNx81Nx81auFDa38Vdwk3"+"X1QzXkJHNo91N0g2NfdDMu9V"+"Nx8lYzc3eElEdy9mR"].reverse().join(""))
Use this approach when:
atob, string
.reverse(), array .join(), concatenated fragments) rather than fetching it
from a server.cat /etc/motd
Liked this one?
Pro unlocks every complete writeup and expanded API access. $9/mo.
$ grep --similar