上一篇我们详细说了如何利用codebase来加载远程类,在RMI服务端执行任意代码。那么,从原理上来讲,codebase究竟是如何传递进而被利用的呢?
我们曾在第4篇文章抓过RMI的数据包,当时通过数据包简单梳理了RMI通信的组成部分与过程。 这次我们尝试抓取了上一篇文章中攻击RMI的数据包,当然也有2个TCP连接:
本机与RMI Registry的通信(在我的数据包中是1099端口)
本机与RMI Server的通信(在我的数据包中是64000端口)
我们用
tcp.stream eq 0
来筛选出本机与RMI Registry的数据流:
可见,在与RMI Registry通信的时候Wireshark识别出了协议类型。我们选择其中序号是8的数据包,然后复制Wireshark识别出的
Java Serialization
数据段:
这段数据由0xACED开头,有经验的同学一眼就能看出这是一段Java序列化数据。我们可以使用 SerializationDumper工具( https://github.com/NickstaDB/SerializationDumper ) 对Java序列化数据进行分析:
SerializationDumper输出了很多预定义常量,像
TC_BLOCKDATA
这种,它究竟表示什么意思呢?此时我们还得借助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)<size> (byte)[size]
newString:
TC_STRING newHandle (utf)
TC_LONGSTRING newHandle (long-utf)
其中
TC_BLOCKDATA
这部分对应的是
contents -> content -> blockdata -> blockdatashort
,
TC_STRING
这部分对应的是
contents -> content -> object-> newString
。
都可以在文档里找到完整的语法定义。
这一整个序列化对象,其实描述的就是一个字符串,其值是
refObj
。
意思是获取远程的
refObj
对象。
接着我们在序号为10的数据包中获取到了这个对象:
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; - 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 - 55 - 0x37
Contents - 0x000a556e6963617374526566000e3134302e3233382e33342e3231360000fa00276c0508063e8d45a4462ec50000016d8d8d6357800101
TC_ENDBLOCKDATA - 0x78
java.rmi.server.RemoteObjectInvocationHandler
values
这是一个
java.lang.reflect.Proxy
对象,其中有一段数据储存在
objectAnnotation
中:
0x000a556e6963617374526566000e3134302e3233382e33342e3231360000fa00276c0508063e8d45a4462ec50000016d8d8d6357800101
,记录了RMI Server的地址和端口。(中间具体调用链,下来后可以自己仔细调试分析)
在拿到RMI Server的地址和端口后,本机就会去连接并正式开始调用远程方法。我们再用
tcp.stream eq 1
筛选出本机与RMI Server的数据流:
可见,wireshark没有再识别出RMI的协议。我们选择序号为19的数据包,其内容是
50 ac ed
开头,50是指
RMI Call
(
https://github.com/JetBrains/jdk8u_jdk/blob/master/src/share/classes/sun/rmi/transport/TransportConstants.java#L47 ),
ac ed
当然是Java序列化数据。
我们使用SerializationDumper查看这段序列化数据:
可见,我们的
codebase
是通过
[Ljava.rmi.server.ObjID;
的
classAnnotations
传递的。
所以,即使我们没有RMI的客户端,只需要修改
classAnnotations
的值,就能控制codebase,使其指向攻击者的恶意网站。
classAnnotations
是什么?
虽然我们还没讲到Java反序列化,但这里还是补充一下这个知识,否则可能会有的同学一头雾水。
众所周知,在序列化Java对象的时候用到了一个类,叫
ObjectOutputStream
。这个类内部有一个方法
annotateClass
,
ObjectOutputStream
的子类有需要向序列化后的数据里放任何内容,都可以重写这个方法,写入你自己想要写入的数据。然后反序列化时,就可以读取到这个信息并使用。
比如,我们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
方法写入的内容。
- END -
加入『代码审计知识星球』,查看Java安全漫谈系列所有文章:
点击“阅读原文”,免费预览知识星球内所有帖子!