漏洞概述
Liferay(又称Liferay Portal)是一个开源门户项目,该项目包含了一个完整的J2EE应用。该项目使用了Web、EJB以及JMS等技术,特别是其前台界面部分使用Struts 框架技术,基于XML的portlet配置文件可以自由地动态扩展,使用了Web Services来支持一些远程信息的获取,使用 Apache Lucene实现全文检索功能。
7.2.1 CE GA2 之前的 Liferay Portal 中不可信数据的反序列化允许远程攻击者通过 JSON Web 服务 (JSONWS) 执行任意代码。
漏洞复现
环境搭建
下载tomcat集成包,在liferay-ce-portal-7.2.0-ga1\tomcat-9.0.17\bin路径下打开startup.bat,等待tomcat服务开启。
访问http://127.0.0.1:8080能正常访问下图页面则说明搭建成功。
跟随网页提示完成初始化后进入以下页面,漏洞环境搭建完成
漏洞利用
构造恶意代码
将其编译为class文件后开启http服务
使用Java反序列化利用工具marshalsec的c3p0模块生成payload
将构造完成的payload放入请求数据包中
可以看到成功执行弹出计算器的命令
漏洞分析
从nvd官网对CVE-2020-7961的描述中发现反序列化的数据是经过了JSON web service的
定位到WEB-INF/web.xml中
通过该映射规则的servlet找到其对应的类为com.liferay.portal.jsonwebservice.JSONWebServiceServlet
跟进这个servlet类,其中的service方法中定义了查看json web服务的路径
在web页面中输入路径http://url/api/jsonws可查看api列表。
在这些接口有多个接口参数接收java.lang.object,比如update-column,意味着在此处传递的参数可以构造符合Java Beans的可利用恶意类
liferay在7以前的版本使用flexjson处理、序列化json数据而在7以后使用jodd json处理、序列化json数据,这里主要分析liferay7之后的版本。
通过查阅资料得知liferay中的com.liferay.portal.json.JSONSerializerImpl实现了jodd中的json序列化的接口JSONSerializer
在com.liferay.portal.json.JSONDeserializerImpl中实现了了jodd中的反序列化接口JSONDeserializer
通过漏洞发现者whitecode团队给出jodd.json.Parser的调用图,我们可以在JSONWebServiceServlet下断点后发送/api/jsonws/invoke请求
一直debug到JSONWebServiceSerlet的父类JSONservlet中发现调用execute处理json继续跟入此方法
观察com.liferay.portal.struts.JSONAction的execute方法,首先调用rerouteExecte检测路径,调用checkAuthToken方法鉴权,接着调用getJSON再对json数据处理,跟进getJSON
在没有上传异常通过后进入getJSONWebServiceAction
在JSONWebServiceAction中继续更热返回的JSONWebServiceInvokerAction类的invoke方法
在invoke方法的第一行中发现这里反序列化了_command,__command参数是用来传入调用的api方法
继续往下调用了_parseStatement,__executeStatement
跟入_executeStatement观察result的成分继续深入jsonWebServiceAction
从下面代码发现getJSONWebServiceAction()最后返回的是一个JSONWebServiceActionImpl实例
public JSONWebServiceAction getJSONWebServiceAction(
HttpServletRequest httpServletRequest)
throws NoSuchJSONWebServiceException {
String path = GetterUtil.getString(httpServletRequest.getPathInfo());
String method = GetterUtil.getString(httpServletRequest.getMethod());
String parameterPath = null;
JSONRPCRequest jsonRPCRequest = null;
int parameterPathIndex = _getParameterPathIndex(path);
if (parameterPathIndex != -1) {
parameterPath = path.substring(parameterPathIndex);
path = path.substring(0, parameterPathIndex);
}
else {
if (method.equals(HttpMethods.POST) &&
!PortalUtil.isMultipartRequest(httpServletRequest)) {
jsonRPCRequest = JSONRPCRequest.detectJSONRPCRequest(
httpServletRequest);
if (jsonRPCRequest != null) {
path += StringPool.SLASH + jsonRPCRequest.getMethod();
method = null;
}
}
}
JSONWebServiceActionParameters jsonWebServiceActionParameters =
new JSONWebServiceActionParameters();
jsonWebServiceActionParameters.collectAll(
httpServletRequest, parameterPath, jsonRPCRequest, null);
if (jsonWebServiceActionParameters.getServiceContext() != null) {
ServiceContextThreadLocal.pushServiceContext(
jsonWebServiceActionParameters.getServiceContext());
}
JSONWebServiceActionConfig jsonWebServiceActionConfig =
_findJSONWebServiceAction(
httpServletRequest, path, method,
jsonWebServiceActionParameters);
return new JSONWebServiceActionImpl(
jsonWebServiceActionConfig, jsonWebServiceActionParameters,
_jsonWebServiceNaming);
}
跟入JSONWebServiceActionImpl观察_prepareParameters方法,通过反射获取所有参数,for遍历拿到参数名并处理参数值,当参数值不为空时,会进行类型转换。
继续往后跟进跟进参数调用_convertValueToParameterValue方法
其中折叠的部分包含了将json反序列化传入的参数值,赋值给parameterValue的过程,并判断不是map实例并且是以{开头就反序列化JSONFactoryUtil.looseDeserialize(valueString, parameterType),如果parameterType可控,那么就会造成反序列化漏洞
上述的_jsonWebServiceActionParameters在其定义的过程中存入了一个JSONWebServiceActionParametersMap
跟入JSONWebServiceActionParametersMap其中的putty方法在whitecode的文章中描述的很清晰,参数的类型取自key传进来的字符串,如果请求参数名称包含 ‘ :
‘,传递进来的值会被构造为”:”前是key,”:”后是typename的hashmap。
参考链接
https://xz.aliyun.com/t/7499#toc-9
https://codewhitesec.blogspot.com/2020/03/liferay-portal-json-vulns.html