Skip to main content

Who are you? picoCTF 2021 Solution

A web challenge that checks the details of your HTTP request before granting access to the flag.

Published: April 2, 2026Updated: August 13, 2026

Description

Only those who use the official PicoBrowser are allowed on this site.

Remote

Access the challenge URL and observe what the server demands.

bash
curl http://mercury.picoctf.net:38322/

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
  1. Step 1Set the User-Agent to picobrowser
    Observation
    The description says only the official PicoBrowser is allowed on the site. That is a User-Agent check, and setting the header to picobrowser is the first gate.
    The server checks User-Agent first. Set it to picobrowser. Each failed check returns the next requirement in plain text.
    bash
    curl --user-agent "picobrowser" http://mercury.picoctf.net:38322/
    What didn't work first

    Tried: Sending -H 'User-Agent: PicoBrowser' with capital letters

    The server matches 'picobrowser' case-sensitively, all lowercase. Send PicoBrowser or Picobrowser and you get the same rejection. curl's --user-agent flag sends the value exactly as typed, so type it in lowercase.

    Tried: Using a browser with a User-Agent switcher extension and navigating to the URL

    A User-Agent spoofing extension clears the first check, but a browser cannot easily set arbitrary Referer, Date, DNT, X-Forwarded-For, or Accept-Language headers on a plain navigation. You pass gate one and stall at gate two. curl with explicit -H flags gives full control over every header.

    Learn more

    The User-Agent header identifies the client software. Servers cannot trust it. Check responses look something like:

    # Wrong UA
    "Sorry, you can only access this server with picobrowser."
    
    # Right UA, wrong Referer
    "Sorry, you can only access this website by clicking through from our official site."
    
    # Right UA + Referer, wrong Date
    "Sorry, this site only worked in 2018."

    Each error message points at the next required header. Read it carefully and add one header per request. See web bug patterns for why header-based access controls always lose.

  2. Step 2Add Referer, Date, DNT, XFF, Accept-Language
    Observation
    After each accepted header the server returns a new plain-text error naming the next missing requirement. So work through the remaining five checks, Referer, Date, DNT, X-Forwarded-For, and Accept-Language, one at a time.
    Six checks total. Add each header one at a time, reading the next error message between requests. The full set: Referer matches the site itself, Date in 2018, DNT 1, X-Forwarded-For from a Swedish IP, Accept-Language Swedish.
    bash
    curl http://mercury.picoctf.net:38322/ \
      --user-agent "picobrowser" \
      --referer "http://mercury.picoctf.net:38322/" \
      -H "Date: Mon, 23 11 2018 23:23:23 GMT" \
      -H "DNT: 1" \
      -H "X-Forwarded-For: 2.71.255.255" \
      -H "Accept-Language: sv-SE"

    Expected output

    picoCTF{http_h34d3rs_v3ry_c0Ol_much_w0w_...}
    What didn't work first

    Tried: Using -H 'X-Forwarded-For: 127.0.0.1' or a random private IP for the geo-check

    The server resolves X-Forwarded-For through a GeoIP database and wants Sweden. Private ranges and most public addresses do not map there, so it tells you that you are not from the right place. Any publicly routable IP in a Swedish ISP's allocation passes; 2.71.255.255 sits in TeliaSonera's range.

    Tried: Setting Date to a full RFC 7231 timestamp like 'Mon, 23 Nov 2018 23:23:23 GMT' and still getting rejected

    The server only checks the year in the Date header, not the whole timestamp. But a malformed format, a wrong day-of-week for the date, a bad month abbreviation, stray spaces, makes some parsers reject the header outright and fail. The value in the commands uses a numeric month, which is non-standard but this challenge's parser accepts it. If the strict RFC format fails, try the numeric form.

    Learn more

    Why a Swedish IP. The error message names Sweden. Confirm an IP's country with a quick whois lookup or a MaxMind GeoIP query. 2.71.0.0/16 through 5.150.0.0 contain Swedish ISP allocations; 2.71.255.255 works because it falls in TeliaSonera's assignment. Any IP that GeoIP resolves to SE passes.

    HTTP headers, by purpose:

    • Referer: the URL the client came from. Same-origin check here.
    • Date: request time. Server checks the year is 2018.
    • DNT: Do Not Track. 1 requests no tracking.
    • X-Forwarded-For: original client IP through a proxy. The server uses this for geo-restriction (a Swedish IP passes).
    • Accept-Language: preferred response language. sv-SE for Sweden.

    Case sensitivity. HTTP header names are case-insensitive (User-Agent, user-agent, and USER-AGENT all match). Values are case-sensitive: picobrowser and PicoBrowser are different strings as far as a strict equality check is concerned. Match the exact value the server demands.

Interactive tools
  • Flask Session DecoderDecode Flask / itsdangerous session cookies. Splits payload, decompresses zlib, parses JSON, and verifies the HMAC signature when given the secret.

Flag

Reveal flag

picoCTF{http_h34d3rs_v3ry_c0Ol_much_w0w_...}

The server validates six HTTP headers in sequence. Each wrong header reveals the next requirement, making this a progressive enumeration challenge.

Key takeaway

HTTP headers like User-Agent, Referer, X-Forwarded-For, and Accept-Language are all client-supplied and trivially forged with curl or a browser extension. Any server-side access control that relies solely on header values for identity or geo-restriction can be bypassed by any requester who knows what values to send. Real geo-restriction requires IP-level enforcement at the network layer, not trusting proxy headers that clients write themselves.

Related reading

Tools used in this challenge

Where to go next