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
| Impact | Details |
|---|---|
| Integrity | Scope: 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 Control | Scope: 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
- MITRE Corporation. "CWE-486: Comparison of Classes by Name." https://cwe.mitre.org/data/definitions/486.html
- Oracle. "Secure Coding Guidelines for Java SE."
- CERT Oracle Secure Coding Standard for Java. "OBJ09-J. Compare classes and not class names."