作者:Hu3sky@360CERT
0x01 漏洞背景
2020年03月20日,Code White发现了影响Liferay Portal版本6.1、6.2、7.0、7.1和7.2的多个关键级别的JSON反序列化漏洞。它们允许通过JSON web服务API执行未经身份验证的远程代码。修复的Liferay Portal版本有6.2 GA6、7.0 GA7、7.1 GA4和7.2 GA2。
Liferay(又称Liferay Portal)是一个开源门户项目,该项目包含了一个完整的J2EE应用,以创建Web站点、内部网,以此来向适当的客户群显示符合他们的文档和应用程序。
对此,360CERT建议广大用户及时安装最新补丁,做好资产自查/自检/预防工作,以免遭受黑客攻击。
0x02 影响版本
- Liferay Portal: 6.1、6.2、7.0、7.1、7.2
0x03 漏洞详情
入口定位
首先对路由的调用进行分析,我们看到web.xml,url-pattern为/api/jsonws/*,对应的servlet-name如下。
然后看到servlet-name对应的servlet-class。
我们在service函数进行下断。
首先获取path,为/api/jsonws/之后的部分,由于我们请求的不是单纯的/api/jsonws,所以进入最下面else的部分,跟入super.service,也就是JSONServlet.service,跟入_jsonAction.execute。
调用getJson,即其子类JSONWebServiceServiceAction.getJson。
跟入getJSONWebServiceAction。
这里匹配到了invoke,即前面图中的第一种调用方式,return了一个实例化对象JSONWebServiceInvokerAction,传入request对象,继续跟入。
获取cmd参数,也就是api,左边的那一列名称。
这里我们api选择的是/expandocolumn/update-column。
不为null,于是赋值给JSONWebServiceInvokerAction对象的_command,回到getJSON,调用JSONWebServiceInvokerAction.invoke。
先调用JSONFactoryUtil.looseDeserialize处理_command。
会先调用getJSONFactory,返回之前初始化过的JSONFactoryImpl。
调用createJSONDeserializer返回JSONDeserializerImpl的实例。
调用parse解析_command。
调用parseValue,之后就是jodd-json的解析器解析流程了。处理command成一个hashmap。
回到invoke,this._parseStatement->setMethod,将command设置为_method,存入statement。
回到invoke,调用_executeStatement,传入statement。
JSONWebServiceActionsManagerUtil.getJSONWebServiceAction。
在collectAll函数里会把POST的参数赋值到jsonWebServiceActionParameters里,并存入上下文(com.liferay.portal.kernel.service.ServiceContext,里面有response,request对象相关,可以用来做回显)。
Parameters key的处理细节
在collectAll里还有一个细节,就是对参数的处理,在_collectFromRequestParameters。
这里会将key和value取出来put到_jsonWebServiceActionParameters这个map里,不过在调用put函数是,还会进一步处理key。
首先定位key里的:符号,然后去掉+/-符号,把:后面的部分赋值给typename,将key赋值成:符号前面的部分,这里就是defaultData。
接着判断在key里是否有.符号,如果有.后面的将被赋值为innerName,当然我们这里没有.,接着执行HashMap.put。
获取ActionConfig
之后调用_findJSONWebServiceAction,传入jsonWebServiceActionParameters,path,获取parameterNames,调用_getJSONWebServiceActionConfig。
获取相关config。
然后回到getJSONWebServiceAction,实例化JSONWebServiceActionImpl进行赋值。
回到_executeStatement,进入JSONWebServiceActionImpl.invoke,jsonRPCRequest变量为null,调用_invokeActionMethod。
调用_prepareParameters方法。
defaultData的处理(关键部分)
跳过了中间的coulmnId,name和type,直接看到defalutData。 parameterType是java.lang.Object,而parameterTypeName是之前从key的:后面部分提取出来的type,不为null,于是调用loadClass加载parameterTypeName。
接着调用ReflectUtil.isTypeOf进行检测,检查指定的类型是否扩展要调用的方法的相应参数的类型,因为这里父类是java.lang.Object,一切类都继承了java.lang.Object类,这就然后我们可以通过parameterTypeName调用任意类。
接着是对value的处理,这里value有值,于是调用_convertValueToParameterValue。
对parameterType进行类型判断,都不符合,于是进入else。
调用_convertType,跟到TypeConverterManagerBean.convertType。
会根据type去调用lookup函数寻找converter。
如果寻找不到,进入下面的else,都不符合的话,那么就会抛出错误,找不到该converter。
回到_convertType,进行catch,输入类型不为Map,继续返回。
在_convertValueToParameterValue中进行catch。
如果value是以{开头,就会进入下面的JSONFactoryUtil.looseDeserialize,传入parameterType和value。
jsonDeserializer.use函数会将我们的class赋值给rootType成员。
接着调用deserialize函数。
之后的内容就不深入分析了,大致列一下就行了。
类的实例化
类的实例化部分在jodd.json.JsonParserBase#newObjectInstance。
从deserialize开始的调用栈如下。
set方法调用
这里的setUserOverridesAsString方法是在我们传入的WrapperConnectionPoolDataSource方法的父类里进行调用的。 jodd.introspector.MethodDescriptor#invokeSetter
从deserialize开始的调用栈。
漏洞利用
出网回显
不出网回显
0x04 时间线
2020-03-20 漏洞细节被公开
2020-04-26 360-CERT 发布分析