Liferay Portal CVE-2020-7961 分析

前言

近日有 Code White 安全团队发现关于 Liferay Portal 多个比较严重的 JSON fan 反序列化漏洞,影响 Liferay Portal 6.1、6.2、7.0、7.1 以及 7.2 版本,这些漏洞可以通过 Json web 进行未授权远程代码执行。固定版本为 6.2 GA6、7.0 GA7、7.1 GA4 和 7.2 GA2。
对应的漏洞分别是:

  • CST-7111: 通过JSON反序列化实现RCE (LPS-88051/LPE-16598)
    在Liferay Portal 6.1和6.2中,Flexjson 库用于序列化和反序列化数据。它支持对象绑定,对任何带有无参数构造函数的类将使用被初始化对象的 setter 方法。类的规范是通过class对象键:
    1
    {"class":"fully.qualified.ClassName", ... }

该漏洞于 2018 年 12 月被报告,已在企业版中被修复,包括 6.1 EE GA3 fixpack 71 和 6.2 EE GA2 fixpack 1692 和 6.2 GA6。

调试

环境信息:

配置 Tomcat 调试端口,修改 tomcat/bin/setenv.sh (windows 为 setenv.bat) 文件加入export JPDA_ADDRESS=0.0.0.0:5555 (windows 为 set JPDA_ADDRESS=0.0.0.0:5555),其中5555 为远程调试端口,0.0.0.0 表示可连接调试端口的 IP 地址。

将整个项目作作为调试运行目录,将 tomcat-9.0.17/webapps/ROOT/WEB-INF/lib 目录添加 Libraries

配置 Remote 方式进行远程调试,ip 设置为开启 tomcat 调试服务的 IP,端口为 5555

注:Tocmat 运行命令为 ./catalina.sh jpda start 使用调试模式运行。

分析

Liferay Portal 提供了一个全面的 JSON Web 服务 API ,并提供了三种不同的调用Web服务方法的示例:

  • 通过通用URL /api/jsonws/invoke,其中服务方法及其参数通过POST作传输,一个JSON对象或基于表单的参数。

  • 通过服务方法特定的 URL ,例如 /api/jsonws/service-class-name/service-method-name ,其中参数传递是通过基于表单的 POST 数据包。

  • 通过服务方法特定的URL,例如 /api/jsonws/service-class-name/service-method-name,其中参数也是通过URL的形式传递,例如 /api/jsonws/service-class-name/service-method-name/arg1/val1/arg2/val2/…

在 Liferay Portal 主要在 /tomcat-9.0.17/webapps/ROOT/WEB-INF/lib/portal-impl.jar文件中com.liferay.portal.jsonwebservice.JSONWebServiceServiceAction#getJSON:43 进行处理 JSON 请求然后交由com.liferay.portal.jsonwebservice.JSONWebServiceServiceAction#getJSONWebServiceAction:115 来进行处理 Json API。

然后会通过 com.liferay.portal.jsonwebservice.JSONWebServiceActionsManagerImpl#getJSONWebServiceAction:74 进行对请求对参数进行来进行遍历处理。


com.liferay.portal.jsonwebservice.JSONWebServiceActionParameters#collectAll:50this._collectFromRequestParameters(httpServletRequest); 来进行处理请求参数。

com.liferay.portal.jsonwebservice.JSONWebServiceActionParameters._collectFromRequestParameters;170 首先会将参数转换为 Set 类型,然后进行迭代循环,最后通过 this._jsonWebServiceActionParameters.put(parameterName, value);

跟踪进入会跳入 com.liferay.portal.jsonwebservice.JSONWebServiceActionParametersMap#put:48 中并且将参数名称以及值进行传入,并且判定参数名是否包含 : ( char 58 表示为 :) ,然后通过 this._parameterTypes.put(key, typeName);: 之前的内容作为 key 以及 : 之后的内容作为类型添加至 this._parameterTypes 变量中。

com.liferay.portal.jsonwebservice.JSONWebServiceServiceAction#getJSONWebServiceAction 执行完毕会跳回 com.liferay.portal.jsonwebservice.JSONWebServiceServiceAction#getJSON 并且执行 jsonWebServiceAction.invoke()

跟进 com.liferay.portal.jsonwebservice.JSONWebServiceActionImpl#invoke:54 然后会执行 this._invokeActionMethod();

进入 com.liferay.portal.jsonwebservice.JSONWebServiceActionImpl#_invokeActionMethod 之后,将actionClass 作为参数调用 thiis._prepareParameters(actionClass);:316

跟进 com.liferay.portal.jsonwebservice.JSONWebServiceActionImpl#_prepareParameters 之后会通过 this._jsonWebServiceActionConfig.getMethodParameters() 获取请求方法参数,然后进行循环根据指定类载入的参数名与请求包中的参数名进行赋值,然后根据载入类中参数的类型。如果通过 this._jsonWebServiceActionParameters.getParameter(parameterName); 提取到内容信息会通过 this._jsonWebServiceActionParameters.getParameterTypeName(parameterName); 进行获取请求参数类型,也就是 : 之后的内容,并且通过 ClassLoader 获取该类的 class。

获取到该类的 Class 之后,会通过 parameterValue = this._convertValueToParameterValue(value, parameterType, methodParameters[i].getGenericTypes()); 将内容转换为参数类型对象。

跟踪进入 com.liferay.portal.jsonwebservice.JSONWebServiceActionImpl#_convertValueToParameterValue 之后会跳入 com.liferay.portal.jsonwebservice.JSONWebServiceActionImpl#_convertType 中进行类型转换,在转换的过程中会进行抛出异常,通过捕获异常之后会进行判定是否为 Map 并且是 { 开头的字符串,然后进行反序列化执行恶意代码。

修复建议

修复版本:6.2 GA6、7.0 GA7、7.1 GA4 和 7.2 GA。

目前官方已发布了安全补丁,可通过 https://liferay.dev/blogs/-/blogs/security-patches-for-liferay-portal-6-2-7-0-and-7-1 进行相应版本的下载。

参考