JAVA安全|Gadget篇:LazyMap CC1链

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

0x01  回顾

我们先来回顾下之前分析TransformedMap CC1链的过程:

首先我们找到在Transformer接口的实现类InvokerTransformer的transform()中,我们可以控制传入的参数,调用任意对象的任意方法,这是关键点;接着我们找到TransformedMap的checkSetvalue()方法会调用transform(),而checkSetvalue()方法会在我们遍历TransformedMap得到Map.Entry后,再调用setValue()时会被调用;最后我们找到在AnnotationInvocationHandler的readObject()方法中若满足一定条件会调用setValue()方法。从而经过我们的精心构造,得到一条TransformedMap CC1链。对于具体细节,如果不记得了,可以回看。

在第二步中,其实我们当时还找到了在LazyMap.get()方法中也调用到了transform():

JAVA安全|Gadget篇:LazyMap CC1链

今天我们就来学下Ysoserial中是如何使用LazyMap来构造CC1链的。

0x02  LazyMap

我们先来看下LazyMap这个类:

JAVA安全|Gadget篇:LazyMap CC1链

实现了Serializable接口,可序列化;有一个Transformer类型的factory属性,构造器为protected的,但可通过decorate()方法获取到一个LazyMap,这与TransformedMap很类似。

重点看get()方法:LazyMap,“懒”Map,大概就来自get()方法的特点吧。如果要找的key不在map中,就会调用factory.transform(key)去“创造”一个value。而factory是Transformer类型,这就和之前那一系列实现了Transformer接口的类拼接起来了,我们编写如下代码就可弹计算器:

JAVA安全|Gadget篇:LazyMap CC1链

那么什么地方又会调用到get()呢?

当我们兴冲冲地想通过find usages去找什么地方调用get()时:

JAVA安全|Gadget篇:LazyMap CC1链

这可咋玩哦?get这个方法太常见了,以致于出现了这么多,菜鸟的我不知道该怎么挑了。那来看下Ysoserial的作者是怎么玩的吧~

0x03  Ysoserial高端玩法

1.sun.reflect.annotation.AnnotationInvocationHandler再认识

JAVA安全|Gadget篇:LazyMap CC1链

学了动态代理,当我们再来看这个类时,它不仅可以序列化,而且还实现了InvocationHandler接口。(坏笑)

那我们就自然会看到它的invoke()方法:会先得到调用的方法名member,参数类型paramTypes,然后判断是否为equals,toString,hashCode,annotationType 方法,如果不是那就会调用memberValues.get(),而memberValues为一个Map。假如这里的memberValues为LazyMap,那么只要不满足前面的if条件且LazyMap中键名没有和调用方法名一样的,就会进入到我们构造的链子中。

那么怎么才能调用到invoke呢?

2.动态代理的应用

我们只要使用Proxy创建一个代理实例,将我们构造的AnnotationInvocationHandler对象作为调用处理器传入。想一想,这里Proxy应代理哪个类型?我们先来看看AnnotationInvocationHandler的readObject()方法:

JAVA安全|Gadget篇:LazyMap CC1链

然后做出回答:只能是Map类型!

因为AnnotationInvocationHandler.readObject()是我们反序列化的入口,所以我们还得再用AnnotationInvocationHandler对象去包装Proxy创建的代理实例,再序列化;而其反序列化时会调用memberValues的entrySet()方法,我们希望memberValues为我们创建的代理实例,这样反序列化时才会调用到invoke(),从而进入到我们构造的链子当中。而memberValues为Map类型,故此处Proxy只能创建Map类型的代理实例。

0x04  POC 编写

结合上面的分析以及之前的TransformedMap CC1链的POC代码,我们可以写出下面的POC:

JAVA安全|Gadget篇:LazyMap CC1链

这里handler中的第一个参数,只要是继承了Annotation接口的class类型就可以。这个跟TransformedMap CC1中不太一样,因为这里我们不需要进入到AnnotationInvocationHandler.readObject()中for循环的第二个if条件句中。

Gadget chain为:

JAVA安全|Gadget篇:LazyMap CC1链

然后我们再去看下Ysoserial的代码:

JAVA安全|Gadget篇:LazyMap CC1链

主要的区别有两个:

第一:在POC的最后才将执行命令的Transformer数组设置到transformerChain中。

第二:ysoserial中的Transformer数组,最后会增加一个ConstantTransformer(1) 。

我参考了p神在《JAVA安全漫谈》中说的,大概是这样:

第一个是因为在使用Proxy代理了map对象后,在任何地方执行map的方法就会触发Payload弹出计算器,会导致在本地调试代码的时候,不经意间触发了命令,弹出两个计算器(在本菜鸟这里都是只弹了一个~)。所以Ysoserial做了处理,先传一个人畜无害的transformerChain,最后才将执行命令的Transformer数组设置到transformerChain中,以避免本地生成序列化流的程序执行到命令(在调试程序的时候可能会触发一次Proxy#invoke )

第二个可能是为了隐藏异常日志中的一些信息。如果这里没有ConstantTransformer,命令进程对象将会被 LazyMap#get 返回,导致我们在异常信息里能看到这个特征:

JAVA安全|Gadget篇:LazyMap CC1链

如果我们增加一个 ConstantTransformer(1) 在TransformChain的末尾,异常信息将会变成java.lang.Integer cannot be cast to java.util.Set ,隐蔽了启动进程的日志特征:

JAVA安全|Gadget篇:LazyMap CC1链

本菜鸟还是不懂,高端玩家,高端玩家(社会)。

0x05  总结

一、LazyMap CC1链与TransformedMap CC1链对比

1.LazyMap CC1链的构造相比TransformedMap CC1链更精妙,但也更略显复杂。精妙之处在于使用了动态代理将LayMap.get()与AnnotationInvocationHandler.readObject()巧妙地连接起来了,否则我们很难从LayMap.get()逆推找到某个类的readObject()

2.LazyMap CC1链中构造的AnnotationInvocationHandler对象的type属性(所传入注解的class对象)为任意继承Annotation接口的class类型即可,而TransformedMap CC1链要求TransformedMap的键名与所传入注解的方法名相同才能触发利用链。原因在于LazyMap的漏洞触发在get和invoke中,而TransformedMap的漏洞出发点在setValue。

至于Ysoserial的作者为什么选择更复杂的LazyMap,而不选择相对好理解的TransformedMap,大概是为了展示下高端玩法吧(皱眉)

二、动态代理在反序列化链构造中的作用

前面说了,LazyMap CC1链的精妙之处在于使用了动态代理将LayMap.get()与AnnotationInvocationHandler.readObject()巧妙地拼接起来了,而这个巧妙之处又在于AnnotationInvocationHandler.invoke()中的逻辑,意向不到的调用了get()。故我觉得动态代理在反序列化链构造中的作用主要是两个:

1.巧妙地拼接两条链

2.InvocationHandler.invoke()是具体代理的逻辑,有函数调用,可能会有意向不到的收获

三、LazyMap CC1与TransformedMap CC1都只能在jdk<8u71中使用

具体什么原因呢?我们来看下jdk 8u71以后的AnnotationInvocationHandler.readObject():

http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/f8a528d0379d

JAVA安全|Gadget篇:LazyMap CC1链

黑色部分为没做修改的,红色部分是jdk8u71之前的,绿色部分为jdk8u71之后的,通过绿色部分代码我们可以知道,jdk8u71之后不能用CC1链的原因是:

不再直接使用反序列化出来的Map对象,而是先获取memberValues字段存入streamVals,然后新建了一个LinkedHashMap对象,使用streamVals进行遍历等一系列操作后将得到的键值添加到LinkedHashMap对象。所以,原来我们精心构造的Map已经没了,也就不会触发RCE了。

既然CC1链不能在jdk8u71之后的版本中使用,那么有没有一条通杀的CC链,可以在jdk高版本中使用,没有版本限制呢?(当然在满足Commons-Collections <= 3.2.1的情况下)。

答案是,有!!!

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

参考:

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安全|基础篇:反射的应用—动态代理


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

原文始发于微信公众号(沃克学安全):JAVA安全|Gadget篇:LazyMap CC1链

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年1月7日12:39:31
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   JAVA安全|Gadget篇:LazyMap CC1链http://cn-sec.com/archives/1504670.html

发表评论

匿名网友 填写信息