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
| Auswirkung | Details |
|---|---|
| Vertraulichkeit | Bereich: Vertraulichkeit Ausführung nicht autorisierter Codes oder Befehle - Beliebiges Klassenladen kann bösartigen Code unter Umgehung von Sicherheitskontrollen ausführen. |
| Integrität | Bereich: Integrität Anwendungsdaten modifizieren - Geladene Klassen können den Anwendungszustand oder das Verhalten unerwartet modifizieren. |
| Verfügbarkeit | Bereich: 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
- 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."