In March 2023, two vulnerabilities were discovered in HTTP: a local elevation of privilege vulnerability and a remote code execution vulnerability. In this analysis, we will focus on the local privilege escalation vulnerability.
Vulnerability Patch Analysis
Based on the summary of patches for the month published on the ZDI Blog, it was revealed that a local elevation of privilege vulnerability was discovered in HTTP. The vulnerability was an integer overflow vulnerability that was submitted to ZDI by a researcher.
Upon comparing the code for httt.sys before and after the update, we found a clear patch code for the integer overflow check, which is shown below:
After a preliminary analysis of the function, it appears to be a loop that saves the ServiceName to memory. The maximum number of ServiceNames is 0x40, and the patch aims to limit the length of a single ServiceName to less than 0xfffc.
However, there is some confusion regarding the subsequent UlpAddServiceNameToContainer function. This function already contains a check to ensure that the length of the ServiceName is less than 0xFFFFFFE8 to prevent memory overflow, which appears to be significantly different from the 0xfffc check implemented in the patch.
Upon further analysis of the code path, it seems that there are two ways to trigger the vulnerability patch function. The first is to use UlSetConfigGroupProperty or UlSetServerSessionProperty to set the properties of the local http service, while the second involves the authentication response packet from a remote client, specifically the ChannelBindConfig data in UlExtractAndAppendAuthenticationResponseInfo obtained by UlCaptureHttpResponse.
Typically, high system privileges are required to bind an http service to a local network port that faces the public internet. To trigger the patched code path, we attempted to set up a local http service.
We have provided a Proof of Concept (PoC) code for CVE-2023-28231 at the following GitHub link: https://github.com/numencyber/Vulnerability_PoC/tree/main/CVE-2023-28231.
To trigger the vulnerability, we referred to Microsoft’s documentation on httpAPI and utilized the HttpSetUrlGroupProperty function. By setting the second parameter to HttpServerChannelBindProperty and reverse-engineering some of the data structures, we were able to manipulate the length of the ServiceName that is restricted by the patch in the UlCaptureChannelBindConfig function. However, this manipulation did not appear to immediately result in any severe consequences.
Upon further analysis, we discovered the UlCopyChannelBindConfigToIrp function, which specifically parses and copies all data in the ChannelBindConfig structure to the buffer provided by the user. In its internal function UlpComputeChannelBindConfigSize, an overflow situation occurs during the calculation of the result, leading to a crash when copying the contents of ServiceName in ChannelBindConfig. The crash stack is as follows:
Although we have now identified a clear PoC code path, a question that arose during the analysis of the patch still remains: why did the patch limit the length of ServiceName to a WORD level of 0xfffc, rather than a DWORD level length of ((0xffffffff-otherlength)/0x40), considering only the overflow when calculating the length of ServiceName?
By examining the keyword ServiceName, we discovered that in other parts of the HTTP protocol, the length of ServiceName is always set to a WORD length. For instance, UlFindServiceNameInContainer and another related function UlpIsServiceContainerEquivalent both set the length of ServiceName to WORD. Therefore, it appears that ServiceName length is consistently set to WORD throughout the HTTP protocol.
Although the overflow we constructed occurred in UlpComputeChannelBindConfigSize, the root cause was an error in the length check of the original input of ServiceName in UlCaptureChannelBindConfig.
In the current PoC, the memory corruption ultimately occurs in the user-level buffer memory mapped to the kernel for HTTP, as shown in the figure below. This location appears to limit our ability to exploit the vulnerability.
As shown above, V12 is a pointer to the MDL. However, setting HttpQueryUrlGroupProperty directly through a local http service does not allow us to control the MDL->flags to include MDL_MAPPED_TO_SYSTEM_VA (0x01) or MDL_SOURCE_IS_NONPAGED_POOL (0x04).
In light of this, our options for exploiting the vulnerability appear limited. One possible approach is to search for other code paths in the http service that handle the memory length of ChannelBindConfig or ServiceName.
Looking back at the analysis process above, we currently trigger the overflow from the process set by http locally. However, there is another possible path to call UlCaptureChannelBindConfig through UlExtractAndAppendAuthenticationResponseInfo in UlCaptureHttpResponse, where Kerberos authentication must be used.
In this case, a process similar to UlCopyChannelBindConfigToIrp needs to be triggered from the client to the server to cause the overflow by computing the ServiceName length. Further analysis may be required to fully understand this calling method. It appears that UlFindServiceNameInContainer or UlpIsServiceContainerEquivalent in UlpCompleteAuthInfo, which has been analyzed thus far, do not directly cause memory damage.
Finally, even if we trigger some overflow process remotely as a client, it is critical that the memory that is eventually damaged by overflow is preferably system kernel memory that can be utilized, such as paged memory or non-paged memory, rather than locked user-level memory.