Hack into skynet to save the world, which way do you prefer?
Note: Skynet is a blackbox detection engine which is not provided. But you don’t have to guess.
Note2: Scanner or sqlmap NOT REQUIRED to solve this challenge, please do not use scanners.
First impressions
For this challenge, we are given a link to a website and the source code for the back-end Flask application.
The website brings you to a login page:
A quick attempt at a SQL injection on this page doesn’t seem to work. The code supports this theory, as passwords are hashed before being sent to the query.
The only SQL-injectable part of the code appears to be in the query_kill_time() function. The name = '{}' part of the query, along with the format() call, means that the user-provided name value from the form body can be directly injected into the SQL statement.
This query_kill_time() function is only called if we have a valid SessionId cookie, something we can only get by logging in. Therefore, we need to somehow log in before we can do any sort of injection.
Logging in
Upon closer inspection of the query_login_attempt() function we see that its login logic is sort of backwards. It first queries for the password in the database, then checks if the username given by the user matches the username associated with the result of the query.
If we send a random, invalid, password along with an empty username, the SQL query will return an empty username from the database. This empty username will match with our empty username and we will “log in”. This provides us with a SessionId that we can use to start querying the query_kill_time() function.
The following curl command illustrates this:
We can now use the SessionId cookie of a706967504e8247d98ad7e583a11e002 in our future requests.
SQL injecting
Now that we have a SessionId cookie we can access the vulnerable SQL query. We can inject using the name key from the form data of a POST request. The problem is the skynet_detect() function seems to do some sort of injection filtering, blocking more obvious injections.
It seemed that by sending the POST request form in the multipart/form-data format we were able to get better results. The following curl was our first PoC of a working injection:
By injecting UNION commands we can start to enumerate the database. To pull out the table names we can run the following curl multiple times, changing the OFFSET each time:
We can also pull out the column names in each table using a similar method:
The following Python 3 script was written to do this enumeration automatically:
The access_key and secret_key column names stood out in the target_credentials table. Modifying our curl command allowed us to pull from those two columns. Using an OFFSET of 0 gave us the flag.