In this excerpt of a Trend Micro Vulnerability Research Service vulnerability report, Kc Udonsi and Yazhi Wang of the Trend Micro Research Team detail a recent code execution vulnerability in the Microsoft Internet Information Services (IIS) for Windows. The bug was originally discovered by the Microsoft Platform Security & Vulnerability Research team. The following is a portion of their write-up covering CVE-2021-31166, with a few minimal modifications.
The Internet Information Services (IIS) for Windows Server is a flexible, scalable, secure, and manageable web server for hosting static as well as dynamic content on the web. IIS supports various web technologies including HTML, ASP, ASP.NET, JSP, PHP, and CGI. IIS features an HTTP listener as part of the networking subsystem of Windows. This functionality is implemented as a kernel-mode device driver called the HTTP Protocol Stack (HTTP.sys). This driver is responsible for parsing HTTP requests and crafting responses to the clients.
Note that the driver http.sys is a mature technology that provides the robustness, security, and scalability of a full-featured web server. It is also used by Windows Remote Management WS-Management (WinRM) and Web Services for Devices (WSD) associated with Network Discovery. It could also be used by any other web servers that need to be exposed to the internet without using IIS. Therefore this vulnerability is reachable via any system utilizing http.sys.
HTTP is a request/response protocol described in RFCs 7230 - 7237 and other RFCs. A request is sent by a client to a server, which in turn sends a response back to the client. An HTTP request consists of a request line, various headers, an empty line, and an optional message body:
where CRLF represents the new line sequence Carriage Return (CR) followed by Line Feed (LF) and SP represents a space character. Parameters can be passed from the client to the server as name-value pairs in either the Request-URI or in the message-body, depending on the Method used and Content-Type header. For example, a simple HTTP request passing a parameter named “param” with value “1”, using the POST method might look like this:
If there is more than one parameter/value pair, they are encoded as &-delimited name=value pairs:
Of relevance to this vulnerability is the HTTP request header “Accept-Encoding”. This header advertises to a web server compression algorithm it can understand. The server selects a proposal from advertised choices, uses it, and informs the client of its decision using the HTTP response header “Content-Encoding”.
The advertised compression algorithms are known as content-coding. The content-codings could be specified in the order of preference with qvalue weighting which describes the order of priority of values in a comma-separated list.
The “Accept-Encoding” HTTP header Field-Value has the following format:
Example "Accept-Encoding" headers are as follows:
where the Field-Value strings are gzip, identity, *, deflate, gzip;q=1.0, *;q=0.5
In HTTP.sys the routines HTTP!UlAcceptEncodingHeaderHandler, HTTP!UlpParseAcceptEncoding and HTTP!
UlpParseContentCoding are responsible for parsing the "Accept-Encoding" HTTP request header.
Upon receiving an HTTP request, the routine HTTP!UlAcceptEncodingHeaderHandler is invoked for each “Accept-Encoding” header present in the request headers. This routine then invokes HTTP!UlpParseAcceptEncoding on the “Accept-Encoding” HTTP header Field-Value.
The HTTP!UlpParseAcceptEncoding routine walks the Field-Value string by invoking HTTP! UlpParseContentCoding. The HTTP!UlpParseContentCoding routine, among other functionalities, returns a reference to the next content-coding string within the Field-Value for the next iteration and determines if the current reference into the Field-Value string is either an unknown, a supported, or an invalid content-coding string.
A valid content-coding string is a well-formatted string according to the “Accept-Encoding” HTTP header Field- Value format illustrated above. It can either be unknown or supported. A supported content-coding string is a valid content-coding string specifying a compression algorithm supported by IIS.
In the following “Accept-Encoding” HTTP request header example:
Accept-Encoding: gzip, aaaa, bbbb;
“gzip” is a supported content-coding string, “aaaa” is an unknown content-coding string, and finally “bbbb;” is an invalid content-coding string because it is improperly formatted.
During the processing of the Field-Value string, the routine HTTP!UlpParseAcceptEncoding maintains a circular doubly linked list of unknown content-codings. However, the presence of an invalid content-coding string will result in an HTTP Bad Request server response.
A circular doubly linked list is a data structure that exhibits properties of both doubly linked lists and circular linked lists. In a circular doubly linked list, two consecutive nodes are connected by previous and next links. Distinctively, the last node connects to the first node by its next link and the first node connects to the last node by its previous link. If the circular doubly linked list contains only one node, the next and previous links connect to itself.
A remote code execution vulnerability exists in the HTTP Protocol Stack for Microsoft Internet Information Services implemented in http.sys. The vulnerability is due to a design flaw in the maintenance of a circular doubly linked list in UlpParseAcceptEncoding.
In the routine HTTP!UlpParseAcceptEncoding, the list of unknown content-codings is initialized to contain just a root node. This root node resides in the function's stack memory region. While processing the Field-Value string, subsequent nodes are allocated for unknown content-codings in the paged memory pool (i.e virtual memory addresses that can be paged in and out of the system). In the event that HTTP!UlpParseAcceptEncoding failed to acquire memory within the paged pool or that HTTP!UlpParseContentCoding determined that a content-coding string is invalid, nodes allocated within the paged pool will be freed and in all but one case, immediately by the routine HTTP!UlFreeUnknownCodingList via the root node residing on the function's stack memory region (original root node).
The routine HTTP!UlFreeUnknownCodingList unlinks and frees nodes in the order they were added to the circular doubly linked list. It begins with the first non-root node and will perform various integrity checks on the node to be deleted effectively in a bid to determine if the circular doubly linked list has been corrupted. If the checks succeed, the node will be freed otherwise the routine aborts with __fastfail leading to a kernel crash.
After fully parsing all content-codings specified in the Field-Value string, if the list of unknown content-codings contains additional nodes, the additional nodes are unlinked from the root node that resides on the function's stack memory region and relinked to a root node in an internal structure. During unlinking, HTTP! UlpParseAcceptEncoding failed to reset the next and previous links of the initial root node such that they connect to itself. Hence, the next and previous links of the original root node still connected to the now migrated nodes.
An attacker could craft an “Accept-Encoding” HTTP request header such that the unknown content-coding list is migrated to the internal structure but also passed to HTTP!UlFreeUnknownCodingList via the original root node as a result of an invalid content-coding string passed to HTTP!UlpParseContentCoding. This scenario occurs because a special error code 0x0c0000225 is returned by HTTP!UlpParseContentCoding when the only non-tab, non-space character in the content-coding string currently being processed is the comma character (,). This error code does not result in the immediate freeing of the unknown content-coding list in HTTP!UlpParseAcceptEncoding. The unknown content-coding list will still be migrated to the internal structure before the error is handled.
Since the original root node's next and previous links still connect to the migrated nodes, the free operation will be performed for the first added node connected by the original root's next link because the integrity checks performed on the node will succeed. However, since the first node is connected to the internal structure’s root node via its previous link, the routine HTTP!UlFreeUnknownCodingList unlinks this node from the internal structure root node instead of the passed in the original root node. On the next iteration, the same node is to be freed again since the routine parses the passed-in initial root node. This time, a non-crashing use-after-free occurs, the integrity checks are performed again on the node but this time they fail. This is because when the routine checks the node connected to the previous link of the already deleted and unlinked node, it finds the internal structure's root node. However, when it checks the node connected to the internal structure root node's next link, it finds a node different from the one specified for freeing. The routine HTTP!UlFreeUnknownCodingList aborts via __fastfail with FAST_FAIL_CORRUPT_LIST_ENTRY. This results in a blue screen of death (BSOD) with stopcode KERNEL SECURITY CHECK FAILURE.
A remote, unauthenticated attacker can exploit this vulnerability by sending a crafted Accept-Encoding HTTP header in a web request to the target system. Successful exploitation of this vulnerability can result in denial-of-service conditions or code execution with kernel privileges in the worst case.
Microsoft addressed this vulnerability in the May patch release cycle. They recommend prioritizing the patching of affected servers.
Special thanks to Kc Udonsi and Yazhi Wang of the Trend Micro Research Team for providing such a thorough analysis of this vulnerability. For an overview of Trend Micro Research services please visit http://go.trendmicro.com/tis/.
The threat research team will be back with other great vulnerability analysis reports in the future. Until then, follow the ZDI team for the latest in exploit techniques and security patches.
CVE-2021-31166: A Wormable Code Execution Bug in HTTP.sys