Dynamische Variablenevaluation

Beschreibung

Dynamische Variablenevaluation tritt auf, wenn Anwendungen Variablennamen verwenden, die von Benutzereingaben abgeleitet sind, um dynamisch auf Variablen zuzugreifen oder sie zu modifizieren. Dies umfasst PHPs variable Variablen ($$var), Pythons eval()/exec(), JavaScripts eval() und ähnliche Konstrukte. Angreifer können Variablennamen manipulieren, um auf sicherheitskritische Variablen zuzugreifen oder diese zu überschreiben, was zu Authentifizierungs-Bypass, Privilegien-Eskalation oder Code-Ausführung führt.

Risiko

Angreifer können beliebige Variablen im Anwendungsbereich lesen oder modifizieren. Sicherheitskritische Variablen wie Authentifizierungs-Flags können überschrieben werden. Konfigurationsvariablen können manipuliert werden. Im schlimmsten Fall führt dies zu beliebiger Code-Ausführung. Die Schwachstelle ermöglicht indirekte Kontrolle über den Anwendungszustand durch Variablennamen-Manipulation.

Lösung

Vermeiden Sie dynamische Variablenevaluation mit Benutzereingaben. Verwenden Sie Arrays oder Objekte mit validierten Schlüsseln stattdessen. Whitelisten Sie erlaubte Variablennamen. Verwenden Sie explizite Mappings zwischen Benutzereingabe und internen Namen. Verwenden Sie niemals eval() oder ähnliche Konstrukte auf Benutzereingaben. Implementieren Sie strikte Eingabevalidierung bei allen Werten, die im Variablenzugriff verwendet werden.

Häufige Auswirkungen

AuswirkungDetails
IntegritätBereich: Variablenmanipulation

Beliebige Variablen können gelesen oder modifiziert werden.
AuthentifizierungBereich: Bypass

Auth-Variablen können überschrieben werden.
VertraulichkeitBereich: Informationsoffenlegung

Sensible Variablenwerte können extrahiert werden.

Beispielcode + Lösungscode

Verwundbarer Code

<?php
// VERWUNDBAR: PHP variable Variablen
function processFormVulnerable($formData) {
    // Erstellt Variablen aus Formulareingabe
    foreach ($formData as $key => $value) {
        $key = $value;  // Variable Variable!
    }

    // Angreifer: formData['is_admin'] = '1'
    // Jetzt $is_admin = '1'

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

// VERWUNDBAR: Dynamischer Property-Zugriff
class UserVulnerable {
    public $name;
    public $email;
    public $role = 'user';

    public function setProperty($prop, $value) {
        // Angreifer kann jede Property setzen
        $this->$prop = $value;
    }
}

$user = new UserVulnerable();
$user->setProperty($_GET['prop'], $_GET['value']);
// Angriff: ?prop=role&value=admin

// VERWUNDBAR: Indirekte Variablenreferenz
function getConfigVulnerable($key) {
    global $key;  // Erlaubt Zugriff auf jedes Global

    // Angriff: key=_SESSION
    // Gibt $_SESSION-Inhalt zurück!
    return $key;
}

// VERWUNDBAR: Dynamische Funktionsaufrufe
function callFunctionVulnerable($funcName, $args) {
    // Ruft jede Funktion auf!
    return $funcName(...$args);
}

// Angriff: funcName=system, args=['rm -rf /']
?>
# VERWUNDBAR: eval für dynamischen Variablenzugriff verwenden
user_data = {}

def set_variable_vulnerable(var_name, value):
    # Verwendet eval um Variablen zu setzen
    exec(f"{var_name} = {repr(value)}")

    # Angriff: var_name="__import__('os').system('whoami')"

# VERWUNDBAR: getattr/setattr ohne Validierung
class ConfigVulnerable:
    debug = False
    secret_key = "supersecret"

    def update(self, key, value):
        # Setzt jedes Attribut
        setattr(self, key, value)

config = ConfigVulnerable()
config.update(request.args['key'], request.args['value'])
# Angriff: ?key=debug&value=True

# VERWUNDBAR: globals()/locals() Manipulation
def update_config_vulnerable(updates):
    for key, value in updates.items():
        globals()[key] = value  # Überschreibt jedes Global!

# Angriff: updates={'is_authenticated': True}

# VERWUNDBAR: Dynamischer Import
def load_module_vulnerable(module_name):
    # Lädt jedes Modul
    return __import__(module_name)

# Angriff: module_name='os' dann system() aufrufen
// VERWUNDBAR: eval() für Variablenzugriff
function getValueVulnerable(varName) {
    // Evaluiert jeden Ausdruck
    return eval(varName);
}

// Angriff: varName = "process.env.SECRET_KEY"
// Angriff: varName = "require('child_process').execSync('whoami')"

// VERWUNDBAR: Klammer-Notation mit Benutzereingabe
const config = {
    theme: 'light',
    language: 'en',
    isAdmin: false
};

function updateConfig(key, value) {
    // Benutzer kontrolliert key
    config[key] = value;
}

// Angriff: updateConfig('isAdmin', true)

// VERWUNDBAR: Dynamischer Property-Zugriff auf global
function accessGlobal(name) {
    return global[name];  // Oder window[name] im Browser
}

// Angriff: name = 'process' -> gibt Zugriff auf process-Objekt

// VERWUNDBAR: new Function() Konstruktor
function createCalculator(formula) {
    // Erstellt ausführbaren Code aus Benutzereingabe
    const calc = new Function('a', 'b', `return ${formula}`);
    return calc;
}

// Angriff: formula = "a; require('child_process').execSync('whoami'); b"

Lösungscode

<?php
// SICHER: Array statt variable Variablen verwenden
function processFormSafe($formData) {
    // Validiertes Array verwenden
    $allowed = ['name', 'email', 'message'];
    $safe = [];

    foreach ($allowed as $key) {
        if (isset($formData[$key])) {
            $safe[$key] = $formData[$key];
        }
    }

    // Sicherheitsvariablen von vertrauenswürdiger Quelle
    $is_admin = checkAdminFromSession();

    return $safe;
}

// SICHER: Property-Whitelist
class UserSafe {
    private $name;
    private $email;
    private $role = 'user';

    private $settableProperties = ['name', 'email'];

    public function setProperty($prop, $value) {
        // Nur spezifische Properties erlauben
        if (!in_array($prop, $this->settableProperties)) {
            throw new InvalidArgumentException("Kann nicht setzen: $prop");
        }

        $this->$prop = $value;
    }

    public function setName($name) { $this->name = $name; }
    public function setEmail($email) { $this->email = $email; }
    // Kein Setter für role!
}

// SICHER: Mapping statt dynamischer Zugriff
function getConfigSafe($key) {
    $configMap = [
        'app_name' => $GLOBALS['config']['app_name'],
        'version' => $GLOBALS['config']['version'],
        'theme' => $GLOBALS['config']['theme']
    ];

    if (!array_key_exists($key, $configMap)) {
        throw new InvalidArgumentException("Unbekannte Config: $key");
    }

    return $configMap[$key];
}

// SICHER: Funktions-Whitelist
function callFunctionSafe($funcName, $args) {
    $allowed = ['strlen', 'strtoupper', 'strtolower', 'trim'];

    if (!in_array($funcName, $allowed)) {
        throw new InvalidArgumentException("Funktion nicht erlaubt: $funcName");
    }

    // Argumenttypen validieren
    foreach ($args as $arg) {
        if (!is_string($arg)) {
            throw new InvalidArgumentException("Ungültiger Argumenttyp");
        }
    }

    return $funcName(...$args);
}
?>
# SICHER: Dictionary-Zugriff mit Validierung
from typing import Any, Dict

class ConfigSafe:
    def __init__(self):
        self._data = {
            'theme': 'light',
            'language': 'en'
        }
        self._settable = {'theme', 'language'}
        self._readonly = {'secret_key', 'debug'}

    def get(self, key: str) -> Any:
        if key in self._readonly:
            raise PermissionError(f"Kann nicht zugreifen: {key}")
        return self._data.get(key)

    def set(self, key: str, value: Any) -> None:
        if key not in self._settable:
            raise PermissionError(f"Kann nicht setzen: {key}")
        self._data[key] = value

# SICHER: Explizites Mapping
def update_config_safe(updates: Dict[str, Any]) -> None:
    allowed_updates = {
        'theme': lambda v: v in ('light', 'dark'),
        'language': lambda v: v in ('en', 'de', 'fr'),
        'page_size': lambda v: isinstance(v, int) and 1 <= v <= 100
    }

    for key, value in updates.items():
        if key not in allowed_updates:
            raise ValueError(f"Unbekannte Einstellung: {key}")
        if not allowed_updates[key](value):
            raise ValueError(f"Ungültiger Wert für {key}")

        # Über explizite Methode aktualisieren
        CONFIG[key] = value

# SICHER: Modul-Whitelist
ALLOWED_MODULES = {'json', 'datetime', 'math'}

def load_module_safe(module_name: str):
    if module_name not in ALLOWED_MODULES:
        raise ImportError(f"Modul nicht erlaubt: {module_name}")
    return __import__(module_name)

# SICHER: getattr mit Whitelist verwenden
class SafeObject:
    name = "test"
    value = 42
    _secret = "hidden"

    PUBLIC_ATTRS = {'name', 'value'}

    def get_attr(self, attr_name: str):
        if attr_name not in self.PUBLIC_ATTRS:
            raise AttributeError(f"Kann nicht zugreifen: {attr_name}")
        return getattr(self, attr_name)
// SICHER: Map-basierter Zugriff
const config = new Map([
    ['theme', 'light'],
    ['language', 'en']
]);

const settableKeys = new Set(['theme', 'language']);
const readonlyKeys = new Set(['isAdmin', 'secretKey']);

function getConfig(key) {
    if (readonlyKeys.has(key)) {
        throw new Error(`Kann nicht zugreifen: ${key}`);
    }
    return config.get(key);
}

function setConfig(key, value) {
    if (!settableKeys.has(key)) {
        throw new Error(`Kann nicht setzen: ${key}`);
    }
    config.set(key, value);
}

// SICHER: Property-Zugriff mit Validierung
const safeObject = {
    name: 'app',
    version: '1.0',

    allowedProps: ['name', 'version'],

    get(prop) {
        if (!this.allowedProps.includes(prop)) {
            throw new Error(`Ungültige Property: ${prop}`);
        }
        return this[prop];
    }
};

// SICHER: Kein eval - explizite Operationen verwenden
const operations = {
    add: (a, b) => a + b,
    subtract: (a, b) => a - b,
    multiply: (a, b) => a * b,
    divide: (a, b) => b !== 0 ? a / b : NaN
};

function calculate(operation, a, b) {
    if (!operations.hasOwnProperty(operation)) {
        throw new Error(`Unbekannte Operation: ${operation}`);
    }
    return operations[operation](a, b);
}

// SICHER: Schema-Validierung für Objekt-Updates
const Joi = require('joi');

const configSchema = Joi.object({
    theme: Joi.string().valid('light', 'dark'),
    language: Joi.string().valid('en', 'de', 'fr'),
    pageSize: Joi.number().integer().min(1).max(100)
});

function updateConfigSafe(updates) {
    const { error, value } = configSchema.validate(updates);
    if (error) {
        throw new Error(`Ungültige Config: ${error.message}`);
    }

    Object.assign(config, value);
}

Ausgenutzt in der Praxis

PHP-Anwendungen

Variable Variablen ausgenutzt um Authentifizierungs-Flags zu überschreiben.

Python Flask/Django

setattr()-Missbrauch ermöglichte Privilegien-Eskalation.

Node.js

eval() in Konfigurations-Handlern führte zu RCE.


Tools zum Testen/Ausnutzen

  • Statische Analyse-Tools für eval()/exec()-Erkennung.
  • Manuelles Testen mit Variablennamen-Manipulation.
  • Fuzzing von Parameternamen und -werten.

CVE-Beispiele

  • CVEs von dynamischen Evaluations-Schwachstellen in Webanwendungen.
  • Python/PHP-CMS-Systeme mit Variablenmanipulations-Problemen.

Referenzen

  1. MITRE. "CWE-627: Dynamic Variable Evaluation." https://cwe.mitre.org/data/definitions/627.html
  2. PHP-Handbuch. Variable Variablen-Dokumentation.