上一篇我们详细说了如何利用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 contentscontents:contentcontents contentcontent:objectblockdataobject:newObjectnewClassnewArraynewStringnewEnumnewClassDescprevObjectnullReferenceexceptionTC_RESETblockdata:blockdatashortblockdatalongblockdatashort: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 edSTREAM_VERSION - 0x00 05ContentsTC_BLOCKDATA - 0x77Length - 15 - 0x0fContents - 0x01a4462ec50000016d8d8d63578008TC_OBJECT - 0x73TC_PROXYCLASSDESC - 0x7dnewHandle 0x00 7e 00 00Interface count - 2 - 0x00 00 00 02proxyInterfaceNames0:Length - 15 - 0x00 0fValue - java.rmi.Remote - 0x6a6176612e726d692e52656d6f74651:Length - 5 - 0x00 05Value - ICalc - 0x4943616c63classAnnotationsTC_NULL - 0x70TC_ENDBLOCKDATA - 0x78superClassDescTC_CLASSDESC - 0x72classNameLength - 23 - 0x00 17Value - java.lang.reflect.Proxy - 0x6a6176612e6c616e672e7265666c6563742e50726f7879serialVersionUID - 0xe1 27 da 20 cc 10 43 cbnewHandle 0x00 7e 00 01classDescFlags - 0x02 - SC_SERIALIZABLEfieldCount - 1 - 0x00 01Fields0:Object - L - 0x4cfieldNameLength - 1 - 0x00 01Value - h - 0x68className1TC_STRING - 0x74newHandle 0x00 7e 00 02Length - 37 - 0x00 25Value - Ljava/lang/reflect/InvocationHandler; - 0x4c6a6176612f6c616e672f7265666c6563742f496e766f636174696f6e48616e646c65723bclassAnnotationsTC_NULL - 0x70TC_ENDBLOCKDATA - 0x78superClassDescTC_NULL - 0x70newHandle 0x00 7e 00 03classdatajava.lang.reflect.Proxyvaluesh(object)TC_OBJECT - 0x73TC_CLASSDESC - 0x72classNameLength - 45 - 0x00 2dValue - java.rmi.server.RemoteObjectInvocationHandler - 0x6a6176612e726d692e7365727665722e52656d6f74654f626a656374496e766f636174696f6e48616e646c6572serialVersionUID - 0x00 00 00 00 00 00 00 02newHandle 0x00 7e 00 04classDescFlags - 0x02 - SC_SERIALIZABLEfieldCount - 0 - 0x00 00classAnnotationsTC_NULL - 0x70TC_ENDBLOCKDATA - 0x78superClassDescTC_CLASSDESC - 0x72classNameLength - 28 - 0x00 1cValue - java.rmi.server.RemoteObject - 0x6a6176612e726d692e7365727665722e52656d6f74654f626a656374serialVersionUID - 0xd3 61 b4 91 0c 61 33 1enewHandle 0x00 7e 00 05classDescFlags - 0x03 - SC_WRITE_METHOD | SC_SERIALIZABLEfieldCount - 0 - 0x00 00classAnnotationsTC_NULL - 0x70TC_ENDBLOCKDATA - 0x78superClassDescTC_NULL - 0x70newHandle 0x00 7e 00 06classdatajava.rmi.server.RemoteObjectvaluesobjectAnnotationTC_BLOCKDATA - 0x77Length - 55 - 0x37Contents - 0x000a556e6963617374526566000e3134302e3233382e33342e3231360000fa00276c0508063e8d45a4462ec50000016d8d8d6357800101TC_ENDBLOCKDATA - 0x78java.rmi.server.RemoteObjectInvocationHandlervalues
这是一个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安全漫谈系列所有文章:
点击“阅读原文”,免费预览知识星球内所有帖子!