Exploiting a HQL injection

While testing a new project I stumbled over something that looked like a SQL injection. When logging into the web application submits a POST request with a JSON body:

POST /login HTTP/1.1
Host:<host>
User-Agent: Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: application/json, text/plain, */*
Accept-Language: en-US,en;q=0.5
Content-Type: application/json
Content-Length: 65
Cookie: JSESSIONID=D6F3D7EAC2FA80E37726928882610255
Connection: close
{"username":"test","password":"test"}

I immediatly tested the input for SQL injection by appending an extra ‘ to the username. The response contains an exception which means that something went wrong. By looking at the server response one can identify that a “org.hibernate.QueryException” occured due to an invalid query:

Unfortunately, this means that we do not have a SQL injection but a HQL injection. The Hibernate Query Language (HQL) is a query language, similar to SQL, but operating on persistent objects instead of tables and columns. The HQL is translated to SQL by the Hibernate framework.

Honestly, this was the first time I stumbled over such an injection, so I started to do some more generic SQL injection testing. From the above exception I knew that the query looks like the following (“test” is the injection point):

SELECT u FROM Users u WHERE u.active = 1 AND login = 'test'

This is a typical login query which searches for a valid user entity for the provided username. In the next step I submitted “test’ or ‘1’=’1” and got another exception:

The new exception is a “javax.persistance.NonUniqueResultException”. This means we got more than one expected result. After further testing, I noticed that the server responds with a HTTP error 422 when there is no valid user in the database (speaking in HQL: “u.active = 1 AND login = ‘user’” evaluates to false for all user entities).

Okay, let’s move further and read some data. My first try was to enumerate all available users. The problem is that HQL does not support all SQL features (e.g. the UNION statement, as well as meta information are missing). There is not very much information about HQL injections. I appended something like “test’ OR substring(login,1,1)=’a’” to the query and got a “NonUniqueResultException”. Fine. Unfortunately, I was not able to extract more than two characters.

I finally found out that I the only way to distinguish the queries is the “NonUniqueResultException”, but after more than two characters there simply is just one result hence no “NonUniqueResultException”. So I needed to find a way to cause a “NonUniqueResultException”. After fiddling around I came up with the following query which meets my requirements:

SELECT u FROM Users u WHERE u.active = 1 AND login = 'test' OR (select count(login) from Users u where login LIKE 'a%')>=1 AND '1'='1'

The introduced subquery counts all user names that start with (in this case) “a”. The query is designed to evaluate to true when we have at least on valid user. I quickly tested it with Burp Suite:

And the negative result when no user with this name is available:

After I found a successful way to exfiltrate data using an error-based approach it was time to automate it. So I wrote a little python script for this purpose:

With this, I was able to enumerate all available users including their passwords. Yeah :)

The sad part is that I could not read arbitrary data because HQL is missing all SQL comfort features like meta data about available tables and columns. This is due to HQLs restriction to object-mapped entities. I tried several methods suggested by http://blog.h3xstream.com/2014/02/hql-for-pentesters.html but none of them worked in my case.