记录黑客技术中优秀的内容, 传播黑客文化,分享黑客技术精华

警惕PHP中的“魔术哈希”

2015-05-13 06:30

分享到:

过去十多年来,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”的哈希碰撞,因此也会比对为真。

http://p1.qhimg.com/t01d6fb9bf3f33e07a2.png

http://p8.qhimg.com/t016d56a4782a9a612f.png

http://p4.qhimg.com/t01671510e8b26e86e8.png

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

<?php
function hex_decode($string) {
  for ($i=0; $i < strlen($string); $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哈希比对非常简单。最后,如果你有一些计算功率且对这个攻击问题感兴趣,可考虑提交任何我们样本中尚未发现、或者我们尚未列出的哈希算法列表的值/哈希对。

本文由 360安全播报 翻译,转载请注明“转自360安全播报”,并附上链接。
原文链接:https://blog.whitehatsec.com/magic-hashes/

知识来源: bobao.360.cn/learning/detail/398.html

阅读:115140 | 评论:0 | 标签:无

想收藏或者和大家分享这篇好文章→复制链接地址

“警惕PHP中的“魔术哈希””共有0条留言

发表评论

姓名:

邮箱:

网址:

验证码:

公告

关注公众号hackdig,学习最新黑客技术

推广

工具

标签云

本页关键词