CVE-2020-1350漏洞深入剖析
2020-07-18 12:02:08 Author: bbs.pediy.com(查看原文) 阅读量:735 收藏

活跃值: 活跃值 (195)

能力值:

( LV4,RANK:50 )

在线值:

[原创]CVE-2020-1350漏洞深入剖析

2天前 1202
目录

CVE-2020-1350 一个 DNS 协议相关的高危漏洞,攻击者利用该漏洞可以远程执行代码,其严重性堪比 SMB/RDP 漏洞,建议所有使用 Windows Server 服务器的企业或机构尽快打上补丁。

此文参考了 CheckPoint 发布的漏洞细节,如果你熟悉 DNS 协议,建议直接肝 CheckPoint 那篇文章,文章链接我文末会贴出。

漏洞简述

简言之就是整数溢出后导致堆溢出,将该漏洞抽象为以下模型:

void Examples(char* in)
{
    unsigned short len_p1 = len(in.p1);
    unsigned short len_p2 = len(in.p2);
    unsigned short len_p =  len_p1 + len_p2;
    char* buf = (char *)malloc(len_p);
    memcpy(buf, in.p1, len_p1);   
}

问题就出在 len_p 的计算上,假设 len_p1 = 0xFFD0,len_p2 = 0x40,相加后高位数据被丢弃,那么 0xFFD0 + 0x40 = 0x10010 = 0x10,所以实际分配的内存是 0x10 而不是 0x10010,而之后往 0x10 的空间内拷贝 0xFFD0 长度的数据,自然造成了内存溢出。

碰巧的是在 dns.exe 的 SigWireRead 函数内,也存在这种模型的溢出漏洞,如图:

触发前提

dns.exe 为一个 SYSTEN 权限的系统服务,该文件只存在于开启 DNS 功能的 Windows Server 服务器上。据 CheckPoint 的描述只有当 Windows Server 上的 DNS 服务接收到类型为 SIG/RRSIG 的 DNS 响应包时,才能进入到可以触发此漏洞的 SigWireRead 函数内。

DNS 服务器收到响应包?DNS 服务器不应该是回复客户端的查询而发送响应包嘛,话虽如此,但如果将 DNS 服务器也当成一个客户端,不就可以发送查询请求并接收响应数据了呀,而 DNS 转发 恰好能够让 DNS 服务器充当客户端,向上级的 DNS 服务器发起查询。

所以该漏洞触发的前提条件是:通过 DNS 转发接收一条 SIG/RRSIG 类型的 DNS 响应包

DNS 协议

DNS 整体结构包括头部区域、查询区域、响应区域、认证区域和附加区域,后面两个暂时用不上,不赘述。结构图如下:

DNS 头部区域固定 12 字节,同一个查询和响应的 ID 是一样的,QR 指明当前是查询还是响应,OPCODE 指明是用标准查询还是反向查询,TC 表示 UDP 长度大于 512 时截断并用 TCP 再次请求(漏洞关键因素),QDCOUNT 表示查询的数量,ANCOUNT 表示响应的数量,结构图如下:

DNS 查询区域主要由欲查询的域名、查询类型和查询类组成,其中最最最重要的是域名不是普通的字符串,而是多个 length + string 的形式,结尾以 length=0 为标志,结构图如下:

DNS 响应区域由资源记录的域名,RDATA 的类型、RDATA 类、TTL 值、RDATA 长度和 RDATA 组成,结构图如下:

必须强调下 DNS 中域名的表示方式:(length + string) * n,这种表示方式是该漏洞成因的关键因素之一:

SIG 类型

资源记录(RR)有多种类型,比如说最常用的 A 类型返回的是 IPv4 地址记录。而 dns!SigWireRead 只有在读取 SIG 类型响应包时才有机会触发 CVE-2020-1350 漏洞。SIG 资源记录的结构图如下:

简单说下各个字段的含义:

  • type covered(2 octet): RRs的类型
  • algorithm(1 octet):签名算法
  • labels(1 octet):域名通配规则
  • original TTL(4 octet):受签名保护的TTL
  • signature expiration(4 octet):签名有效的起始时间
  • signature inception(4 octet):签名有效的截至时间
  • key tag(2 octet):公钥模数或RR的简单校验和
  • signer's name:签名者的域名,可被压缩
  • signature:签名数据序列

域名压缩

域名压缩是 DNS 协议节省空间的一种方式,因为请求中的域名字符会频繁出现在响应包的内,故采用了一种标志+偏移的格式来压缩域名。压缩方式很简单:1 + 1 + offset,即最高 2bit 位为 1 时代表了将采用域名压缩,剩余的组合为偏移值(基地址为 DNS 头),如下图:

响应区域中的 Name 字段为 0xc00c,明显是被压缩过的,最高 2bit 位是标志,剩余的 bit 位组合得到的值为 0xc,所以当前 Name 表示的字符串在 DNS 头部向后偏移 0xc 的位置,也就是图中 0x3 开始(0x50 表示 DNS 头起始位置)的域名 bbs.pediy.com

TCP 响应

dns!SigWireRead 函数内溢出点的阈值为 65535(寄存器低 2 字节),且在计算 Buffer 长度的加法中,另外的两个值不会很大,所以只有当发送的数据长度接近 65535 时才有机会造成溢出。可是 DNS 协议默认使用 53 号 UDP 端口,此时发送的数据不能超过 512 字节,超过后会被截断。

CheckPoint 找到一种方法可以突破上述限制:UDP 响应时将 DNS 头部的 TC 标志置 1,之后客户端便会尝试用 TCP 协议和服务器建立连接,并通过 TCP 协议传递数据。TCP 协议在 53 端口上可传输长度最大为 65535 的 DNS 数据,这无疑给该漏洞创造了先决条件。

数据构造

万事俱备,只欠东风,如何构造 DNS 响应包的数据就是最后一道坎了。

图一中可以看到长度计算的公式为:v10 + v13 + 0x14,v13 为 SIG RR 中的 signature 长度,v10 为 SIG RR 中 signer's name 需要分配的空间。加法的结果不超过 0xffff,如果使得 v10 + v13 + 0x14 > 0xffff,即可造成整数溢出并导致堆溢出。可是正常情况下是不可能会溢出的,因为 TCP 传输时所有 DNS 数据最长为 0xffff,那么 v13 = 0xffff - 12 - len(queries) - 20,且 v10 的值应该为 0(因为 0xc00c 表当前域名与查询时的域名相同,而 v10 会减去查询时域名的长度),若指定查询的域名为 a.cn,计算得到 len(queries) 的最小值为 10,代入公式死活都不可能溢出。

正常的 DNS 响应数据自然是不可能溢出的,但精心构造的就不一定了哦。v10 的值主要是由 Name_PacketNameToCountNameEx 函数决定,该函数计算的方式是将 (length + string) * nlength * n 取出并返回(实际可能有所差别,大概是这样子),如果将域名压缩的偏移值指向一片可控的区域,而不是指向查询时域名,就完全有可能使得 v10 > 0

比如将默认域名压缩 0xc00c 改为 0xc00d 后,你再算算之前的公式呢,v10 取个最小值 0x62 - 0xb = 0x57,len(queries) 取个稍大的值 0x20,代入公式化简得到 v10 + v13 + v14 = 0x57 + 0xffff - 12 - 0x20 - 20 + 0x14 = 0x1002A,此时的整数溢出将导致堆溢出,并最终造成 DNS 服务 Crash,如果配合内存地址泄露和任意地址读写,就可以 RCE 了。下图是修改域名压缩后计算过程的对比:

核心总结

鄙人虽然没复现(实在是没空折腾了),但是理论上可行的数据包还是有的,熬夜肝出这篇文章主要是想混个助攻(万一有人看了就写出 POC 了呢)。截个代码片段,以免被喷,如图:

以下是我构造 POC 过程的一些关键点:

  • Windows Server A 设置转发器到 B,并绑定域名 b,随便找个客户端将 DNS 服务器指向 A,然后 nslookup -type sig 域名 b,此时 A 会向 B 发起 SIG 类型的查询,并期待收到 B 的 SIG 类型响应
  • UDP 响应包头部的 TC 标志置 1,并在 53 端口上同时监听 UDP 和 TCP 协议
  • 将 SIG RR 中 signer's name 字段设置为 0xc00d,其它的也行,但高 2bit 必须都为 1,且偏移位置需要控制。要是能将偏移指向 signature 更好,因为 signature 能随便填数据,理论上想要啥就有啥

参考链接

[赠书活动] 《云计算安全》和《云存储安全实践》上线!老师留下通讯地址,即可获得赠书一套!送100套,送完为止!

最后于 1天前 被年少无知编辑 ,原因:


文章来源: https://bbs.pediy.com/thread-260712.htm
如有侵权请联系:admin#unsafe.sh