10.2 Lösung mit UserInput 

Wie Sie in den Abschnitten 10.1.2, »Betrachtung des Webbereichs«, und 10.1.3, »PHP-Sicherheit«, gesehen haben, sind die Anforderungen an die Validierung und Entwertung von Benutzereingaben vielfältig und äußerst kritisch. Nur wenn sämtliche vom Benutzer empfangenen Daten korrekt behandelt werden, ist es möglich, die Sicherheit einer PHP-Applikation überhaupt zu gewährleisten. Obwohl hierbei natürlich noch weitere Aspekte eine Rolle spielen, wollen wir hierauf aber in diesem Buch nicht eingehen.
Die UserInput-Komponente bietet Ihnen zu diesem Zweck eine objektorientierte API zur in PHP (seit Version 5.1) integrierten Erweiterung ext/filter. Die Filter-Extension ist der aktuellste Versuch der PHP-Entwickler, einen standardisierten Mechanismus zur Absicherung von Benutzereingaben bereitzustellen, nachdem frühere Ansätze, wie zum Beispiel Magic-Quotes [http://php.net/magic_quotes ] , offiziell als gescheitert angesehen werden. Mit UserInput als Aufsatz für diesen in PHP eingebauten Mechanismus wird es Ihnen ermöglicht, die von einem Formular erwarteten Variablen zu spezifizieren, ihnen Validierungsregeln zuzuordnen und auf gesichertem Weg auf ihre Werte zuzugreifen.
| UserInput und PHP 5.1.1 |
|
eZ Components werden unter der Prämisse bereitgestellt, dass das Komplettpaket ab PHP-Version 5.1.1 funktioniert. Da die UserInput-Komponente aber auf der ext/filter-Erweiterung von PHP basiert und diese Erweiterung einigen Turbulenzen in der Vergangenheit unterlag, empfehlen wir, die aktuellste Version von UserInput ab PHP-Version 5.2 einzusetzen. |
Die Architektur von UserInput sieht vor, dass zunächst definiert wird, welche Variablen erwartet werden, aus welcher Quelle diese stammen müssen und wie sie zu validieren und zu entwerten sind. Anschließend wird mit Hilfe dieser Definition ein Formularobjekt erzeugt, über das gesichert auf die Werte zugegriffen werden kann.
10.2.1 Formulare definieren 

Zunächst muss bei der Benutzung der UserInput-Komponente definiert werden, welche Variablen erwartet werden und wie diese zu validieren beziehungsweise zu entwerten sind. Zur Illustration sehen Sie im Folgenden die Definition des Formulars zur Abgabe eines Kommentars im GP-Blog:
private static function getFormDefinition()
{
$definition = array(
'E-Mail' => new ezcInputFormDefinitionElement(
ezcInputFormDefinitionElement::REQUIRED,
'validate_email'
),
'name' => new ezcInputFormDefinitionElement(
ezcInputFormDefinitionElement::REQUIRED,
'string'
),
'body' => new ezcInputFormDefinitionElement(
ezcInputFormDefinitionElement::REQUIRED,
'string'
),
'entryId' => new ezcInputFormDefinitionElement(
ezcInputFormDefinitionElement::REQUIRED,
'number_int'
),
);
return $definition;
}Listing 10.1 Die Felder des Kommentarformulars definieren
Der Übersichtlichkeit halber ist die Definition in eine eigene Methode namens getFormDefinition() ausgelagert, die aus der eigentlichen Aktion heraus aufgerufen wird. Die Definition der Formularvariablen besteht aus einem assoziativen Array, in dem die Schlüssel mit den Namen der erwarteten Variable korrespondieren. Jedem Schlüssel ist eine Instanz der Klasse ezcInputFormDefinitionElement zugeordnet, welche die Validierungs- und Entwertungsregeln für diese Variable definiert.
Abbildung 10.1 Das zu verarbeitende Formular
Der erste Parameter zum Konstruktor von ezcInputFormDefinitionElement gibt an, ob die Variable immer übergeben werden muss oder ob der Wert optional ist. Der Konstantenwert REQUIRED bedeutet allerdings nicht, dass das entsprechende Formularfeld ausgefüllt werden muss, sondern lediglich, dass die Variable in der Übermittlung erwartet wird. Auch wenn der Benutzer ein Feld nicht ausfüllt, erhält PHP beim Absenden des Formulars einen leeren String, welcher ebenfalls die Erfüllung der REQUIRED-Regel bedeutet. OPTIONAL hingegen verlangt nicht, dass eine Variable übergeben wird, was nützlich sein kann, falls die Verarbeitungsroutine für mehrere HTML-Formulare verwendet wird, bei denen einige Felder verschieden sind oder Checkboxen bzw. Radio-Buttons verwendet werden.
Der zweite Parameter zur Definition einer Formularvariable bezeichnet den Filter selbst. Im gezeigten Beispiel wurden drei verschiedene Filter verwendet: Der allgemeinste Filter string dient der Entwertung von Textwerten. In diesem Fall wird er verwendet, um den Namen des Autors sowie den Inhalt des Kommentars zu entwerten. Beides sind typische Beispiele für die Verwendung des string-Filters, da beide Werte keinem bestimmten Format folgen und beliebigen Text enthalten dürfen. Allerdings ist es wünschenswert, dass potentiell gefährliche Inhalte, wie zum HTML-Tags, daraus entfernt werden. Genau dies übernimmt der string-Filter, ähnlich der PHP-Funktion strip_tags(). [http://php.net/strip_tags ] Da der Filter eine Reinigung der Daten vornimmt, gehört er in die Kategorie der sogenannten Sanitizing Filters, also der Entwertungsfilter. Über mehrere Flags lässt sich die Arbeitsweise des string-Filters beeinflussen. Mehr dazu aber später.
Ein weiterer Filter, der hier verwendet wurde, trägt den Namen int und dient dazu, eine Ganzzahl zu validieren. Da dieser Filter keine Entwertung der Daten vornimmt, sondern lediglich prüft, ob es sich bei den übergebenen Daten um eine Ganzzahl handelt, gehört er in die Kategorie der logischen Filter (Logical Filters). Werden andere Zeichen als in einer Ganzzahl erlaubt übergeben, schlägt die Validierung fehl, was später in diesem Abschnitt thematisiert wird. Handelt es sich bei den übertragenen Daten allerdings um eine valide Ganzzahl, übernimmt der Filter auch die entsprechende Konvertierung in den PHP-Datentyp int. Alternativ zu diesem Filter können Sie ebenfalls number_int verwenden, welcher in die Kategorie der Entwertungsfilter gehört. Bei diesem Filter werden sämtliche Zeichen bis auf +, – und Ziffern aus der Eingabe entfernt und die verbleibenden Zeichen in int konvertiert.
Schließlich kommt der Filter validate_email zum Einsatz, der ebenfalls zu den logischen Filtern zählt. Auch dieser Filter validiert lediglich, dass es sich bei der Eingabe um eine syntaktisch korrekte E-Mail-Adresse handelt und nimmt keinerlei Entwertung vor. Die Validierung erfolgt mittels eines hinreichend guten regulären Ausdrucks, garantiert also nicht, dass die E-Mail-Adresse auch wirklich existiert und erreichbar ist. Analog zu number_int existiert auch für E-Mail-Adressen ein Entwertungsfilter namens email, welcher alle unerlaubten Zeichen aus dem übergebenen Wert entfernt. Dies garantiert allerdings nicht, dass jede Eingabe validiert, da die restlichen Kriterien für eine E-Mail-Adresse erfüllt werden müssen.
Ob Sie sich in den letzten beiden Fällen für den logischen Filter oder die Entwertungsvariante entscheiden, hängt weitestgehend vom persönlichen Geschmack und dem Anwendungsfall ab. Einerseits kann es sinnvoll sein, dass Tippfehler des Benutzers mittels Entwertungsfiltern ausgebügelt werden, andererseits kann dies gerade bei E-Mail-Adressen zu Problemen führen.
Für viele Filter werden weitere Parameter von ezcInputFormDefinitionElement akzeptiert, die jedoch im gezeigten Beispiel nicht verwendet wurden. Hierbei kommen zum einen der Parameter $options, der Optionen für den Filter definiert, zum anderen der vierte Parameter $flags, der eine Bitmaske von festgelegten Konstanten akzeptiert, um die Arbeitsweise des Filters zu ändern, zum Einsatz.
Optionen werden als assoziatives Array übergeben, das die Optionsnamen als Schlüssel verwendet, denen gewünschte Optionswerte zugeordnet wird. Als Optionen akzeptiert zum Beispiel der int-Filter die Schlüssel min_range und max_range, mit denen ein Minimal- und ein Maximalwert für den übergebenen Wert festgelegt werden können. Als Flags ist eine Kombination aus ALLOW_OCTAL und ALLOW_HEX erlaubt. ALLOW_OCTAL veranlasst den Filter, auch Zahlen in oktaler Schreibweise, also mit führender 0 zur Kennzeichnung des Oktalsystems, zu akzeptieren. Die zweite Konstante erlaubt das Voranstellen der Zeichenkette 0x, womit auch Zahlen in hexadezimaler Darstellung erlaubt werden.
Der string-Filter akzeptiert ebenfalls Flags, besitzt aber keine Optionen. Standardmäßig entfernt er HTML-Tags und encodiert Anführungszeichen zu HTML-Entitäten, sodass sie keine Gefahr in SQL-Abfragen darstellen. Letzteres Verhalten kann über das Flag FILTER_FLAG_NO_ENCODE_QUOTES abgeschaltet werden. Zusätzlich lässt sich einstellen, dass Sonderzeichen ebenfalls entfernt oder encodiert werden. Dabei werden Sonderzeichen in zwei Kategorien unterschieden: Solche, denen im ASCII-Standard Werte kleiner als 32 zugeordnet werden (LOW), und solche, die Werte größer als 127 haben (HIGH). Für beide Typen steht jeweils ein Flag namens FILTER_FLAG_ENCODE_* für die Encodierung und FILTER_FLAG_STRIP_* zur expliziten Entfernung der Zeichen zur Verfügung. Das Flag FILTER_FLAG_ENCODE_LOW weist den Filter also an, alle Sonderzeichen mit Werten kleiner als 32 zu encodieren, während das Flag FILTER_FLAG_STRIP_HIGH dafür sorgt, dass Sonderzeichen entfernt werden. Das letzte verfügbare Flag FILTER_FLAG_ENCODE_AMP sorgt dafür, dass nur das kaufmännische Und-Zeichen (&) encodiert wird, was praktisch im Umgang mit Texten ist, die URLs enthalten.
Der validate_email-Filter akzeptiert keinerlei Optionen und Flags. Eine vollständige Übersicht der verfügbaren Filter können Sie der PHP-Onlinedokumentation, genauer dem Kapitel zur filter-Extension, entnehmen. [http://php.net/filter ] Da sich die UserInput-Komponente auf diese Standard-PHP-Erweiterung stützt, sind sämtliche dort beschriebenen Optionen und Flags verwendbar. Das folgende Beispiel soll kurz erläutern, wie beide Einstellungsvarianten verwendet werden können:
$definition = array(
// ...
'name' => new ezcInputFormDefinitionElement(
ezcInputFormDefinitionElement::REQUIRED,
'string',
array(),
FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH
),
// ...
'entryId' => new ezcInputFormDefinitionElement(
ezcInputFormDefinitionElement::REQUIRED,
'number_int',
array(
'min_range' => 23,
'max_range' => 42,
)
),
);Listing 10.2 Ein Definitionsbeispiel mit Optionen und Flags
Wie Sie sehen, handelt es sich um eine abgewandelte Version der Kommentarformulardefinition. Würde dieser Code im GP-Blog verwendet, wäre das Ergebnis wie folgt: Aus dem eingegebenen Namen des Kommentators würden alle Sonderzeichen entfernt, denen ein ASCII-Code kleiner als 32 oder größer als 127 zugeordnet ist. Außerdem wären für das Formularfeld $entryId nur noch Ganzzahl-Werte zwischen 23 und 42 zugelassen. Beides würde im GP-Blog keinen Sinn ergeben, und dient an dieser Stelle lediglich zur Illustration.
10.2.2 Sicherer Zugriff auf Formularvariablen 

Nachdem im vorhergehenden Abschnitt die erwarteten Variablen für das Kommentarformular definiert wurden, werden Sie nun sehen, wie Sie mit dieser Definition die UserInput-Komponente füttern und anschließend gesichert auf die definierten Variablen zugreifen können. Der hier gezeigte Code findet sich in der Aktion zum Speichern eines neuen Kommentars wieder, konkret in der Methode save() der Aktionsklasse gpBlogActionComment.
$comment = new gpBlogComment();
$comment->date = time();
$definition = self::getFormDefinition();
$form = new ezcInputForm( INPUT_POST, $definition );
foreach ( $definition as $elementName => $dummy )
{
if ( $form->hasValidData( $elementName ) )
{
$comment->$elementName = $form->$elementName;
}
else
{
gpBlogController::getInstance()->mainSignals->emit(
"error",
"Invalid data for input field $elementName,
ð
please go back and correct!"
);
return;
}
}Listing 10.3 Sicherer Zugriff auf Formulardaten
Zunächst wird eine neue Instanz des Kommentarmodells erzeugt und das Datum des Kommentars auf die aktuelle Zeit gesetzt. Anschließend wird die im vorhergehenden Abschnitt erläuterte Methode getFormDefinition() aufgerufen, um die Konfiguration der Formularelemente zu laden. Mit dieser Konfiguration wird nun ein neues Formularobjekt instanziiert, über welches der gesicherte Zugriff auf die definierten Variablen bereitgestellt wird. Als erster Parameter wird dem Konstruktor der Klasse die super-globale Variable mitgeteilt, aus der die gewünschten Daten gelesen werden sollen. Im gezeigten Fall ist dies das $_POST-Array. Entsprechende Konstanten im Namensformat INPUT_* existieren für alle super-globalen Arrays, die sensible Daten enthalten können:
- INPUT_POST
- INPUT_GET
- INPUT_COOKIE
- INPUT_SERVER
- INPUT_SESSION
- INPUT_REQUEST
Die Namen der Konstanten korrespondieren mit der Namensgebung der entsprechenden Arrays.
Der Konstruktor von ezcInputForm kann optional noch einen dritten Parameter namens $characterEncoding verarbeiten, der allerdings aktuell noch keine Funktion erfüllt. Ab PHP-Version 6 lässt sich hiermit definieren, wie die empfangenen Daten codiert sind, um so eine Übersetzung in die lokal verwendete Codierung zu gewährleisten. Sie können diesen Parameter also vorerst getrost ignorieren.
Die im Beispiel folgende foreach-Schleife iteriert durch die Definition der Formularvariablen und fragt beim Formularobjekt nach, ob für das gegebene Feld valide Daten vorliegen. Ist dies der Fall, wird der übergebene Wert aus dem Formularobjekt gelesen und dem entsprechenden Attribut des Modells zugewiesen. Sie sehen, der Zugriff auf die bereinigten und validierten Daten erfolgt komfortabel über das Konzept der Virtual Properties. Beachten Sie jedoch, dass Sie nur lesend auf diese Attribute zugreifen können und keine Änderungen möglich sind. Da die Formularfelder analog zu den Attributnamen der Modell-Klasse bezeichnet wurden, ist diese Zuweisung sehr einfach mittels variabler Variablen möglich.
Gibt der Aufruf von hasValidData() auf dem Formularobjekt den Wert false zurück, ist die Validierung des entsprechenden Elements fehlgeschlagen. Dies kann zum einen bedeuten, dass ein nicht optionales Element bei der Übertragung fehlte, und zum anderen, dass der übergebene Wert nicht den definierten Regeln entsprach. Zum Beispiel würde der Wert "http://example.com" nicht den Regeln für das Feld $E-Mail entsprechen und der Aufruf von hasValidData() dementsprechend false zurückgeben.
Schlägt eine Validierung fehl, sendet die gezeigte Aktion ein Fehlersignal und terminiert, ohne den Kommentar zu speichern. Die Signalverarbeitung kennen Sie bereits aus Kapitel 3, »Die Applikationsbasis«, und das Fehlersignal wurde bereits in Kapitel 4, »Fehlerbehandlung und Debugging«, eingeführt. Die im Anschluss an die foreach-Schleife folgende Speicherung des Modell-Objekts mittels der PersistentObject-Komponente wurde hier weggelassen. Sie können sich jedoch den kompletten Sourcecode der Aktion auf der Buch-CD im Unterverzeichnis stage08/ ansehen.
10.2.3 Ein weiteres Beispiel 

Nachdem Sie nun an einem einfachen Beispiel gesehen haben, wie Sie die UserInput-Komponente benutzen, werden wir im Folgenden ein weiteres Beispiel vorstellen, das Ihnen zwei weitere wichtige Filter und einige zusätzliche Features zeigt.
Neben dem Formular zur Erzeugung eines neuen Kommentars existiert im GP-Blog noch eines für die Dateneingabe beim Erstellen und Editieren eines Blog-Eintrags. Auch in der dazu passenden Aktion gpBlogActionEntryEdit wird nun eine Methode namens getFormDefinition() eingefügt, die wie folgt aufgebaut ist:
private static function getFormDefinition()
{
$definition = array(
'title' => new ezcInputFormDefinitionElement(
ezcInputFormDefinitionElement::REQUIRED,
'string'
),
'body' => new ezcInputFormDefinitionElement(
ezcInputFormDefinitionElement::REQUIRED,
'unsafe_raw'
),
'tags' => new ezcInputFormDefinitionElement(
ezcInputFormDefinitionElement::REQUIRED,
'callback',
array(
__CLASS__,
'validateTags'
)
),
);
return $definition;
}Listing 10.4 Komplexere Filter definieren
Neben dem aus Abschnitt 10.2.1, »Formulare definieren«, bereits bekannten string-Filter werden an dieser Stelle zwei neue Filter benutzt: unsafe_raw dient dazu, Daten unentwertet zu übernehmen, während es Ihnen der Filter callback erlaubt, eine beliebige PHP-Funktion selbst anzugeben, die zur Validierung verwendet werden soll.
Bitte achten Sie darauf, wann und wozu Sie den Filter unsafe_raw einsetzen, denn er widerspricht eigentlich dem gesamten Konzept hinter der filter-Erweiterung und der UserInput-Komponente. Die Daten, die Sie aus diesem Filter erhalten, müssen mit den gleichen Sicherheitsbedenken betrachtet werden, als wenn Sie direkt auf Daten aus dem entsprechenden super-globalen Array zugreifen würden. Der Vorteil gegenüber dem direkten Zugriff auf das Array ist, dass Sie explizit daran erinnert werden, dass es sich um unsichere Daten handelt, wenn Sie den sprechenden Namen des Filters bei der Definition verwenden müssen. Wir verwenden den Filter an dieser Stelle, da ein Benutzer des GP-Blogs im Text eines Eintrags HTML-Code verwenden darf und dieser bei Verwendung des string-Filters entfernt würde. Es Benutzern zu erlauben, HTML-Code zu verwenden, ist prinzipiell keine gute Idee. Sollten Sie also einmal in die Situation kommen, dass dies nötig wird, denken Sie noch ein weiteres Mal über alternative Möglichkeiten nach, denn das Potential für Sicherheitslücken ist hierbei immens hoch.
Der Filter callback erlaubt Ihnen eine beliebige, selbst definierte Funktion oder Methode zur Validierung und Entwertung von Formularfeldern anzugeben. Im dargestellten Fall wird zur Validierung des Formularfeldes $tags die statische Methode validateTags() auf der gleichen Klasse definiert. Die Funktionsweise dieser Methode haben Sie bereits in Kapitel 7, »ORM mit PersistentObject«, kennengelernt. Sie verwandelt den im Formular angegebenen String von kommaseparierten Tags in ein entsprechendes Array um, das dann in der Datenbank gespeichert werden kann. Hinzukommt an dieser Stelle noch, dass die Gültigkeit des Strings validiert und im Fall eines Fehlschlags false zurückgegeben wird. Hiermit wird der filter-Extension mitgeteilt, dass die Validierung fehlgeschlagen ist und somit keine korrekten Daten vorliegen.
public static function validateTags( $tagsField )
{
if ( preg_match( '/^[a-z0–9, ]*$/', $tagsField ) < 1 )
{
return false;
}
return array_map( "trim", explode( ",", $tagsField ) );
}Listing 10.5 Ein Callback-Filter
Die Filter-Methode erhält als einzigen Parameter den Wert des entsprechenden Formularfeldes. Gibt sie false zurück, ist dies für UserInput das Zeichen, dass die Validierung fehlgeschlagen ist. Alle weiteren Rückgabewerte werden als Ergebnis der Entwertung interpretiert. Sollten Sie nur eine Validierung vornehmen, so geben Sie einfach den Eingabeparameter wieder zurück, wenn die Validierung erfolgreich war. In diesem Beispiel besteht die Validierung darin, zu testen, ob der übergebene Text nur aus validen Zeichen für Tags besteht. Als valide werden alle Buchstaben und Zahlen angenommen; Sonderzeichen sind bis auf das Leerzeichen tabu. Zusätzlich enthält der reguläre Ausdruck, der zum Test verwendet wird, auch das Komma, da dies zur Trennung der einzelnen Tags benutzt wird.
Letztendlich fehlt noch die Anwendung der gezeigten Definition, die sich nicht wesentlich von der in Abschnitt 10.2.2, »Sicherer Zugriff auf Formularvariablen«, gezeigten Variante unterscheidet. Deshalb haben wir darauf verzichtet, den Code an dieser Stelle abzudrucken. Bei Interesse können Sie ihn auf der CD nachvollziehen.





Ihre Meinung






