February 26, 2024

RMI

RMI基本要素

RMIServer构成

Client

Java是强类型语言

Naming.lookup() 是 Java RMI 提供的一个方法,用于从远程 RMI 注册表中查找远程对象

该方法返回的是一个通用的 Remote 类型的对象,因为 Remote 是所有远程接口的父接口(标记接口)。也就是说,Naming.lookup() 不知道它具体返回的是什么类型的远程对象,只能返回一个通用的 Remote 对象。

因此,Naming.lookup("rmi://localhost:1099/hello") 返回的对象是 Remote 类型,但这只是一个父类类型的引用,实际的对象类型可能是你定义的远程接口类型,例如 RMIServer.IRemoteHello

通信过程

代码实现相关

多重继承

java不允许多重继承,但是允许一个类继承一个类,然后实现多个接口。

就像Server里面实现的接口的类,其签名为:

1
public class RemoteHello extends UnicastRemoteObject implements IRemoteHello

同时接口IRemoteHello则继承RMI的父类Remote

1
public interface IRemoteHello extends Remote

可以看到Remote实际是一个标记接口(marker interface),它不包含任何方法,但是通过继承 Remote,Java RMI 系统知道这个接口中的方法可以通过网络进行调用。

image-20240918115606272

所以继承Remote接口的意义就是 IRemoteHello 成为一个 远程接口。所有继承自 Remote 的接口表示该接口中的方法可以通过 RMI 在远程调用。

攻击方法

攻击RMI Registry

限制:只能localhost源才能访问rebind,bind,unbind方法。

但是能用的是:list 和 lookup方法

1
2
3
4
5
6
String[] s = Naming.list("rmi://localhost:1099");
System.out.println("可用类:"+ Arrays.toString(s));

//可用类:[//localhost:1099/hello]

如果直接输出s,则会输出:[Ljava.lang.String;@4411d970,是一个不可读的信息

lookup作用就是获得某个远程对象。

其实就是这段image-20240922185700301

那么,只要目标服务器上存在一些危险方法,我们通过RMI就可以对其进行调用

当然肯定不会这么简单

RMI利用codebase执行任意代码

codebase是一个地址,告诉Java虚拟机我们应该从哪个地方去搜索类,有点像我们日常用的

CLASSPATH,但CLASSPATH是本地路径,而codebase通常是远程URL,比如http、ftp等。

如果我们指定 codebase=http://example.com/ ,然后加载 org.vulhub.example.Example 类,则

Java虚拟机会下载这个文件 http://example.com/org/vulhub/example/Example.class ,并作为

Example类的字节码。

这个时候问题就来了,如果codebase被控制,我们不就可以加载恶意类了吗?

对,在RMI中,我们是可以将codebase随着序列化数据一起传输的,服务器在接收到这个数据后就会去

CLASSPATH和指定的codebase寻找类,由于codebase被控制导致任意命令执行漏洞。

不过显然官方也注意到了这一个安全隐患,所以只有满足如下条件的RMI服务器才能被攻击:

其中 java.rmi.server.useCodebaseOnly 是在Java 7u21、6u45的时候修改的一个默认设置:

https://docs.oracle.com/javase/7/docs/technotes/guides/rmi/enhancements-7.html

https://www.oracle.com/technetwork/java/javase/7u21-relnotes-1932873.html

官方将 java.rmi.server.useCodebaseOnly 的默认值由 false 改为了 true 。在

java.rmi.server.useCodebaseOnly 配置为 true 的情况下,Java虚拟机将只信任预先配置好的

codebase ,不再支持从RMI请求中获取。

查看example.com的日志,可见收到了来自Java的请求 /RMIClient$Payload.class 。因为我们还没

有实际放置这个类文件,所以上面出现了异常:

我们只需要编译一个恶意类,将其class文件放置在Web服务器的 /RMIClient$Payload.class 即可。

在java序列化流里加入codebase

通过查看序列化流数据

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
88
89
90
91
92
93
94
95
96
97
98
99
STREAM_MAGIC - 0xac ed
STREAM_VERSION - 0x00 05
Contents
TC_BLOCKDATA - 0x77
Length - 15 - 0x0f
Contents - 0x01a4462ec50000016d8d8d63578008
TC_OBJECT - 0x73
TC_PROXYCLASSDESC - 0x7d
newHandle 0x00 7e 00 00
Interface count - 2 - 0x00 00 00 02
proxyInterfaceNames
0:
Length - 15 - 0x00 0f
Value - java.rmi.Remote - 0x6a6176612e726d692e52656d6f7465
1:
Length - 5 - 0x00 05
Value - ICalc - 0x4943616c63
classAnnotations
TC_NULL - 0x70
TC_ENDBLOCKDATA - 0x78
superClassDesc
TC_CLASSDESC - 0x72
className
Length - 23 - 0x00 17
Value - java.lang.reflect.Proxy -
0x6a6176612e6c616e672e7265666c6563742e50726f7879
serialVersionUID - 0xe1 27 da 20 cc 10 43 cb
newHandle 0x00 7e 00 01
classDescFlags - 0x02 - SC_SERIALIZABLE
fieldCount - 1 - 0x00 01
Fields
0:
Object - L - 0x4c
fieldName
Length - 1 - 0x00 01
Value - h - 0x68
className1
TC_STRING - 0x74
newHandle 0x00 7e 00 02
Length - 37 - 0x00 25
Value - Ljava/lang/reflect/InvocationHandler; -
0x4c6a6176612f6c616e672f7265666c6563742f496e766f636174696f6e48616e646c65723
b
classAnnotations
TC_NULL - 0x70
TC_ENDBLOCKDATA - 0x78
superClassDesc
TC_NULL - 0x70
newHandle 0x00 7e 00 03
classdata
java.lang.reflect.Proxy
values
h
(object)
TC_OBJECT - 0x73
TC_CLASSDESC - 0x72
className
Length - 45 - 0x00 2d


Value - java.rmi.server.RemoteObjectInvocationHandler -
0x6a6176612e726d692e7365727665722e52656d6f74654f626a656374496e766f636174696
f6e48616e646c6572
serialVersionUID - 0x00 00 00 00 00 00 00 02
newHandle 0x00 7e 00 04
classDescFlags - 0x02 - SC_SERIALIZABLE
fieldCount - 0 - 0x00 00
classAnnotations
TC_NULL - 0x70
TC_ENDBLOCKDATA - 0x78
superClassDesc
TC_CLASSDESC - 0x72
className
Length - 28 - 0x00 1c
Value - java.rmi.server.RemoteObject -
0x6a6176612e726d692e7365727665722e52656d6f74654f626a656374
serialVersionUID - 0xd3 61 b4 91 0c 61 33 1e
newHandle 0x00 7e 00 05
classDescFlags - 0x03 - SC_WRITE_METHOD |
SC_SERIALIZABLE
fieldCount - 0 - 0x00 00
classAnnotations
TC_NULL - 0x70
TC_ENDBLOCKDATA - 0x78
superClassDesc
TC_NULL - 0x70
newHandle 0x00 7e 00 06
classdata
java.rmi.server.RemoteObject
values
objectAnnotation
TC_BLOCKDATA - 0x77
Length - 55 - 0x37
Contents -
0x000a556e6963617374526566000e3134302e3233382e33342e3231360000fa00276c05080
63e8d45a4462ec50000016d8d8d6357800101
TC_ENDBLOCKDATA - 0x78
java.rmi.server.RemoteObjectInvocationHandler
values

这是一个 java.lang.reflect.Proxy 对象,其中有一段数据储存在 objectAnnotation 中:

0x000a556e6963617374526566000e3134302e3233382e33342e3231360000fa00276c0508063e8d45a

4462ec50000016d8d8d6357800101 ,记录了RMI Server的地址和端口。

再看RMI Call回来的数据包

image-20240924210814936

可以看出我们的 codebase 是通过 [Ljava.rmi.server.ObjID; 的 classAnnotations 传递的。

所以,即使我们没有RMI的客户端,只需要修改 classAnnotations 的值,就能控制codebase,使其

指向攻击者的恶意网站。

classAnnotations(类注释

众所周知,在序列化Java类的时候用到了一个类,叫 ObjectOutputStream 。这个类内部有一个方法annotateClass ObjectOutputStream 的子类有需要向序列化后数据里放任何内容,都可以重写这个方法,写入你自己想要写入的数据。然后反序列化时,就可以读取到这个信息并使用。

比如,我们RMI的类 MarshalOutputStream 就将当前的 codebase 写入:https://github.com/JetBrains/jdk8u_jdk/blob/8db9d62a1cfe07fd4260b83ae86e39f80c0a9ff2/

src/share/classes/java/rmi/server/RMIClassLoader.java#L657

image-20240924211301413

https://github.com/JetBrains/jdk8u_jdk/blob/8db9d62a1c/src/share/classes/sun/rmi/server/

LoaderHandler.java#L282

所以,我们在分析序列化数据时看到的 classAnnotations ,实际上就是 annotateClass 方法写入的

内容。

Java 序列化流格式详解


Java 序列化机制允许将对象的状态转换为字节流,以便保存到文件、数据库或通过网络传输。为了实现序列化,Java 定义了一套特定的二进制流格式,称为 序列化流格式(Serialization Stream Format)。理解这个格式对于深入理解 Java 序列化机制、调试序列化问题以及防范反序列化漏洞都有重要意义。

本文将详细解释序列化流格式中的各个组成部分,包括各个标记(Token)和数据结构。


一、序列化流的整体结构

序列化流由以下几个主要部分组成:

  1. Stream Header(流头部):标识序列化流的开始,包括魔数和版本号。
  2. Contents(内容):序列化的对象和数据。

1. Stream Header(流头部)

示例:

1
2
Magic:    0xAC ED
Version: 0x00 05

2. Contents(内容)

Contents 包含一个或多个 Content,每个 Content 可以是一个对象、块数据或控制标记。


二、详细结构解析

Contents

也就是说,Contents 是由多个 Content 组成的序列。

Content

Content 可以是一个对象或者块数据。


Object

Object 可以是以下类型之一:

  1. newObject:一个新的对象实例。
  2. newClass:一个新的类对象。
  3. newArray:一个新的数组对象。
  4. newString:一个新的字符串对象。
  5. newEnum:一个新的枚举对象。
  6. newClassDesc:一个新的类描述符。
  7. prevObject:对先前对象的引用。
  8. nullReference:空引用。
  9. exception:异常对象。
  10. TC_RESET:重置流上下文。

BlockData

BlockData 是序列化流中的块数据,可以是短块数据或长块数据。


三、各个标记和结构详解

1. Magic(魔数)和 Version(版本号)

2. TC_ 标记*

序列化流使用了一系列的标记(Token),以 TC_ 开头,用于标识不同的类型和操作。


3. Object 类型详解

(1) newObject

(2) newClass

(3) newArray

(4) newString

(5) newEnum

(6) newClassDesc

(7) prevObject

(8) nullReference

(9) exception

(10) TC_RESET


4. BlockData 类型详解

(1) blockdataShort

(2) blockdataLong


四、序列化过程中的句柄(Handle)

在序列化过程中,每个新创建的对象都会分配一个句柄,用于引用该对象。这有助于处理对象共享和循环引用。


五、标记(Token)及其对应的十六进制值

标记名 值(十六进制) 描述
TC_NULL 0x70 空引用
TC_REFERENCE 0x71 对先前对象的引用
TC_CLASSDESC 0x72 类描述符
TC_OBJECT 0x73 新对象
TC_STRING 0x74 新字符串(短字符串)
TC_ARRAY 0x75 新数组
TC_CLASS 0x76 新类对象
TC_BLOCKDATA 0x77 块数据(短)
TC_ENDBLOCKDATA 0x78 块数据结束
TC_RESET 0x79 重置流上下文
TC_BLOCKDATALONG 0x7A 块数据(长)
TC_EXCEPTION 0x7D 异常对象
TC_LONGSTRING 0x7C 新字符串(长字符串)
TC_PROXYCLASSDESC 0x7D 代理类描述符
TC_ENUM 0x7E 新枚举对象

六、具体示例解析

1. 序列化一个字符串

当序列化一个字符串时,可能会使用 TC_STRINGTC_LONGSTRING,取决于字符串的长度。

(1) 短字符串

(2) 长字符串

注意:当字符串长度超过 65535 个字节时,需要使用 TC_LONGSTRING

2. 序列化一个新对象


七、序列化流中的异常处理

1. exception

2. TC_RESET


八、特殊标记的作用

1. TC_ENDBLOCKDATA

2. TC_PROXYCLASSDESC


九、序列化类描述符(ClassDesc)

在序列化对象时,需要先序列化其类描述符,包含以下信息:

  1. 类名(className):以 UTF-8 编码的完全限定类名。
  2. 序列化版本 UID(serialVersionUID):用于版本控制的长整型值。
  3. 类描述符信息(classDescInfo)
    • 类标志(classFlags):指示类的序列化特性,如是否有 writeObject 方法。
    • 字段描述符(fieldDesc[]):类的字段信息。
    • 方法数据:可选,包括 writeObjectreadObject 方法的数据。

十、反序列化时的注意事项

  1. 句柄引用:在反序列化过程中,需要维护对象句柄表,以正确处理对象引用和共享。

  2. 类的加载:反序列化时,JVM 会尝试加载对应的类。如果类不存在,或版本不匹配,可能会导致异常。

  3. 安全风险:反序列化不可信数据可能导致安全漏洞,如反序列化漏洞。因此,反序列化时应确保数据的可信性。


十二、参考资料

About this Post

This post is written by void2eye, licensed under CC BY-NC 4.0.

#Web