0

Say I have this code for authentication.

   $me = mysql_query("SELECT * from users WHERE id='$_COOKIE[userid]' && password ='$_COOKIE[pass]'") or die (mysql_error());
   $me = mysql_fetch_array($me);

To authenticate a user to a website, something on the user's computer has to match what's on the server (this question is not about preventing cookie stealing as all websites can have cookies stolen regardless), but is there a way to do use sql where to check if it matches in the database without it being prone to a timing attack?

What if someone modifies their cookies to a different userid and password to try to find out someone else's password using a timing attack? If $me produces 0 results instead of 1 result, does that change the average time it takes to run the query?

If yes, how should I change my code, because I can't figure it out? Should I add a random delay to the code above, and does it make a difference as I read it wouldn't.

  • 1
    This thing that you posted has far worse issues than timing attaks: ist a straight away injection vulnerability. Read about prepared statements – marstato Jun 18 '17 at 17:21
  • mysqli_real_escape_string or php prepared statements does not fully protect against sql injections. I can find 5 ways to bypass prepared statements online. The best protection against sql injections is a firewall, and I use mod_security and Comodo Firewall. – desbest Jun 18 '17 at 17:27
  • Five, you say? Go earn a lot of money then because all the infosec people i know, know only one loophole that is unlikely to ever occur (obscure encoding for php and db server). – marstato Jun 18 '17 at 17:31
  • Anyways: php has its flaws but sure is more secure than that plain concatenation you are doing. Where do you host this? – marstato Jun 18 '17 at 17:32
  • 1
    "The best protection against sql injections is a firewall, and I use mod_security and Comodo Firewall." A firewall will do nothing to protect your from SQL-injection. Don't stick your head in the sand, use prepared statements. – Jacco Jun 18 '17 at 18:15
1

Dont worry about a timing attack. The difference between 0 und 1 row is miniscule.

However, there are a number of other issues with the Code you have posted:

  • Dont allow an attacker to send enough requests so that they could exploit a timing difference here. Block out people after a small number auf failed authentication attempts
  • $_COOKIE is entirely attacker Controlled. You are exposing a SQL injection vulnerability here that any semi-professional pentester will be able to exploit in a matter of minutes
  • Do not EVER store the password in plaintext, anywhere. Dont do it in your DB and for sure dont store it in the Cookie where it can easily become visible to the public. Instead, store a permlogin token for each user that is entirely random (see CSPRNG) and large (256bit or more). Use that for cookie-based authentication
  • Use a salt for your passwords. Select the user record by id and then compare the passwords. Doing so will also decrease the surface for a timing attack
  • I have brute force protection on my authentication/website so there can only be 50 failed logins for an account per day, if it gets to 50, the user can't login for the rest of the day. I store passwords with bcrypt so the password is by default salted and bcrypt cannot be broken. To authenticate a user to a website, something on the user's computer has to match what's on the server, so ALL cookies are attacker controlled as anyone can steal cookies from someone's computer to login as someone else. I don't store plaintext passwords, yet the bcrypt password is in my database. – desbest Jun 18 '17 at 17:36
  • If I store the permlogin token in the database and as a cookie, what if someone steals the permlogin cookie? It doesn't make a difference if I store a permlogin token or a password. Also someone can do a timing attack on the permlogin cookie, which is what my question is asking about. – desbest Jun 18 '17 at 17:37
  • Also, should I have a random millisecond delay when validating the cookies? I think it's a good idea, but Stack Overflow said it was a bad idea. – desbest Jun 18 '17 at 17:43
  • 1
    @desbest, Only 50 failed logins per account per day you say? Well, that sounds like a perfect denial of service attack vector. Also, most attackers try a set of the most common passwords, against a wide set of usernames. So, limiting your failed logins per account, won't add that much protection, while opening up another attack vector. – Jacco Jun 18 '17 at 18:18
  • 1
    @desbest: One last thing; if you still want to discuss, lets move to chat then. The time it takes to brute-force any hash depends on the entropy of the plaintext. If a users uses a low entropy password (such as p4ssw0rd) your bcrypt hash wont stand a chance against a determined attack (see dictionary attacks). A truly random 256bit permlogin token has an entropy of 256 bits. Thats hard to reach with a password, even with generated ones. – marstato Jun 18 '17 at 19:33
0

If you wanted to be sure then you could do

`SELECT * FROM Users`

and then enumerate the entire list in code checking for the password against the entire list. That would be near enough constant time.

My PHP is a bit rusty but something like

$authUser = NULL;
$users = mysql_query("SELECT * from users");
$users = mysql_fetch_array($users);
foreach($users as $user){
    if ($user['id'] = $_COOKIE[userid]' && $user['password']='$_COOKIE[pass]'){
        $authUser = $user;
    }
}

if(is_null($authUser))
     // whatever

However I still think it is a mistake to store the actual password hash in a cookie. It means that it is impossible to revoke a session without changing the password. Any type of session identifier even with a 100 year expiration still allows clearing sessions or changing the expiration without password change

  • Sorry but I can't cycle through all my users in order to authenticate a user, as it will make my code slow. It isn't designed for growth. If I have 40,000 users, cycling through all the users on every webpage load will slow down the website. – desbest Jun 18 '17 at 20:33
  • If you use a token you only need to do it on logon. Generally speaking verifying the hash is a lot slower and to properly avoid a timing attack on logon you should ensure that you spend the same amount of time checking the hash whether or not the user exists – ste-fu Jun 18 '17 at 22:54
  • How do I only need to do it on login? When the user navigates to a members only page, I need to verify the hash to see if they have permission to view the page. I need to verify the hash on every page. – desbest Jun 19 '17 at 9:04
  • If you use a token ie if you store a single token eg a uuid generated in logon.This can be stored in a table. Existence of the token does not leak an actual user id, and the table can be cleared at suitable intervals without requiring a password change. – ste-fu Jun 19 '17 at 9:56
  • The only way to securely use a single token to identify a user is if the token is HMAC'd before sending it to the user, and the HMAC is verified before allowing a query against the database. This prevents a timing attack by only allowing the user to query using an identifier than is provably correct (the attacker cant get perform queries to extract timing information). Letting a user authenticate with a single-token where that token hasn't been verified before doing the query opens up the system to a timing attack and is insecure. – phayes Aug 4 at 17:06
-1

The correct way to do this is to break the identifying information in half. The first half is what you query against, the second half is the secret that is encrypted with bcrypt (or pbkdf2, scrypt or equivalent).

The key thing to understand is that it's not possible to make a query constant-time in most databases, so you need to work around that fact.

For example, let's say you wanted to create a secure invite-code, where a user can use an invite code to sign-up and create an account, you might structure your invite database table like so:

CREATE TABLE invites (
  id SERIAL,
  token_first_half VARCHAR,
  token_second_half VARCHAR CHECK (pass like '$2$12$%'), -- Must be bcrypt version 2 with strength of 12
)

The user would be given a invite code like XY0F-CD37-HZ5J-KL6P, where XY0F-CD37 is token_first_half and HZ5J-KL6P is token_second_half. You would probably want to use human-readable base32 (https://www.crockford.com/base32.html) to encode these tokens.

When the user provides a token, you would break it in half, and use the first half to query against the database, then verify it by checking the second half with bcrypt (https://en.wikipedia.org/wiki/Bcrypt). Bcrypt will perform this operation in constant-time, which avoids timing attacks.

The important insight here is that the first-half is open to a timing attack, but that is irrelevant, since it's only used a selector to access the bcrypt string, which is what we are actually using.

For your particular example, the first-half would be the username, and the second half would be the password.

If you can't break the token in half for some reason, or can't use bcrypt for performance reasons (for example for handling cookies / session), then you need to prevalidate the secret token using an HMAC. Most web-frameworks have a "secure-cookie" functionality that can be used to get and set HMAC-secured cookies for session management. Only after the HMAC has been validated do you do the database query. This prevents a timing attack by denying the attacker enough queries to obtain timing information.

  • The key is that a bcrypt operation is constant-time. The first-half of the token is open to a timing-attack, but that's OK because the information is useless without the second half. – phayes Aug 4 at 16:43
  • I've updated the comment to better explain that how the first-half (open to a timing attack) and the second half (not open to a timing attack) work together to make the entire system secure. I'm finding the downvotes baffling since this is the correct answer. – phayes Aug 4 at 16:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.