成因

网站服务器在用户下载文件之前需验证下载权限。这个网站会用如下的算法产生一个关于文件名的MAC:
def create_mac(key, fileName)
   return Digest::SHA1.hexdigest(key + fileName)
End

最终产生的URL会是这样:

http://example.com/download?file=report.pdf&mac=563162c9c71a17367d44c165b84b85ab59d036f9

当用户发起请求要下载一个文件时,将会执行下面这个函数:

def verify_mac(key, fileName, userMac)
    validMac = create_mac(key, filename)
    if (validMac == userMac) do
        initiateDownload()
    else
        displayError()
    end
End

这样,只有当用户没有擅自更改文件名时服务器才会执行initiateDownload()开始下载。实际上,这种生成MAC的方式,给攻击者在文件名后添加自定义字串留下可乘之机。

实现

哈希摘要算法,都是依靠Merkle-Damgard结构这类算法有意思的是,如果你知道message和MAC,只需要知道key的长度即可在message后面添加信息,就可以计算相应的MAC。

Example: message + padding +extension

继续用上面的例子,对文件下载的功能进行长度扩展攻击:

http://example.com/download?file=report.pdf%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%A8/../../../../../../../etc/passwd&mac=ee40aa8ec0cfafb7e2ec4de20943b673968857a5

例如:lctf2016 web350 你一定不能来着

验证函数:

if(strpos($filename,"www.rar")>-1){
    if($hash === md5($secret.$filename)){

        download("www.rar");
    }
    else
        exit("mac不对,你根本不是xdsec的人。") ;
}
elseif(strpos($filename,"download.php")>-1){
    if($hash === md5($secret.$filename)){

        download("download.php");
    }
    else
        exit("mac不对,你根本不是xdsec的人。");
}

通过hashpumpy函数得到payload:

download.php?mac=ab5924deff7ff61328a25204f1c604fe&filename=download.php%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%F0%00%00%00%00%00%00%00www.rar

即可获得www.rar

Length Extensions In Depth

为了理解这种攻击方式,你必须先了解hash函数的内部原理。

How Hash Algorithms Work

哈希函数以区块为单位操作数据。比如说,MD5, SHA1, SHA256的区块长度是512 bits 。大多数message的长度不会刚好可以被哈希函数的区块长度整除。这样一来,message就必须被填充(padding)至区块长度的整数倍。用前面文件下载的MAC的例子来说,填充后的message是这样的(‘x'表示key):

xxxxxxxxxxxreport.pdf\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xA8

在本例所用的SHA1算法中,哈希值由五组整数构成。一般我们看到的形式是把这五个整数转换为16进制然后连接到一起。运行算法时,初始值(又叫registers)被设置为这组数:67452301, EFCDAB89, 98BADCFE, 10325476, C3D2E1F0. 紧接着,填充message,再将其分割为512bits的区块。算法轮流操作每个区块,进行一系列的计算并更新registers的值。一旦完成了这些运算,registers里的值就是最终的哈希值。

Calculating An Extension

计算扩展值得第一步是创建一个新的MAC。我们首先对待扩展的值:上例中的‘/../../../../../../../etc/passwd’进行哈希摘要。但是,在进行摘要之前,我们要把registers里的初始值设置为原始message的MAC。你可以将其想象为让SHA1函数从服务器上的函数运行结束的地方继续进行。

攻击者的 MAC = SHA1(extension + padding) <- 覆盖registers初始值

这个攻击有个前提,在传入服务器的哈希函数时,扩展值必须存在于单独的区块中。所以我们的第二步,就是计算出一个填充值使得 key + message + padding == 512 bits 的整数倍。在此例中,key是11个字符的长度。因此填充之后的message是这样的: report.pdf\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xA8

传送到服务器的填充及扩展之后的message以及新的MAC:

http://example.com/download?file=report.pdf%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%A8/../../../../../../../etc/passwd&mac=ee40aa8ec0cfafb7e2ec4de20943b673968857a5 服务器要进行摘要运算的被攻击者篡改过的message如下:

secret + message + padding to the next block +
extension + padding to the end of that block.

服务器算出的哈希值将是ee40aa8ec0cfafb7e2ec4de20943b673968857a5,正好与我们添加扩展字串并覆盖registers初始值所计算出来的一样。这是因为攻击者的哈希计算过程,相当于从服务器计算过程的一半紧接着进行下去。

How To Run The Attack

为了简单,在这个例子中我透露了密钥长度是11位。在现实攻击环境中,攻击者无法获知密钥长度,需要对其长度进行猜测。 继续之前的例子,假设当MAC验证失败时,这个存在漏洞的网站会返回一个错误信息(HTTP response code 或者response body中的错误消息之类)。当验证成功,但是文件不存在时,也会返回一个错误信息。如果这两个错误信息是不一样的,攻击者就可以计算不同的扩展值,每个对应着不同的密钥长度,然后分别发送给服务器。当服务器返回表明文件不存在的错误信息时,即说明存在长度扩展攻击,攻击者可以随意计算新的扩展值以下载服务器上未经许可的敏感文件。

解决办法:HMAC算法