作者: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 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1884/
暫無評論