Live Art picoCTF 2022 Solution

Published: July 20, 2023
\nEOF"}},{"@type":"HowToStep","position":3,"name":"Collect the flag from the admin's response or session","text":"After the admin bot executes your action, the flag appears in the app's state, in a response, or is exfiltrated to your listener."}]}

Description

A collaborative pixel-art web app with an admin bot. This was one of the hardest web challenges of picoCTF 2022 (500 points) and went largely unsolved. No verified public solution is available, so the page below is an honest, clearly-labeled methodology for auditing this kind of admin-bot web app, not a confirmed step-by-step solve. Treat the endpoints and payloads as illustrative until verified against the real source.

Interact with the pixel-art app to understand the drawing mechanics.

Analyze the source code for CSRF protections, XSS sinks, or race conditions.

Set up an exfiltration listener and craft a malicious payload.

bash
# Open the challenge URL in your browser and inspect the page source
bash
curl -s http://saturn.picoctf.net:<PORT_FROM_INSTANCE>/ | grep -i csrf
bash
curl -s http://saturn.picoctf.net:<PORT_FROM_INSTANCE>/ | grep -i script

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
  1. Step 1
    Audit the app for vulnerabilities
    Observation
    I noticed the challenge features a collaborative pixel-art app with an admin bot that visits URLs, which suggested the attack surface involves tricking the bot's authenticated session, pointing directly to CSRF, stored XSS, or race conditions as the relevant bug classes to audit for.
    Read the page source and JavaScript. Look for: missing CSRF tokens on state-changing requests, unsanitized innerHTML rendering of art titles or descriptions, or unprotected admin-only endpoints.
    Learn more

    CSRF (Cross-Site Request Forgery): if the app uses cookie-based authentication and doesn't validate a CSRF token, an attacker can create a page that makes requests on the victim's behalf. Example: an image tag or form submission that calls an admin action.

    XSS: if artwork names, descriptions, or chat messages are rendered without escaping, injecting a script payload will execute in the admin bot's browser when it views the art.

    Race condition: if there's a check-then-act pattern (e.g., check if pixel is owned, then write), two concurrent requests can both pass the check before either updates the state, allowing double writes or privilege escalation.

  2. Step 2
    Exploit the identified vulnerability
    Observation
    I noticed the admin bot visits attacker-supplied URLs with its privileged session cookie active, which suggested crafting a CSRF form or stored XSS payload that fires against a real state-changing endpoint to perform admin actions on the bot's behalf.
    Craft a CSRF page (if CSRF) or inject a stored XSS payload (if XSS sink found) to execute actions as the admin bot.
    bash
    # NOTE: there is no known 'give_flag' endpoint - the form below is a GENERIC
    # CSRF template to adapt only after you confirm a real state-changing endpoint
    # and parameter names in the actual source. Do not assume these exist.
    cat > exploit.html << 'EOF'
    <form id='f' action='http://CHALLENGE_URL/REAL_ENDPOINT' method='POST'>
      <input name='REAL_PARAM' value='REAL_VALUE'>
    </form>
    <script>document.getElementById('f').submit()</script>
    EOF
    What didn't work first

    Tried: Submitting the exploit page URL to the admin bot before verifying a real state-changing endpoint exists.

    The CSRF template uses placeholder endpoint and parameter names. If you submit the exploit before reading the actual source to find a real endpoint, the bot visits the page and fires a POST to a nonexistent route, returning a 404 with no visible effect. You must first confirm the actual endpoint path and parameter names from the app's JavaScript or network traffic before adapting the template.

    Tried: Using a stored XSS payload with alert() to confirm injection rather than an exfiltration payload.

    alert() executes in the admin bot's headless browser context, which has no visible window - you will never see the popup fire. The correct approach is to exfiltrate data using a fetch() or new Image().src pointing to a webhook listener (e.g., webhook.site), so you receive an out-of-band HTTP request confirming execution and carrying cookie or flag data.

    Learn more

    For a CSRF attack: host the exploit page on a public server (or use webhook.site), then report that URL to the admin bot. When the bot visits your exploit page, the embedded form or fetch() request will fire against the challenge server, carrying the bot's session cookie automatically (same-origin cookies).

    CSRF defenses include: CSRF tokens (random values bound to the session), SameSite cookie attribute (SameSite=Strict or Lax prevents the cookie from being sent in cross-site requests), and checking the Origin header.

    For a race condition exploit: send many simultaneous requests (using asyncio, threading, or Burp Repeater's parallel send feature) to win the race between the check and the update.

  3. Step 3
    Collect the flag from the admin's response or session
    Observation
    I noticed that once the admin bot executes the crafted request, the flag must surface somewhere accessible, which suggested checking the app's public state, an out-of-band webhook listener, or the admin panel directly depending on whether the exploit elevates privileges or exfiltrates data.
    After the admin bot executes your action, the flag appears in the app's state, in a response, or is exfiltrated to your listener.
    Learn more

    After exploitation, check: the app's main page for newly visible content (the flag may appear publicly), your webhook listener for exfiltrated data, or the admin panel URL directly (if the admin action gave you elevated privileges).

    Real-world collaborative apps (Figma, Miro, Notion) have extensive security reviews precisely because user-generated content is rendered to other users. Content security policies, DOMPurify sanitization, and strict CSRF protections are standard defenses. This challenge illustrates what happens without them.

Flag

Reveal flag

picoCTF{...}

Hard, largely-unsolved 500pt web challenge with no verified public solution. The steps are an honest auditing methodology (CSRF / stored XSS / race condition on the pixel logic), not a confirmed solve; there is no known give_flag endpoint, so adapt only to real endpoints found in the source.

Key takeaway

Admin-bot web challenges combine two distinct vulnerability classes: a stored injection point (XSS or CSRF) that the attacker controls, and a privileged browser session that visits attacker-supplied content. The bot acts as a proxy, executing requests with its elevated cookies against the same origin, which is why SameSite cookie policies and Content Security Policy headers are the primary defenses. The same threat model applies to real phishing campaigns, where an attacker convinces an authenticated employee to click a link that performs actions on their behalf inside a corporate application.

Related reading

Want more picoCTF 2022 writeups?

Useful tools for Web Exploitation

What to try next