Dynamic Variable Evaluation
Description
Dynamic Variable Evaluation occurs when applications use variable names derived from user input to access or modify variables dynamically. This includes PHP's variable variables ($$var), Python's eval()/exec(), JavaScript's eval(), and similar constructs. Attackers can manipulate variable names to access or overwrite security-critical variables, leading to authentication bypass, privilege escalation, or code execution.
Risk
Attackers can read or modify arbitrary variables in the application's scope. Security-critical variables like authentication flags can be overwritten. Configuration variables can be manipulated. In worst cases, this leads to arbitrary code execution. The vulnerability allows indirect control over application state through variable name manipulation.
Solution
Avoid dynamic variable evaluation with user input. Use arrays or objects with validated keys instead. Whitelist allowed variable names. Use explicit mappings between user input and internal names. Never use eval() or similar constructs on user input. Implement strict input validation on any values used in variable access.
Common Consequences
| Impact | Details |
|---|---|
| Integrity | Scope: Variable Manipulation Arbitrary variables can be read or modified. |
| Authentication | Scope: Bypass Auth variables can be overwritten. |
| Confidentiality | Scope: Information Disclosure Sensitive variable values can be extracted. |
Example Code + Solution Code
Vulnerable Code
<?php
// VULNERABLE: PHP variable variables
function processFormVulnerable($formData) {
// Creates variables from form input
foreach ($formData as $key => $value) {
$$key = $value; // Variable variable!
}
// Attacker: formData['is_admin'] = '1'
// Now $is_admin = '1'
if (isset($is_admin) && $is_admin) {
showAdminPanel();
}
}
// VULNERABLE: Dynamic property access
class UserVulnerable {
public $name;
public $email;
public $role = 'user';
public function setProperty($prop, $value) {
// Attacker can set any property
$this->$prop = $value;
}
}
$user = new UserVulnerable();
$user->setProperty($_GET['prop'], $_GET['value']);
// Attack: ?prop=role&value=admin
// VULNERABLE: Indirect variable reference
function getConfigVulnerable($key) {
global $$key; // Allows access to any global
// Attack: key=_SESSION
// Returns $_SESSION contents!
return $$key;
}
// VULNERABLE: Dynamic function calls
function callFunctionVulnerable($funcName, $args) {
// Calls any function!
return $funcName(...$args);
}
// Attack: funcName=system, args=['rm -rf /']
?>
# VULNERABLE: Using eval for dynamic variable access
user_data = {}
def set_variable_vulnerable(var_name, value):
# Uses eval to set variables
exec(f"{var_name} = {repr(value)}")
# Attack: var_name="__import__('os').system('whoami')"
# VULNERABLE: getattr/setattr without validation
class ConfigVulnerable:
debug = False
secret_key = "supersecret"
def update(self, key, value):
# Sets any attribute
setattr(self, key, value)
config = ConfigVulnerable()
config.update(request.args['key'], request.args['value'])
# Attack: ?key=debug&value=True
# VULNERABLE: globals()/locals() manipulation
def update_config_vulnerable(updates):
for key, value in updates.items():
globals()[key] = value # Overwrites any global!
# Attack: updates={'is_authenticated': True}
# VULNERABLE: Dynamic import
def load_module_vulnerable(module_name):
# Loads any module
return __import__(module_name)
# Attack: module_name='os' then call system()
// VULNERABLE: eval() for variable access
function getValueVulnerable(varName) {
// Evaluates any expression
return eval(varName);
}
// Attack: varName = "process.env.SECRET_KEY"
// Attack: varName = "require('child_process').execSync('whoami')"
// VULNERABLE: Bracket notation with user input
const config = {
theme: 'light',
language: 'en',
isAdmin: false
};
function updateConfig(key, value) {
// User controls key
config[key] = value;
}
// Attack: updateConfig('isAdmin', true)
// VULNERABLE: Dynamic property access on global
function accessGlobal(name) {
return global[name]; // Or window[name] in browser
}
// Attack: name = 'process' -> gives access to process object
// VULNERABLE: new Function() constructor
function createCalculator(formula) {
// Creates executable code from user input
const calc = new Function('a', 'b', `return ${formula}`);
return calc;
}
// Attack: formula = "a; require('child_process').execSync('whoami'); b"
Fixed Code
<?php
// SAFE: Use array instead of variable variables
function processFormSafe($formData) {
// Use validated array
$allowed = ['name', 'email', 'message'];
$safe = [];
foreach ($allowed as $key) {
if (isset($formData[$key])) {
$safe[$key] = $formData[$key];
}
}
// Security variables from trusted source
$is_admin = checkAdminFromSession();
return $safe;
}
// SAFE: Property whitelist
class UserSafe {
private $name;
private $email;
private $role = 'user';
private $settableProperties = ['name', 'email'];
public function setProperty($prop, $value) {
// Only allow specific properties
if (!in_array($prop, $this->settableProperties)) {
throw new InvalidArgumentException("Cannot set: $prop");
}
$this->$prop = $value;
}
public function setName($name) { $this->name = $name; }
public function setEmail($email) { $this->email = $email; }
// No setter for role!
}
// SAFE: Mapping instead of dynamic access
function getConfigSafe($key) {
$configMap = [
'app_name' => $GLOBALS['config']['app_name'],
'version' => $GLOBALS['config']['version'],
'theme' => $GLOBALS['config']['theme']
];
if (!array_key_exists($key, $configMap)) {
throw new InvalidArgumentException("Unknown config: $key");
}
return $configMap[$key];
}
// SAFE: Function whitelist
function callFunctionSafe($funcName, $args) {
$allowed = ['strlen', 'strtoupper', 'strtolower', 'trim'];
if (!in_array($funcName, $allowed)) {
throw new InvalidArgumentException("Function not allowed: $funcName");
}
// Validate argument types
foreach ($args as $arg) {
if (!is_string($arg)) {
throw new InvalidArgumentException("Invalid argument type");
}
}
return $funcName(...$args);
}
?>
# SAFE: Dictionary access with validation
from typing import Any, Dict
class ConfigSafe:
def __init__(self):
self._data = {
'theme': 'light',
'language': 'en'
}
self._settable = {'theme', 'language'}
self._readonly = {'secret_key', 'debug'}
def get(self, key: str) -> Any:
if key in self._readonly:
raise PermissionError(f"Cannot access: {key}")
return self._data.get(key)
def set(self, key: str, value: Any) -> None:
if key not in self._settable:
raise PermissionError(f"Cannot set: {key}")
self._data[key] = value
# SAFE: Explicit mapping
def update_config_safe(updates: Dict[str, Any]) -> None:
allowed_updates = {
'theme': lambda v: v in ('light', 'dark'),
'language': lambda v: v in ('en', 'de', 'fr'),
'page_size': lambda v: isinstance(v, int) and 1 <= v <= 100
}
for key, value in updates.items():
if key not in allowed_updates:
raise ValueError(f"Unknown setting: {key}")
if not allowed_updates[key](value):
raise ValueError(f"Invalid value for {key}")
# Update via explicit method
CONFIG[key] = value
# SAFE: Module whitelist
ALLOWED_MODULES = {'json', 'datetime', 'math'}
def load_module_safe(module_name: str):
if module_name not in ALLOWED_MODULES:
raise ImportError(f"Module not allowed: {module_name}")
return __import__(module_name)
# SAFE: Using getattr with whitelist
class SafeObject:
name = "test"
value = 42
_secret = "hidden"
PUBLIC_ATTRS = {'name', 'value'}
def get_attr(self, attr_name: str):
if attr_name not in self.PUBLIC_ATTRS:
raise AttributeError(f"Cannot access: {attr_name}")
return getattr(self, attr_name)
// SAFE: Map-based access
const config = new Map([
['theme', 'light'],
['language', 'en']
]);
const settableKeys = new Set(['theme', 'language']);
const readonlyKeys = new Set(['isAdmin', 'secretKey']);
function getConfig(key) {
if (readonlyKeys.has(key)) {
throw new Error(`Cannot access: ${key}`);
}
return config.get(key);
}
function setConfig(key, value) {
if (!settableKeys.has(key)) {
throw new Error(`Cannot set: ${key}`);
}
config.set(key, value);
}
// SAFE: Property access with validation
const safeObject = {
name: 'app',
version: '1.0',
allowedProps: ['name', 'version'],
get(prop) {
if (!this.allowedProps.includes(prop)) {
throw new Error(`Invalid property: ${prop}`);
}
return this[prop];
}
};
// SAFE: No eval - use explicit operations
const operations = {
add: (a, b) => a + b,
subtract: (a, b) => a - b,
multiply: (a, b) => a * b,
divide: (a, b) => b !== 0 ? a / b : NaN
};
function calculate(operation, a, b) {
if (!operations.hasOwnProperty(operation)) {
throw new Error(`Unknown operation: ${operation}`);
}
return operations[operation](a, b);
}
// SAFE: Schema validation for object updates
const Joi = require('joi');
const configSchema = Joi.object({
theme: Joi.string().valid('light', 'dark'),
language: Joi.string().valid('en', 'de', 'fr'),
pageSize: Joi.number().integer().min(1).max(100)
});
function updateConfigSafe(updates) {
const { error, value } = configSchema.validate(updates);
if (error) {
throw new Error(`Invalid config: ${error.message}`);
}
Object.assign(config, value);
}
Exploited in the Wild
PHP Applications
Variable variables exploited to overwrite authentication flags.
Python Flask/Django
setattr() misuse allowed privilege escalation.
Node.js
eval() in configuration handlers led to RCE.
Tools to test/exploit
-
Static analysis tools for eval()/exec() detection.
-
Manual testing with variable name manipulation.
-
Fuzzing parameter names and values.
CVE Examples
-
CVEs from dynamic evaluation vulnerabilities in web applications.
-
Python/PHP CMS systems with variable manipulation issues.
References
-
MITRE. "CWE-627: Dynamic Variable Evaluation." https://cwe.mitre.org/data/definitions/627.html
-
PHP Manual. Variable variables documentation.