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 1
Inspect the filterObservationI noticed the challenge description mentioned a dedicated /filter endpoint, which suggested reading the blocklist before crafting any payload so I could see exactly which keywords and characters were 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 the word OR in all case variants, so the payload is rejected before it reaches SQLite. The blocklist is applied as a case-insensitive substring match on the raw input, meaning even splitting the letters with spaces does not help here. The || concatenation trick bypasses this because || is an operator token, not a keyword on the blocklist.
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 2
Concatenate the username with ||ObservationI noticed the blocklist blocked OR and other common keywords but did not include the || operator, which suggested using SQLite's string concatenation operator to build the string 'admin' from split fragments without ever typing the 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 3
Submit the bypass and read the flagObservationI noticed the login form POSTs credentials to /login and that a successful authentication renders the flag in the response, which suggested sending the crafted adm'||'in payload via curl so I could control the exact bytes and read the server response 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: URL-encode the pipe characters as %7C%7C and submit username=adm%27%7C%7C%27in
curl -d already applies form-encoding, so double-encoding the pipes turns || into the literal string %7C%7C in the SQL query, breaking the concatenation entirely. The server sees username='adm%7C%7C'in' which is a syntax error or a non-matching string. The single quotes still need to be percent-encoded as %27, but the || operators must be sent as literal pipe characters so SQLite can parse them.
Tried: Try sending the payload through the browser form manually and inspect the raw HTML for the flag
The flag is embedded in the server response only when login succeeds and the session is authenticated. If the form submission does not carry the correct payload (e.g. the apostrophes get escaped by a browser autofill or extension), the login fails silently and returns a generic error page. Using curl with explicit -d values is more reliable because you control the exact byte string sent and can verify the response body directly.
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.