0x01 前言

之前在俱乐部CTF平台上做了一道md5弱比较的题,在网上找到了题解,就是根据强弱类型比较的特性来对其进行绕过,我只是看到了加密结果以0E开头的就可以绕过弱类型比较,这个是利用了PHP处理Hash的缺陷,而hash加密结果以0e开头的payload就可以利用这个缺陷来进行绕过。这篇文章就是我对这个缺陷的了解,同时,也涉及到了强类型比较和“Md5强碰撞”。

0x02 正文

科学计数法

首先要提到一点的是计算机中的科学计数法,它是一种带有 E 的表示方法。比如, 1234用科学计数法表示为1.234e3,表示的是1.234*10^3123456.7的表示为 1.234567e5, 表示的是1.234567*10^5;由此可知,以 0e 开头的科学技术法表示的值都为 0。

哈希加密结果比较

在哈希加密结果当中,会出现以 0e 开头 + 纯数字 的组合,这种 特殊的Hash 也被称作 Magic Hash,这种情况会被当作科学计数法(被当作数字类型)来识别。因为这个缺陷,导致了在比较哈希值的时候,有了绕过的方式。这里提一下,科学计数法当中存在 INF,表示极大的意思,如:

<?php
    var_dump(10e982137421342135);

# 返回值 float(INF)

弱类型比较

在弱类型比较,符号为 ==!=,这种比较方式只会比较值,不会比较数据类型,它是将两个变量转换为同一种类型然后进行比较

<?php

    var_dump(0 == '0');             # ①bool(true)
    var_dump(0e01 == 0);            # ②bool(true)
    var_dump('0e01' == 0e01);       # ③bool(true)

    var_dump('0e0a' == 0);          # ④bool(false)

这个例子当中就可以得知:

  1. 通过 ①、②、③ 的推断出,00e01'0e01'的值是相等的,达到这一条件只能是他们的值都是数字类型(科学计数法)。

  2. 在 ④ 中,因为'0e0a'无法转为 数字类型 的数据,所以它只能将后者转为字符串格式,导致值不相等。

强类型比较

而在强类型比较中,比较的不仅值,还有数据类型,这就意味着它不会将比较类型进行转换,只要不是同一类型就会返回 false

<?php

    var_dump(0 === '0');                # ①bool(false)
    var_dump('0e01' === '0');           # ②bool(false)
    var_dump(0e01 === 0);               # ③bool(false)

    var_dump(0e01 === 0.0);             # ④bool(true)

强类型比较相较于弱类型理解起来就简单多了(没那么复杂),这里不同类型的比较就不多说了,在类型相同的前提下,我们的比较当中:

  1. 在 ② 当中,类型不会被转换,直接对比值

  2. 在 ③ 和 ④ 里面,0e01的类型为 float0int0.0float,三者的值都是相同的,但类型只有两者是相同。

题型

所以在遇到哈希值强弱类型比较的时候,就可以利用这个哈希缺陷。以这两个字符串和字符串的哈希值为例:

Plantext

MD5 Hash

QLTHNDT

0e405967825401955372549139051580

QNKCDZO

0e830400451993494058024219903391

0e215962017

0e291242476940776845150308577824

弱类型比较

在弱比较的情况下,是这样子的

<?php 
	$a = $_GET['a'];

	if (isset($a)){
    if ($a == md5($a)) {
      echo 'OK';
    } else {
      echo 'Failed';
    }
  } else {
      echo 'You need to post the parameter a and b';
  }

# 传入 a=0e215962017,返回的结果是 OK

强类型比较

而在强比较的时候,有了不同

<?php 
	$a = $_GET['a'];
	$b = $_GET['b'];
	if (isset($a) && isset($b)){
    if (md5($a) === ma5($b) && $a !== $b){
      echo 'OK';
    } else {
      echo 'Failed';
    }
  } else {
      echo 'You need to post the parameter a and b';
  }

# 传入 a=QLTHNDT&b=QNKCDZO,返回的结果是 Failed

显而易见,这里失败就是因为MD5加密结果的值不同,这里就可以引入一个新的方法,利用的是 哈希函数 在处理非字符数据的时候返回异常,如这里传入的是数组,那么返回的就会是 NULL,这里就可以利用 NULL 来对比较进行绕过

# 传入 a[]=1&b[]=2,返回的结果是 OK

MD5强碰撞

当对传参进行控制的时候,强比较就不能再像上面那样子使用数组传入一个参数就解决问题

<?php
  $a = (string)$_GET['a'];
  $b = (string)$_GET['b'];
  
  if (isset($a) && isset($b)) {
      if (md5($a) === md5($b) && $a !== $b) {
          echo 'OK';
      } else {
          echo 'Failed';
      }
  
  } else {
      echo 'You need to pass the parameter a and b';
  }

# # 传入 a[]=1&b[]=2,返回的结果是 Failed

这就引出了一个问题,是否存在md5加密前不相同,加密后相同的字符串?

在网上搜索资料后,发现是存在经md5加密后,加密值相同,原本不同的字符串(表示为“md5冲突”)。

这里给出payload

<?php

  $s1 = "%af%13%76%70%82%a0%a6%58%cb%3e%23%38%c4%c6%db%8b%60%2c%bb%90%68%a0%2d%e9%47%aa%78%49%6e%0a%c0%c0%31%d3%fb%cb%82%25%92%0d%cf%61%67%64%e8%cd%7d%47%ba%0e%5d%1b%9c%1c%5c%cd%07%2d%f7%a8%2d%1d%bc%5e%2c%06%46%3a%0f%2d%4b%e9%20%1d%29%66%a4%e1%8b%7d%0c%f5%ef%97%b6%ee%48%dd%0e%09%aa%e5%4d%6a%5d%6d%75%77%72%cf%47%16%a2%06%72%71%c9%a1%8f%00%f6%9d%ee%54%27%71%be%c8%c3%8f%93%e3%52%73%73%53%a0%5f%69%ef%c3%3b%ea%ee%70%71%ae%2a%21%c8%44%d7%22%87%9f%be%79%6d%c4%61%a4%08%57%02%82%2a%ef%36%95%da%ee%13%bc%fb%7e%a3%59%45%ef%25%67%3c%e0%27%69%2b%95%77%b8%cd%dc%4f%de%73%24%e8%ab%66%74%d2%8c%68%06%80%0c%dd%74%ae%31%05%d1%15%7d%c4%5e%bc%0b%0f%21%23%a4%96%7c%17%12%d1%2b%b3%10%b7%37%60%68%d7%cb%35%5a%54%97%08%0d%54%78%49%d0%93%c3%b3%fd%1f%0b%35%11%9d%96%1d%ba%64%e0%86%ad%ef%52%98%2d%84%12%77%bb%ab%e8%64%da%a3%65%55%5d%d5%76%55%57%46%6c%89%c9%df%b2%3c%85%97%1e%f6%38%66%c9%17%22%e7%ea%c9%f5%d2%e0%14%d8%35%4f%0a%5c%34%d3%73%a5%98%f7%66%72%aa%43%e3%bd%a2%cd%62%fd%69%1d%34%30%57%52%ab%41%b1%91%65%f2%30%7f%cf%c6%a1%8c%fb%dc%c4%8f%61%a5%93%40%1a%13%d1%09%c5%e0%f7%87%5f%48%e7%d7%b3%62%04%a7%c4%cb%fd%f4%ff%cf%3b%74%28%1c%96%8e%09%73%3a%9b%a6%2f%ed%b7%99%d5%b9%05%39%95%ab";
  $s2 = "%af%13%76%70%82%a0%a6%58%cb%3e%23%38%c4%c6%db%8b%60%2c%bb%90%68%a0%2d%e9%47%aa%78%49%6e%0a%c0%c0%31%d3%fb%cb%82%25%92%0d%cf%61%67%64%e8%cd%7d%47%ba%0e%5d%1b%9c%1c%5c%cd%07%2d%f7%a8%2d%1d%bc%5e%2c%06%46%3a%0f%2d%4b%e9%20%1d%29%66%a4%e1%8b%7d%0c%f5%ef%97%b6%ee%48%dd%0e%09%aa%e5%4d%6a%5d%6d%75%77%72%cf%47%16%a2%06%72%71%c9%a1%8f%00%f6%9d%ee%54%27%71%be%c8%c3%8f%93%e3%52%73%73%53%a0%5f%69%ef%c3%3b%ea%ee%70%71%ae%2a%21%c8%44%d7%22%87%9f%be%79%6d%c4%61%a4%08%57%02%82%2a%ef%36%95%da%ee%13%bc%fb%7e%a3%59%45%ef%25%67%3c%e0%27%69%2b%95%77%b8%cd%dc%4f%de%73%24%e8%ab%66%74%d2%8c%68%06%80%0c%dd%74%ae%31%05%d1%15%7d%c4%5e%bc%0b%0f%21%23%a4%96%7c%17%12%d1%2b%b3%10%b7%37%60%68%d7%cb%35%5a%54%97%08%0d%54%78%49%d0%93%c3%b3%fd%1f%0b%35%11%9d%96%1d%ba%64%e0%86%ad%ef%52%98%2d%84%12%77%bb%ab%e8%64%da%a3%65%55%5d%d5%76%55%57%46%6c%89%c9%5f%b2%3c%85%97%1e%f6%38%66%c9%17%22%e7%ea%c9%f5%d2%e0%14%d8%35%4f%0a%5c%34%d3%f3%a5%98%f7%66%72%aa%43%e3%bd%a2%cd%62%fd%e9%1d%34%30%57%52%ab%41%b1%91%65%f2%30%7f%cf%c6%a1%8c%fb%dc%c4%8f%61%a5%13%40%1a%13%d1%09%c5%e0%f7%87%5f%48%e7%d7%b3%62%04%a7%c4%cb%fd%f4%ff%cf%3b%74%a8%1b%96%8e%09%73%3a%9b%a6%2f%ed%b7%99%d5%39%05%39%95%ab";
  $s3 = "%af%13%76%70%82%a0%a6%58%cb%3e%23%38%c4%c6%db%8b%60%2c%bb%90%68%a0%2d%e9%47%aa%78%49%6e%0a%c0%c0%31%d3%fb%cb%82%25%92%0d%cf%61%67%64%e8%cd%7d%47%ba%0e%5d%1b%9c%1c%5c%cd%07%2d%f7%a8%2d%1d%bc%5e%2c%06%46%3a%0f%2d%4b%e9%20%1d%29%66%a4%e1%8b%7d%0c%f5%ef%97%b6%ee%48%dd%0e%09%aa%e5%4d%6a%5d%6d%75%77%72%cf%47%16%a2%06%72%71%c9%a1%8f%00%f6%9d%ee%54%27%71%be%c8%c3%8f%93%e3%52%73%73%53%a0%5f%69%ef%c3%3b%ea%ee%70%71%ae%2a%21%c8%44%d7%22%87%9f%be%79%ed%c4%61%a4%08%57%02%82%2a%ef%36%95%da%ee%13%bc%fb%7e%a3%59%45%ef%25%67%3c%e0%a7%69%2b%95%77%b8%cd%dc%4f%de%73%24%e8%ab%e6%74%d2%8c%68%06%80%0c%dd%74%ae%31%05%d1%15%7d%c4%5e%bc%0b%0f%21%23%a4%16%7c%17%12%d1%2b%b3%10%b7%37%60%68%d7%cb%35%5a%54%97%08%0d%54%78%49%d0%93%c3%33%fd%1f%0b%35%11%9d%96%1d%ba%64%e0%86%ad%6f%52%98%2d%84%12%77%bb%ab%e8%64%da%a3%65%55%5d%d5%76%55%57%46%6c%89%c9%df%b2%3c%85%97%1e%f6%38%66%c9%17%22%e7%ea%c9%f5%d2%e0%14%d8%35%4f%0a%5c%34%d3%73%a5%98%f7%66%72%aa%43%e3%bd%a2%cd%62%fd%69%1d%34%30%57%52%ab%41%b1%91%65%f2%30%7f%cf%c6%a1%8c%fb%dc%c4%8f%61%a5%93%40%1a%13%d1%09%c5%e0%f7%87%5f%48%e7%d7%b3%62%04%a7%c4%cb%fd%f4%ff%cf%3b%74%28%1c%96%8e%09%73%3a%9b%a6%2f%ed%b7%99%d5%b9%05%39%95%ab";
  var_dump(md5(urldecode($s1)));
  var_dump(md5(urldecode($s2)));
  var_dump(md5(urldecode($s3)));

# 运行结果
# string(32) "ea8b4156874b91a4ef00c5ca3e4a4a34"
# string(32) "ea8b4156874b91a4ef00c5ca3e4a4a34"
# string(32) "ea8b4156874b91a4ef00c5ca3e4a4a34"

从这里我们得到了md5加密结果相同但不一样的字符串,深究这个原理参考了网上的这篇文章 https://crypto.stackexchange.com/questions/1434/are-there-two-known-strings-which-have-the-same-md5-hash-value

0x03 总结

在哈希比较当中,弱类型比较可以利用“科学计数法”来进行绕过;强类型比较在不做传入参数类型限制的情况下,可以利用到哈希函数处理非字符数据时返回异常,使得结果为 NULL,而在做了限制之后,就需要利用“Md5冲突”了。这里的弱类型比较没有限制是因为限制一般都是将传入的参数转为字符串类型,而弱类型比较所利用到的缺陷就是字符串加密结果为“科学计数法”形式的字符串,所以无法限制弱类型比较。

参考文章:

PHP的hash比较缺陷_天才大野狼的博客-CSDN博客

PHP哈希弱类型比较的缺陷_php弱类型hash比较缺陷_ADreamClusive的博客-CSDN博客

php中hash比较缺陷_php代码审计-php-hash比较缺陷_swag_000的博客-CSDN博客

浅谈PHP中哈希比较缺陷问题及哈希强比较相关问题_哈希连接 不比较_末 初的博客-CSDN博客