关于SLP几个漏洞的成因与利用,网上已经有彭博士非常精彩且详细的分享(vSphere攻防技法分享),这里不会做过多的赘述,写这篇文章的意义是想记录一下针对特定低版本ESXi场景下32位SLP的利用心路。网上的一些公开利用,在低版本情况下皆无法成功。
安全研究人员在对某个目标进行研究时,经常会遇到一个场景,研究人员在一个正在开发的功能中发现漏洞,但因此功能未完善,无法进一步利用的遗憾。
笔者遇到的正是类似于此类场景,不过不是正在开发的功能场景,而是一个漏洞的触发路径上存在另一个漏洞,两个漏洞的相互影响下,导致利用无法完成的悲伤。
以下是SLP的历年漏洞:
CVE编号 | 漏洞类型 |
CVE-2019-5544 |
堆溢出 |
CVE-2020-3992 |
UAF |
CVE-2021-21974 |
堆溢出 |
CVE-2022-31699 |
堆溢出 |
笔者最初选择的是CVE-2021-21974。
该漏洞存在于SLP的“目录代理通告”,目录代理 (DA) 是可选的 SLP 代理,用于存储和维护服务代理 (SA) 发送的服务广告的缓存。
在SLPParseSrvUrl
函数中,会解析“目录代理通告”中的URL字段,如下面代码所示。
问题出现在slider2 = strstr(slider1, "://")
。
漏洞分析
要分析这个问题,先从wireshark的抓包看起,下图是发生“目录代理通告”时捕获的数据包。
重点关注Scope List Length
字段,该字段的大小被定义为uint16,且紧跟在URL字段后面。
带着这些知识,来分析slider2 = strstr(slider1, "://")
的问题所在。
slider2 = strstr(slider1, "://")
的本意是解析URL字段,获取://
后面的内容。
URL的正常构造示例:service:daytime://www.mtjones.com
此时假设一个情况,如果发包时URL字段不带有://
,且Scope List Length
字段的值小于0x100。slider2 = strstr(slider1, "://")
遍历完URL后遇到Scope List Length
的x00,将x00视为URL字段的终止符,strstr
函数结束,未找到目标字符串,返回空。
如果Scope List Length
字段的值大于0x100,strstr
函数就会搜寻到Scope List Length
字段甚至Scope List
字段,在箭头所指的地方产生越界写。
悲剧的诞生
在走完有漏洞的SLPParseSrvUrl
函数后,紧跟着的是下面代码所示的SLPNetResolveHostToAddr
,该函数大意是将”www.test.com“之类的转为IP,这个Host从上述的URL
字段中获取。
正常URL如下:
很不幸,此时触发漏洞需要的URL不是正常的构造,要想触发漏洞且进行正常利用,需要满足以下几个条件:
1. Scope List Length
需要大于等于0x100;
2. Scope List
中"://"
出现的位置不能过于后面,不然会导致越界写入过多的内存,破坏下一个堆的结构。
这就构成了一个悖论,要想利用,Scope List Length
必须>=0x100,要想>=0x100,”://”后的数据要足够长,数据足够长又导致无法通过SLPNetResolveHostToAddr
,导致程序执行流转入下面所示的LABEL_3
中进行SLPBufferFree
(还有一种可能是申请一个足够长的域名,携带在”://”后进行解析,但是在实战中,有泄露信息的风险,这里不予考虑)。
接着又在下面进行了一次SLPBufferFree
,构成了无法避免的,完美的Double Free。
在稍微高一点的版本中进行了内部修复,在进行第一次 SLPBufferFree
后对指针进行了置零操作。
这次笔者换个漏洞,开始第二次利用尝试,这次选择CVE-2019-5544。
漏洞分析
Service Registration报文
该报文旨在注册相应的服务。
Service Request报文
该报文旨在定位服务并查询他们的信息。
正常交互逻辑是,先向SLP发送Service Registration报文,在SLP中注册某类服务。后续如果有用户想要查询该服务信息,就向SLP发送Service Request报文进行查询。而漏洞就诞生在向SLP进行查询时候的处理。
SLP会重新定义返回的信息包大小,该大小由你发送Service Request报文的langtaglen字段决定。
接着根据Service Request报文中请求的服务在SLPDataBase中查找,看该服务是否已经被注册,如果被注册就取出、读取服务信息到返回包中。
但很不幸,读取服务信息这一操作的读取大小,是由注册时服务的urllen决定的,其没有校验服务的urllen大小与返回包大小之间的差异。
漏洞利用
此部分很大程度上参考彭博士的利用手法,但不完全相同。
思路:
1. 泄露libc
2. 布局出任意写
3. 通过任意写覆写free_hook
清理内存碎片:
发送大量SLP的Service Request报文清理碎片。
泄露libc:
和彭博士的手法一样,要注意的是,在布局SLP SendBuffer和RecvBuffer的同时,也要一并布局SLPSocket结构体,在目标堆被放入LargeBin时立马修改对应SLPSocket的状态,将其转变为STREAM_WRITE_FIRST
,读回glibc地址。
在泄露libc时,涉及到修改SLPSocket,这是一个需要关注的点,修改操作破坏了SLPSocket链表的完整性,导致链表被断链,一些Socket连接无法被搜寻到,且无法产生新的Socket连接。
所以在任意写操作进行前,需要修复SLPSocket链表,使其恢复正常。
以下是SLPSocket链表结构、插入、使用代码:
SLPSocket链表插入是头插法,使用时从头部开始获取。
假设一共有30个SLPSocket连接,第十五号SLPSocket连接被修改,产生了断链,1号-14号连接就无法被搜寻到。很不幸的是,SLP连接的监听描述符是8,一旦断链,就无法再被寻找到,新连接也就无法接入。需要人为进行伪造,恢复功能,在设计利用时需要特别注意这一点。
布局任意写:
CVE-2019-5544的任意写手法与彭博士介绍的手法稍有不同,该CVE的触发路径上有大量堆块的申请操作,其中包含一些大堆块的申请,要想再次保证目标堆块在unsorted bin中,需新创建大量连接进行布局。
彭博士的手法更倾向实战,构造一个永久性的任意写,能多次使用。但理论上不考虑其他因素的话,只复现,拿shell,进行一次任意写即可完成。故笔者转变思路,使用堆溢出修改RecvBuf的start、curpos、end指向目标内存。
然后通过堆溢出修改该RecvBuf的SLPSocket,使其转为”STREAM_READ”状态,也就是等待用户的数据输入。此时用户往连接中发送payload即可在目标内存里修改。
最后通过SLP连接,构造一个带有shellcode的堆,将其释放即可完成利用。
本文解析了CVE-2021-21974和CVE-2019-5544,并尝试在低版本ESXi上进行漏洞利用,利用本身并不复杂。在之后的版本中,VMware将ESXi中的SLP设置为默认关闭,使得SLP不再是一个研究优先度高的攻击面。
【版权说明】
本作品著作权归S1duku所有
未经作者同意,不得转载
天工实验室安全研究员
GeekCon2023 参赛选手;专注于代码审计、虚拟化安全
原文始发于微信公众号(破壳平台):ESXi SLP漏洞复现