Almost everyone has heard about SQL injections, but what about HQL injections? Hibernate Query Language (HQL) is an object-oriented query language, similar to Structured Query Language (SQL), but instead of operating on tables and columns, HQL works with persistent objects and their properties.
Imagine that you are doing a source-code review on a Java-based web application that uses Direct Web Remoting (DWR) and you come across the following line of code:
String statement = "select x from Rights x, Roles y where x.uId = y.uId " + "and y.roleId = " + rId;
List roles = DataUtils.getHQL().queryList(statement);
DWRAction.getUserRightsByRoleID("1 or 1=2");
DWRAction.getUserRightsByRoleID("1 or 1=1");
This looks like a confirmed case of a blind HQL injection vulnerability. The goal now would be to dump some important information from the database. Attempts were made to use SQLmap as well as an obscure HQLmap tool from GitHub, but they were not successful. Attempts were then made to exploit this vulnerability manually.
Consulting the official HQL manual shows that the following expressions are supported:
After a bit of trial and error with the supported expressions, it was observed that the accepted inputs are:
- ( ) Parentheses
- Between x and y (this will be the alternative to < > comparators)
- Sub SELECT statements allowed (alternative to UNION)
With this knowledge, the next step would be to find out valid tables and columns. Searching through the source code for other HQL statements gave some idea of what tables and columns exists. In this case, the table name was ‘TBL_USERS’ with several columns, but what we are interested in is just the “password” and “uId” columns.
The first step in order to extract some meaningful data will require a valid ‘uId’ from the ‘TBL_USERS’ table. The following sub SELECT payload was used:
DWRAction.getUserRightsByRoleID("1 or (select id from TBL_USERS where id between 1 and 2000) = 1");
After tweaking the ‘between’ values manually by elimination and looking at the server responses, it was observed that the ‘uId’ of value 1900 exists. Now, its time to get the password hash for the user who has the ‘uId’ value of 1900. One way to determine the characters of the password is to use ASCII comparison, and we will start with the first character of the password column using the following payload:
DWRAction.getUserRightsByRoleID("1 or (select ascii(substr(password,1,1)) from TBL_USERS where id = 1900) between [ASCII value] and [ASCII value]");
The ASCII values can be obtained from the chart below:
This part gets repetitive as the trial and error process begins. After many tries using elimination by tweaking the ‘between’ ASCII values, we now know the first character is an ASCII value of 49 which is equivalent to ‘1’. Repeat this tedious process till the n character by altering the substr() parameter in the statement. You can make the process less manual by using Burp Intruder and observing the responses.
Update: After knowing that its a hash value from the first few characters, the statement can be simplified to:
DWRAction.getUserRightsByRoleID("1 or (select ascii(substr(password,1,1)) from TBL_USERS where id = 1900) = [ASCII value]");
Where the ASCII value can range from 48 to 57 and 97 to 122. (Thanks @gifted88 for the tip.)
After all the hard work, the password hash for the user with “uId” 1900 is: