CWE-470: Use of Externally-Controlled Input to Select Classes or Code ('Unsafe Reflection')
Overview
Unsafe reflection occurs when applications use untrusted input to select classes, methods, or code to instantiate/execute via reflection APIs (Class.forName, eval, import), enabling arbitrary code execution, object instantiation, and complete application compromise.
OWASP Classification
A05:2025 - Injection
Risk
Critical: Unsafe reflection enables remote code execution, instantiation of dangerous classes (Runtime, ProcessBuilder), method invocation on arbitrary objects, deserialization attacks, and complete server compromise through reflection-based exploits.
Relationship to Other CWEs
- CWE-470 (This page): Untrusted input selecting classes or methods via reflection.
- CWE-94 (Code Injection) / CWE-95: Untrusted input executed as code.
- CWE-502 (Insecure Deserialization): Untrusted input controlling object instantiation via deserialization.
Remediation Steps
Core principle: Never allow untrusted input to select classes/types/methods for execution; map to an explicit allowlist or factory.
Locate the unsafe reflection vulnerability
- Review the data_paths in security scan results to identify where untrusted input selects classes/code
- Identify the source: where class name, method name, or code comes from (user input, HTTP parameters, external files)
- Trace to the sink:
Class.forName(),getMethod(),eval(),ScriptEngine.eval(), dynamic imports - Determine risk: what classes/methods could attacker instantiate or invoke
Use allowlist for class names (Primary Defense)
- Define explicit allowlist of permitted classes:
Map<String, Class<?>> ALLOWED_CLASSES = Map.of("user", UserHandler.class, "admin", AdminHandler.class) - Validate user input against allowlist:
Class<?> clazz = ALLOWED_CLASSES.get(userInput); if (clazz == null) throw IllegalArgumentException - Never use user input directly: Don't pass user input to
Class.forName(),getMethod(),eval()without validation - Use factory pattern instead: Map user input to predefined object instances, not reflection
- Why this works: Attacker can only instantiate pre-approved, safe classes
Avoid reflection from user input entirely
- Use factory pattern:
Map<String, Supplier<Handler>> handlers = Map.of("type1", Type1Handler::new); Handler h = handlers.get(userInput).get() - Use strategy pattern: Define interface, map user input to implementation, no reflection needed
- Use dependency injection: DI frameworks with predefined beans instead of dynamic class loading
- Avoid eval() and script engines: Never evaluate user-supplied code with
eval(),ScriptEngine
Validate fully qualified class names if reflection is unavoidable
- Validate package name:
if (!className.matches("^com\\.example\\.safe\\.[A-Za-z0-9]+$")) throw SecurityException - Verify inheritance:
if (!SafeInterface.class.isAssignableFrom(clazz)) throw SecurityException - Check class annotations: Verify class has @Safe annotation or similar marker
- Block dangerous classes: Reject Runtime, ProcessBuilder, FileWriter, URLClassLoader, ScriptEngine, Unsafe
Monitor and audit reflection usage
- Log all reflection operations (class loading, method invocation, script evaluation)
- Alert on reflection with untrusted input (attempts to load unauthorized classes)
- Track allowlist effectiveness (requests blocked vs allowed)
- Review code for new reflection usage in code reviews
- Use static analysis to detect unsafe reflection patterns
Test the reflection fix thoroughly
- Test with allowed class names (should work)
- Test with dangerous classes:
java.lang.ProcessBuilder,java.io.FileWriter,sun.misc.Unsafe(should be rejected) - Test with malformed class names:
../../../Evil,java.lang.Runtime(should be rejected) - Test package validation regex with bypass attempts
- Verify factory pattern works for all legitimate use cases
- Re-scan with security scanner to confirm the issue is resolved
Common Vulnerable Patterns
// Class name from user
String className = request.getParameter("handler");
Object handler = Class.forName(className).newInstance();
// Method invocation
String methodName = request.getParameter("action");
Method m = obj.getClass().getMethod(methodName);
m.invoke(obj);
// Script evaluation
String script = request.getParameter("code");
ScriptEngine engine = new ScriptEngineManager().getEngineByName("javascript");
engine.eval(script); // RCE!
Attack Examples
// RCE via ProcessBuilder
?class=java.lang.ProcessBuilder
// File operations
?class=java.io.FileWriter
// Reflection attacks
?class=sun.misc.Unsafe