Variablenextraktions-Fehler

Beschreibung

Variablenextraktions-Fehler tritt auf, wenn Anwendungen Funktionen verwenden, die automatisch Variablen aus externen Eingaben erstellen, ohne ordnungsgemäße Validierung. In PHP können Funktionen wie extract() Variablen aus Arrays erstellen und dabei potenziell existierende Variablen überschreiben, einschließlich sicherheitskritischer. Ähnliche Probleme existieren mit import_request_variables() und register_globals. Dies ermöglicht Angreifern, beliebige Variablen in den Anwendungsbereich zu injizieren.

Risiko

Angreifer können Authentifizierungs-Flags ($is_authenticated = true), Benutzer-IDs ($user_id), Berechtigungsstufen ($is_admin) und andere sicherheitskritische Variablen überschreiben. Konfigurationsvariablen können modifiziert werden und das Anwendungsverhalten ändern. Der Kontrollfluss kann durch Überschreiben von Variablen, die in Bedingungen verwendet werden, gekapert werden. Dies gibt Angreifern effektiv Schreibzugriff auf den Variablen-Namensraum der Anwendung.

Lösung

Vermeiden Sie extract() bei nicht vertrauenswürdigen Daten oder verwenden Sie das EXTR_SKIP-Flag, um Überschreiben zu verhindern. Weisen Sie Variablen explizit zu, anstatt Massenextraktion. Wenn Extraktion notwendig ist, verwenden Sie Präfixe (EXTR_PREFIX_ALL). Validieren Sie erwartete Variablennamen mit Whitelist. Verwenden Sie moderne Frameworks, die nicht auf Variablenextraktion angewiesen sind. Halten Sie register_globals deaktiviert (Standard in PHP 5.4+).

Häufige Auswirkungen

AuswirkungDetails
AuthentifizierungBereich: Bypass

Überschreiben von Auth-Variablen gewährt unbefugten Zugriff.
AutorisierungBereich: Privilegien-Eskalation

Modifizieren von Rollen-/Berechtigungsvariablen erhöht Privilegien.
IntegritätBereich: Variablen-Injection

Beliebige Variablen können den Anwendungszustand korrumpieren.

Beispielcode + Lösungscode

Verwundbarer Code

<?php
// VERWUNDBAR: Direktes extract von $_GET oder $_POST
function processRequestVulnerable() {
    // Erstellt Variablen aus allen GET-Parametern
    extract($_GET);

    // Angreifer: ?is_admin=1&user_id=1
    // Jetzt $is_admin = "1" und $user_id = "1"!

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

// VERWUNDBAR: Extract überschreibt existierende Variablen
function authenticateVulnerable() {
    $authenticated = false;
    $user_role = 'guest';

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

    // $authenticated ist jetzt "1" (truthy)
    // $user_role ist jetzt "admin"

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

// VERWUNDBAR: Extract im Template-Rendering
function renderTemplateVulnerable($template, $data) {
    // Benutzerkontrolliertes $data überschreibt jede Variable
    extract($data);

    include $template;  // Template verwendet extrahierte Variablen
}

// Verwendung - Angreifer kontrolliert $data
$userData = $_POST['template_vars'];
renderTemplateVulnerable('profile.php', $userData);

// VERWUNDBAR: Konfigurations-Override
$config = [
    'debug' => false,
    'allow_uploads' => false,
    'max_upload_size' => 1024
];

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

if ($debug) {
    ini_set('display_errors', 1);  // Exponiert Fehler
}

// VERWUNDBAR: Extract von dekodiertem JSON
function processApiRequestVulnerable() {
    $json = file_get_contents('php://input');
    $data = json_decode($json, true);

    extract($data);  // Beliebige Variablen-Injection von API

    // Anwendungslogik mit extrahierten Variablen
}

// VERWUNDBAR: Ähnliches Problem mit parse_str
function parseUrlVulnerable($url) {
    $query = parse_url($url, PHP_URL_QUERY);
    parse_str($query);  // Erstellt Variablen im aktuellen Scope!

    // Angreifer kontrolliert $url
}
?>

Lösungscode

<?php
// SICHER: Array statt variable Variablen verwenden
function processRequestSafe() {
    // Nur erwartete Variablen explizit extrahieren
    $page = isset($_GET['page']) ? $_GET['page'] : 'home';
    $action = isset($_GET['action']) ? $_GET['action'] : 'view';

    // Sicherheitsvariablen kommen nie von Benutzereingabe
    $is_admin = checkAdminStatus();  // Von Session/Datenbank
}

// SICHER: EXTR_SKIP verwenden um Überschreiben zu verhindern
function processDataSafe($data) {
    $authenticated = false;
    $user_role = 'guest';

    // EXTR_SKIP überschreibt keine existierenden Variablen
    extract($data, EXTR_SKIP);

    // $authenticated und $user_role unverändert
}

// SICHER: Präfix für extrahierte Variablen verwenden
function templateWithPrefix($data) {
    // Alle extrahierten Variablen bekommen 'tpl_' Präfix
    extract($data, EXTR_PREFIX_ALL, 'tpl');

    // Verwende $tpl_name, $tpl_email, etc.
    // Kann $authenticated, $user_role, etc. nicht überschreiben
}

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

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

    extract($safeData);  // Nur whitelistete Variablen
}

// SICHER: Objekt/Array-Ansatz statt Variablen
function processRequestModern() {
    // Array/Objekt verwenden, nicht lose Variablen
    $params = [
        'page' => $_GET['page'] ?? 'home',
        'action' => $_GET['action'] ?? 'view'
    ];

    // Zugriff über $params['page'], nicht $page
    handleRequest($params);
}

// SICHER: Template-Rendering mit begrenzten Variablen
class SafeTemplateRenderer {
    private array $vars = [];

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

    public function render(string $template): string {
        // Nur unsere kontrollierten Variablen in isoliertem Scope extrahieren
        return (function($__template, $__vars) {
            extract($__vars);
            ob_start();
            include $__template;
            return ob_get_clean();
        })($template, $this->vars);
    }
}

// SICHER: parse_str mit explizitem Ziel-Array
function parseUrlSafe($url) {
    $query = parse_url($url, PHP_URL_QUERY);

    // In Array parsen, nicht in aktuellen Scope
    parse_str($query, $params);

    // Zugriff über $params['key'], nicht $key
    return $params;
}

// SICHER: Konfiguration mit Validierung
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) {
            // Unveränderliche und unbekannte Schlüssel überspringen
            if (in_array($key, $this->immutable)) {
                continue;
            }
            if (!array_key_exists($key, $this->defaults)) {
                continue;
            }
            // Typprüfung
            if (gettype($value) === gettype($this->defaults[$key])) {
                $config[$key] = $value;
            }
        }

        return $config;
    }
}

// SICHER: Moderner PHP-Ansatz
function handleApiRequestSafe(): void {
    $json = file_get_contents('php://input');
    $data = json_decode($json, true);

    // Struktur validieren
    if (!is_array($data)) {
        throw new InvalidArgumentException('Ungültiges JSON');
    }

    // DTO oder typisierte Extraktion verwenden
    $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 erforderlich');
        }
        if (!filter_var($this->email, FILTER_VALIDATE_EMAIL)) {
            throw new ValidationException('Ungültige E-Mail');
        }
    }
}
?>

Ausgenutzt in der Praxis

Authentifizierungs-Bypass

Anwendungen mit extract($_POST) hatten $authenticated überschrieben.

Admin-Zugriff

Variablen-Injection gab Angreifern Admin-Privilegien durch Setzen von $is_admin.

Remote Code Execution

In Kombination mit anderen Schwachstellen ermöglichte Variablen-Injection RCE.


Tools zum Testen/Ausnutzen

  • Manuelles Testen mit URL/POST-Parametern.
  • Statische Analyse-Tools, die extract()-Verwendung erkennen.
  • PHP_CodeSniffer mit Sicherheitsregeln.

CVE-Beispiele

  • CVEs von PHP-Anwendungen mit verwundbarem extract().
  • register_globals-bezogene Schwachstellen in Legacy-PHP.

Referenzen

  1. MITRE. "CWE-621: Variable Extraction Error." https://cwe.mitre.org/data/definitions/621.html
  2. PHP-Handbuch. extract()-Funktionsdokumentation.