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的优化
概念验证代码
|
|
这个漏洞利用成功触发了堆栈溢出,导致系统蓝屏,证明了该漏洞的严重性和可利用性。