Go Back   vb.org Archive > vBulletin Article Depository > Read An Article > vBulletin 4 Articles
FAQ Community Calendar Today's Posts Search

Reply
 
Thread Tools
Changing vBulletin 4 its password hashing to use BCrypt
Dave
Join Date: May 2010
Posts: 2,583

 

Show Printable Version Email this Page Subscription
Dave Dave is offline 10-02-2014, 10:00 PM

This article has also been published at http://blog.technidev.com/changing-v...to-use-bcrypt/, my personal blog about security, exploits, development, etc.

Introduction
By default, vBulletin uses a very basic and easy to crack password hashing:
PHP Code:
md5(md5($password) . $salt
With the GPU's of these days, it may be a matter of minutes to crack a password using a dictionary attack/rainbow tables.

Fortunately, it's very easy to change the password hashing. I will change the password hashing of vBulletin to use BCrypt, a much better algorithm. The password hashing will look like this at the end of this guide:
PHP Code:
password_hash(md5(md5($password) . $salt), PASSWORD_BCRYPT, array('salt' => $salt)) 

Requirements for this guide:
- Access to the FTP or permission to edit .php files on the server.
- PHP version of 5.5+ (we make use of the password_hash function)
- You must update all of the passwords in the current database.
- vBulletin 4.2.2 (this modification has been made on 4.2.2)
- Database access in the form of SSH/PHPMyAdmin or any other database manager tool

Create a backup of the files we are going to edit first so you can always restore it in case it goes wrong!

Step 1: modify password column type

First things first, we have to modify the type of the password column in the user table.
You can either do this in PHPMyAdmin by changing it to char(60) or by executing the following query:
PHP Code:
ALTER TABLE user MODIFY password char(60); 

Step 2: edit /includes/functions_login.php

Now let's modify the verify_authentication function in the /includes/functions_login.php file.
Look for:
PHP Code:
        if (
            
$vbulletin->userinfo['password'] != iif($password AND !$md5passwordmd5(md5($password) . $vbulletin->userinfo['salt']), '') AND
            
$vbulletin->userinfo['password'] != iif($md5passwordmd5($md5password $vbulletin->userinfo['salt']), '') AND
            
$vbulletin->userinfo['password'] != iif($md5password_utfmd5($md5password_utf $vbulletin->userinfo['salt']), '')
        ) 
And replace it with:
PHP Code:
        $hash1 password_hash(md5(md5($password) . $vbulletin->userinfo['salt']), PASSWORD_BCRYPT, array('salt' => $vbulletin->userinfo['salt']));
        
$hash2 password_hash(md5($md5password $vbulletin->userinfo['salt']), PASSWORD_BCRYPT, array('salt' => $vbulletin->userinfo['salt']));
        
$hash3 password_hash(md5($md5password_utf $vbulletin->userinfo['salt']), PASSWORD_BCRYPT, array('salt' => $vbulletin->userinfo['salt']));

        if (
            
$vbulletin->userinfo['password'] != iif($password AND !$md5password$hash1'') AND
            
$vbulletin->userinfo['password'] != iif($md5password$hash2'') AND
            
$vbulletin->userinfo['password'] != iif($md5password_utf$hash3'')
        ) 
This function will, as the function name describes, very the authentication.


Step 3: edit /includes/class_dm_user.php

This file contains the verify_password and hash_password function which must be changed as well.
Look for the hash_password function:
PHP Code:
    function hash_password($password$salt)
    {
        
// if the password is not already an md5, md5 it now
        
if ($password == '')
        {
        }
        else if (!
$this->verify_md5($password))
        {
            
$password md5($password);
        }

        
// hash the md5'd password with the salt
        
return md5($password $salt);
    } 
And replace it with:
PHP Code:
    function hash_password($password$salt)
    {
        
// if the password is not already an md5, md5 it now
        
if ($password == '')
        {
        }
        else if (!
$this->verify_md5($password))
        {
            
$password md5($password);
        }

        
// hash the md5'd password with the salt
        
return password_hash(md5($password $salt), PASSWORD_BCRYPT, array('salt' => $salt));
    } 
Now look for the verify_password function:
PHP Code:
    function verify_password(&$password)
    {
        
//regenerate the salt when the password is changed.  No reason not to and its
        //an easy way to increase the size when the user changes their password (doing
        //it this way avoids having to reset all of the passwords)
        
$this->user['salt'] = $salt $this->fetch_user_salt();

        
// generate the password
        
$password $this->hash_password($password$salt);

        if (!
defined('ALLOW_SAME_USERNAME_PASSWORD'))
        {
            
// check if password is same as username; if so, set an error and return false
            
if ($password == md5(md5($this->fetch_field('username')) . $salt))
            {
                
$this->error('sameusernamepass');
                return 
false;
            }
        }

        
$this->set('passworddate''FROM_UNIXTIME(' TIMENOW ')'false);

        return 
true;
    } 
And replace it with:
PHP Code:
    function verify_password(&$password)
    {
        
//regenerate the salt when the password is changed.  No reason not to and its
        //an easy way to increase the size when the user changes their password (doing
        //it this way avoids having to reset all of the passwords)
        
$this->user['salt'] = $salt $this->fetch_user_salt();

        
// generate the password
        
$password $this->hash_password($password$salt);

        if (!
defined('ALLOW_SAME_USERNAME_PASSWORD'))
        {
            
// check if password is same as username; if so, set an error and return false
            
if ($password == password_hash(md5(md5($this->fetch_field('username')) . $salt), PASSWORD_BCRYPT, array('salt' => $salt)))
            {
                
$this->error('sameusernamepass');
                return 
false;
            }
        }

        
$this->set('passworddate''FROM_UNIXTIME(' TIMENOW ')'false);

        return 
true;
    } 
We have to modify this function since there's a check to see if the password matches the current username, which is not allowed by default.


Step 4: update all passwords in the user table

The only downside of this is that we have to update all passwords in the database.
But no worries, no one will have to change or update their password to make this work since we use the old password hashing in BCrypt.

Please make a backup of your database or user table before doing this so you can restore it in case it does not work as intended!

Create a file in the root of your forum and name it something like update_passwords.php. Contents of the file:
PHP Code:
require("./global.php");
$query $db->query_read("SELECT userid, password, salt FROM user WHERE password NOT LIKE '$%' ORDER BY userid ASC");

echo 
'We have ' $db->num_rows($query) . ' password(s) to update..<br><br>';

while(
$row $db->fetch_array($query)){
    
$db->query_write("UPDATE user SET password = '" password_hash($row['password'], PASSWORD_BCRYPT, array('salt' => $row['salt'])) . "' WHERE userid = '" $row['userid'] . "'");
}

echo 
'Updated all passwords, re-run this script to be sure!'
This will update all of the current password hashes to use BCrypt.
In case the script timed out, just run it again or change the script execution timeout.


Step 5: verifying functionality

Now try to login onto your forum and see if that works, in case you get an incorrect username/password error, it means you did something wrong in this guide.
In case it works, try to update your password and relog to see if that part works as well.

I tested the login, registration, remember me checkbox and changing password function on a local test forum which seemed to work fine.

Support will be provided here (comment box). I'll answer the most basic questions here at vbulletin.org.
Reply With Quote
  #2  
Old 11-09-2014, 06:29 AM
AndrewSimm AndrewSimm is offline
 
Join Date: Sep 2006
Location: Atlanta, GA
Posts: 222
Благодарил(а): 0 раз(а)
Поблагодарили: 0 раз(а) в 0 сообщениях
Default

Has anyone tried this?
Reply With Quote
  #3  
Old 11-09-2014, 08:16 AM
Dave Dave is offline
 
Join Date: May 2010
Posts: 2,583
Благодарил(а): 0 раз(а)
Поблагодарили: 0 раз(а) в 0 сообщениях
Default

Yeah I've done this on my local vBulletin testing forum.
Reply With Quote
  #4  
Old 11-09-2014, 01:38 PM
nerbert nerbert is offline
 
Join Date: May 2008
Posts: 784
Благодарил(а): 0 раз(а)
Поблагодарили: 0 раз(а) в 0 сообщениях
Default

A problem I see here is that the browser still transmits the password with the md5 hashes and an eavesdropper can still intercept them and crack them. All your advanced hashing happens on the server after it receives the md5 hashes. What you could do is replace the original vbulletin_md5.js file with one that does the md5 and bcrypt hashing . Then the password is better encrypted in transit. I think you would have to use vBulletin's original code and then add more code (available as open source) to further encrypt both versions of the hashes. Once this is done you could skip the PHP code that bcrypt hashes on the server

There's a further thing to consider though, it seems to me that there's a problem with vbulletin_md5.js and older (and maybe newer) versions of IE, so it doesn't encrypt at all. You'll have to check this out with various browsers. There's open source md5 javascript available and you could copy the vbulletin code that handles the difference between vb_login_md5password and vb_login_md5password_utf into the new md5 algorithm.
Reply With Quote
  #5  
Old 11-09-2014, 02:08 PM
Dave Dave is offline
 
Join Date: May 2010
Posts: 2,583
Благодарил(а): 0 раз(а)
Поблагодарили: 0 раз(а) в 0 сообщениях
Default

Quote:
Originally Posted by nerbert View Post
A problem I see here is that the browser still transmits the password with the md5 hashes and an eavesdropper can still intercept them and crack them. All your advanced hashing happens on the server after it receives the md5 hashes. What you could do is replace the original vbulletin_md5.js file with one that does the md5 and bcrypt hashing . Then the password is better encrypted in transit. I think you would have to use vBulletin's original code and then add more code (available as open source) to further encrypt both versions of the hashes. Once this is done you could skip the PHP code that bcrypt hashes on the server

There's a further thing to consider though, it seems to me that there's a problem with vbulletin_md5.js and older (and maybe newer) versions of IE, so it don't encrypt at all. You'll have to check this out with various browsers. There's open source md5 javascript available and you could copy the vbulletin code that handles the difference between vb_login_md5password and vb_login_md5password_utf into the new md5 algorithm.
True, but this will be more difficult (because of cross-browser support if you want to implement other hashing methods with JavaScript). Also this is a reason why you should use HTTPS.
Reply With Quote
  #6  
Old 11-09-2014, 07:45 PM
Zachery's Avatar
Zachery Zachery is offline
 
Join Date: Jul 2002
Location: Ontario, Canada
Posts: 11,440
Благодарил(а): 0 раз(а)
Поблагодарили: 0 раз(а) в 0 сообщениях
Default

If someone can get an MITM attack, you've got a lot bigger problems than the password being transmitted as md5 or plaintext.
Reply With Quote
  #7  
Old 11-11-2014, 03:00 AM
AndrewSimm AndrewSimm is offline
 
Join Date: Sep 2006
Location: Atlanta, GA
Posts: 222
Благодарил(а): 0 раз(а)
Поблагодарили: 0 раз(а) в 0 сообщениях
Default

Dave could this be written into a mod? It would help when IB updates vbulletin as I wouldn't have to redo the edits.
Reply With Quote
  #8  
Old 11-11-2014, 07:19 AM
Dave Dave is offline
 
Join Date: May 2010
Posts: 2,583
Благодарил(а): 0 раз(а)
Поблагодарили: 0 раз(а) в 0 сообщениях
Default

Quote:
Originally Posted by AndrewSimm View Post
Dave could this be written into a mod? It would help when IB updates vbulletin as I wouldn't have to redo the edits.
Nope, this requires modifications in files which can not be done in hooks.
Reply With Quote
Reply


Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT. The time now is 08:56 AM.


Powered by vBulletin® Version 3.8.12 by vBS
Copyright ©2000 - 2024, vBulletin Solutions Inc.
X vBulletin 3.8.12 by vBS Debug Information
  • Page Generation 0.04584 seconds
  • Memory Usage 2,340KB
  • Queries Executed 23 (?)
More Information
Template Usage:
  • (1)SHOWTHREAD
  • (1)ad_footer_end
  • (1)ad_footer_start
  • (1)ad_header_end
  • (1)ad_header_logo
  • (1)ad_navbar_below
  • (1)ad_showthread_beforeqr
  • (10)bbcode_php
  • (2)bbcode_quote
  • (1)footer
  • (1)forumjump
  • (1)forumrules
  • (1)gobutton
  • (1)header
  • (1)headinclude
  • (1)modsystem_article
  • (1)navbar
  • (4)navbar_link
  • (120)option
  • (8)post_thanks_box
  • (2)post_thanks_box_bit
  • (8)post_thanks_button
  • (1)post_thanks_javascript
  • (1)post_thanks_navbar_search
  • (1)post_thanks_postbit
  • (8)post_thanks_postbit_info
  • (7)postbit
  • (8)postbit_onlinestatus
  • (8)postbit_wrapper
  • (1)spacer_close
  • (1)spacer_open
  • (1)tagbit_wrapper 

Phrase Groups Available:
  • global
  • inlinemod
  • postbit
  • posting
  • reputationlevel
  • showthread
Included Files:
  • ./showthread.php
  • ./global.php
  • ./includes/init.php
  • ./includes/class_core.php
  • ./includes/config.php
  • ./includes/functions.php
  • ./includes/class_hook.php
  • ./includes/modsystem_functions.php
  • ./includes/functions_bigthree.php
  • ./includes/class_postbit.php
  • ./includes/class_bbcode.php
  • ./includes/functions_reputation.php
  • ./includes/functions_post_thanks.php 

Hooks Called:
  • init_startup
  • init_startup_session_setup_start
  • init_startup_session_setup_complete
  • cache_permissions
  • fetch_postinfo_query
  • fetch_postinfo
  • fetch_threadinfo_query
  • fetch_threadinfo
  • fetch_foruminfo
  • style_fetch
  • cache_templates
  • global_start
  • parse_templates
  • global_setup_complete
  • showthread_start
  • showthread_getinfo
  • forumjump
  • showthread_post_start
  • showthread_query_postids
  • showthread_query
  • bbcode_fetch_tags
  • bbcode_create
  • showthread_postbit_create
  • postbit_factory
  • postbit_display_start
  • post_thanks_function_post_thanks_off_start
  • post_thanks_function_post_thanks_off_end
  • post_thanks_function_fetch_thanks_start
  • fetch_musername
  • post_thanks_function_fetch_thanks_end
  • post_thanks_function_thanked_already_start
  • post_thanks_function_thanked_already_end
  • post_thanks_function_fetch_thanks_bit_start
  • post_thanks_function_show_thanks_date_start
  • post_thanks_function_show_thanks_date_end
  • post_thanks_function_fetch_thanks_bit_end
  • post_thanks_function_fetch_post_thanks_template_start
  • post_thanks_function_fetch_post_thanks_template_end
  • postbit_imicons
  • bbcode_parse_start
  • bbcode_parse_complete_precache
  • bbcode_parse_complete
  • postbit_display_complete
  • post_thanks_function_can_thank_this_post_start
  • tag_fetchbit_complete
  • forumrules
  • navbits
  • navbits_complete
  • showthread_complete