Web Gauntlet - Web
Challenge: Web Gauntlet 2 & 3
Author: Shotokhan
Description: SQL injection filter bypass
CTF: picoCTF 2021
Category: Web
Writeup
We have a login form, and we need to perform login as admin.
The challenge also gives us a /filter.php endpoint to look for filtered expressions:
Filters: or and true false union like = > < ; -- /* */ admin
Filters are the same for both challenge 2nd and 3rd, but in the 3rd one we have a limit of 25 characters.
From the filter list, it’s clearly a SQL injection challenge.
Head to Burp Suite, from a few tests we can see that both username and admin are vulnerable to the injection, and filters apply to both of them.
Note that “password” is filtered because “or” is filtered.
We can also see that input length is “combined input length”, which of course is much more limiting.
Although it wasn’t specified in 2nd challenge description, we can check that this one has a combined input length limit, too; the limit is of 35 characters.
From the hints, we see that the underlying DB is sqlite; we can also guess it from filters, because there are the filters for sqlite’s comments.
At this point, the first idea is to bypass the filter on admin using a string concatenation, which is done with the || operator in sqlite.
Therefore, as user parameter we pass: ad'||'min
To avoid getting filtered, we send “pass” as password, and we see that the server leaks the SQL query being made in backend:
Now that we know the query, we can open a sqlite3 shell with an in-memory database to do some offline tests.
The first thing to do is to create a table.
Since the query is: SELECT username, password FROM users WHERE username='username' AND password='pass';
We create a table like that:
CREATE table users (username string, password string);
Then, we insert a row with username “admin” and a random password:
INSERT into users (username,password) values ('admin', 'asndaisndiasdn');
At this point we’re ready to do offline tests.
Since “union” operator is filtered, we look for other set operators in sqlite documentation, and we find “except”:
We use ad'||'min' EXCEPT SELECT 0,0 FROM users WHERE '1
as user and a blank password, building the following query:
SELECT username, password FROM users WHERE username='ad'||'min' EXCEPT SELECT 0,0 FROM users WHERE '1' AND password='';
It works locally, but it has too many characters for both challenges.
What are we doing wrong here is: we can inject on both parameters, but we’re trying to bypass the filter injecting on username only.
We can’t use operators like OR and AND on password because they’re filtered, so let’s try to use concatenation again.
We know that the right password is stored in “password” column, and if we did the following:
SELECT username, password FROM users WHERE username='ad'||'min' AND password=password;
It would pass for sure.
To achieve that, we need string concatenation, so we use ad'||'min
as username and '||password||'
as password, building:
SELECT username, password FROM users WHERE username='ad'||'min' AND password=''||password||'';
Again, it works locally but we’re very unlucky because the filter on OR also filters passwORd.
At this point, what we can do is to look for some operator which has precedence over AND but that is not filtered.
Operators “IS” and “IS NOT” are good candidates.
Well, we use the same username, and as password we use a' IS NOT 'b
, building:
SELECT username, password FROM users WHERE username='ad'||'min' AND password='a' IS NOT 'b';
It works both locally and remotely, with both challenges (we solved both challenges with the same payload!)
The last thing to do to get the flag is to visit /filter.php within the same session:
We thought it was useful to also add a little script to the writeup: