Description
Can you beat the filters? Log in as admin across 5 progressive rounds, each adding more filtered SQL keywords.
Check /filter.php to see what is being filtered in each round. The flag is in a comment in that page after completing all rounds.
Setup
Navigate to the Web Gauntlet challenge URL and open the login page.
Open the /filter.php page on the same host to see exactly which keywords are blocked in the current round.
Always check the raw HTTP response to see what query was executed.
If your cookie keeps getting reset, use a private browser window.
Solution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
|| operator bypass used in later rounds when OR is blocked.Step 1
Round 1 - filter: orObservationI noticed filter.php showed only the keyword 'or' was blocked, which suggested that a SQL comment (--) could truncate the query after the username and bypass the password check without needing any boolean operator at all.Only 'or' is filtered. Use a comment to bypass the password check. Enter admin'-- as the username. The SQL becomes: SELECT * FROM users WHERE username='admin'--' AND password='...' and the - comment drops everything after it.bashUsername: admin'--bashPassword: anythingWhat didn't work first
Tried: Trying ' OR '1'='1 as the username to bypass authentication
The filter blocks the word 'or' (case-insensitive), so the payload is stripped or rejected before the query runs. The server returns an error or simply fails to log in. The '--' comment approach works because it does not require any boolean operator - it just ends the query after the username check.
Tried: Using admin' # as the username (MySQL hash comment instead of --)
The # character is a comment syntax in MySQL but the database here is SQLite, which does not recognize # as a comment marker. The query syntax error causes login to fail. SQLite supports - line comments and /* */ block comments, so - is the correct syntax to use.
Learn more
SQL injection occurs when user-supplied data is interpolated directly into a SQL query without sanitization. The single-quote closes the string literal, and
--is a SQL line comment that causes the database to ignore the rest of the line, including theAND password='...'check.The fix is parameterized queries (prepared statements), where the user value is passed as a data argument and can never be interpreted as SQL syntax.
Step 2
Round 2 - filter: or, like, =, --ObservationI noticed the Round 2 filter.php added '--' and '=' to the blocklist, which suggested switching to C-style block comments (/* */) that were not yet filtered and could swallow the password clause without using any blocked token.Double-dash comments and equality operators are now blocked, but C-style block comments (/* */) are not. Open a block comment in the username field with /*. The database sees SELECT ... WHERE username='admin' and then the /* swallows the rest of the line, including ' AND password='...' entirely. No manipulation of the password field is needed.bashUsername: admin'/*bashPassword: anythingWhat didn't work first
Tried: Reusing the Round 1 admin'-- payload since - is not blocked yet in the filter list
Round 2 explicitly adds - to the blocklist, so the payload is rejected. The filter.php page shows the updated list; - is now filtered. Block comments (/* */) are the correct alternative since they achieve the same comment effect without using the blocked token.
Tried: Trying admin' OR '1'='1 with a space to get past the = block
Both 'or' and '=' are blocked in Round 2, so this payload hits two filter rules at once. Even bypassing one would not help because the other still triggers. The block comment approach sidesteps both restrictions entirely by discarding the password clause rather than overriding it with a boolean expression.
Learn more
The SQL template is roughly:
SELECT * FROM users WHERE username='{username}' AND password='{password}'. With the payload above, it becomes:WHERE username='admin'/* AND password='anything'The block comment opener
/*causes the database to treat everything after it as a comment until a matching*/is encountered. Since no*/appears, the entire remainder of the query (includingAND password='anything') is discarded. The database simply checks whether a user named admin exists.Block comments are a standard SQL feature and are not filtered in this round, making them a direct drop-in replacement for the now-blocked
--line comment.Step 3
Round 3 - filter adds spaceObservationI noticed the updated filter.php added a space character to the blocklist, which suggested using the cross-field block comment trick (username opens /* and password supplies */) so no spaces are needed anywhere in the injected tokens.Spaces in user input are now filtered. The cross-field block comment trick still works and requires no spaces in the input. Reuse the same pattern: username opens the comment, password closes it and concatenates an empty string. Since the space filter applies to user input, not to the SQL template itself, the injected SQL still evaluates correctly.bashUsername: admin'/*bashPassword: */||'What didn't work first
Tried: Sending admin'/* in the username and leaving the password blank or as anything
With the space filter active, the password field value 'anything' does not close the block comment that was opened in the username. The resulting SQL has an unclosed /* which causes a parse error and login fails. The password must supply the */ to close the comment and make the query syntactically valid.
Tried: Using a tab character instead of a space to bypass the space filter
The filter implementation in this challenge blocks the space character specifically, but tab is also whitespace that SQL accepts as a separator. However, HTML form fields strip or normalize whitespace inconsistently, and the exact filter behavior may also catch tabs. The block comment approach avoids the need for any whitespace character in the injected tokens entirely.
Learn more
The resulting SQL is:
WHERE username='admin'/* AND password='*/||''. The comment eats the AND clause. The||''concatenates an empty string to admin, which is a no-op. The query selects the admin user with no password check.The space filter only looks at the raw input strings. Once inside the database parser, SQL has no memory of where the space came from. The block comment itself provides the needed separator without using any space character.
Step 4
Round 4 - filter adds 'admin'ObservationI noticed filter.php added the literal string 'admin' to the blocklist, which suggested using SQLite's || concatenation operator to split the word into fragments ('adm'||'in') that the filter never recognizes as the complete username.The literal string 'admin' in user input is now filtered. Use SQLite's string concatenation operator || to rebuild the word inside the username field itself: enter adm'||'in'/* as the username. The filter scans the raw input and sees only the fragments 'adm' and 'in', not the complete word 'admin'. The database evaluates the concatenation at query time and matches the admin row.bashUsername: adm'||'in'/*bashPassword: anythingWhat didn't work first
Tried: Using CONCAT('adm','in') instead of the || operator to rebuild the username
SQLite does not have a CONCAT() function; it uses || for string concatenation. CONCAT() is a MySQL/PostgreSQL function. Calling it in SQLite produces an 'undefined function' error and the query fails. The || operator is the correct SQLite-compatible concatenation syntax.
Tried: Using 'ADMIN' (uppercase) instead of splitting the word, since the filter might be case-sensitive
The admin account in the database is stored as lowercase 'admin', and SQLite's WHERE clause comparisons on text are case-sensitive by default for the LIKE operator but exact-match equality (=) is also case-sensitive. Logging in as 'ADMIN' finds no matching row. The split-and-concatenate technique reconstructs exactly 'admin' (lowercase) so the row lookup succeeds.
Learn more
The resulting SQL is:
WHERE username='adm'||'in'/* AND password='anything'. SQLite evaluates'adm'||'in'as the stringadmin, which matches the stored username. The/*block comment then discards the password check entirely.This technique works because the filter operates on the raw input string before the SQL engine sees it. By the time the database receives the query, the full word has been reconstructed through an operator the filter does not block.
Step 5
Round 5 - filter adds unionObservationI noticed filter.php only added 'union' to the Round 5 blocklist, and reviewing the Round 4 payload showed it never contained UNION, which suggested re-testing the same adm'||'in'/* payload before attempting any new approach.UNION is now blocked, but the exploit from Round 4 does not use UNION at all. Send exactly the same payload. It succeeds unchanged.bashUsername: adm'||'in'/*bashPassword: anythingWhat didn't work first
Tried: Trying a UNION-based payload to dump the users table since UNION is newly mentioned in the filter
The UNION keyword is now blocked, so any payload containing it is stripped before reaching the database. More importantly, authentication bypass does not require UNION at all. The Round 4 concatenation-plus-block-comment payload works unchanged in Round 5 because it never used UNION.
Tried: Crafting a new payload that avoids UNION by using a subquery (SELECT) instead
Adding UNION to the filter does not introduce any new restriction on the Round 4 bypass technique. Attempting to design a subquery-based payload from scratch adds unnecessary complexity when the existing adm'||'in'/* payload already passes all five round filters. Always re-test the previous working payload before trying a new approach.
Learn more
The Round 4 payload reconstructs the username via concatenation and comments out the password check. Neither of those techniques involves UNION, so the new filter has no effect on it.
This progression through five rounds mirrors real-world iterative patching: each restriction closes one technique while leaving others open. The correct fix is parameterized queries, which would have stopped every round with a single change.
Step 6
Retrieve the flag from /filter.phpObservationI noticed the challenge description stated the flag is hidden in a comment on /filter.php after completing all rounds, which suggested curling or browsing that path directly to read the embedded PHP comment containing the flag.After completing all five rounds, navigate to /filter.php on the same host. The page displays the full PHP source code of the filter, including a PHP comment containing the flag.bashcurl http://<host>/filter.phpLearn more
The flag is embedded as a comment in the filter source code. The base of the flag is
picoCTF{y0u_m4d3_1t_...}where the trailing suffix is a 32-character hex hash unique to your challenge instance. The page also shows all the filters applied across the five rounds, confirming that space was introduced in Round 3 as suspected.Leaving diagnostic or filter-listing pages publicly accessible is a Security Misconfiguration vulnerability. In a real application, such pages should require authentication or be removed from production entirely.
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{y0u_m4d3_1t_...}
The trailing suffix is a 32-character hex hash generated per challenge instance (e.g. a5f58d5564fce237fbcc978af033c11b). Your exact flag will differ. Rounds 2-5 use a /* block comment in the username to discard the AND password clause entirely. Rounds 4-5 additionally split the word 'admin' via the || concatenation operator (adm'||'in'/*) so the filter never sees the complete word in the raw input.