Java安全[RMI(3)]

RMI2中成功实现了codebase进行加载恶意类,但是在实际环境确实难实现符合环境,所以只作为一个了解和认识。

这篇文章主要分析codebase是如何在RMI中进行传递的,以及对分析Java序列化数据的工具SerializationDumper的使用介绍

Java安全[RMI(3)]

数据包解析以及SerializationDumper工具

这里选择的是JRMI,Call的数据包,打开右键Java Serialization数据段,复制Hex Stream,看到ac ed就知道后面是java序列化数据

我们可以用

tcp.stream eq 0 来筛选出本机与RMI Registry的数据流

tcp.stream eq 1 来筛选出本机与RMI Server的数据流

image-20231025005115061

将复制的Hex Stream用工具解析

image-20231025005053560

但是有很多参数看不懂,可以看看Java序列化的协议文档:

https://docs.oracle.com/javase/8/docs/platform/serialization/spec/protocol.html

这篇文档里用了一种类似BNF(巴科斯范式)的形式描述了序列化数据的语法,比如我们这里的这段简单的数据,其涉及到如下语法规则:

stream:
magic version contents
contents:
content
contents content
content:
object
blockdata
object:
newObject
newClass
newArray
newString
newEnum
newClassDesc
prevObject
nullReference
exception
TC_RESET
blockdata:
blockdatashort
blockdatalong
blockdatashort:
TC_BLOCKDATA (unsigned byte) (byte)[size]
newString:
TC_STRING newHandle (utf)
TC_LONGSTRING newHandle (long-utf)

其中 TC_BLOCKDATA 这部分对应的是 contents -> content -> blockdata -> blockdatashortTC_STRING 这部分对应的是 contents -> content -> object-> newString 。都可以在文档里找到完 整的语法定义。 这一整个序列化对象,其实描述的就是一个字符串,其值是 refObj 。意思是获取远程的 refObj 对 象。

JRMI,ReturnData数据包大一些,下面用它来展示

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
STREAM_MAGIC - 0xac ed
STREAM_VERSION - 0x00 05
Contents
TC_BLOCKDATA - 0x77
Length - 15 - 0x0f
Contents - 0x0120dea1410000018b3e3ad3308005
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 - 11 - 0x00 0b
Value - RMI_2.ICalc - 0x524d495f322e4943616c63
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; - 0x4c6a6176612f6c616e672f7265666c6563742f496e766f636174696f6e48616e646c65723b
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 - 0x6a6176612e726d692e7365727665722e52656d6f74654f626a656374496e766f636174696f6e48616e646c6572
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 - 56 - 0x38
Contents - 0x000a556e6963617374526566000f3139322e3136382e3136392e3134360000c5b2e3476369ea6a56e120dea1410000018b3e3ad330800101
TC_ENDBLOCKDATA - 0x78
java.rmi.server.RemoteObjectInvocationHandler
values
<Dynamic Proxy Class>

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

这段数据记录了RMI Server的地址和端口。

image-20231025010959831

然后再看看,本机与RMI Server的交互,虽然wireshark没有识别出RMI,但是从数据包中可以看到50 ac ed,所以是存在序列化数据,一样用工具解析试试,只保留ac ed开头。

image-20231025011457922

image-20231025012001967

可以看到codebase是通过过 java.rmi.server.Obj;的 classAnnotations 传递的。

= >即使我们没有RMI的客户端,只需要修改 classAnnotations 的值,就能控制codebase,使其 指向攻击者的恶意网站。

classAnnotations是什么?

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

比如,我们RMI的类 MarshalOutputStream 就将当前的 codebase 写入:

  • https://github.com/JetBrains/jdk8u_jdk/blob/8db9d62a1cfe07fd4260b83ae86e39f80c0a9ff2/src/share/classes/java/rmi/server/RMIClassLoader.java#L657
  • https://github.com/JetBrains/jdk8u_jdk/blob/8db9d62a1c/src/share/classes/sun/rmi/server/LoaderHandler.java#L282

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