Most secure php user athentication function - php

What is the best way to create a secure user authentication function? Below is the core of a php function that takes in the username and password and checks it against the database.
I am specifically interested in the query and its return value. Is using the 'else if($query1)' the best way to validate and set the session variable? Also, what value is best to set for the session variable? An email address, username, bool variable, primary key index, etc?
$query1 = mysql_fetch_array(mysql_query("SELECT primaryKey
FROM loginInfo
WHERE email = md5('$email')
AND password = md5(CONCAT('$password',salt))
LIMIT 1"));
if (!$query1)
return false;
else if ($query1) {
$_SESSION['userNumber'] = $query1[primaryKey];
return true;
}
else
return false;

MD5 has known vulnerabilities and is no longer considered secure. You should switch to a stronger hash such as SHA-2.
Also, $query1 can only evaluate to true or false, so the final else part is useless and will never be reached. Your 3 branches are equivalent to just this:
if (!$query1)
return false;
else { // else $query1 is obviously true
$_SESSION['userNumber'] = $query1[primaryKey];
return true;
}
There is no such thing as a "best value" to store in the session, but the primary key is usually a convenient choice, since it is guaranteed to be unique and also provides an easy way to look up the remaining details. Additionally, if you find yourself frequently displaying some information such as the user's name, you could additionally store that in the session for easy access.

There are multiple issues with this code:
SQL injection vulnerability. (What happens when a user enters an email address of ') OR 1=1 OR '' = ('?) You should have a look at mysql_real_escape_string, or consider using parametrized queries.
Your never call mysql_free_result on the resource returned from mysql_query, which will leak resources on the MySQL server (until the script terminates), and may prevent future queries in the same script from executing.
MD5 is deprecated due to vulnerabilities. Consider using a hash in the SHA family instead.

It depends on where the attacker will have access. If he somehow has access to the database, the suggested changes of the hash-type are important.
If he doesn't, it's more important to restrict the number of failed logins to avoid bruteforce-attacks.
However, the vulnerability cdhowie pointed at has to be fixed at all.

Related

Best practices in PHP using $_POST and $_GET variables

Considering a project where will work more than one developer and that will receive constant update and maintenance, what of the two codes below can be considered the best practice in PHP, considering Readability and Security? If we talk in performace, the second option will probably be a little better, but there are ways to solve this point.
Option 1
$user = $_POST['user'];
$pass = $_POST['pass'];
// Prevent SQL Injection and XSS
$user = anti_injection($user);
$pass = anti_injection($pass);
if(strlen($user) <= 8 && strlen($pass) <= 12)
{
// SQL query
$sql = "SELECT id
FROM users
WHERE username = '$user' AND password = '$pass';";
}
Option 2
// Retrieve POST variables and prevent SQL Injection and XSS
$_POST['user'] = anti_injection($_POST['user']);
$_POST['pass'] = anti_injection($_POST['pass']);
if(strlen($_POST['user']) <= 8 && strlen($_POST['pass']) <= 12)
{
// SQL query
$sql = "SELECT id
FROM users
WHERE username = '" . $_POST['user']. "' AND password = '" . $_POST['pass'] . "';";
}
EDIT 1
I am not using MySQL, my database is PostgreSQL
Don't do either.
I can only assume anti_injection is some sort of custom filtering function, which is a Bad Idea™. If you really want to adopt either of these idea, you should be using mysql_real_escape_string.
The only way to remain secure when writing SQL queries is to use parameters, e.g. through MySQLi. The mysql_* functions are becoming deprecated anyway, so you're best to move across as soon as possible.
In fact mysql_real_escape_string is not a foolproof defense against injection attacks. Consider an integer comparison in a query, such as WHERE $var > 30. I could inject 1=1 or 100 into $var successfully, and completely break the logic.
Parameters completely separate data from query language, completely mitigating the injection risk. The server receives a query containing parameter notation, and a set of values to insert, so it can handle the query language and data completely differently.
Furthermore, you seem to be storing passwords in plaintext. This is a bad idea. You should look into a strong password storage hash algorithm such as bcrypt, which makes it very difficult to obtain plaintext passwords from the hashes.
MD5 and SHA1 are not ideal for password storage, because they are designed to be fast, meaning an attacker can quickly crack even strong salted passwords. Modern GPUs can achieve 5 billion MD5 hashes per second. In fact, there are people with dedicated hash cracking rigs, some of which can crack MD5 at 45 billion hashes per second.
You should also take a look at these awesome questions, which completely cover SQL injection attacks, password storage, and a multitude of other security issues:
The definitive guide to form-based website authentication
How can I prevent SQL injection in PHP?
Secure hash and salt for PHP passwords
Update: You mentioned you're using postgres. You can use PDO to run parameterised queries from PHP, as described briefly here.
Neither of these options is a best practice, if only for the dubious anti_injection which is almost certainly not working as advertised. There's also the matters of:
munging your input data before validating them
storing plaintext passwords
constructing SQL queries manually instead of using bound parameters
Depending on the scope of the project the last one might be acceptable.
Regarding performance, the first option is going to be theoretically faster because it does less array indexing. But the difference would definitely be so small as to not be observable at all. The first option is also more readable and provides better abstraction, all for just a negligible amount of extra memory.
Both are wrong. You appear to be storing the password as plain text, which is just asking for trouble.
Also, if my username is "123456", I would be unable to log in because you would escape it to \"123456\" and that would fail the "length <= 8" check.
I would suggest using PDO if you want security and performance. You cannot sql inject when using parameters. Below is an example, you need to "define" the mysql params for the below to work.
This also allows for the database to cache your query, since it won't change every time you execute with a different parameter which will increase performance as well.
:p_user and :p_pass
is used to denote the parameters and
array ( ':p_user' => $user, ':p_pass' => $pass ' )
sets the parameters to the values you need to pass in.
You should also consider adding a password salt, and storing the sha1 of that in the databased, so that if your database is compromised, your passwords are not clearly revealed to the hacker.
class users{
function __construct() {
define('MySQLHost', 'localhost');
define('MySQLName', 'databasename');
define('MySQLUser', 'username');
define('MySQLPass', 'password');
define('pwsalt', 'tScgpBOoRL7A48TzpBGUgpKINc69B4Ylpvc5Xc6k'); //random characters
}
static function GetQuery($query, $params)
{
$db = new PDO('pgsql:host='.MySQLHost.';dbname='.MySQLName.';', MySQLUser, MySQLPass);
$cmd = $db->prepare($query);
$cmd->execute($params);
if($cmd->rowCount()==0)
return null;
return $cmd->fetchAll();
}
static function GetUser($user, $pass)
{
$query = "select id
from `users`
where username = :p_user and password = :p_pass";
$rows = users::GetQuery($query, array(
':p_user' => $user,
':p_pass' => sha1($pass.pwsalt) //Append the salt to the password, hash it
));
return $rows;
}
}
$user = $_POST['user'];
$pass = $_POST['pass'];
if( strlen($user) <= 8 && strlen($pass) <= 12 )
{
$result = users::GetUser($user, $pass);
if($result != null)
print 'Login Found';
}
While I agree with the other posters, don't try to "reinvent the wheel" regarding SQL security, the question seems to be about performance and how to use the superglobals.
As best practice, do NOT mutate the superglobals ($_GET, $_POST, $_REQUEST, $_SYSTEM, etc.) I can't think of a single example where violating this rule would improve your performance, and any modification will cause uncertainty and confusion down the road.
So in this case, neither option is correct. Option1 copies a variable needlessly (a no-no according to the Google performance docs). Option 2 mutates the superglobals, which violates the maxim above. Instead do something like:
$user = anti_injection( $_POST['user'] );
$pass = anti_injection( $_POST['pass'] );
if( strlen($user) <= 8 && strlen($pass) <= 12 ) ...
However I should reiterate that "homemade sanitation" is a frightening prospect, the other commentors have elaborated quite thoroughly on this point.

Confused about return statement in user defined function

i have few question regarding the return statement.
a) is it compulsory to define a return statement in a user defined function.?
b) is it still valid if i just define a return statement without any parameter? will it return the null value?
c) is the following function valid?
function admin_credential($password = 0, $email = 0) {
if( $password != 0) {
$password = sha1($password);
$query = "UPDATE admins SET password = '$password'";
$result = mysql_query($query);
}
if( $email != 0) {
$query = "UPDATE admins SET email = '$email'";
$result = mysql_query($query);
}
return;
}
a) is it compulsory to define a return statement in a user defined function.?
No.
b) is it still valid if i just define a return statement without any parameter? will it return the null value?
Yes.
c) is the following function valid?
Yes, but $result will be lost because you are not returning it. The return is not really necessary.
a) is it compulsory to define a return statement in a user defined function.?
No. At times you write functions which do not return anything to the called function, say a function to print a multidimensional array in a pretty way.
b) is it still valid if i just define a return statement without any parameter? will it return the null value?
Yes. Omitting return is same as return without any parameter and both return NULL.
c) is the following function valid?
It is syntactically valid. But it would be more meaningful if you return a boolean value to mark the success/failure of the query. So that the caller knows if the DB update went through fine or not.
EDIT:
"UPDATE admins SET password = '$password'"
The query is missing a WHERE clause. So it effectively updated the password of all users in the admins table.
a) you do not NEED to return a value at the end of a function in PHP. This is roughly equivalent to C's void function.
b) a return of no value is valid, but it can be confusing to other people looking at your code later, so it's a bad idea to make that your standard practice. Consider returning NULL instead, which will have the same effect.
c) Yes, your function uses valid syntax.
When you look at PHP tables for user authentication there are many things to consider. You have to mull over how you want to store the data in your tables and how to retrieve it. This means you need to consider how many users are going to have access, and how many user types. If you have a handful of users like even less than 10, I recommend not bothering with database user authentication.
The questions you posed have already been answered effectively but I would urge you to try and avoid complicating your system with a lot of unnecessary features: keep it simple.

php authentication script

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.

How dangerous is this PHP code?

How dangerous is this php code? What can be done about it?
$name = $_POST["user"];
$pwd = $_POST["pwd"];
$query = "SELECT name,pwd FROM users WHERE name = '$name' AND pwd = '$pwd'";
Possible Problems:
SQL Injection
XSS Injection (if this code was an insert query, it would be a definite problem)
Plain Text Password
Your SQL Statement can be problematic. It is bad practice to leave yourself open for SQL injection.
SQL Injection is bad. Trust me.
If you want to display the $user on an HTML page, then you may not want to include the ability for people to "hack" your layout by typing in commands like
<H1>HI MOM</H1>
or a bunch of javascript.
Also, never store your password in plain text (good catch cagcowboy!). It gives too much power to people administering (or hacking) your database. You should never NEED to know someone's password.
Try tactics like these:
// mostly pulled from http://snippets.dzone.com/posts/show/2738
function MakeSafe($unsafestring)
{
$unsafestring= htmlentities($unsafestring, ENT_QUOTES);
if (get_magic_quotes_gpc())
{
$unsafestring= stripslashes($unsafestring);
}
$unsafestring= mysql_real_escape_string(trim($unsafestring));
$unsafestring= strip_tags($unsafestring);
$unsafestring= str_replace("\r\n", "", $unsafestring);
return $unsafestring;
}
// Call a function to make sure the variables you are
// pulling in are not able to inject sql into your
// sql statement causing massive doom and destruction.
$name = MakeSafe( $_POST["user"] );
$pwd = MakeSafe( $_POST["pwd"] );
// As suggested by cagcowboy:
// You should NEVER store passwords decrypted.
// Ever.
// sha1 creates a hash of your password
// pack helps to shrink your hash
// base64_encode turns it into base64
$pwd = base64_encode(pack("H*",sha1($pwd)))
It's this dangerous:
SQL Injection aside, it looks like your passwords might be stored in plain text, which isn't great.
That code is very safe if you never pass $query to a SQL database.
If one were to post 0';drop table users;-- for a name
your command would end up being
select name, pwd form users where name='0';
drop table users; --'and pwd = '[VALUE OF PWD]'
So first it would get your data, then kill your users table, and do nothing with the rest since it is a comment.
Certain mysql commands in php will perform multiple queries when passed sql, the best way to avoid this is parametrized queries.
I use PDO for all my DB access, and highly recommend it. I do not have any links off the top of my head but I remember the tutorials I used topped Google.
It is not only prone to SQL injections, it will also fail in cases where an injection is not even intended:
For example a user wants the name "Guillaume François Antoine, Marquis de L’Hospital". Since the username contains a quote and you are not escaping it, your query will fail, although the user never wanted to break the system!
Either use PDO or do it in this way:
$query = sprintf(
"SELECT 1 FROM users WHERE name = '%s' AND password = '%s'",
mysql_real_escape_string($_POST['name']),
mysql_real_escape_string(md5($_POST['password']))
);
Believe it or not, this is safe... if magic_quotes_gpc is turned on. Which it will never be in PHP6, so fixing it prior to then is a good idea.
$_POST['user'] = "' or 1=1; --";
Anyone gets instant access to your app
$_POST['user'] = "'; DROP TABLE user; --";
Kiss your (paid?) user list goodbye
If you later echo $name in your output, that can result in a XSS injection attack
:O don't do it never ever,
This can cause SQLInjection attack. If for example user input somehow:
' drop table users --
as input in $username; this code will concatinate to your orginal code and will drop your table. The hackers can do more and can hack your website.
This is typically very dangerous. It could be mitigated by database permissions in some cases.
You don't validate the input ($name and $pwd). A user could send in SQL in one or both of these fields. The SQL could delete or modify other data in your database.
Very very dangerous. A good idea for passwords is to convert the password into a MD5 hash and store that as the user's 'password'.
1) protects the users from having their passwords stolen
2) if a user writes a malicious string they could wipe out your entry/table/database
Also you should do some basic match regex expression on the name to make sure it only uses A-Za-z0-9 and maybe a few accented characters (no special characters, *'s, <'s, >'s in particular).
When user data is involed in a SQL query, always sanatize the data with mysql_real_escape_string.
Furthermore, you should store just a salted hash of the password instead of the password itself. You can use the following function to generate and check a salted hash with a random salt value:
function saltedHash($data, $hash=null)
{
if (is_null($hash)) {
$salt = substr(md5(uniqid(rand())), 0, 8);
} else {
$salt = substr($hash, 0, 8);
}
$h = $salt.md5($salt.$data);
if (!is_null($hash)) {
return $h === $hash;
}
return $h;
}
All together:
$query = 'SELECT pwd FROM users WHERE name = "'.mysql_real_escape_string($_POST['user']).'"';
$res = mysql_query($query);
if (mysql_num_rows($res)) {
$row = mysql_fetch_assoc($res);
if (saltedHash($_POST["pwd"], $row['pwd'])) {
// authentic
} else {
// incorrect password
}
} else {
// incorrect username
}
Its not safe, you might want to look into something like PDO.
PHP PDO

Should I mysql_real_escape_string all the cookies I get from the user to avoid mysql injection in php?

When a user goes to my site, my script checks for 2 cookies which store the user id + part of the password, to automatically log them in.
It's possible to edit the contents of cookies via a cookie editor, so I guess it's possible to add some malicious content to a written cookie?
Should I add mysql_real_escape_string (or something else) to all my cookie calls or is there some kind of built in procedure that will not allow this to happen?
What you really need to do is not send these cookie values that are hackable in the first place. Instead, why not hash the username and password and a (secret) salt and set that as the cookie value? i.e.:
define('COOKIE_SALT', 'secretblahblahlkdsfklj');
$cookie_value = sha1($username.$password.COOKIE_SALT);
Then you know the cookie value is always going to be a 40-character hexidecimal string, and can compare the value the user sends back with whatever's in the database to decide whether they're valid or not:
if ($user_cookie_value == sha1($username_from_db.$password_drom_db.COOKIE_SALT)) {
# valid
} else {
#not valid
}
mysql_real_escape_string makes an additional hit to the database, BTW (a lot of people don't realize it requires a DB connection and queries MySQL).
The best way to do what you want if you can't change your app and insist on using hackable cookie values is to use prepared statements with bound parameters.
The point of mysql_real_escape_string isn't to protect against injection attacks, it's to ensure your data is accurately stored in the database. Thus, it should be called on ANY string going into the database, regardless of its source.
You should, however, also be using parameterized queries (via mysqli or PDO) to protect yourself from SQL injection. Otherwise you risk ending up like little Bobby Tables' school.
I only use mysql_real_escape_string before inserting variables into an SQL statement. You'll just get yourself confused if some of your variables are already escaped, and then you escape them again. It's a classic bug you see in newbies' blog webapps:
When someone writes an apostrophe it keeps on adding slashes ruining the blog\\\\\\\'s pages.
The value of a variable isn't dangerous by itself: it's only when you put it into a string or something similar that you start straying into dangerous waters.
Of course though, never trust anything that comes from the client-side.
Prepared statements and parameter binding is always a good way to go.
PEAR::MDB2 supports prepared statements, for example:
$db = MDB2::factory( $dsn );
$types = array( 'integer', 'text' );
$sth = $db->prepare( "INSERT INTO table (ID,Text) (?,?)", $types );
if( PEAR::isError( $sth ) ) die( $sth->getMessage() );
$data = array( 5, 'some text' );
$result = $sth->execute( $data );
$sth->free();
if( PEAR::isError( $result ) ) die( $result->getMessage() );
This will only allow proper data and pre-set amount of variables to get into database.
You of course should validate data before getting this far, but preparing statements is the final validation that should be done.
You should mysql_real_escape_string anything that could be potentially harmful. Never trust any type of input that can be altered by the user.
I agree with you. It is possible to modify the cookies and send in malicious data.
I believe that it is good practice to filter the values you get from the cookies before you use them. As a rule of thumb I do filter any other input that may be tampered with.
Yegor, you can store the hash when a user account is created/updated, then whenever a login is initiated, you hash the data posted to the server and compare against what was stored in the database for that one username.
(Off the top of my head in loose php - treat as pseudo code):
$usernameFromPostDbsafe = LimitToAlphaNumUnderscore($usernameFromPost);
$result = Query("SELECT hash FROM userTable WHERE username='$usernameFromPostDbsafe' LIMIT 1;");
$hashFromDb = $result['hash'];
if( (sha1($usernameFromPost.$passwordFromPost.SALT)) == $hashFromDb ){
//Auth Success
}else{
//Auth Failure
}
After a successful authentication, you could store the hash in $_SESSION or in a database table of cached authenticated username/hashes. Then send the hash back to the browser (in a cookie for instance) so subsequent page loads send the hash back to the server to be compared against the hash held in your chosen session storage.
mysql_real_escape_string is so passé... These days you should really use parameter binding instead.
I'll elaborate by mentionning that i was referring to prepared statements and provide a link to an article that demonstrates that sometimes mysl_real_escape_string isn't sufficient enough: http://www.webappsec.org/projects/articles/091007.txt
I would recommend using htmlentities($input, ENT_QUOTES) instead of mysql_real_escape_string as this will also prevent any accidental outputting of actual HTML code. Of course, you could use mysql_real_escape_string and htmlentities, but why would you?

Categories