April 13, 2015

Blind SQL Injection Testing and Efficient Fingerprinting

There are a number of "universal" methods that can be used to determine if a page is vulnerable to SQL injection; but once a vulnerability has been discovered, fingerprinting the database engine to determine the vendor (MySQL, PostgreSQL, Microsoft SQL Server, or others) can come with a bit of a headache and take more requests than desired. Some functionality and variables exist on multiple database engines (and in some cases all of them), while others are unique to specific services or service versions. This article will guide the reader through a couple of "universal" testing methods and eventually through efficient database fingerprinting.

Universal Injection Tests

Nearly everyone is familiar with using mathematical operators to test for SQL injection, but in some cases cross-site-scripting filters or other sanitizing may prevent mathematical characters (such as =,<, and >) from entering the vulnerable SQL query unscathed. There are two other "universal" comparison operators that can be used to confirm the presence of a vulnerability in an application: the "... between ... and ..." comparison operator, and the "is" operator.

Using the entry table from our last coverage on SQL injection, examples of the comparison operators referenced here have been provided in SQL statement examples in conjunction with a set of testing URLs that could produce similar results.

Testing with "... between ... and ..."

The "... between ... and ..." operator is simple. It tests to see if the first value is between the other two. It returns true if the value falls between the specified range, and false otherwise (queries in Figure 1-A).

Figure 1-A: Query examples using the "... between ... and ..." comparison operator
mysql> select * from entry where id=1 and 3 between 1 and 5;  # True
| id | name        |
|  1 | First entry |
1 row in set (0.01 sec)

mysql> select * from entry where id=1 and 7 between 1 and 5;  # False
Empty set (0.00 sec)

The first query displays the entry because 3 is between 1 and 5, but the second query returns an empty set because 7 is not between 1 and 5. When testing for remote injection vulnerabilities, test URL's may look something like the examples in Figure 1-B.

Figure 1-B: Integer testing examples using the "... between ... and ..." comparison operator
http://domain.tld/file.ext?id=1 and 3 between 1 and 5
http://domain.tld/file.ext?id=1 and 7 between 1 and 5

If the page at http://domain.tld/file.ext?id=1 were vulnerable to in-band or partially blind injection, the first example testing URL would return the same output as the unmodified URL, while the second example testing URL would cause data to be missing from the page.

In many cases, this operator also works on strings. Figure 1-C provides an example set of test URLs implementing a string test with this technique.

Figure 1-C: String testing examples using the "... between ... and ..." comparison operator
http://domain.tld/file.ext?title=Text' and 'b' between 'a' and 'c
http://domain.tld/file.ext?title=Text' and 'd' between 'a' and 'c

Testing with "is" and "is not"

The "is" operator is not a range or mathematical operator. It is also not to be confused with the equals (=) operator for equality, as the equals operator can be used on a variety of data types. The "is" operator works on fewer data types, but can also be used to determine where data has or hasn't been defined. SQL Servers have a way of representing it when columns have not been given a default value and a row in that column is undefined. This lack of value is referred to as null by every database engine, and every database engine allows the comparison operators of "is null" and "is not null". Example queries using this operator have been provided in Figure 2-A.

Figure 2-A: Query examples using the "is" and "is not" comparison operators
mysql> select * from entry where id=1 and null is null;      # True
| id | name        |
|  1 | First entry |
1 row in set (0.01 sec)

mysql> select * from entry where id=1 and null is not null;  # False
Empty set (0.00 sec)

Similarly to the first example, the first query returns data because null is always null. Null is also never not null, thusly the second query returns an empty set. A remote injection test using these operators may look something like Figure 2-B.

Figure 2-B: Testing examples using the "is" and "is not" comparison operators
http://domain.tld/file.ext?id=1 and null is null
http://domain.tld/file.ext?id=1 and null is not null

Remote Database Fingerprinting

By isolating the similarities and differences between different database engines, it is possible to use very few requests to determine which database server provides dynamic content support for the vulnerable application. While the techniques here do not reveal a specific version number, they do reveal the vendor of the server software. Knowing the vendor of the database server software is enough information to use vendor-specific techniques that reveal the rest of the version information.

There are enough similarities and differences between Microsoft SQL Server, PostgreSQL Server, and MySQL server to determine which of these (or if any of these) vendors provided the server software powering the application in only two requests. MySQL and PostgreSQL share a version() function that returns the server version information. Microsoft SQL Server has the @@version system variable in common with MySQL, but PostgreSQL does not. Knowing this, seeing the output of the two requests in Figure 3 from a vulnerable page gives us the ability to logically deduce which, if any, of the three vendors mentioned developed the software.

Figure 3: Example version fingerprinting URLs
http://domain.tld/file.ext?id=1 and version() is not null
http://domain.tld/file.ext?id=1 and @@version is not null

If the output of the page returns a "true" response for both requests, the vulnerable application is using a MySQL back-end. This can be deduced because MySQL is the only database server that has both the version() function and the @@version system variable. If only the test using the version() function returns true, the target application is running PostgreSQL. If only the test using the @@version system variable returns true, the vulnerability applies to a Microsoft SQL Server. If both queries return false, and characters are not being filtered, the target may be running Oracle Server, as Oracle does not support either the system variable or the version() function. Once the vendor of the database software has been determined, version information can be enumerated or selected using standard SQL injection techniques. Other efficient fingerprinting techniques can be found by inspecting reference manuals for nuances related to functions and environment variables with a little bit of testing.

No comments:

Post a Comment

Note: Please keep comments academic in nature.