Skip to content
This repository has been archived by the owner on Nov 25, 2020. It is now read-only.

Commit

Permalink
Security enforcements:
Browse files Browse the repository at this point in the history
> Switch password hashing from md5 to more secure hashing (backward compatible).
> Do not use the server time() as the base for the tokens (secure token & remember me cookie token) as it's too predictible
> Make sure the remember me cookie has httpOnly and Secure flags.
  • Loading branch information
cdujeu committed Aug 21, 2013
1 parent 33f0e93 commit 5239828
Show file tree
Hide file tree
Showing 8 changed files with 187 additions and 20 deletions.
18 changes: 18 additions & 0 deletions core/src/conf/bootstrap_context.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,24 @@
define("AJXP_SERVER_DEBUG" , false);
define("AJXP_SKIP_CACHE" , false);


// PBKDF2 CONSTANTS FOR A SECURE STORAGE OF PASSWORDS
// These constants may be changed without breaking existing hashes.
define("PBKDF2_HASH_ALGORITHM", "sha256");
define("PBKDF2_ITERATIONS", 1000);
define("PBKDF2_SALT_BYTE_SIZE", 24);
define("PBKDF2_HASH_BYTE_SIZE", 24);

define("HASH_SECTIONS", 4);
define("HASH_ALGORITHM_INDEX", 0);
define("HASH_ITERATION_INDEX", 1);
define("HASH_SALT_INDEX", 2);
define("HASH_PBKDF2_INDEX", 3);

// CAN BE SWITCHED TO TRUE TO MAKE THE SECURE TOKEN MORE SAFE
// MAKE SURE YOU HAVE PHP.5.3, OPENSSL, AND THAT IT DOES NOT DEGRADE PERFORMANCES
define("USE_OPENSSL_RANDOM", false);

require(AJXP_BIN_FOLDER."/compat.php");

function AjaXplorer_autoload($className){
Expand Down
148 changes: 148 additions & 0 deletions core/src/core/classes/class.AJXP_Utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,27 @@
define('AJXP_SANITIZE_HTML_STRICT', 2);
define('AJXP_SANITIZE_ALPHANUM', 3);
define('AJXP_SANITIZE_EMAILCHARS', 4);

// THESE ARE DEFINED IN bootstrap_context.php
// REPEAT HERE FOR BACKWARD COMPATIBILITY.
if(!defined('PBKDF2_HASH_ALGORITHM')){

define("PBKDF2_HASH_ALGORITHM", "sha256");
define("PBKDF2_ITERATIONS", 1000);
define("PBKDF2_SALT_BYTE_SIZE", 24);
define("PBKDF2_HASH_BYTE_SIZE", 24);

define("HASH_SECTIONS", 4);
define("HASH_ALGORITHM_INDEX", 0);
define("HASH_ITERATION_INDEX", 1);
define("HASH_SALT_INDEX", 2);
define("HASH_PBKDF2_INDEX", 3);

define("USE_OPENSSL_RANDOM", false);

}


/**
* Various functions used everywhere, static library
* @package AjaXplorer
Expand Down Expand Up @@ -1603,4 +1624,131 @@ public static function runCreateTablesQuery($p, $file){

}


/*
* PBKDF2 key derivation function as defined by RSA's PKCS #5: https://www.ietf.org/rfc/rfc2898.txt
* $algorithm - The hash algorithm to use. Recommended: SHA256
* $password - The password.
* $salt - A salt that is unique to the password.
* $count - Iteration count. Higher is better, but slower. Recommended: At least 1000.
* $key_length - The length of the derived key in bytes.
* $raw_output - If true, the key is returned in raw binary format. Hex encoded otherwise.
* Returns: A $key_length-byte key derived from the password and salt.
*
* Test vectors can be found here: https://www.ietf.org/rfc/rfc6070.txt
*
* This implementation of PBKDF2 was originally created by https://defuse.ca
* With improvements by http://www.variations-of-shadow.com
*/
static function pbkdf2_apply($algorithm, $password, $salt, $count, $key_length, $raw_output = false) {

$algorithm = strtolower($algorithm);

if(!in_array($algorithm, hash_algos(), true))
die('PBKDF2 ERROR: Invalid hash algorithm.');
if($count <= 0 || $key_length <= 0)
die('PBKDF2 ERROR: Invalid parameters.');

$hash_length = strlen(hash($algorithm, "", true));
$block_count = ceil($key_length / $hash_length);

$output = "";

for($i = 1; $i <= $block_count; $i++) {
// $i encoded as 4 bytes, big endian.
$last = $salt . pack("N", $i);
// first iteration
$last = $xorsum = hash_hmac($algorithm, $last, $password, true);

// perform the other $count - 1 iterations
for ($j = 1; $j < $count; $j++) {
$xorsum ^= ($last = hash_hmac($algorithm, $last, $password, true));
}

$output .= $xorsum;
}

if($raw_output)
return substr($output, 0, $key_length);
else
return bin2hex(substr($output, 0, $key_length));
}


// Compares two strings $a and $b in length-constant time.
static function pbkdf2_slow_equals($a, $b) {
$diff = strlen($a) ^ strlen($b);
for($i = 0; $i < strlen($a) && $i < strlen($b); $i++)
{
$diff |= ord($a[$i]) ^ ord($b[$i]);
}

return $diff === 0;
}

static function pbkdf2_validate_password($password, $correct_hash) {
$params = explode(":", $correct_hash);

if(count($params) < HASH_SECTIONS){
if(strlen($correct_hash) == 32 && count($params) == 1){
return md5($password) == $correct_hash;
}
return false;
}

$pbkdf2 = base64_decode($params[HASH_PBKDF2_INDEX]);
return self::pbkdf2_slow_equals(
$pbkdf2,
self::pbkdf2_apply(
$params[HASH_ALGORITHM_INDEX],
$password,
$params[HASH_SALT_INDEX],
(int)$params[HASH_ITERATION_INDEX],
strlen($pbkdf2),
true
)
);
}


static function pbkdf2_create_hash($password) {
// format: algorithm:iterations:salt:hash
$salt = base64_encode(mcrypt_create_iv(PBKDF2_SALT_BYTE_SIZE, MCRYPT_DEV_URANDOM));
return PBKDF2_HASH_ALGORITHM . ":" . PBKDF2_ITERATIONS . ":" . $salt . ":" .
base64_encode(self::pbkdf2_apply(
PBKDF2_HASH_ALGORITHM,
$password,
$salt,
PBKDF2_ITERATIONS,
PBKDF2_HASH_BYTE_SIZE,
true
));
}

/**
* generates a random password, uses base64: 0-9a-zA-Z
* @param int [optional] $length length of password, default 24 (144 Bit)
* @return string password
*/
static function generateRandomString($length = 24) {
if(function_exists('openssl_random_pseudo_bytes') && USE_OPENSSL_RANDOM) {
$password = base64_encode(openssl_random_pseudo_bytes($length, $strong));
if($strong == TRUE)
return substr(str_replace(array("/","+"), "", $password), 0, $length); //base64 is about 33% longer, so we need to truncate the result
}

//fallback to mt_rand if php < 5.3 or no openssl available
$characters = '0123456789';
$characters .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
$charactersLength = strlen($characters)-1;
$password = '';

//select some random characters
for ($i = 0; $i < $length; $i++) {
$password .= $characters[mt_rand(0, $charactersLength)];
}

return $password;
}

}
7 changes: 4 additions & 3 deletions core/src/core/classes/class.AuthService.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ static function generateSeed(){
* @return
*/
static function generateSecureToken(){
$_SESSION["SECURE_TOKEN"] = md5(time());

$_SESSION["SECURE_TOKEN"] = AJXP_Utils::generateRandomString(32); //md5(time());
return $_SESSION["SECURE_TOKEN"];
}
/**
Expand Down Expand Up @@ -212,7 +213,7 @@ static function refreshRememberCookie($user){
$user->invalidateCookieString(substr($current, strpos($current, ":")+1));
}
$rememberPass = $user->getCookieString();
setcookie("AjaXplorer-remember", $user->id.":".$rememberPass, time()+3600*24*10);
setcookie("AjaXplorer-remember", $user->id.":".$rememberPass, time()+3600*24*10, null, null, (isSet($_SERVER["HTTPS"]) && strtolower($_SERVER["HTTPS"]) == "on"), true);
}

/**
Expand All @@ -233,7 +234,7 @@ static function clearRememberCookie(){
if(!empty($current) && $user != null){
$user->invalidateCookieString(substr($current, strpos($current, ":")+1));
}
setcookie("AjaXplorer-remember", "", time()-3600);
setcookie("AjaXplorer-remember", "", time()-3600, null, null, (isSet($_SERVER["HTTPS"]) && strtolower($_SERVER["HTTPS"]) == "on"), true);
}

static function logTemporaryUser($parentUserId, $temporaryUserId){
Expand Down
8 changes: 4 additions & 4 deletions core/src/plugins/auth.remote/class.remoteAuthDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ function checkPassword($login, $pass, $seed){
$userStoredPass = $this->getUserPass($login);
if(!$userStoredPass) return false;
if($seed == "-1"){ // Seed = -1 means that password is not encoded.
return ($userStoredPass == md5($pass));
return AJXP_Utils::pbkdf2_validate_password($pass, $userStoredPass);// ($userStoredPass == md5($pass));
}else{
return (md5($userStoredPass.$seed) == $pass);
}
Expand Down Expand Up @@ -164,7 +164,7 @@ function checkPassword($login, $pass, $seed){
$userStoredPass = $this->getUserPass($login);
if(!$userStoredPass) return false;
if($seed == "-1"){ // Seed = -1 means that password is not encoded.
$res = ($userStoredPass == md5($pass));
$res = AJXP_Utils::pbkdf2_validate_password($pass, $userStoredPass); //($userStoredPass == md5($pass));
}else{
$res = (md5($userStoredPass.$seed) == $pass);
}
Expand Down Expand Up @@ -195,7 +195,7 @@ function createUser($login, $passwd){
if(!is_array($users)) $users = array();
if(array_key_exists($login, $users)) return "exists";
if($this->getOption("TRANSMIT_CLEAR_PASS") === true){
$users[$login] = md5($passwd);
$users[$login] = AJXP_Utils::pbkdf2_create_hash($passwd);
}else{
$users[$login] = $passwd;
}
Expand All @@ -206,7 +206,7 @@ function changePassword($login, $newPass){
$users = $this->listUsers();
if(!is_array($users) || !array_key_exists($login, $users)) return ;
if($this->getOption("TRANSMIT_CLEAR_PASS") === true){
$users[$login] = md5($newPass);
$users[$login] = AJXP_Utils::pbkdf2_create_hash($newPass);
}else{
$users[$login] = $newPass;
}
Expand Down
6 changes: 3 additions & 3 deletions core/src/plugins/auth.serial/class.serialAuthDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ function checkPassword($login, $pass, $seed){
$userStoredPass = $this->getUserPass($login);
if(!$userStoredPass) return false;
if($seed == "-1"){ // Seed = -1 means that password is not encoded.
return ($userStoredPass == md5($pass));
return AJXP_Utils::pbkdf2_validate_password($pass, $userStoredPass);//($userStoredPass == md5($pass));
}else{
return (md5($userStoredPass.$seed) == $pass);
}
Expand All @@ -125,7 +125,7 @@ function createUser($login, $passwd){
if(!is_array($users)) $users = array();
if(array_key_exists($login, $users)) return "exists";
if($this->getOption("TRANSMIT_CLEAR_PASS") === true){
$users[$login] = md5($passwd);
$users[$login] = AJXP_Utils::pbkdf2_create_hash($passwd);//md5($passwd);
}else{
$users[$login] = $passwd;
}
Expand All @@ -136,7 +136,7 @@ function changePassword($login, $newPass){
$users = $this->_listAllUsers();
if(!is_array($users) || !array_key_exists($login, $users)) return ;
if($this->getOption("TRANSMIT_CLEAR_PASS") === true){
$users[$login] = md5($newPass);
$users[$login] = AJXP_Utils::pbkdf2_create_hash($newPass);//md5($newPass);
}else{
$users[$login] = $newPass;
}
Expand Down
10 changes: 5 additions & 5 deletions core/src/plugins/auth.serial_otp/class.serial_otpAuthDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ function checkPassword($login, $pass, $seed){

//No OTP token is set
if ($g == '' and $y1 == '' and $y2 == ''){
return ($userStoredPass == md5($pass));
return AJXP_Utils::pbkdf2_validate_password($pass, $userStoredPass); //($userStoredPass == md5($pass));
}

//Just the Google Authenticator set
Expand Down Expand Up @@ -171,7 +171,7 @@ function createUser($login, $passwd){
if(!is_array($users)) $users = array();
if(array_key_exists($login, $users)) return "exists";
if($this->getOption("TRANSMIT_CLEAR_PASS") === true){
$users[$login] = md5($passwd);
$users[$login] = AJXP_Utils::pbkdf2_create_hash($passwd);
}else{
$users[$login] = $passwd;
}
Expand All @@ -183,7 +183,7 @@ function changePassword($login, $newPass){
$users = $this->_listAllUsers();
if(!is_array($users) || !array_key_exists($login, $users)) return ;
if($this->getOption("TRANSMIT_CLEAR_PASS") === true){
$users[$login] = md5($newPass);
$users[$login] = AJXP_Utils::pbkdf2_create_hash($newPass);
}else{
$users[$login] = $newPass;
}
Expand Down Expand Up @@ -314,7 +314,7 @@ function checkGooglePass($login, $pass, $userStoredPass, $userToken, $userInvali
}
}

return ($userStoredPass == md5($pass) && $valid == 1);
return ( AJXP_Utils::pbkdf2_validate_password($pass, $userStoredPass) && $valid == 1);
}


Expand All @@ -336,7 +336,7 @@ function checkYubiPass($pass, $userStoredPass, $yubikey1, $yubikey2) {
$yubi = new Auth_Yubico($this->yubico_client_id, $this->yubico_secret_key);
$auth = $yubi->verify($yotp);

return ((!PEAR::isError($auth)) && $userStoredPass == md5($pass));
return ((!PEAR::isError($auth)) && AJXP_Utils::pbkdf2_validate_password($pass, $userStoredPass));
}
}
?>
6 changes: 3 additions & 3 deletions core/src/plugins/auth.sql/class.sqlAuthDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ function checkPassword($login, $pass, $seed){
if(!$userStoredPass) return false;

if($this->getOption("TRANSMIT_CLEAR_PASS") === true){ // Seed = -1 means that password is not encoded.
return ($userStoredPass == md5($pass));
return AJXP_Utils::pbkdf2_validate_password($pass, $userStoredPass); //($userStoredPass == md5($pass));
}else{
return (md5($userStoredPass.$seed) == $pass);
}
Expand All @@ -116,7 +116,7 @@ function createUser($login, $passwd){
if($this->userExists($login)) return "exists";
$userData = array("login" => $login);
if($this->getOption("TRANSMIT_CLEAR_PASS") === true){
$userData["password"] = md5($passwd);
$userData["password"] = AJXP_Utils::pbkdf2_create_hash($passwd); //md5($passwd);
}else{
$userData["password"] = $passwd;
}
Expand All @@ -127,7 +127,7 @@ function changePassword($login, $newPass){
if(!$this->userExists($login)) throw new Exception("User does not exists!");
$userData = array("login" => $login);
if($this->getOption("TRANSMIT_CLEAR_PASS") === true){
$userData["password"] = md5($newPass);
$userData["password"] = AJXP_Utils::pbkdf2_create_hash($newPass); //md5($newPass);
}else{
$userData["password"] = $newPass;
}
Expand Down
4 changes: 2 additions & 2 deletions core/src/plugins/core.conf/class.AbstractAjxpUser.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,11 @@ function getCookieString(){
}else{
$hashes = explode(",", $hashes);
}
$newHash = md5($this->id.":".time());
$newHash = md5($this->id.":".AJXP_Utils::generateRandomString());
array_push($hashes, $newHash);
$this->setPref("cookie_hash", implode(",",$hashes));
$this->save("user");
return $newHash; //md5($this->id.":".$newHash.":ajxp");
return $newHash;
}

function getId(){
Expand Down

0 comments on commit 5239828

Please sign in to comment.