CVE-2020-16898 – “Bad Neighbor"漏洞利用分析
引言
在2020年10月的补丁星期二(10月13日),微软修复了一个非常有趣(且具有威胁性)的漏洞:CVE-2020-16898 – Windows TCP/IP远程代码执行漏洞。微软对该漏洞的描述如下:
“当Windows TCP/IP堆栈不当处理ICMPv6路由器通告数据包时,存在远程代码执行漏洞。成功利用此漏洞的攻击者可以在目标服务器或客户端上执行代码。要利用此漏洞,攻击者必须向远程Windows计算机发送特制的ICMPv6路由器通告数据包。该更新通过纠正Windows TCP/IP堆栈处理ICMPv6路由器通告数据包的方式来解决此漏洞。”
漏洞信息收集
最初,作者希望了解更多关于该漏洞的信息。唯一能找到的额外信息是检测逻辑提供的分析报告。有趣的是,关于如何防御攻击的信息反而有助于漏洞利用。
关键信息如下:
- 需要发送RDNSS数据包
- 问题出现在Length字段为偶数时
- 负责解析数据包的函数会将RDNSS负载的最后8字节引用为下一个头部
RDNSS技术分析
递归DNS服务器选项(RDNSS)是路由器通告(RA)消息的子选项之一。RA可以通过ICMPv6发送。RDNSS的格式如下:
|
|
Length字段描述:8位无符号整数。选项的长度(包括Type和Length字段)以8字节为单位。如果选项中包含一个IPv6地址,最小值为3。每个额外的RDNSS地址会使长度增加2。
漏洞触发条件分析
问题1 – IPv6链路本地地址
漏洞只能在源地址为IPv6链路本地地址时被利用。这一要求限制了潜在的目标范围。
问题2 – 正确的头部类型
通过逆向工程发现,代码中存在两个循环:
- 第一个循环遍历所有头部并进行基本验证(长度大小等)
- 第二个循环不再进行验证,而是解析数据包
当缓冲区中有更多"可选头部"时,就会进入循环。这是一个很好的利用原语。
问题3 – 数据包大小验证
在验证数据包大小时,所有定义的"长度"必须与数据包大小匹配。通过偶数Length值会产生8字节的差异,需要精心构造数据包布局来绕过此检查。
问题4 – 大小再次验证
即使成功绕过初始检查,仍需要确保"伪造头部"的Length值与数据包剩余数据大小匹配。通过减小"伪造头部"的大小并向数据包附加更多数据来解决此问题。
问题5 – NdisGetDataBuffer()和分片
最大的挑战来自NdisGetDataBuffer函数的优化行为。如果请求的缓冲区数据是连续的,返回值是NDIS提供的位置指针;如果数据不连续,则会将数据复制到Storage参数指定的缓冲区。
解决方案是对数据包进行分片,迫使NDIS将数据复制到本地堆栈缓冲区,从而触发栈溢出。
概念验证代码
|
|
技术总结
该漏洞的利用需要满足多个严格条件:
- 源地址必须是IPv6链路本地地址
- 整个负载必须是有效的IPv6数据包
- 需要精心构造数据包布局以通过大小验证
- 必须使用分片来绕过NDIS优化
成功利用后,攻击者可以在目标系统上实现内核级内存破坏,最终导致系统崩溃或可能执行任意代码。