Heartbleed 漏洞 CVE-2014-0160 是 OpenSSL 库中的一个严重实现缺陷,攻击者可借此从受害服务器的内存中窃取数据。窃取的数据内容取决于服务器内存中已有的内容,可能包含私钥、TLS 会话密钥、用户名、密码、信用卡信息等。该漏洞存在于 SSL/TLS 用于保持连接活跃的心跳(Heartbeat)协议的实现中。
受影响的 OpenSSL 版本范围为 1.0.1 至 1.0.1f。Ubuntu 虚拟机中的版本为 1.0.1。
Heartbleed 攻击基于心跳请求。该请求仅向服务器发送一些数据,服务器会将数据复制到响应包中,从而实现数据回显。在正常情况下,假设请求包含 3 字节数据 "ABC",则长度字段的值为 3。服务器会将数据放入内存,并从数据开头复制 3 字节到响应包中。在攻击场景中,请求可能包含 3 字节数据,但长度字段可能设置为 1003。当服务器构造响应包时,它从数据起始位置(即 "ABC")开始复制 1003 字节,而非 3 字节。多出的 1000 字节显然不来自请求包,而是来自服务器的私有内存,其中可能包含其他用户的信息、密钥、密码等。
接下来,更改请求的长度字段。首先,了解上图中心跳响应包的构建方式。当心跳请求包到达时,服务器会解析该包以获取有效载荷和 Payload_length 值(上文已高亮)。此处有效载荷仅为 3 字节的字符串 "ABC",且 Payload_length 值正好为 3。服务器程序会盲目地接受请求包中的这个长度值,然后通过指向存储 "ABC" 的内存并复制 Payload_length 字节到响应有效载荷来构建响应包。这样,响应包将包含 3 字节的字符串 "ABC"。
接下来,按下图所示发起 Heartbleed 攻击。保持相同的有效载荷(3 字节),但将 Payload_length 字段设置为 1003。服务器在构建响应包时会再次盲目地接受这个 Payload_length 值。此时,服务器程序将指向字符串 "ABC" 并从内存中复制 1003 字节到响应包作为有效载荷。除了字符串 "ABC" 外,多余的 1000 字节会被复制到响应包中,这些字节可能是内存中的任意内容,例如秘密活动、日志信息、密码等等。
攻击代码允许更改 Payload_length 值。默认情况下,该值设置得相当大(0x4000),但可以减小。
修复 Heartbleed 漏洞最简单的方法是更新 OpenSSL 库至最新版本。然而,目标是通过源代码修补该漏洞。
心跳请求/响应包的格式
struct {
HeartbeatMessageType type; // 1 byte: request or the response
uint16 payload_length; // 2 byte: the length of the payload
opaque payload[HeartbeatMessage.payload_length];
opaque padding[padding_length];
} HeartbeatMessage;
包的第一个字段(1 字节)是类型信息,第二个字段(2 字节)是有效载荷长度,随后是实际有效载荷和填充。有效载荷的大小应与有效载荷长度字段的值一致,但在攻击场景中,有效载荷长度可以设置为不同的值。以下代码片段展示了服务器如何将数据从请求包复制到响应包。
处理心跳请求包并生成响应包
/* Allocate memory for the response, size is 1 byte
* message type, plus 2 bytes payload length, plus
* payload, plus padding
*/
unsigned int payload;
unsigned int padding = 16; /* Use minimum padding */
// Read from type field first
hbtype = *p++; /* After this instruction, the pointer
* p will point to the payload_length field */
// Read from the payload_length field from the request packet
n2s(p, payload); /* Function n2s(p, payload) reads 16 bits
* from pointer p and store the value
* in the INT variable "payload". */
pl = p; // pl points to the beginning of the payload content
if (hbtype == TLS1_HB_REQUEST)
{
unsigned char *buffer, *bp;
int r;
/* Allocate memory for the response, size is 1 byte
* message type, plus 2 bytes payload length, plus
* payload, plus padding
*/
buffer = OPENSSL_malloc(1 + 2 + payload + padding);
bp = buffer;
// Enter response type, length and copy payload *bp++ = TLS1_HB_RESPONSE;
s2n(payload, bp);
// copy payload
memcpy(bp, pl, payload); /* pl is the pointer which
* points to the beginning
* of the payload content */
bp += payload;
// Random padding
RAND_pseudo_bytes(bp, padding);
// this function will copy the 3+payload+padding bytes
// from the buffer and put them into the heartbeat response
// packet to send back to the request client side.
OPENSSL_free(buffer);
r = ssl3_write_bytes(s, TLS1_RT_HEARTBEAT, buffer, 3 + payload + padding);
}
漏洞所在位置
// copy payload
memcpy(bp, pl, payload);
此处未检查 pl 是否有效,因此可能发生内存越界。
补丁:
memcpy() 执行前进行边界检查虚拟机上已应用补丁,但未在本仓库中展示。
感谢您的关注,这个项目既有趣又富有启发性!