5.5. Exploiting SQLi through Error-Based Payload

Very often, the contents of a database are not displayed in the browser after sending a request to the server. An example of such behavior is user authentication on the server. Upon successful authentication, an additional menu is usually added to the page, and the links in it typically lead to the user's personal page. Another example is submitting a review or comment to the server. In this case, you usually receive a notification that the message was successfully sent to the server.

In all these examples, the contents of the database are not displayed, so exploiting it using the UNION method is not always possible. In such cases, we can trigger a SQL query execution error, which may be displayed in the browser. Note that we need to trigger not a syntactical error, where the SQL expression contains an error, but an error in execution. Let's explain this with an example from the bWAPP application. Log in to the site and select the option "SQL Injection (Login Form)”:

Selecting SQL test in bWAPP

Here, we are given an option to bypass authentication without knowing the login or password. We won't bypass authentication; instead, we will trigger a code execution error, and the server will display it.

Since MySQL is used as the database management system in the bWAPP application, the most suitable functions are ExtractValue() and UpdateXML(). Both functions work with XML. ExtractValue() takes two arguments: an XML fragment and an XPath expression.

XPath, or XML Path Language, is a query language for accessing XML elements. We don't need to know XPath; on the contrary, we need to specify an incorrect expression that will cause an error later.

Suppose we want to find out the current version of the database. For this, we use the expression extractvalue('', concat('<', version())). In the first argument, an empty string is used, and in the second argument, the version() function is used to determine the current database version. The result of this function is concatenated with the "<" symbol, meaning it is simply added at the beginning.

Why do we use the "<" symbol and the concat() function?

The reason is that XPath expressions use numbers and symbols such as "/", ".", and "@". The result of the version() function contains some of these characters at the very beginning, so the database interpreter can use them to execute a query. To avoid this, we need to add a special symbol at the very beginning that XPath does not understand. Good candidates for this are the symbols "<" and ">".

Now let's try applying this expression:

1' extractvalue('', concat('<', version()))

DB version retreived with error-base SQL payload

The result is evident. Let's try to extract more significant information, such as the list of tables in the current database. As you know, this can be achieved with the following expression:

SELECT table_name FROM information_schema.tables WHERE table_schema='database_name'

So, first, we need to find out the name of the database. From the previous lesson, we already know it, but let's go through how to obtain it. For this, we will use the expression:

1' and extractvalue('', concat('>', database())) #

DB name retrieved with error-based SQL

Now we compose an expression to obtain a list of all tables:

1' and extractvalue('', concat('>', (select table_name from information_schema.tables where table_schema='bwapp'))) #

Output with one row in SQL DB error

However, only one row is expected from the database, so let's add an additional LIMIT operator, which will limit the output to one row and allow us to retrieve only the desired row from the entire list. In MySQL, the LIMIT operator can be used with two arguments: LIMIT row_number, row_count.

The first argument indicates the row number from which to start displaying data. Numbering starts from zero, so if you want to retrieve the first row, you should use 0 as the value, and for the second row, you will use 1, and so on.

The second argument specifies the number of rows. Numbering starts from one.

To obtain a list of all tables, we need to sequentially execute the queries LIMIT 0,1, then LIMIT 1,1, LIMIT 2,1, and so on. Our payload will look like this:

For the first row:

1' and extractvalue('', concat('>', (select table_name from information_schema.tables where table_schema='bwapp' limit 0,1))) #

For the second row:

1' and extractvalue('', concat('>', (select table_name from information_schema.tables where table_schema='bwapp' limit 1,1))) #

For the third row:

1' and extractvalue('', concat('>', (select table_name from information_schema.tables where table_schema='bwapp' limit 2,1))) #

Let’s try to retrieve the first row:

First entry of SQL tables obtained via error-based payload

The subsequent queries can be executed manually or by using Burp Suite. Open the built-in browser of Burp Suite and access the bWAPP application with the appropriate test case. Then, in Burp Suite, go to the Proxy tab, and activate request interception (Intercept is on):

Enabling BurpSuite interception

Enter our SQL query in the browser and send it to the server. The query will be intercepted by Burp Suite, after which you can send it to Intruder for further testing:

Sending intercpeted request to BurpSuite intruder

In Intruder, add a marker to the first argument of the LIMIT operator:

Setting BurpSuite intruder marker

Next, switch to the Payloads tab and set the payload to sequential numbers from 0 to 10 with a step of 1. The number 10 was chosen arbitrarily. If during testing it is discovered that there are more tables in the database, the number 10 can be replaced with a larger number:

Configuring payload for SQL attack

Click the Start attack button and observe the process in a new window:

Attack result of SQL error-based payload

Can similar techniques be applied in other DBMSs?

Yes. Below are the tested methods:

DBMS

SQL query

Explanations

MySQL

ExtractValue(‘’, concat(‘>’, version()))

Incorrect syntax XPath query

UpdateXML(null,concat(‘>’,(version())),null)

MS SQL

Cast(@@version as int)

Conversion to incompatible data type or incorrect type of variable

Convert(int,(SELECT @@version))

PostgreSQL

Cast(version() as int)

Oracle DB

to_char(dbms_xmlgen.getxml('select " '|| (select substr(banner,0,30) from v$version where rownum=1)||'" from sys.dual'))

Conversion to XML form

utl_inaddr.get_host_name((select banner from v$version where rownum=1))

Resolving hostname and its IP address