1

Here is a pretty short, simple and straightforward code I wrote to use for my site to log users in. I was hoping someone could check it out and tell me whether there's anything wrong with it what could use some improvement.

Thanks in advance!

session_start();

/* connect to the db */
define("INCLUDED-PUBLIC", true);
include('dbConnection.php');   

/* define safe variables */
$login = mysqli_real_escape_string($connection, $_POST['login-email']);
$pass = md5($_POST['login-pass']);

/* send the query */
$query = mysqli_query($connection, "
  SELECT `user_id`
    FROM `users`
   WHERE `user_contact_email`='$login'
     AND `user_password`='$pass'
   LIMIT 1
");

/* does such account exist? */
$count = mysqli_num_rows($query);

if ($count > 0){

   /* user exists, loggin' in! */
   $data = mysqli_fetch_array($query);
   $userID = $data['user_id'];
   $_SESSION['user']['user_id'] = $userID;

}
OMG Ponies
  • 325,700
  • 82
  • 523
  • 502
Frantisek
  • 7,485
  • 15
  • 59
  • 102
  • unless your trying to protect a bank account it should be adequate –  Mar 26 '11 at 04:26
  • Why use `LIMIT 1`? That would allow duplicates to get through, for what should only return one row anyways -- entirely useless. – OMG Ponies Mar 26 '11 at 04:28
  • I get your point. I thought that with LIMIT 1, the query stops being executed when **at least one** = the only one result is found and it doesn't try to look further in the table? I thought it simply saves time, tells the sql that I'm expecting only 1 result, so it can stop when it finds it? Am I wrong here? – Frantisek Mar 26 '11 at 04:30
  • 3
    if only wanting one row using limit 1 speeds up the query by stopping on the first match, generally recommended –  Mar 26 '11 at 04:30
  • Cool, thanks for making me sure, Dagon! – Frantisek Mar 26 '11 at 04:32
  • 2
    @Dagon Mysql is not that dumb. If table is properly indexed, the search will be stopped anyway. And darn **indexing** IS the thing that speeds up the search, not some lame tricks – Your Common Sense Mar 26 '11 at 06:40
  • I wonder, why noone mentioned prepared statements yet – Your Common Sense Mar 26 '11 at 06:44
  • please use salts for your passwords (or bcrypt) – knittl Mar 26 '11 at 08:19
  • some notes on the use of limit 1: http://stackoverflow.com/questions/455476/does-adding-limit-1-to-mysql-queries-make-them-faster-when-you-know-there-will –  Mar 26 '11 at 08:56
  • 1
    @Dagon learn indexes, dude. For such case an index should be used, not lame LIMIT trick – Your Common Sense Mar 26 '11 at 10:24
  • @ Col. Shrapnel if you don't want a unique user-name, then an index will not help, the search will continue to the end of the table. So the use of LIMIT is the *proper* approach. –  Mar 27 '11 at 03:35

4 Answers4

2

Actually, there is quite some room for improvement.

First: You should add a random salt to the hashing algorithm as to protect against pre-computation attacks. (search for rainbow table)

Second: you should fetch the user_password value from the DB and do the comparison on the PHP side.

Important

Statements that invoke PASSWORD() may be recorded in server logs or in a history file such as ~/.mysql_history, which means that passwords may be read by anyone having read access to that information. See Section 5.3.2, “Password Security in MySQL”.

Source: http://dev.mysql.com/doc/refman/5.1/en/encryption-functions.html#function_password

Third: Do not use MD5 it's a nice hashing algorithm but considered broken for security purposes. If you use PHP 5.3 or later, use crypt, with CRYPT_BLOWFISH.
CRYPT_BLOWFISH in PHP is an implementation of the Bcrypt hash. Bcrypt is based on the Blowfish block cipher, making use of it's expensive key setup to slow the algorithm down.

Also: don't use LIMIT in your query, instead put a UNIQUE constraint on the user_contact_email. Using LIMIT is a trick that could mask duplicate user-emails and cause unexpected results when a duplicate email somehow gets entered.

And finally: Why don't you use a standard library.
Security tends to be a lot more complicated and with more invisible screw up possibilities than most programmers could tackle alone, using a standard library is almost always easiest and most (if not the only) secure option available. (also read: Help me make my password storage safe)

Community
  • 1
  • 1
Jacco
  • 23,534
  • 17
  • 88
  • 105
1

The only thing i would change would be:

$pass = md5($_POST['login-pass']);

To something like:

$pass = hash('whirlpool', $_POST['login-pass']);

And also change how it is stored in the database on registration.

Other than that, its all good.

Brent
  • 155
  • 2
  • 6
  • Could you please clarify why your solution is better and how exactly? I'm not saying it isn't, I just want to know the details. Thanks! – Frantisek Mar 26 '11 at 05:17
  • 2
    @rimmer, md5 is [considered broken](http://en.wikipedia.org/wiki/MD5#Security). sha1 is also effectively broken thanks to the new generation of powerful GPUs. You'll want to use a more effective hashing mechanism, **[and a salt](http://en.wikipedia.org/wiki/Salt_\(cryptography\))**. – Charles Mar 26 '11 at 05:25
  • I believe it is better because MD5 is not as secure as whirlpool for hashing. You could alternatively use sha256, ripemd160, etc. You can read more about it at [wikipedia](http://en.wikipedia.org/wiki/MD5) **Edit:** Yea, and a salt like Charles said. – Brent Mar 26 '11 at 05:30
  • @Charles nothing is really broken in terms of password security. The only things you need is some salt and **password strongness**. The latter is the only thing that really matters in practice, not in theory (ans always forgotten by everyone), while with weak password no super-hash nor extra-salt will help. – Your Common Sense Mar 26 '11 at 06:32
  • 1
    @Col, password strength is irrelevant if it's possible to whip up a collision with the hashed password in an economical fashion. TBH, the entire password discussion is silly. There are so many factors that can screw something up that we constantly have to step all over ourselves just to try and explain the whole picture: If someone manages to grab your user database to begin with, you're screwed ten ways from sunday *no matter what password scheme you use*! (Even if you have the best password scheme in the world, you've lost the trust of your users, and I'd argue *that* is more valuable.) – Charles Mar 26 '11 at 06:47
  • (Of course, I'm not saying that we shouldn't protect the passwords of our users with what we consider an adequate and appropriate level of protection. The sillyness comes from the "which is the best algo" discussion and how utterly irrelevant it is in the face of the larger threat of a data leak.) – Charles Mar 26 '11 at 06:51
  • @Charles there's the only one problem. All your blab is theoretical one but you never put your hands on the matter. What if you've found a collision for the salted password hash? How you gonna use it? (lets put aside possibility of such collision) – Your Common Sense Mar 26 '11 at 07:08
  • Password strength IS relevant because with weak password none of your romantic things like 10435734-bit salt, extra-slow-never-colliding hashing algorithm would ever help. – Your Common Sense Mar 26 '11 at 07:14
  • @Col, the hash strength (and the time it takes to generate it) *does* provide an amount of protection when the password is weak -- it just happens to be *less* protection than you'd also see when the password is strong. The protection is time (== money), but little else. But as you pointed out, the entire discussion is unimportant unless you *expect* your data to leak. A password hash that isn't made available to someone that wants to crack it is uncrackable, even if the hash is rot13. – Charles Mar 26 '11 at 07:22
  • @Charles again you're thinking of it from the theoretical point of view, not practical one. Not less protection but no protection at all. several thousands iterations from john the ripper database is a matter of several seconds. – Your Common Sense Mar 26 '11 at 07:26
  • @Col, that concept fails when each hash takes a half second to generate (single thread) on today's fastest hardware. It's still *doable*, but it means more time, more hardware, more manpower, more money. And the wrong strategy if the attacker was smart -- it'd be more economical to brute force the login through the live system, and *that* is where weak passwords are the #1 threat. Anyway, I'm off, g'night. – Charles Mar 26 '11 at 07:33
1

Things to changes

  • include shoud change into require_once
  • check that password and email are not empty
  • you also check for valid email but invalid password
  • free the result set after storing into session
  • close the mysqli connection
  • if user/email is invalid than send back

my way:

session_start();



require_once('dbConnection.php');
/* connect to the db */
define("INCLUDED-PUBLIC", true);


/* define safe variables */
if(!empty($_POST['login-pass']) && !empty($_POST['login-email']) )
{
    $login = mysqli_real_escape_string($connection, $_POST['login-email']);
    $pass = md5($_POST['login-pass']);

    /* send the query */
    $query = mysqli_query($connection, "
      SELECT `user_id`
        FROM `users`
       WHERE `user_contact_email`='$login'
            AND `user_password`='$pass'
        LIMIT 1
    ");

    /* does such account exist? */
    $count = mysqli_num_rows($query);

    if ($count > 0){
       /* user exists, loggin' in! */
       $data = mysqli_fetch_array($query);
       $_SESSION['user']['user_id'] = $data['user_id'];        
       /* free result set */
       mysqli_free_result($query);
       mysqli_close($connection);
    }
    else {
            header("location:login.php");
            exit();
    }

}
xkeshav
  • 53,360
  • 44
  • 177
  • 245
  • From PHP manual, "Using mysql_close() isn't usually necessary, as non-persistent open links are automatically closed at the end of the script's execution." So this doesn't apply? – Frantisek Mar 26 '11 at 05:33
  • Also, "mysql_free_result() only needs to be called if you are concerned about how much memory is being used for queries that return large result sets. All associated result memory is automatically freed at the end of the script's execution." – Frantisek Mar 26 '11 at 05:34
  • @RiMMER if `mysqli_close()` is not necessary then why this function exists?? same thing for `mysqli_free_result()`. please refer php Manual regadring that – xkeshav Mar 26 '11 at 05:38
  • may be for some reason inapplicable in this case? – Your Common Sense Mar 26 '11 at 06:46
  • Upvoted for good style -- checking variables and freeing up memory. But I most of all agree with Jacco's answer: "Why don't you use a standard library", and other answers on this page that deal with use of alternatives to md5, and use of SQL placeholders. – Luke H Jun 14 '12 at 11:39
1

In general your code is okay, only minor improvements can be made.

  • counting rows is unnecessary, fetching array will do the same with shorter code.
  • some error handling is required. You have to check query result and raise an error if it fails
  • code structure and readability.
    Writing readable code lets you omit obvious comments. include('dbConnection.php'); is self-explanatory, isn't it?
    $query variable in your code doesn't contain query, but rather query result. So, you're obfuscating your own code, making it less readable. Always use sensible names, it will save you useless comments.

like this

define("INCLUDED-PUBLIC", true);
include('dbConnection.php');   
$login = mysqli_real_escape_string($connection, $_POST['login-email']);
$pass  = md5($_POST['login-pass']);

$query = "SELECT `user_id` FROM `users` WHERE `user_contact_email`='$login'
                                               AND `user_password`='$pass'";

$result = mysqli_query($connection, $query);
if (!$result) trigger_error(mysqli_error($connection).$query);

if ($data = mysqli_fetch_array($query)){
   session_start();
   $_SESSION['user']['user_id'] = $data['user_id'];
}

However, there are some conceptual improvements can be made

  • It's always preferred to use placeholders to insert data into query.
  • password salting. that's long and obscure story, everyone gets impressed with it and almost noone understands the matter. But it can be boiled down to just simple thing:
    have site specific salt, defined in your dbconnect file, and salt your password with it and user's email. It won't help if the password is weak, but will help if passwords is strong.
  • Having some library to ease database calls is a must:

compare your code to this one:

define("INCLUDED-PUBLIC", true);
include('dbConnection.php');   
$pass  = md5(SITE_SALT.$_POST['login-email'].$_POST['login-pass']);
$query = "SELECT `user_id` FROM `users` WHERE `user_contact_email`=? AND `user_password`=?";
$data  = dbgetone($query, $_POST['login-email'],$pass);
if ($data){
   session_start();
   $_SESSION['user']['user_id'] = $data['user_id'];
}
Your Common Sense
  • 156,878
  • 40
  • 214
  • 345