Why is password hash not updating.... Mysqli Prepared Statement - php

I have a little bit of a problem with a password hash not being updated in a database and I'm struggling to see what I've done wrong. If anyone could help it would be much appreciated.
Basically I have a login.php script, which used to just employ md5 and salt to a user's password and store it in a mysql db, which I know is out of date. So I've recently tried to update it to update users passwords to PHP password_hash when they next log in.
I first collect the password via post then call the db to compare it to the hash in the db. If the password verifies I then run the db password through password_needs_rehash to check if it needs rehashing. If it does then it'll generate a new has and store this to a variable to be run through a mysqli prepared statement that updates the hash along with the time they've last logged in, otherwise it'll just update the db with the last login.
It seems to fail to update the new hash (even though I've checked it is generated) but still updates the time in the query. There are no emails coming through from the exception code or any php errors to report.
// Check if a newer hashing algorithm is available or if the cost has changed
if (password_needs_rehash($dbpass, PASSWORD_DEFAULT, $cost)) {
// If so, create a new hash, and replace the old one
$newHash = password_hash($password, PASSWORD_DEFAULT, $cost);
}
//update last login date
date_default_timezone_set("Europe/London");
$servertime = date("Y-m-d H:i:s");
// connect to db for mysqli
require('../dbconn/user.php');
// query to update password, increment number of logins and set last login date for user
$update_pass_stmt = $mysqli->stmt_init();
// if new hash has been generated
if (isset($newHash)) {
$q = "UPDATE users SET theirpassword=?, lastlogin=? WHERE email=? LIMIT 1";
if ($update_pass_stmt->prepare($q)) {
$update_pass_stmt->bind_param('sss', $newHash, $servertime, $email);
$update_pass_stmt->execute();
$update_pass_stmt->close();
}
} else {
$q = "UPDATE users SET lastlogin=? WHERE email=? LIMIT 1";
if ($update_pass_stmt->prepare($q)) {
$update_pass_stmt->bind_param('ss', $servertime, $email);
$update_pass_stmt->execute();
$update_pass_stmt->close();
}
}
if ($mysqli->error) {
try {
throw new Exception("MySQL error $mysqli->error <br> Query:<br> $q", $mysqli->errno);
} catch(Exception $e) {
$mess = "Error No: ".$e->getCode(). " - ". $e->getMessage() . "<br >";
$mess .= nl2br($e->getTraceAsString());
$contact_email = "webmaster#example.co.uk";
$message_sub = "Mysqli Login Query Error [EN-UUTIME01]";
$hdrs = "From: " . $contact_email . "\r\n";
$hdrs .= "Reply-To: ". $contact_email . "\r\n";
$hdrs .= "MIME-Version: 1.0\r\n";
$hdrs .= "Content-Type: text/html; charset=UTF-8\r\n";
mail($contact_email, $message_sub, $mess, $hdrs);
}
$mysqli->close();
$_SESSION['internalerror'] = TRUE;
header("Location: ". $MM_redirectLoginFailed );
exit();
}
$mysqli->close();
I've echoed out in each section of the code and it seems to be running through to the correct sql statement.
I can only think I've either missed an obvious typo or I've got my logic wrong somewhere.
Any help always appreciated, thank you.

Well as embarassing as it may be, I found the solution to my issue. The email variable I was passing into the query was empty. I'd named it differently earlier on in my code!
Although, why I didn't get a query error email or log anywhere I'm not sure still... see my comment with #RyanVincent above.
Thanks all for taking the time to look over the code!

Related

Bad Update Data in PHP PDO

Well, I'm working around with PHP with PDO extension as the database driver.
I'm working on a 'changing password' scenario where things is pretty messed up for me :(
We (some Stackoverflow helpers, Kudos!) have managed to verify the password with the current password, but it doesn't update the data, as I think.
Here is the encountering scenario:
You are changing your password from quora to stackoverflow. We know that if you change the password, you cannot use the old password, but in my situation, you can use the old password but not the new one, making the new one obselete.
Here is an snippet of my code:
$option = ['cost' => 12];
$password = password_hash($_currentpassword, PASSWORD_BCRYPT, $option);
$selectpasswordsql = "SELECT `password` FROM `auth` WHERE username=?";
$selectpasswordstmt = $conn->prepare($selectpasswordsql);
$selectpasswordstmt->execute(array($_SESSION['account']['username']));
$selectpasswordresults = $selectpasswordstmt->fetch(PDO::FETCH_ASSOC);
$databasepass = $selectpasswordresults['password'];
if(password_verify($_currentpassword,$databasepass)){
if(empty($passmsgs)){
$updatepasssql = "UPDATE `auth` SET
`password`=?
WHERE username=?
";
$updatepassstmt = $conn->prepare($updatepasssql);
$updatepassstmt->execute(array(password_hash($password, $_SESSION['account']['username']));
if($updatepassstmt){
array_push($passmsgs, 'Successfully updating your password!');
} else {
array_push($passmsgs, 'There was a problem executing your command!');
}
}
} else {
array_push($passmsgs, 'Your current password is wrong!');
}
First of all, please be sure that you can see errors by including following lines to top of your page.
error_reporting(E_ALL);
ini_set("display_errors", 1);
Your following code has both logic and syntax errors.
$updatepassstmt->execute(array(password_hash($password, $_SESSION['account']['username']));
if($updatepassstmt){
You try to re-hash $password value that is hashed of $_currentpassword. (please clearly define new password variable.)
you haven't closed password_hash() function properly. ($_SESSION['account']['username'] passed as a second parameter.)
you don't check execute() result which returns bool. you need to check it or you'll always get success message as $updatepassstmt will be PDOStatement object on successfull prepare() calls.
I suggest you to convert your code logic like this:
...
$updatepassstmt = $conn->prepare($updatepasssql);
$new_hashed_password = password_hash($_POST['new_password'], PASSWORD_BCRYPT, $option); // replace $_POST['new_password'] with the correct one.
$result = $updatepassstmt->execute($new_hashed_password, $_SESSION['account']['username']);
if ($result){
...etc...

PHP MySQL update statement fails

Yes, I know there are answers for PHP MySQL update statements, however none of them have yet to solve the issue. My reset PHP file prepares the query just fine using bind_param or concatenated variables. It also executes fine as it redirects to the reset_success page. The database record however, goes unchanged. I have confirmed that the user the website is using has update privileges. I have even tried escaping password as it is a lowercase version of a reserve word, nothing has worked. The apache2 error log also shows no errors, so no help there.
The code is as follows:
if (empty($error_msg)) {
// Create a random salt
$random_salt = hash('sha512', uniqid(openssl_random_pseudo_bytes(16), TRUE));
// Create salted password
$password = hash('sha512', $password . $random_salt);
// Prepare the update statement
//if ($update_stmt = $mysqli->prepare("UPDATE members SET password = ?, salt = ? WHERE email = ?")) {
if ($update_stmt = $mysqli->prepare("UPDATE members SET password = '".$password."', salt = '".$random_salt."' WHERE email = '".$_SESSION['email']."';")) {
// Binding params
//$update_stmt->bind_param('sss', $password, $random_salt, $_SESSION['email']);
// Execute the update statement
if ($update_stmt->execute()) {
header('Location: reset_success.php');
exit();
}
else{
header('Location: error.php?err=Reset failure: UPDATE');
exit();
}
}
else{
header('Location: error.php?err=Reset failure: PREPARE');
exit();
}
}
Any insight would be greatly appreciated!
Ok, it is working. I needed an additional call to session_start() in the function file even though the reset_password.php does not redirect ($_SERVER['PHP_SELF']). This was NOT needed in for the registration page and its corresponding function file which operates in the same manner. I'm still unclear as to why that is needed again when the referrer is itself and is not redirecting.
This should be a comment, but its a bit long.
$random_salt = hash('sha512', uniqid(openssl_random_pseudo_bytes(16), TRUE));
This very wrong. You are padding the salt from 16 to 64 bits and removing the entropy by hashing it. If you want 64 random bytes then just use openssl_random_pseudo_bytes(64).
The apache2 error log also shows no errors
Have you checked that PHP error logging is working?
Why didn't you check the mysql logs?
if ($update_stmt->execute()) {
Try checking $update_stmt->affected_rows()

PHP - Secure Change Password Form/Method

I've made my own login system based on a Wikihow article and I want to make a change password field, but I don't know how I should be doing it. Here is what I have now:
JavaScript
function formchange(form, current, newpass) {
// Create a new element input, this will be our hashed password field.
var p = document.createElement("input");
var p2 = document.createElement("input");
// Add the new element to our form.
form.appendChild(p);
p.name = "p";
p.type = "hidden";
p.value = hex_sha512(current.value);
form.appendChild(p2);
p.name = "p2";
p.type = "hidden";
p.value = hex_sha512(newpass.value);
// Make sure the plaintext password doesn't get sent.
current.value = "";
newpass.value = "";
// Finally submit the form.
form.submit();
}
PHP
if (isset($_POST['cpass'], $_POST['npass'])) {
$cpass = $_POST['cpass'];
$npass = $_POST['npass']; // The hashed password.
echo "Here";
$un = getUser();
if ($cpass == $npass) {
echo "If";
$random_salt = hash('sha512', uniqid(mt_rand(1, mt_getrandmax()), true));
$password = hash('sha512', $npass . $random_salt);
// Insert the new user into the database
if ($insert_stmt = $mysqli->prepare("MYSQL UPDATE STATEMENT")) {
$insert_stmt->bind_param('p', $password);
$insert_stmt->bind_param('s', $random_salt);
echo "Binded";
// Execute the prepared query.
if (! $insert_stmt->execute()) {
header('Location: ../home.php?msg=1');
}
}
header('Location: ../home.php?msg=2');
} else {
// Failed
header('Location: ../?error=1');
}
} else {
// The correct POST variables were not sent to this page.
echo 'Invalid Request';
}
It doesn't seem to work, it keeps going to home.php?msg=2, but the database isn't updated. I'm new to all of this, and it's one of my projects to learn it so apologies for terrible code. My MySQL statement is this:
UPDATE user_data SET user_data.password = 'p', user_data.salt = 's' WHERE username = '$un';
Your script is set up to forward to home.php?msg=2 for many conditions, including when the query succeeds.
if (! $insert_stmt->execute()) {
header('Location: ../home.php?msg=1');
}
This will send it to home.php?msg=1 if the query fails to execute, note the !. Since the query doesn't fail, the script continues and the next function is...
header('Location: ../home.php?msg=2');
"It doesnt seem to work, it keeps going to home.php?msg=2. "
So what do you expect the script to do? How you've designed it, it is supposed to do this.
Update on new edit
You should avoid using header location like this, otherwise you'll never be able to find your errors. You should avoid using header location at all during development, especially when a query fails.
I would enclose header location more and grab the error to see why the statement failed. I don't ever use mysqli because pdo, in my opinion, is better but I think this should work.
if ($insert_stmt = $mysqli->prepare("MYSQL UPDATE STATEMENT")) {
$insert_stmt->bind_param('p', $password);
$insert_stmt->bind_param('s', $random_salt);
echo "Binded";
// Execute the prepared query.
if (! $insert_stmt->execute()) {
die($mysqli->error);
// header('Location: ../home.php?msg=1');
}
else {
// Success, forward
header('Location: ../home.php?msg=2');
}
}
else {
die($mysqli->error);
}
Removing the header('Location: ../home.php?msg=2'); at the bottom of this section in your original script.
What you are trying to do is fundamentally flawed. An attacker could still perform a replay attack with the hash values created by your JavaScript function. Also you are only protecting the un-hashed password (but I can still look it up in a rainbow table) not the account. An attacker can use the hash values to authenticate as the user. This is because the hash values are the password.
To actually make a secure form you will need to use HTTPS and a Cross site request forgery token of some kind.
Here is an example say my name is Alice, and I am watching Bob's network traffic.
I (as Alice) see Bob submit your form, and then I see your web server respond back to Bob saying "thanks, password updated"
I (as Alice) now know Bob's password, even worse I can now send the same HTTP request Bob just sent you but now since I know Bob's password I can change the request to update the password again, to anything I want.
Your form is no more secure than sending the password un-hashed.

PHP Activating account with UPDATE SQL

OK I have this code to send an email account verification link
$verifyemail = $clean['email'];
$to = $verifyemail;
$subject = 'Virtual Pierz Close | Verify Your Account';
$message = "Thanks for registering with VPC, on clicking the verification link below, your account will be confirmed, you can then go ahead buy Virtual Properties, donating £5 each time to the worthwhile charity.
http://www.cambrianvacation.co.uk/vpc/registered.php?
email='$verifyemail'&hash='$hash1' ";
$headers = 'From:noreply#cambrianvacation.co.uk'; // Set from headers
mail($to, $subject, $message, $headers);
And then I have this code, that is trying to activate the account by setting active = 1 in the database, which will then be part of the access control logic at login, without active = 1, there is no login, amongst other protection
if(isset($_GET['email']) && !empty($_GET['email']) AND isset($_GET['hash']) && !empty($_GET['hash'])){
// Verify data
$accountemail = $_GET['email'];
$accounthash = $_GET['hash'];
}
$accountActive = 1;
$notactive = 0;
$username = '';
$password2 = '';
$username = 'xxxxxxx';
$password2 = 'xxxxxxx';
$db1 = new PDO('mysql:host=localhost;dbname=xxxxxxxxxxxxx', $username, $password2, array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"));
$db1->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$db1->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
try{
$search = $db1->prepare("SELECT email, hash, active FROM users WHERE email = :email AND hash= :hash AND active = :active");
$search->bindParam(':email', $accountemail);
$search->bindParam(':hash', $accounthash);
$search->bindParam(':active', $notactive);
$search->execute();
$colcount = $search->columnCount();
}catch(PDOException $e) {
$e->getMessage();
}
print_r($colcount);
if($colcount === 3){
//try{
$update = $db1->prepare("UPDATE users SET active=:active WHERE email=:email AND hash=:hash AND active = :active");
$update->bindParam(':active', $accountActive);
$update->bindParam(':email', $accountemail);
$update->bindParam(':hash', $accounthash);
$update->bindParam(':active', $notactive);
$update->execute();
//}catch(PDOException $e) {
// $e->getMessage();
//}
However I cannot get the active column to update.
I've also thought about using the GET['email'] could be subject to semantic url attacks, however the logic won't activate the account without the matching hash, which is randomly generated with crypt().........
If anyone can see any security holes in the code, please tell me.........
There really is no reason to make two separate queries here. Why not just have one query to update the record based on hash and email and active = 0? If the count of modified rows = 1, then you had a success, else you had a failure. You probably don't care why it failed, as it would be bad from a security perspective to indicate back to the user why update failed (i.e. bad email, bad hash, already active user, etc.).
That being said, your problem actually lies in the fact that your update uses ? style bindings, while you are using bindParam() with :param style bindings. This won't work since those values are not present in the prepared statement.
So just use this one single query:
UPDATE users SET active = 1 WHERE email = :email AND hash = :hash AND active = 0
Obviously if you think you are going to change the value for active/non-active then feel free to use a parameter for those as well, but my guess is you would want to treat that as a boolean-style tinyint field with only allowable values of 0 and 1, so there is really no point in having the parametrization there.
What you could do, is not include "email" at all.
You could try to gen the url by doing this:
$secret = "1032940fdjsjdkf##$!##%djsfisd";
$hash = md5($email.$secret);
$url = "http://www.cambrianvacation.co.uk/vpc/registered.php?hash=".$hash;
In the update query, you are using "?" for parameters, but then you try to set them as named with bindParam(). You should use
$update->execute(array($accountActive, $accountemail, $accounthash, $notactive));
Or modify the update query this way:
$update = $db1->prepare("UPDATE users SET active=:active WHERE email=:email AND hash=:hash");
Your new parameters are not bound correctly, change:
$update = $db1->prepare("UPDATE users SET active=? WHERE email=? AND hash=? AND active = ?");
To:
$update = $db1->prepare("UPDATE users SET active=:active WHERE email=:email AND hash=:hash");
EDIT - Full Update Code:
$update = $db1->prepare("UPDATE users SET active=:active WHERE email=:email AND hash=:hash");
$update->bindParam(':active', $accountActive);
$update->bindParam(':email', $accountemail);
$update->bindParam(':hash', $accounthash);
$update->execute();

mysqli - warning and error

i have this code but i got two errors. I put in the comments the errors
if(!empty($_POST['email']) && validateEmail($email)) {
$email = $_POST["email"];
if ($sql = $db->prepare("select email from users where email=?")) {
$sql->bind_param('s', $email);
$sql->execute();
$sql->bind_result($email);
while ($sql->fetch()) {
$salt = "PiuwrO1#O0rl#+luH1!froe*l?8oEb!iu)_1Xaspi*(sw(^&.laBr~u3i!c?es-l651";
$password = md5($salt . $userExists["email"]);
$pwrurl = "www.yoursite.com/reset_password.php?q=" . $password;
$mailbody = "Dear user,<br><br>If this e-mail does not apply to you please ignore it. It appears that you have requested a password reset at our website www.yoursitehere.com<br>
To reset your password, please click the link below. If you cannot click it, please paste it into your web browser's address bar.<br> <a href='$pwrurl'>$pwrurl</a> <br> <br>
Thanks,\nThe Administration";
$mail->MsgHTML($mailbody);
$mail->AddAddress("dxxb#hotmail.com","Nome da Pessoa");
$mail->IsHTML(true);
if(!$mail->Send()) {
echo "Deu erro: " . $mail->ErrorInfo;
} else {
echo "Enviado com sucesso";
}
}
$sql->close();
$db->close();
}
($sql = $db->prepare('insert into password_reset (code) values (?)')); // Warning: mysqli::prepare() [mysqli.prepare]: Couldn't fetch mysqli in
$sql->bind_param('s', $password); // Fatal error: Call to a member function bind_param() on a non-object
$sql->execute();
$sql->fetch();
$sql->close();
$db->close();
}
all code works fine, but now i need to insert the salt in the db but i can't, and i don't know why
thanks
Edited code to the last version
After you execute a query, fetch returns one result. There may be more -- there may be many, many more -- so you should be calling fetch in a loop to get them all. You aren't supposed to prepare a new query until you've finished dealing with the old one, which would usually mean fetching every row of the result and closeing (in your case) $sql. Otherwise, the database is still in the middle of answering one request when you're trying to issue another one.
The first error says it all - you can't have more than 1 prepared statement/query "in flight" at once. You've not finished fetching data from the first query (select email ...) when you tried to prepare another statement (insert into ...).

Categories