What is the possibility of finding a particular 192-character string in 10 minutes?

Asked

Viewed 156 times

4

I’m reinforcing an authentication system, where in the password recovery part, I implemented it as follows.

  1. The user informs the e-mail address.
  2. If it exists, the e-mail is sent (see example below).
  3. The user clicks on the email and goes to the recovery page (through URL with code)
  4. The code is filled automatically in an Hidden field of the form (sanitization and exhaust ok).
  5. If the user enters the two correct passwords and the code is still valid, the password is changed.

The automatically generated email points to the link

"...../admin/session/reset/FYLPB557EKLGMPV6IYNOT2TF5MZUZM2ZB6O43PEUH5XOHCTBX42TLMLJZHSHZKF77V4ZEXELICSAOHDNTJJ4R2Z67R4JZ4ST72JOVWKK2BQCKRFQMQWJNFF2LQIHJ55C7FH5GO3LIMH4WEF3J6HOU4AQQVG3LVBKV7O3DDARB6IQVCGGUFI6RUC2QUYEIOHH"

admin is my namespace, session is the Controller, reset the method and code is the parameter.

To verify that the code is valid, the database returns the ID of the user who requested the reset, where the "secret" is identical to the code, that is, if it exists and if it has not expired (10-minute limit). Note that this ID is never sent to the customer. This way the bot or person who is trying to hack, will not know the user ID and there will be no risk of a possible "date tamper" for example.

The whole process uses the HTTPS procotol.

The string is randomly generated, can only contain characters of A-Z0-9 and has 192 characters, through the following function (being concatenated, once 128 and the second 32).

public function createSecretKey($secretLength = 128)
{
    $validChars = $this->_getBase32LookupTable();

    // Valid secret lengths are 80 to 640 bits
    if ($secretLength < 16 || $secretLength > 128) 
    {
        throw new Exception('Bad secret length');
    }
    $secret = '';
    $rnd = false;

    if (function_exists('random_bytes')) 
    {
        $rnd = random_bytes($secretLength);
    } 
    elseif (function_exists('mcrypt_create_iv')) 
    {
        $rnd = mcrypt_create_iv($secretLength, MCRYPT_DEV_URANDOM);
    } 
    elseif (function_exists('openssl_random_pseudo_bytes')) 
    {
        $rnd = openssl_random_pseudo_bytes($secretLength, $cryptoStrong);
        if (!$cryptoStrong) 
        {
            $rnd = false;
        }
    }
    if ($rnd !== false) 
    {
        for ($i = 0; $i < $secretLength; ++$i) {
            $secret .= $validChars[ord($rnd[$i]) & 31];
        }
    } 
    else 
    {
        throw new Exception('No source of secure random');
    }

    return $secret;
}

protected function _getBase32LookupTable()
{
    return array(
        'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', //  7
        'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 15
        'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 23
        'Y', 'Z', '2', '3', '4', '5', '6', '7', // 31
        '=',  // padding char
    );
}

The form to send the password reset request has 1 send limit every 10 minutes.

Considering the logic described, and that the system blocks the session and the IP every 10 incorrect attempts, and invalidates the code immediately when the user recovers the password or after 10 minutes. What would be the possibility of this URL being broken by some bot? Is there any weak point in this logic that can be broken? (not speaking at the level of code or vulnerabilities in programming, but in terms of logic).

  • 1

    Bots usually fill in all fields, including the ones that are Hidden, you can place an Hidden field and if it contains any value you cancel the token. You have an answer on that approach - very useful by the way.

  • What if two separate user password reset requests are made during this period? Taking only the last record of the table, one of them will be invalidated.

  • Sorry, I expressed myself a little poorly on this part. I edited the question. No problem two simultaneous requests from different users. 1 user can only request a new code every 10 minutes. The question of returning the last line was an additional check I had done but there was no need to describe it here as this was before adding the criterion of limiting 1 code every 10 minutes.

1 answer

5


Well, there are exactly 2 128 possible combinations, 340 cylinders. Making such a request in 10 minutes is somewhat complicated, virtually undetectable.


However there are some flaws, a little darker, to be mentioned, for the creation of the "combination":

  1. The ord and its array $validChars makes it vulnerable by a side-Channel attack, the timming-Attack.

    How long it takes for him to find the $validChars1[1] is different from the time that meets the $validChars1[32], the same occurs with internment with the ord. So it is possible that some process (very much) next to the server monitors this and can find out what was the generated key.

  2. Your email server is compromised (or the client’s).

    Once the message is passed in plain text a third party can see what has been written, including the URL, in this situation no matter if this address is HTTPS or HTTP.


For comparison (using the generated "key" there may be other problems:

  1. The database can deliver data based also "by the time".

    The time it takes the database to find depends on how close the value is. This indicates that, assuming a FYLPB557EKL, do a search for AAAAAAAAAAAAAA tends to return result faster (with 0 lines found) than searching for FYLPB557EKA. Doing this remotely is extremely complicated, I do not know if there is any proof of this concept. The point is he uses memcmp() when a bit is different it ignores everything else, simply A is different from F in the first letter the rest is not even compared. This is good because it makes the database fast but not secure.

  2. A read-only database failure would break the system.

    Assuming there is a fault in your system that allows someone to read the contents of the database (or where you store the generated information) would deliver all the information in plain text. This indicates that such a person would be able to change passwords of others once they have access to table data.


This does not indicate that other problems do not exist!

The ideal would be to have two separate keys, so that:

  1. Client:
    • Receives Chave A + Chave B

Do you remember "convert" to Constant-time for a desired representation (Base64, HEX...), or use unpack in the latter case and use only the random_bytes.

  1. Your server:
    • Stores Chave A + HASH(Chave B)

It would be stored in your server, in the database, the HASH (could be SHA2, SHA3, BLAKE, or even Bcrypt/Argon2i/Scrypt....), as well as can be an HMAC. Some may believe that being a number generated by CSPRNG would be an "expense" using Bcrypt/Argon2i/Scrypt for this, but use what you prefer.

When the user accesses the link search using the Chave A in the database, if any, safely compare to Chave B.

This would cause:

  1. If you allow someone read the data of the bank he will have possession of Chave A. But the other, Chave B, will only have the resulting HASH, which is insufficient. In other words, the Chave B is still safe and known only to the customer who requested the reset.

  2. If anyone can still "hit" the Chave A, common to both, the Chave B, this should be compared in Constant-time, using hash_equals() or password_verify().


In addition you can add some "extra" things, which usually do not have high implementation cost, there are already libraries for this.

Client:

  • Allow him to send a GPG key, so he can receive encrypted emails, even Facebook does this.
  • Allow you to use 2FA, a unique software-generated code (e.g. Google Authenticator) or hardware (e.g. Yubikey).

Establish a loss policy, that is if the GPG/2FA key/hardware user what will be done. Generally loss of account access is the safest, since it becomes impossible to prove that he is who he says he is.

Server:

  • Check if the IP that is resetting the password has ever accessed the account and is the one who requested the reset, or if the IP is from places close to those who access the account.
  • Always send the HSTS header to always force use of HTTPS before the request exits the browser.
  • As already done, create a rate-limit, limiting the number of attempts that can be made per IP.
  • Excellent answer! I need to research several things I didn’t know about.

  • Your answer made me ask another question. Since I thought you were somewhat out of the context of this question, I have created another, if you can answer it, I would be grateful. https://answall.com/questions/207650/o-que-%C3%A9-timing-Attack-e-where it applies

Browser other questions tagged

You are not signed in. Login or sign up in order to post.