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.
Step 1
Find the hidden debug parameter in the page sourceObservationI noticed that submitting a standard authentication-bypass payload returned a blank page instead of a login success or a visible error, which suggested the server was transforming the input in some unexpected way and that inspecting the raw page source for hidden form fields might reveal additional controls.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:bashcurl -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 from the query string. Appending ?debug=1 to the URL leaves the POST body unchanged, so the server still returns a blank page with no debug output. Include debug=1 inside the --data string alongside username and password.
Learn more
The real lever in this challenge is the hidden
debugparameter, not the encoding itself. Settingdebug=1makes the server print the SQL string it constructed. Comparing what you sent against what the server built shows that your letters were rotated:orarrives asbe, 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.Step 2
Pre-rotate your payload so it survives the transformObservationI noticed the debug output showed my keyword OR had been rotated to BE, which indicated the server applies ROT13 to the username before building the query, and since ROT13 is self-inverse, pre-rotating my payload would cause the server's own transform to restore the original 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.pythonpython3 -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.bashcurl -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 (a-z, A-Z); punctuation like apostrophes, spaces, and dashes pass through unchanged. If you ROT13 a string in Python with codecs.encode it touches only alphabetic characters, so the SQL structural characters are already correct. Over-encoding non-letter characters 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 builds its query using the username field for the injection point. Sending the payload in the password field leaves the username slot as a plain string that does not break the query, so authentication fails normally. The username field is what 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:
bebecomesORagain. 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 isnqzva'--, which the server turns back intoadmin'--.Step 3
Submit the payload and get the flagObservationI noticed the pre-rotated payload confirmed through debug output that the server reconstructed a valid OR-based injection, which indicated sending that payload for real would authenticate us 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
debugparameter 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.