“One in 256 times *any* password might get you in” – MySQL authentication disaster

We’ve reported on a number of hacks lately in which password hashes have been stolen.

A hashed password is useless on its own, of course, but checking a list of really obvious passwords against a hash can quickly reveal low-hanging fruit.

That’s why we keep advising you to choose non-obvious passwords – so they’re harder for hackers to guess.

But what if the authentication system itself is at fault?

You could have the hardest-to-guess password, salted and hashed thousands of times, and still be at risk.

That happened about a year ago at Dropbox, for instance, when the file-sharing site inadvertently removed its authentication validation altogether for a few hours. Anyone could use any password.

It’s happened again, this time with a more corporate angle.

Open source database giant MySQL (and its post-Oracle fork, MariaDB) contained a bug which meant that your password might be checked correctly only 255 out of every 256 times. One in 256 times, anything might get you in.

The good news is that this bug doesn’t show itself on all systems, and was fixed well over a month ago in both MariaDB and MySQL. To summarise:

All MariaDB and MySQL versions up to 5.1.61, 5.2.11, 5.3.5, 5.5.22 are vulnerable. MariaDB versions from 5.1.62, 5.2.12, 5.3.6, 5.5.23 are not. MySQL versions from 5.1.63, 5.5.24, 5.6.6 are not.

The bad news is that the popular exploitation automation tool Metasploit now has a plug-in that tries to retrieve the password hashes from a vulnerable system. That’s so you can use this exploit whilst it lasts, and then recover weak passwords off-line to use later when the system is patched.

The vulnerability is a weird one.

When you compare two memory buffers in C – as you might when checking if a supplied password’s hash matches the one from your database – you usually use the memcmp() function.

If the two memory blocks are the same, memcmp() returns zero. (Although zero generally means FALSE in C, many library functions return zero on success, leaving a choice of non-zero values to denote various error conditions.)

If the memory blocks differ, memcmp() returns a number which indicates the sort order of the two memory buffers. A negative number means that the first block would sort before the second; positive means it would sort after it.

But what do “a negative number” and “a positive number” mean? According to my Linux man page:

And according to my OS X man page:

Since a byte can hold a value from zero to 255, my conclusions are therefore that:

* The value returned by memcmp() on OS X is theoretically limited to the range from -255 (zero minus 255) up to +255 (255 minus zero).

* The value returned by Linux isn’t obviously limited other than by the range of the int that the function returns.

* It would be unwise to assume any artificial limits on the return value of memcmp().

Apparently, in the MySQL code, the password hash validation bug happened if memcmp() returned a value outside the range from -128 up to +127 (the ambit of a signed char).

Then, with a one-in-256 chance, the code would recognise two cryptographic hashes as equal regardless of reality. In short, with repeated attempts to log in with any password, you’d eventually get in just by chance.

With most paths through the Linux memcmp() library code, the function just happens to stick to a return value which fits into a signed char, so the bug never shows itself.

But in some cases – if an SSE-accelerated implementation of memcmp() is used, for example – the bug may be triggered:

Whether a particular build of MySQL or MariaDB is vulnerable, depends on how and where it was built. A prerequisite is a memcmp() that can return an arbitrary integer (outside of -128..127 range). To my knowledge gcc builtin memcmp is safe, BSD libc memcmp is safe. Linux glibc sse-optimized memcmp is not safe, but gcc usually uses the inlined builtin version.

Ouch. Four security morals from this story:

1. Never make assumptions when coding. If a function returns an int, accept that any value which fits in an int might be returned, regardless of what the developer, the manual and received wisdom tell you.

2. Patch your MySQL or MariaDB installations if you haven’t already.

3. Don’t expose database servers to external connectivity unless you really mean to. Even inside your corporate network, be very restrictive about who can reach database servers at all, let alone log in to them.

4. Watch out for speed optimisations. Invoke them only if you genuinely need extra speed – they may represent a less-travelled code path and thus expose your users to obscure bugs.

NB. Thanks to Naked Security reader Bill for his email about this intriguing bug.