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
| Impact | Details |
|---|---|
| Authentication | Scope: Bypass Overwriting auth variables grants unauthorized access. |
| Authorization | Scope: Privilege Escalation Modifying role/permission variables elevates privileges. |
| Integrity | Scope: 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
-
MITRE. "CWE-621: Variable Extraction Error." https://cwe.mitre.org/data/definitions/621.html
-
PHP Manual. extract() function documentation.