EJB Bad Practices: Use of Class Loader

Description

EJB Bad Practices: Use of Class Loader is a vulnerability where an Enterprise JavaBean violates the EJB specification by directly using class loader functionality. The EJB specification explicitly prohibits beans from creating class loaders, obtaining the current class loader, setting context class loaders, managing security managers, or modifying JVM streams. According to the specification, "these functions are reserved for the EJB container" and allowing bean access "could compromise security and decrease the container's ability to properly manage the runtime environment."

Risk

Using class loaders in EJBs poses serious security and stability risks. Manipulating class loaders can bypass the container's security boundaries, allowing loading of arbitrary code. This can lead to execution of unauthorized code, circumvention of security policies, and privilege escalation. Class loader manipulation can cause memory leaks as classes cannot be garbage collected. It interferes with the container's class isolation mechanisms, potentially causing class version conflicts. Applications become non-portable as class loader behavior varies across containers. Additionally, it can compromise the container's ability to redeploy applications cleanly.

Solution

Do not use class loader APIs in EJB code. For resource loading, use JNDI lookups or container-provided resource mechanisms. For configuration, use environment entries, property files loaded through JNDI, or external configuration services. If dynamic class loading is required for plugin architectures, implement it outside the EJB container or use OSGi-based frameworks designed for this purpose. For reflection-based operations, use standard Java reflection with classes already loaded in the application's classpath. Use container-provided mechanisms for all resource access needs.

Common Consequences

ImpactDetails
ConfidentialityScope: Confidentiality

Execute Unauthorized Code or Commands - Arbitrary class loading can execute malicious code bypassing security controls.
IntegrityScope: Integrity

Modify Application Data - Loaded classes may modify application state or behavior unexpectedly.
AvailabilityScope: Availability

DoS: Resource Consumption - Class loader manipulation can cause memory leaks and resource exhaustion.

Example Code

Vulnerable Code

// Vulnerable: Obtaining ClassLoader in EJB
import javax.ejb.Stateless;
import java.io.*;

@Stateless
public class VulnerableClassLoaderBean implements ConfigService {

    // Vulnerable: Using getClassLoader() in EJB
    public Document loadConfiguration() {
        try {
            // Violates EJB specification
            ClassLoader cl = this.getClass().getClassLoader();

            // Loading resource via class loader
            InputStream is = cl.getResourceAsStream("config.xml");

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

        } catch (Exception e) {
            throw new RuntimeException("Cannot load config", e);
        }
    }

    // Vulnerable: Getting context class loader
    public Properties loadProperties() {
        try {
            // Violates EJB specification
            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("Cannot load properties", e);
        }
    }
}

// Vulnerable: Creating custom ClassLoader in EJB
@Stateless
public class VulnerablePluginBean implements PluginService {

    // Vulnerable: Creating ClassLoader in EJB
    public Object loadPlugin(String className, byte[] classBytes) {
        try {
            // Violates EJB specification - creating class loader
            CustomClassLoader loader = new CustomClassLoader(classBytes);

            // Load and instantiate arbitrary class
            Class<?> pluginClass = loader.loadClass(className);
            return pluginClass.getDeclaredConstructor().newInstance();

        } catch (Exception e) {
            throw new RuntimeException("Plugin load failed", e);
        }
    }

    // Custom class loader - should not exist in EJB
    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);
        }
    }
}

// Vulnerable: Setting context class loader
@Stateless
public class VulnerableContextBean implements ContextService {

    // Vulnerable: Modifying context class loader
    public void executeWithCustomClasspath(String classpath) {
        ClassLoader originalCl = Thread.currentThread().getContextClassLoader();

        try {
            // Violates EJB specification
            URL[] urls = parseClasspath(classpath);
            URLClassLoader customCl = new URLClassLoader(urls, originalCl);

            // Setting context class loader - not allowed
            Thread.currentThread().setContextClassLoader(customCl);

            // Execute with custom classpath
            performOperation();

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

// Vulnerable: Using reflection with class loader manipulation
@Stateless
public class VulnerableDynamicBean implements DynamicService {

    // Vulnerable: Dynamic class loading from external source
    public Object createInstance(String className) {
        try {
            // Violates specification - obtaining class loader
            ClassLoader cl = getClass().getClassLoader();

            // Could load malicious class
            Class<?> clazz = cl.loadClass(className);
            return clazz.getDeclaredConstructor().newInstance();

        } catch (Exception e) {
            throw new RuntimeException("Cannot create instance", e);
        }
    }

    // Vulnerable: Loading from URL
    public Object loadFromUrl(URL jarUrl, String className) {
        try {
            // Creating URL class loader - violates spec
            URLClassLoader ucl = new URLClassLoader(
                new URL[] { jarUrl },
                getClass().getClassLoader()
            );

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

        } catch (Exception e) {
            throw new RuntimeException("Load failed", e);
        }
    }
}

Fixed Code

// Fixed: Use JNDI for resource access
import javax.ejb.Stateless;
import javax.annotation.Resource;
import javax.naming.InitialContext;

@Stateless
public class SecureConfigBean implements ConfigService {

    // Fixed: Use JNDI for configuration
    @Resource(name = "config/settings")
    private String configXml;

    // Fixed: Load configuration through JNDI
    public Document loadConfiguration() throws Exception {
        InitialContext ctx = new InitialContext();

        // Look up configuration from JNDI
        String xmlContent = (String) ctx.lookup("java:comp/env/config/settings");

        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        // Secure 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)));
    }

    // Fixed: Use environment entries for properties
    @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;
    }
}

// Fixed: Use dependency injection for extensibility
import javax.ejb.Stateless;
import javax.enterprise.inject.Instance;
import javax.inject.Inject;

@Stateless
public class SecurePluginBean implements PluginService {

    // Fixed: Use CDI for plugin discovery
    @Inject
    @Any
    private Instance<Plugin> plugins;

    // Fixed: Use CDI qualifiers for specific plugins
    @Inject
    @PluginType("analytics")
    private Plugin analyticsPlugin;

    // Fixed: Get plugins through CDI
    public List<Plugin> getAvailablePlugins() {
        List<Plugin> result = new ArrayList<>();
        for (Plugin plugin : plugins) {
            result.add(plugin);
        }
        return result;
    }

    public Plugin getPlugin(String type) {
        // Use CDI programmatic lookup
        for (Plugin plugin : plugins) {
            if (plugin.getType().equals(type)) {
                return plugin;
            }
        }
        return null;
    }
}

// Plugin interface - implemented by plugin classes
public interface Plugin {
    String getType();
    void execute(Map<String, Object> params);
}

// CDI qualifier for plugin types
@Qualifier
@Retention(RUNTIME)
@Target({FIELD, METHOD, PARAMETER, TYPE})
public @interface PluginType {
    String value();
}

// Fixed: Use standard class for reflection without class loader manipulation
@Stateless
public class SecureDynamicBean implements DynamicService {

    // Fixed: Use Class.forName with known classes only
    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) {
        // Fixed: Whitelist allowed classes
        if (!ALLOWED_CLASSES.contains(className)) {
            throw new SecurityException("Class not allowed: " + className);
        }

        try {
            // Use Class.forName - container controls class loading
            Class<?> clazz = Class.forName(className);
            return clazz.getDeclaredConstructor().newInstance();

        } catch (Exception e) {
            throw new RuntimeException("Cannot create instance", e);
        }
    }
}

// Fixed: Use database or external service for dynamic loading needs
@Stateless
public class SecureExtensionBean implements ExtensionService {

    @PersistenceContext
    private EntityManager em;

    // Fixed: Store extension metadata in database
    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;
    }

    // Extensions are configured, not dynamically loaded
    @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("Extension not found");
        }

        // Use injected handler based on extension type
        switch (extension.getType()) {
            case "reporting":
                reportingHandler.handle(extension.getConfiguration(), params);
                break;
            // Other cases...
            default:
                throw new UnsupportedOperationException(
                    "Unknown extension type: " + extension.getType());
        }
    }
}

// Fixed: Use ServiceLoader through container-managed mechanism
@Singleton
@Startup
public class SecureServiceLoaderBean {

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

    // Fixed: Load at startup, container manages class loading
    @PostConstruct
    public void initProviders() {
        // ServiceLoader works with container's class loading
        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 Examples

No specific CVEs are commonly attributed to this CWE directly, though class loader manipulation has been involved in various Java deserialization and code execution vulnerabilities.


References

  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."