Skip to main content

Irish-Name-Repo 3 picoCTF 2019 Solution

Bypass a web login that filters common attack inputs by finding alternative ways to manipulate the query.

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

Description

Even more SQL injection. A plain authentication-bypass payload returns a blank page instead of logging you in. The twist is hidden in the page source: a debug parameter that, when enabled, shows the server is transforming your input before it reaches the query.

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
This is the third and hardest challenge in the Irish-Name-Repo series. Work through Irish-Name-Repo 1 and Irish-Name-Repo 2 first. The SQL Injection for CTF guide covers authentication bypass, including challenges where the input is encoded before injection. The SQL Injection Payload Generator can generate the pre-ROT13 payloads - encode the output before submitting.
  1. Step 1Find the hidden debug parameter in the page source
    Observation
    A standard authentication-bypass payload returns a blank page, not a login success and not a visible error. Something is transforming the input, so the raw page source is worth checking for hidden form fields.
    Submit a normal authentication-bypass payload (for example ' or 1=1 - ) and notice you get a blank page rather than a login or a clear error. View the source of the login page: there is a hidden form field named debug with value 0. Resend the request with debug=1 to turn on the server's debug output, which prints the SQL query it actually built - and you can see your input has been mangled by a letter rotation before it was placed in the query.
    bash
    # In the browser: View Source on the login page, find <input ... name="debug" value="0">
    bash
    # Resend the POST with debug flipped on so the server echoes the built query:
    bash
    curl -s 'http://<host>:<PORT_FROM_INSTANCE>/login.php' \
    bash
      --data "username=test&password=' or 1=1 -- &debug=1"

    Expected output

    ' be 1=1 -- 
    What didn't work first

    Tried: Send ' or 1=1 - directly as the username without enabling debug, hoping the blank page is just a CSS glitch

    The blank page is intentional - the server applies ROT13 to your input before building the query, so the keyword OR becomes BE and the injection never fires. You need debug=1 to see the mangled query and understand what transformation is happening.

    Tried: Try adding debug=1 as a GET parameter in the URL rather than as a POST body field

    The PHP script reads debug from the POST body, not the query string. Appending ?debug=1 to the URL leaves the body unchanged, so the server still returns a blank page. Put debug=1 inside the --data string alongside username and password.

    Learn more

    The real lever in this challenge is the hidden debug parameter, not the encoding itself. Setting debug=1 makes the server print the SQL string it constructed. Comparing what you sent against what the server built shows that your letters were rotated: or arrives as be, the whole alphabet comes back shifted. That is ROT13 applied to your input before it hits the query.

    ROT13 is a Caesar cipher that rotates each letter by 13 positions; applying it twice returns the original. Crucially it touches only letters - digits, spaces, and punctuation (including SQL characters like ' and -) pass through unchanged. So the SQL structure of your payload survives, but any letters (table names, keywords like OR) are scrambled.

  2. Step 2Pre-rotate your payload so it survives the transform
    Observation
    The debug output shows the keyword OR came back as BE, so the server ROT13s the username before building the query. ROT13 is self-inverse, so pre-rotating the payload makes the server's own transform restore the SQL keywords.
    Take the authentication-bypass payload that worked in part 2 and ROT13 its letters before sending it. Because ROT13 is self-inverse, the server's rotation restores the original keywords. The apostrophe, dashes, spaces, and digits are unaffected, so the SQL structure stays intact. For example ' or 1=1 - has its letters rotated to ' be 1=1 - ; when the server ROT13s that back, OR returns and the injection runs.
    python
    python3 -c "import codecs; print(codecs.encode(\"' or 1=1 -- \", 'rot_13'))"
    bash
    # Send the rotated string; the server ROT13s it back to a valid SQLi payload.
    bash
    curl -s 'http://<host>:<PORT_FROM_INSTANCE>/login.php' \
    bash
      --data "username=' be 1=1 -- &password=x"
    What didn't work first

    Tried: ROT13-encode the entire payload including the apostrophe and dashes, expecting the punctuation to also need encoding

    ROT13 only shifts letters; apostrophes, spaces, and dashes pass through unchanged. Python's codecs.encode behaves the same way, so the SQL structural characters are already correct. Over-encoding the non-letters is a no-op here, and the real payload is exactly ' be 1=1 - .

    Tried: Send the rotated payload in the password field instead of the username field

    The debug output shows the server uses the username field as the injection point. Put the payload in the password field and the username slot stays a plain string that never breaks the query, so authentication just fails. Only the username gets ROT13'd and interpolated into the WHERE clause.

    Learn more

    The trick is to pre-image the transformation. You ROT13 only the letters of your payload, so after the server applies its own ROT13 the keywords come back to life: be becomes OR again. The non-letter SQL syntax (apostrophe, spaces, digits, dashes) is identical before and after rotation, so the injection structure is never disturbed.

    If you want a literal username match instead, the same logic applies: admin'-- rotated is nqzva'--, which the server turns back into admin'--.

  3. Step 3Submit the payload and get the flag
    Observation
    With the payload pre-rotated, the debug output shows the server reconstructing a valid OR-based injection. Sending it for real should authenticate and return the flag.
    Send the pre-rotated payload as the username. The server ROT13s it back into a valid SQL injection, the broken query authenticates you, and the flag is returned. You can leave debug on while testing to confirm the reconstructed query looks right.
    Learn more

    This challenge shows that a transformation on input is not a security control: the injection is unchanged once you account for the rotation. Two lessons stack here - a hidden debug parameter handed you the server-side behavior, and a reversible transform on input does nothing to stop injection. The real fix is parameterized queries regardless of any encoding applied to the input.

Interactive tools
  • SQL Injection Payload GeneratorGenerate SQL injection payloads for auth bypass, UNION extraction, blind SQLi, NoSQL operator injection, and sqlmap commands. Supports MySQL, PostgreSQL, SQLite, and MSSQL.

Flag

Reveal flag

picoCTF{3v3n_m0r3_SQL_...}

ROT13-encode your SQL injection payload before submitting - the server ROT13-decodes it before placing it in the query.

Key takeaway

A reversible transformation applied to input before it reaches a query does not stop SQL injection. An attacker who can watch the server through a debug parameter identifies the transformation and pre-images the payload, so the server's own decoding hands it valid SQL. The only real fix is parameterized queries, which keep data and code separate whatever encoding the input carries.

Related reading

Tools used in this challenge

Where to go next