环境代建
去官网下载 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 => …

