I am attempting to create a register page and am having trouble trying to hash my password using PASSWORD_BCRYPT with mysqli. Can someone explain what is wrong with my code below?
<?php
if(isset($_POST['Register'])) { // checking that form is submitted
session_start(); //creating variables for form entries
$FName = $_POST['First_Name'];
$LName = $_POST['Last_Name'];
$Email = $_POST['Email'];
$PW = $_POST['Password'];
$StorePassword = password_hash($PW, PASSWORD_BCRYPT, array('cost' => 10));
$sql = $con->query("INSERT INTO users (Fname, Lname, Email, Password)Values('{$FName}', '{$LName}', '{$Email}', '{$PW}')");
header('Location: Login.php');
Firstly, you're using the wrong variable for the password in the query being $PW rather than the intended $StorePassword variable where you're using it on top, then passing it to the hashing function.
Your password is being stored as "rasmuslerdorf" rather than "$2y$10$.vGA1O9wmRjrwAVXD98HNOgsNpDczlqm3Jq7KnEd1rVAGv3Fykk1a"
Example pulled from the manual.
If that still doesn't work then that function may not be available for you to use and will need to use the password compatibility pack
(if PHP < 5.5) https://github.com/ircmaxell/password_compat/
Add error reporting to the top of your file(s) which will help find errors.
<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);
// rest of your code
Sidenote: Displaying errors should only be done in staging, and never production.
Consult these following links
http://php.net/manual/en/mysqli.error.php
http://php.net/manual/en/function.error-reporting.php
and apply that to your code.
You may have errors in your query but you're not checking for them.
Plus, seeing you did not post your HTML form, make sure it is using a POST method and that all inputs bear the proper name attributes.
Just for argument's sake; your posted code is missing a closing brace }
Also add exit; after header, should there be more code after that. Otherwise, your code may want to continue to execute.
Make sure you are indeed successfully connected using the same MySQL API as you are using for querying, being mysqli_. That is unknownst to us.
Different APIs such as mysql_ and PDO do not intermix with mysqli_ and vice-versa.
Make sure you're not outputting before header using session_start(); in the place it's in now; it looks as if there's a space before your opening PHP tag, that is considered as output. Error reporting will tell you that also.
Your present code is open to SQL injection. Use prepared statements, or PDO with prepared statements, they're much safer.
Footnotes:
Make sure that the password column is long enough to store the hash. PHP.net recommends using VARCHAR(255) and in order to accomodate for the future. Same thing for all columns and of the correct lengths/types.
http://php.net/manual/en/function.password-hash.php
"Note that this constant is designed to change over time as new and stronger algorithms are added to PHP. For that reason, the length of the result from using this identifier can change over time. Therefore, it is recommended to store the result in a database column that can expand beyond 60 characters (255 characters would be a good choice)."
Related
We have been attacked; the hackers entered the system from a page <login> that's in the code shown below, but we couldn't figure out the actual problem in this code.
Could you point out the problem in this code and also a possible fix?
<?php
//login.php page code
//...
$user = $_POST['user'];
$pass = $_POST['password'];
//...
mysql_connect("127.0.0.1", "root", "");
mysql_select_db("xxxx");
$user = mysql_real_escape_string($user);
$pass = mysql_real_escape_string($pass);
$pass = hash("sha1", $pass, true);
//...
$query = "select user, pass from users where user='$user' and pass='$pass'";
//...
?>
The problem here is in $pass= hash("sha1",$pass, true);
You need to put it like this $pass= hash("sha1",$pass, false);
A good option is to move to PDO.
Let's see why this happen:
What your code is doing is returning a raw binary hash that means at a point in time the hash may contain an equal character =,
for your example the hash that going to result in SQL injection in this case is "ocpe" because hash ("ocpe",sha1) have a '=' character,
but how can I figure that out?
You only need to run a simple brute force and test if it contains a '=' inside the hash raw bit.
This is a simple code which can help you with that
<?php
$v = 'a';
while(1)
{
$hash = hash("sha1",$v, true);
if( substr_count( $hash, "'='" ) == 1 ) {
echo $v;
break;
}
$v++;
}
?>
Now you you have a string that gives a hash that has an equal inside of it '='
The query becomes:
$query = "select user, pass from users where user='$user' and pass='hash("ocpe",sha1)'";
then
$query = "select user, pass from users where user='$user' and pass='first_Part_of_hash'='Second_part_of_hash'";
In this case I assume that ocpe string has a hash of this format first_Part_of_hash'='Second_part_of_hash
Because pass='first_Part_of_hash' going to result in 0 and 0='Second_part_of_hash' is typecasted by the SQL engine, but in case of string if we type cast it to a int it's going to give as 0 ((int)'Second_part_of_hash' is result in 0)
so in the end 0=0
$query = "select user, pass from users where user='$user' and 0=0";
Which going to result in "true" every time and as you can see it can be applied to all hash functions like MD5 and sha256 etc.
Good resources to check:
How can I prevent SQL injection in PHP?
Could hashing prevent SQL injection?
To supplement the excellent answer from zerocool.
The problem here is the false notion that mysql(i)_real_escape_string prevents SQL injection. Unfortunately, too many people have been led to believe that this function's purpose is to protect them from injections. While of course it is not nearly true.
Had the author of this code the correct understanding of this function's purpose (which is escaping special characters in a string literal), they would have written this code as
$user = mysql_real_escape_string($user);
$pass = hash("sha1", $pass, true);
$pass = mysql_real_escape_string($pass);
and there wouldn't have been any injections at all.
And here we come to an important conclusion: given escaping's purpose is not to prevent SQL injections, for such a purpose we should use another mechanism, namely prepared statements. Especially given the fact that mysql extension doesn't exist in PHP anymore while all other extensions support prepared statements all right (yet if you want to reduce the pain of transition you should definitely use PDO, however paradoxical it may sound).
(Supplementary to the other answers / comments about using PDO, correct use of passwords etc; Logging this here in case someone else stumbles on this question.)
No one has pointed out:
mysql_connect("127.0.0.1","root","");
mysql_select_db("xxxx");
as being a point of weakness.
This means that:
- the DB server is on the same host as the web server, and therefore has a network interface to the world.
- this have the most basic user (root) available,
- and without a password.
Hopefully this is an example/test, but if not, ensure that at least the server port (3306) is blocked by firewall / not accessible externally.
Otherwise a simple mysql -h [webserver address] -u root will connect and it's game over.
You can rewrite your validation logic as a quick fix to the issue explained by #zerocool.
// don't send password hash to mysql, user should be uniqe anyway
$query = "select user, pass from users where user='$user'";
// validate hash in php
if (hash_equals(hash('sha1', $pass, true), $user_hash_from_db)){...}
And as others wrote, stop using mysql_* functions ASAP, and use stronger hashing algo.
You can fix your existing code, without breaking any of the existing passwords, by adding one line:
$pass = $_POST['password']; // the actual password
$pass = mysql_real_escape_string($pass); // escaped version of the actual password
$pass = hash("sha1",$pass, true); // binary hash of the escaped password
// At this point, $pass is the exact string that is stored in the database.
$pass = mysql_real_escape_string($pass); // ***ADD THIS LINE***
$query = "select user, pass from users where user='$user' and pass='$pass'";
Note that the password stored in the database is the binary hash of the escaped version of the actual password. Since it is a binary string, you need to escape it.
Be sure to add the extra escaping to the code that stores the password in the first place, otherwise password setting will also have a SQL injection vulnerability.
pals, got a quite disgusting situation here with my submit form. So, the problem: once user submits form his data goes to database and THEN if he refreshes the page the form submits again and he can do it infinite times and my DB will be full of useless data. So, how to prevent form submition after f5? I tried to use header('Location: success.php'); , but it doesn't help. Here is my server.php code:
<?php
session_start();
$message = "Wrong input";
$username = "";
$email = "";
$errors = array();
$db = mysqli_connect('localhost', 'root', 'root', 'example');
if(isset($_POST['register'])) {
$username = mysqli_real_escape_string($db, $_POST['username']);
$email = mysqli_real_escape_string($db, $_POST['email']);
$password = mysqli_real_escape_string($db, $_POST['password']);
if(empty($username) || empty($email) || empty($password)) {
echo "<script type='text/javascript'> alert('$message');</script>";
array_push($errors, "err");
}
}
if(count($errors) == 0) {
$pass = md5($password);
$query = "INSERT INTO users (username, email, password) VALUES ('$username', '$email', '$pass')";
mysqli_query($db, $query);
header('Location: success.php');
}
?>
f5 will just send the last request again, so you can't stop it
if you want to prevent that, you should add some test before creating a new user (check if the user is new or not, validate email...) and storing it inside the db
and change your md5 hash for the password. you should use a salt + sha* hash solution. you should considere to update your code with preparedStatement too
The reason you're getting junk data here is because you have zero validation logic. Every application must do at least some sort of superficial parameter cleanup, such as removing extraneous spaces, forcing lower or upper case for certain fields, and stripping any unwanted characters.
Then you'll need to check that the required fields are present, and are in a form that's acceptable. For example, an email address must contain at least an #, a name must contain at least one letter, and so on. Different field types have different requirements, where generally passwords have the most constraints, as you might need to enforce minimum/maximum lengths, presence of capital letters and/or numbers, and other such considerations.
If and only if the data's passed that scruitiny do you move on to the creation phase where applications must do is verify uniqueness of certain fields. This usually involves a quick check of the form:
SELECT COUNT(*) FROM users WHERE username=?
Note that this check is considered advisory only, that the information gleaned here is immediately considered obsolete. It can be used to present information to the user in the form of errors. It cannot be trusted going forward in the code, that is, your database can and will change after that statement is executed, rendering any tests invalid.
The third step is to commit the record, actually insert it. To prevent duplication at this point you'll need to have a UNIQUE constraint on any fields that must be unique. You'll also need to have code that captures these errors and re-surfaces them to the user in a form they can understand.
If this sounds like a lot of work, it's because it is. Don't write all this code by hand, it's a huge waste of time and you will get it wrong. Use a development framework to give you the foundation for this sort of thing. They come in a variety of flavours, from very light-weight like Fat-Free Framework to extremely full-featured like Laravel and all shades between. These implement everything I've talked about in different ways, the syntax and methodology can vary considerably, but the principles are the same.
Find one that you like, learn it well, and follow their community best-practices.
I'm trying to make a register/login system. To check if usernames and email addresses aren't used, I use this :
$username = $_POST['uLogin'];
$usernameLC = strtolower($username);
$query1 = $db0->query("SELECT userLogin FROM tbuser WHERE userLogin=':login';");
$query1->bindValue(":login", $usernameLC, PDO::PARAM_STR);
But it doesn't work. I can create as much users with the same username as I want. By extension, it also won't let me connect onto the website as it doesn't bind values or anything, so it can't compare my username to the one in the DB.
Verifying if a username is not taken worked when I used it like this
$username = $_POST['uLogin'];
$usernameLC = strtolower($username);
$query1 = $db0->query("SELECT userLogin FROM tbuser WHERE userLogin='$usernameLC';");
But it isn't the proper way to go :/
Can anybody help ? :)
They're not working because your binded values contain quotes; remove them.
userLogin=':login'
as
userLogin=:login
"Verifying if a username is not taken worked when I used it like this"
WHERE userLogin='$usernameLC'
You need to remove the quotes in the bind as already stated on top, and make sure you're using PDO to connect with, as stated below; if that is the case.
Using setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) would have signaled the syntax errors.
Read up on how to use prepared statements in PDO, to prepare and execute:
http://php.net/pdo.prepared-statements
An insight:
Make sure you are indeed using a PDO connection rather than a mysqli-based (it's unknown). I see these types of questions often, where OP's use mysqli_ to connect with and querying with PDO.
Those different MySQL APIs do not intermix with each other.
Connecting through PDO on PHP.net
If you're using mysqli_ to connect with:
See mysqli prepared statements and how to use them.
Add error reporting to the top of your file(s) which will help find errors, if any in regards to your POST arrays, or other possible errors.
<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);
// rest of your code
Sidenote: Error reporting should only be done in staging, and never production.
Edit:
"Thanks, it works great. When logging in though, comparing submitted password to the password in DB returns false. I try stocking the received password in $_SESSION['test'] to see what it gets and print_r($_SESSION); returns me this : Array ( [test] => Array ( [userPwd] => test12 [0] => test12 ) ) (test12 is my password, userPwd is the password Field in the db) Any idea ? ^^"
In regards to a comment you left about using passwords.
It seems you are storing passwords in plain text, rather than a hash. This is highly discouraged, as well as being stored in sessions; a very bad idea.
Read up on sessions hijacking.
See this Q&A on Stack on hashed passwords:
Q: Php 5.5 And Pdo Login
A: https://stackoverflow.com/a/27023211/
Using PHP's password_hash() function and password_verify() function.
For PHP < 5.5 use the password_hash() compatibility pack.
A note about the column type and length when storing a hashed password.
The password column should be VARCHAR.
It should also be long enough to store the hash.
Using VARCHAR(255) is best.
First off, if you're going to prepare, use ->prepare(), and remove quotes in your named placeholders, they don't need to have that:
$query1 = $db0->prepare("SELECT userLogin FROM tbuser WHERE userLogin= :login");
Then $query1->execute(), the prepared statement after the binding, so all in all:
$username = $_POST['uLogin'];
$usernameLC = strtolower($username);
$query1 = $db0->prepare('SELECT userLogin FROM tbuser WHERE userLogin = :login'); // prepare
$query1->bindValue(':login', $usernameLC, PDO::PARAM_STR); // bind
$query1->execute(); // execute
I'm working on a web app and I came across this code snippit
$email=$_POST['email'];
$pass=$_POST['pass'];
$pass=md5($pass);
$query=mysql_real_escape_string($email,$link);
//echo $query."<br>";
$sql=mysql_query("SELECT pass FROM users WHERE email='".$email."'",$link);
if($row=mysql_fetch_array($sql))
{
I think the programmer intended $query=mysql_real_escape_string($email,$link); to be $email=mysql_real_escape_string($email,$link);
Do I have the right idea here?
Yes, you're absolutely right - just correct that part, like you said, by changing it to
$email = mysql_real_escape_string($email, $link);
, and that will protect against SQL injection there.
On a side note, I suggest you use hash("sha512", xxx) instead of md5 because MD5 is becoming obsolete. If your column size doesn't allow for that though and you don't have the ability to change it, it's still OK.
Yes, $email is set, but then not filtered, it's used directly in the query. As you pointed out, it looks like an error as the filtered value is not being used in the query.
to prevent from blind SQL , wrap your POST data with tow more filters:
$email = mysql_real_escape_string(strip_tags(stripslashes($email)), $link)
I need the following authentication script finished. I am weak at php/pdo so I do not know how to ask for the number of rows equalling one and then setting the session id's from the results of the query. I need to not only set the $_SESSION['userid'] but also the ['company'] and the ['security_id'] as well from the results.
here is what I have:
$userid = $_POST['userid'];
$password = $_POST['pass'];
if ( $userid != "" || $password != "" )
{
$sql = "SELECT * FROM contractors WHERE userid = '" . $userid . "' AND password = '" . $password . "'";
$result = $dbh->query( $sql );
} else
{
echo "login failed. Your fingers are too big";
}
Optional Information:
Browser: Firefox
DO NOT EVER USE THAT CODE!
You have a very serious SQL injection open there. Every user input that you take, whether from cookies or CGI, or wherever, must be sanitized before it's used in an SQL statement. I could easily break into that system by attempting a login with an username like:
user'; UPDATE contractors SET password = '1337'
... after which I could then login as anyone. Sorry if I sound aggressive, but what that code does is like forgetting to lock the front door into your company which probably doesn't even contain an alarm system.
Note that it doesn't matter whether the input is actually coming from the user or not (perhaps it's in a pre-filled, hidden from). From the security point of view, anything that comes from anywhere outside has to be considered to contain malicious input by the user.
As far as I know, you need to use the quote function of PDO to properly sanitize the string. (In mysql, this would be done with mysql_real_escape_string().) I'm not an expert on PDO, mind you, somebody please correct if I'm wrong here.
Also you probably shouldn't store any passwords directly in the database, but rather use a hash function to create a masked password, then also create a hash from the user provided password, and match the hashes. You can use the PHP hash function to do this.
As for other issues, I don't know if the approach you have on SQL SELECT is the best approach. I would just select the corresponding user's password and try matching that in the program. I don't think there's any fault in the method you're using either, but it just doesn't seem as logical, and thus there's a greater chance of me missing some bug - which in case of passwords and logins would create a window for exploits.
To do it your way, you need to notice that the result you are getting from the PDO query is a PDOStatement, that doesn't seem to have a reliable function to diretly count the amount of result rows. What you need to use is fetchAll which returns an array of the rows, and count that. However, as I said this all feels to me like it's open for failures, so I'd feel safer checking the password in the code. There's just too much distance from the actual password matching compasion for my taste, in such a security-critical place.
So, to the get the resulting password for the userid, you can use PDOStatement's fetch() which returns the contents of the column from the result. Use for example PDO::FETCH_ASSOC to get them in an associative array based on the column names.
Here's how to fix it:
$userid_dirty = $_POST['userid'];
$password_dirty = $_POST['pass'];
$success = false; // This is to make it more clear what the result is at the end
if ($userid != "" || $password != "") {
$userid = $dbh->quote($userid_dirty);
$passwordhash = hash('sha256',$password_dirty);
$sql = "SELECT userid, passwordhash, company, security_id FROM contractors WHERE userid = ".$userid;
$result = $dbh->query( $sql );
if ($result) { // Check if result not empty, that userid exists
$result_array = $result->fetch(PDO::FETCH_ASSOC);
if ($result_array['PASSWORDHASH'] == $passwordhash) {
// login success
$success = true;
// do all the login stuff here...
// such as saving $result_array['USERID'], $result_array['COMPANY'], $result_array['SECURITY_ID'] etc.
} // else fail, wrong password
} // else fail, no such user
} else {
// fail, userid or password missing
echo ' please enter user id and password.';
}
if (!$success) {
echo ' login failed.';
}
Of course, the code can be cleaned up a bit, but that should explain what needs to be done. Note that since the password is both hashed, and never used in the SQL, it doesn't actually need cleaning. But I left it there just in case, since in the original code it was used in the query.
Note that all the code concerning storing passwords need to be changed to store the hash instead of the password. Also, it would be a very good idea to use a salt added to the password before hashing.
Also, I provided the code simply for educational purposes - I just thought that code was the clearest way to explain how to do this. So do not mistake this site as a service to request code. :)
The php manual is an excellent resource for learning PHP. It looks like you know a little SQL, and you have heard of PDO, which is a good start. If you search google for "PDO", or look in the PHP manual for the term, you'll find the PDO section of the manual. It looks like you've found the ->query function, so now you need to see what that returns. Going to the that function's manual page, we see that it returns a PDOStatement object. The word PDOStatement is helpfully linked to the relevant page in the manual, which lists the methods available on that object. There is a rowCount() method that will likely do what you want.