Exploit SQL Injection Vulnerabilities

Now that we've determined how to find SQL injection vulnerabilities, it's time to determine the vulnerability's impact on the application's security. It's one thing to produce an error by inserting a single quote into a cookie value or substitute a POST parameter with a MOD() function; it's another thing to be able to retrieve arbitrary information from the database. This section explains several methods that can be used to exploit a vulnerability.

As we've already seen in the previous section, SQL provides a rich set of functions and enables the construction of rather complex queries. In addition, database platforms extend the SQL standard with functions that can manipulate files, data, and interact with the operating system. We'll start with techniques that should be applicable to any database. Then, we'll examine how to take advantage of some of the SQL extensions available from popular database platforms.

We'll get into specific techniques in this section. In some cases, we may gloss over SQL subtleties or use certain SQL commands or constructs without explaining why they were necessary. None of the SQL constructs should be too difficult to understand. If you're unfamiliar with SQL, then we recommend reading the additional resources mentioned in the "SQL Primer" section at the beginning of this chapter.

Alter A Process

Databases store information, so it's no surprise that targeting data with an attack is probably the first thing that comes to mind. However, if we can use SQL injection to change the logic of a query, then it might be possible to change a process flow in the application. A good example is the login prompt. A database-driven application may use a query similar to the following example to validate a username and password from a user .

 SELECT COUNT(ID) FROM UserTable WHERE UserId='' AND Password='' 

If the user supplies arguments for the UserId and Password that match a record in the UserTable, then the COUNT(ID) will be equal to one. The application will permit the user to pass through the login page in this case. If the COUNT(ID) were NULL or zero, then that means the UserId or Password were incorrect and the user is not permitted to access the application.

Now, imagine if no input validation were performed on the username parameter. We could rewrite the query in a way that will ensure that the SELECT statement succeeds and only needs a username to do so! Here's what a modified query looks like:

 SELECT COUNT(ID) FROM UserTable WHERE UserId='mike'-- ' AND Password='' 

Notice that the username includes a single quote and a comment delimiter . The single quote correctly delineates the UserId (mike) and the double dash followed by a space represents a comment, which means everything to the right of it is ignored. The username would have been entered into the login form like this:

 mike'--%20 

In this manner, we've used SQL injection to alter a process flow in the application rather than try to retrieve some arbitrary data. This might work against a login page, viewing the profile information for a user account or bypassing access controls. Table 7-11 lists some other SQL constructs that you can try as part of a parameter value. These are the raw payloads; remember to encode spaces and other characters so that their meaning is not changed in the HTTP request. For example, spaces can be encoded with %20 or the plus symbol (+).

Table 7-11: Characters to Modify a Query

Payload

Description

/*

'/*

Comment the remainder of the query.

--

'--

Comment the remainder of the query. (Alternate symbols)

OR 1=1

Attempt to force a true condition.

Query Alternate Data

Since databases contain the core information of an application, they represent a high-profile target. An attacker that wishes to grab usernames and passwords might try phishing and social engineering attacks against some of the application's users. On the other hand, the attacker could try to pull everyone's credentials from the database.

Subqueries

Subqueries can retrieve information ranging from Boolean indicators (whether a record exists or is equal to some value) to arbitrary data (a complete record). Subqueries are also a good technique for semantic-based vulnerability identification ("blind" SQL injection) explained in the previous section. A properly designed subquery enables the attacker to infer whether a request succeeded or not.

The simplest subqueries use the logical AND operator to force a query to be false or to keep it true:

 AND 1=1 AND 1=0 

Now, the important thing is that the subquery be injected such that the query's original syntax suffers no disruption. It's easy to inject into a simple query:

 SELECT price FROM Products WHERE ProductId=5436 AND 1=1 

More complex queries that have several levels of parentheses and clauses with JOINs might not be as easy to inject with that basic method. So, we alter the approach and focus on creating a subquery from which we can infer some piece of information. For example, here's a simple rewrite of the example query:

 SELECT price FROM Products WHERE ProductId=(SELECT 5436) 

We can avoid most problems with disrupting syntax by using the (SELECT foo) subquery technique and expanding it into more useful tests. We don't often have access to the syntax of the original query, but the syntax of the subquery, like SELECT foo, is one of our making. In this case, we need not worry about matching the number of opening or closing parenthesis or other characters. When a subquery is used as a value, its content is resolved before the rest of the query. In the following example, we try to count the number of users in the default mysql.user table whose name equals "root". If there is only one entry, then we'll see the same response as when using the value 5436 (5435+1 = 5436).

 SELECT price FROM Products WHERE ProductId=(SELECT 5435+(SELECT COUNT(user) FROM mysql.user WHERE user=0x726f6f74)) 

This technique could be adapted to any database and any particular SELECT statement. Basically, we just fashion the statement such that it will return a numeric (or true/ false) value.

 SELECT price FROM Products WHERE ProductId=(SELECT 5435+(SELECT COUNT(*) FROM  SomeTable  WHERE  column=value  )) 

Subqueries can be further expanded so that you're not limited to inferring the success or failure of a SELECT statement. They can be used to enumerate values, albeit in a slower, roundabout manner. For example, you can apply bitwise enumeration to extract the value of any column from a custom SELECT subquery. This is based on being able to distinguish different responses from the server when injecting AND 1=1 and AND 1=0 .

Bitwise enumeration is based on testing each bit in a value to determine if it is set (equivalent to AND 1=1) or unset (equivalent to AND 1=0). For example, here is what bitwise comparison for the letter 'a' (ASCII 0x61) looks like. It would take eight requests of the application to determine this value. (In fact, ASCII text only uses seven bits, but

we'll refer to all eight for completeness):

 0x61 & 1 = 1 0x61 & 2 = 0 0x61 & 4 = 0 0x61 & 8 = 0 0x61 & 16 = 0 0x61 & 32 = 32 0x61 & 64 = 64 0x61 & 128 = 0 0x61 = 01100001 (binary) 

The comparison template for a SQL injection subquery is shown in the following pseudo-code example. Two loops are required: one to enumerate each byte of the string ( i ) and one to enumerate each bit in the byte ( n ):

 for i = 1 to length(column result):  for p = 0 to 7:   n = 2**p   AND  n  IN (SELECT CONVERT(INT,SUBSTRING(  column  ,  i  ,1)) &  n  FROM  clause  

This creates a series of subqueries like this:

 AND 1 IN (SELECT CONVERT(INT,SUBSTRING(column,i,1)) & 1 FROM clause AND 2 IN (SELECT CONVERT(INT,SUBSTRING(column,i,1)) & 2 FROM clause AND 4 IN (SELECT CONVERT(INT,SUBSTRING(column,i,1)) & 4 FROM clause ... AND 128 IN (SELECT CONVERT(INT,SUBSTRING(column,i,1)) & 128 FROM clause 

Finally, this is what a query might look like that enumerates the sa user password from a Microsoft SQL Server database (you would need to iterate n 8 times through each position i 48 times for 384 requests). The sa user is a built-in administrator account for SQL Server databases; think of it like the UNIX root or Windows Administrator users. So, it is definitely dangerous if the sa user's password can be extracted via a web application. Each time a response came back that matched the injection of AND 1=1 , the bit equals one in that position.

 AND n IN (SELECT CONVERT(INT,SUBSTRING(password,i,1)) & n   FROM master.dbo.sysxlogins   WHERE name LIKE 0x73006100) 

Subqueries take advantage of complex SQL constructs to infer the value of a SELECT statement. They are limited only by internal data access controls and the characters that can be included in the payload.

UNION

The SQL UNION operator combines the result sets of two different SELECT statements. This enables a developer to use a single query to retrieve data from separate tables as one record. The following is a simple example of a UNION operator that will return a record with three columns :

 SELECT c1,c2,c3 FROM table1 WHERE foo=bar UNION SELECT d1,d2,d3 FROM table2 WHERE this=that 

A major restriction to the UNION operator is that the number of columns in each record set must match. This isn't a terribly difficult thing to overcome ; it just requires some patience and brute force.

Column undercounts, where the second SELECT statement has too few columns, are easy to address. Any SELECT statement will accept repeat column names or a value. For example, these are all valid queries that return four columns:

 SELECT c,c,c,c FROM table1 SELECT c,1,1,1 FROM table1 SELECT c,NULL,NULL,NULL FROM table1 

Column overcounts, where the second SELECT statement has too many columns, are just as easy to address. In this case, use the CONCAT() function to concatenate all of the results to a single column:

 SELECT CONCAT(a,b,c,d,e) FROM table1 

Let's take a look at how the UNION operator is used with a SQL injection exploit. It's only a small step from understanding how UNION works to using it against a web application. First, we'll verify that a parameter is vulnerable to SQL injection. We'll do this by appending an alpha character to a numeric parameter. This results in an error like the one in Figure 7-5. Notice that the error provides details about the raw querymost especially the number of columns, 12, in the original SELECT.


Figure 7-5: Application error that reveals database fields

We could also have tested for this vulnerability using a "blind" technique by comparing the results of these two URLs:

http://website/freznoshop-1.4.1/product_details.php?id=43
http://website/freznoshop-1.4.1/product_details.php?id=MOD(43,44)

An error could also have been generated with this URL (note the invalid use of the MOD() function):

http://website/freznoshop-1.4.1/product_details.php?id=MOD(43,a)

In any case, the next step is to use a UNION operator to retrieve some information from the database. The first step is to match the number of columns. We verify the number (12) with two different requests. We'll continue to use the http://website/freznoshop-1.4.1/ URL. The complete URL is somewhat long when we include the UNION statement. So, we'll just show how the id parameter is modified rather than include the complete URL. We expect that we'll need 12 columns, but we'll submit a request with 11 columns to demonstrate an error when the UNION column sets do not match.

id=43+UNION+SELECT+1,1,1,1,1,1,1,1,1,1,1 /*

Figure 7-6 shows the error returned when this id value is submitted to the application. Note that the error explicitly states an unmatched number of columns.


Figure 7-6: Using column placeholders to establish a valid UNION query

id=43+UNION+SELECT+1,1,1,1,1,1,1,1,1,1,1 ,1/*

If we then modify the id parameter with 12 columns in the right-hand set of UNION, then the query is syntactically valid and we receive the page associated with id=43 . Figure 7-7 shows the page when no error is present.


Figure 7-7: Successful UNION query displays user id

Of course, the real reason to use a UNION operator is to retrieve arbitrary data. Up to this point, we've only succeeded in finding a vulnerability and matching the number of columns. Since our example application uses a MySQL database, we'll try to retrieve user credentials associated with MySQL. MySQL stores database- related accounts in a manner different from Microsoft SQL Server, line but we can now access the default table names and columns. Notice the response in Figure 7-8. There is an entry in the table that reads "1 .: root"this is the username (root) returned by the UNION query. This is the value submitted to the id parameter:


Figure 7-8: Successful UNION query reveals username

id=43+UNION+SELECT+1,cast(user+AS+CHAR(30)),1,1,1,1,1,1,1,1,1,1+FROM+ mysql.user/*

Of course, there are several intermediate steps necessary to get to the previous value for id . The initial test might start out with one of these entries,

id=43'
id=43/*

and then move on to using a UNION statement to extract data from an arbitrary table. In this example, it was necessary to create a SELECT on 12 columns in the right-hand side of the UNION statement in order to match the number of columns on the left-hand side. This number is typically reached through trial and error, e.g., try one column, then two, then three, and so on. Finally, it was discovered that the result of the second column would be displayed in the web application, which is why the other columns have '1' as a placeholder.

Tip 

The CAST() function was necessary to convert MySQL's internal storage type (utf8_bin) for the username to the storage type expected by the application (latin1_Swedish_ci). The CAST() function is part of the SQL2003 standard and supported by all popular databases. It may or may not be necessary depending on the platform.

Like many SQL injection techniques, the UNION operator works best when the parameter's value is not wrapped by single quotes (as for numeric arguments) or when single quotes can be included as part of the payload. When UNION can be used, the methodology is simple:

  • Identify vulnerability.

  • Match the number of columns in the original SELECT query.

  • Create a custom SELECT query.

Enumeration

All databases have a collection of information associated with its installation and users. Even if the location of application-specific data cannot be determined, there are several tables and other information that can be enumerated to determine versions, patches, and users.

Platforms

This chapter strives to present datastore and SQL injection attacks common to all platforms. Of course, the application's language and the database type and version affect the success of certain attacks. In this section we review some of the specific platform extensions that can be exploited in an attack.

Microsoft Access Database

It's not likely that you'll encounter an MS Access Database in a high-performance e-commerce application, but that doesn't mean such a database back end isn't used for web applications. Access supports a large subset of SQL and behaves similarly to Microsoft SQL Server; however, it doesn't have the same information schema or stored procedures. It is possible to identify an Access database by requesting columns from one of the tables listed next. You won't be able to extract information from them, but their presence identifies the backend as MS Access.

  • MSysACEs

  • MSysObjects

  • MSysAccessObjects

  • MSysQueries

  • MSysAccessXML

Microsoft SQL Server

MS SQL Server is a popular database with several extended stored procedures that provide access to the operating system, network, and Windows domain. SQL Server also has some internal variables that can reveal the platform and version of the database. Each one can be queried via this syntax:

 SELECT @@variable 

There are several variables, but these are the most useful in that they return one record or provide useful information.

  • @@language

  • @@microsoftversion

  • @@ servername

  • @@servicename

  • @@version

Stored Procedures   SQL Server contains a small number of stored procedures that users can call without explicit casting to the "master.." database. By default, queries are made against tables in the current database. For example, an e-commerce application might have a database called "Books" and another one called "Users." The master table, on the other hand, is present in all installations and contains the data necessary to define tables, columns, data types, and built-in procedures. Consequently, these are short, to-the-point procedures that return useful information. Table 7-12 contains a list of the stored procedures commonly used to enumerate users, tables, and custom stored procedures.

Table 7-12: Useful Stored Procedures to Enumerate System Information

Procedure

Description

sp_columns <table>

Most importantly, return the column names of a table.

sp_configure [name]

Return internal database settings. Specify a particular setting to retrieve just that value. For example, sp_configure 'remote query timeout (s)'

sp_dboption

View (or set) user-configurable database options.

sp_depends <object>

List the tables associated with a stored procedure.

sp_helptext <object>

Describe the object. This is more useful for identifying areas where you can execute stored procedures. It rarely executes successfully.

sp_helpextendedproc

List all extended stored procedures.

sp_spaceused [object]

With no parameters, returns the database name(s), size , and unallocated space. If an object is specified it will describe the rows and other information as appropriate.

sp_who2 [username] sp_who

sp_who2 is far superior to its anumeric cousin. It displays usernames, the host from which they've connected, the application used to connect to the database, the current command executed in the database, and several other pieces of information. Both procedures accept an optional username. This is an excellent way to enumerate a SQL database's users as opposed to application users.

The extended stored procedures, signified by the "xp_" prefix, provide robust system administration from the comfort of SQL. We will cover countermeasures at the end of this chapter, but we'll hint that one countermeasure involves removing these commands entirely. Table 7-13 lists some procedures that do not require a parameter. Table 7-14 contains a list of useful procedures that require a parameter. Depending on the injection vector, you may not always be able to execute SQL statements that require a parameter.

Table 7-13: Extended Procedures That Do Not Require Parameters

Procedure

Description

xp_loginconfig

Display login information, particularly the login mode (mixed, etc.) and default login.

xp_logininfo

Show currently logged in accounts. Only applies to NTLM accounts.

xp_msver

List SQL version and platform information.

xp_enumdsn

Enumerate ODBC data sources.

xp_enumgroups

Enumerate Windows groups.

xp_ntsec_enumdomains

Enumerate domains present on the network.

Table 7-14: Parameterized Stored Procedures

Procedure

Description

xp_cmdshell <command>

The equivalent of cmd.exe. In other words, full command-line access to the database server. Cmd.exe is assumed, so you would only need to enter dir to obtain a directory listing. The default current directory is the %SYSTEMROOT%\System32.

xp_regread <rootkey>, <key>, <value>

Read a registry value.

xp_reg*

n.b. There are several other registry-related procedures. Reading a value is the most useful.

xp_servicecontrol <action>, <service>

Start or stop a Windows service.

xp_terminate_process <PID>

Kill a process based on its process ID.

These few commands cover just about any aspect of system-level access. Also, before you're tempted to use xp_regread to grab the SAM file, you should know that that technique only works against systems that do not have Syskey enabled. Windows 2000 enables this by default.

Default Local Tables (the Useful Ones)   Also known as System Table Objects, these tables contain information about the database and the operating system. Table 7-15 lists tables that have the most useful information.

Table 7-15: System Table Objects

Table

Description

Syscolumns

All column names and stored procedures for the current database, not just the master

Sysobjects

Every object (such as stored procedures) in the database

Sysusers

All of the users who can manipulate the database

Sysfiles

The filename and path for the current database and its log file

Systypes

Data types defined by SQL or new types defined by users

The easiest method to retrieve information from one of these tables is a SELECT * statement. For example:

 SELECT * FROM sysfiles 

However, if you are familiar with databases, then you can trim the request to certain fields. For example, to view all stored procedures, use

 SELECT name FROM sysobjects WHERE type = 'P' 

Table 7-16 lists selected tables from the master database. These tables provide detailed information on the operating system and database configurations. A SELECT from one of these tables usually requires the "master.." indication:

Table 7-16: Master Database Tables

Table

Description

Sysconfigures

Current database configuration settings.

Sysdevices

Enumerate devices used for databases, logs, and temporary files.

Syslogins

Enumerate user information for each user permitted to access the database.

Sysremotelogins

Enumerate user information for each user permitted to access the database or its stored procedures remotely.

Sysservers

List all peers that the server can access as an OLE database server.

 SELECT * FROM master..sysdevices 

MySQL

MySQL is a powerful open -source database platform. The recent 5.0 series added features like stored procedures, triggers, and views that have been common to commercial databases for many years . MySQL supports most of the SQL 2003 specification and adds some interesting extensions.

Probably one of the most interesting extensions in MySQL is the case where comments are not actually ignored. There is a special syntax that will cause SQL statements embedded within comment delimiters to be executed. This was designed to enable backwards compatibility with schemas. It also serves as a useful enumeration tool. The syntax for these special comments needs relies on version information for the database. The following example will execute the SELECT statement on any MySQL database greater than or equal to version 3.23.00:

 /*!32300 SELECT user FROM mysql.user*/ 

The version uses the major, minor, and build numbers preceded by a bang (!). So, version 4.1.15 would look like /*!40115 SELECT*/, whereas version 5.0.15 would look like / *!50015 SELECT*/ . This technique doesn't enable you to execute any special SQL statements, but it does enable you to determine the specific version of MySQL by trying queries such as these:

 /*!32310 AND 0 */ /*!40026 AND 0 */ /*!50000 AND 0 */ 

Another useful extension of MySQL is that it supports the LIMIT operator. This can be used to limit the number of records returned by a query and can also be used to index into an arbitrary record of the result set. This is especially useful in combination with UNION statements in order to walk through a result set.

Oracle

Oracle databases and supporting applications have had a significant number of buffer overflows and exploits, but they are not specifically addressed in this chapter. The majority of these are exploitable if direct access can be gained to the database (TNS listener) or via an Oracle web interface; many of them are documented at http://www.ngssoftware.com/advisory.htm.

Oracle has several system tables from which you can extract useful schema and account information. The simplest way to extract user account names is with

 SELECT username FROM ALL_USERS; 

Oracle provides commands that write to the file system; however, your success in executing them will vary based on the user connection's level of access. There are some simple file enumeration tricks that you can perform with one-line SQL statements. For example, you can try to copy parameter files (PFILE and SPFILE) to or from known locations. Unfortunately, this command returns syntax errors because the boot.ini (or /etc/ passwd, etc.) is not in the correct format.

 SQL> CREATE SPFILE = 'bar' FROM PFILE = 'c:\boot.ini'; CREATE SPFILE = 'bar' FROM PFILE = 'c:\boot.ini' * ERROR at line 1: ORA-01078: failure in processing system parameters LRM-00110: syntax error at '[boot' 

For the intrepid few who wish to brave the dangers of writing to the database's file system, the following commands might prove useful:

 CREATE DIRECTORY somedir AS '/path/to/dir'; CREATE TABLE foo (bar varchars2(20)) ORGANIZATION EXTERNAL (TYPE oracle_loader DEFAULT DIRECTORY somedir LOCATION ('somefile.dat')); 

There is also the UTL_FILE command, but this requires multiple statements and left-hand values. In other words, you must be able to create and track variables:

 DECLARE fh UTL_FILE.FILE_TYPE; BEGIN fh := UTL_FILE.fopen('/some/dir','file.name','W'); -- 'W'rite UTL_FILE.PUTF(fh, somedata); UTL_FILE.FCLOSE(fh); END 

So, this attack could write table data to a file or read a file's content to a table. A large set of documentation about Oracle attacks and countermeasures is found at http://www.petefinnigan.com/orasec.htm.



Hacking Exposed Web Applications
HACKING EXPOSED WEB APPLICATIONS, 3rd Edition
ISBN: 0071740643
EAN: 2147483647
Year: 2006
Pages: 127

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net