XMB Forum Software
Not logged in [Login - Register]
Go To Bottom

Printable Version  
Author: Subject: [1.9.12] UPDATED Secure Password Handling
AlwaysLosingPasswords
Member
***




Posts: 21
Registered: 11-25-2022
Member Is Offline


[*] posted on 11-30-2022 at 04:03 PM
[1.9.12] UPDATED Secure Password Handling


This version upgrades to PHP's hash_pbkdf2() function vs hash(). It also introduces a salt, custom iterations count and uses the SHA-512 algorithm.

pbkdf2 is NIST certified and FIPS 140 validated for secure hashing of passwords.

Here we go!

Step 1: While logged in as a Super Administrator, go to the Admin CP and select "Insert Raw SQL." Execute the following query:

Code:
ALTER TABLE $table_members MODIFY password varchar(128) NOT NULL


What it does is expands the length of the password field to 512 bits - 64 bytes: 128 characters.

Step 2: Create updatepw.php in the same directory as header.php, index.php, etc. Here are the contents of updatepw.php:

Code:
<?php define('X_SCRIPT', 'updatepw.php'); require 'header.php'; if (!X_MEMBER) { header('HTTP/1.0 403 Forbidden'); loadtemplates('misc_feature_notavailable'); eval('$css = "'.template('css').'";'); nav('Update Password'); eval('$header = "'.template('header').'";'); eval('$featureoff = "'.template('misc_feature_notavailable').'";'); end_time(); eval('$footer = "'.template('footer').'";'); echo $header; echo "<style>".$css."</style>"; echo $featureoff, $footer; exit(); } if (X_MEMBER) { $query = $db->query("SELECT COUNT(*) FROM ".X_PREFIX."members WHERE username='$xmbuser' AND LENGTH(password) > '33'"); $result = $db->result($query, 0); $db->free_result($query); if($result > 0) { header('HTTP/1.0 403 Forbidden'); loadtemplates('misc_feature_notavailable'); //eval('$css = "'.template('css').'";'); nav('Update Password'); eval('$header = "'.template('header').'";'); eval('$featureoff = "'.template('misc_feature_notavailable').'";'); end_time(); eval('$footer = "'.template('footer').'";'); echo $header; echo "<style>".$css."</style>"; echo $featureoff, $footer; exit(); } } nav('One-Time Password Update'); eval('$header = "'.template('header').'";'); eval('$css = "'.template('css').'";'); end_time(); eval('$footer = "'.template('footer').'";'); echo $header; echo "<style>".$css."</style>"; if(isset($_POST['updatepw']) && isset($_POST['updatepwcf'])) { //Form submitted if($_POST['updatepw'] != $_POST['updatepwcf']) { ?> <div class="card"> <div class="card-body"> <h5 class="card-title">Password Update Required</h5> <?PHP echo "<center><font class='mediumtxt'><b>The passwords you typed do not match!</b></font></center><br>"; echo "<center><form method='post' action='updatepw.php?runonce=true'>"; echo "<font class='smalltxt'>Enter new password: <input type='password' name='updatepw'> Enter again to confirm: <input type='password' name='updatepwcf'></font>"; echo " <input type='submit' name='updatebtn' value='Update'></form></center>"; ?> </div> </div> <?PHP }elseif(strlen($_POST['updatepw']) < 7 || strlen($_POST['updatepwcf']) < 7){ ?> <div class="card"> <div class="card-body"> <h5 class="card-title">Password Update Required</h5> <?PHP echo "<center><font class='mediumtxt'><b>The password you selected is too short! Longer passwords are more secure. Enter at least 7 characters.</b></font></center><br>"; echo "<center><form method='post' action='updatepw.php?runonce=true'>"; echo "<font class='smalltxt'>Enter new password: <input type='password' name='updatepw'> Enter again to confirm: <input type='password' name='updatepwcf'></font>"; echo "<p><input type='submit' name='updatebtn' value='Update'></p></form></center>"; ?> </div> </div> <?PHP }else{ ?> <div class="card"> <div class="card-body"> <h5 class="card-title">Password Update Required</h5> <?PHP $betterpassword = hash_pbkdf2("sha512",$_POST['updatepw'],$self['regdate'] . $self['regip'].PBKDF2PEPPER,PBKDF2ITERATIONS); $db->query("UPDATE ".X_PREFIX."members SET password='$betterpassword' WHERE username='$xmbuser' LIMIT 1"); echo "<center><font class='mediumtxt'><b>Your password has been updated! Please <a href='index.php'>return to the forum index</a> and login using your new password.</b></font></center><br>"; ?> </div> </div> <?PHP } }else{ ?> <div class="card"> <div class="card-body"> <h5 class="card-title">Password Update Required</h5> <?PHP //Collect new PW combo from form echo "<center><form method='post' action='updatepw.php?runonce=true'>"; echo "<font class='smalltxt'>Enter new password: <input type='password' name='updatepw'> Enter again to confirm: <input type='password' name='updatepwcf'></font>"; echo " <input type='submit' name='updatebtn' value='Update'></form></center>"; ?> </div> </div> <?PHP } echo $footer; ?>


STEP 3 On Next Post (below)
View user's profile View All Posts By User
AlwaysLosingPasswords
Member
***




Posts: 21
Registered: 11-25-2022
Member Is Offline


[*] posted on 11-30-2022 at 04:11 PM


Step 3: Our next step is update header.php.

Please open header.php:

Find

Code:
// check for new u2u's


Add the following code above

Code:
//more secure passwords //Determine whether client's password is hashed using MD5 if(X_MEMBER && !isset($_GET['runonce'])) { $query = $db->query("SELECT COUNT(*) FROM ".X_PREFIX."members WHERE username='$xmbuser' AND LENGTH(password) < '128'"); $result = $db->result($query, 0); $db->free_result($query); if($result > 0) { eval('$header = "'.template('header').'";'); eval('$css = "'.template('css').'";'); eval('$footer = "'.template('footer').'";'); echo "<style>".$css."</style>"; echo $header; ?> <div class="alert alert-warning alert-dismissible fade show" role="alert"> <strong>Password Update Required</strong> <a href="updatepw.php?runonce=true">Update Password</a> now. <button type="button" class="close" data-dismiss="alert" aria-label="Close"> <span aria-hidden="true">&times;</span> </button> </div> <?PHP echo $footer; exit; } }


Step 4: Next, open cp.php:

Find

Code:
$query = $db->query("SELECT uid, username, password, status FROM ".X_PREFIX."members $where");


Replace with

Code:
$query = $db->query("SELECT uid, username, password, status, regdate, regip FROM ".X_PREFIX."members $where");


Find

Code:
$newpw = md5($_POST['pw'.$mem['uid']]);


Replace that line with

Code:
$salt = $mem['regdate'] . $mem['regip']; $newpw = hash_pbkdf2("sha512",$_POST['pw'.$mem['uid']],$salt.PBKDF2PEPPER,PBKDF2ITERATIONS);


Step 5: Open editprofile.php

Find

Code:
$newpassword = md5($newpassword);


Replace with

Code:
$salt = $mem['regdate'] . $mem['regip']; $newpassword = hash_pbkdf2("sha512",$newpassword,$salt.PBKDF2PEPPER,PBKDF2ITERATIONS);


Step 6: Open member.php

Find

Code:
$self['password'] = md5( $self['password'] );


Replace with

Code:
$salt = $onlinetime . $onlineip; $self['password'] = hash_pbkdf2("sha512",$self['password'],$salt.PBKDF2PEPPER,PBKDF2ITERATIONS);


STEP 7 On Next Post (below)
View user's profile View All Posts By User
AlwaysLosingPasswords
Member
***




Posts: 21
Registered: 11-25-2022
Member Is Offline


[*] posted on 11-30-2022 at 04:21 PM


Step 7: Open memcp.php

Find

Code:
if ( $member['password'] != md5($_POST['oldpassword'])) { error($lang['textpwincorrect']); }


Replace the entire block with

Code:
$salt = $member['regdate'] . $member['regip']; $pwhash = hash_pbkdf2("sha512",$_POST['oldpassword'],$salt.PBKDF2PEPPER,PBKDF2ITERATIONS); if ( $member['password'] != $pwhash) { error($lang['textpwincorrect']); }


Find

Code:
$newpassword = md5($_POST['newpassword']);


Replace with

Code:
$newpassword = hash_pbkdf2("sha512",$_POST['newpassword'],$salt.PBKDF2PEPPER,PBKDF2ITERATIONS);


Step 8: Open sessions.inc.php (includes/session.inc.php)

Find

Code:
$pinput = md5( $pinput );


Replace with

Code:
$salt = $data->member['regdate'] . $data->member['regip']; $needschanged = md5($pinput); $pinput = hash_pbkdf2("sha512",$pinput,$salt.PBKDF2PEPPER,PBKDF2ITERATIONS);


Just below the $pinput/code above, you will see the following line:

Code:
if ( $data->member['password'] !== $pinput) {


Replace the entire line line with

Code:
if ( $data->member['password'] !== $pinput && $data->member['password'] != $needschanged) {


Step 9: Open lost.php

Find

Code:
$newpassword = md5( $password1 );


Replace with

Code:
$member = \XMB\SQL\getMemberByName( $username ); $salt = $member['regdate'] . $member['regip']; unset($member); $newpassword = hash_pbkdf2("sha512",$password1,$salt.PBKDF2PEPPER,PBKDF2ITERATIONS);


Step 10 (Configuration Section) Below
View user's profile View All Posts By User
AlwaysLosingPasswords
Member
***




Posts: 21
Registered: 11-25-2022
Member Is Offline


[*] posted on 11-30-2022 at 04:30 PM


Step 10: Open config.php

Find

Code:
// Do not edit below this line.


Add the following block above that line

Code:
//PBKDF2 Password Hash Settings /*Do not change these values after installing. Doing so will break your board. **High numbers provide improved security at the cost of performance during login.*/ define('PBKDF2ITERATIONS', 450000); define('PBKDF2PEPPER', 'uniquestring'); //OWASP recommends at least 310000 iterations //You should choose something other than the default value //The pepper prevents an attacker from obtaining key details via SQL injection
View user's profile View All Posts By User
AlwaysLosingPasswords
Member
***




Posts: 21
Registered: 11-25-2022
Member Is Offline


[*] posted on 12-1-2022 at 09:45 PM


The cryptographic scheme can be broken down into these components:

PBKDF2 - PBKDF2 is a password based key derivation function certified by NIST and approved for use in FIPS 140.2 validated applications. It is designed specifically to resist brute force attacks.

Your Password - The plaintext password passed into the function. Password length and randomness still matter.

SHA-512 - A 512-bit one way hashing algorithm.

$salt - The salt is a unique value derived per user, from information stored in the database. It consists of the registration timestamp and registration IP. May be obtained by an attacker via exploitation of SQLi/XSS vulnerabilities.

PBKDF2PEPPER - A unique site-wide value, stored in the configuration file. Set this to any value you would like, the longer and more random the better. May be obtained by an attacker via exploitation of Remote File Include and other system-level vectors. May not be obtained by an attacker via exploitation of SQLi/XSS vulnerabilities.

PBKDF2ITERATIONS - A unique integer, stored in the configuration file. May be obtained by an attacker via exploitation of RFI and other system-level vectors. May not be obtained by an attacker via exploitation of SQLi/XSS vulnerabilities. OWASP recommends a minimum of 310000 iterations. NIST's year 2000 guidance was 1000. The higher the better, performance impact is negligible except during login.

SHA512(Plaintext Password + Salt + Pepper) x Number of Iterations = SHA512 Password Hash

Password re-use is a huge problem in information security. By securely storing passwords, you make it computationally more expensive for an attacker to exhaustively search the keyspace. This protects your users within your community and even on their other information products.

New vulnerabilities are frequently discovered in web applications, web server software, server OSes and the underlaying hardware. This schema would require multiple serious vulnerabilities/failures to collect the pieces required to mount a feasible brute force attack. This where the strength of individual plaintext passwords becomes paramount.

Here are some Hashcat benchmarks:

Code:
[b]Hashmode: 0 - MD5[/b] Speed.#1.........: 65079.1 MH/s (41.37ms) @ Accel:32 Loops:1024 Thr:1024 Vec:1 [b]Hashmode: 12100 - PBKDF2-HMAC-SHA512 (Iterations: 999)[/b] Speed.#1.........: 1431.6 kH/s (38.64ms) @ Accel:2 Loops:499 Thr:1024 Vec:1


Device #1: NVIDIA GeForce RTX 3090, 24005/24259 MB, 82MCU

This GPU just devastates md5 with a whopping 65 billion hashes per second. Without salting, they could even be cracked in parallel without a real performance impact for the attacker.

Our above code stands up nicely, allowing the attacker to compute roughly 1.4 million hashes per second. This number decreases as you increase the iterations of the key derivation function. Because of the entropy salt/pepper, an attacker is unable to crack more than one hash at a time. Every time a hash is cracked, the whole process has to start all over again.

This GPU can crack md5 over 43000x faster, and in parallel.

Unfortunately although it hardens a server and provides excellent post-attack mitigation, it is likely to do little in actually preventing attacks. For instance, if the attacker's goal is simply to gain access and deface your community, they don't actually need to crack your password. If their intent is to dump your database and try email/user/password sets in various banking applications they will need the plaintext password which is where this really shines.
View user's profile View All Posts By User
Xian
Member
***




Posts: 50
Registered: 9-12-2017
Location: Los Angeles, California
Member Is Offline

Mood: w00h00!

[*] posted on 12-4-2022 at 12:10 AM


Nice work!
View user's profile View All Posts By User
AlwaysLosingPasswords
Member
***




Posts: 21
Registered: 11-25-2022
Member Is Offline


[*] posted on 1-4-2023 at 04:56 PM


Thank you Xian! 😁
View user's profile View All Posts By User
lottos
Administrator
********




Posts: 477
Registered: 6-3-2002
Member Is Offline

Mood: pass me a TimTam

[*] posted on 3-7-2023 at 11:40 PM


Thanks for sharing
View user's profile View All Posts By User

  Go To Top

Powered by XMB 1.9.12 (Debug Mode)
XMB Forum Software © 2001-2024 The XMB Group
[Queries: 16] [PHP: 23.9% - SQL: 76.1%]