概述

Code White发现了多个严重的JSON严重反序列化漏洞,这些漏洞影响Liferay Portal 6.1、6.2、7.0、7.1和7.2版。它们允许通过JSON Web服务API进行未经身份验证的远程代码执行。固定的Liferay Portal版本是6.2 GA6、7.0 GA7、7.1 GA4和7.2 GA2。

相应的漏洞是:

  • CST-7111:通过JSON反序列化进行RCE(LPS-88051 / LPE-16598 1)该JSONDeserializerFlexjson的允许任意类的实例化和任意setter方法调用。
  • CST-7205:通过JSONWS执行未经身份验证的远程代码(LPS-97029 / CVE-2020-7961)该JSONWebServiceActionParametersMapLiferay门户的允许任意类和任意setter方法调用的实例。
两者都允许通过其无参数构造函数实例化任意类,并允许调用类似于JavaBeans约定的setter方法。这允许通过各种公共已知的小工具执行未经身份验证的远程代码执行。

本文出发点为根据官方披露的信息尝试复现漏洞的过程,文章有不准的地方轻拍.
基础信息部分为啰嗦点,多图预警,没兴趣的可以直接看“梳理关键点”部分。

环境准备

本文漏洞环境为:Liferay Portal 7

源码地址:
liferay-ce-portal-src-7.2.0-ga1-20190531153709761.zip

应用地址:
liferay-ce-portal-tomcat-7.2.0-ga1-20190531153709761.tar.gz

其他:
Idea、marshalsec、Python -m SimpleHTTPServer

基础信息

官网披露的该漏洞信息:
地址

英语不好,翻译文章看了以后有几个重点:

  • 漏洞点存在位置为/api/jsonws/invoke
  • Liferay Portal 7的json库为Jodd Json
  • 如果传入的key为parameterName:fully.qualified.ClassName,那么将会载入后面的包。
  • api方法中存在java.lang.Object参数的服务方法(重点,吃亏在此处..)

根据上面几个官方文章中披露的问题点,逐一进行分析

JoddJson

既然Liferay Portal 7使用的是joddjson的库,本次又是json反序列化的问题,那么就先了解下joddjson库。

joddjson

从官方的介绍中,我们可以看到使用方式很简单

new JsonParser().parse(json, Book.class);

即可反序列化一段json数据,下面的代码执行后将调用mac下的calc。

-w897

单步调试

从文章中可以提取到,本次漏洞的url为/api/jsonws/invoke,本地搭建起来环境并且初始配置以后访问http://192.168.77.18:8080/api/jsonws,即可看到api列表

-w1157

载入源码后定位到位置
com.liferay.portal.jsonwebservice.JSONWebServiceServiceAction#getJSONWebServiceAction:163

-w645

先在入口打个debug断点,方便跟进,顺便在官方文章中提到的源码位置以及其方法进入的位置也打个debug断点。
com.liferay.portal.jsonwebservice.JSONWebServiceActionParametersMap#put:63
-w701

src/com/liferay/portal/jsonwebservice/JSONWebServiceActionParametersMap.java#put:101
-w743

然后随便选一个API,先走一遍流程看看。
我使用的api是AssetCategory下的第一个search测试跟进的。
-w683

点击调用以后不出所料,在我们的断点停了下来。
-w781

一步步跟进下,接下来会调用jsonWebServiceAction.invoke()方法
-w799

跟进invoke,可以看到第一个json反序列化。
-w829

跟进JSONFactoryUtil.looseDeserialize看一下有点什么方法
-w543

其中looseDeserialize有两个方法,一个的接参为String,另一个接参为String、CLass。

然后循环commond的list,并且调用_executeStatement方法,设置cmd中所对应的webservice路径

-w738

-w797

继续跟下去,会在com.liferay.portal.jsonwebservice.JSONWebServiceActionParameters#collectAll中调用_collectFromRequestParameters方法,来对表单提交的参数进行遍历

-w705

-w865

然后将值放入_jsonWebServiceActionParameters中.
-w838

跟进_jsonWebServiceActionParametersput方法,可以看到我们就进入到了官方说的存在问题的方法。
-w740

如果传入的key为+开头,则会进入else if (key.startsWith(StringPool.PLUS)) {判断逻辑,
在此逻辑中,会将该值放入_parameterTypes hashmap中,并将value改为void

-w660

-w751

跟进后发现并不会跳到官方说的问题点,因为pos不满足条件,不等于-1.
看一下决定pos是什么值的方法

key.indexOf(CharPool.COLON);

CharPool.COLON的值为":",那么也就应对官方文章中说的

请求参数名称包含:,则其后面的部分将指定参数的类型

然而我们的请求中的参数并不包含:,所以进入不到官方说的点也很正常。在所有参数都遍历且放入_jsonWebServiceActionParameters后,会回到com.liferay.portal.jsonwebservice.JSONWebServiceActionsManagerImpl#getJSONWebServiceAction方法。

jsonWebServiceActionConfig会查找cmd中指定的webservice路径,然后将该类载入进来。
-w751

可以看到当前访问的url所对应的类以及方法还有入参值,
跟进JSONWebServiceActionImpl,
-w1096

-w832

其中会将jsonWebServiceActionParameters的值赋值给_jsonWebServiceActionParameters
,此时跳回到了com.liferay.portal.jsonwebservice.action.JSONWebServiceInvokerAction#_executeStatement:379.

-w796

跟进jsonWebServiceAction.invoke()方法看看。
-w760

继续跟进
-w808
可以看到此处会调用_prepareParameters,并且将actionClass类传入进去。
-w880

跟进此处后
-w814

_jsonWebServiceActionConfig.getMethodParameters将该类的方法入参获取出来
-w798
然后依次循环,根据指定类载入的参数名与请求包中的参数名进行赋值,然后根据载入类中参数的类型。

在此处有个判断,如果_jsonWebServiceActionParameters.getParameterTypeName(parameterName);能获取到值,那么将使用classload载入类。其中就是从_parameterTypes中获取值

-w600

-w662

继续往下跟发现抛出了异常
-w728
不要急,这是因为我们传入的obc的值为6,而6根本不是一个类..

我们将请求包中的6改为一个存在的类,然后在载入类的地方打个断点继续调试。
-w780

在过了classLoader.loadClass(parameterTypeName);的问题后又有一个问题的产生,那么就是会对输入的类以及其webservice所对应参数的类进行对比,这边对比不成功,导致抛出异常。
-w779
那么假如此处过了以后,看看下面的代码会干什么。
-w749

_createDefaultParameterValue会对类名进行判断,然后返回个newInstance
-w624

然而com.liferay.portal.jsonwebservice.JSONWebServiceActionImpl#_convertValueToParameterValue会对其进行指定的类型转换,且最终会调用

JSONFactoryUtil.looseDeserialize(valueString, parameterType);

来完成序列化触发漏洞。

-w850

至此,一个大概的逻辑应该已经梳理完成(ps:这套代码有点太大了..其中有疏漏的地方轻拍砖.)

梳理关键点

根据上面走过的流程我们知道了问题点,以及核心利用的条件要求.
触发漏洞的核心点是:

  • 执行命令的类不在cmd中,而是在参数中
  • 如果参数key为aa:bb=ccc,那么也会将bb=ccc放入_jsonWebServiceActionParameters
  • web service的入参要为java.lang.Object
  • JSONFactoryUtil.looseDeserialize(valueString, parameterType);
  • 循环调用_jsonWebServiceActionParameters

首先我们找一个接收参数有java.lang.Object的web services,直接打开网页/api/jsonws,然后右键源代码,查找java.lang.Object(我当时卡在怎么找这个.看到放出来的poc后心态蹦了,这么简单的方法居然没想到..)

由图可见,轻轻松松找到了有object参数的方法。
-w1118

-w832

可以看到defaultData的参数类型为object,根据上面梳理的几个点,我们将defaultData改为defaultData:com.mchange.v2.c3p0.WrapperConnectionPoolDataSource
在发送的包中为:

接着上面打的断点继续跟进,
-w732

此处看到parameterTypeName为我们传入的com.mchange.v2.c3p0.WrapperConnectionPoolDataSource值。并且methodParameters[i].getType()java.lang.Object,所有此处判断已经通过。
-w805

跟进下去后进入了_convertValueToParameterValue方法,在_convertValueToParameterValue中前面一大堆if判断匹配不上所以没有进去,最后进入了else判断。
-w846

_convertType(value, parameterType);中,由于抛出异常,被catch捕获到,进入到了catch逻辑。
-w829
在catch中,由于value不map
-w878

从而进入到else逻辑,else逻辑中调用JSONFactoryUtil.looseDeserialize(valueString, parameterType);,从而导致反序列化成功。

-w1261

-w1013

至此,整个漏洞利用分析完成。

总结

总结一下此漏洞,在不熟悉这个框架的情况下,复现这个漏洞,以及过流水似的大概搞清楚逻辑还是坑点比较多的,因为整套源码体量挺大的,载入idea经常性卡死,最终载入以后很多问题。
-w100

其实说了这么长篇大论,因为对此框架不是很熟悉的原因,看的也是比较迷,在大概分析完以后,因为在第一次分析的时候已经知道了两个关键点:

  • 执行漏洞点不在cmd
  • 参数需要object

在解决那个url的入参可以为object的时候,看到/api/jsonws 一大堆api的时候无从下手,最终看到github上有人放出来的poc源码,在看到从网页中直接查找object的时候,瞬间泪崩+顿悟。
-w100

然后接着之前的自己的分析把object传入后,漏洞复现成功。
-w107

最后也理解了官方的文章中对于此漏洞的概述。

"以后的版本中JSONWebServiceActionImpl._prepareParameters(Class<?>),ReflectUtil.isTypeOf(Class, Class)用来检查指定的类型是否扩展了要调用的方法的相应参数的类型。由于存在带有java.lang.Object参数的服务方法,因此可以指定任何类型。"

好好学习,天天向上.

本文参考链接

最后修改:2021 年 11 月 22 日
如果觉得我的文章对你有用,请随意赞赏