views:

53

answers:

2

I am in the process of moving from a "username/password" system to one that uses email for login. I don't think that there's any horrible problem with allowing either email or username for login, and I remember seeing sites that I consider somewhat respectable doing it as well, but I'd like to be aware of any major security flaws that I may be introducing.

More specifically, here is the pertinent function (the query_row function parameterizes the sql).

function authenticate($p_user, $p_pass) {
    $user        = (string)$p_user;
    $pass        = (string)$p_pass;
    $returnValue = false;

    if ($user != '' && $pass != '') {
        // Allow login via username or email.
        $sql = "SELECT account_id, account_identity, uname, player_id 
                FROM accounts 
                JOIN account_players ON account_id=_account_id 
                JOIN players ON player_id = _player_id 
                WHERE lower(account_identity) = lower(:login) 
                      OR lower(uname) = lower(:login) 
                      AND phash = crypt(:pass, phash)";
        $returnValue = query_row($sql, array(':login'=>$user, ':pass'=>$pass));

    }
    return $returnValue;
}

Notably, I have added the WHERE lower(account_identity) = lower(:login) OR lower(uname) = lower(:login) ...etc section to allow graceful backwards compatibility for users who won't be used to using their email for the login procedure. I'm not completely sure that that OR is safe, though. Are there some ways that I should tighten the security of the php code above?

A: 

Well, I don't see any particular harm in doing both email/username pairs. At the end of the day, using the email as login is just like having a different username, nothing more nothing less. Just ask the users to provide a screen name, you don't want to show their email if they login with that.

As for your code, you should really do a mysql_real_escape_string or similar on $user to avoid SQL injection (unless the query_row function does that for you). You can also escape $pass although no injection is possible there, as it's going to be hashed.

As far as I know the OR statement does not pose any particular harm.

nico
No, he shouldn't as he obviously uses a parametrized query
Col. Shrapnel
@Col. Shrapnel: That's why I wrote *unless the query_row function does that for you*. Or do you mean something else?
nico
parametrized queries do not escape anything
Col. Shrapnel
@Col. Shrapnel: I'm sorry I still don't understand. I never used parametrised queries can you be more explicit?
nico
parametrized queries just separate data from the query, and send it to the database separately. so, data never being added to the query string and therefore don't need to be escaped.
Col. Shrapnel
More to say, escaping works only when combined with quotes. W/o quotes it is useless. So, you either to say "properly formatted" or add quoting to the escaping and some other rules too, like type casting for the numbers.
Col. Shrapnel
I'm sorry but he's not querying the DB with those parameter, he is using a custom `query_row` function and we don't really know what's going on in there. As far as I know it may just be substituting `:user` with the `$user` parameter (putting quotes around it) with a regexp without escaping anything. So I think my point is still valid.
nico
I was using query_row to parameterize the sql inputs, yeah.
Tchalvak
+1  A: 

Well, after formatting your query, it become clear to me that your OR is unsafe.
Make it

WHERE (lower(account_identity) = lower(:login) OR lower(uname) = lower(:login)) 
      AND phash = crypt(:pass, phash)";

Note the parenthesis around OR clause.

Col. Shrapnel
Ah, thanks for the catch, I thought that I was making it too easy on myself.
Tchalvak