Общий сценарий атаки:
В 2013 году у Django была общая уязвимость, так как злоумышленник мог создавать чрезвычайно интенсивные вычисления CPU с помощью очень больших паролей ( см. Здесь уведомление о безопасности ). Я не уверен, что это возможно при использовании PHP password_verify () и других методов хеширования пароля без каких-либо дополнительных проверок.
Документация PHP говорит:
Использование параметра PASSWORD_BCRYPT для параметра algo приведет к усечению параметра пароля до максимальной длины 72 символа.
Но, код PHP MAYBE говорит что-то другое:
Однако код C за функцией password_verify () PHP 5.5.0 не ограничивает переданный аргумент напрямую (возможно, на более глубоком уровне внутри алгоритма bcrypt?). Кроме того, реализация PHP не ограничивает аргумент.
Вопрос:
Является ли password_verify () (и другие функции одного и того же набора функций) уязвимыми для DoS с помощью максимальных параметров POST? Также рассмотрите конфигурационные ситуации на уровне сайта в размерах загрузки POST, значительно превышающих 4 МБ.
Пароль ограничен 72 символами внутри алгоритма склепа.
Чтобы понять, почему, давайте посмотрим на источник crypt()
: http://lxr.php.net/xref/PHP_TRUNK/ext/standard/crypt.c#202
} else if ( salt[0] == '$' && salt[1] == '2' && salt[3] == '$') { char output[PHP_MAX_SALT_LEN + 1]; memset(output, 0, PHP_MAX_SALT_LEN + 1); crypt_res = php_crypt_blowfish_rn(password, salt, output, sizeof(output)); if (!crypt_res) { ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN + 1); return NULL; } else { result = zend_string_init(output, strlen(output), 0); ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN + 1); return result; }
Поле password
– это простое поле char*
. Таким образом, информация о длине отсутствует. Все, что передано, является нормальным указателем.
Поэтому, если мы последуем этому, мы, в конце концов, приземлимся на BC_set_key
.
Важной частью является цикл:
for (i = 0; i < BF_N + 2; i++) { tmp[0] = tmp[1] = 0; for (j = 0; j < 4; j++) { tmp[0] <<= 8; tmp[0] |= (unsigned char)*ptr; /* correct */ tmp[1] <<= 8; tmp[1] |= (BF_word_signed)(signed char)*ptr; /* bug */ if (j) sign |= tmp[1] & 0x80; if (!*ptr) ptr = key; else ptr++; } diff |= tmp[0] ^ tmp[1]; /* Non-zero on any differences */ expanded[i] = tmp[bug]; initial[i] = BF_init_state.P[i] ^ tmp[bug]; }
BF_N
определяется как 16. Таким образом, внешний цикл будет цикл 18 раз ( BF_N + 2
).
Внутренний цикл будет циклически 4 раза. 4 * 18 == 72.
И там у вас есть, будет прочитано всего 72 символа ключа. Больше не надо.
Теперь есть интересный побочный эффект для этого алгоритма. Поскольку он использует C-Strings (строки, завершенные нулевым байтом \0
), невозможно использовать что-либо в прошлом \0
. Таким образом, пароль, содержащий нулевой байт, теряет любую энтропию. Пример: http://3v4l.org/Y6onV