5.3. SQL Injection Testing

 

What is SQLi

To begin with, let's examine how a SQL Injection (SQLi) attack works. As you already know, in modern web applications, users can provide certain data to the application, which the server uses to generate and provide the user with specific information. All this information is stored in a database on the server.

Let's assume we have a website where we want to log in using the POST method. The application, upon receiving a POST request, extracts parameters and constructs an SQL query, which is then sent to the database. In the simplest scenario, the SQL query would look like this:

SELECT * FROM example_table WHERE login='admin' AND password='Passw0rd!'

If the username and password are correct, we successfully log in to the website.

However, what if we input entirely different data in the POST request? For example, in the login form, we input the expression OR 1=1 #.

If there is no input validation and data sanitization on the server, the application will generate the following SQL query:

SELECT * FROM example_table WHERE login='admin' OR 1=1 # ' AND password='blablabla'

In this case, the condition '1=1' always evaluates to true, allowing the attacker to bypass the login mechanism and gain unauthorized access. The # symbol is used as a comment in some Database Management Systems (DBMS), meaning that everything after it is ignored by the SQL interpreter. As a result, the system will execute the query

SELECT * FROM example_table WHERE login='admin' OR 1=1

What will happen after executing such an expression?

There are two possible scenarios. Please note that we have two conditions, and it is required that at least one of them is met, not necessarily both. If the 'admin' login exists, we will be able to log in to the website as that user without knowing their password. In this case, the first condition is satisfied.

However, if the user account does not exist, the second condition will be met, and the database will return a list of all users. The condition '1=1' always evaluates to true. If you need a false expression, you can use '1=2' or a similar expression.

I hope you understand how the attack works. Things can be more complicated because the server may use complex SQL statements and data filtering. It's also worth noting that there are two types of SQL injection: regular SQL injection and Blind SQL injection.

In the first case, the result of the SQL query is immediately displayed in the browser, typically showing the requested data. In the second case, the data is not displayed, making it challenging to determine whether the injected code has executed successfully. Special methods and techniques are used to identify Blind SQL injection vulnerabilities.

Where to look for SQL vulnerabilities:

  • URL with GET/POST/PUT requests.
  • HTML forms (usually used for POST and PUT requests, occasionally for GET requests).
  • Links in HTML documents. They typically represent URLs with GET parameters.
  • HTTP headers, such as cookies. These can also be non-standard headers that carry service information.
  • Dropdown lists (HTML Select/Option elements). They are usually part of an HTML Form element.
  • JavaScript – it is used to send parameters to the server via POST/PUT requests. These requests are typically sent in JSON format, sometimes XML.

Below, we will examine some methods that allow you to identify SQL injection vulnerabilities.

 

SQL Injection Testing – Vulnerability Detection Methods

Testing for SQL injection vulnerability involves determining whether the server is using input filtering and data validation. To do this, special characters are injected into the parameters sent to the server to trigger execution errors or other behaviors that are then displayed in the browser.

 

String Delimiter – Input of Single or Double Quotes

The very first and simplest method is to input a single or double quote. This is done to trigger an error or change the behavior of the application. For example, consider a URL like https://example.com?name=John. The server generates a query as follows:

SELECT * FROM users WHERE name='John';

which works fine. However, if you input a quote character as a parameter, the SQL query will look like this:

SELECT * FROM users WHERE name='John''; or SELECT * FROM users WHERE name='John"';

The query syntax is incorrect, causing the database behavior to change. This is precisely what we want. By noticing any deviations from the norm after inputting quotes, we can assume that the entered character made its way into the database, and the query was executed. It's important to note that we can only assume this, and we can't be completely certain. There might be a situation where the server filters incoming parameter values and, if something seems amiss, it simply removes them. In such a case, the application will behave differently depending on how it's programmed.

Now let's try testing this method on some applications.

As the first application, we'll use Mutillidae. In the left sidebar, navigate to OWASP 2013 → A1 – Injection (SQL) → SQLi Extract Data → User Info (SQL). This will open a page with a login form. If you enter admin/admin as the login and password, the following information will be displayed:

Normal operation of Muttilidae

However, if we add a quote to the Name or Password field (admin'/admin or admin/admin'), we will see an error message:

Adding quote in Muttilidae to cause SQL error

The message contains a lot of information, but the most important part is the SQL query. Knowing it, we can create our own query that will execute on the server. Such error messages are not uncommon. Sometimes developers forget to turn off the display of such messages.

Let's look at another application, for example, WackoPicko. Open the login page and enter admin'/admin:

Testing WackoPicko login page for SQL Injection

The following error will be displayed:

WackoPicko SQL error

Here, the entire SQL query is not displayed, only some hints that may help us guess the SQL query.

 

Sorting

With the help of the ORDER BY sorting operators, we can not only sort the output data but also determine the number of columns in the query and trigger execution errors.

Let's assume we have a query like

SELECT name, surname FROM table

The query will output data in 2 columns. If we add ORDER BY 1 or ORDER BY 2 to the end of the query, it won't cause an error. However, ORDER BY 3 or any other number different from 1 and 2 will trigger execution errors.

Let's open Mutillidae and enter the query admin' ORDER BY 10 #. I entered 10 as the column number because I don't have exact information about the number of columns, so it's better to enter any large number. At the end, you need to specify a comment symbol because the SQL query must be syntactically correct, otherwise, it won't be executed. All other query operators and symbols will be ignored after the comment symbol. You can use this expression in the Name or Password field. The result looks like this:

SQL error in Mutillidae caused by sorting method

The server generated an error, indicating that column number 10 does not exist. Below is the actual query. As you can see, immediately after the # symbol, there is a single quote and part of the expression. If we hadn't used the comment symbol, the query wouldn't have been executed.

We can observe a similar error in another application:

WackoPicko SQL sorting error

What does this mean?

If you see such an error, it means the server is not filtering the entered characters and is passing them for execution. This is clear evidence of the presence of an SQLi vulnerability.

 

Boundary testing

SQL queries can accept specific data types, such as numeric values, text, or a combination of both. Numeric values can include integers, decimals, positive numbers, and negative numbers. In a database, these values occupy specific memory space. This testing technique is based on these principles.

Let me explain with a simple example. Imagine a URL like https://example.com?id=1, where the server generates a query SELECT * FROM table WHERE id=1. In this case, the "id" parameter accepts only integer and positive values.

What happens if we input text, negative numbers, or decimal data as the parameter value? We can also input very large integer values, like 1,000,000 or higher.

Using the wrong data type can trigger an error in the database, indicating that the input data is not being properly validated on the server for the required data type.

 

Fuzzing – Automated Testing

Manually inputting various combinations for SQL queries can be quite tedious, especially if server-side filtering is in place. In such cases, the best approach is automated testing using a pre-prepared list of SQL combinations, or in other words, a dictionary. This process is called fuzzing.

Fuzzing can be conducted using tools like Burp Suite, ZAP, Wfuzz, and many others. You can use the Seclist dictionary, or any other files found online or created manually as your dictionary. Let's explore fuzzing using the example of OWASP Juice Shop and Burp Suite. In Kali, the free version of Burp Suite is installed, which has somewhat limited fuzzing capabilities. However, it's sufficient for our testing purposes. Open Burp Suite and navigate to the Proxy tab. In the tab, click on "Open Browser," and an embedded Chrome browser will open. In the browser's address bar, enter the Juice Shop address http://127.0.0.1:3000/#/login:

JuiceShop login page

In the opened page, enter any email and password. After that, go to Burp Suite and click on the "Target" tab. Here, you will see all intercepted requests and responses between the browser and the server. To begin, let's configure the filter. Click on the filter and check the box as shown in the image:

Changing BurpSuite filter

Scroll through the list of intercepted requests and find the following POST request:

Intercepted Login page in Burp Suite

At the bottom, you can see the request along with the entered login and password, as well as the server's response. Click on the hamburger icon to open the context menu and select the "Send to Intruder" option, or simply press Ctrl+I:

Sending intercepted  request to Burp Suite Intruder

Open the Intruder tab. Intruder is used for fuzzing, so in the POST request, we will replace various SQL combinations from the dictionary instead of the login/email. To do this, place the mouse cursor in the value field of "email," delete the current value, and double-click on the "Add" button:

Changing POST data in Intruder for fuzzing

This way, we set a placeholder where various combinations will be substituted. Next, go to the Payloads tab and click the "Load" button to load combinations from the dictionary. Choose the file /usr/share/wordlists/wfuzz/Injections/SQL.txt as the dictionary:

Adding dictionary file  as the payload in Intruder

We are all set, press the "Start attack" button.

After that, a new window will open where you can monitor the progress of the attack. Pay attention to the length of the response message and the response code. We are more interested in the code 500 (Internal Server Error) and the actual content of the response:

Filtering JuiceShop SQL error

The server responded with an error and kindly provided us with the actual SQL query. Now, knowing what the SQL looks like, we can easily craft our own expression to log into the system or retrieve a list of users.