Heartbleed Attack Lab
1 Overview
Heartbleed bug (CVE -2014-0160)是OpenSSL库中一个严重的实现缺陷,它是允许攻击者从受害者服务器的内存中窃取数据。被盗数据的内容取决于服务器内存中的内容。它可能包含私钥,TLS会话密钥,用户姓名、密码、信用卡等。漏洞存在于心跳协议的实现中,
SSL/TLS使用它来保持连接处于活动状态。
这个实验室的目的是让学生了解这个漏洞有多严重,攻击是如何进行的以及如何解决问题。受影响的OpenSSL版本范围从1.0.1到1.0.1f。我们的Ubuntu VM中的版本是1.0.1。
2 Lab Environment
在这个实验室中,我们需要设置两个虚拟机:一个称为攻击机,另一个称为受害者服务器。我们使用预构建的SEEDUbuntu12.04 VM。虚拟机需要使用nat网络适配器网络设置。这可以通过转到VM设置、选择网络并单击适配器来完成标记来将适配器切换到NAT-Network。确保两个虚拟机在相同的nat网络上。
在这次攻击中使用的网站可以是任何使用SSL/TLS的HTTPS网站.然而,因为它攻击真实网站是非法的,我们在自己的虚拟机中建立了一个网站,并自己进行攻击VM.我们使用一个名为ELGG的开源社交网络应用程序,并将其托管在以下URL中:https://www.heartbleedlabelgg.com。
我们需要修改攻击者计算机上的/ etc / hosts文件,以将服务器名称映射到服务器VM的IP地址。搜索/ etc / hosts中的以下行,并将IP地址127.0.0.1替换为承载ELGG应用程序的服务器VM的实际IP地址。
3 Lab Tasks
在进行实验任务之前,您需要了解Heartbeat协议的工作原理。 Heartbeat协议由两种消息类型组成:HeartbeatRequest包和HeartbeatResponse包。 客户端向服务器发送HeartbeatRequest包。 当服务器接收到它时,它在HeartbeatResponse数据包中发送一个接收到的消息的副本。 目标是保持持续连接状态。
3.1 Task 1: Launch the Heartbleed Attack.
在这项任务中,学生将在我们的社交网站上启动Heartbleed攻击,并看看可以实现什么样的损害。 Heartbleed攻击的实际损坏取决于服务器内存中存储的是什么类型的信息。 如果服务器上没有太多活动,您将无法窃取有用的数据。 因此,我们需要作为合法用户与Web服务器进行交互。 让我们以管理员身份执行操作,并执行以下操作:
打开虚拟机,从虚拟机访问https://www.heartbleedlabelgg.com
以网站管理员身份登录。(用户名:admin;密码:seedelgg)
加Boby为好友。(more–>members选择Boby–>add friend)
给Boby发送私人信息
可以看到消息成功发送给Boby
在作为合法用户进行了足够的交互后,您可以启动攻击,并查看可以从受害服务器中获取的信息。 编写程序以从头开始执行Heartbleed攻击并不容易,因为它需要Heartbeat协议的低级知识。 幸运的是,其他人已经写了攻击代码。 因此,我们将使用现有代码在Heartbleed攻击中获得第一手的体验。 我们使用的代码称为attack.py,它最初是由Jared Stafford编写的。 我们对教育目的的代码做了一些小的改动。 您可以从实验室的网站下载代码,更改其权限,以便文件可执行。 然后,您可以运行攻击代码,如下所示:
运行攻击代码,得到的结果如下
您可能需要多次运行攻击代码以获取有用的数据。 尝试看看是否可以从目标服务器获取以下信息。
对于你从Heartbleed攻击中窃取的每一个秘密,你需要显示屏幕转储作为证明,并解释你是如何进行攻击,以及你的观察是什么。
多次执行攻击代码,可以得到如下信息
用户的账号和密码可以被知道
添加Boby为好友
给Boby发送信息
私人信息的内容可以被看到
3.2 Task 2: Find the Cause of the Heartbleed Vulnerability
在此任务中,学生将比较良性数据包的结果和攻击者代码发送的恶意数据包,以找出Heartbleed漏洞的根本原因。
Heartbleed攻击基于Heartbeat请求。这个请求只是发送一些数据到服务器,并且服务器将数据复制到其响应数据包,所以所有的数据被回送。在正常情况下,假设请求包括3个字节的数据“ABC”,因此长度字段具有值3.服务器将数据放置在存储器中,并将3个字节从数据开始复制到其响应包。在攻击情形中,请求可以包含3字节的数据,但是长度字段可以表示为1003.当服务器构造其响应分组时,它从数据的开始(即“ABC”)复制,但是它复制1003字节,而不是3字节。这些额外的1000个类型显然不是来自请求包;它们来自服务器的私有内存,它们可能包含其他用户的信息,密钥,密码等。
在这个任务中,我们将使用请求的长度字段。首先,让我们了解Heartbeat响应数据包是如何从图2构建的。当心跳请求数据包到来时,服务器将解析数据包以获取有效负载和有效负载长度值。这里,有效载荷只是一个3字节的字符串“ABC”,有效载荷长度值正好为3.服务器程序将盲目地从请求数据包中取这个长度值。然后通过指向存储“ABC”的存储器并将有效载荷长度字节复制到响应有效载荷来构建响应分组。这样,响应包将包含3字节字符串“ABC”。
我们可以启动HeartBleed攻击。我们保持相同的有效负载(3字节),但将Payload长度字段设置为1003.当构建响应数据包时,服务器将再次盲取该有效负载长度值。这一次,服务器程序将指向字符串“ABC”,并将1003字节从内存复制到响应包作为有效载荷。除了字符串“ABC”,额外的1000字节被复制到响应数据包,这可能是来自内存的任何东西,如秘密活动,日志信息,密码等。
我们的攻击代码允许你使用不同的有效载荷长度值。 默认情况下,该值设置为相当大的值(0x4000),但可以使用命令选项“-l”(letter ell)或“–length”减小大小,如以下示例所示:
不断测试不同的值进行攻击,得到以下结果
你的任务是使用不同有效载荷长度值的攻击程序,并回答以下问题:
- 问题2.1:随着长度变量减少,您可以观察到什么样的差异? 根据测试结果可以知道,当length的值减少时,可以获得的额外信息也随之减少.
- 问题2.2:随着长度变量减小,输入长度变量有一个边界值。 在该边界处或以下,Heartbeat查询将接收响应分组,而不附加任何额外数据(这意味着请求是良性的)。 请找到边界长度。 您可能需要尝试许多不同的长度值,直到Web服务器发送回应,没有额外的数据。 为了帮助您,当返回的字节数小于预期的长度,程序将打印"Server processed malformed Heartbeat, but did not return any extra data."经过不断的修改length值进行测试,最终得到边界值为22
3.3 Task 3: Countermeasure and Bug Fix
要修复Heartbleed漏洞,最好的方法是将OpenSSL库更新到最新版本。 这可以使用以下命令实现。 应该注意的是,一旦更新,很难回到脆弱的版本。 因此,请确保在执行更新之前完成了先前的任务。 您还可以在更新之前创建VM的快照。
拍摄快照后进行更新
再次进行攻击,可以发现攻击失败
- 任务3.1在更新OpenSSL库之后再次尝试您的攻击。 请描述你的观察。攻击失败,不能获得账号密码等信息
- 任务3.2此任务的目标是找出如何修复Heartbleed错误在源代码中。 以下C风格结构(与源代码不完全相同)是Heartbeat请求/响应数据包的格式。
分组的第一字段(1字节)是类型信息,第二字段(2字节)是有效载荷长度,随后是实际有效载荷和填充。 有效载荷的大小应该与有效载荷长度字段中的值相同,但在攻击情形中,有效载荷长度可以设置为不同的值。 以下代码段显示了服务器如何将数据从请求数据包复制到响应数据包。
请从清单1中的代码中指出该问题,并提供一个解决方案来修复该错误(即修复该错误需要进行哪些修改)。 你不需要重新编译代码; 只是描述如何解决您的实验报告中的问题。
- Heart Bleed漏洞产生原因:由于未能在memcpy()调用受害用户输入的内容作为长度参数之前正确进行边界检查。攻击者可以追踪OpenSSl所分配的64KB内存,将超出必要范围的字节信息复制到缓存当中,再返回缓存内容,这样一来,受害者的内存内容就会每次泄露64KB.
- 宏n2s从p中获取两个字节,并将它们放入有效负载中。这实际上是有效载荷的长度。注意,没有检查记录中的实际长度。
- 变量pl是结果心跳数据,由请求者提供。
- 在后面的函数中,它这样做:
- 接着继续分析代码
- 以上代码可以看出,我们分配的内存与请求的一样多:精确地说,最多为65535+1+2+16。变量bp将是用于访问内存的指针。
- 宏s2n的作用与n2s相反:它接受一个16位的值并将其放入两个字节中。所以它放置相同的负载长度请求。然后,它将用户提供的数据pl中的有效负载字节复制到新分配的bp数组中。在此之后,它将所有这些返回给用户。
- 如果请求者没有像要求的那样实际提供有效负载字节,那该怎么办?如果pl真的只有一个字节呢?然后,从memcpy读取的数据将读取记录附近和同一进程内的所有内存。显然,附近有很多东西。
- malloc动态分配内存有两种方式(至少在Linux上是这样):使用sbrk(2)和mmap(2)。如果使用sbrk分配内存,那么它将使用旧的堆增长规则,并限制可以从中找到什么,尽管多个请求(尤其是同时)仍然可以找到一些有用的东西
- 实际上,对bp的分配根本不重要。然而,pl的分配非常重要。由于malloc中的mmap阈值,几乎可以肯定它是由sbrk分配的。然而,有用的东西(如文档或用户信息)很可能由mmap分配,并且可以从pl访问到。多个同时发生的请求也可以提供一些有用的数据。
- 如果要修复代码,我认为的解决方案如下
//fix.c
unsigned char *p = &s->s3->rrec.data[0];
// Read from type field first
if (1+2+16 > s->s3->rrec.length)
return 0; /* silently discard */
hbtype = *p++; /* After this instruction,
the pointer p will point to the payload_length field */
if (1+2+payload+16 > s->s3->rrec.length)
return 0; /* silently disable per RFC 6520 sec.4 */
pl = p;
- 这可以做两件事:第一次检查可以阻止零长度的心跳。第二个检查确保实际记录长度足够长。就是这样。
4 attack.py原程序
#!/usr/bin/python
# Code originally from https://gist.github.com/eelsivart/10174134
# Modified by Haichao Zhang
# Last Updated: 2/12/15
# Version 1.20
#
#
# -added option to the payload length of the heartbeat payload
# Don't forget to "chmod 775 ./attack.py" to make the code executable
# Students can use eg. "./attack.py www.seedlabelgg.com -l 0x4001" to send the heartbeat request with payload length variable=0x4001
# The author disclaims copyright to this source code.
import sys
import struct
import socket
import time
import select
import re
import time
import os
from optparse import OptionParser
options = OptionParser(usage='%prog server [options]', description='Test and exploit TLS heartbeat vulnerability aka heartbleed (CVE-2014-0160)')
options.add_option('-p', '--port', type='int', default=443, help='TCP port to test (default: 443)')
options.add_option('-l', '--length', type='int', default=0x4000,dest="len", help='payload length to test (default: 0x4000)')
options.add_option('-n', '--num', type='int', default=1, help='Number of times to connect/loop (default: 1)')
options.add_option('-s', '--starttls', action="store_true", dest="starttls", help='Issue STARTTLS command for SMTP/POP/IMAP/FTP/etc...')
options.add_option('-f', '--filein', type='str', help='Specify input file, line delimited, IPs or hostnames or IP:port or hostname:port')
options.add_option('-v', '--verbose', action="store_true", dest="verbose", help='Enable verbose output')
options.add_option('-x', '--hexdump', action="store_true", dest="hexdump", help='Enable hex output')
options.add_option('-r', '--rawoutfile', type='str', help='Dump the raw memory contents to a file')
options.add_option('-a', '--asciioutfile', type='str', help='Dump the ascii contents to a file')
options.add_option('-d', '--donotdisplay', action="store_true", dest="donotdisplay", help='Do not display returned data on screen')
options.add_option('-e', '--extractkey', action="store_true", dest="extractkey", help='Attempt to extract RSA Private Key, will exit when found. Choosing this enables -d, do not display returned data on screen.')
opts, args = options.parse_args()
if opts.extractkey:
import base64, gmpy
from pyasn1.codec.der import encoder
from pyasn1.type.univ import *
def hex2bin(arr