I am setting up a PHP lost password page for my website (www.qbstaxsubmission.co.uk) and the code for creating a lost password email which is sent to a user is working just fine. However when the user clicks on the email link he arrives at a new password php page. It's the script on this page which produces a error message 'Registration failure in updating recovery key: INSERT' which fires up my styled error page to transfer the user back to my standard login page.
So my problem is I cannot see what's wrong with my new password2.php. Can anyone help with this?
Here's the full new password2.php code:
<?php
ob_start();
include ('config.php');
include ('function.php');
$error_msg = "";
$token = $_GET['token'];
$userID = UserID($email);
$verifytoken = verifytoken($userID, $token);
// Sanitize and validate the data passed in
if (isset($_POST['submit'],$_POST['username'], $_POST['email'], $_POST['p'])) {
$email = filter_input(INPUT_POST, 'email', FILTER_SANITIZE_STRING);
$username = filter_input(INPUT_POST, 'username', FILTER_SANITIZE_STRING);
$new_password = filter_input(INPUT_POST, 'new_password', FILTER_SANITIZE_STRING);
$retype_password = filter_input(INPUT_POST, 'retype_password', FILTER_SANITIZE_STRING);
$id = filter_input(INPUT_POST, 'id', FILTER_SANITIZE_STRING);}
$new_password = filter_input(INPUT_POST, 'p', FILTER_SANITIZE_STRING);
if (strlen($new_password) != 128) {
// The hashed pwd should be 128 characters long.
// If it's not, something really odd has happened
$error_msg .= '<p class="error">Invalid password configuration.</p>';
}
$prep_stmt = "SELECT id FROM members WHERE email = ? LIMIT 1";
$stmt = $db ->prepare($prep_stmt);
if ($stmt) {
$stmt->bind_param('s', $email);
$stmt->execute();
$stmt->store_result();
if ($stmt->num_rows == 1) {
// A user with this email address already exists
$error_msg .= '<p class="error">A user with this email address already exists.</p>';
}
} else {
$error_msg .= '<p class="error">Database error</p>';
}
if($new_password != $retype_password) {
// Create a random salt
$salt = hash('sha512', uniqid(openssl_random_pseudo_bytes(16), TRUE));
// Create salted password
$new_password = hash('sha512', $random_salt . $salt);
}
// Insert the new hashed password into the database
if ($insert_stmt = $db->prepare("UPDATE members SET password = ? WHERE id = ? ")) {
$insert_stmt->bind_param('si', $newpassword, $id);
// Execute the prepared query.
if (!$insert_stmt->execute()) {
header('Location: ../error.php?err=Database Registration failure: INSERT');
}
// Update recovery key
if ($insert_stmt = $db->prepare("UPDATE recovery_keys SET valid = 0 WHERE id = ? AND token = ? "));
$insert_stmt->bind_param('is', $id, $token);
// Execute the prepared query.
if ($insert_stmt->execute())
$msg = 'Your password has changed successfully. Please login with your new password.';
}else
{
header('Location: ../error.php?err=Registration failure in updating recovery key: INSERT'); }
{exit();}
?>
When the code above is run I get a blank page with the the correct token code shown in the site link.
This password2.php page has an include to a functions page which is shown below.
function checkUser($email)
{
global $db;
$query = mysqli_query($db, "SELECT id FROM members WHERE email = '$email'");
if(mysqli_num_rows($query) > 0)
{
return 'true';
}else
{
return 'false'; }
}
function id($email)
{
global $db;
$query = mysqli_query($db, "SELECT id FROM members WHERE email = '$email'");
$row = mysqli_fetch_assoc($query);
return $row['id'];
}
function generateRandomString($length = 25) {
// This function has taken from stackoverflow.com
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$charactersLength = strlen($characters);
$randomString = '';
for ($i = 0; $i < $length; $i++) {
$randomString .= $characters[rand(0, $charactersLength - 1)];
}
return md5($randomString);
}
function send_mail($to, $token)
{
require 'PHPMailer/PHPMailerAutoload.php';
$mail = new PHPMailer;
//$mail->SMTPDebug = 3;
$mail->isSMTP();
$mail->Host = '';
$mail->SMTPAuth = true;
$mail->Username = '';
$mail->Password = '';
$mail->SMTPSecure = 'ssl';
$mail->Port = 465;
$mail->SetFrom = '';
$mail->FromName = '';
$mail->addAddress($to);
$mail->addReplyTo('', 'Reply');
$mail->isHTML(true);
$mail->Subject = 'Company Password Recovery Instruction';
$link = 'x.php?email='.$to.'&token='.$token;
$mail->Body = "<b>Hi</b><br><br>You have just requested a new password for your company account with QBS Tax Submission. <a href='$link' target='_blank'>Click here</a> to reset your password. If you are unable to click the link then copy the hyper link below and paste into your browser to reset your password.<br><i>". $link."</i>";
$mail->AltBody = 'This is the body in plain text for non-HTML mail clients';
if(!$mail->send()) {
return 'fail';
} else {
return 'success';
}
}
function verifytoken($id, $token)
{
global $db;
$query = mysqli_query($db, "SELECT valid FROM recovery_keys WHERE id = $id AND token = '$token'");
$row = mysqli_fetch_assoc($query);
if(mysqli_num_rows($query) > 0)
{
if($row['valid'] == 1)
{
return 1;
}else
{
return 0;
}
}else
{
return 0;
}
}
function login($email, $password, $mysqli) {
// Using prepared statements means that SQL injection is not possible.
if ($stmt = $mysqli->prepare("SELECT id, username, email, password, salt
FROM members
WHERE email = ? LIMIT 1")) {
$stmt->bind_param('s', $email); // Bind "$email" to parameter.
$stmt->execute(); // Execute the prepared query.
$stmt->store_result();
// get variables from result.
$stmt->bind_result($id, $username, $db_password, $salt);
$stmt->fetch();
// hash the password with the unique salt.
$password = hash('sha512', $password . $salt);
if ($stmt->num_rows == 1) {
// If the user exists we check if the account is locked
// from too many login attempts
if (checkbrute($id, $mysqli) == true) {
// Account is locked
// Send an email to user saying their account is locked
return false;
} else {
// Check if the password in the database matches
// the password the user submitted.
if ($db_password == $password) {
// Password is correct!
// Get the user-agent string of the user.
$user_browser = $_SERVER['HTTP_USER_AGENT'];
// XSS protection as we might print this value
$id = preg_replace("/[^0-9]+/", "", $id);
$_SESSION['id'] = $id;
// XSS protection as we might print this value
$username = preg_replace("/[^a-zA-Z0-9_\-]+/", "", $username);
$_SESSION['username'] = $username;
$_SESSION['login_string'] = hash('sha512', $password . $user_browser);
// Login successful.
return true;
} else {
// Password is not correct
// We record this attempt in the database
$now = time();
if (!$mysqli->query("INSERT INTO login_attempts(id, time)
VALUES ('$id', '$now')")) {
header("Location: ../error.php?err=Database error: login_attempts");
exit();
}
return false;
}
}
} else {
// No user exists.
return false;
}
} else {
// Could not create a prepared statement
header("Location: ../error.php?err=Database error: cannot prepare statement");
exit();
}
}
function checkbrute($id, $mysqli) {
// Get timestamp of current time
$now = time();
// All login attempts are counted from the past 2 hours.
$valid_attempts = $now - (2 * 60 * 60);
if ($stmt = $mysqli->prepare("SELECT time
FROM login_attempts
WHERE id = ? AND time > '$valid_attempts'")) {
$stmt->bind_param('i', $id);
// Execute the prepared query.
$stmt->execute();
$stmt->store_result();
// If there have been more than 5 failed logins
if ($stmt->num_rows > 5) {
return true;
} else {
return false;
}
} else {
// Could not create a prepared statement
header("Location: ../error.php?err=Database error: cannot prepare statement");
exit();
}
}
function login_check($mysqli) {
// Check if all session variables are set
if (isset($_SESSION['id'], $_SESSION['username'], $_SESSION['login_string'])) {
$id = $_SESSION['id'];
$login_string = $_SESSION['login_string'];
$username = $_SESSION['username'];
// Get the user-agent string of the user.
$user_browser = $_SERVER['HTTP_USER_AGENT'];
if ($stmt = $mysqli->prepare("SELECT password
FROM members
WHERE id = ? LIMIT 1")) {
// Bind "$id" to parameter.
$stmt->bind_param('i', $id);
$stmt->execute(); // Execute the prepared query.
$stmt->store_result();
if ($stmt->num_rows == 1) {
// If the user exists get variables from result.
$stmt->bind_result($password);
$stmt->fetch();
$login_check = hash('sha512', $password . $user_browser);
if ($login_check == $login_string) {
// Logged In!
return true;
} else {
// Not logged in
return false;
}
} else {
// Not logged in
return false;
}
} else {
// Could not prepare statement
header("Location: ../error.php?err=Database error: cannot prepare statement");
exit();
}
} else {
// Not logged in
return false;
}
}
function esc_url($url) {
if ('' == $url) {
return $url;
}
$url = preg_replace('|[^a-z0-9-~+_.?#=!&;,/:%#$\|*\'()\\x80-\\xff]|i', '', $url);
$strip = array('%0d', '%0a', '%0D', '%0A');
$url = (string) $url;
$count = 1;
while ($count) {
$url = str_replace($strip, '', $url, $count);
}
$url = str_replace(';//', '://', $url);
$url = htmlentities($url);
$url = str_replace('&', '&', $url);
$url = str_replace("'", ''', $url);
if ($url[0] !== '/') {
// We're only interested in relative links from $_SERVER['PHP_SELF']
return '';
} else {
return $url;
}
}
I believ this is where the issue is:
if (! $insert_stmt->execute())
$msg = 'Your password has changed successfully. Please login with your new password.';
}else{..}
if (! $insert_stmt->execute()) this means if the query fails echo that password fails.....
the reason your code always display Registration failure in updating recovery key: INSERT its because you instructed your code that when the query does not fail it must produce that.
And your code all of it is in mess, you need to clean it up.
Thi is how this should look.
if ($insert_stmt = $db->prepare("UPDATE recovery_keys SET valid = 0 WHERE userID = ? AND token = ? "));
$insert_stmt->bind_param('ss', $userID, $token);
// Execute the prepared query.
if ($insert_stmt->execute())
$msg = 'Your password has changed successfully. Please login with your new password.';
}else
{
$msg = "Password doesn't match";
header('Location: ../error.php?err=Registration failure in updating recovery key: INSERT'); }
{exit();}
Edit
Tried to clean up some of the mess in your code. Now this how should look.
<?php
ob_start();
include('config.php');
include('function.php');
$error_msg = "";
$token = $_GET['token'];
$userID = UserID($email);
$verifytoken = verifytoken($userID, $token);
// Sanitize and validate the data passed in
if (isset($_POST['submit'], $_POST['username'], $_POST['email'], $_POST['p'])) {
$email = filter_input(INPUT_POST, 'email', FILTER_SANITIZE_STRING);
$username = filter_input(INPUT_POST, 'username', FILTER_SANITIZE_STRING);
$new_password = filter_input(INPUT_POST, 'new_password', FILTER_SANITIZE_STRING);
$retype_password = filter_input(INPUT_POST, 'retype_password', FILTER_SANITIZE_STRING);
$id = filter_input(INPUT_POST, 'id', FILTER_SANITIZE_STRING);
}
$new_password = filter_input(INPUT_POST, 'p', FILTER_SANITIZE_STRING);
if (strlen($password) != 128) {
// The hashed pwd should be 128 characters long.
// If it's not, something really odd has happened
$error_msg .= '<p class="error">Invalid password configuration.</p>';
}
$prep_stmt = "SELECT id FROM members WHERE email = ? LIMIT 1";
$stmt = $db->prepare($prep_stmt);
if ($stmt) {
$stmt->bind_param('s', $email);
$stmt->execute();
$stmt->store_result();
if ($stmt->num_rows == 1) {
// A user with this email address already exists
$error_msg .= '<p class="error">A user with this email address already exists.</p>';
}
} else {
$error_msg .= '<p class="error">Database error</p>';
}
if ($new_password != $retype_password) {
// Create a random salt
$salt = hash('sha512', uniqid(openssl_random_pseudo_bytes(16), TRUE));
// Create salted password
$new_password = hash('sha512', $random_salt . $salt);
}
// Insert the new hashed password into the database
if ($insert_stmt = $db->prepare("UPDATE members SET password = ? WHERE id = ?")) {
$insert_stmt->bind_param('si', $new_password, $userID);
// Execute the prepared query.
if (!$insert_stmt->execute()) {
header('Location: ../error.php?err=Database Registration failure: INSERT');
exit();
}
// Update recovery key
if ($insert_stmt = $db->prepare("UPDATE recovery_keys SET valid = 0 WHERE userID = ? AND token = ? "));
$insert_stmt->bind_param('is', $userID, $token);
// Execute the prepared query.
if ($insert_stmt->execute())
$msg = 'Your password has changed successfully. Please login with your new password.';
} else {
$msg = "Password doesn't match";
header('Location: ../error.php?err=Registration failure in updating recovery key: INSERT');
exit();
}
?>
Important things you need to learn proper.
Update : here's the link
Prepared statements : link here
password hashing : link here
Your hashing method is very easy php does provide better and secure ways, please follow the links above.
Related
I am working on php and mysql code on making access to different pages based on the role of the user, through one Login Page.
Its working good for 'admin' page ..
but not able to login with 'normal type'
Little Help is really appreciated, Thank You
Here is my Code
<?php
session_start();
include 'dbcon.php';
if($_SERVER["REQUEST_METHOD"] == "POST") {
$username = $_POST['username'];
$password = $_POST['password'];
$query = "SELECT * FROM wp_users WHERE user_login = '$username' AND user_pass = '$password'";
$result = mysqli_query($con,$query) ;
$row = mysqli_fetch_assoc($result);
$count=mysqli_num_rows($result) ;
if ($count == 1) {
if($row['user_type'] == 'admin')
{
header('Location: user_registration.php');
$_SESSION['ID'] = $row['ID'];
$_SESSION['user_login'] = $row['user_login'];
$_SESSION['password'] = $row['user_pass'];
}
elseif($row['user_type'] = 'normal')
{
header('Location: index.php');
}
else
{
echo "WRONG USERNAME OR PASSWORD";
}
}
}
?>
Move your session code after if condition and then redirect. Also is there any specific reason to store password in session. == missing
Use proper filters for inputs.
if ($count == 1) {
if(!empty($row['user_type'])) {
$_SESSION['ID'] = $row['ID'];
$_SESSION['user_login'] = $row['user_login'];
//$_SESSION['password'] = $row['user_pass'];
}
if($row['user_type'] == 'admin')
{
header('Location: user_registration.php');
}
elseif($row['user_type'] == 'normal')
{
header('Location: index.php');
}
else
{
echo "WRONG USERNAME OR PASSWORD";
}
}
The logic test for the normal user was using a single = sign which sets a value rather than tests for equality - it needs to be ==
Also, I think the WRONG USERNAME OR PASSWORD wa at the wrong level - it needs to be the else to the record count
<?php
session_start();
include 'dbcon.php';
if($_SERVER["REQUEST_METHOD"] == "POST") {
$username = $_POST['username'];
$password = $_POST['password'];
$query = "SELECT * FROM wp_users WHERE user_login = '$username' AND user_pass = '$password'";
$result = mysqli_query($con,$query);
$row = mysqli_fetch_assoc($result);
$count=mysqli_num_rows($result);
if ($count == 1) {
if($row['user_type'] == 'admin') {
header('Location: user_registration.php');
$_SESSION['ID'] = $row['ID'];
$_SESSION['user_login'] = $row['user_login'];
$_SESSION['password'] = $row['user_pass'];
/* require `==` here */
} elseif( $row['user_type'] == 'normal' ) {
header('Location: index.php');
} else {
die('unknown/unhandled user level');
}
/* changed location of this by one level */
} else {
echo "WRONG USERNAME OR PASSWORD";
}
}
?>
This is function for login.
It presumes password come from user with sha512 encryption (see js libs like https://github.com/emn178/js-sha512) - it's good for non-encrypted connections.
It uses salt, and have some protection from brute force, CSRF, XSS and SQL-injection.
static public function db_login($email, $p)
{
if ($stmt = Site::$db->prepare(
"SELECT id, password, salt, name
FROM user
JOIN contact ON contact_id = id
WHERE email = ?
LIMIT 1")
) {
$stmt->bind_param('s', $email);
$stmt->execute();
$stmt->store_result();
$stmt->bind_result($user_id, $db_password, $salt, $name);
$stmt->fetch();
// hash the password with the unique salt
$p = hash('sha512', $p . $salt);
if ($stmt->num_rows == 1) {
// If the user exists we check if the account is locked
// from too many login attempts
if (self::checkBrute($user_id) == true) {
// Account is locked
$res['code'] = 0;
$res['reason'] = 'trylimit';
$res['message'] = 'You try too many times. Come back on 30 minutes';
return $res;
} else {
// Check if the password in the database matches
// the password the user submitted.
if ($db_password == $p) {
// Password is correct!
// Get the user-agent string of the user.
// CSRF
$user_browser = filter_input(INPUT_SERVER, 'HTTP_USER_AGENT', FILTER_SANITIZE_SPECIAL_CHARS);
// XSS protection as we might print this value
$user_id = preg_replace("/[^0-9]+/", "", $user_id);
Login::sec_session_start();
$_SESSION['user_id'] = $user_id;
$_SESSION['email'] = htmlspecialchars($email);
$_SESSION['name'] = htmlspecialchars($name);
$_SESSION['token'] = md5(uniqid(rand(), TRUE));
$_SESSION['login_string'] = hash('sha512', $p . $user_browser);
session_write_close();
// Login successful
$res['isLogined'] = 1;
$res['code'] = 1;
$res['name'] = $name;
$res['id'] = $user_id;
return $res;
} else {
// Password is not correct
// We record this attempt in the database
$now = time();
Site::$db->query("INSERT INTO login_attempts(user_id, time) VALUES ('$user_id', '$now')");
$res['code'] = 0;
$res['reason'] = 'pass';
$res['message'] = 'Wrong password';
return $res;
}
}
} else {
// No user exists.
$res['code'] = 0;
$res['reason'] = 'user';
$res['message'] = 'We have no such email';
return $res;
}
}
$res['code'] = 0;
$res['reason'] = 'SQL-error';
return $res;
}
So I'm trying to make a fairly simple login system, but for some reason the hashed password that is being sent to my database is not hashing correctly. I checked my database and the stored password is not what the sha256 hashed with the generated salt appended is not what it's supposed to be. Here's my code for generating the hash that's being uploaded to the database:
<?php
include "connection.php";
//Check Connection
if ($connect->connect_error) {
echo "Failed to connect to server: " . mysqli_connect_error();
}
//Reset all Checks
$username_exists = NULL;
$email_valid = NULL;
$passwords_match = NULL;
$password_acceptable = NULL;
$password_long_enough = NULL;
$password = NULL;
//Prepare Statements
//Check for Username Existing Statement
$check_username_match = $connect->stmt_init();
$sql_check_username = "SELECT id FROM $tablename WHERE username=?";
$check_username_match->prepare($sql_check_username);
$check_username_match->bind_param("s", $username);
//Insert Into Table Statement
$register_query = $connect->stmt_init();
$sql_register = "INSERT INTO $tablename (username, email, password, token, active, level) VALUES (?, ?, ?, ?, ?, ?)";
$register_query->prepare($sql_register);
$register_query->bind_param("sssssi", $username, $email, $hashedpassword, $token, $activated, $level);
//Execute When Form Submitted
if($_SERVER["REQUEST_METHOD"] == "POST") {
$username = mysqli_escape_string($connect, $_POST['username']);
$email = mysqli_escape_string($connect, $_POST['email']);
$password = $_POST['password'];
$confirm_password = $_POST['confirm_password'];
//Check if Username Exists
$check_username_match->execute();
$check_username_match->store_result();
$numrows = $check_username_match->num_rows;
if ($numrows==0){
$username_exists = false;
} else {
$username_exists=true;
}
//Check if Passwords Match
if ($password==$confirm_password){
$passwords_match = true;
} else {
$passwords_match = false;
}
//Check if Email Address is Valid
if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
$email_valid = true;
} else {
$email_valid = false;
}
//Check if Passwords Contains Special Characters
$uppercase = preg_match('#[A-Z]#', $password);
$lowercase = preg_match('#[a-z]#', $password);
$number = preg_match('#[0-9]#', $password);
//Check if Password is Long Enough
$password_length = strlen($password);
if ($password_length>8){
$password_long_enough = true;
} else {
$password_long_enough = false;
}
//Validate Password
if(!$uppercase || !$lowercase || !$number || !$password_long_enough || $password = '') {
$password_acceptable = false;
} else {
$password_acceptable = true;
}
//Register if all Validations Met
if(!$username_exists && $email_valid && $passwords_match && $password_acceptable){
//$salt = dechex(mt_rand(0, 2147483647)) . dechex(mt_rand(0, 2147483647));
$token = dechex(mt_rand(0, 2147483647)) . dechex(mt_rand(0, 2147483647));
$activated="No";
$level = 0;
$hashedpassword = password_hash($password, PASSWORD_DEFAULT);
$register_query->execute();
$message = "Hello, welcome to the site.\r\n\r\nPlease click on the following link to activate your account:\r\nlocalhost/login_system/activate.php?token=".$token;
mail($email, 'Please Activate Your Account', $message);
header("Location: login.php");
}
}
?>
UPDATE: I changed my above code to reflect the changes I made with password_hash. However, the problem still persists.
This is my login php:
<?php
include("connection.php");
session_start();
//Reset Variables
$message = '';
$location = "/login_system/index.php"; //default location to redirect after logging in
$username = '';
$password = '';
//Check to see if user is newly activated; if he is display a welcome message.
if(isset($_GET['activated'])){
if($_GET['activated'] == "true"){
$message = "Thank you for verifying your account. Please login to continue.";
}
}
//Check to see if user is coming from another page; if he is then store that page location to redirect to after logging in.
if(isset($_GET['location'])) {
$location = htmlspecialchars($_GET['location']);
}
echo $location;
//Prepare login check statement
$check_login = $connect->stmt_init();
$sql = "SELECT id, password FROM $tablename WHERE username=?";
$check_login->prepare($sql);
$check_login->bind_param("s", $username);
//Execute Login Check
if($_SERVER["REQUEST_METHOD"] == "POST") {
$username = mysqli_escape_string($connect, $_POST['username']);
$password = $_POST['password'];
$check_login->execute();
$check_login->store_result();
$numrows = $check_login->num_rows;
$check_login->bind_result($id, $match);
$check_login->fetch();
if ($numrows==1 && password_verify($password, $match)) {
$_SESSION['login_user'] = $id;
$goto = "localhost".$location;
header("location: $goto");
$message = "Success!";
} else {
$message="Username or password is not valid."."<br>".$match."<br>";
}
}
$connect->close();
?>
You should just feed the password you want to hash into PHP's password_hash();function. Like so...
$password = $_POST['password'];
$options = [
'cost' => 12,
];
echo password_hash($password, PASSWORD_BCRYPT, $options);
Then when you want to check if the password exists in the database use password_verify(); Like so...
$password = PASSWORD_HERE;
$stored_hash = HASH_HERE;
if (password_verify($password, $stored_hash)) {
echo 'Password is valid!';
} else {
echo 'Invalid password.';
}
I am new to development and the last 3 days i have been spending on a piece of code i wrote. This is what it needs to do: It needs to check of the email &password are filled in on the log in form. It also has to check if the data base is set to 'active'. $row returns a boolean. So how is it possible that I get redirected to the error page wether the status in the database is set to active or not?
Can somebody explain and talk me through the code and on what I am doing wrong? I have been searching the internet but can't find the right answer. This is my code:
<?php
include_once 'db_connect.php';
include_once 'functions.php';
sec_session_start(); // Our custom secure way of starting a PHP session.
error_reporting(E_ALL);
ini_set('display_erros', 1);
if (isset($_POST['email'], $_POST['p'])) {
$email = $_POST['email'];
$password = $_POST['p']; // The hashed password.
$query = "SELECT * FROM `members` WHERE `email` = '$email'AND status = 1 LIMIT 1";
$result = mysqli_query($mysqli, "SELECT COUNT(`ID`) AS count FROM members WHERE email='$email' AND `status`='1'");
$row = mysqli_fetch_assoc($result);
var_dump($row['count'] < 1);
if (login($email, $password, $mysqli) === true && $row === true) {
// Login success
header('Location: ../index2.php');
} else {
// Login failed
header('Location: ../index.php?error=1');
}
} else {
// The correct POST variables were not sent to this page.
echo 'Invalid Request';
}
function login($email, $password, $mysqli) {
// Using prepared statements means that SQL injection is not possible.
if ($stmt = $mysqli->prepare("SELECT id, username, password, salt
FROM members
WHERE email = ?
LIMIT 1")) {
$stmt->bind_param('s', $email); // Bind "$email" to parameter.
$stmt->execute(); // Execute the prepared query.
$stmt->store_result();
// get variables from result.
$stmt->bind_result($user_id, $username, $db_password, $salt);
$stmt->fetch();
// hash the password with the unique salt.
$password = hash('sha512', $password . $salt);
if ($stmt->num_rows == 1) {
// If the user exists we check if the account is locked
// from too many login attempts
if (checkbrute($user_id, $mysqli) == true) {
// Account is locked
// Send an email to user saying their account is locked
return false;
} else {
// Check if the password in the database matches
// the password the user submitted.
if ($db_password == $password) {
// Password is correct!
// Get the user-agent string of the user.
$user_browser = $_SERVER['HTTP_USER_AGENT'];
// XSS protection as we might print this value
$user_id = preg_replace("/[^0-9]+/", "", $user_id);
$_SESSION['user_id'] = $user_id;
// XSS protection as we might print this value
$username = preg_replace("/[^a-zA-Z0-9_\-]+/",
"",
$username);
$_SESSION['username'] = $username;
$_SESSION['login_string'] = hash('sha512',
$password . $user_browser);
// Login successful.
return true;
} else {
// Password is not correct
// We record this attempt in the database
$now = time();
$mysqli->query("INSERT INTO login_attempts(user_id, time)
VALUES ('$user_id', '$now')");
return false;
}
}
} else {
// No user exists.
return false;
}
}
}
function checkbrute($user_id, $mysqli) {
// Get timestamp of current time
$now = time();
// All login attempts are counted from the past 2 hours.
$valid_attempts = $now - (2 * 60 * 60);
if ($stmt = $mysqli->prepare("SELECT time
FROM login_attempts
WHERE user_id = ?
AND time > '$valid_attempts'")) {
$stmt->bind_param('i', $user_id);
// Execute the prepared query.
$stmt->execute();
$stmt->store_result();
// If there have been more than 5 failed logins
if ($stmt->num_rows > 5) {
return true;
} else {
return false;
}
}
}
function login_check($mysqli) {
// Check if all session variables are set
if (isset($_SESSION['user_id'],
$_SESSION['username'],
$_SESSION['login_string'])) {
$user_id = $_SESSION['user_id'];
$login_string = $_SESSION['login_string'];
$username = $_SESSION['username'];
// Get the user-agent string of the user.
$user_browser = $_SERVER['HTTP_USER_AGENT'];
if ($stmt = $mysqli->prepare("SELECT password
FROM members
WHERE id = ? LIMIT 1")) {
// Bind "$user_id" to parameter.
$stmt->bind_param('i', $user_id);
$stmt->execute(); // Execute the prepared query.
$stmt->store_result();
if ($stmt->num_rows == 1) {
// If the user exists get variables from result.
$stmt->bind_result($password);
$stmt->fetch();
$login_check = hash('sha512', $password . $user_browser);
if ($login_check == $login_string) {
// Logged In!!!!
return true;
} else {
// Not logged in
return false;
}
} else {
// Not logged in
return false;
}
} else {
// Not logged in
return false;
}
} else {
// Not logged in
return false;
}
}
function esc_url($url) {
if ('' == $url) {
return $url;
}
$url = preg_replace('|[^a-z0-9-~+_.?#=!&;,/:%#$\|*\'()\\x80-\\xff]|i', '', $url);
$strip = array('%0d', '%0a', '%0D', '%0A');
$url = (string) $url;
$count = 1;
while ($count) {
$url = str_replace($strip, '', $url, $count);
}
$url = str_replace(';//', '://', $url);
$url = htmlentities($url);
$url = str_replace('&', '&', $url);
$url = str_replace("'", ''', $url);
if ($url[0] !== '/') {
// We're only interested in relative links from $_SERVER['PHP_SELF']
return '';
} else {
return $url;
}
}
It seems to work now. I fixed a little bit of the code. And now I'm trying to use mysqli_real_escape_string to prevent SQL injection. Im reading up on SQL injection but I still have a lot to learn. What I did is change the database settings and instead of $row['status'] === true. I changed it to $row['status'] === NULL. I changed it in this way because vardump on status when activated returnen NULL.
if (isset($_POST['email'], $_POST['p'])) {
$email = $_POST['email'];
$password = $_POST['p']; // The hashed password.
$query = "SELECT * FROM `members` WHERE `email` = '". mysqli_real_escape_string($mysqli,$email) ."'AND status = 1 LIMIT 1";
// check the changes around $email and mysqli_real_escape_string use to prevent SQL Injection
$result = mysqli_query($mysqli,$query) or die(mysqli_error($mysqli)); // execute previous written query
$row = mysqli_fetch_assoc($result); // fetch record
//var_dump($row['status'] < 1);
if ($row['status'] === NULL && login($email, $password, $mysqli) === true) {
// Login success
header('Location: ../index2.php');
} else {
// Login failed
header('Location: ../index.php?error=1');
}
} else {
// The correct POST variables were not sent to this page.
echo 'Invalid Request';
}
i created the secure login with www.wikihow.com/Create-a-Secure-Login-Script-in-PHP-and-MySQL . The register is success, but the login was failed.
This is my functions.php code :
<?php
function login($email, $password, $mysqli) {
// Using prepared statements means that SQL injection is not possible.
if ($stmt = $mysqli->prepare("SELECT id, username, password, salt
FROM users
WHERE email = ? LIMIT 1")) {
$stmt->bind_param('s', $email); // Bind "$email" to parameter.
$stmt->execute(); // Execute the prepared query.
$stmt->store_result();
// get variables from result.
$stmt->bind_result($user_id, $username, $db_password, $salt);
$stmt->fetch();
// hash the password with the unique salt.
$password = hash('sha512', $password . $salt);
if ($stmt->num_rows == 1) {
// If the user exists we check if the account is locked
// from too many login attempts
if (checkbrute($user_id, $mysqli) == true) {
// Account is locked
// Send an email to user saying their account is locked
return false;
} else {
// Check if the password in the database matches
// the password the user submitted.
if ($db_password == $password) {
// Password is correct!
// Get the user-agent string of the user.
$user_browser = $_SERVER['HTTP_USER_AGENT'];
// XSS protection as we might print this value
$user_id = preg_replace("/[^0-9]+/", "", $user_id);
$_SESSION['user_id'] = $user_id;
// XSS protection as we might print this value
$username = preg_replace("/[^a-zA-Z0-9_\-]+/", "", $username);
$_SESSION['username'] = $username;
$_SESSION['login_string'] = hash('sha512', $password . $user_browser);
// Login successful.
return true;
} else {
// Password is not correct
// We record this attempt in the database
$now = time();
if (!$mysqli->query("INSERT INTO login_attempts(user_id, time)
VALUES ('$user_id', '$now')")) {
header("Location: ../error.php?err=Database error: login_attempts");
exit();
}
return false;
}
}
} else {
// No user exists.
return false;
}
} else {
// Could not create a prepared statement
header("Location: ../error.php?err=Database error: cannot prepare statement");
exit();
}
}
and this is my register.inc.php code
<?php
include_once 'db_connect.php';
include_once 'psl-config.php';
$error_msg = "";
if (isset($_POST['username'], $_POST['email'], $_POST['p'])) {
// Sanitize and validate the data passed in
$username = filter_input(INPUT_POST, 'username', FILTER_SANITIZE_STRING);
$email = filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL);
$email = filter_var($email, FILTER_VALIDATE_EMAIL);
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
// Not a valid email
$error_msg .= '<p class="error">The email address you entered is not valid</p>';
}
$password = filter_input(INPUT_POST, 'p', FILTER_SANITIZE_STRING);
if (strlen($password) != 128) {
// The hashed pwd should be 128 characters long.
// If it's not, something really odd has happened
$error_msg .= '<p class="error">Invalid password configuration.</p>';
}
// Username validity and password validity have been checked client side.
// This should should be adequate as nobody gains any advantage from
// breaking these rules.
//
$prep_stmt = "SELECT id FROM users WHERE email = ? LIMIT 1";
$stmt = $mysqli->prepare($prep_stmt);
if ($stmt) {
$stmt->bind_param('s', $email);
$stmt->execute();
$stmt->store_result();
if ($stmt->num_rows == 1) {
// A user with this email address already exists
$error_msg .= '<p class="error">A user with this email address already exists.</p>';
}
} else {
$error_msg .= '<p class="error">Database error</p>';
}
// TODO:
// We'll also have to account for the situation where the user doesn't have
// rights to do registration, by checking what type of user is attempting to
// perform the operation.
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);
$date = date('Y-m-d H:i:s');
// Insert the new user into the database
if ($insert_stmt = $mysqli->prepare("INSERT INTO users (username, email, password, salt, registerdate) VALUES (?, ?, ?, ?, ?)")) {
$insert_stmt->bind_param('sssss', $username, $email, $password, $random_salt, $date);
// Execute the prepared query.
if (! $insert_stmt->execute()) {
header('Location: ../error.php?err=Registration failure: INSERT');
exit();
}
}
header('Location: ./register_success.php');
exit();
}
}
and this is the forms.js code :
function formhash(form, password) {
// Create a new element input, this will be our hashed password field.
var p = document.createElement("input");
// Add the new element to our form.
form.appendChild(p);
p.name = "p";
p.type = "hidden";
p.value = hex_sha512(password.value);
// Make sure the plaintext password doesn't get sent.
password.value = "";
// Finally submit the form.
form.submit();
}
EDIT :
my proccess_login.php
include_once 'db_connect.php';
include_once 'functions.php';
sec_session_start(); // Our custom secure way of starting a PHP session.
if (isset($_POST['email'], $_POST['p'])) {
$email = filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL);
$password = $_POST['p']; // The hashed password.
if (login($email, $password, $mysqli) == true) {
// Login success
header("Location: ../protected_page.php");
exit();
} else {
// Login failed
header('Location: ../index.php?error=1');
exit();
}
} else {
// The correct POST variables were not sent to this page.
header('Location: ../error.php?err=Could not process login');
exit();
}
i already create the user email : xxx#gmail.com and the password is Xxx123
the registration show success, but the login show wrong. what's the wrong with the code?
i have try to try this question Can't Login into my Secure Login Script PHP and MySQL . i already remove the double hasing in register.inc.php :
// Create a random salt
$random_salt = hash('sha512', uniqid(openssl_random_pseudo_bytes(16), TRUE));
// Create salted password
$password = $password . $random_salt;
what's the wrong with my code? thank you
I have this script for change password :
<?php
/*
Page/Script Created by Shawn Holderfield
*/
//Establish output variable - For displaying Error Messages
$msg = "";
//Check to see if the form has been submitted
if (mysql_real_escape_string($_POST['submit'])):
//Establish Post form variables
$username = mysql_real_escape_string($_POST['username']);
$password = mysql_real_escape_string(md5($_POST['password']));
$npassword = mysql_real_escape_string(md5($_POST['npassword']));
$rpassword = mysql_real_escape_string(md5($_POST['rpassword']));
//Connect to the Database Server
mysql_connect("mysql..", "", "")or die(mysql_error());
// Connect to the database
mysql_select_db("") or die(mysql_error());
// Query the database - To find which user we're working with
$sql = "SELECT * FROM members WHERE username = '$username' ";
$query = mysql_query($sql);
$numrows = mysql_num_rows($query);
//Gather database information
while ($rows = mysql_fetch_array($query)):
$username == $rows['username'];
$password == $rows['password'];
endwhile;
//Validate The Form
if (empty($username) || empty($password) || empty($npassword) || empty($rpassword)):
$msg = "All fields are required";
elseif ($numrows == 0):
$msg = "This username does not exist";
elseif ($password != $password):
$msg = "The CURRENT password you entered is incorrect.";
elseif ($npassword != $rpassword):
$msg = "Your new passwords do not match";
elseif ($npassword == $password):
$msg = "Your new password cannot match your old password";
else:
//$msg = "Your Password has been changed.";
mysql_query("UPDATE members SET password = '$npassword' WHERE username = '$username'");
endif;
endif;
?>
<html>
<head>
<title>Change Password</title>
</head>
<body>
<form method="POST" action="">
<table border="0">
<tr>
<td align="right">Username: </td>
<td><input type="TEXT" name="username" value=""/></td>
</tr>
<tr>
<td align="right">Current Password: </td>
<td><input type="password" name="password" value=""/></td>
</tr>
<tr>
<td align="right">New Password: </td>
<td><input type="password" name="npassword" value=""/></td>
</tr>
<tr>
<td align="right">Repeat New Password: </td>
<td><input type="password" name="rpassword" value=""/></td>
</tr>
<tr><td>
<input type="submit" name="submit" value="Change Password"/>
</td>
</tr>
</table>
</form>
<br>
<?php echo $msg; ?>
</body>
</html>
This actually works fine, access databse and overwrite the current password, but it does not hash the password like while registration
Registration process script:
<?php
include_once 'db_connect.php';
include_once 'psl-config.php';
$error_msg = "";
if (isset($_POST['username'], $_POST['email'], $_POST['p'])) {
// Sanitize and validate the data passed in
$username = filter_input(INPUT_POST, 'username', FILTER_SANITIZE_STRING);
$email = filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL);
$email = filter_var($email, FILTER_VALIDATE_EMAIL);
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
// Not a valid email
$error_msg .= '<p class="error">The email address you entered is not valid</p>';
}
$password = filter_input(INPUT_POST, 'p', FILTER_SANITIZE_STRING);
if (strlen($password) != 128) {
// The hashed pwd should be 128 characters long.
// If it's not, something really odd has happened
$error_msg .= '<p class="error">Invalid password configuration.</p>';
}
// Username validity and password validity have been checked client side.
// This should should be adequate as nobody gains any advantage from
// breaking these rules.
//
$prep_stmt = "SELECT id FROM members WHERE email = ? LIMIT 1";
$stmt = $mysqli->prepare($prep_stmt);
// check existing email
if ($stmt) {
$stmt->bind_param('s', $email);
$stmt->execute();
$stmt->store_result();
if ($stmt->num_rows == 1) {
// A user with this email address already exists
$error_msg .= '<p class="error">A user with this email address already exists.</p>';
$stmt->close();
}
$stmt->close();
} else {
$error_msg .= '<p class="error">Database error Line 39</p>';
$stmt->close();
}
// check existing username
$prep_stmt = "SELECT id FROM members WHERE username = ? LIMIT 1";
$stmt = $mysqli->prepare($prep_stmt);
if ($stmt) {
$stmt->bind_param('s', $username);
$stmt->execute();
$stmt->store_result();
if ($stmt->num_rows == 1) {
// A user with this username already exists
$error_msg .= '<p class="error">A user with this username already exists</p>';
$stmt->close();
}
$stmt->close();
} else {
$error_msg .= '<p class="error">Database error line 55</p>';
$stmt->close();
}
// TODO:
// We'll also have to account for the situation where the user doesn't have
// rights to do registration, by checking what type of user is attempting to
// perform the operation.
if (empty($error_msg)) {
// Create a random salt
//$random_salt = hash('sha512', uniqid(openssl_random_pseudo_bytes(16), TRUE)); // Did not work
$random_salt = hash('sha512', uniqid(mt_rand(1, mt_getrandmax()), true));
// Create salted password
$password = hash('sha512', $password . $random_salt);
// Insert the new user into the database
if ($insert_stmt = $mysqli->prepare("INSERT INTO members (username, email, password, salt) VALUES (?, ?, ?, ?)")) {
$insert_stmt->bind_param('ssss', $username, $email, $password, $random_salt);
// Execute the prepared query.
if (! $insert_stmt->execute()) {
header('Location: ../error.php?err=Registration failure: INSERT');
}
}
header('Location: ./continue.php');
}
}
?>
What can I do to fix this? I want the password that is changed to be in format like while registration. Because now, when I change password, it's hashed but not + salted so it doesnt work when logging in with new password.
EDIT:
Here is login script:
<?php
include_once 'psl-config.php';
function sec_session_start() {
$session_name = 'sec_session_id'; // Set a custom session name
$secure = SECURE;
// This stops JavaScript being able to access the session id.
$httponly = true;
// Forces sessions to only use cookies.
if (ini_set('session.use_only_cookies', 1) === FALSE) {
header("Location: ../error.php?err=Could not initiate a safe session (ini_set)");
exit();
}
// Gets current cookies params.
$cookieParams = session_get_cookie_params();
session_set_cookie_params($cookieParams["lifetime"],
$cookieParams["path"],
$cookieParams["domain"],
$secure,
$httponly);
// Sets the session name to the one set above.
session_name($session_name);
session_start(); // Start the PHP session
session_regenerate_id(true); // regenerated the session, delete the old one.
}
function login($email, $password, $mysqli) {
// Using prepared statements means that SQL injection is not possible.
if ($stmt = $mysqli->prepare("SELECT id, username, password
FROM members
WHERE email = ?
LIMIT 1")) {
$stmt->bind_param('s', $email); // Bind "$email" to parameter.
$stmt->execute(); // Execute the prepared query.
$stmt->store_result();
// get variables from result.
$stmt->bind_result($user_id, $username, $db_password );
$stmt->fetch();
// hash the password
$passwordHash = password_hash($password, PASSWORD_BCRYPT);
if ($stmt->num_rows == 1) {
// If the user exists we check if the account is locked
// from too many login attempts
if (checkbrute($user_id, $mysqli) == true) {
// Account is locked
// Send an email to user saying their account is locked
return false;
} else {
// Check if the password in the database matches
// the password the user submitted.
if ($db_password == $password) {
// Password is correct!
// Get the user-agent string of the user.
$user_browser = $_SERVER['HTTP_USER_AGENT'];
// XSS protection as we might print this value
$user_id = preg_replace("/[^0-9]+/", "", $user_id);
$_SESSION['user_id'] = $user_id;
// XSS protection as we might print this value
$username = preg_replace("/[^a-zA-Z0-9_\-]+/",
"",
$username);
$_SESSION['username'] = $username;
$_SESSION['email'] = $email;
$_SESSION['login_string'] = hash('sha512',
$password . $user_browser);
// Login successful.
return true;
} else {
// Password is not correct
// We record this attempt in the database
$now = time();
$mysqli->query("INSERT INTO login_attempts(user_id, time)
VALUES ('$user_id', '$now')");
return false;
}
}
} else {
// No user exists.
return false;
}
}
}
function checkbrute($user_id, $mysqli) {
// Get timestamp of current time
$now = time();
// All login attempts are counted from the past 2 hours.
$valid_attempts = $now - (2 * 60 * 60);
if ($stmt = $mysqli->prepare("SELECT time
FROM login_attempts
WHERE user_id = ?
AND time > '$valid_attempts'")) {
$stmt->bind_param('i', $user_id);
// Execute the prepared query.
$stmt->execute();
$stmt->store_result();
// If there have been more than 5 failed logins
if ($stmt->num_rows > 5) {
return true;
} else {
return false;
}
}
}
function login_check($mysqli) {
// Check if all session variables are set
if (isset($_SESSION['user_id'],
$_SESSION['email'],
$_SESSION['username'],
$_SESSION['login_string'])) {
$user_id = $_SESSION['user_id'];
$email = $_SESSION['email'];
$login_string = $_SESSION['login_string'];
$username = $_SESSION['username'];
// Get the user-agent string of the user.
$user_browser = $_SERVER['HTTP_USER_AGENT'];
if ($stmt = $mysqli->prepare("SELECT password
FROM members
WHERE id = ? LIMIT 1")) {
// Bind "$user_id" to parameter.
$stmt->bind_param('i', $user_id);
$stmt->execute(); // Execute the prepared query.
$stmt->store_result();
if ($stmt->num_rows == 1) {
// If the user exists get variables from result.
$stmt->bind_result($password);
$stmt->fetch();
$login_check = hash('sha512', $password . $user_browser);
if ($login_check == $login_string) {
// Logged In!!!!
return true;
} else {
// Not logged in
return false;
}
} else {
// Not logged in
return false;
}
} else {
// Not logged in
return false;
}
} else {
// Not logged in
return false;
}
}
function esc_url($url) {
if ('' == $url) {
return $url;
}
$url = preg_replace('|[^a-z0-9-~+_.?#=!&;,/:%#$\|*\'()\\x80-\\xff]|i', '', $url);
$strip = array('%0d', '%0a', '%0D', '%0A');
$url = (string) $url;
$count = 1;
while ($count) {
$url = str_replace($strip, '', $url, $count);
}
$url = str_replace(';//', '://', $url);
$url = htmlentities($url);
$url = str_replace('&', '&', $url);
$url = str_replace("'", ''', $url);
if ($url[0] !== '/') {
// We're only interested in relative links from $_SERVER['PHP_SELF']
return '';
} else {
return $url;
}
}
Please have a look at the password_hash() and the password_verify() function, MD5 or sha512 are not appropriate to hash passwords, because they are ways too fast and can be brute-forced too easily. The password_hash function will generate the salt on its own, you won't need an extra field in the database to store it.
// Hash a new password for storing in the database.
// The function automatically generates a cryptographically safe salt.
$hashToStoreInDb = password_hash($password, PASSWORD_BCRYPT);
// Check if the hash of the entered login password, matches the stored hash.
// The salt and the cost factor will be extracted from $existingHashFromDb.
$isPasswordCorrect = password_verify($password, $existingHashFromDb);
Note: Using the password_hash function, there is no need to escape the password with mysql_real_escape_string, just use the original user entry.
Edit:
So i will try to point out some problems in your code, to be honest it looks a bit strange. I would try to start anew and use PDO or mysqli instead of the mysql functions, this would make it easier to protect against SQL-injection.
Change-password script
One problem is that you reuse the $password variable, which leads to the wrong comparison, though because you used == instead of = it does nothing:
$password = mysql_real_escape_string(md5($_POST['password']));
...
$password == $rows['password'];
...
elseif ($password != $password):
$msg = "The CURRENT password you entered is incorrect.";
I myself spend a lot of time to find descriptive variable names, this helps to prevent such mistakes.
$oldPassword = $_POST['password'];
...
$passwordHashFromDb = $rows['password'];
...
elseif (!password_verify($oldPassword, $passwordHashFromDb))
$msg = "The CURRENT password you entered is incorrect.";
Then before you store the new password in the database you calculate the hash:
$username = mysql_real_escape_string($_POST['username']);
$newPassword = $_POST['npassword'];
...
$newPasswordHash = password_hash($newPassword, PASSWORD_BCRYPT);
mysql_query("UPDATE members SET password = '$newPasswordHash' WHERE username = '$username'");
Registration-script
In your registration script there are other problems, surely you don't expect the user to enter a 128 character password?
if (strlen($password) != 128) { // looks strange to me
Instead of using a different hash algo, you should use the same as above:
// Create salted password
$passwordHash = password_hash($password, PASSWORD_BCRYPT);
// Insert the new user into the database
if ($insert_stmt = $mysqli->prepare("INSERT INTO members (username, email, password) VALUES (?, ?, ?)")) {
$insert_stmt->bind_param('sss', $username, $email, $passwordHash);
...
Login-script
In your login script you check whether the password matches the password stored in the database.
if ($db_password == $password) {
// Password is correct!
There you should test the hash like this:
if (password_verify($password, $db_password) {
// Password is correct!
The call to the password_hash() function does not help in a login script and should be removed.
There are some more comments in the modified code. Here is how you should do it.
Get the old salt from your database
//Gather database information
while ($rows = mysql_fetch_array($query)):
$username = $rows['username']; //single equals to assign value
$password = $rows['password'];
$user_salt = $rows['salt']; // here get old salt
endwhile;
and this part to read and use the old salt
else:
//$msg = "Your Password has been changed.";
$salted_password = hash('sha512', $npassword . $user_salt);// here use old salt
mysql_query("UPDATE members SET password = '$salted_password' WHERE username = '$username'");
endif;