反序列化思路(如何高效地捡漏反序列化利用链)(1)

#1 前言

之前在文章如何高效地挖掘Java反序列化利用链中提到了我是如何高效挖掘利用链的,这其中提到了工具tabby。

目前,tabby开源也有一段时间了,这段时间里有不少小伙伴问我如何在实际环境中更好地使用它?

为此,本文将介绍我是如何利用tabby捡漏XStream CVE-2021-39147 && CVE-2021-39148。

#2 材料准备

跟之前那篇文章提到的一样,我们先对JDK生成代码属性图

# 生成图缓存文件 java -Xmx8g -jar build/libs/tabby-1.1.0-RELEASE.jar --isJDKOnly # 导入Neo4j图数据 java -Xmx8g -jar build/libs/tabby-1.1.0-RELEASE.jar --isSaveOnly

#3 背景介绍

XStream自1.4.16版本的修复之后,有师傅在TSRC提交了CVE-2021-29505(PS: TSRC yyds)

该链跟之前我们在1.4.15版本时候挖的利用链都不太一样,它重新找到了一个新的触发toString的对象。这条链之前应该有师傅分析过了,就不细说了,这里直接贴一下调用链。

javax.naming.ldap.Rdn$RdnEntry#compareTo com.sun.org.apache.xpath.internal.objects.XString#equal com.sun.xml.internal.ws.api.message.Packet#toString com.sun.xml.internal.ws.message.saaj.SAAJMessage#copy com.sun.xml.internal.ws.message.saaj.SAAJMessage#getAttachments com.sun.xml.internal.ws.message.saaj.SAAJMessage$SAAJAttachmentSet#<init> com.sun.xml.internal.messaging.saaj.soap.ver1_1.Message1_1Impl#getAttachments com.sun.xml.internal.messaging.saaj.soap.ver1_1.Message1_1Impl#initializeAllAttachments com.sun.xml.internal.messaging.saaj.packaging.mime.internet.MimePullMultipart#getCount com.sun.xml.internal.messaging.saaj.packaging.mime.internet.MimePullMultipart#parse com.sun.xml.internal.messaging.saaj.packaging.mime.internet.MimePullMultipart#parseAll com.sun.xml.internal.org.jvnet.mimepull.MIMEMessage#getAttachments com.sun.xml.internal.org.jvnet.mimepull.MIMEMessage#parseAll com.sun.xml.internal.org.jvnet.mimepull.MIMEMessage#makeProgress com.sun.org.apache.xml.internal.security.keys.storage.implementations.KeyStoreResolver$KeyStoreIterator#hasNext com.sun.org.apache.xml.internal.security.keys.storage.implementations.KeyStoreResolver$KeyStoreIterator#findNextCert com.sun.jndi.toolkit.dir.LazySearchEnumerationImpl#nextElement com.sun.jndi.toolkit.dir.LazySearchEnumerationImpl#findNextMatch com.sun.jndi.rmi.registry.BindingEnumeration#next sun.rmi.registry.RegistryImpl_Stub#lookup

这里接过toString函数的是com.sun.xml.internal.ws.api.message.Packet,并且最后以com.sun.jndi.rmi.registry.BindingEnumeration#next触发lookup函数

反序列化思路(如何高效地捡漏反序列化利用链)(2)

这里红框框的地方ctx被设置为sun.rmi.registry.RegistryImpl_Stub来对外发起RMI连接。

这里另外说一下,其实CVE-2021-29505的利用链可以简化为

sun.rmi.registry.RegistryImpl_Stub#readObject sun.rmi.server.UnicastRef#readExternal # trigger rmi connect

有兴趣的小伙伴可以看RMI绕过相关的文章,除了RegistryImpl_Stub,另外还有link

回到主题,通过29505这条链我们可以对外发起RMI连接,并且他是完全绕过16版本的黑名单的。

为此,XStream的官方出了17版本的黑名单补丁,这里来看一下17版本下所有的黑名单

黑名单字符对象

this.denyTypes(new String[] { "java.beans.EventHandler", "java.lang.ProcessBuilder", "javax.imageio.ImageIO$ContainsFilter", "jdk.nashorn.internal.objects.NativeString", "com.sun.corba.se.impl.activation.ServerTableEntry", "com.sun.tools.javac.processing.JavacProcessingEnvironment$NameProcessIterator", "sun.awt.datatransfer.DataTransferer$IndexOrderComparator", "sun.swing.SwingLazyValue" });

黑名单正则

GETTER_SETTER_REFLECTION = Pattern.compile(".*\\$GetterSetterReflection"); PRIVILEGED_GETTER = Pattern.compile(".*\\$PrivilegedGetter"); LAZY_ENUMERATORS = Pattern.compile(".*\\.Lazy(?:Search)?Enumeration.*"); LAZY_ITERATORS = Pattern.compile(".*\\$LazyIterator"); JAXWS_ITERATORS = Pattern.compile(".*\\$ServiceNameIterator"); JAVAFX_OBSERVABLE_LIST__ = Pattern.compile("javafx\\.collections\\.ObservableList\\$.*"); JAVAX_CRYPTO = Pattern.compile("javax\\.crypto\\..*"); JAVA_RMI = Pattern.compile("(?:java|sun)\\.rmi\\..*"); BCEL_CL = Pattern.compile(".*\\.bcel\\..*\\.util\\.ClassLoader");

黑名单继承对象

this.denyTypeHierarchy(InputStream.class); this.denyTypeHierarchyDynamically("java.nio.channels.Channel"); this.denyTypeHierarchyDynamically("javax.activation.DataSource"); this.denyTypeHierarchyDynamically("javax.sql.rowset.BaseRowSet");

从上面列举的黑名单,我们可以知道17版本的补丁对29505这条链的几个对象进行了黑名单处理。

Pattern.compile("(?:java|sun)\\.rmi\\..*"); ==拉黑==> sun.rmi.registry.RegistryImpl_Stub, sun.rmi.server.UnicastRef Pattern.compile(".*\\.Lazy(?:Search)?Enumeration.*"); ==拉黑==> com.sun.jndi.toolkit.dir.LazySearchEnumerationImpl

到这里我们可以发现,对于29505这条利用链,如下部分利用链仍然是可用的

javax.naming.ldap.Rdn$RdnEntry#compareTo com.sun.org.apache.xpath.internal.objects.XString#equal com.sun.xml.internal.ws.api.message.Packet#toString com.sun.xml.internal.ws.message.saaj.SAAJMessage#copy com.sun.xml.internal.ws.message.saaj.SAAJMessage#getAttachments com.sun.xml.internal.ws.message.saaj.SAAJMessage$SAAJAttachmentSet#<init> com.sun.xml.internal.messaging.saaj.soap.ver1_1.Message1_1Impl#getAttachments com.sun.xml.internal.messaging.saaj.soap.ver1_1.Message1_1Impl#initializeAllAttachments com.sun.xml.internal.messaging.saaj.packaging.mime.internet.MimePullMultipart#getCount com.sun.xml.internal.messaging.saaj.packaging.mime.internet.MimePullMultipart#parse com.sun.xml.internal.messaging.saaj.packaging.mime.internet.MimePullMultipart#parseAll com.sun.xml.internal.org.jvnet.mimepull.MIMEMessage#getAttachments com.sun.xml.internal.org.jvnet.mimepull.MIMEMessage#parseAll com.sun.xml.internal.org.jvnet.mimepull.MIMEMessage#makeProgress com.sun.org.apache.xml.internal.security.keys.storage.implementations.KeyStoreResolver$KeyStoreIterator#hasNext com.sun.org.apache.xml.internal.security.keys.storage.implementations.KeyStoreResolver$KeyStoreIterator#findNextCert

如果能找到从findNextCert函数开始的其他端点,我们仍然能对17版本的补丁进行绕过。

答案当然是肯定的,必然存在一些其他的端点,但是人工去挖掘会特别耗时,那么为了高效捡漏,我们当然不能人工去做这件事。这里我们的tabby就要登场了。

#4 高效捡漏

这里我们先来分析一下findNextCert函数

反序列化思路(如何高效地捡漏反序列化利用链)(3)

从这里看,我们可以利用类属性aliases和keyStore来进行后续利用链的挖掘。

这里以aliases举例,之前29505利用链就是依靠LazySearchEnumerationImpl#nextElement开始找到的rmi的sink函数。那么,我们现在重新找一个新的,就需要满足如下条件:

如果能满足上述条件,那么我们就挖到了能绕过17版本补丁的新利用链。接下来的工作就交给tabby来做了。

首先,我们需要根据上述的条件,描述出具体的查询语句。先来限制一下source函数

match (source:Method {NAME:"nextElement"}) <-[:HAS]-(cls:Class)-[:INTERFACE|EXTENDS*] ->(cls1:Class {NAME:"java.util.Enumeration"}) match (source)-[:CALL]->(m1:Method)

source函数为nextElement,并且实现了java.util.Enumeration接口

再来限制sink函数

match (sink:Method {IS_SINK:true, VUL:"JNDI"})

这里就限制当前sink函数最终能达成JNDI注入的效果。

最后,我们再来限制一下路径上不能出现的黑名单对象

call apoc.algo.allSimplePaths(sink, m1, "<CALL|ALIAS", 8) yield path where none(n in nodes(path) where n.CLASSNAME in ["java.beans.EventHandler","java.lang.ProcessBuilder", "javax.imageio.ImageIO$ContainsFilter","jdk.nashorn.internal.objects.NativeString", "com.sun.corba.se.impl.activation.ServerTableEntry", "com.sun.tools.javac.processing.JavacProcessingEnvironment$NameProcessIterator", "sun.awt.datatransfer.DataTransferer$IndexOrderComparator","sun.swing.SwingLazyValue", "com.sun.jndi.toolkit.dir.LazySearchEnumerationImpl","sun.rmi.registry.RegistryImpl_Stub", "com.sun.jndi.dns.BindingEnumeration","com.sun.jndi.cosnaming.CNBindingEnumeration","com.sun.jndi.toolkit.dir.HierMemDirCtx$FlatBindings","com.sun.jndi.ldap.LdapReferralException"]) return source,path limit 50

这里第9行上的黑名单是后续人工排除后添加的(不可行或构造过于麻烦)。

把上面的3个部分合起来后查询最终能得到如下3条利用链。

反序列化思路(如何高效地捡漏反序列化利用链)(4)

分别是一下对象

他们分别最终将调用sink函数NamingManager.getContext和DirectoryManager.getObjectInstance

这两个sink函数是JNDI处理Reference的函数,通过这两个函数可以在特定JDK版本下载入外部的任意代码,也可以在tomcat下执行el表达式,具体可以看JNDI关于Reference的知识。

#5 补丁修复

XStream 1.4.18版本开始将默认开启白名单模式,只允许几个基础类型的进行还原。

反序列化思路(如何高效地捡漏反序列化利用链)(5)

但是这里想不明白的是为何将之前的黑名单处理直接删除了,这意味着未来XStream的安全性将依靠开发者对于反序列化风险的认识。

此外,后续版本绕过默认白名单还是存在可能性的,但是估计没办法像现在这样能造成那么大的危害了吧。

#6 总结

本文讲述了我是如何通过tabby来捡漏利用链的,这里欢迎师傅们给tabby提pr或issue。

另外,这里还有一件趣事,由于国内过于积极提交利用链,XStream官方无奈之下默认开启白名单的方式来解决后续可能的绕过。嗯,手动狗头。

反序列化思路(如何高效地捡漏反序列化利用链)(6)

,