freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Z3专栏 | 正版的CommonsCollections1分析
2021-12-15 21:24:43
所属地 辽宁省

正版的CommonsCollections1介绍

从这篇开始正式学习ysoserial的CommonsCollection系列,前几篇很重要,彻底弄明白后,后面的链一看就懂

建议按顺序看,知识点都在前面几篇,后面遇到不会详细分析了,后面的文章里经常会callback前面文章的知识点

预备知识

本篇预备知识如下,我按自己理解介绍下
接口:
接口内只声明方法,方法由继承它的类去实现
例如对象A继承了B接口,那就要对B负责,B内声明的所有方法,A都要去实现
在这之后,B就可以接收A对象了,但如果这样做,就只能调用B内声明的方法,但对象的其它方法还在,只不过用B接收时不能调用了。
如果B再把对象还给A,A还可以调用所有方法
所以B就像一个A的指针,可以接收A,但又会对A做出限制(其实还要看代码理解的更透彻)

代理模式:
例如盗版视频网站,假设他的视频来源是腾讯视频,它开个会员,就可以拿到会员视频了,
我去看盗版视频网站时,盗版视频网站先去腾讯视频,拿到视频资源,然后把视频上加个自己的广告,再把视频发给我,而在我看来,这个视频来源就是盗版网站,感受不到腾讯视频存在
这里的盗版网站,就是代理类,腾讯视频是委托类
实际上代理模式分三种,静态代理、动态代理和cglib代理,自己去了解下吧(主要前两种)。三种实现方式不一样,但目的都是做代理

分析

读代码

代码如图
看第一个红框内,是构造transformer的过程
第一个Transformer,只有一个ConstantTransformer
在之前的简化版代码中已经学过了,ConstantTransformer的参数是1,那transform方法返回的也是1
第二个Transformer代码很长

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}, execArgs), 
    new ConstantTransformer(1)
};

学过反射篇的都知道,这个Transformer是通过反射获取Runtime对象,通过反射,获取invoke方法,再通过反射,执行exec方法,这个不难
第二个红框,通过反射,修改了transformerChain的iTransformers值为transformers,
而iTransformers应用于这里

也就是将transformerChain的调用链改为了transformers
为什么这么做?
之前的transformerChain内部的iTransformers是{1},而transformers才是真正Transformer链
先使用transformerChain构造利用链,之后在构造好反序列化对象后,将transformerChain中的Transformer链替换为真正的链,就避免了在构造利用链时触发命令执行

利用链都构造好了,再修改transformerChain,有用吗?
因为transformerChain只是引用,用来序列化的对象内储存的也是引用,,引用相当于指针,二者访问的是同一块内存的数据,而我们修改的正是这块内存的数据,所以当然有用

继续看代码
然后创建了LazyMap对象,参数是之前的map和ConstantTransformer对象

LazyMap通过HashMap初始化了自己,然后将Transformer对象存到factory变量

LazyMap和HashMap有什么区别?
这是网上的解释,看一下get方法
当get一个不存在的key时,会将key通过Transformer产生value,然后存储到map中,返回value
到这里明白了,使用当LazyMap的get一个不存在的键时,就会调用Transformer,导致命令执行

再继续看
调用了Gadgets.createMemoitizedProxy函数
再看下createMemoizedInvocationHandler函数,如图
通过反射,创建了AnnotationInvocationHandler对象,构造参数为Override.class, map

看下AnnotationInvocationHandler的构造函数
如图,可以看见将Override.class和map存到了对象的变量里

所以可以将AnnotationInvocationHandler当做一个封装了LazyMap的类
回到最初的createMemoitizedProxy方法内,AnnotationInvocationHandler、Map.class和new Class[0]一起传给了createProxy

看下createProxy
这里的Proxy.newProxyInstance是,java代理模式的一种:动态代理
什么意思?
可以理解为将AnnotationInvocationHandler设置为了LazyMap的代理类,代理的方法为Map接口中的所有方法,
即通过这个代理类访问接口Map中的所有方法都会交给代理类AnnotationInvocationHandler中的invoke方法处理

Map mapProxy = (Map)Gadgets.createMemoitizedProxy(lazyMap, Map.class, new Class[0]);

所以,调用mapProxy的所有方法,都会调用这个invoke方法

看一下invoke方法
看红框内的代码,先获取方法名,再调用memberValues.get(这里的memberValues是之前构造好的LazyMap对象)
而蓝框内做出了一些限制:方法参数数量必须为0、方法名不能为toString、hashCode、annotationType
所以,只要满足蓝框限制,就会调用LazyMap的get,就会触发命令执行
所以,Map接口内除了toString、hashCode、annotationType的所有参数数量为0的方法,都可以触发命令执行(神奇!)

如图,经过测试,这些参数为空的都可以触发

再看下一行代码createMemoizedInvocationHandler,之前分析过了
使用mapProxy,创建了AnnotationInvocationHandler对象,相当于对mapProxy的封装

为什么要这么做?
因为在上一步构造出的mapProxy对象,只要被调用除toString、hashCode、annotationType的所有参数数量为0的方法,就会造成命令执行
而且我们需要在反序列化时触发上面的条件,即在readObject中调用mapProxy的参数为空的方法

那看一下AnnotationInvocationHandler的readObject方法
先看红框,从序列化数据中读取到了memberValues(之前构造的LazyMap对象),然后调用了它的memberTypes()方法,满足了触发条件
再看蓝框,从序列化数据中读取type的Class对象t,然后使用t创建了AnnotationType类型对象,如果创建失败,就会抛异常
这个type是什么?
如图,是Override.class
在AnnotationInvocationHandler构造函数里可以通过Class<? extends Annotation> type接收,那就说明Override.class一定是继承自Annotation的
所以反序列化时,蓝框内条件百分百满足

所以,至此,,反序列化链构造完毕
再总结一下

总结

疑问

ysoserial在构造Transformers时,为什么要用那么多反射?

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}, execArgs), 
    new ConstantTransformer(1)
};

因为Runtime类没有继承Serializable接口,所以不能被序列化,在序列化时会出错,使用反射可以解决这个问题

为什么使用LazyMap,不使用HashMap?InvocationHandler可以换成其它类吗

LazyMap配合LazyMap.decorate,可以在get时触发,InvocationHandler在反序列化时调用get
HashMap配合TransformedMap,可以在put时触发,------还没找到反序列化时调用put的类--------(好像Java 8u71之前的InvocationHandler的readObject方法会调用put?)
所以使用LazyMap
InvocationHandler是用来在反序列化时触发LazyMap.get的,如果其他类在反序列化时会调用LazyMap.get,应该也可以

为什么Transformers数组最后要加一个ConstantTransformer(1)

因为Transformers链执行后的内容要添加进map的
而前面的Transformer执行的是Runtime.exec,返回结果是ProcessImpl对象,,ProcessImpl类没有继承serializable类,不能被序列化
所以后面加个ConstantTransformer(1),被添加进map的就是1

如图,去掉ConstantTransformer(1)会报错

莫名其妙的问题

我在调试时
执行到红线方法之前,有时候就会弹出计算器,可能弹出1个、2个、3个,有时候没有
但是我在Runtime.exec处添加断点,,断点没被触发,但是计算器却弹出了
想不通,,只能怪idea了,可能它偷偷调用了代理类的什么方法

对本篇用到的ysoserial库总结

CommonsCollections1链使用方式

CommonsCollections1的getObject方法,参数是想要执行的命令,返回构造好的对象,对这个对象序列化,就是想要的payload了

createMemoizedInvocationHandler方法

通过反射创建AnnotationInvocationHandler对象,参数是Map,这个对象反序列化时会调用map的entrySet方法

Gadgets.createMemoitizedProxy方法

第一个参数是委托类(需要Map类型),第二个参数是代理接口的Class对象,第三个是其它代理接口的Class对象,如果没有,则设置为new Class[0]
返回的是通过AnnotationInvocationHandler代理的对象,调用这个代理对象的所有方法,都会被转到AnnotationInvocationHandler类的invoke方法。

# java漏洞 # java # java反序列化 # Java代码审计 # JAVA安全
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录