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
| Impact | Details |
|---|---|
| Confidentiality | Scope: Confidentiality Execute Unauthorized Code or Commands - Arbitrary class loading can execute malicious code bypassing security controls. |
| Integrity | Scope: Integrity Modify Application Data - Loaded classes may modify application state or behavior unexpectedly. |
| Availability | Scope: 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
- MITRE Corporation. "CWE-578: EJB Bad Practices: Use of Class Loader." https://cwe.mitre.org/data/definitions/578.html
- Oracle. "Enterprise JavaBeans Specification."
- CERT. "SEC58-J. Deserialization methods should not perform potentially dangerous operations."