Variable Extraction Error

Description

Variable Extraction Error occurs when applications use functions that automatically create variables from external input without proper validation. In PHP, functions like extract() can create variables from arrays, potentially overwriting existing variables including security-critical ones. Similar issues exist with import_request_variables() and register_globals. This allows attackers to inject arbitrary variables into the application's scope.

Risk

Attackers can overwrite authentication flags ($is_authenticated = true), user IDs ($user_id), permission levels ($is_admin), and other security-critical variables. Configuration variables can be modified, changing application behavior. Control flow can be hijacked by overwriting variables used in conditionals. This effectively gives attackers write access to the application's variable namespace.

Solution

Avoid extract() on untrusted data or use the EXTR_SKIP flag to prevent overwriting. Explicitly assign variables instead of bulk extraction. If extraction is necessary, use prefixes (EXTR_PREFIX_ALL). Validate and whitelist expected variable names. Use modern frameworks that don't rely on variable extraction. Keep register_globals disabled (default in PHP 5.4+).

Common Consequences

ImpactDetails
AuthenticationScope: Bypass

Overwriting auth variables grants unauthorized access.
AuthorizationScope: Privilege Escalation

Modifying role/permission variables elevates privileges.
IntegrityScope: Variable Injection

Arbitrary variables can corrupt application state.

Example Code + Solution Code

Vulnerable Code

<?php
// VULNERABLE: Direct extract of $_GET or $_POST
function processRequestVulnerable() {
    // Creates variables from all GET parameters
    extract($_GET);

    // Attacker: ?is_admin=1&user_id=1
    // Now $is_admin = "1" and $user_id = "1"!

    if ($is_admin) {
        showAdminPanel();
    }
}

// VULNERABLE: Extract overwrites existing variables
function authenticateVulnerable() {
    $authenticated = false;
    $user_role = 'guest';

    // Attacker: ?authenticated=1&user_role=admin
    extract($_REQUEST);

    // $authenticated is now "1" (truthy)
    // $user_role is now "admin"

    if ($authenticated) {
        if ($user_role === 'admin') {
            performAdminAction();
        }
    }
}

// VULNERABLE: Extract in template rendering
function renderTemplateVulnerable($template, $data) {
    // User-controlled $data overwrites any variable
    extract($data);

    include $template;  // Template uses extracted variables
}

// Usage - attacker controls $data
$userData = $_POST['template_vars'];
renderTemplateVulnerable('profile.php', $userData);

// VULNERABLE: Configuration override
$config = [
    'debug' => false,
    'allow_uploads' => false,
    'max_upload_size' => 1024
];

extract($_GET);  // Attacker: ?debug=1&allow_uploads=1

if ($debug) {
    ini_set('display_errors', 1);  // Exposes errors
}

// VULNERABLE: Extract from decoded JSON
function processApiRequestVulnerable() {
    $json = file_get_contents('php://input');
    $data = json_decode($json, true);

    extract($data);  // Arbitrary variable injection from API

    // Application logic using extracted variables
}

// VULNERABLE: Similar issue with parse_str
function parseUrlVulnerable($url) {
    $query = parse_url($url, PHP_URL_QUERY);
    parse_str($query);  // Creates variables in current scope!

    // Attacker controls $url
}
?>

Fixed Code

<?php
// SAFE: Explicit variable assignment
function processRequestSafe() {
    // Explicitly extract only expected variables
    $page = isset($_GET['page']) ? $_GET['page'] : 'home';
    $action = isset($_GET['action']) ? $_GET['action'] : 'view';

    // Security variables never come from user input
    $is_admin = checkAdminStatus();  // From session/database
}

// SAFE: Use EXTR_SKIP to prevent overwriting
function processDataSafe($data) {
    $authenticated = false;
    $user_role = 'guest';

    // EXTR_SKIP won't overwrite existing variables
    extract($data, EXTR_SKIP);

    // $authenticated and $user_role unchanged
}

// SAFE: Use prefix for extracted variables
function templateWithPrefix($data) {
    // All extracted variables get 'tpl_' prefix
    extract($data, EXTR_PREFIX_ALL, 'tpl');

    // Use $tpl_name, $tpl_email, etc.
    // Cannot overwrite $authenticated, $user_role, etc.
}

// SAFE: Whitelist approach
function extractWhitelistSafe($input) {
    $allowed = ['name', 'email', 'message'];
    $safeData = [];

    foreach ($allowed as $key) {
        if (isset($input[$key])) {
            $safeData[$key] = $input[$key];
        }
    }

    extract($safeData);  // Only whitelisted variables
}

// SAFE: Object/Array approach instead of variables
function processRequestModern() {
    // Use array/object, not loose variables
    $params = [
        'page' => $_GET['page'] ?? 'home',
        'action' => $_GET['action'] ?? 'view'
    ];

    // Access via $params['page'], not $page
    handleRequest($params);
}

// SAFE: Template rendering with scoped variables
class SafeTemplateRenderer {
    private array $vars = [];

    public function assign(string $name, $value): void {
        // Whitelist allowed variable names
        $allowed = ['title', 'content', 'user_name'];
        if (in_array($name, $allowed)) {
            $this->vars[$name] = $value;
        }
    }

    public function render(string $template): string {
        // Extract only our controlled variables in isolated scope
        return (function($__template, $__vars) {
            extract($__vars);
            ob_start();
            include $__template;
            return ob_get_clean();
        })($template, $this->vars);
    }
}

// SAFE: parse_str with explicit target array
function parseUrlSafe($url) {
    $query = parse_url($url, PHP_URL_QUERY);

    // Parse into array, not current scope
    parse_str($query, $params);

    // Access via $params['key'], not $key
    return $params;
}

// SAFE: Configuration with validation
class Config {
    private array $defaults = [
        'debug' => false,
        'allow_uploads' => false,
        'max_upload_size' => 1024
    ];

    private array $immutable = ['debug', 'allow_uploads'];

    public function load(array $overrides): array {
        $config = $this->defaults;

        foreach ($overrides as $key => $value) {
            // Skip immutable and unknown keys
            if (in_array($key, $this->immutable)) {
                continue;
            }
            if (!array_key_exists($key, $this->defaults)) {
                continue;
            }
            // Type checking
            if (gettype($value) === gettype($this->defaults[$key])) {
                $config[$key] = $value;
            }
        }

        return $config;
    }
}

// SAFE: Modern PHP approach
function handleApiRequestSafe(): void {
    $json = file_get_contents('php://input');
    $data = json_decode($json, true);

    // Validate structure
    if (!is_array($data)) {
        throw new InvalidArgumentException('Invalid JSON');
    }

    // Use DTO or typed extraction
    $request = new ApiRequest(
        name: $data['name'] ?? '',
        email: $data['email'] ?? '',
        action: $data['action'] ?? 'default'
    );

    processRequest($request);
}

class ApiRequest {
    public function __construct(
        public readonly string $name,
        public readonly string $email,
        public readonly string $action
    ) {
        $this->validate();
    }

    private function validate(): void {
        if (empty($this->name)) {
            throw new ValidationException('Name required');
        }
        if (!filter_var($this->email, FILTER_VALIDATE_EMAIL)) {
            throw new ValidationException('Invalid email');
        }
    }
}
?>

Exploited in the Wild

Authentication Bypass

Applications using extract($_POST) had $authenticated overwritten.

Admin Access

Variable injection gave attackers admin privileges by setting $is_admin.

Remote Code Execution

Combined with other vulnerabilities, variable injection enabled RCE.


Tools to test/exploit

  • Manual testing with URL/POST parameters.

  • Static analysis tools detecting extract() usage.

  • PHP_CodeSniffer with security rules.


CVE Examples

  • CVEs from PHP applications using vulnerable extract().

  • register_globals-related vulnerabilities in legacy PHP.


References

  1. MITRE. "CWE-621: Variable Extraction Error." https://cwe.mitre.org/data/definitions/621.html

  2. PHP Manual. extract() function documentation.