aart was a web challenge worth 200 points at the 2015 GITS CTF. There were several ways to solve it, three of which will be described here.
This challenge consisted of a website that allowed the creation of user accounts, login to those accounts as well as submitting ASCII art and voting for it. Furthermore there was a link on top of the page that provided the whole source of the website (except connect.php which contained the MySQL connection information).
There are three files of interest to us, the login script, the register script and the database scheme:
The first information we obtain is that we can retrieve the flag by logging into an account which does not have
isRestricted set to TRUE. Upon further inspection we can see that the input goes through mysqli_real_escape_string sanitation. SQL injection would be possible, but only if the variables inside the SQL query are not surrounded by apostrophes. As it turns out, in all the files they are however, so SQL injection is not possible.
Looking at the register.php file, we can see two
INSERTs being executed. In the first, the new user account is simply created by inserting the username and password into the
users table. This will generate the id, as the
id column in
users is set to AUTO_INCREMENT (see schema.sql).
In the second
INSERT the privileges of this newly created account are set. For this a userid, isRestricted pair are inserted into the
privs table. The userid, however, is fetched in a subquery by comparing the username from the *$_POST variable to the one existing in
There are several ways to exploit this script, two involving race conditions and one involving truncation of usernames. During the CTF we used the latter one, but we are going to describe all three of them.
Exploit 1: Username truncation
This exploit uses the fact, that data is getting truncated by MySQL if it is above the max length of the field’s datatype.
Looking at schema.sql, we note that the username field has datatype
TEXT, in MySQL the
TEXT datatype has a capacity of 65535 bytes (see MySQL storage requirements). Everything that is longer than that, will just be truncated! Now the exploit is straight forward:
Register a new account with a username length > 65535 characters, let’s say we choose
username = 'A'*65535 + 'B'. The newly created account has its username set to ‘A’*65535, since the ‘B’ will simply be ignored due to truncation. In the subquery, the
id is selected by comparing the chosen username (i.e. ‘A’*65535 + ‘B’) to usernames in the database. But since the username inserted into the database was truncated, this subquery will return no results! Therefore there will be no entry in
privs table for our newly created account. We login using the truncated username and retrieve the flag:
this is a key is the actual flag.
Finally, a simple PoC script that will register a random username with length > 65535 and then use the truncated username to login and fetch the flag:
Exploit 2: Race condition - Double registration
In the register script there are two SQL queries that are executed one after the other with the second one depending on the first. This results in a race condition which we can exploit.
The first way to exploit this vulnerability is to register twice before the privileges are inserted into
priv table. We can see in schema.sql, that the username field is not declared UNIQUE and hence we can have two rows with identical usernames. When the subquery in the second query will try to fetch the
id for the given username, it will retrieve two rows as it does not have a
LIMIT 1 statement. The whole query will then fail with an
Subquery returned more than 1 row error and no privileges will be inserted.
We need to register twice with the same username, for this we can use the following PoC python script:
Executing that script will output the flag:
[*] Registered twice [x] fail [*] Registered twice [*] flag: 'this is a key'
Exploit 3: Race Condition - Login before privileges inserted
Similar to Exploit 2, we can also try to login before the privileges are inserted, again exploiting the race condition. The following PoC script will do exactly that and output the flag if successful:
We retrieve the flag again:
[x] fail [x] fail [*] flag: 'this is a key'
While this exploit works, it is not persistent as Exploit 1 and Exploit 2 are. Meaning that if we were to login with the created account again, it would be restricted now and not display the flag.