PHP的password_verify()对超长密码(DoS攻击)是否安全?

2022-08-30 21:09:59

一般攻击场景:

在2013年,Django有一个普遍的漏洞,因为攻击者可以通过非常大的密码创建非常密集的CPU计算[请参阅此处的安全通知]。我不确定在使用PHP的password_verify()和其他密码哈希方法而无需任何进一步检查的情况下,这是否仍然可能。

PHP文档说:

对 algo 参数使用PASSWORD_BCRYPT将导致密码参数被截断为最大长度为 72 个字符。

但是,PHP的代码可能会说一些不同的东西:

然而,PHP 5.5.0 的 password_verify() 函数背后的 C 代码并不直接限制传递的参数(也许在 bcrypt 算法的更深层次上?此外,PHP 实现不限制参数。

问题:

password_verify()(以及同一函数集的其他函数)是否通过最大化的POST参数容易受到DoS的攻击?另请考虑 POST 上传大小远大于 4MB 的站点范围的配置情况。


答案 1

在加密算法中,密码在内部限制为 72 个字符。

要了解原因,让我们看一下 的来源:ext/standard/crypt.ccrypt()

    } 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;
        }

该字段是一个简单的字段。所以没有长度的信息。所有传递的只是一个普通的指针。passwordchar*

因此,如果我们遵循这一点,我们最终将降落在BF_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字符串(以空字节终止的字符串),所以它不可能使用过去的任何内容。因此,包含空字节的密码将丢失超过它的任何熵。示例:http://3v4l.org/Y6onV\0\0


答案 2

推荐