环境代建
去官网下载 jdk8u65
https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html
然后去 下载openjdk
http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/af660750b2f4/
将”Java\jdk1.8.0_65”中的的src.zip解压到当前目录
把下载的openjdk里 /share/classes/ 里面的 sun包 复制到 jdk1.8.0_65\src中,并将 jdk1.8.0_65\src路径添加到项目的源路径
然后”文件”==> “项目结构”==>”SDK”==>”源路径”==>”添加”==>”应用”
从这个网站下载commons-collections-3.2.1.jar
https://nowjava.com/jar/detail/m02261229/commons-collections-3.2.1.jar.html
然后”文件”==> “项目结构”==>”库”==>”添加”==>”应用”
CheckSetValue
Runtime
java任意执行命令的代码
1 | Runtime.getRuntime().exec("calc"); |
将Runtime执行命令的方式以反射的形式写出
1 | Runtime r = Runtime.getRuntime(); |
InvokerTransformer
再InvokerTransformer
类中的transform
这样一段代码非常危险,完全符和反射调用类的成员方法的形式
1 | public Object transform(Object input) { |
而且三个参数完全可控
1 | public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) { |
尝试将Runtime反射的形式改成invokerTransformer调用transform,也是可以执行命令的
1 | Runtime r = Runtime.getRuntime(); |
按照Java反序列化中找POP链的思路,我们需要寻找哪里调用transform方法,然后尝试”移花接木”;
TransformedMap
其实有很多地方都有用到transform方法,但是可以利用的方法在TransformedMap
中的checkSetValue
1 | protected Object checkSetValue(Object value) { |
我们可以看看TransformedMap
类
构造方法的访问修饰符为protected
(只能在同包或者别的包的子类)
1 | protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) { |
此类还有一个public
修饰的静态方法decorate
,返回一个TransformedMap
对象
1 | public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) { |
接下来看看哪里调用了checkSetValue
方法,其实只有一处地方调用了;
AbstractInputCheckedMapDecorator
抽象类AbstractInputCheckedMapDecorator
的静态内部类MapEntry
的setValue
方法调用了
1 | static class MapEntry extends AbstractMapEntryDecorator { |
也是在抽象类AbstractInputCheckedMapDecorator
下的一个静态内部类EntrySetIterator
的next
方法;返回了一个MapEntry对象
1 | public Object next() { |
所以我们将代码改成下面这样也是可以执行命令的,看似有点麻烦,但是动态调试一下就非常简单
1 | Runtime r = Runtime.getRuntime(); |
其实setValue
是一个常用的方法;主要用来处理双列集合的,例如:
1 | HashMap<Object, Object> hashMap = new HashMap<>(); |
我们不妨将代码改成下面,也是可以执行命令的,增强for的底层还是调用了迭代器;
1 | Runtime r = Runtime.getRuntime(); |
可以动态调试看看
实际上AbstractInputCheckedMapDecorator
是TransformedMap
的父类;TransformedMap
没有entrySet()
方法,所以会去他的父类找;
并且赋值parent
为TransformedMap
对象
增强for的底层还是调用了迭代器;返回了一个EntrySetIterator
对象,接下了就是调用hasNext()
和next()
方法
可以着重看一下next
方法;返回一个MapEntry
对象,
最后调用setValue
方法
然后就一步一步调用到invokerTransformer的transform方法
AnnotationInvocationHandler
最终发现sun.reflect.annotation.AnnotationInvocationHandler
类下中的readObject方法调用了setValue
三个问题
- Runtime是没有实现Serializable接口的,无法序列化
- AnnotationInvocationHandler中的readObject执行 memberValue.setValue需要绕过两if判断
- memberValue.setValue中的值无法控制
Serializable
先来解决第一个问题
虽然Runtime是没有实现Serializable接口的,但是Class
实现了Serializable接口;
1 | Runtime r = Runtime.getRuntime(); |
改成下面这种形式
1 | Class r = Runtime.class; |
接着改
1 | Class r = Runtime.class; |
改成这样也是可以执行命令的;但是问题又来了,上面一共执行了三次transform
方法。但是InvokerTransformer
类中只能执行一次transform
方法;
这里又要介绍一个需要利用的类ChainedTransformer
;可以看一下此类的构造方法和transform
方法
1 | //传递一个transformers方法 |
因此我们可以改成下面这种形式,完美解决
1 | Transformer[] transformers = { |
绕过if
再来解决怎么绕过两if判断
1 | private void readObject(java.io.ObjectInputStream s) |
先看第一个if
判断;如果memberType不为空就会接着执行下面的代码;
1 | Annotation是一个注解类,下面三步就是在得到type的成员类型,而this.type = type;就是AnnotationInvocationHandler构造方法中的第一个参数,是我们可控的 |
第二个if
判断
1 | memberType.isInstance(value) |
ConstantTransformer
最后解决memberValue.setValue中的值无法控制
这里需要借助另一个类ConstantTransformer;这个类非常有意思,构造方法传递什么值,transform就返回什么值
1 | public ConstantTransformer(Object constantToReturn) { |
改成下面这种形式
1 | Transformer[] transformers = { |
动态调试看到,new ConstantTransformer(Runtime.class)
将iConstant
赋值为了Runtime.class
;接下来调用ConstantTransformer.transform
方法,不管传过来的”input”是什么值,始终返回Runtime.class
Gadget chain:
1 | ObjectInputStream.readObject() |
poc1
1 | package demo1; |
LazyMap
什么是代理模式?
代理(Proxy)是一种设计模式,为其他对象提供一种代理以控制对这个对象的访问。
代理模式的组成
- 抽象角色:通过接口或抽象类声明真实角色实现的业务方法。
- 代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
- 真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。
代理模式的关键点是:代理对象与目标对象.代理对象是对目标对象的扩展,并会调用目标对象。
静态代理:
代理一个User类的show方法
先创建一个接口作为抽象角色;
1 | public interface IUser { |
创建一个需要被代理的类 User
,
1 | public class User implements IUser { |
再创建一个代理类
1 | public class UserProxy implements IUser{ |
再创建一个测试类
1 | public class ProxyTest { |
总结:
- 静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现接口或继承相同 的父类
缺点:
- 因为代理对象需要和被代理对象实现相同的接口或父类,所以会有太多的代理类
- 一旦接口中增加了方法后,被代理对象和代理对象都需要维护(非常麻烦,不方便)
比如我如果代理的方法还有update和dowload
需要在接口,被代理的类,和代理类都需要写update和dowload方法,这就非常繁琐;
动态代理:
代理一个User类的show方法
需要代理的类和接口都和动态代理展示的一样
将静态代理类改成动态代理类
1 | import java.lang.reflect.InvocationHandler; |
测试类
1 | public class ProxyTest { |
当调用o.show()
时,就会去调用UserProxy002
类中的invoke
方法,懂了这一点的话,看第二条链会简单许多;
LazyMap
第二条链的入口和后面一部分都类似,
我们不用TransformedMap
,改用LazyMap
;
LazyMap
中的get方法调用了factory.transform(key);
1 | public Object get(Object key) { |
invoke
而在AnnotationInvocationHandler
中的invoke
方法中调用了get
,并且memberValues是可控的;
1 | public Object invoke(Object proxy, Method method, Object[] args) { |
我们仍需要绕过两if
;
第一个if
,调用的方法不能是equals
;第二个if
,只能代理无参方法,否则报异常;
Gadget chain:
1 | ObjectInputStream.readObject() |
poc2
1 | package demo1; |
注意:o2.entrySet() ==> o1.invoke ==> lazyMap.get => …