So, it's now 2016. GPU password-cracking is pretty well-known and accessible, yet companies and developers are still using little more than md5 to hash customers' passwords! Notice I simply said "hash", because I hesitate to call this "protection". Every time I see someone still using md5 it makes my question their entire security model.

I was just discussing with my friend @jmgosney the benefits of patching your own systems when vendors won't. Especially with these PHP systems (MyBB and vBulletin both come to mind here), it's often easy to swap out their cryptographic functions with much better industry standard functions. We happen to like bcrypt a great deal for this, and have both made adjustments to production systems to replace md5 with bcrypt. Since Jeremi already made one of these patches just recently for his own MyBB deployment, we've decided to share the patch with everyone in hopes to try and make the Internet a little safer if we can.

The following patch has been updated and tested to work with the latest MyBB 1.8.6.
You will need to make a few slight modifications to your db for this patch to work.

ALTER TABLE mybb.mybb_users MODIFY salt CHAR(21);
ALTER TABLE mybb.mybb_users MODIFY password CHAR(128);
ALTER TABLE mybb.mybb_forums MODIFY password CHAR(128);

Download the latest MyBB 1.8.6, unzip it, and change into that directory:

$ wget http://resources.mybb.com/downloads/mybb_1806.zip
$ unzip mybb_1806.zip
$ cd mybb_1806/

Next, copy his patch (listed below) into a file epixoip.patch in that directory.

diff -Naur Upload/inc/datahandlers/login.php Upload-epixoip/inc/datahandlers/login.php
--- Upload/inc/datahandlers/login.php   2015-05-25 11:37:14.000000000 -0700
+++ Upload-epixoip/inc/datahandlers/login.php   2015-06-14 21:14:23.516673438 -0700
@@ -171,39 +171,9 @@
            $this->invalid_combination();
        }

-       if($strict == true)
-       {
-           if(!$this->login_data['salt'])
-           {
-               // Generate a salt for this user and assume the password stored in db is a plain md5 password
-               $this->login_data['salt'] = generate_salt();
-               $this->login_data['password'] = salt_password($this->login_data['password'], $this->login_data['salt']);
-
-               $sql_array = array(
-                   "salt" => $this->login_data['salt'],
-                   "password" => $this->login_data['password']
-               );
-
-               $db->update_query("users", $sql_array, "uid = '{$this->login_data['uid']}'");
-           }
-
-           if(!$this->login_data['loginkey'])
-           {
-               $this->login_data['loginkey'] = generate_loginkey();
-
-               $sql_array = array(
-                   "loginkey" => $this->login_data['loginkey']
-               );
-
-               $db->update_query("users", $sql_array, "uid = '{$this->login_data['uid']}'");
-           }
-       }
-
-       $salted_password = md5(md5($this->login_data['salt']).$password);
-
        $plugins->run_hooks('datahandler_login_verify_password_end', $args);

-       if($salted_password !== $this->login_data['password'])
+       if(validate_password_from_username($this->login_data['username'], $password) === false)
        {
            $this->invalid_combination(true);
            return false;
diff -Naur Upload/inc/functions_user.php Upload-epixoip/inc/functions_user.php
--- Upload/inc/functions_user.php   2015-05-25 11:37:14.000000000 -0700
+++ Upload-epixoip/inc/functions_user.php   2015-06-14 21:34:07.812664455 -0700
@@ -74,7 +74,7 @@
  * Checks a password with a supplied uid.
  *
  * @param int $uid The user id.
- * @param string $password The plain-text password.
+ * @param string $password The password md5 hash.
  * @param array $user An optional user data array.
  * @return boolean|array False when not valid, user data array when valid.
  */
@@ -90,17 +90,6 @@
        $query = $db->simple_select("users", "uid,username,password,salt,loginkey,usergroup", "uid='".(int)$uid."'");
        $user = $db->fetch_array($query);
    }
-   if(!$user['salt'])
-   {
-       // Generate a salt for this user and assume the password stored in db is a plain md5 password
-       $user['salt'] = generate_salt();
-       $user['password'] = salt_password($user['password'], $user['salt']);
-       $sql_array = array(
-           "salt" => $user['salt'],
-           "password" => $user['password']
-       );
-       $db->update_query("users", $sql_array, "uid='".$user['uid']."'");
-   }

    if(!$user['loginkey'])
    {
@@ -108,9 +97,37 @@
        $sql_array = array(
            "loginkey" => $user['loginkey']
        );
+
        $db->update_query("users", $sql_array, "uid = ".$user['uid']);
    }
-   if(salt_password(md5($password), $user['salt']) === $user['password'])
+
+   if(strlen($user['password']) === 32)
+   {
+       if(!$user['salt'])
+       {
+           if ($password !== $user['password'])
+               return false;
+       }
+       else
+       {
+           if (md5(md5($user['salt']).$password) !== $user['password'])
+               return false;
+       }
+
+       $user['salt'] = generate_salt();
+       $user['password'] = salt_password($password, $user['salt']);
+
+       $sql_array = array(
+           "salt" => $user['salt'],
+           "password" => $user['password']
+       );
+
+       $db->update_query("users", $sql_array, "uid='".$user['uid']."'");
+
+       return $user;
+   }
+
+   if(salt_password($password, $user['salt']) === $user['password'])
    {
        return $user;
    }
@@ -118,6 +135,8 @@
    {
        return false;
    }
+
+   return false;
 }

 /**
@@ -175,7 +194,7 @@
  */
 function salt_password($password, $salt)
 {
-   return md5(md5($salt).$password);
+   return crypt($password, '$2y$12$'.$salt.'$');
 }

 /**
@@ -185,7 +204,7 @@
  */
 function generate_salt()
 {
-   return random_str(8);
+   return random_str(21);
 }

 /**
diff -Naur Upload/install/index.php Upload-epixoip/install/index.php
--- Upload/install/index.php    2015-06-14 16:01:16.896792359 -0700
+++ Upload-epixoip/install/index.php    2015-06-14 21:33:55.976678146 -0700
@@ -2217,9 +2217,9 @@

    echo $lang->done_step_admincreated;
    $now = TIME_NOW;
-   $salt = random_str();
+   $salt = random_str(21);
    $loginkey = generate_loginkey();
-   $saltedpw = md5(md5($salt).md5($mybb->get_input('adminpass')));
+   $saltedpw = crypt(md5($mybb->get_input('adminpass')), '$2y$12$'.$salt.'$');

    $newuser = array(
        'username' => $db->escape_string($mybb->get_input('adminuser')),
diff -Naur Upload/install/resources/mysql_db_tables.php Upload-epixoip/install/resources/mysql_db_tables.php
--- Upload/install/resources/mysql_db_tables.php    2015-05-25 11:37:16.000000000 -0700
+++ Upload-epixoip/install/resources/mysql_db_tables.php    2015-06-14 21:33:32.116665530 -0700
@@ -287,7 +287,7 @@
   usepostcounts tinyint(1) NOT NULL default '0',
   usethreadcounts tinyint(1) NOT NULL default '0',
   requireprefix tinyint(1) NOT NULL default '0',
-  password varchar(50) NOT NULL default '',
+  password char(128) NOT NULL default '',
   showinjump tinyint(1) NOT NULL default '0',
   style smallint unsigned NOT NULL default '0',
   overridestyle tinyint(1) NOT NULL default '0',
@@ -1034,8 +1034,8 @@
 $tables[] = "CREATE TABLE mybb_users (
   uid int unsigned NOT NULL auto_increment,
   username varchar(120) NOT NULL default '',
-  password varchar(120) NOT NULL default '',
-  salt varchar(10) NOT NULL default '',
+  password char(128) NOT NULL default '',
+  salt char(21) NOT NULL default '',
   loginkey varchar(50) NOT NULL default '',
   email varchar(220) NOT NULL default '',
   postnum int(10) NOT NULL default '0',

Finally, use the patch utility to apply the patch:

$ patch -p0 epixoip.patch

And that's it! It makes you wonder why the vendors don't do this themselves, doesn't it?

(Now, we just need enough people to write in to the vendors/developers of these products and urge them to use better/modern security practices for said products.)