Last week I wrote about the OWASP WebGoat XSS lessons. Today I’d like to write a few pointers on how to solve the SQL injection (advanced) lesson 5. The goal is simple: you are presented with a login box and given a username; log in as that user.
The usual username’ OR ‘1’=’1 — unfortunately doesn’t work (that would have been too easy!), and since the last slide talked about blind SQL injection, it was pretty obvious that this was the way to go. After a few unfruitful hours trying to use SLEEP and UNION on the register new user page I started looked online for a hint. What I found was to register a new user, and then register it again with either TRUE or FALSE:
Register new user: testuser
Again register the same user but like this: testuser’ AND ‘1’=’1
Which equates to “testuser and TRUE”. When registering this user, pay attention to the server reply:
User testuser’ and ‘1’=’1 already exists please try to register with a different username.
Now try registering: testuser’ AND ‘1’=’2
Which translates to “testuser and FALSE”. The server now replies:
User testuser’ and ‘1’=’2 created, please proceed to the login page.
At first I thought it literally created the user “testuser’ and ‘1’=’1” but no, it still creates only “testuser” but apparently the added 1=1 means “user already exists”, as opposed to 1=2 meaning user doesn’t exist, although it does (we already created user “testuser”).
Again, since the last slide talked about extracting the database version string with substring(database_version(),1,1)=’1 I tried tagging that on to the username instead of ‘1’=’1. Iterating through all 10 numbers for the database version, I got 2 as the only TRUE statement. Using this technique, I was able to find 2.3.4 as being the database version.
So what other kind of information can we extract using substring? I first tried ‘user’ and ‘username’, but got an error saying the string didn’t exist. ‘userid’ worked though, but I saw no need for that information. In a flash of inspiration I tried ‘password’, and lo and behold, the first letter that equated to TRUE was ‘t’:
tom’ AND substring(password,1,1)=’t
User tom’ AND substring(password,1,1)=’t already exists please try to register with a different username.
As we saw before, ‘1’=’1 equals TRUE and returns “user already exists”, so if the above command returns the same, it must equal TRUE as well. We now have the tool to extract Tom’s password!
At this point I got tired of going through all characters manually and fired up Burp and configured BURP Intruder for a sniper attack. There’s only 1 parameter to fuzz, the very last letter in the string. This is very easy to do with Intruder with the following settings:
Attack type: Sniper
Payload: Brute Forcer
Character set: abcdefghijklmnopqrstuvwxyz0123456789
Min length: 1
Max length: 1
To find a hit, sort the results by length. A successful hit is slightly longer than a miss.
Each time I got a hit I changed the start position of substring (2nd parameter) and ran intruder again. Within half an hour I had poor Tom’s password!