Bypassing authentication in Invision Power board with CVE-2016–2564

Summary

I discovered a potential issue with how Invision Power Board generates its session cookies, which could lead to the compromise of user sessions or the cron key. It was reported and assigned CVE-2016–2564 on 02/25/2016, fixed on 2/26/2016, and the update with the fix was released on 3/21/2016 in version 4.1.9. As far as I am aware, it was present in all 4.0 releases and all releases prior to 4.1.9.

4.1.9 fixed a few other security issues reported by me in the suite, including:

  • Timing-unsafe comparison of the cron key used in the web-exposed cron runner.
  • A type conversion issue when comparing MD5 hashes in the web-exposed cron runner, when the auto-generated cron key was a “magic hash”.
  • It was impossible to re-generate the session cookie for a given user, even if the password was changed or all sessions were logged out of.
  • The generic encryption class uses `md5(sql_password . sql_database_name)` for deriving an AES key, as well as for the IPS Connect master key. This was not resolved.

Exploitation

Invision Power Board takes the MD5 hash of the result of uniqid() , and uses this for various random keys. Most critically, the session key used to identify a user from the cookie uses this method. As the documentation for uniqid() warns, “This function does not generate cryptographically secure values, and should not be used for cryptographic purposes.”

uniqid() is worse than the manual makes it out to be. An example return value is 58fc30c53db63 . Already, this is only <7 bytes of entropy. But it becomes worse, because without the more_entropy flag set, PHP only uses the current time to generate the return value, as seen in the PHP source code:

sec = (int) tv.tv_sec; 
usec = (int) (tv.tv_usec % 0x100000);
if (more_entropy) {  
uniqid = strpprintf(0, "%s%08x%05x%.8F", prefix, sec, usec, php_combined_lcg() * 10);
} else {
uniqid = strpprintf(0, "%s%08x%05x", prefix, sec, usec);
}

The first four bytes are the current UNIX timestamp, and the last 20 bits are derived from the current time in microseconds.

This gives a bit less than 2²⁰, or one million, possible results per given second. If you are able to predict when a new session key is generated for a user, you can guess their key with a decent number of requests, depending on how accurate your guess is. On a popular forum, you may not even need to target a specific user, as the number of users logging in at one time may be large enough.

Someone with the ability to sniff the timing of encrypted traffic may be able to perform this attack with a much lower number of requests.

Resolution

Invision Power Board chose to simply flip the more_entropy flag. This is still dangerous, but much harder to exploit. As seen above, this utilizes PHP’s internal LCG, which is an insecure RNG, but many more guesses are required, and are likely remotely impractical.

A better resolution would be to use random_bytes(32) , and ship the permissively licensed polyfill for it, written by Scott Arciszewski. They did not provide a reason for choosing not to use this.

It is worth noting that something may have changed in terms of their resolution between March of 2016 and today, but since I have not paid $35 to renew my license, I am incapable of checking.