Comparison of Classes by Name

Description

Comparison of Classes by Name is a vulnerability where code compares classes using their string names rather than actual class identity. When trust decisions rely on class names instead of verifying actual class identity, attackers can create malicious classes with names matching trusted classes to exploit that trust relationship. Multiple classes can share identical names when loaded by different class loaders or from different packages, making name-based comparison inherently unreliable for security decisions.

Risk

Name-based class comparison creates serious security vulnerabilities in Java applications. Attackers can craft malicious classes with names identical to trusted classes, then submit objects of these malicious classes to bypass security checks. In Java, different class loaders can load different classes with the same fully qualified name, and these classes are not equal even though their names match. This allows attackers to inject malicious code that passes name-based trust checks. The vulnerability is particularly dangerous in plugin systems, deserialization contexts, and any code that dynamically loads and validates classes.

Solution

Use class equivalency checking with the getClass() method and the == operator to verify actual type identity. Compare against the actual class object rather than its name. For example, use obj.getClass() == TrustedClass.class instead of comparing name strings. When validating class hierarchies, use instanceof or Class.isAssignableFrom(). Never use class names for security-critical decisions. If class name comparison is unavoidable, also verify the class loader to ensure the class comes from a trusted source.

Common Consequences

ImpactDetails
IntegrityScope: Integrity, Confidentiality, Availability

Execute Unauthorized Code - The system may execute incorrect or unintended code when relying solely on class name identity, allowing attackers to substitute malicious classes.
Access ControlScope: Access Control

Bypass Protection Mechanism - Attackers can bypass type-based security checks by providing malicious objects with matching class names.

Example Code

Vulnerable Code

// Vulnerable: Comparing classes by name
public class VulnerableClassComparison {

    // Vulnerable: Name-based class comparison for trust decision
    public void processObject(Object inputObject) {
        // Vulnerable: An attacker's class with the same name passes this check
        if (inputObject.getClass().getName().equals("com.trusted.SecurityToken")) {
            // Attacker can create their own SecurityToken class
            // and it will pass this check
            SecurityToken token = (SecurityToken) inputObject;
            grantAccess(token);
        }
    }

    // Vulnerable: String comparison of class names
    public boolean isTrustedClass(Class<?> clazz) {
        // Vulnerable: Multiple classes can have the same name
        String className = clazz.getName();
        return className.equals("TrustedProcessor") ||
               className.equals("SecureHandler") ||
               className.equals("AuthenticatedUser");
    }

    // Vulnerable: Simple name comparison (even worse)
    public void validatePlugin(Object plugin) {
        // Vulnerable: Only checks simple name, ignores package
        if (plugin.getClass().getSimpleName().equals("SafePlugin")) {
            // Any class named "SafePlugin" from any package passes
            executePlugin(plugin);
        }
    }

    // Vulnerable: Deserialization with name-based validation
    public Object deserializeObject(byte[] data, String expectedClassName)
            throws Exception {
        ObjectInputStream ois = new ObjectInputStream(
            new ByteArrayInputStream(data)
        );
        Object obj = ois.readObject();

        // Vulnerable: Attacker controls both the serialized data
        // and can create a malicious class with the expected name
        if (obj.getClass().getName().equals(expectedClassName)) {
            return obj;
        }
        throw new SecurityException("Unexpected class");
    }

    // Vulnerable: Class whitelist using names
    private static final Set<String> ALLOWED_CLASSES = Set.of(
        "java.lang.String",
        "java.lang.Integer",
        "com.app.SafeData"
    );

    public void processWithWhitelist(Object obj) {
        // Vulnerable: Custom class with same name as whitelisted class
        if (ALLOWED_CLASSES.contains(obj.getClass().getName())) {
            // Trusts any class with a matching name
            process(obj);
        }
    }
}
// Vulnerable: Plugin system using name-based trust
public class VulnerablePluginLoader {

    public void loadPlugin(String pluginPath) throws Exception {
        URLClassLoader loader = new URLClassLoader(
            new URL[]{new File(pluginPath).toURI().toURL()}
        );

        Class<?> pluginClass = loader.loadClass("com.plugins.Plugin");

        // Vulnerable: Only checking if class name matches expected interface
        if (pluginClass.getName().endsWith("Plugin")) {
            Object plugin = pluginClass.getDeclaredConstructor().newInstance();
            // Attacker's malicious plugin has the expected name
            runPlugin(plugin);
        }
    }

    // Vulnerable: Interface check by name
    public boolean implementsInterface(Object obj, String interfaceName) {
        for (Class<?> iface : obj.getClass().getInterfaces()) {
            // Vulnerable: Comparing interface names
            if (iface.getName().equals(interfaceName)) {
                return true;
            }
        }
        return false;
    }
}

Fixed Code

// Fixed: Comparing classes by identity
public class SecureClassComparison {

    // Fixed: Use class identity comparison
    public void processObject(Object inputObject) {
        // Fixed: Compare actual class objects, not names
        if (inputObject.getClass() == SecurityToken.class) {
            // Only the actual SecurityToken class from our classloader passes
            SecurityToken token = (SecurityToken) inputObject;
            grantAccess(token);
        }
    }

    // Fixed: Use Class object comparison
    public boolean isTrustedClass(Class<?> clazz) {
        // Fixed: Compare against actual class objects
        return clazz == TrustedProcessor.class ||
               clazz == SecureHandler.class ||
               clazz == AuthenticatedUser.class;
    }

    // Fixed: Use instanceof for type checking
    public void validatePlugin(Object plugin) {
        // Fixed: instanceof checks actual type hierarchy
        if (plugin instanceof SafePlugin) {
            // Only actual SafePlugin instances pass
            executePlugin((SafePlugin) plugin);
        }
    }

    // Fixed: Class-based validation with classloader verification
    public Object deserializeObject(byte[] data, Class<?> expectedClass)
            throws Exception {
        ObjectInputStream ois = new ObjectInputStream(
            new ByteArrayInputStream(data)
        );
        Object obj = ois.readObject();

        // Fixed: Compare class objects and verify classloader
        if (obj.getClass() == expectedClass &&
            obj.getClass().getClassLoader() == expectedClass.getClassLoader()) {
            return obj;
        }
        throw new SecurityException("Unexpected class or classloader");
    }

    // Fixed: Class whitelist using actual Class objects
    private static final Set<Class<?>> ALLOWED_CLASSES = Set.of(
        String.class,
        Integer.class,
        SafeData.class
    );

    public void processWithWhitelist(Object obj) {
        // Fixed: Check against actual class objects
        if (ALLOWED_CLASSES.contains(obj.getClass())) {
            process(obj);
        }
    }

    // Fixed: Type-safe processing with generics
    public <T> void processTypeSafe(T obj, Class<T> expectedType) {
        // Fixed: Verify actual class
        if (expectedType.isInstance(obj)) {
            T typedObj = expectedType.cast(obj);
            processInternal(typedObj);
        }
    }
}
// Fixed: Secure plugin system
public class SecurePluginLoader {

    // Fixed: Define expected interface
    private static final Class<Plugin> PLUGIN_INTERFACE = Plugin.class;

    public void loadPlugin(String pluginPath) throws Exception {
        URLClassLoader loader = new URLClassLoader(
            new URL[]{new File(pluginPath).toURI().toURL()},
            getClass().getClassLoader()  // Set parent classloader
        );

        Class<?> pluginClass = loader.loadClass("com.plugins.ConcretePlugin");

        // Fixed: Check if class implements our Plugin interface
        if (PLUGIN_INTERFACE.isAssignableFrom(pluginClass)) {
            Plugin plugin = (Plugin) pluginClass
                .getDeclaredConstructor()
                .newInstance();

            // Fixed: Additional classloader verification
            if (verifyClassLoader(plugin.getClass())) {
                runPlugin(plugin);
            }
        }
    }

    // Fixed: Interface check by class identity
    public boolean implementsInterface(Object obj, Class<?> interfaceClass) {
        // Fixed: Use isAssignableFrom for proper type checking
        return interfaceClass.isAssignableFrom(obj.getClass());
    }

    // Fixed: Verify class comes from trusted classloader
    private boolean verifyClassLoader(Class<?> clazz) {
        ClassLoader loader = clazz.getClassLoader();
        while (loader != null) {
            if (loader == getClass().getClassLoader() ||
                loader == ClassLoader.getSystemClassLoader()) {
                return true;
            }
            loader = loader.getParent();
        }
        return loader == null; // Bootstrap classloader is trusted
    }
}

CVE Examples

No specific CVEs are listed in the MITRE database for this CWE. However, the vulnerability pattern is documented in:

  • Java security guidelines and best practices
  • Deserialization vulnerability research (related to CWE-502)

References

  1. MITRE Corporation. "CWE-486: Comparison of Classes by Name." https://cwe.mitre.org/data/definitions/486.html
  2. Oracle. "Secure Coding Guidelines for Java SE."
  3. CERT Oracle Secure Coding Standard for Java. "OBJ09-J. Compare classes and not class names."