Over the past few years, the number of SQL injection attacks has been steadily increasing. The growth in the number of database applications, as well as in the number of various publications (both in electronic and in print) that describe this problem and how it can be used, leads to the implementation of numerous attacks of this kind.
Introduction
Over the past few years, the number of SQL injection attacks has been steadily increasing. The growth in the number of database applications, as well as in the number of various publications (both in electronic and in print) that describe this problem and how it can be used, leads to the implementation of numerous attacks of this kind.
The increasing number of SQL injection attacks is leading to attempts to find a solution to this problem. The most obvious solution, which can only be, is to write programs based on the principles of security.
Numerous published papers deal with the development of reliable web applications with a focus on database access, which has not changed significantly so far. Usually, web application developers are calm, do not think about security, and therefore problems arise again.
As a result, security experts are looking for measures that can be taken to address this issue. Unfortunately, the general solution to this problem is to suppress verbose error messages.
Since the SQL injection docs rely on the extraction of information from error messages (some even claim that certain tasks cannot be solved without detailed error messages), web application security experts have come to the conclusion, that SQL injection cannot be performed without analyzing detailed error messages (or without directly source code).
Hiding error messages is just another implementation of the “Security of Obscurity” that has proven to be incorrect throughout its history. The purpose of this document is to dispel the misconception that SQL injection can only be done with verbose error messages, and to show the simple ways an attacker can use if verbose error messages are not being output. These methods can be combined under the general name “Blindfolded SQL Injection“.
This name reflects the awareness that anyone can try to exploit a SQL injection vulnerability without detailed error messages. It also reflects that, with the right approach, it is easy to implement SQL injection, even if no detailed error messages are displayed.
To understand how this is accomplished, we first show how easy it is to determine if SQL injection is possible based on minimal server response. Then we will move on to the ways of constructing a working SQL query, which can later be replaced with any valid SQL query. Ultimately, we will consider using the SQL UNION SELECT clause (which is often overlooked when studying SQL injection attacks) without verbose error messages.
The use of the methods described in the document is based on zero knowledge of the application under study, the type of database, table structure, etc. All this knowledge can be gained by us in the process of SQL injection.
We hope to make it clear that application-level vulnerabilities must be handled appropriately, and that it is useless to rely on SQL injection protection by simply suppressing error messages.
Two important notes: First, this document is neither a guide to exploiting SQL injection vulnerabilities, nor a guide to the SQL language. This document will not cover SQL injection attacks in detail, it is also assumed that the reader has a basic understanding of what SQL injection attacks are and wants to understand how attacks of this kind can be dangerous if detailed no error messages are output.
The second note concerns the examples used throughout the document. Although the examples refer to MS SQL and Oracle database servers, the same techniques can be successfully applied to other database servers.
Determining the possibility of code injection
To implement SQL injection, the first step is obviously to recognize it. To accomplish this, the attacker must first determine for himself the relative display type of errors in the system.
Although the error messages themselves are not displayed, the application itself must be able to distinguish the correct request from the incorrect one. An attacker can take advantage of this, and he can easily learn to identify external manifestations of errors, find them, and determine whether they are related to SQL or not.
Recognizing errors
First, we need to understand what types of errors an attacker might encounter. There are two main types of errors that web applications can generate. Errors of the first type are generated by the web server as a result of some programmatic exception.
If the display of such errors is not changed, such exceptions are accompanied by messages like “500: Internal Server Error”. Typically, injection of syntactically incorrect code (for example, unclosed quotes) should cause the application to return this type of error, although other errors can lead to the same error message.
A simple error suppression technique is to replace the default error text with a custom HTML page. But, if you observe directly the server response, you can find that there was a latent server error.
Errors of the second type are generated by the application code, which usually indicates the best programming style. The application is ready for certain incorrect situations and generates special error messages for them.
Usually errors of this type should be reported as part of a correct (200 OK) server response, they can also be replaced with redirects to other pages or accompanied by other hiding methods, like “Internal server error”.
As a simple example, consider the differences between two similar e-commerce applications, let’s call them A and B. Both applications use a page called proddetails.asp.
This page is passed a parameter named ProdID. It takes a ProdId and retrieves the product details from the database, then does some action on the record and returns the result. Both applications call proddetails.asp using a reference, so ProdId must be valid at all times. Appendix A is content with this and does not perform additional checks.
When an attacker spoofs a ProdId by inserting a ProdId value that does not match any row in the table, an empty dataset is returned. While application A does not expect an empty dataset, it tries to process the data stored in the record, and most likely an exception will be thrown, generating the error message “500: Internal Server Error”.
Appendix B, however, verifies that the dataset has a dimension greater than 0 before performing any operations on it. If it is not, the error message “This product does not exist” appears. If the developer wants to hide the error, the user will simply be returned to the product list.
An attacker attempting Blindfolded SQL Injection will first try to generate some bad queries and determine how the application handles errors and what to expect when an SQL error occurs.
Locating the error
The second part of the attack is to find the location of the errors. Now, knowing the application, the attacker can spoof the input and carry out this part of the attack. To do this, common SQL injection techniques can be applied, such as: adding SQL keywords (OR, AND, etc.) and META characters (such as; or ‘).
Each parameter is checked separately, while the server response is carefully examined in order to find out when the error occurs. Using a sniffer proxy or any of the many tools available, it is easy to spot redirects and other ways to hide errors. Every parameter that returns an error is suspicious and can be used for SQL injection.
As always, all parameters are checked individually, with an emphasis on ensuring that the request remains valid. In this case, it is very important how this process should neutralize any possible cause of the error, which is other than code injection. The result of this process is usually a long list of suspicious parameters. Some of these parameters can be really vulnerable to SQL injection and can be exploited. Other parameters cause errors that are not SQL related and can therefore be discarded. Therefore, the next step for the attacker is to determine the parameters that can be discarded, and leave for consideration the parameters that are really vulnerable to SQL injection.
Determining Parameters Affected by SQL Injection
In order to better understand all of this, it is important to understand the basic data types that are used in SQL. SQL fields are usually defined by one of 3 basic types: number, string or date. Each base type can have many subtypes, but this is not so important for implementing code injection. Each parameter passed by a web application to an SQL query is considered to belong to one of these types. It is usually very easy to determine the type of the parameter (‘abc’ is obviously a string, while 4 is a number, but it could just as well be considered a string).
In SQL, numeric parameters are passed to the server unchanged, while string or temporal expressions are quoted on both sides. For instance:
SELECT * FROM Products WHERE ProdID = 4
instead of
SELECT * FROM Products WHERE ProdName = ‘Book’
The SQL Server, however, does not care about the type of expressions passed to it, as long as they match the expected type. This behavior provides an attacker with a great way to determine when an error is actually a SQL error. For a numeric expression, the simplest way to determine this is to use basic arithmetic operations. For example, consider the following query:
/myecommercesite/proddetails.asp?ProdID=4
It is very easy to test this query for SQL injection capability. The first way is to use 4 ‘as a parameter. Another way is to use 3 + 1 as a parameter. Assuming that this parameter is indeed passed to the SQL query, these two checks will result in the following SQL queries:
- SELECT * FROM Products WHERE ProdID = 4 ‘
- SELECT * FROM Products WHERE ProdID = 3 + 1
The first query, by definition, should generate an error, since it is syntactically incorrect. The second request, however, succeeds without errors, returning the same product as the original request (with ProdId = 4). Thus, we have shown that this parameter is indeed vulnerable to SQL injection.
A similar technique can be used to replace a string SQL parameter. But there are still two differences. First, string parameters are enclosed in quotes and must be broken. Second, different SQL servers use different syntax for string concatenation. For example, Microsoft SQL Server uses the + sign for string concatenation, while Oracle uses || for the same purpose. Then a similar technique is applied. For example: /myecommercesite/proddetails.asp?ProdName=Book
Let’s test this SQL injection query by replacing the ProdName parameter the first time with a syntactically invalid string such as B ‘, the second time by creating a valid string expression such as B’ + ‘ook (or B’ || ‘ook for Oracle). As a result, we get the following requests:
- SELECT * FROM Products WHERE ProdName = ‘Book’
- SELECT * FROM Products WHERE ProdID = ‘B’ + ‘ook’
Again, the first query throws an SQL error, while the second will probably return the same product as the original query with the value Book.
You can also use any other expression to replace the original parameters. System-specific functions can be used to return a numeric, string, or temporal expression. (For example, in Oracle, the sysdate function returns a temporal expression, while in MS SQL Server, the getdate () function is used for the same purpose). Other techniques can be used in a similar manner to determine the feasibility of SQL injection.
So, we have shown that it is a simple task to determine if SQL injection is possible, even if such error messages are not displayed, which allows an attacker to easily continue their attack.
Performing SQL Injection
After the attacker determines the possibility of SQL injection, the next step is to try to exploit this vulnerability. To do this, the attacker must be able to compose a syntactically correct expression, determine the database server being used, and create the required exploit (an exploit is a program that exploits an existing vulnerability).
Getting the correct syntax
This part of “Blindfolded SQL Injection” usually requires the most cunning and dexterity. If the original queries are simple, then getting a query with the correct syntax is also easy. However, if the original query is complex, it will take a lot of trial and error to break it. In any case, knowledge of only a few basic techniques is required to accomplish this.
The main syntax determination process begins by examining the standard SELECT… WHERE clause, assuming that the vulnerable parameters are in the WHERE clause. In order to properly construct a correct expression, an attacker must be able to append data to the original WHERE clause so that data other than expected is returned. In a simple application, this “trick” is usually to add OR 1 = 1. However, in many cases this is not enough to create a successful exploit. For example, it is often necessary to close quotes if they were open in the original query. An additional problem could be a forged query that will cause an application error and cannot be distinguished from an SQL error (for example, if one record is expected, then adding the expression OR 1 = 1,
Since each WHERE clause is usually a set of expressions evaluated as True or False, combined with logical operators OR, AND and parentheses, it is possible to investigate the possibility of obtaining the correct syntax that would break the parentheses and properly terminate the query , carried out by trying various combinations. For example, adding ‘AND 1 = 2’ will make the whole expression false, while adding ‘AND 1 = 2’ will have no effect, but only if the precedence operator is not applied.
With this type of injection, simply changing the WHERE clause may be sufficient. With other types of SQL injection, such as UNION SELECT stored procedure injection and injection, it is not enough to simply change the WHERE clause. The entire SQL expression must be properly terminated so that additional syntax can be added, for which a very simple technique can be used. Once the attacker has determined the correct combination of AND, OR, 1 = 2, and 1 = 1, the SQL comment mark can be used.
This character, which is two consecutive hyphens (-), tells the SQL server to ignore the rest of the input string. For example, let’s look at a simple authentication page that takes a Username and Password into a request, as shown below:
SELECT Username, UserID, Password FROM Users
WHERE Username = ‘user’ AND Password = ‘pass’
By sending johndoe ‘- as the username, the following WHERE clause will be generated:
WHERE Username = ‘johndoe’ – ‘AND Password =’ pass’
In this case, not only the syntax of the expression is correct, but also the authentication will be successful. However, let’s take a look at the slightly modified WHERE clause:
WHERE (Username = ‘user’ AND Password = ‘pass’)
Pay attention to the surrounding parentheses. With the same code injection (‘johndoe’ -), the request will fail:
WHERE (Username = ‘johndoe’ – ‘AND Password =’ pass’)
This request contains unclosed parentheses and therefore cannot be completed.
This example shows how the comment mark can be used to determine if an SQL statement was terminated correctly. If no error occurs after adding a comment mark, it means that the query was broken syntactically correctly before the comment mark. Otherwise, an additional attempt will be required.