In this post, we’re going to be looking at Level 15 of the Natas wargame, hosted by Over The Wire.
What’s Going On?
Upon logging in, we are presented with another form. This one prompts us for a username, and provides a “check existence” button. A quick test of the form with a random username, such as “asdf”, and an assumed valid one, such as “natas16”, gives us a page which tells us the first user doesn’t exist and that the second one does. To get a better understanding from here, let’s follow the link to the source code, and take a look at the PHP.
We can see that the PHP source starts out with a comment about the creation of a table in a database. This table is called “users” and has two columns, “username” and “password”. Below this comment we see the main PHP section of the page. This code checks that the “username” field of the HTTP request is set before continuing on. If it is set, there is a connection established to the level15 database, and a query is constructed to look up entries which match the value submitted through the username field of the HTTP request. Looking at how the query is constructed, we can see that it’s vulnerable to injection. However, if we look through the rest of the code, we notice it’s slightly different than the previous level in that there is no line printing the password, nor printing the results of the query. So, how will we get the password to the next level?
Exploit
As we can see in the PHP code, there is no direct way to get the results of the query, or get the password. Thus, we’re going to have to take an indirect approach. So, how can we indirectly get information from the database?
Well, if we were able to compare the password field of the user natas16 to various values, and do something different when the values match, we could somehow get information out. Since we know we cannot print information, how about doing something like making the process sleep? Unfortunately, MySQL doesn’t have a sleep function we can embed in a query, but let’s look at what it does have.
- Union – Combines sets, allows additional select statements to be run.
- If – We can make conditional logic!
- Substring – We can look at only one part of a string, say 1 character at a time.
- Benchmark – Allows repeated execution of code, say, to delay the return of the query results (sounds similar to sleep!)
Now let’s think about how to combine those things to figure out the length of the password. As we can see from the PHP code, the query is as follows:
SELECT * from users where username=”username”
So, let’s choose the natas16 user, then join on another select query for the password which, if the length of the password is 32, will run a long benchmark before returning:
natas16″ UNION SELECT password, IF(LENGTH(password)=32,BENCHMARK(500000000,ENCODE(‘The Message’,’The Key’)),null) FROM users WHERE username=”natas16
When submitted, we can see that it takes a long time for the page to load (if it ever does). If we change to length 31, the page loads immediately. Thus, we have used this indirect path to gain information about what was in the table by using time as a pathway to transfer information. Now that we know the password is 32 characters (like the previous passwords), let’s attempt to make another construction to test the values of the password.
As stated above, we can use MySQL’s substring function to return part of a string. So, let’s use this to select one character in the string and compare it’s value to a character we specify. If they are equal, we’ll force a benchmark, otherwise we’ll see the resultant page immediate.
natas16″ UNION SELECT password, IF(SUBSTRING(password,1,1) = BINARY(CHAR(50)),BENCHMARK(600000000,ENCODE(‘The Message’,’The Key’)),null) FROM users WHERE username=”natas16
One thing to specify about the above code is the use of binary(char(50)). Char(50) specifies the character with ascii decimal code 50. However, when MySQL compares strings (or characters), it does so without regard to case. Thus, not including the binary() call, this query would run the benchmark for both char(97) and char(65) (a or A). The use of binary(char()) forces case-sensitivity.
Now that we know how the above code works, we can submit it for every ASCII value we’d expect could be in the password as the input into the char() function, until we reach a value which forces the page to not return quickly (aka forces the benchmark to run). Once this happens, we know we’ve found the ASCII value of the 1st character of the password. For example, if we increase 50 to 51 and submit the query, we find out the page doesn’t load and thus, the first character of the password is ASCII 51, also known as the number 3.
Once we’ve found the 1st character, we can change the substring call from SUBSTRING(password,1,1) to SUBSTRING(password,2,1) and start all over again brute forcing char values at 48. Once the 2nd character is found, restart with the 3rd, repeat until finished.
Now I hear you saying, “But that sounds boring and like it will take a long time!” Yeah, well, write a script to do it. Computers are great at repetitive tasks. In fact, a PHP script to figure out the entire password isn’t too hard to write and is what I used for this challenge. Expect a script post here in the future.
So What?
Even when results of a query are not displayed on the page, or directly used to display sensitive information, results are still transferred to users. Results are a relative term, and with injection a result can be the delayed processing of a page, such as in this example. More strenuous actions need to be taken to prevent injection in the first place, because as this level shows, even blind injection can be used to get the exact values in a database, such as a 32 character long password in under an hour (or faster in production web environments which will allow more connections at a time and thus, less throttling for a script).
One Response to Natas Level 15 – Blind Injection