月底在即,再不写点东西,每月一篇都要食言了。上个月刚挖的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 | # dump neo4j database |
1 | # load neo4j dump file |
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
寻找Bullet
1 | java.net.URL#openConnection |
寻找中间链
1 | //m0d9 xstream URLDNS |
节点很多,以javax.activation.URLDataSource$getInputStream 为例
寻找Trigger
1 | //m0d9 xstream URL URLData |
POC
1 | <java.util.PriorityQueue serialization="custom"> |
CVE-2021-39152
这个链应该是 ssrf最短的链了
寻找Bullet
1 | java.net.URL#openConnection |
寻找中间链
1 | //m0d9 xstream URLDNS |
节点很多,以jdk.nashorn.internal.runtime.Source$URLData#loadMeta 为例
寻找Trigger
1 | //m0d9 xstream URL URLData |
poc
1 | <map> |
CVE-2021-21351
寻找核心调用链1
中间链以com.sun.org.apache.xml.internal.dtm.ref.IncrementalSAXSource_Xerces#parseSome为例
发现method、class、args都可控,需要做的就是找到一条链
寻找核心调用链2
1 | match (source:Method) where source.NAME in ["compare","hashCode","equals","compareTo","toString"] |
调用栈
调用链如下
1 | connect:615, JdbcRowSetImpl (com.sun.rowset) |
CVE-2021-21346
核心调用链
1 | match (source:Method) where source.NAME in ["compare","hashCode","equals","compareTo","toString"] |
调用栈
1 | lookup:417, InitialContext (javax.naming) |
CVE-2020-21347/21350
21347/21350这两个类似,都是用NameProcessIterator自定义classloader加载,链也比较简单。
核心调用链1
1 | match path=(sink:Method {IS_SINK:true, NAME:"loadClass"})<-[:CALL]-(m1:Method) return m1.CLASSNAME,m1.NAME,m1.PARAMETERS |
核心调用链2
比较难搞的是hasNext关联Next,很容易关联到Trigger,造成误报
需要手动发现至SequenceInputStream.nextStream,然后参考CVE-2020-26217的ByteArrayOutputStreamEx链
1 | match path=(m1:Method{NAME:"hasNext",CLASSNAME:"com.sun.tools.javac.processing.JavacProcessingEnvironment$NameProcessIterator"})-[r:CALL|ALIAS*3..10]-(m2:Method) |
这部分的与之类似,不单独列了
调用栈
1 | loadClass:131, ClassLoader (com.sun.org.apache.bcel.internal.util) |
CVE-2020-26217
核心调用链1
1 | match (source:Method) where source.NAME in ["compare","hashCode","equals","compareTo","toString"] |
转接点
可以看到,只标注了部分的链,原因为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的可行性。
核心调用链2
1 | match (source:Method) where source.NAME in ["toString"] |
调用栈
1 | start:1007, ProcessBuilder (java.lang) |
CVE-2021-21344/21345
21344/21345的这个Gadgets核心在DataTransferer$IndexedComparator到GetterSetterReflection的流程,流程比较复杂
调用栈
1 | verify:173, ServerTableEntry (com.sun.corba.se.impl.activation) |
寻找核心中间链1
1 | //m0d9 xstream Runtime.exec |
如图,com.sun.corba.se.impl.activation.ServerTableEntry#verify
尝试寻找Trigger
1 | match (source:Method) where source.NAME in ["compare","hashCode","equals","compareTo","toString"] |
但是无果,无法直接串联
寻找核心中间链2
有一种核心中间链,proxy类,存在invoke,且method、obj可控的
1 | // invoke |
都过一遍,可以得出类似以下可控的类
寻找核心中间链3
1 | match (source:Method) where source.NAME in ["compare"] |
寻找核心中间链4
1 | match (source:Method) where source.NAME in ["compare"] |
寻找核心中间链4
1 | match (source:Method) where source.NAME in ["compare"] |
寻找核心中间链5
1 | match (source:Method) where source.NAME in ["get"] |
poc
原理及分析可以看上一篇
1 | <java.util.PriorityQueue serialization='custom'> |
小结
本文从Gadget挖掘角度,使用tabby复现了几个Gadget的挖掘过程,ssrf的2个链很简单,rce有些个比较复杂,最短路径算法不能直接串起。这些个链的重点在于中间核心链路的组装,本菜🐔也只能倒推出部分节点,还有一些关键转折点如果在不知情的情况下自己来,大概率是发现不了的。
其他思考&总结
- apoc.algo.allSimplePaths 是最短路径,有最短就不会列出其他的,而最短的不一定是有效的,导致可能会漏(也可以搭配[r:CALL|ALIAS*5..8]使用)。
- 中间核心链路的组装,因为invoke等不能自动串起,需要积累。
- 21342、21345 可以看出还有其他的路径,是否也可行?(等有精力再验证下吧😓)
最后再次感谢wh1t3p1g师傅,用tabby混了个cve,也逆向复现了这些Gadgets的挖掘思路。
1.4.17新出那些其他的Gadgets,还没空分析如何找到链的,等有精力再水一节吧。
参考
- [1] tabby-现有利用链覆盖
- [2] 如何高效的挖掘Java反序列化利用链?