Подтвердить что ты не робот

Является ли PHP password_verify() безопасным для чрезвычайно длинных паролей (DoS-атака)?

Общий сценарий атаки:

В 2013 году у Django была общая уязвимость, так как злоумышленник мог создавать чрезвычайно интенсивные вычисления ЦП с помощью очень больших паролей [см. здесь предупреждение безопасности]. Я не уверен, что это возможно при использовании PHP password_verify() и других методов хеширования пароля без каких-либо дополнительных проверок.

Документация PHP говорит:

Использование PASSWORD_BCRYPT для параметра algo приведет к усечению параметра пароля до максимальной длины 72 символа.

Но, PHP-код MAYBE говорит что-то другое:

Код C за функцией PHP 5.5.0 password_verify(), однако, не ограничивает переданный аргумент напрямую (возможно, на более глубоком уровне внутри алгоритма bcrypt?). Кроме того, PHP-реализация не ограничивает аргумент.

Вопрос:

Является ли password_verify() (и другие функции одного и того же набора функций) уязвимыми к DoS через maxed out POST parameters? Также рассмотрите конфигурационные ситуации на уровне сайта в размерах загрузки POST намного больше, чем 4 МБ.

4b9b3361

Ответ 1

Пароль ограничивается 72 символами внутри алгоритма склепа.

Чтобы узнать, почему, посмотрим на crypt() source: 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-строки (строки, завершенные нулевым байтом \0), невозможно использовать что-либо за прошлым \0. Таким образом, пароль, содержащий нулевой байт, теряет любую энтропию. Пример: http://3v4l.org/Y6onV