请查看 ssl/.c。 该漏洞的补丁以这一行开头:
整数
吃(SSL*s)
char*p = &s->s3->rrec.data[0], *pl;
;
;
= 16; /* 使用 */
一旦我们起床,我们就会得到一个指向 SSLv3 记录中数据的指针。 该结构体的定义如下(译者注:该结构体并不是SSLv3记录的实际存储格式,SSLv3记录后面的存储格式请看下面的分析):
10
11
; /* 类型 */
;/* 有多少字节 */
; /* 读/写到 'buf' */
char*data;/* 转为数据*/
字符*输入; /* 字节所在的位置 */
char*comp;/* 仅与 - ()ed 一起使用 */
;/* 纪元 , 由 DTLS1 */
[8]; /* , 通过 DTLS1 */
};
每个SSLv3记录都包含一个类型字段(type)、一个长度字段()和一个指向记录数据的指针(data)。 我们回去吃饭吧:
/* 读取类型和第一个 */
=*p++;
n2s(p, );
pl = p;
SSLv3记录的第一个字节表示心跳包的类型。 宏n2s从指针p指向的数组中取出前两个字节并将它们存储在一个变量中——这实际上是心跳包有效负载的长度字段()。 请注意,程序不会检查此 SSLv3 记录的实际长度。 变量pl指向访客提供的心跳包数据。
该函数背后完成了以下工作:
字符*,*bp;
内部;
/* 对于 ,大小为 1 字节
* 类型,加2个字节,加
*, 加
*/
= (1 + 2 + + );
基点 = ;
因此,程序会分配访问者指定大小的内存区域。 该内存区域的最大大小为(65535 + 1 + 2 + 16)字节。 变量bp是用于访问该内存区域的指针。
/* 输入类型,然后复制 */
*bp++=;
s2n(, bp);
(bp, pl, );
宏 s2n 和宏 n2s 的作用恰恰相反:s2n 读取一个 16 位长值,然后将其存储为一个两字节值,因此 s2n 将在变量中存储与请求的心跳包有效负载长度相同的长度值。 然后程序将从pl开始的字节复制到新分配的bp数组中——pl指向用户提供的心跳包数据。 最后,程序将所有数据发送回用户。 那么bug在哪里呢?
0x01a 用户可以控制变量和pl
如果用户在心跳包中没有提供足够的数据,会出现什么问题? 例如,pl指向的数据实际上只有一个字节,那么这条SSLv3记录之后的数据——无论数据是什么——都会被复制出来。
很明显,距离 SSLv3 记录还有相当多的距离。
老实说,我对那些发现 漏洞的人的说法感到惊讶。 当我听到他们的公告时,我认为 64 KB 的数据根本不足以推断出私钥之类的东西。 至少在x86上,堆向更高的地址增长,所以我认为从指针pl读取只能读到新分配的内存区域,例如指针bp指向的区域。 存储私钥等信息的内存区域的分配早于指针pl指向的内存区域,因此攻击者无法读取那些敏感数据。 当然,考虑到现代魔法的各种实现方式,我的推论并不总是正确的。
当然,你无法读取其他进程的数据,所以“重要业务文档”必须位于当前进程的内存区域,小于64 KB,并且正好位于指针pl指向的内存块附近。
研究人员声称他们成功恢复了密钥,我希望看到一个 PoC。 如果您找到 PoC,请与我联系。
0x01b漏洞补丁
修复代码中最重要的部分如下:
/* 读取类型和第一个 */
if(1 + 2 + 16 > s->s3->rrec.)
; /* */
=*p++;
n2s(p, );
if(1 + 2 + + 16 > s->s3->rrec.)
; /* 根据 RFC 6520 秒。 4 */
pl = p;
这段代码做了两件事:首先,第一行语句丢弃长度为0的心跳包,然后第二步检查以确保心跳包足够长。 就是这么简单。