CVE-2020-16899: Microsoft Windows TCP/IP Denial of Service Vulnerability
CVSS Score: 7.5
CVSS Vector: CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:N/A:H/E:P/RL:O/RC:C
On October 13, Microsoft announced a critical vulnerability in the Windows IPv6 stack, which allows an attacker to send maliciously crafted packets which results in an immediate BSOD (Blue Screen of Death), on the most recent versions of Windows 10 and Windows Server 2019. While this vulnerability does not appear to grant code execution to an attacker, it could be used to do mass denial-of-service attacks to vulnerable Windows versions. Detection logic for the more impactful RCE version of this vulnerability can be found at CVE-2020-16898: "Bad Neighbor".
This document has been prepared by McAfee Advanced Threat Research. It is intended to provide valuable insights for network administrators and security personnel, looking to further understand this vulnerability and to defend against exploitation. The signature produced here should be thoroughly considered and vetted in staging environments prior to being used in production and may benefit from specific tuning to the target deployment.
The information provided herein is subject to change without notice, and is provided "AS IS", with all faults, without guarantee or warranty as to the accuracy or applicability of the information to any specific situation or circumstance and for use at your own risk. Additionally, we cannot guarantee any performance or efficacy benchmarks for any signatures.
The vulnerability is the result of an out-of-bounds read that can occur when the Windows IPv6 stack processes ICMPv6 Router Advertisement (Type = 134) packets containing one or more DNSSL Option records (Option Type = 31). The purpose of the DNSSL record is to provide a search list of DNS name suffixes, which are contained within its last field. Since this Search List can hold multiple null-terminated DNS names back-to-back, the field (and thus the entire record) can vary widely in size. To accomodate this, the DNSSL Option record contains its own Length field. However, since the Length is counted in 8-byte increments, at least one of the domain names in the Search List may have additional null-padding to preserve 8-byte alignment of the record. It's in the processing of these nulls that the vulnerability can be found.
For each domain name in the Search List, the Windows IPv6 stack allocates a 256-byte buffer. Since RFC 1035 limits domain names to 255 bytes, this would typically be enough to contain a domain name plus its null terminator. However, the code responsible for consuming the trailing nulls at the end of each domain name has an upper bound equal to the remaining bytes in the Option, which can exceed 256 bytes. The result is that the null-consuming code can incorrectly consume more bytes than were allocated for the buffer, resulting in an out-of-bounds read. In the case where the buffer falls at the end of a memory page, this OOB read can result in a BSOD.
The Suricata signature for this vulnerability is located in cve-2020-16899.rules and contains the following logic:
alert icmp any any -> any any (msg:"Potential CVE-2020-16899 Exploit"; lua:cve-2020-16899.lua; sid:202016899; rev:1;)
The corresponding Lua script may be found in cve-2020-16899.lua. It contains the logic necessary to properly parse the ICMPv6 layer and identify potentional exploitation of CVE-2020-16899, as follows:
Once we've located the start of the ICMPv6 layer, we test the first byte of the layer to ensure that it's a Router Advertisement ICMPv6 packet - if it isn't, we exit.
Since Suricata primitives have not been updated to parse the ICMPv6 Options, we simply jump to the 17th byte of the ICMPv6 layer, since that's where the Options should start, if present (the first 16 bytes are static-length fields, per RFC 4443). From there, we loop over every Option until we run out of bytes in the packet. For each Option, we begin by inspecting the first byte, which corresponds to the Option Type field. While we ignore all Options that aren't DNSSL, for Option Type = 31 (DNSSL), we check to see if the Length (second byte in the Option) is greater than or equal to 35, the minimum length needed to trigger the vuln:
- If it is, we jump ahead to the DNS Search List field and compute the length of each DNS name (including optional null padding) contained within. Testing revealed that exploitation requires a DNS name that is at least 264 bytes long (including padding), so we flag any packets that meet this and the other aforementioned criteria.
- If it isn't, we move on to the next Option. Since the Length is counted in increments of 8 bytes, we multiply the Length by 8 and jump ahead that many bytes to get to the start of the next Option (subtracting 1 to account for the length byte we've already consumed).