EJB Bad Practices: Verwendung von Class Loader

Beschreibung

EJB Bad Practices: Verwendung von Class Loader ist eine Schwachstelle, bei der ein Enterprise JavaBean die EJB-Spezifikation verletzt, indem es direkt Class-Loader-Funktionalität verwendet. Die EJB-Spezifikation verbietet Beans ausdrücklich, Class Loader zu erstellen, den aktuellen Class Loader zu erhalten, Context Class Loader zu setzen, Security Manager zu verwalten oder JVM-Streams zu modifizieren. Laut Spezifikation sind "diese Funktionen dem EJB-Container vorbehalten", und Bean-Zugriff darauf "könnte die Sicherheit gefährden und die Fähigkeit des Containers verringern, die Laufzeitumgebung ordnungsgemäß zu verwalten."

Risiko

Die Verwendung von Class Loadern in EJBs birgt ernste Sicherheits- und Stabilitätsrisiken. Die Manipulation von Class Loadern kann die Sicherheitsgrenzen des Containers umgehen und das Laden von beliebigem Code ermöglichen. Dies kann zur Ausführung von nicht autorisiertem Code, Umgehung von Sicherheitsrichtlinien und Privilegieneskalation führen. Class-Loader-Manipulation kann Speicherlecks verursachen, da Klassen nicht garbage-collected werden können. Sie stört die Klassenisolationsmechanismen des Containers und kann potenziell Klassenversionskonflikte verursachen. Anwendungen werden nicht portabel, da das Class-Loader-Verhalten zwischen Containern variiert. Zusätzlich kann sie die Fähigkeit des Containers beeinträchtigen, Anwendungen saüber neu zu deployen.

Lösung

Verwenden Sie keine Class-Loader-APIs in EJB-Code. Für Ressourcenladung nutzen Sie JNDI-Lookups oder vom Container bereitgestellte Ressourcenmechanismen. Für Konfiguration verwenden Sie Umgebungseinträge, über JNDI geladene Property-Dateien oder externe Konfigurationsdienste. Wenn dynamisches Klassenladen für Plugin-Architekturen erforderlich ist, implementieren Sie es außerhalb des EJB-Containers oder verwenden Sie OSGi-basierte Frameworks, die für diesen Zweck konzipiert sind. Für Reflection-basierte Operationen verwenden Sie Standard-Java-Reflection mit bereits im Anwendungs-Classpath geladenen Klassen. Nutzen Sie Container-bereitgestellte Mechanismen für alle Ressourcenzugriffsbedürfnisse.

Häufige Auswirkungen

AuswirkungDetails
VertraulichkeitBereich: Vertraulichkeit

Ausführung nicht autorisierter Codes oder Befehle - Beliebiges Klassenladen kann bösartigen Code unter Umgehung von Sicherheitskontrollen ausführen.
IntegritätBereich: Integrität

Anwendungsdaten modifizieren - Geladene Klassen können den Anwendungszustand oder das Verhalten unerwartet modifizieren.
VerfügbarkeitBereich: Verfügbarkeit

DoS: Ressourcenverbrauch - Class-Loader-Manipulation kann Speicherlecks und Ressourcenerschöpfung verursachen.

Beispielcode

Verwundbarer Code

// Verwundbar: ClassLoader in EJB erhalten
import javax.ejb.Stateless;
import java.io.*;

@Stateless
public class VulnerableClassLoaderBean implements ConfigService {

    // Verwundbar: getClassLoader() in EJB verwenden
    public Document loadConfiguration() {
        try {
            // Verletzt EJB-Spezifikation
            ClassLoader cl = this.getClass().getClassLoader();

            // Ressource über Class Loader laden
            InputStream is = cl.getResourceAsStream("config.xml");

            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = dbf.newDocumentBuilder();
            return db.parse(is);

        } catch (Exception e) {
            throw new RuntimeException("Kann Konfiguration nicht laden", e);
        }
    }

    // Verwundbar: Context Class Loader erhalten
    public Properties loadProperties() {
        try {
            // Verletzt EJB-Spezifikation
            ClassLoader contextCl = Thread.currentThread().getContextClassLoader();

            InputStream is = contextCl.getResourceAsStream("app.properties");
            Properties props = new Properties();
            props.load(is);
            return props;

        } catch (Exception e) {
            throw new RuntimeException("Kann Properties nicht laden", e);
        }
    }
}

// Verwundbar: Benutzerdefinierten ClassLoader in EJB erstellen
@Stateless
public class VulnerablePluginBean implements PluginService {

    // Verwundbar: ClassLoader in EJB erstellen
    public Object loadPlugin(String className, byte[] classBytes) {
        try {
            // Verletzt EJB-Spezifikation - Class Loader erstellen
            CustomClassLoader loader = new CustomClassLoader(classBytes);

            // Beliebige Klasse laden und instanziieren
            Class<?> pluginClass = loader.loadClass(className);
            return pluginClass.getDeclaredConstructor().newInstance();

        } catch (Exception e) {
            throw new RuntimeException("Plugin-Ladung fehlgeschlagen", e);
        }
    }

    // Benutzerdefinierter Class Loader - sollte in EJB nicht existieren
    private static class CustomClassLoader extends ClassLoader {
        private byte[] classBytes;

        public CustomClassLoader(byte[] bytes) {
            this.classBytes = bytes;
        }

        @Override
        protected Class<?> findClass(String name) {
            return defineClass(name, classBytes, 0, classBytes.length);
        }
    }
}

// Verwundbar: Context Class Loader setzen
@Stateless
public class VulnerableContextBean implements ContextService {

    // Verwundbar: Context Class Loader modifizieren
    public void executeWithCustomClasspath(String classpath) {
        ClassLoader originalCl = Thread.currentThread().getContextClassLoader();

        try {
            // Verletzt EJB-Spezifikation
            URL[] urls = parseClasspath(classpath);
            URLClassLoader customCl = new URLClassLoader(urls, originalCl);

            // Context Class Loader setzen - nicht erlaubt
            Thread.currentThread().setContextClassLoader(customCl);

            // Mit benutzerdefiniertem Classpath ausführen
            performOperation();

        } finally {
            Thread.currentThread().setContextClassLoader(originalCl);
        }
    }
}

// Verwundbar: Reflection mit Class-Loader-Manipulation verwenden
@Stateless
public class VulnerableDynamicBean implements DynamicService {

    // Verwundbar: Dynamisches Klassenladen aus externer Quelle
    public Object createInstance(String className) {
        try {
            // Verletzt Spezifikation - Class Loader erhalten
            ClassLoader cl = getClass().getClassLoader();

            // Könnte bösartige Klasse laden
            Class<?> clazz = cl.loadClass(className);
            return clazz.getDeclaredConstructor().newInstance();

        } catch (Exception e) {
            throw new RuntimeException("Kann Instanz nicht erstellen", e);
        }
    }

    // Verwundbar: Von URL laden
    public Object loadFromUrl(URL jarUrl, String className) {
        try {
            // URL-Class-Loader erstellen - verletzt Spezifikation
            URLClassLoader ucl = new URLClassLoader(
                new URL[] { jarUrl },
                getClass().getClassLoader()
            );

            Class<?> clazz = ucl.loadClass(className);
            return clazz.getDeclaredConstructor().newInstance();

        } catch (Exception e) {
            throw new RuntimeException("Ladung fehlgeschlagen", e);
        }
    }
}

Lösungscode

// Behoben: JNDI für Ressourcenzugriff verwenden
import javax.ejb.Stateless;
import javax.annotation.Resource;
import javax.naming.InitialContext;

@Stateless
public class SecureConfigBean implements ConfigService {

    // Behoben: JNDI für Konfiguration verwenden
    @Resource(name = "config/settings")
    private String configXml;

    // Behoben: Konfiguration über JNDI laden
    public Document loadConfiguration() throws Exception {
        InitialContext ctx = new InitialContext();

        // Konfiguration aus JNDI suchen
        String xmlContent = (String) ctx.lookup("java:comp/env/config/settings");

        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        // Sicheres XML-Parsing
        dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        DocumentBuilder db = dbf.newDocumentBuilder();

        return db.parse(new InputSource(new StringReader(xmlContent)));
    }

    // Behoben: Umgebungseinträge für Properties verwenden
    @Resource(name = "app/maxConnections")
    private Integer maxConnections;

    @Resource(name = "app/timeout")
    private Integer timeout;

    public Properties getAppProperties() {
        Properties props = new Properties();
        props.setProperty("maxConnections", String.valueOf(maxConnections));
        props.setProperty("timeout", String.valueOf(timeout));
        return props;
    }
}

// Behoben: Dependency Injection für Erweiterbarkeit verwenden
import javax.ejb.Stateless;
import javax.enterprise.inject.Instance;
import javax.inject.Inject;

@Stateless
public class SecurePluginBean implements PluginService {

    // Behoben: CDI für Plugin-Erkennung verwenden
    @Inject
    @Any
    private Instance<Plugin> plugins;

    // Behoben: CDI-Qualifier für spezifische Plugins verwenden
    @Inject
    @PluginType("analytics")
    private Plugin analyticsPlugin;

    // Behoben: Plugins über CDI abrufen
    public List<Plugin> getAvailablePlugins() {
        List<Plugin> result = new ArrayList<>();
        for (Plugin plugin : plugins) {
            result.add(plugin);
        }
        return result;
    }

    public Plugin getPlugin(String type) {
        // CDI-programmatische Suche verwenden
        for (Plugin plugin : plugins) {
            if (plugin.getType().equals(type)) {
                return plugin;
            }
        }
        return null;
    }
}

// Plugin-Interface - von Plugin-Klassen implementiert
public interface Plugin {
    String getType();
    void execute(Map<String, Object> params);
}

// CDI-Qualifier für Plugin-Typen
@Qualifier
@Retention(RUNTIME)
@Target({FIELD, METHOD, PARAMETER, TYPE})
public @interface PluginType {
    String value();
}

// Behoben: Standard-Klasse für Reflection ohne Class-Loader-Manipulation verwenden
@Stateless
public class SecureDynamicBean implements DynamicService {

    // Behoben: Class.forName nur mit bekannten Klassen verwenden
    private static final Set<String> ALLOWED_CLASSES = Set.of(
        "com.example.handlers.TextHandler",
        "com.example.handlers.JsonHandler",
        "com.example.handlers.XmlHandler"
    );

    public Object createInstance(String className) {
        // Behoben: Erlaubte Klassen whitelisten
        if (!ALLOWED_CLASSES.contains(className)) {
            throw new SecurityException("Klasse nicht erlaubt: " + className);
        }

        try {
            // Class.forName verwenden - Container kontrolliert Klassenladen
            Class<?> clazz = Class.forName(className);
            return clazz.getDeclaredConstructor().newInstance();

        } catch (Exception e) {
            throw new RuntimeException("Kann Instanz nicht erstellen", e);
        }
    }
}

// Behoben: Datenbank oder externen Service für dynamische Ladebedürfnisse verwenden
@Stateless
public class SecureExtensionBean implements ExtensionService {

    @PersistenceContext
    private EntityManager em;

    // Behoben: Erweiterungs-Metadaten in Datenbank speichern
    public ExtensionDTO getExtension(String extensionId) {
        ExtensionEntity entity = em.find(ExtensionEntity.class, extensionId);
        if (entity == null) {
            return null;
        }

        ExtensionDTO dto = new ExtensionDTO();
        dto.setId(entity.getId());
        dto.setName(entity.getName());
        dto.setConfiguration(entity.getConfiguration());
        return dto;
    }

    // Erweiterungen werden konfiguriert, nicht dynamisch geladen
    @Inject
    @Extension("reporting")
    private ExtensionHandler reportingHandler;

    public void executeExtension(String extensionId, Map<String, Object> params) {
        ExtensionDTO extension = getExtension(extensionId);
        if (extension == null) {
            throw new NotFoundException("Erweiterung nicht gefunden");
        }

        // Injizierten Handler basierend auf Erweiterungstyp verwenden
        switch (extension.getType()) {
            case "reporting":
                reportingHandler.handle(extension.getConfiguration(), params);
                break;
            // Andere Fälle...
            default:
                throw new UnsupportedOperationException(
                    "Unbekannter Erweiterungstyp: " + extension.getType());
        }
    }
}

// Behoben: ServiceLoader über Container-verwalteten Mechanismus verwenden
@Singleton
@Startup
public class SecureServiceLoaderBean {

    private Map<String, ServiceProvider> providers = new HashMap<>();

    // Behoben: Beim Start laden, Container verwaltet Klassenladen
    @PostConstruct
    public void initProviders() {
        // ServiceLoader funktioniert mit Container-Klassenladen
        ServiceLoader<ServiceProvider> loader =
            ServiceLoader.load(ServiceProvider.class);

        for (ServiceProvider provider : loader) {
            providers.put(provider.getName(), provider);
        }
    }

    public ServiceProvider getProvider(String name) {
        return providers.get(name);
    }
}

CVE-Beispiele

Keine spezifischen CVEs werden dieser CWE direkt zugeordnet, obwohl Class-Loader-Manipulation in verschiedenen Java-Deserialisierungs- und Code-Ausführungsschwachstellen involviert war.


Referenzen

  1. MITRE Corporation. "CWE-578: EJB Bad Practices: Use of Class Loader." https://cwe.mitre.org/data/definitions/578.html
  2. Oracle. "Enterprise JavaBeans Specification."
  3. CERT. "SEC58-J. Deserialization methods should not perform potentially dangerous operations."