作者:Y4er
原文链接:https://y4er.com/post/cve-2022-22954-vmware-workspace-one-access-server-side-template-injection-rce/
安装环境
r师给的镜像 identity-manager-21.08.0.1-19010796_OVF10.ova,导入ova的时候要设置下fqdn,不然安装时链接数据库会报错。
分析
这个老外的推特中有一点点可以参考的信息
有两个报错信息,我们先找到这个模板所在。
看路由是在catalog-portal app下,cd到/opt/vmware/horizon/workspace/webapps/catalog-portal
,然后把jar包拖出来解压之后,grep -irn "console.log"
发现在lib/endusercatalog-ui-1.0-SNAPSHOT-classes.jar!/templates/customError.ftl:61
这个地方存在模板注入
freemarker官网文档中给出了安全问题的提示 https://freemarker.apache.org/docs/ref_builtins_expert.html#ref_builtin_eval
确认了这个地方就是freemarker ssti的地方。
接着看哪个路由可以渲染这个模板,找到了com.vmware.endusercatalog.ui.web.UiErrorController#handleGenericError
这个函数没有requestMapping,其中errorObj由参数传入,查找函数调用,寻找从requestMapping进来的控制器能调用到这个函数的。
endusercatalog-ui-1.0-SNAPSHOT-classes.jar这个jar包是一个spring项目
有几个控制器,其中UiErrorController控制器有两个requestMapping
这两个路由均可以走到getErrorPage
getErrorPage会根据handleUnauthorizedError和handleGenericError两个函数拿到需要渲染的模板
其中handleUnauthorizedError只有一个分支可以进入handleGenericError
到这里,想要控制errorObj,则整个数据流向如图
我们需要让其走到handleGenericError才可以rce。
但是此时有一个问题,如果直接访问这两个requestMapping,我们无法控制javax.servlet.error.message
,也就无法控制errorObj,所以找一找哪个控制器跳转过来的。
在com.vmware.endusercatalog.ui.web.UiApplicationExceptionResolver
类中,通过@ExceptionHandler
注解标明这是一个异常处理类。
当程序直接抛出Exception类型的异常时会进入handleAnyGenericException,最终都会返回/ui/view/error
,并且设置了errorObj所需要的Attribute
request.setAttribute("javax.servlet.error.status_code", responseCode);
request.setAttribute("javax.servlet.error.message", errorJson);
request.setAttribute("javax.servlet.error.exception_type", ex.getClass());
errorJson来自于LocalizationParamValueException异常的getArgs。
即自身args属性,通过构造函数的第二个参数传入
如果我们可以控制抛出异常的参数,就可以把freemarker的payload传入errorObj。
失败的exception
然后我找到了com.vmware.endusercatalog.ui.web.WorkspaceOauth2CodeVerificationController#authorizeError
尝试构造一下
https://id.test.local/catalog-portal/ui/oauth/verify?error=1&error_description=a
直接302了,调试发现errorMessage确实已经有我们的恶意值1了,但是被sendRedirect,而不是handleGenericError。
上文讲到必须要handleGenericError才能return customError
。调试发现
isSpecificUnauthError(excpClass)为false,this.isMdmOnlyUnauthorizedAccessError(request, excpClass)也为false。
private boolean isSpecificUnauthError(String exceptionClass) {
return Predicates.or(new Predicate[]{this::isDeviceRecordNotFoundError, this::isUserMismatchError, this::isMdmAuthUnhandledError, this::isDeviceStateInvalidError, this::isExternalUserIdNotFoundError}).apply(exceptionClass);
}
isSpecificUnauthError过不去,因为com.vmware.endusercatalog.ui.web.WorkspaceOauth2CodeVerificationController#authorizeError
抛出的异常是AuthorizationCodeFailedRetrievalException,并非DeviceRecordNotFoundException、UserMismatchException、MdmAuthUnhandledException、DeviceStateInvalidException、ExternalUserIdNotFoundException之一,这个死绕不过去。
isMdmOnlyUnauthorizedAccessError中this.isMdmOnlyMode(request)
永为false
因为((TenantAdapters)adapters).isMdmOnlyMode()
一直追溯到com.vmware.endusercatalog.repositories.TenantAdapters#getAdapterAttributesOptional
当程序配置好之后this.adapters
就有了AdapterType.WORKSPACE
public boolean isWorkspaceConfigured() {
return this.getAdapterAttributesOptional(AdapterType.WORKSPACE).isPresent();
}
而取反之后为false。
public boolean isMdmOnlyMode() {
return !this.isWorkspaceConfigured();
}
所以isMdmOnlyUnauthorizedAccessError判断永为false,所以这条路走不通了。
真正的exception
回头看com.vmware.endusercatalog.ui.UiApplication
,注解声明自动装配com.vmware.endusercatalog.auth
包
在com.vmware.endusercatalog.auth.interceptor.AuthContextPopulationInterceptor
中
build函数
public AuthContext build() {
return new AuthContext(this);
}
withDeviceId和withDeviceType分别设置自身的deviceId和deviceType字段。然后build()函数new了一个AuthContext,跟进到com.vmware.endusercatalog.auth.AuthContext#AuthContext
构造函数
这里抛出了一个InvalidAuthContextException异常,参数也可控,if判断只需要让this.deviceId、this.deviceType不为空即可。
payload
有个坑,host可以为localhost,可以为域名,但是不能为ip,因为ip对不上fqdn。
后利用
写shell在/opt/vmware/horizon/workspace/webapps/catalog-portal/
tomcat目录下,发现post会校验csrf,导致哥斯拉连不上,打入一个listener的内存马就可以了。
文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1884/