PHP 弱类型带来的安全问题

前一段看到的一个 TIP,实践了下,有所收获。

强制转换规则

字符串转换为数值

由上图可以看出,当对两个字符串进行比较时,会『进行数字或词汇比较』。

当一个字符串被当作一个数值来取值,其结果和类型如下:

如果该字符串没有包含 ‘.’,’e’ 或 ‘E’ 并且其数字值在整型的范围之内(由 PHP_INT_MAX 所定义),该字符串将被当成 integer 来取值。其它所有情况下都被作为 float 来取值。

该字符串的开始部分决定了它的值。如果该字符串以合法的数值开始,则使用该数值。否则其值为 0(零)。合法数值由可选的正负号,后面跟着一个或多个数字(可能有小数点),再跟着可选的指数部分。指数部分由 ‘e’ 或 ‘E’ 后面跟着一个或多个数字构成。

再结合文档,可以得出:

<?php
    var_dump('1024' == '1024.00'); //bool(true)
?>

实践

微博上看到安全宝发起的 yuebaomei,最终关中代码审计运用到了以上 TIP。漏洞代码如下:

<?php

$flag = "THIS IS FLAG";

if  ("POST" == $_SERVER['REQUEST_METHOD'])
{
    $password = $_POST['password'];
    if (0 >= preg_match('/^[[:graph:]]{12,}$/', $password))
    {
        echo 'Wrong Format';
        exit;
    }

    while (TRUE)
    {
        $reg = '/([[:punct:]]+|[[:digit:]]+|[[:upper:]]+|[[:lower:]]+)/';
        if (6 > preg_match_all($reg, $password, $arr))
            break;

        $c = 0;
        $ps = array('punct', 'digit', 'upper', 'lower');
        foreach ($ps as $pt)
        {
            if (preg_match("/[[:$pt:]]+/", $password))
            $c += 1;
        }

        if ($c < 3)
            break;

        if ("42" == $password)
            echo $flag;
        else
            echo 'Wrong password';

        exit;
    }
}

?>

上面的部分是一些对密码强度的验证,读懂代码后可以简单通过。最后的验证在于:

if ("42" == $password) echo $flag;

依照上述思路,令 $password 为 420000000.0e-7 便可通过验证,得到 flag。

References & Recommendation