CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析

  • A+
所属分类:安全文章


一、漏洞信息

1. 漏洞简述

  • 漏洞名称:Windows TCP/IP Denial of Service Vulnerability
  • 漏洞编号:CVE-2020-16899
  • 漏洞类型:Read out of Bound
  • 漏洞影响:Denial of Service
  • CVSS评分:7.5
  • 利用难度:Medium
  • 基础权限:不需要

2. 组件概述

TCP/IP是Internet上使用的通信协议。在Windows的早期版本中,TCP/IP是一个单独的可选组件,可以像其他任何协议一样删除或添加。从Windows XP/Server 2003开始,TCP/IP成为操作系统的核心组件,无法删除。将TCP/IP作为Windows的核心组件是非常有意义的,因为它的功能在Microsoft Windows Server上对网络操作和Active Directory域环境尤为重要。整个Active Directory架构基于DNS层次结构,依赖于TCP/IP 传输协议 。

Microsoft Windows中的TCP/IP功能在内核级别运行,并由驱动程序tcpip.sys提供。该驱动程序处理所有传入和传出的TCP/IP通信信息,包括解析从网络接口接收到的数据包,并将其传递给更高级别的组件。

3. 漏洞利用

该漏洞主要是由于Windows TCP/IP堆栈在处理选项类型为31(0x1f,DNS搜索表选项)的ICMPv6的路由广播数据包时,处理逻辑存在越界读,导致拒绝服务漏洞。攻击者成功利用该漏洞可使目标主机失去响应,但无法直接进行任意代码执行或权限提取。

4. 漏洞影响

• Microsoft Windows 10 1709
• Microsoft Windows 10 1803
• Microsoft Windows 10 1809
• Microsoft Windows 10 1903
• Microsoft Windows 10 1909
• Microsoft Windows 10 2004
• Microsoft Windows Server 2019
• Microsoft Windows Server, version 1903
• Microsoft Windows Server, version 1909
• Microsoft Windows Server, version 2004

5. 解决方案

1. 微软官方补丁

微软官方针对该漏洞已发布安全更新补丁,补丁地址:

https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-16899


2. 深信服解决方案



 【深信服终端检测平台EDR】EDR已完成漏洞规则库的更新,支持该漏洞的检测及补丁分发。EDR3.2.10及以上版本需要升级相应的SP包,更新漏洞补丁规则库版本到20201020171823及以上版本,即可检测漏洞并分发补丁进行漏洞修复。联网用户可直接在线更新,离线升级包及漏洞补丁规则库已上传至深信服社区,有需要的用户请到深信服社区下载。


3. 缓解措施

管理员启动powershell或cmd,输入以下命令检查所有网络IPv6接口的列表以及相应的索引号:

netsh int ipv6 sh int

样例输出如下:

CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析

确认网络接口的RDNSS功能开启情况:

netsh int ipv6 sh int Idx number

CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析


执行以下命令关闭RA DNS配置功能(将Idx number替换为要关闭的网络接口的Idx值):

 netsh int ipv6 set int Idx number rabaseddnsconfig=disable

样例输出如下:

CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析


此时再次确认接口的RA DNS开启情况,已被关闭:

CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析


二、漏洞复现

1. 环境搭建

  • 靶机:Windows 10 1809 x64

  • 靶机操作:使用verifier开启tcpip.sys的验证


CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析

2. 复现过程

  1. 获取目标主机的IPv6地址和MAC地址

  2. 攻击机python3运行poc:


    CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析

  3. 靶机crash:


CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析

三、漏洞分析

1. 基本信息

  • 漏洞文件:tcpip.sys
  • 漏洞函数:Ipv6pUpdateDNSSL()函数
  • 漏洞对象:ICMPv6路由广播中的Option结构(DNS Option)

2. 背景知识

(限于篇幅问题,此处不对用于DNS配置的IPv6路由广播进行详细介绍,更详细资料可参考RFC8106)

1. 基本知识

IPv6 Router Advertisment (RA) options,也称为DNS RA options,允许IPv6的路由器向IPv6的主机广播DNS Recursive Server Address(DNS递归路由器地址)列表和DNS Search List(DNS搜索列表),其主要用途为在IPv6的主机上进行DNS名称解析以及域后缀的处理。

IPv6 Neighbor Discovery(ND,IPv6邻居发现)和IPv6 Stateless Address Autoconfiguratioin(SLAAC,IPv6无状态地址自动配置)提供了使用一个或多个IPv6地址,默认路由器以及一些其他参数配置固定节点或移动节点的方法。

当漫游主机每次连接到另一个网络时,无法进行手动配置。虽然可以进行静态配置,但是在诸如笔记本电脑之类的通用主机上通常不建议这样操作。例如,如果主机运行直接连接到全局DNS的自己的递归名称服务器,那么本地定义的名称空间对主机来说就不可用了。访问DNS是几乎所有主机的基本要求,因此IPv6 SLAAC在没有任何DNS配置支持的情况下,不能在任何实际的网络环境中单独作为替代部署模型。

对于IPv4环境中的DNS服务器来说,这些问题都很容易解决。但是对于IPv6的网络环境,这些问题显得比较棘手。因此,RFC8106定义了一种基于DNS RA选项的机制,以允许IPv6主机执行自动DNS配置。

在通过IPv6 SLAAC自动配置IPv6主机地址并且没有DHCPv6基础结构或一些主机没有DHCPv6客户端的网络环境中,可以使用基于RA的DNS配置作为替代。但是,对于需要分发其他信息的网络,可能仍然会使用DHCPv6。在这些网络中,可能不需要基于RA的DNS配置。基于RA的DNS配置允许IPv6主机获取主机连接到的链接的DNS配置(即DNS递归服务器地址和DNSSL)。此外,主机会从提供链接配置信息的同一RA消息中学习此DNS配置。

2. 名词解释

  • Recursive DNS Server (RDNSS):递归DNS服务器,提供递归DNS解析服务的服务器,用于将域名转换为IP地址或解析成RFC1034和RFC1035中定义的PTR记录。
  • RDNSS Option:一个用于向IPv6主机传送RDNSS信息的IPv6的RA option【RFC4861】。
  • DNS Search List (DNSSL):IPv6主机在执行DNS查询搜索时使用的DNS后缀域名列表,用于搜索简短的不合格域名。
  • DNSSL Option:一个IPv6 RA选项,用于将DNSSL信息传递到IPv6主机。

3. 详细分析

1. 基础分析

RFC8106标准化了DNSSL Option,该结构中包含DNS搜索列表(DNSSL),保证与DHCPv6 option保持相同的奇偶校验,并确保具备确定搜索域的必要功能。

1. 邻居发现扩展

RFC8106中定义的在邻居发现中使用的IPv6 DNS配置算法需要用到2种ND options:RDNSS optionDNSSL option。与该漏洞相关的是DNSSL Option,另外一种则与 CVE-2020-16898相关。

2. DNSSL Option Structure

DNSSL Option包含一个或多个DNS后缀,所有的domain name使用相同的Lifetime。如果需要不同的Lifetime值,则需要多个DNSSL Option结构。

DNSSL Option总体结构如下:

Offset Size(bytes) Field Descriptioin
0x00 1
Type 8-bit,DNSSL Option type identifier,0x1f(31)
0x01 1
Length option长度(包括"Type"和"Length"字段),以8个八位位组为单位。最小值为2,此时option中仅有1个domain name。
0x02 2
Reserved 保留字段
0x04 4
Lifetime DNSSL中的domain name可用于名称解析的最长时间(以秒为单位)。默认情况下,该值至少为3 * MaxRtrAdvInterval,其中MaxRtrAdvInterval是RFC4861中定义的最大RA间隔。0xffffffff表示无穷大, 零值意味着必须不再使用domain names。
0x08 8
Domain Names of DNS Search List 一个或多个domain name。

Domain Names of DNS Search List字段中的domain name的编码要遵循RFC1035的3.1节中定义的格式:对于Length字段,如果option中仅有一个domain name,则为最小值2。

CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析

多个domian name直接相连。

3. Procedure in IPv6 Hosts

当主机接收到RA消息中的DNS的options时,其处理过程如下:

  1. 首先检查Lengh字段的合法性:是否大于等于最小值2;
  2. 如果以上验证通过,则主机应按顺序将选项的值复制到DNS存储库和解析器存储库中。否则,主机必须丢弃这些选项。
4. Crash分析

首先分析dmp文件,查看crash现场:

CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析


CallStack直接给出了函数的调用链:

Icmpv6ReceiveDatagrams() -> Ipv6pHandleRouterAdvertisement() ->Ipv6pUpdateDNSSL() -> GetNextSuffixFromOption()

最终是在GetNextSuffixFromOption()函数中报了内存页错误,导致最终的crash。

5. 漏洞原因

Windows IPv6堆栈为DNSSL中的每个domain name分配一个256字节的buffer。RFC 1035将域名限制为255个字节,因此domain name长度加上末尾的空字符刚好可以满足buffer的大小要求。但是,漏洞代码处理该部分数据时,其上限等于DNSSL Option中的剩余字节,可以超过256个字节。因此,漏洞代码可能会错误地消耗比为buffer分配的字节更多的字节,从而导致越界读。如果buffer位于一个内存页的末尾,则该OOB读取就会导致BSOD。

2. 漏洞函数分析

分析使用的文件为Windows 10 1809 x64的tcpip.sys文件,版本为10.0.17763.316

经过简单分析可以确认,调用链的顶层函数Icmpv6ReceiveDatagrams()没有实质性的与漏洞触发密切相关的处理逻辑,故而跳过。

1. Ipv6pHandleRouterAdvertisement()

Ipv6pHandleRouterAdvertisement()函数中先对传入的RA消息做预处理,然后根据不同类型的option进入不同的处理流程:

CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析


2. Ipv6pUpdateDNSSL()

首先读取Option结构的数据:

CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析


接下来,确认读取的数据后,处理后缀部分:

CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析


这里在计算Suffixes的长度时,BytesToRead会被限制在0x100字节长度范围内,也就是Suffixes的最大长度为256。然后调用GetNextSuffixFromOption()函数读取Suffix。

3. GetNextSuffixFromOption()

在该函数中,在解析完一个DNS记录后,代码逻辑会来到以下位置:

CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析


这里的主要作用是跳过Domain Name中的空字符部分,遇到0就跳过,读取下一个数据,直到遇到非0值。但是在进行边界检查时,使用的条件参数BytesToRead_1是可以进行控制的,从而可以实现绕过,进行越界读。

3. 动态分析

Ipv6pUpdateDNSSL()函数下断,然后发送poc后断下,检查CallStack,确认断点触发流程与静态分析中的函数调用链一致:

CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析


此时的各寄存器情况如下:

CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析


这里重点看下rdx寄存器中的内容(漏洞函数的第2个参数):

CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析


rdx中存放的是一个_NET_BUFFER结构,其详细结构如下:

typedef struct _NET_BUFFER {  union {    struct {      PNET_BUFFER Next;      PMDL        CurrentMdl;      ULONG       CurrentMdlOffset;      union {        ULONG  DataLength;        SIZE_T stDataLength;      };      PMDL        MdlChain;      ULONG       DataOffset;    };    SLIST_HEADER      Link;    NET_BUFFER_HEADER NetBufferHeader;  };  USHORT                ChecksumBias;  USHORT                Reserved;  NDIS_HANDLE           NdisPoolHandle;  PVOID                 NdisReserved[2];  PVOID                 ProtocolReserved[6];  PVOID                 MiniportReserved[4];  NDIS_PHYSICAL_ADDRESS DataPhysicalAddress;  union {    PNET_BUFFER_SHARED_MEMORY SharedMemoryInfo;    PSCATTER_GATHER_LIST      ScatterGatherList;  };} NET_BUFFER, *PNET_BUFFER;

而且在其中找到了触发漏洞的ICMPv6的相关数据。继续向下,来到NdisGetDataBuffer()函数的第1处调用:

CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析


NdisGetDataBuffer()函数的第1个参数为传入的_NET_BUFFER结构。NdisGetDataBuffer()函数声明如下:

PVOID NdisGetDataBuffer(  PNET_BUFFER NetBuffer,	// [in], a pointer to a NetBuffer structure  ULONG       BytesNeeded,	// [in], the number of contiguous bytes of data requested  PVOID       Storage,		// [in, optional], a pointer to a buffer, or NULL if no buffer is provided by the caller  UINT        AlignMultiple, // [in], the alignment multiple expressed in power of two. For example, 2, 4, 8, 16, and so forth. If AlignMultiple is 1, then there is no alignment requirement.  UINT        AlignOffset	 // [in], the offset, in bytes, from the alignment multiple.);
// Return ValueA pointer to the start of the contiguous data or NULL.

如果NetBuffer参数指向的NET_BUFFER结构中的NET_BUFFER_DATA部分的DataLength字段的值小于BytesNeeded参数的值,那么函数返回NULL。函数执行完成后,返回结果如下:

CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析


返回的恰好为DNSSL Option的地址。

然后调用NetioAdvanceNetBuffer()函数。执行NetioAdvanceNetBuffer()函数之前,_NET_BUFFER的结构如下所示:

CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析


在执行完NetioAdvanceNetBuffer()函数后,结构变为:

CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析


此处该函数主要作用是前进8个字节进行数据读取,其整体流程及部分关键参数值如下:

CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析


继续向下,通过Length字段计算BytesToRead的长度,并对Lifetime的值进行是否为0xffffffff的检查:

CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析


后续会进行一些上下文初始化、事件记录等操作,然后进入循环,开始处理Option中的Suffixes。首先是BytesToRead的取值范围限定(限制为0x100字节):

CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析


然后使用NdisGetDataBuffer()函数读取Suffixes

CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析


在读取Suffixes之前,BytesToRead的值会被设置为0x100:

CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析


但是在读取完Suffixes后,此时的BytesToRead的值使用r15进行重新赋值为0x110:

CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析


然后调用GetNextSuffixFromOption()函数,该函数关键处理逻辑如下:

CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析


其参数中有受控参数,可以在上图代码处实现越界读。函数执行前各参数值如下:

CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析


rcxSuffixes的地址,rdx为要读取的字节数,r8Suffix的一个buffer,r9的作用暂时未知,其值为0,推测可能用于读取时的计数:

CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析


在进行过memcpy后,buffer中的情况如下:

CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析


此时已完成第一个Domain Name信息的复制。

后续继续执行,来到处理第2个Domain Name逻辑处:

CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析


第2次调用GetNextSuffixFromOption()函数:

CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析


剩余流程大致与第1次相同,并利用memcpy读取Suffix到buffer中:

CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析


接下来进入一个循环:

CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析


r14中存放的是Suffixes,其中存放了大量的0,dil寄存器中为0,第1处cmp恒成立。而在esi中存放的是BytesToRead,该值受控,在正常读取完第2个Domain Name后,该值为0x101。此时会继续处理后续的0字符,理论上应该在256范围内,但是因为BytesToRead的值设置为大于0,所以会越界读:

CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析


在执行到esi = 8时,r14中的数据已不可读:

CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析


此时再继续执行就会造成crash:

CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析


也就是说,因为BytesToRead的受控,尝试去进行了越界读取,但是读取到了内存页末尾无法再进行正常读取,从而导致crash。

4. 利用思路

1. 利用条件
  1. 基本条件

    • attacker需要获取target的IPv6和MAC地址
  2. 触发过程

    • attacker可以直接发起远程攻击
2. 利用过程

attacker直接发送特制的ICMPv6路由广播数据包给target:

[ Attacker ] <--------------------> [ Target ]
3. 攻击向量

建立连接后,利用IPv6直接发送攻击数据包即可。

5. 流量分析

因为该漏洞直接走的IPv6,所以对于一些部署在IP层以上的防火墙方案就无法针对该漏洞进行流量检测,但是具备IP层流量检测的防火墙可以轻松检测恶意流量:

CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析


使用大量的0进行填充以触发漏洞。

6. 补丁分析

1. 补丁对比结果

针对Ipv6pUpdateDNSSL()函数的补丁对比结果如下:

CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析


2. 补丁思路

根据补丁对比结果,微软新增了对BytesToRead值的校验,在读取完Suffixes之后,为确保不发生越界读,新增了一次对BytesToRead的校验,确保小于0x100。而在漏洞分析中,触发漏洞时该值是大于0x100的。

3. 补丁验证

使用安装更新补丁后的Ipv6UpdateDNSSL()函数进行验证,新增保证BytesToRead的值最大为0x100的代码:

CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析


四、参考文献

  1. https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-16899
  2. https://tools.ietf.org/html/rfc8106



本文始发于微信公众号(深信服千里目安全实验室):CVE-2020-16899: Windows TCP/IP拒绝服务漏洞分析

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: