Reminder: VERT Vuln School guides are published for educational purposes only.
In our last post, we demonstrated how an attacker could leverage a classical SQL injection vulnerability in a web application to leak database information (by reflecting the result of the database queries onto the web application itself). In this post, we are going to show how an attacker can exploit a slightly different variation of SQL injection, known as "blind SQL injection." Blind SQL injection is nearly identical to classical SQL injection. That is, attacker-tainted queries are still able to reach the backend DBMS interpreter and alter the result set of the queries. However, the result of the query is no longer displayed on the web application. Instead, the attacker only knows whether the tainted-query returned a result or not (using inference based on how the web application behaves with certain inputs). Let's see how this binary output can be leveraged to extract database information. To demonstrate the exploitation of a blind SQL injection vulnerability, we added a page, forgot.php, into our custom vulnerable web application, BankOfVERT.
This page takes a single input, the username, and will “send” a reset link to the user’s email address if the username exists in the database.
By submitting the username “alice,” we can infer that the username exists based on the response of the page indicating that an email was sent out. Let’s see what happens if we try a known fake username…
By submitting a non-existent username, we can see that the response of the page changes. On this particular web application, we’re told that the username cannot be found. Let’s see what happens when we try to inject SQL snippets into the username field…
By submitting the username fakeusername'or'1'='1
, we see that the response of the page changes. Recall that by injecting 'or'1'='1
, we are effectively changing the original query from returning 0 results, to now returning all results whenever 1=1, which is always. However, unlike our previous examples, we are not able to directly see the output of the results. Therefore, we need to figure out a way to use this true/false output to leak details about the backend database. Let’s take a look at one way this can be achieved…
fakeusername' or mid((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1)='a
Notice that this seemingly complicated injection payload resulted in the query being true. Let’s break the query down and examine what is happening. fakeusername' or
allows us to break out of the original query using a single quote. mid((...),1,1)
is a MySQL built-in function which is an alias of substring(). It takes in 3 arguments: the original string, the start position of the substring, and the desired length of the substring. In this case, we are asking MySQL to return a substring of length 1 starting at position 1 (the first character). (select group_concat(table_name) from information_schema.tables where table_schema=database())
is simply a nested MySQL query. In this example, we are asking for a list of tables in the current database. ='a
is comparing the result of the previous mid() function to the letter ‘a’. Note that the trailing single quote is left out because it will be completed by the original query hardcoded by the web application. We see that the output of the page indicates the query resulted as true, which means the first letter of the list of tables is the letter ‘a’. Let’s see what happens if we compare it with the letter ‘b’...
We get a false response. We now have a way to effectively bruteforce each letter of the result set of a query of our choosing. Let’s whip up a quick python script and see it in action…
The updated BankOfVERT application, as well as the exploit script, is available on our GitHub account here.