Blogs & Stories

SpiderLabs Blog

Attracting more than a half-million annual readers, this is the security community's go-to destination for technical breakdowns of the latest threats, critical vulnerability disclosures and cutting-edge research.

From Creative Password Hashes to Administrator: Gone in 60 Seconds (Or Thereabouts)

Picture the scene, you’re on an application penetration test (as a normal user) and you’ve managed to bag yourself some password hashes from the application. This can happen in various ways but in my experience, this is often the result of either a SQL injection vulnerability (resulting in the dumping of the users table) or finding that the application (or associated API) spits these hashes out in responses (because they are only hashes and what could go wrong!?). An Insecure Direct Object Reference (IDOR) vulnerability is then attached to the latter and all the hashes are yours for the keeping.

In most instances these hashes are using known hashing algorithms without much fanfare, typically aligned with the framework being used and/or language the application is coded in. In these cases, you’d take the hashes out, plug them into something like John, hope for a good outcome, and ultimately re-login as an administrator. However, I am seeing a trend of developers using ‘creative’ implementations of hashing algorithms where this approach no longer fits.

I’ve put together a small ‘helper’ tool I call "masher" to assist with these cases – the multiple password 'asher. Python’s hashlib is doing all the heavy lifting ‘under the hood’ so it really deserves all the credit here. I’ve just put everything in one place. I’ll go through two scenarios where hopefully it can add some value and save you some time when you find yourself with password hashes that do not conform to the norms.

You can download "masher.py" here: https://github.com/SpiderLabs/masher. It uses Python 3 and you’ll also need the hashlib module installed. It takes a password as the first argument and an optional salt as the second. You can of course extend masher and add many more creative hashing examples as you want to cover all the things! :)


SCENARIO 1 - There is no spoon/salt

admin | c405809bd81ba3f61551ec53c02a27f8ca651b1b
alice | c405809bd81ba3f61551ec53c02a27f8ca651b1b
bob   | aaff79a667366827c143384a3b51280174e90b7a
eve   | 8012d6062a92cc0bcbba6649bfb48de59006d9f2
tom   | a091a096b0947112c9e8838ea1996a3b6d568417

Looking at the users table above, what jumps out at me right away is that the users "admin" and "alice" share exactly the same password hash. The only way this could happen is if they both had the same password and that there is either no salt or that the salt is the same for both users (and all the other users for that matter). Let’s explore the no salt option in the first instance.

We know the password of our own user (tom) which is “Password123” since we're logged in as them for the application test. The password hash here is "a091a096b0947112c9e8838ea1996a3b6d568417".

We drop "a091a096b0947112c9e8838ea1996a3b6d568417" into 'hash-identifier' which states there are two likely hashes, this could be either a SHA-1 hash or a MySQL5 hash made up of SHA-1(SHA-1($pass)).

Let's double SHA-1 "Password123" using Python and see if that works out to be correct.



The password hash "b867055c61bea33bab533ef0900d1b193fbe6844" from SHA1(SHA1(password)) does not match "a091a096b0947112c9e8838ea1996a3b6d568417" (hash from the users table), even on a good day.

Below, the SHA-1 of "Password123" is "b2e98ad6f6eb8508dd6a14cfa704bad7f05f6fb1" also doesn't match.



We could, at this stage, manually try all different transformations and permutations of this and other hashes, but that would be somewhat painful and take a lot of time, or... we could use masher, the multiple (password) 'asher.



..and just like that, we have a match.

SHA1(SHA1(SHA1(PASS): a091a096b0947112c9e8838ea1996a3b6d568417

From our users table the password hash for tom if you remember:

tom | a091a096b0947112c9e8838ea1996a3b6d568417

So it turns out all we needed to do was a little SHA SHA SHA dance... (I'm here all week...)

Armed with this knowledge, we can now go a little bit crazy inside a for loop and have masher do the SHA SHA SHA dance for us with a dance partner from passwords.txt (our dictionary file), with t.txt watching.

# for i in `cat passwords.txt`; do python3 masher.py $i >> t.txt; done

[grab a coffee]

Now what we have inside "t.txt" is what I can only describe as pure 100% organic hash goodness. It will contain various permutations of all the things, but more importantly, the SHA1(SHA1(SHA1(password))) of every dictionary word in passwords.txt.

Let's go fishing.

As a reminder, the password hash of admin (and also of alice) from the users table:

admin | c405809bd81ba3f61551ec53c02a27f8ca651b1b

Bit of grepping, and we have our winner.



Admin's password is "Hello123".

SHA1(SHA1(SHA1(PASS): c405809bd81ba3f61551ec53c02a27f8ca651b1b

The users table:

admin | c405809bd81ba3f61551ec53c02a27f8ca651b1b


SCENARIO 2 - Ready salted flavour

A new users table with freshly baked password hashes.

admin | 9217241c733ba996556b590a410fdd8be2ae4d54
alice | 9217241c733ba996556b590a410fdd8be2ae4d54
bob   | cc45da7467df85da194442d0ad1189eaab443f1e
eve   | 6da7195f8e01826b6600554a962590125675434f
tom   | bd7eec2924255c6571efcbf9800fab947cb17098

A salt could still be in play here even with the two exact password hashes for "admin" and "alice" - it could be that the same salt has been applied globally to all users.

Let's feed in the password hash of our own user tom (bd7eec2924255c6571efcbf9800fab947cb17098) from the users table into 'hash-identifier'. It states like before, two likely hashes, this could be either a SHA-1 hash or a MySQL5 hash made up of SHA-1(SHA-1($pass)).

We already know what the double SHA-1 of our password (Password123) is from before: "b867055c61bea33bab533ef0900d1b193fbe6844".

The double SHA-1 hash "b867055c61bea33bab533ef0900d1b193fbe6844" does not match "bd7eec2924255c6571efcbf9800fab947cb17098" (from the users table).

It could be that there are other transformations to try or it could also be that there is a salt in play. Let's explore that assumption.

Like before, we pull the old masher.py out the toolkit but no messing about this time, we go straight into the for loop party - ain't no party like a for loop party. This time we're passing in the password which we know for our user (tom) but letting masher ask passwords.txt for the salt to pair up with it. [masher takes two inputs, a password followed by an optional salt.]

# for i in `cat passwords.txt`; do python3 masher.py Password123 $i >> tt.txt; done

[grab another coffee]

Like before, tt.txt will contain the pirate treasure. If you don't remember (I don't...), the password hash entry in the users table for tom was:

tom | bd7eec2924255c6571efcbf9800fab947cb17098

Let's open up that treasure chest with good old grep.



We have a successful match for a salt of "saltedchips":

SHA1(SHA1(SHA1(SALTPASS)): bd7eec2924255c6571efcbf9800fab947cb17098

With our password hash of:

tom | bd7eec2924255c6571efcbf9800fab947cb17098

Now we know the salt, we're going to have to call in again at the for loop party but with a twist. This time the password will be the centre of attention in these loops since we can now feed in the salt value - a little bit like scenario 1 again but slightly different.

# for i in `cat passwords.txt`; do python3 masher.py $i saltedchips >> ttt.txt; done

[grab some biscuits]

ttt.txt will contain treasure, sprinkled with sea salt and gold. Ok let's aim the grep cannons at that admin user:

admin | 9217241c733ba996556b590a410fdd8be2ae4d54

Deploy grep.



And we have a direct hit for a password of "Hello123", with the salt of "saltedchips" for the admin user.

SHA1(SHA1(SHA1(SALTPASS)): 9217241c733ba996556b590a410fdd8be2ae4d54

The users table:

admin | 9217241c733ba996556b590a410fdd8be2ae4d54

…and if all that fails, you can always tail -f ttt.txt while in the for loop and live your best Hollywood hacker life.