RSS Feed
更好更安全的互联网

Liferay Portal Json Web Service 反序列化漏洞(CVE-2020-7961)

2020-07-31

作者:Longofo@知道创宇404实验室
时间:2020年3月27日
英文版本:https://paper.seebug.org/1163/

之前在CODE WHITE上发布了一篇关于Liferay Portal JSON Web Service RCE的漏洞,之前是小伙伴在处理这个漏洞,后面自己也去看了。Liferay Portal对于JSON Web Service的处理,在6.1、6.2版本中使用的是 Flexjson库,在7版本之后换成了Jodd Json

总结起来该漏洞就是:Liferay Portal提供了Json Web Service服务,对于某些可以调用的端点,如果某个方法提供的是Object参数类型,那么就能够构造符合Java Beans的可利用恶意类,传递构造好的json反序列化串,Liferay反序列化时会自动调用恶意类的setter方法以及默认构造方法。不过还有一些细节问题,感觉还挺有意思,作者文中那张向上查找图,想着idea也没提供这样方便的功能,应该是自己实现的查找工具,文中分析下Liferay使用JODD反序列化的情况。

JODD序列化与反序列化

参考官方使用手册,先看下JODD的直接序列化与反序列化:

TestObject.java

TestObject1.java

Test.java

输出:

在Test.java中,使用了两种方式,第一种是常用的使用方式,在反序列化时指定根类型(rootType);而第二种官方也不推荐这样使用,存在安全问题,假设某个应用提供了接收JODD Json的地方,并且使用了第二种方式,那么就可以任意指定类型进行反序列化了,不过Liferay这个漏洞给并不是这个原因造成的,它并没有使用setClassMetadataName("class")这种方式。

Liferay对JODD的包装

Liferay没有直接使用JODD进行处理,而是重新包装了JODD一些功能。代码不长,所以下面分别分析下Liferay对JODD的JsonSerializer与JsonParser的包装。

JSONSerializerImpl

Liferay对JODD JsonSerializer的包装是com.liferay.portal.json.JSONSerializerImpl类:

能看出就是设置了JODD JsonSerializer在序列化时的一些功能。

JSONDeserializerImpl

Liferay对JODD JsonParser的包装是com.liferay.portal.json.JSONDeserializerImpl类:

能看出也是设置了JODD JsonParser在反序列化时的一些功能。

Liferay 漏洞分析

Liferay在/api/jsonws API提供了几百个可以调用的Webservice,负责处理的该API的Servlet也直接在web.xml中进行了配置:

随意点一个方法看看:

看到这个有点感觉了,可以传递参数进行方法调用,有个p_auth是用来验证的,不过反序列化在验证之前,所以那个值对漏洞利用没影响。根据CODE WHITE那篇分析,是存在参数类型为Object的方法参数的,那么猜测可能可以传入任意类型的类。可以先正常的抓包调用去调试下,这里就不写正常的调用调试过程了,简单看一下post参数:

总的来说就是Liferay先查找/announcementsdelivery/update-delivery对应的方法->其他post参数参都是方法的参数->当每个参数对象类型与与目标方法参数类型一致时->恢复参数对象->利用反射调用该方法

但是抓包并没有类型指定,因为大多数类型是String、long、int、List、map等类型,JODD反序列化时会自动处理。但是对于某些接口/Object类型的field,如果要指定具体的类型,该怎么指定?

作者文中提到,Liferay Portal 7中只能显示指定rootType进行调用,从上面Liferay对JODD JSONDeserializerImpl包装来看也是这样。如果要恢复某个方法参数是Object类型时具体的对象,那么Liferay本身可能会先对数据进行解析,获取到指定的类型,然后调用JODD的parse(path,class)方法,传递解析出的具体类型来恢复这个参数对象;也有可能Liferay并没有这样做。不过从作者的分析中可以看出,Liferay确实这样做了。作者查找了jodd.json.Parser#rootType的调用图(羡慕这样的工具):

通过向上查找的方式,作者找到了可能存在能指定根类型的地方,在com.liferay.portal.jsonwebservice.JSONWebServiceActionImpl#JSONWebServiceActionImpl调用了com.liferay.portal.kernel.JSONFactoryUtil#looseDeserialize(valueString, parameterType), looseDeserialize调用的是JSONSerializerImpl,JSONSerializerImpl调用的是JODD的JsonParse.parse

com.liferay.portal.jsonwebservice.JSONWebServiceActionImpl#JSONWebServiceActionImpl再往上的调用就是Liferay解析Web Service参数的过程了。它的上一层JSONWebServiceActionImpl#_prepareParameters(Class<?>),JSONWebServiceActionImpl类有个_jsonWebServiceActionParameters属性:

这个属性中又保存着一个JSONWebServiceActionParametersMap,在它的put方法中,当参数以+开头时,它的put方法以:分割了传递的参数,:之前是参数名,:之后是类型名。

而put解析的操作在com.liferay.portal.jsonwebservice.action.JSONWebServiceInvokerAction#_executeStatement中完成:

通过上面的分析与作者的文章,我们能知道以下几点:

  • Liferay 允许我们通过/api/jsonws/xxx调用Web Service方法
  • 参数可以以+开头,用:指定参数类型
  • JODD JsonParse会调用类的默认构造方法,以及field对应的setter方法

所以需要找在setter方法中或默认构造方法中存在恶意操作的类。去看下marshalsec已经提供的利用链,可以直接找Jackson、带Yaml的,看他们继承的利用链,大多数也适合这个漏洞,同时也要看在Liferay中是否存在才能用。这里用com.mchange.v2.c3p0.JndiRefForwardingDataSource这个测试,用/expandocolumn/add-column这个Service,因为他有java.lang.Object参数:

Payload如下:

解析出了参数类型,并进行参数对象反序列化,最后到达了jndi查询:

补丁分析

Liferay补丁增加了类型校验,在com.liferay.portal.jsonwebservice.JSONWebServiceActionImpl#_checkTypeIsAssignable中:

_JSONWS_WEB_SERVICE_PARAMETER_TYPE_WHITELIST_CLASS_NAMES所有白名单类在portal.properties中,有点长就不列出来了,基本都是以com.liferay开头的类。


Paper

本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1162/

作者:叶霖 杨 | Categories:技术分享 | Tags:

发表评论