Description
Final boss! Log in as admin using the SQLite-backed login form. More keywords are filtered this time, and the combined username + password input is capped at 25 characters.
Visit /filter to see the full blocklist before attempting your bypass.
Setup
Open the challenge URL and check /filter to see blocked keywords.
curl http://mercury.picoctf.net:<PORT_FROM_INSTANCE>/filterSolution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
|| concat variant that keeps the payload under the character limit. For the broader filter bypass library, see the SQL Injection for CTF guide.Step 1Enumerate the expanded blocklist
ObservationThe description names a /filter endpoint and a 25-character cap. Read the expanded blocklist first, before crafting anything.GET /filter to see every blocked term. Compared to Web Gauntlet 2, this version adds=,>,<,;,--,/*, and*/to the blocklist. Crucially, the||string-concatenation operator is still allowed, and so areIS NOTandGLOB. The twist is a hard 25-character limit on the combined username + password input.bashcurl http://mercury.picoctf.net:<PORT_FROM_INSTANCE>/filterWhat didn't work first
Tried: Trying the same 'ad'||'min' + OR 1=1 payload that worked in Web Gauntlet 2
Both OR and = are blocked this round, so the usual OR 1=1 tautology is stripped before the query runs; /filter lists both operators explicitly. You need a different always-true expression, such as 'a' IS NOT 'b', that touches nothing on the list.
Tried: Skipping /filter and guessing the blocklist from prior rounds
Each round adds new blocked terms, so the previous round's list is always incomplete. This one adds =, >, <, ;, and the comment markers on top of Gauntlet 2. Craft a payload without checking /filter and you will reach for a newly banned operator and get a silent login failure with no useful error.
Learn more
Full blocklist. The filtered terms are:
or and true false union like = > < ; -- /* */ admin. Everything else is fair game, including||,IS NOT,GLOB, and the bitwise|operator.Strategy. The same
||concatenation trick that cracked Gauntlet 2 still works here. The new challenge is staying within 25 total characters while also bypassing the password check without=orLIKE.Step 2Build 'admin' via || concatenation and bypass the password check
ObservationThe blocklist bans OR, =, and LIKE while leaving || and IS NOT alone, and the 25-character cap forces something compact. Split 'admin' with ||, and use 'a' IS NOT 'b' as the always-true password tautology, which fits exactly.Split the blocked wordadminacross two string literals joined by||: usernameadm'||'in. SQLite evaluates the concatenation before comparing, so the query seesusername='admin'without the literal word ever appearing in your input. For the password, useIS NOT(not blocked) to construct a condition that is always true:a' IS NOT 'b. Together the two fields are 21 characters, well within the 25-character cap.bash# Username: adm'||'in (9 chars) # Password: a' IS NOT 'b (12 chars) # Total: 21 chars -- well within the 25-char limit curl -X POST http://mercury.picoctf.net:<PORT_FROM_INSTANCE>/login \ --data-urlencode "username=adm'||'in" \ --data-urlencode "password=a' IS NOT 'b"A successful login redirects to a page containing the flag.What didn't work first
Tried: Using GLOB '*' as the tautology but exceeding the 25-character limit by choosing a long username split
Both username splits ('adm'||'in' and 'ad'||'min') are 9 characters each. Combined with the 12-character password 'a' IS NOT 'b' the total is 21 characters, well within the 25-char cap. No alternative split is actually required by the character limit.
Tried: Using 'OR 'a'='a or 1=1 as the password tautology since those patterns bypassed earlier gauntlet rounds
Both = and OR sit on this round's blocklist, so either form is stripped and the login silently fails back to the login page with no SQL error. IS NOT is the unblocked alternative: 'a' IS NOT 'b' evaluates true in SQLite while touching nothing banned.
Learn more
How the query looks to SQLite. The server likely runs something like:
SELECT username, password FROM users WHERE username='adm'||'in' AND password='a' IS NOT 'b'
adm'||'inevaluates toadmin, matching the row. The password half parses left to right as(password='a') IS NOT 'b': the inner comparison yields0or1, and an integer is never the same value as the text'b', so the whole expression is1regardless of the stored password.Why IS NOT works as a truth bomb. Unlike
=(blocked),IS NOTis SQLite's null-safe inequality operator, and it never returns NULL. Because it shares precedence with=and associates left to right, appending it to the password comparison wraps that comparison in a test that is true for either outcome, which is the tautology that replaces the blockedOR 1=1pattern.25-character accounting. If the limit is tight, count carefully:
adm'||'inis 9 characters,a' IS NOT 'bis 12 characters, summing to 21 (well within the 25-char cap). Alternative: usernamead'||'min(9 chars) plus a shorter tautology likea' GLOB '*(10 chars) totals 19.See SQL injection for CTF for the broader operator substitution trick library.
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{k3ep_1t_sh0rt_...}
Per-instance flag. All confirmed variants share the prefix k3ep_1t_sh0rt_ with a different hex hash suffix per team (e.g. fc8788aa1604881093434ba00ba5b9cd, ef4a5b40aa736f5016b4554fecb568d0, 30593712914d76105748604617f4006a).