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 that 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 an 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 an 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 with static code analysis.
LogicalDoc PreAuth HQLi 8.3.2
This vulnerability is a very intrinsic Hibernate Injection we have found in LogicalDoc. At first glance, it may look like it was correctly sanitized:
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.
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 breakout cheat sheet from below, one can deduce that this code snippet allows breaking 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:
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.
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
The following code snippets shows a HQL Injection in OpenBravo ERP 3.0 19Q.3 which is an ERP platform deployed by large retailers.
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:
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:
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:
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>.
|DBMS||SQL Injection (no quotes)|
|MySQL||'abc\'' INTO OUTFILE -- '|
|PostgreSQL||$$='$$=chr(61)||chr(0x27) and 1=pg_sleep(2)||version()'|
|Oracle||NVL(TO_CHAR(DBMS_XMLGEN.getxml('select 1 where 1337>1')),'1')!='1'|
%C2%A0 notation represents a urlencoded unicode whitespace.
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.