PostgreSQL: Pass the Hashed Hash

| By Trenton Ivey | Comment | |

After a friend sent me a link to a thread on the forum, I spent a little time digging through the PostgreSQL source code.

TL;DR: Hashing the user password before sending it over the wire could be a good thing if done correctly. PostgreSQL didn’t do it correctly. This resulted in a (somewhat different) pass-the-hash style vulnerability

Authentication Review

Before looking at the PostgreSQL vulnerability, it helps to review how many other authentication mechanisms work:

  • The username and password is sent to a server over some secure channel (e.g. via a post parameter in HTTPS)
  • The server then hashes the password (we will use p to represent passwords from here on out). p' = hash(p);
  • The server then compares the p' with a stored value P'
  • If they match, “come on in”. Otherwise, “get out of here”

In these cases, the ‘cleartext’ password is sent to the server for authentication (hopefully over an encrypted channel).

PostgreSQL Authentication

PostgreSQL tried to avoid sending user passwords to the server by doing things a bit differently (at least in the MD5 authentication mode). When the PostgreSQL client sends a user password (p) during authentication it:

  1. Calculates p' = MD5(p + user)
  2. Calculates p'' = MD5(p' + connection-salt)
  3. Sends 'MD5' + p'' to the server

I think there were good intentions behind step #2. The goal was to prevent the user’s password from going over the wire in the clear if the transmission was unencrypted. Anyone who captures the hash on the wire would have to crack p'' and then p' before they could use the user’s password elsewhere (assuming it was reused). Because these hashes are salted with a username and then a unique salt per connection, this should be difficult.

Pass the Hashed Hash

Looking at the source, the server side does a few things when authenticating a user (the following is a bit of an oversimplification):

  • The server checks to see if the authentication method used was MD5 or plain:
    • If plain authentication (p) was used, the server calculates p'' with MD5(MD5(p + username) + connection-salt)
    • If MD5 authentication (p'') was used, the server doesn’t do anything
      • note that the server has no way of knowing if the client generated p'' from p' or from p
  • The Server checks if the stored password is plaintext or hashed (the source calls it encrypted, which makes me question their crypto prowess)
    • If the stored value is hashed (P'), the server gets P'' by calculating (MD5(P' + connection-salt))
    • If the stored value is plaintext (P), the server gets P'' by calculating MD5(MD5(P + username) + connection-salt))
  • The server then compares P'' with p''
  • If they match, “come on in”. Otherwise, “get out of here”

The researchers at Hashcat found that you could skip step #1 in the client if you knew p'. You could just calculate and send p'' = MD5(p' + connection-salt). Authentication still works!

It is important to note that capturing the hash over the wire (p'') cannot directly be used in a pass-the-hash style attack due to the random connection-salt. However, it may be possible to keep connecting to the server over and over again until the salt that the PostgreSQL server provides is the same as the one used to create the captured hash. Even with a small salt (255^4) this would be a difficult attack.

comments powered by Disqus