IIS/HTTP协议
Request = Request-Line headers CRLF [message-body]
Request-Line = Method SP Request-URI SP HTTP-Version CRLF
Headers = *[Header]
Header = Field-Name ":" Field-Value CRLF
其中CRLF代表新的行序列回车符(CR),后跟换行符(LF),SP表示空格。参数可以作为请求URI或消息正文中的名称/值对,从客户端传递到服务器,具体取决于使用的Method和Content-Type标头。例如,一个简单的HTTP请求使用POST方法,传递一个名为“param”且值为“1”的参数,如下所示:
POST /ecp/default.aspx HTTP/1.1
Host: www.myhost.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 7
param=1
如果参数/值对不止一个,则将它们编码为&分隔的name=value对:
var1=value1&var2=value2
Accept-Encoding请求头
与CVE-2021-31166漏洞相关的是HTTP请求头“Accept-Encoding”。该请求头会将客户端能够理解的内容编码方式(通常是某种压缩算法),通知给服务端。服务端会选择一个客户端提议的方式进行使用,并在响应头“Content-Encoding”中通知客户端该选择。
内容编码可以用qvalue加权按优先顺序指定,qvalue加权描述以逗号分隔的列表中值的优先级顺序。
HTTP请求头“Accept-Encoding”字段具有以下格式:
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请求后,将为请求头中存在的每个“Accept-Encoding”调用HTTP!UlAcceptEncodingHeaderHandler例程。然后,该例程在“Accept-Encoding” 请求头的字段值上调用HTTP!UlpParseAcceptEncoding。
HTTP!UlpParseAcceptEncoding例程通过调用HTTP! UlpParseContentCoding来遍历Field-Value字符串。HTTP!UlpParseContentCoding例程以及其他功能,会返回对Field-Value中下一个内容编码字符串的引用,以进行下一次迭代。并确定当前对Field-Value字符串的引用是未知的、受支持的,还是无效的内容编码字符串。
有效的内容编码字符串的格式如上诉“Accept-Encoding”请求头字段值所具有的格式所示。它可以是未知的或受支持的。受支持的内容编码字符串是有效的内容编码字符串,指定IIS支持的压缩算法。
在以下“Accept-Encoding” 请求头示例中:
Accept-Encoding: gzip, aaaa, bbbb;
“gzip”是受支持的内容编码字符串,“aaaa”是未知的内容编码字符串,“bbbb;”则是无效的内容编码字符串,因为格式不正确。
在处理Field-Value字符串期间,HTTP!UlpParseAcceptEncoding例程维护未知内容编码的循环双向链接列表。但是,无效的内容编码字符串的存在,将导致HTTP错误请求服务器响应。
循环双向链接列表是一种数据结构,它同时显示了双向链表和循环链表的属性。在循环双向链接列表中,两个连续的节点通过上一个和下一个链接连接。截然不同的是,最后一个节点通过其下一个链接连接到第一个节点,而第一个节点通过其前一个链接连接到最后一个节点。如果循环双向链接列表仅包含一个节点,则下一个和上一个链接将连接到自身。
漏洞详情
HTTP 协议堆栈(HTTP.sys)中存在一个远程执行代码漏洞(CVE-2021-31166)。该漏洞是由于维护UlpParseAcceptEncoding中的循环双链表时存在设计缺陷导致的。
在HTTP!UlpParseAcceptEncoding例程中,未知内容编码列表被初始化为仅包含一个根节点,该根节点位于函数的堆栈存储区中。在处理Field-Value字符串时,会为分页的内存池中的未知内容编码分配后续节点。如果HTTP!UlpParseAcceptEncoding无法获取页面缓冲池中的内存,或者HTTP!UlpParseContentCoding确定内容编码字符串无效,则该例程将立即释放在页面缓冲池中分配的节点。除了一种情况外,其他所有情况都将调用HTTP!UlFreeUnknownCodingList例程,通过位于函数的堆栈存储区中的根节点进行释放。
HTTP!UlFreeUnknownCodingList例程按照节点被添加到循环双向链表中的顺序,取消链接并释放节点。它从第一个非根节点开始,对要删除的节点进行各种完整性检查,以确定循环双向链表是否已损坏。如果检查成功则释放该节点,否则该例程以__fastfail中止,从而导致内核崩溃。
完全解析Field-Value字符串中指定的所有内容编码后,如果未知内容编码列表包含其他节点,则这些附加节点将与驻留在函数堆栈内存区域中的根节点解除链接,并重新链接至内部结构中的根节点。在取消链接期间,HTTP!UlpParseAcceptEncoding无法重置初始根节点的下一个和上一个链接,以使它们连接到自身。因此,原始根节点的下一个和上一个链接仍连接到现在迁移的节点。
攻击者可以制作“Accept-Encoding” HTTP请求头,从而将未知的内容编码列表迁移到内部结构,而且还通过原始根节点传递给HTTP!UlFreeUnknownCodingList,导致无效的内容编码字符串被传递到HTTP!UlpParseContentCoding。发生这种情况的原因是,当前正在处理的内容编码字符串中唯一的非制表符是逗号字符(,)时,HTTP!UlpParseContentCodingj将返回特殊错误代码0x0c0000225。该错误代码不会导致HTTP!UlpParseAcceptEncoding中的未知内容编码列表被立即释放。在处理错误之前,未知的内容编码列表仍将迁移到内部结构中。
由于原始根节点的下一个和上一个链接仍连接到已迁移的节点,因此将对通过原始根节点的下一个链接连接的第一个添加节点执行释放操作,因为对节点执行的完整性检查成功。但是,由于第一个节点是通过其先前的链接连接到内部结构的根节点的,因此HTTP!UlFreeUnknownCodingList例程将该节点从内部结构的根节点取消链接,而不是传递原始根节点。
在下一次迭代中,由于该例程将解析传入的初始根节点,因此将再次释放同一节点。这次,发生了非崩溃的释放后使用,在节点上再次执行了完整性检查,但这一次它们失败了。这是因为例程在检查与已删除和未链接的节点的上一个链接连接的节点时,会找到内部结构的根节点。但是,当它检查连接到内部结构根节点的下一个链接的节点时,会发现该节点与指定释放的节点不同。HTTP!UlFreeUnknownCodingList例程通过__fastfail与FAST_FAIL_CORRUPT_LIST_ENTRY中止。这将导致蓝屏崩溃(BSOD),并显示KERNEL SECURITY CHECK FAILURE停止代码。
总结
未经身份验证的远程攻击者可以通过在Web请求中,向目标系统发送特制的Accept-Encoding请求头来利用该漏洞。攻击者可以利用该漏洞进行DoS拒绝服务攻击,甚至还能以内核权限执行任意代码。
END
本文始发于微信公众号(SecTr安全团队):CVE-2021-31166:HTTP.SYS中的蠕虫级代码执行漏洞
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论