Use of Externally-Controlled Input to Select Classes or Code (Unsafe Reflection)
This weakness occurs when an application uses user-supplied input to dynamically load or instantiate classes, invoke methods, or execute code via reflection…
This weakness occurs when an application uses user-supplied input to dynamically load or instantiate classes, invoke methods, or execute code via reflection without proper validation. An attacker can exploit this to load arbitrary classes or execute unintended code, potentially leading to remote code execution or unauthorized functionality.
02How It Happens
Reflection is a powerful programming feature that allows code to inspect and manipulate classes, methods, and objects at runtime. When an application accepts user input (from query parameters, form fields, API requests, or configuration files) and passes it directly to reflection functions without validation, an attacker can specify any class or method available in the application's classpath or namespace. The application then loads and executes that code without checking whether it was intended. This is especially dangerous when the attacker can control which class is instantiated or which method is called, or when the application uses reflection to deserialize untrusted data.
03Real-World Impact
Successful exploitation can lead to remote code execution if the attacker can instantiate a class with a dangerous constructor or invoke a method that performs sensitive operations. Even without direct RCE, an attacker might instantiate unintended classes to bypass authentication, access restricted functionality, manipulate application state, or trigger denial-of-service conditions. In Java applications, this has been the root cause of deserialization vulnerabilities and gadget-chain exploits.
04Vulnerable & Fixed Patterns
Vulnerable pattern
import importlib
# User input from a web request
user_class_name = request.args.get('class')
# Directly load and instantiate the class
module = importlib.import_module('myapp.handlers')
handler_class = getattr(module, user_class_name)
handler = handler_class()
handler.process()
Why it's vulnerable: The application loads any class name the user provides from the myapp.handlers module without checking whether it is an allowed handler. An attacker could request a different class that performs unintended operations.
Fixed pattern
import importlib
# Whitelist of allowed handler classes
ALLOWED_HANDLERS = {'UserHandler', 'AdminHandler', 'GuestHandler'}
user_class_name = request.args.get('class')
# Validate against whitelist before loading
if user_class_name not in ALLOWED_HANDLERS:
raise ValueError(f"Unknown handler: {user_class_name}")
module = importlib.import_module('myapp.handlers')
handler_class = getattr(module, user_class_name)
handler = handler_class()
handler.process()
Vulnerable pattern
<?php
// User input from a query parameter
$class_name = $_GET['handler'];
// Directly instantiate the class
$handler = new $class_name();
$handler->execute();
?>
Why it's vulnerable: PHP allows dynamic class instantiation using a variable. An attacker can pass any class name available in the application's namespace, potentially triggering unintended constructors or methods.
Fixed pattern
<?php
// Whitelist of allowed handler classes
$allowed_handlers = ['UserHandler', 'AdminHandler', 'GuestHandler'];
$class_name = $_GET['handler'];
// Validate against whitelist before instantiation
if (!in_array($class_name, $allowed_handlers, true)) {
throw new Exception("Unknown handler: " . htmlspecialchars($class_name));
}
$handler = new $class_name();
$handler->execute();
?>
05Prevention Checklist
Use allowlists, not blacklists. Maintain a strict whitelist of classes or methods that are safe to instantiate or invoke via reflection. Reject anything not on the list.
Never deserialize untrusted data. Avoid using native deserialization functions (e.g., pickle in Python, unserialize() in PHP) on user input. Use JSON or other safe formats instead.
Avoid reflection for user-controlled logic. If possible, use conditional logic (if/switch statements) or a factory pattern with a fixed set of options instead of dynamic class loading.
Validate input type and format. Ensure user input matches expected patterns (e.g., alphanumeric class names only) before passing it to reflection functions.
Run with least privilege. Limit the permissions of the application process so that even if reflection is exploited, the damage is contained.
Log and monitor reflection calls. Track which classes are being loaded dynamically and alert on unexpected patterns.
06Signs You May Already Be Affected
Look for unexpected classes being instantiated in application logs, unusual method calls in stack traces, or error messages referencing classes that should not be accessible to users. If you see serialized objects in user input (especially in cookies, POST bodies, or configuration files), check whether they are being deserialized without validation. Unexpected functionality or behavior changes after user input is processed may also indicate exploitation.