标题 简介 类型 公开时间
关联规则 关联知识 关联工具 关联文档 关联抓包
参考1(官网)
参考2
参考3
详情
[SAFE-ID: JIWO-2021-2798]   作者: Hello大鱼 发表于: [2020-12-11]

本文共 [25] 位读者顶过

一、漏洞信息

[出自:jiwo.org]

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 1803Microsoft Windows 10 1809Microsoft Windows 10 1903Microsoft Windows 10 1909Microsoft Windows 10 2004Microsoft Windows Server 2019Microsoft Windows Server, version 1903 Microsoft Windows Server, version 1909 Microsoft Windows Server, version 2004 

5. 解决方案


微软官方针对该漏洞已发布安全更新补丁,补丁地址:https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-16899


二、漏洞复现


1. 环境搭建

靶机:Windows 10 1809 x64

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



2. 复现过程

(1)通过各种手段获取目标主机的IPv6地址和MAC地址(具体方法可自行探索,较为简单)
(2)攻击机python3运行poc:



(3)靶机crash:

三、漏洞分析


1. 基本信息

  • 漏洞文件:tcpip.sys

  • 漏洞函数:Ipv6pUpdateDNSSL()函数

  • 漏洞对象:ICMPv6路由广播中的option结构(DNS Option structure)

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保持相同的奇偶校验,并确保具备确定搜索域的必要功能。
a. 邻居发现扩展

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

b. DNSSL Option Structure

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

 DNSSL Option总体结构如下:



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

多个domian name直接相连。

c. Procedure in IPv6 Hosts
当主机接收到RA消息中的DNS的options时,其处理过程如下:
首先检查Lengh字段的合法性:是否大于等于最小值2
如果以上验证通过,则主机应按顺序将选项的值复制到DNS存储库和解析器存储库中。 否则,主机必须丢弃这些选项。
d. Crash分析
首先分析dmp文件,查看crash现场:



CallStack直接给出了函数的调用链: Icmpv6ReceiveDatagrams() -> Ipv6pHandleRouterAdvertisement() ->Ipv6pUpdateDNSSL() -> GetNextSuffixFromOption() 最终是在GetNextSuffixFromOption()函数中报了内存页错误,导致最终的crash。

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

(2)漏洞函数分析

分析使用的文件为Windows 10 1809 x64的tcpip.sys文件,版本为10.0.17763.316。 经过简单分析可以确认,调用链的顶层函数Icmpv6ReceiveDatagrams()没有实质性的与漏洞触发密切相关的处理逻辑,故而跳过。
a. Ipv6pHandleRouterAdvertisement()
在Ipv6pHandleRouterAdvertisement()函数中先对传入的RA消息做预处理,然后根据不同类型的option进入不同的处理流程:



b. Ipv6pUpdateDNSSL()
首先读取Option结构的数据以及Domain name的长度:



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


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

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



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

(3)动态分析

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



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

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

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处调用:

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 Value
A pointer to the start of the contiguous data or NULL.

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

返回的恰好为DNSSL Option的地址。 然后调用NetioAdvanceNetBuffer()函数。执行NetioAdvanceNetBuffer()函数之前,_NET_BUFFER的结构如下所示:

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

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

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

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

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

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

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

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

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

rcx为Suffixes的地址,rdx为要读取的字节数,r8为Suffix的一个buffer,r9的作用暂时未知,其值为0,推测可能用于读取时的计数: 
在进行过memcpy后,buffer中的情况如下:

此时已完成第一个Domain Name信息的复制。
后续继续执行,来到处理第2个Domain Name逻辑处:

第2次调用GetNextSuffixFromOption()函数:

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

%20

接下来进入一个循环:

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

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

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

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

4. 利用思路

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

    • attacker需要获取target的IPv6和MAC地址

  2. 触发过程

    • attacker可以直接发起远程攻击

B. 利用过程
attacker直接发送特制的ICMPv6路由广播数据包给target:


Attacker ] <--------------------> [ Target ]


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

5. 流量分析

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



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

6. 补丁分析

A. 补丁对比结果

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



B. 补丁思路

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

C. 补丁验证

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



四、缓解措施

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

netsh int ipv6 sh int

样例输出如下:

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

netsh int ipv6 sh int Idx number

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

netsh int ipv6 set int Idx number rabaseddnsconfig=disable

样例输出如下:

此时再次确认接口的RDNSS开启情况,RDNSS功能已被关闭:

五、漏洞检测和防御

A. 漏洞检测

针对该漏洞,目前暂未发现漏洞原理侧的无损检测。

B. 漏洞防御思路

流量防御:需要监控IPv6的流量传输,对于Type为134的路由广播数据包进行检测,确认其Type为0x1f的DNSSL的Padding部分是否有大于等于256个0字符。 终端防御:根据补丁对比结果,可以按照微软的补丁思路使用热补丁进行防御,对BytesToRead的值再加一次校验。

评论

暂无
发表评论
 返回顶部 
热度(25)
 关注微信