家喻户晓,在做音讯认证或者签名时,仅应用hash函数安全性是不高的,容易蒙受字典和暴力破解(https://www.cmd5.com/)。所以通常会应用带密钥或加盐的哈希算法作为音讯认证或者口令存储,正如题目所说,咱们在检索互联网上对于加盐的实现时,内容往往都是在明文前面加上随机值:

那做音讯认证的密钥或者盐可不可以加在明文后面呢?

这就引出本文的攻击方式。

MD5 算法计算逻辑

为了分明这个攻击方式原理,须要先理解下md5的计算逻辑。

md5算法实质上是一种压缩算法,将长度小于2^64bit的任意字符压缩成128bit固定长度字符。同AES之类的分组加密算法一样,md5也须要进行分组计算。

如图所示,md5的计算须要通过两个步骤:

  1. 分组&填充
  2. 具体计算

1.分组&填充

首先会对明文依照512bit的长度进行分组,最初一个分组可能会产生长度有余512bit,或者刚刚512bit。

无论最初一个分组的长度是否刚好等于512bit,依照填充规定都须要进行填充,具体细节:

  • 假如明文刚好能被512整除,须要新增一个分组,在开端8*8=64bit 依照小端存储放入原始明文的长度,分组两头残余的bit 依照10000000的形式进行填充,造成一个总长512bit的新分组。
  • 假如整除512bit余数大于0

    • 且余数大于512-8*8 =448 bit 则须要持续填充10000(0x80000.....)至下一个分组的448bit,残余的bit依照小端存储填充原始明文长度
    • 余数小于448bit,开端填充原始明文长度,两头残余局部填充填充10000(0x80000.....)

以上两个步骤用代码示意即为:

        m_l = len(message)  # 原始音讯长度        length = struct.pack('<Q', (m_l) * 8)  # 长度转化为小端 unsigned long long 8B        blank_padding = b""        message += b'\x80'  # 10000000        # 此分组不足以填充长度时        if 56 < len(message) < 64:            blank_padding += b'\x00' * (56 + 64 - len(message))  # 填充至下一个分组        # 分组能填充长度        else:            blank_padding = b'\x00' * (56 - len(message) % 64)  # 本分组填充        if len(blank_padding) > 0:            # 填充10000            message += blank_padding        # 填充长度        message += length

2. 具体计算

其实具体的计算过程,咱们不必关注,把这个过程当做一个黑盒(关注细节的能够关注文末github地址)就能够:

在上一步分组的根底上,第一组的分组的明文会和128bit的初始序列(幻数)做为输出进行压缩计算,初始序列是一组固定的值:

计算后会产生新的序列,做为下一组的“初始序列”和下一组的明文再次进行压缩计算,接下来的分组反复这种“上一组的输入作为下一组的输出”,最初一组的128bit输入即为最终的md5值。

哈希长度拓展攻打

理解了md5的计算逻辑,再回到这张图,上一次的的输入作为下一次的输出这种形式可能会导致一个问题。假如存在明文分组abc,明文分组产生md5的过程能够简化为:

  • 分组a:h.a = md5(iv,a), md5计算须要两个参数,iv 为初始序列,h.a 为压缩计算结果
  • 分组b:h.ab = md5(h.a,b)
  • 分组c或者最终md5: h.abc = md5(h.ab,c)

在这个过程当中,如果密钥被放在a分组当中,bc为原音讯认证的明文,那攻击者能够在不晓得密钥的状况下,扩大明文长度,如减少明文d,计算abcd的hash,只须要晓得基于abc的hash值,即可生成新的hash。

即:h.abcd = md5(h.abc,d)

这个过程即为hash长度扩大攻打。

接下来咱们依据理论的例子来实操下

实操

假如存在一个商城订单领取场景,订单的确认是通过前端参数给出,存在一个逻辑破绽能够通过前端参数来管制商品价格,从而实现“零元购”或者越权购买。

失常状况下进行购买,因为默认此用户只有300积分,所以会购买失败:

但如果进行参数价格good_price批改,会因为签名校验不通过:

所以须要进行签名破解,先看下后端验证的逻辑:

<?php    $total_score = 300;    $flag = 'xxxxxxxxxxxx';    $secret_key = "??????????????????????????????????????";  // 前端未知    $post_data =urldecode(file_get_contents("php://input"));    $user_sign = $_GET['signature'];    $sign = md5($secret_key.$post_data);    if ($user_sign === $sign) {        $price = $_POST['good_price'];        if ($price > $total_score){            echo '对不起,您的积分余额有余,交易失败!';        }else{            echo "祝贺,购买胜利!$flag";        }    }else{        echo '签名数据被篡改!';    }?>

后端存在一个签名逻辑,会验证用户的post参数加上密钥的md5值,如果用户批改了post参数,但因为不晓得密钥也就没产生成非法的md5,所以验证会不通过。

如果没有理解过哈希长度扩大攻打,这个代码是没啥问题的,所以知识面决定攻击面。

而且这个中央密钥被放在了明文后面拼接,针对哈希长度扩大攻打,利用起来还挺简略的,能够应用现成的工具,比方hashpump,依照提醒输出内容即可:

最初的明文中十六进制局部须要url编码,但因为hashpump须要编译,在win平台编译比拟麻烦,所以我本人实现了一个md5版本的利用工具(https://github.com/shellfeel/hash-ext-attack)

最初把失去的后果粘贴到burp,胜利购买。

总结

文章剖析了下md5的计算逻辑,以及哈希长度扩大的攻打原理,对于此类攻打的修复,其实很简略只须要把密钥由加在明文后面改为明文前面,或者应用规范的hmac算法,hmac算法外面会用密钥和明文做移位异或操作,从而加强hash的安全性,本文是以md5为例,其实对于有着相似M-D后果的hash算法都是能够这样利用的,比方sha-0,sha-1,sha-2 等。

参考

  • 《白帽子讲web平安》
  • 先知社区
  • hashpump

公众号

欢送大家关注我的公众号,这里有干货满满的硬核平安常识,和我一起学起来吧!