A cron job that I run every day to screen scrape some figures from a financial services provider recently broke and upon investigation it transpired that it was no longer able to login to my account. On it’s own that isn’t particularly unusual but it was when I started investigating what had changed and needed to be fixed in my script that things got a bit weird…
The login process involves submitting a form consisting of a username, a PIN and a password over a secure https connection. The PIN and password are sent in plain text, but that is fine as the connection is secure.
So it seems they are just being run through MD5 on the client before being sent to the server which seems a little odd given the connection is secure. This does however give rise to a whole series of questions:
Why hash passwords on the client? They are secure in transit so the only threat this seems to protect against is somebody at the server end reading them.
Given that this is new code why on earth is it using MD5 which is widely considered to be at best unsuitable for new applications.
Why reduce entropy unnecessarily by uppercasing the password before hashing it?
The answer to the those last two questions probably lies in what this whole scheme implies about how both the PIN and password are being stored on the server, namely that this code is mirroring how they have historically been stored, which is to say as unsalted, unstretched MD5 hashes, in violation of pretty much every modern password security guideline.
We pretty much know that must be the case, as the client side hashing means they are not able recover the original password in order to hash it more securely, so unless they are rehashing the MD5 hash with a salt which would be very odd they must just be comparing what the client sends directly with their database.
So the end result is that what was presumably intended as a security upgrade (hashing passwords on the client) has wound up revealing just how bad the backend security is without actually improving anything!
One odd thing I noticed is that although the code shown above appears to be base64 encoding the hash what I was seeing on the wire appeared to be a hex string rather than a base64 string.
A little investigation revealed that although they were using a
had wrongly decided that
CryptoJS.enc.Base64 was unused and
removed it meaning that it evaluated to
toString to default to hex encoding instead.
It then turned out that the server didn’t actually accept base64 encoding so obviously it was either changed to accept what the client was actually sending or the attempt to do base64 encoding was never right in the first place!