ORM Leak Exploitation Against SQLite

We are currently building our ORM Leak labs and found a quirk worth sharing. The goal of our labs is to recover the hashed password of one of the users by leveraging ORM Leaks.

First, if you don’t know much about ORM Leaks, you should definitely check out the excellent article from elttam: PlORMbing Your Django ORM.

While building our labs, we came across an issue, we used SQLite3...

SQLite3 is great and it makes building challenges a lot easier than other databases as you can keep everything in one single container. But SQLite has one limitation: "SQLite doesn’t support case-sensitive LIKE statements".

And the key to solve the lab is case-sensitive. Basically, our subscribers had to recover the key and the case of the key. And retrieving the case of the key doesn’t seem to be something that is well-documented until now. Since this was the first challenge of the set, we also didn’t want to be too hard so we moved to MySQL.

Now that you have a bit of context, let’s go down the rabbit hole. This issue allowed us to discover how to solve the challenge with sqlite3.

Solving the challenge with SQLite3

To get started, you will need to use the same algorithm as any other database, this will give you the lowercase value of the hash of the password you are trying to recover. Now we want to recover the exact match. To solve our issue, a good start is to start reading some Django documentation, this allowed us to come across __exact

To get an exact match, we will have to try all the possible combinations of uppercase and lowercase until you get a result by using __exact.

But that seems a bit slow? Let’s improve the second part of the recovery by replacing __exact with __regex (that we can also find in the same documentation).! The second part of the recovery takes a lot of time because we need to test all combinations for only one match. With __regex, we can achieve the same result progressively.

First we need to escape a few characters to avoid breaking the regular expression: $, /, =. Once we get that working, what we will do is replace every character a-z with an equivalent that matches the lowercase and the uppercase value: [a|A].

If we recovered the hash pbkdf2_sha256$720000$abcd, our new value becomes: pbkdf2_sha256\\$720000\\$[a|A][b|B][c|C][d|D]... or (shorter): pbkdf2_sha256\\$720000\\$[aA][bB][cC][dD].... Then we can start replacing one expression at a time with one of the two possible letters and test for: pbkdf2_sha256\\$720000\\$a[bb][cC][dD]… to see if we have a match. If we do, we keep a, otherwise we keep A. Let’s say we don’t have a match (and keep A).

We can move to the next one and test for: pbkdf2_sha256\\$720000\\$Ab[cC][dD]… to see if we have a match. If we do, we keep b, otherwise we keep B.

And you keep going until you recover the full hashed password with the proper case!

Conclusion

In this post, we explored ORM leaks, focusing on the quirks of SQLite3 databases. Building our ORM Leak labs revealed valuable lessons about SQLite limitations and strategies to overcome them. Whether you're dealing with SQLite issues or curious about ORM leaks, this guide offers useful insights and tips.

Photo of Louis Nyffenegger
Written by Louis Nyffenegger
Founder and CEO @PentesterLab