Currently regarded as the one of the greatest risks to web application security (and listed in third place in the OWASP Top 10 for 2021), injection is a class of vulnerability that sits at the core of developer security nightmares. Not only can the exploitation of an injection vulnerability lead to the compromise of sensitive user data, injection vulnerabilities can also be easily overlooked during development and are some of the most common vulnerabilities found during penetration testing engagements.
One of the most common and arguably the most well-known type of injection vulnerability is SQL injection. This type of injection involves an attacker sending specially crafted user inputs to the application to escape the context of SQL queries and manipulate the syntax. As a result, the attacker could have free reign over the data contained in the database, allowing them to modify, extract and/or delete potentially sensitive information.
Even SQL injection has its own vulnerability sub-variants such as blind (relying on true or false indicators in application responses) and double-blind SQL injection (relying on time-based differences in application responses). These variants can be more complex for attackers to exploit than the standard SQL injection vulnerabilities and cause significantly more noise, but the resulting impact is just as potent.
In this post we’re going to be looking at how a Blind SQL injection vulnerability is identified and the steps that may be taken to fully exploit the vulnerability manually using a PortSwigger’s Burp Suite. If you would like to follow along and try exploitation yourself, I will be using the Damn Vulnerable Web Application (DVWA) as the target with security set to ‘Low’.
Identifying the Blind SQL Injection
The User ID field returns a message indicating whether or not a user with the provided ID number already exists in the database.
Since the resulting output can only be one of two messages, it’s not as obvious what effects our input is having in the backend and this makes it difficult to identify whether there is a SQL injection vulnerability by relying on the response messages alone.
What we can do instead is try and add a delay function to the injection input. For applications using MySQL or MariaDB we can use the SLEEP() function and create a payload such as the one below:
1′ OR SLEEP(5);– –
If SQL injection is successful, the SQL server will wait for 5 seconds before finishing the query and there will be a delay in the application’s response time (which is the result we receive here since the page hangs).
Identify the Number of Characters in the Current Database Name
Now that we know that a blind SQL injection exists, we can start to map out the SQL database. The SQL query appears to be boolean-based returning ‘User ID exists in the database’ when the query resolves to true and ‘User ID MISSING from the database’ when the query resolves to false.
Using this we can use the true and false output along with PortSwigger’s Burp Suite to understand when we are identifying the right values, starting with the name of the current database.
We enter the following payload into the vulnerable field, capture the request with Burp Suite and send the request to Intruder:
1′ and length(database())=1– –
We set the value of the database length as the field we would like to manipulate and set our payloads as a list of integers from 1 to 20.
We also go into ‘Options’ Intruder tab and use Grep to extract the resulting message so we can see whether the query resolves to true or false more clearly and then execute the attack.
Looking at the results, we can see that the SQL query resolved to true when the current database name’s length was equal to 4, so we now know the database name is 4 letters long:
Identifying the Current Database Name
Knowing the number of letters we need to identify in the database’s name, we can use the ASCII() function (which returns the ASCII value of the first character in a given string) along with the SUBSTR function (that will extract the character we want the ASCII function to focus on) to identify what each character is.
We capture the request with the following payload and send it to intruder, this time setting the ASCII decimal value as the payload we want to brute force:
1′ and ascii(substr(database(),1,1))=1– –
We set our payloads as a list of integers between 1 and 128 (the total number of standard ASCII characters), use Grep to extract the boolean message from the response like before and then execute the attack:
We see that ASCII character 100 is the first character of the database name which resolves to ‘d’.
We can now use the same technique on the other database name characters to identify the full database name:
1′ and ascii(substr(database(),2,1))=x — –
1′ and ascii(substr(database(),3,1))=x — –
1′ and ascii(substr(database(),4,1))=x — –
The results provide us with ‘118’, ‘119’ and ’97’ respectably, so the full name of the database is ‘dvwa’.
Identifying the Number of Tables
The next step is to identify the number of tables. Similar to obtaining the database’s name length, we will use Intruder to automate the process for us. Providing the following payload, setting the ‘x’ as the field we would like to manipulate and providing a list of integers to iterate through:
1′ and (select count(table_name) from information_schema.tables where table_schema=’dvwa’)=x — –
We identify that there are two tables in the database.
Identifying Table Name Lengths
To identify the first table’s length we will use the following payload (the LIMIT statement will just match the first row which is the table we want) with integers from 1 – 20 for the payloads to replace ‘x’:
1′ and length(substr((select table_name from information_schema.tables where table_schema=database() limit 1),1))=x;– –
We see the length is ‘9’. For the second table we can use the following command but with the LIMIT statement offset for the second row (second table):
1′ and length(substr((select table_name from information_schema.tables where table_schema=database() limit 1, 2),1))=x;– –
We receive a length of ‘5’.
Identifying Table Names
Now that we have the table name lengths, we can use the ASCII function again to identify the characters in the table names. Starting with the second table, the following payload is entered into the vulnerable field and the request is captured (and sent to Burp Suite Intruder):
1′ and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),y,1))=x; — –
In Burp Suite Intruder, we highlight ‘y’ and ‘x’ as the fields we would like to set our payloads in. ‘y’ will change which character we are focusing on within the table name and ‘x’ will be where we loop through the ASCII decimal values to see which letter matches. We set the attack type to ‘Cluster Bomb’ which will try all the ASCII values for each character position. For the first payload set ‘y’, we generate a list of numbers between 1 and the length of the second table name (‘5’ which we identified previously) and for the second payload set ‘x’, we generate a list of numbers between 1 and 128 (the ASCII values). With this in place we can start the attack.
If we filter the results table by status code we can see the ASCII decimal values for each character: 117, 115, 101, 114, 115 (u,s,e,r,s). We know that the second table’s name is ‘users’.
We can use the same technique with the other table name by making a small amendment to LIMIT values on the SQL injection payload template and changing the number of characters in the second payload list to 9:
1′ and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),y,1))=x; — –
From the other table we determine the following ASCII decimal values 103, 117, 101, 115, 116, 98, 111, 111, 107 (g,u,e,s,t,b,o,o,k). Therefore the table’s name is ‘guestbook’.
Identifying the Number of Columns in a Table
Now we have the table names, we can now determine how many fields there are in each table. Since the ‘users’ table seems like a higher value target, we’ll attempt to enumerate the number of fields by using the following payload (where ‘x’ will be the number of columns):
1′ and (select count(column_name) from information_schema.columns where table_schema=database() and table_name=’users’)=x — –
We use intruder again and a payload list of numbers between 1 and 50 to identify the point at which the number of columns is met. When we run the attack observe a positive result for 8 columns/fields.
Identifying Table Column Names
At this stage we could identify the length of each column name to be more precise when enumerating column name characters, but you can also try the ASCII decimal values for up to 10 characters in each field (or higher if required). Where there is no character at the position being brute forced the database will respond negatively like the other false requests and won’t affect your enumeration. Please note, however, that this will increase the number of requests that will be made to the server and prolong the attack.
We can use the following payload, capturing the request in Burp Suite and forwarding it to Intruder (again using the Cluster Bomb attack type and highlighting the x, y and z fields as where our payloads will go):
1′ and ascii(substr((select column_name from information_schema.columns where table_schema=database() and table_name=’users’ limit z,1),y,1))=x– –
The ‘z’ value will be the column number (number payloads 0 – 7), the ‘y’ value will be the character position number in the column name (number payloads 1 – 10) and the ‘x’ will be the ASCII decimal values (number payloads 1 – 128). When the attack is finished, we can filter by payload 2, then payload 1 and then status to receive a chronological list of ASCII values.
If we translate the decimal values into ASCII characters we get the full list of column names:
Identifying the Number of Rows in a Table
We can use the following payload with an incrementing value for ‘x’ to identify the number of entries in the table (using Burp Suite Intruder):
1′ and (select count(*) from users)=x– –
Similar to the other techniques we select ‘x’ as the placeholder for our payloads and set a list of numbers between 1 and 100 for the number of rows (you can increase the number from 100 if you don’t get a positive result during the attack).
We receive a positive result for 5 rows, so we now know this table holds the data for 5 users.
Extracting the User Data
We can now start to map out the usernames and password hashes in the users table using the below injection query:
1′ and ascii(substr((select user from users where user_id=z),y,1))=x– –
1′ and ascii(substr((select password from users where user_id=z),y,1))=x– –
We will capture the response as usual send it to Burp Suite Intruder with the ‘Cluster Bomb’ attack type configured. For the payloads of this attack, ‘z’ will be the number of rows we enumerated in the previous setting (number payload 1 – 5), the ‘y’ value will be the character position number in the name of the user/password (number payloads 1 – 32 to accommodate for the hash length in this example) and the ‘x’ will be the ASCII decimal values (number payloads 1 – 128). When the attack is finished we can filter by payload 2, then payload 1 and then status to receive a chronological list of ASCII values.
We can use CrackStation to crack the hashes and reveal the plaintext passwords:
Predatech is a cyber security consultancy that offers a range of services including CREST accredited penetration testing and Cyber Essentials/Cyber Essentials Plus assessments. What makes us different? We combine expert cyber security with great customer service and value for money. Please contact us if you’re interested in a free consultation.