crypt: Fix validation of malformed BCrypt hashes

PHP’s implementation of crypt_blowfish differs from the upstream Openwall
version by adding a “PHP Hack”, which allows one to cut short the BCrypt salt
by including a `$` character within the characters that represent the salt.

Hashes that are affected by the “PHP Hack” may erroneously validate any
password as valid when used with `password_verify` and when comparing the
return value of `crypt()` against the input.

The PHP Hack exists since the first version of PHP’s own crypt_blowfish
implementation that was added in 1e820eca02.

No clear reason is given for the PHP Hack’s existence. This commit removes it,
because BCrypt hashes containing a `$` character in their salt are not valid
BCrypt hashes.
This commit is contained in:
Tim Düsterhus 2023-01-23 21:15:24 +01:00 committed by Stanislav Malyshev
parent 255e08ac56
commit c840f71524
2 changed files with 82 additions and 8 deletions

View file

@ -371,7 +371,6 @@ static const unsigned char BF_atoi64[0x60] = {
#define BF_safe_atoi64(dst, src) \
{ \
tmp = (unsigned char)(src); \
if (tmp == '$') break; /* PHP hack */ \
if ((unsigned int)(tmp -= 0x20) >= 0x60) return -1; \
tmp = BF_atoi64[tmp]; \
if (tmp > 63) return -1; \
@ -399,13 +398,6 @@ static int BF_decode(BF_word *dst, const char *src, int size)
*dptr++ = ((c3 & 0x03) << 6) | c4;
} while (dptr < end);
if (end - dptr == size) {
return -1;
}
while (dptr < end) /* PHP hack */
*dptr++ = 0;
return 0;
}

View file

@ -0,0 +1,82 @@
--TEST--
bcrypt correctly rejects salts containing $
--FILE--
<?php
for ($i = 0; $i < 23; $i++) {
$salt = '$2y$04$' . str_repeat('0', $i) . '$';
$result = crypt("foo", $salt);
var_dump($salt);
var_dump($result);
var_dump($result === $salt);
}
?>
--EXPECT--
string(8) "$2y$04$$"
string(2) "*0"
bool(false)
string(9) "$2y$04$0$"
string(2) "*0"
bool(false)
string(10) "$2y$04$00$"
string(2) "*0"
bool(false)
string(11) "$2y$04$000$"
string(2) "*0"
bool(false)
string(12) "$2y$04$0000$"
string(2) "*0"
bool(false)
string(13) "$2y$04$00000$"
string(2) "*0"
bool(false)
string(14) "$2y$04$000000$"
string(2) "*0"
bool(false)
string(15) "$2y$04$0000000$"
string(2) "*0"
bool(false)
string(16) "$2y$04$00000000$"
string(2) "*0"
bool(false)
string(17) "$2y$04$000000000$"
string(2) "*0"
bool(false)
string(18) "$2y$04$0000000000$"
string(2) "*0"
bool(false)
string(19) "$2y$04$00000000000$"
string(2) "*0"
bool(false)
string(20) "$2y$04$000000000000$"
string(2) "*0"
bool(false)
string(21) "$2y$04$0000000000000$"
string(2) "*0"
bool(false)
string(22) "$2y$04$00000000000000$"
string(2) "*0"
bool(false)
string(23) "$2y$04$000000000000000$"
string(2) "*0"
bool(false)
string(24) "$2y$04$0000000000000000$"
string(2) "*0"
bool(false)
string(25) "$2y$04$00000000000000000$"
string(2) "*0"
bool(false)
string(26) "$2y$04$000000000000000000$"
string(2) "*0"
bool(false)
string(27) "$2y$04$0000000000000000000$"
string(2) "*0"
bool(false)
string(28) "$2y$04$00000000000000000000$"
string(2) "*0"
bool(false)
string(29) "$2y$04$000000000000000000000$"
string(2) "*0"
bool(false)
string(30) "$2y$04$0000000000000000000000$"
string(60) "$2y$04$000000000000000000000u2a2UpVexIt9k3FMJeAVr3c04F5tcI8K"
bool(false)