|
php.net | support | documentation | report a bug | advanced search | search howto | statistics | random bug | login |
[2023-01-05 12:52 UTC] tech at mkdgs dot fr
Description: ------------ Password_verify() always return true with some hash. I able to login without password when password_verify is used and a bad hash is stored in database. see test script for a evil hash example. There is other forms of magic hash with the same consequence. seen on many version of php on linux (maybe present on other system). perhaps a bigger security problem in the underlying code. Test script: --------------- <?php $pass_rand = rand(1,999).'test'.rand(1,999); echo $pass_rand. ':'; echo (password_verify($pass_rand , '$2y$10$am$2y$10$am')) ? 'OK' : 'FAIL';; PatchesPull Requests
Pull requests:
HistoryAllCommentsChangesGit/SVN commits
|
|||||||||||||||||||||||||||
Copyright © 2001-2025 The PHP GroupAll rights reserved. |
Last updated: Sat Oct 25 14:00:01 2025 UTC |
From the RFC which introduced the password functions[1]: | When passed a correct password and the generated hash from | password_hash(), the function will return a boolean true. If there | is any failure (hash is invalid, password is incorrect, hash is | corrupted, etc), the function will return a boolean false. So according to that statement, the current behavior is a bug. > […], values outside this range will cause crypt() to fail. Hmm: php > var_dump(crypt("foo", '$2y$10$am$2y$10$am')); string(18) "$2y$10$am$2y$10$am" How is the programmer supposed to know that this is a failure return? Ah, the return values section clarifies: | Returns the hashed string or a string that is shorter than 13 | characters and is guaranteed to differ from the salt on failure. Okay. Still, I fail to see why password_verify() returns true, while crypt() fails for the same broken salt/hash. Furthermore, it seems that password_verify() fails for broken argon2 hashes. And when I look at the implementation of php_password_bcrypt_verify() in ext/standard/password.c: zend_string *ret = php_crypt(ZSTR_VAL(password), (int)ZSTR_LEN(password), ZSTR_VAL(hash), (int)ZSTR_LEN(hash), 1); if (!ret) { return 0; } if (ZSTR_LEN(hash) < 13) { zend_string_free(ret); return 0; } I see that we are checking whether the length is less than 13 characters, but we do not check that the hash is returned unmodified. [1] <https://wiki.php.net/rfc/password_hash>$password = '$2a$07$58a5710f2225d$$$$$$$$.2AucaR7lucHAhCAnW0VUPNgiajDdVkO'; $pattern = '/(^\$.+\$).+/'; preg_match($pattern, $password, $matches); if (empty($matches[1])) { return false;// Unknown salt pattern. } $hash = crypt('test123', $matches[1]); var_dump($hash);die; => it's return *0 Dear support, I had a script code above. With 8.0.28, it returned an error instead of an encrypted string. Could you help to fix that? Thanks!