I have a reset password form on my site and I'm unsure whether it's being compromised or if my code is just flakey. The process goes like this:
User enters email address of forgotten password
Password is changed in DB and email is sent to use
User can now login with new password and change once in their account
(I realise this is an imperfect process but it's v1 of the site).
The problem is that intermittently all user passwords are being changed (to the same password) and I can't for the life of me figure it out. XSS and CSRF filters are both enabled inside Codeigniter.
Here's my code:
Controller
$this->form_validation->set_rules('email','Email address','required');
if ($this->form_validation->run() === FALSE)
{
$this->password_reset();
}
else
{
/**
* Generate a new password and send to model
*/
$newpass = substr(md5(rand()),0,10);
$this->users_model->reset_password($newpass);
// stuff for email here
$this->email->send();
/**
* Load view
**/
$data['content'] = 'content/login/reset_done';
$data['title'] = 'Password Reset';
$this->load->view($this->layout, $data);
}
}
Model
public function reset_password($newpass)
{
$email = $this->input->post('email');
$query = $this->db->get_where('users', array('user_email' => $email));
if($query->num_rows())
{
$newpass = do_hash($newpass, 'md5');
$this->db->where('user_email', $email);
$this->db->update('users', array('user_password' => $newpass));
}
}
So to repeat the problem once again: every so often all users passwords are being changed (to the same password). How is this happening?
In your model, don't use $this->input->post, instead send this data as an argument. (Because your model shouldn't act like a controller with routes/sessions/headers/etc information)
It is possible that this happens because $this->input->post('email') is empty or '' (in this case, it will update everybody and num_rows will return the total of users in database), you must check for :
public function reset_password($email, $newpass)
{
if(!$email || !$newpass)
return false;
$query = $this->db->get_where('users', array('user_email' => $email));
if($query->num_rows())
{
$newpass = do_hash($newpass, 'md5');
$this->db->where('user_email', $email);
$this->db->update('users', array('user_password' => $newpass));
}
}
EDIT
- You don't need to do 2 queries (check email then update)
public function reset_password($email, $newpass)
{
if(!$email || !$newpass)
return false;
$newpass = do_hash($newpass, 'md5');
$this->db->where('user_email', $email);
$this->db->update('users', array('user_password' => $newpass));
if ($this->db->affected_rows() < 1) {
return false; //Email wasn't found
} else {
return true;
}
}
You can also call for $this->db->last_query(); to see the query and if the where() did work.
this is not an answer to your specific question, but it needs to be addressed, and it was too long for a comment.
DO NOT use MD5 as a hashing mechanism for passwords. it is outdated, and basically provides zero security anymore. instead use PHPs password_hash() and if you don't have PHP5.5 use the compatibility library. it's very easy to use and works great, and uses better hashing algorithms.
second, it is not generally good practice to create a new password for a user. rather create a token that expires within 1 hour of the request, and then pass that along in the email. then the user enters their email, token, and new password.
and because I can't say it enough, DONT USE MD5
Related
I'm trying to achieve a redirect to the home page of the user storing email in the session in Codeigniter. I have used password_hash($this->input->post('password'), PASSWORD_DEFAULT)) to hash the passwords and it works fine, but when I try to password_verify() it, it fails.
Here is my Model
public function canLogin($email, $password) {
$this->db->where('email',$email);
$query = $this->db->get($this->tableName);
$row = $query->row();
if ($row) {
return password_verify($password, $row->password);
}
else {
return false;
}
}
and here is my Controller
public function loginValidation() {
// User Model Loaded in constructor
if ($this->user->canLogin($_POST['email'], $_POST['password'])) {
$session_data = array('email' => $_POST['email'] );
$this->session->set_userdata($session_data);
redirect('profile/personal','Refresh');
} else {
echo 'fail';
$this->session->set_flashdata('error', 'Invalid Username or Password');
// redirect('login','Refresh');
}
}
I don't know where the logic went wrong and it everytime redirects to the same login page, I am trying to authenticate it, store email in session and redirect it to profile/personal , Can anyone point where I missed the logic?
#YashKaranke what is the password column's length? – Funk Forty Niner
#FunkFortyNiner It is 50 with datatype varchar – Yash Karanke
The password column's length is too short, it should be 60 or 255 as the manual on PHP.net for password_hash() suggests.
You now have to start over with new hashes.
The verification failed silently.
If you're using:
password_hash($this->input->post('password', PASSWORD_DEFAULT));
Are you sure this is hashing correctly? Shouldn't it be:
password_hash($this->input->post('password'), PASSWORD_DEFAULT);
I have a problem when decrypting passwords hashed with bcrypt. I can't login when I use this code. So, are there any mistakes?
function login(){
if ($this->session->userdata('username'))
{
redirect('dasbor');
}
//fungsi login
$valid = $this->form_validation;
$username = $this->input->post("username");
$password = $this->input->post("password");
$hash = $this->db->get('users')->row('password');
$hashp = $this->bcrypt->check_password($password,$hash);
$valid->set_rules("username","Username","required");
$valid->set_rules("password","Password","required");
if ($hashp) {
if($valid->run()) {
$this->simple_login->login($username,$hashp, base_url("dasbor"), base_url("Auth/login"));
}
}
// End fungsi login
$data = array('title'=>'Halaman Login Admin');
$this->load->view('admin/login_view',$data);
}
please help me to solve this problem.
I know this is an old question, but I want to help others who face the same problem.
First thing first, you need to rework again on your algorithm. The password_verify() function needs 2 parameters:
Password, the text that the user input in the text field before submitting the form.
Hash, a hash that is already stored in your database.
The goal is to verify if Password and Hash are similar. As you know, the password_hash() will return a different result at different times even when you hash the same string. Because of that, you can not use this->db->where() active record.
So, what I would do are these simple 2 steps:
Create a function in the model (e.g. Main_model.php) for getting user data.
public function get_user($user) {
$this->db->where('username', $user);
return $this->db->get('user')->row_array();
}
Get the password from the controller and use password_verify
$get_user = $this->main_model->get_user($this->input->post('username'));
if(password_verify($this->input->post('password'), $get_user['password'])){
// Success
}
else {
// Not Success
}
And one additional tip from me, don't write any active record in the Controller. It is not neat for the MVC method.
I am trying to code an application around an existing database. It already has several hundred users and data involving those users. So trying to change over the database that is used by a different program(game) from md5 to password_bcrypt or the like is not possible. This application is supposed to be a user's panel to allow for profile info and display user's character information. what I have atm is using PASSWORD_BCRYPT. I can get it to register the user with the md5 hash, however, my biggest issue is coding a password check. Here is the current code using PASSWORD_BCRYPT:
public function password($password)
{
return password_hash(
$password,
$this->config->get('app.hash.algo'),
['cost' => $this->config->get('app.hash.cost')]
);
}
public function passwordCheck($password, $hash)
{
return password_verify($password, $hash);
}
Again I know how to write out the code to let the user register with an md5 hash, but when they login it fails. Here is the call to the passwordCheck function:
if ($v->passes()) {
$user = $app->user
->where('username', $identifier)
->first();
if ($user && $app->hash->passwordCheck($password, $user->password)) {
$_SESSION[$app->config->get('auth.session')] = $user->id;
$app->flash('global', 'You are now signed in!');
} else {
$app->flash('global', 'Could not log you in!');
}
$app->response->redirect($app->urlFor('login'));
}
any and all suggestions welcome.
Thanks in advance!
The issue turned out to be that because I need to use a different hash, I can't use PASSWORD_HASH or the follow up function password_verify since the password_verify is checking apparently for the password_hash function. I'm not totally sure. But the following code is what works:
public function password($encrypt)
{
$encrypt = $salt . md5($encrypt);
return $encrypt;
}
public function passwordCheck($password, $hash)
{
return (strcmp($password, $hash) == 0);
}
the salt is a custom salt that I will keep to myself. I've tested this with my current app I am building and it is working like I want it to. The user can register and log in. Thanks for the help, but sadly the answer above came from else where. I know this isn't as secure as it should be but again it is a must since I am forced to use a pre-existing database that is still in use.
I have login code that is supposed to work by attempting to authenticate the user using Laravel's Auth::attempt() method. This code works on another site of mine, I have altered it as instead of the Password in the database, it is stored as passwdEncrypted. I cannot change it as the database is in use by another application as well.
The code is below:
// check if in database
$isInDb = User::where('ReferenceCode', $username)->first();
if($isInDb) {
// is in database
// check if password is encrypted yet
if($isInDb->passwdEncrypted) {
// password is encrypted
if(Auth::attempt(array('ReferenceCode' => $username, 'passwdEncrypted' => $password))) {
// authenticated
$array = array();
$array['verified'] = 'true';
$array['type'] = Auth::user()->UserType;
return Response::json($array);
} else {
// not authenticated
$array = array();
$array['verified'] = 'false';
$array['type'] = $type;
return Response::json($array);
}
} else {
// password is not encrypted
// check if plain text password is correct
if($isInDb->Password == $password) {
// plain text password is correct
$hashed = Hash::make($password);
$arr = array('passwdEncrypted' => $hashed);
$updated = User::where('rsmsOnlineUserID', $isInDb->rsmsOnlineUserID)->update($arr);
if($updated) {
$newUser = User::find($isInDb->rsmsOnlineUserID);
echo $newUser->passwdEncrypted;
if(Auth::attempt(array('ReferenceCode' => $username, 'passwdEncrypted' => $password))) {
echo 'logged in';
} else {
dd(DB::getQueryLog());
echo 'could not log in';
}
} else {
echo 'did not update';
}
} else {
// plain text password is incorrect
$array = array();
$array['verified'] = 'false';
$array['type'] = $type;
return Response::json($array);
}
}
} else {
// not in database
return Respone::json(array('success' => 'false'));
}
What is happening: I can't log in, the username and password in the database is 1234, even if I hard code that, it does not work.
It first checks to see if the user is in the database, if it is, it checks to see if there is an encrypted password, if there is not, it will create one from the password given if it matches the plain text password in the database and then log the user in (I have no choice but to have the plain text password stored in the database, that is how they want it).
But it returns the {"verified":"false","type":"prospective_employee"} from the not authenticated part of the code. So neither of the Auth::attempt() blocks work.
I was logging them in manually but even Auth::login() won't work.
I have the following in my User model (with the main database table):
public function getAuthPassword()
{
return $this->Password;
}
/**
* Get the token value for the "remember me" session.
*
* #return string
*/
public function getRememberToken() {
return $this->remember_token;
}
public function setRememberToken($value) {
$this->remember_token = $value;
}
public function getRememberTokenName() {
return 'remember_token';
}
/**
* Get the e-mail address where password reminders are sent.
*
* #return string
*/
public function getReminderEmail()
{
return $this->email;
}
Please note that there is a field in the table called Password, but that is the plain text password, I need to authenticate against the passwdEncrypted field.
You cannot do this with Laravel, and for good reason, but it is ridiciously unsecure and dangerous.
I have no choice but to have the plain text password stored in the
database, that is how they want it
I dont understand - why are you storing BOTH an "unencrypted" and "encrypted" password? There is no point. You should only ever store encrypted passwords. There is no need for any other way, and you need to educate the people as to why.
This code works on another site of mine, I have altered it as instead
of the Password in the database, it is stored as passwdEncrypted. I
cannot change it as the database is in use by another application as
well.
The Laravel Auth code is hard coded to use the "password" column. You cannot simply change it to another colum. That is why your code is failing.
Since you are not using the password column, and since you are not using encrypted passwords, you might as well just create your own unsecure login system, customised to suit your requirements.
I have a site which runs off an MD5 hashing scheme for passwords. As a way of supporting this legacy system, I've this answer to manually override the login system for now. But this isn't really ideal, as MD5 is pretty much universally known to be awful at encryption. So in the interest of security, what's the best way to migrate users over to the safer CakePHP auth system without causing them undue grief?
Figured it out thanks to this answer (albeit lightly modified). Basically, it updates the user behind the scenes to use the new system if the current system doesn't match up with it.
/**
* Login method
*/
public function login() {
$this->layout = 'homepage';
// If the user is already logged in, redirect to their user page
if($this->Auth->user() != null) {
$this->redirect();
} else {
// If this is being POSTed, check for login information
if($this->request->is('post')) {
if($this->Auth->login($this->loginHelper($this->request->data))) {
// Redirect to origin path, ideally
} else {
$this->Session->setFlash('Invalid username or password, try again');
}
}
}
}
/**
* Update password method
* #param array The user's data array
* #param Returns either a user object if the user is valid or null otherwise
*/
private function loginHelper($data) {
$username = $this->data['User']['username'];
$plainText = $this->data['User']['password'];
$user = current($this->User->findByUsername($username));
$salted = Security::hash($plainText, null, true);
if ($salted === $user['password']) {
return $user; // user exists, password is correct
}
$md5ed = Security::hash($plainText, 'md5', null);
if ($md5ed === $user['password']) {
$this->User->id = $user['id'];
$this->User->saveField('password', $plainText);
return $user; // user exists, password now updated to blowfish
}
return null; // user's password does not exist.
}