Description
This website looks familiar... Log in as admin, and use the filter to find the flag. Unlike Web Gauntlet 1, the filter here blocks keywords in a case-insensitive way, catching OR, or, Or, and oR alike.
Visit the /filter endpoint to see what words and characters are blocked.
Setup
Open the challenge URL in your browser. Check /filter to see the current blocklist.
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.
OR are filtered, SQLite's || concatenation operator is the standard bypass. The SQL Injection Payload Generator (Auth Bypass tab) includes the concat bypass variant. For the full bypass library, see the SQL Injection for CTF guide.Step 1Inspect the filter
ObservationThe description mentions a dedicated /filter endpoint. Read the blocklist there before crafting anything, so you know exactly which keywords and characters are off limits.Visit /filter to read the blocklist. Common entries: OR, AND, UNION, SELECT, WHERE, --. The filter is case-insensitive for blocked keywords, so case-flipping does not help here. String concatenation (||) is the bypass route.bashcurl http://mercury.picoctf.net:<PORT_FROM_INSTANCE>/filterWhat didn't work first
Tried: Attempt a classic OR-based bypass like ' OR '1'='1 after seeing the blocklist
The filter blocks OR in every case variant, so the payload never reaches SQLite. It matches case-insensitively on raw input as a substring, so splitting the letters with spaces does not help either. The || trick gets through because it is an operator token rather than a keyword on the list.
Tried: Try mixed-case variants like Or or oR to slip past the filter
Unlike Web Gauntlet 1, the filter here is case-insensitive for blocked keywords, so 'Or', 'or', and 'oR' are all caught just like 'OR'. Checking /filter directly confirms exactly which strings are blocked before guessing.
Learn more
Assumed query. Web Gauntlet servers run a SQLite-backed login that builds the query by string interpolation, roughly:
SELECT * FROM users WHERE username='${u}' AND password='${p}'With
ORblocked you can't use the classic' OR '1'='1. SQLite has another trick: the||operator concatenates strings, and it isn't a keyword. See SQL injection for CTF for the broader bypass library.Step 2Concatenate the username with ||
ObservationThe blocklist covers OR and other common keywords but leaves the || operator alone. SQLite's concatenation can build the string 'admin' out of fragments without ever typing a blocked word.SQLite's || joins two string literals: 'adm' || 'in' evaluates to 'admin'. The split keeps the literal word 'admin' out of your input and bypasses simple word filters.Learn more
Operator precedence.
||is the SQL string-concatenation operator (in SQLite, PostgreSQL, Oracle). It binds tighter than the comparison=, so:username='adm'||'in' <==> username = ('adm' || 'in') <==> username = 'admin'With this idiom you can build any string out of allowed substrings:
'a'||'d'||'m'||'i'||'n', or pull from a SELECT subquery. None of the joined fragments contain the blocked keywords.If quotes survive but
--is filtered. Submit the password as anything and rely on the password column being NULL for theadminrow, or use another||for the password too.Step 3Submit the bypass and read the flag
ObservationThe form POSTs credentials to /login, and a successful authentication renders the flag in the response. Send the crafted payload with curl, so you control the exact bytes and can read the response body directly.POST username=adm'||'in (and any password). On success the server renders the authenticated page with the flag.bashcurl -X POST http://mercury.picoctf.net:<PORT_FROM_INSTANCE>/login \ -d "username=adm'||'in&password=x"What didn't work first
Tried: Percent-encode the payload by hand and then also pass it through curl --data-urlencode
curl -d sends the body verbatim and encodes nothing, so raw pipes and quotes already arrive intact. --data-urlencode does encode, so handing it a string you already encoded produces %257C%257C, which the server decodes back to the literal text %7C%7C rather than ||, and the concatenation never happens. Encode exactly once: raw characters with -d, or the plain payload with --data-urlencode.
Tried: Try sending the payload through the browser form manually and inspect the raw HTML for the flag
The flag appears in the response only when the login succeeds and the session is authenticated. If the submission carries the wrong payload, say a browser autofill or extension escaped the apostrophes, the login fails quietly and you get a generic error page. curl with explicit -d values is more reliable, because you control the exact bytes sent and can read the response body yourself.
Learn more
Lesson. Keyword blocklists are unfixable: there are too many ways to spell every reserved word, and SQL dialects have escape hatches like
||,CHAR(), and hex blob literals. Parameterized queries are the only correct mitigation.
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{0n3_m0r3_t1m3_...}
SQLite || concatenation bypass ('adm'||'in') to log in as admin and retrieve the flag.