php与哈希的魔法,警惕PHP中的“魔术哈希”

警惕PHP中的“魔术哈希”

2015-05-12 18:14:44

阅读:0次

6c8ec2380760d851e3d101c80d750546.png

过去十多年来,PHP程序员们一直在跟运算符“==”作斗争。这个运算符引发了许多问题,尤其是对于密码哈希来说更是如此。PHP中的密码哈希是十六进制编码并且可以“0e812389…”形式出现。问题出在“==”跟0e的对比中,这意味着如果以下字符均为数字,那么整个字符串都会被看做浮点。早在五年前Gregor Kopf就指出了这个问题,两年前Tyler Borland与Raz0r也说明了该问题,并且一年前Michal Spacek及Jos Wetzels也有提及,但上周这个问题掀起了更大的波澜。

以下是一个哈希类型类表,当在PHP中使用==运算符后等于0的^0+e\d*$被哈希时会出现。这就意味着当密码哈希以“0e…”开头时,它总是会与如下字符串匹配,而不管当序列的所有字符是“0-9”的数字时中的哪些。也就是说这些数字被哈希时被当成数字“0”处理并且会与其他哈希作对比,对比将会是真值。将“0e…”看做“0是某个值的多少次方”,这样结果总是“0”。PHP将该字符串解释为一个整数。<?php

if (hash('md5','240610708',false) == '0') {

print "Matched.\n";

}

if ('0e462097431906509019562988736854' == '0') {

print "Matched.\n";

}

?>

实际上这就意味着以下“神奇的”字符串被以完全随机的哈希(如随机分配的密码、随机数、文件哈希或凭证)所哈希时,就越可能为真。同样,如果大胆猜想一下,将一个哈希跟相关哈希均以浮点数“0”通过PHP中的“==”运算符比对,并且如果数据库中的另外一个哈希同样以“0e…”开头,结果也将是真。因此,当跟一个哈希数据库对比时,哈希也很有可能是真,即使它们实际上并不匹配。例如许多cookies不过是哈希,而且找到碰撞更多滴依赖于测试时所使用的有效凭证数量。

用例1:使用下列“神奇的”数字当做你想哈希的密码或字符。当跟实际值的哈希对比时,如果它们都被当做“0”因此为真,你就可以在不持有有效密码的情况下登录账户。例如,这种情况的发生场景是:用户在忘记密码流时选择了自动密码,随后立即尝试登录。https://example.com/login.php?user=bob&token=0e462097431906509019562988736854

用例2:攻击者仅需利用下表“哈希(Hash)”一栏中的一个例子将其当做一个值。在某些情况下这些值仅会当做已知值的查询(在内存中,或可能来自数据库并且被比对)。仅仅通过提交哈希值,这个magic hash便会与其他亦被当做“0”的哈希碰撞,因此也会比对为真。

94c17e588c10ad328296813e53b67cc4.png

9bb0ad425b7fa174455f2b1d68edd040.png

1a9490c6a0f25a306238316afe0fe947.png

为找到如上结果,我对每个哈希类型都遍历了10亿个哈希整数,试图找到一个当与“0”比对时为真的赋值。如果我在这10亿次尝试中仍未找到,我将会尝试下一个哈希算法。这个方法效率低下但在找到与多数哈希算法有关的“Magic”数字/字符串时,能产生合理效果,另外这些哈希算法的长度为32 hex字符或在单核时较少。其中的一个例外是“adler32”,用于zlib压缩算法并且要求的方法稍有不同。这样做的道理是,对于大多内容来讲,哈希中的熵越多,你的防御就越好。如下是我所使用的代码(adler32要求很多特殊处理以找到不包含特殊字符的有效哈希):<?php

function hex_decode($string) {

for ($i=0; $i 

$decoded .= chr(hexdec(substr($string,$i,2)));

$i = (float)($i)+2;

}

return $decoded;

}

foreach (hash_algos() as $v) {

$a = 0;

print "Trying $v\n";

while (true) {

$a++;

if ($a > 1000000000) {

break;

}

if ($v === 'adler32') {

$b = hex_decode($a);

} else {

$b = $a;

}

$r = hash($v, $b, false);

if ($r == '0') {

if(preg_match('/^[\x21-\x7e]*$/', $b)) {

printf("%-12s %s %s\n", $v, $b, $r);

break;

}

}

}

}

?>

我不必使用多数结果中找到的整数,但它让编程序更简单一点。此外,回过头来看,使用整数也更为有效,因为有时候人们强制密码注意字母大小写而数字不受此影响,因此使用整数也更为安全。然而,在实际攻击中,攻击者可能必须找到与密码要求(至少有一个大写字母、一个小写字母、一个数字及一个特殊字符组成)相符的密码,而且再被哈希时会被估值为0。例如,经过1.47亿次暴力尝试之后,我发现md5将“Password147186970!”转换成了“0e153958235710973524115407854157”,满足了严格的密码要求并且估值依然为0。

为完成这个测试,我们发现一个32位字符的哈希在大约1/200,000,000次随机哈希测试中发生碰撞现象。幸亏发生频率不太多,但对于访问量高的网站或会生成许多有效凭证的网站来说,通常已经足够了。不过万幸的是在实际生活中要做到还是很困难的,因为它需要在最可能的实例中发送大规模的尝试。需要注意的是,“0x”(hex)以及“0o”(octal)中也会发生类似问题,但这些字符不会在哈希中出现,因此在多数情况下并不会频繁发生。还有一点需要注意的是,“==” 与 “!=”中也存在类似问题。

网站真的会容易受到此类攻击的困扰吗?答案是肯定的。它在大批不同类型的代码库中会产生问题。在Perl的“==”及“eq”中、以及JavaScript等语言中也会产生相同的困扰。(Jeremi M Gosney对这一问题已做详细解释。)如果出现了与这个问题相关的多个CVE列表,一点都不奇怪。

补丁

很幸运,补丁很简单。如果你用的是PHP,那么你可能听说过人们提到使用三个等号“===”的事儿。这就是原因所在。你所需做的不过是将“==”改为 “===”,并且将 “!=”改为 “!==”来防止PHP猜到变量类型(浮点及字符串)。一些人还建议使用“hash_equals”函数。

WhiteHat将通过自己的动态扫描器及静态代码分析为客户进行测试。点击此处可获得免费检测。使用静态代码分析找到PHP哈希比对非常简单。最后,如果你有一些计算功率且对这个攻击问题感兴趣,可考虑提交任何我们样本中尚未发现、或者我们尚未列出的哈希算法列表的值/哈希对。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值