PHP-Validierung und Bereinigung sind äußerst wichtige Themen, denen sich jeder Entwickler bewusst sein sollte. Besonders bei leistungsstarken, modernen Frameworks scheinen die Leute die zugrunde liegenden Konzepte zu vergessen und fälschlicherweise anzunehmen, dass es schon irgendwie gelöst ist. Richtig eingesetzt und frühzeitig integriert spielen beide die zentrale Rolle bei der Abwehr von Angriffen auf Ihre Anwendung.
Dieser Artikel veranschaulicht die zugrunde liegende Notwendigkeit und erklärt, warum Sie sich darum kümmern sollten. Dann eine allgemeine Diskussion über Techniken und Ansätze, die zu konkreten Implementierungen von drei verschiedenen Frameworks führen. Endlich wird kurz ein Wochenendprojekt von mir vorgestellt.
Angriffe können natürlich Ihre Anwendungsschicht vollständig umgehen und beispielsweise direkt auf die Betriebssystem-, Netzwerk- usw. Schicht angreifen.
Warum kümmern?
Sie schreiben eine neue App – entweder für sich selbst, Ihren Kunden/Arbeitgeber oder vielleicht im Rahmen eines Open-Source-Projekts. Wie auch immer, Sie haben viele Stunden und Mühe hineingesteckt. Wie Sie vielleicht wissen, gibt es auch andere Leute, die viel Mühe in Ihre Website investieren: Spammer, Betrüger, Malware-Verteiler, Blackhat-SEOs und Kinder mit zu viel Zeit. Sie können Ihre sorgfältig durchdachte und erstellte Website für sich nutzen – wenn Sie keine Vorkehrungen treffen.
Das willst du nicht. Entweder ist es nur ärgerlich, wenn man kein Geld auf dem Spiel hat, oder es ist gefährlich, wenn man es hat – oder es kann echten Menschen schaden, wenn man mit sensiblen Daten arbeitet. Sobald Ihre App verletzt wurde, könnten Sie entweder als Spam-Zentrifuge enden oder Ihre Kunden werden Sie verlassen – wirklich, ich kann mir kein einziges gutes Ergebnis vorstellen.
Der Angriff auf Ihre Website kann auf mehreren Ebenen erfolgen. Belassen wir es zur Vereinfachung bei:
- Ihre Anwendung
- Dein Code
- Das Framework oder Module von Drittanbietern
- Dienste, die an der Bereitstellung Ihrer App beteiligt sind (z. B. Ihr Webserver, Datenbankserver)
- Andere Dienste, die von Computern bereitgestellt werden, auf denen Ihre App ausgeführt wird (z. B. FTP, SSH, ..)
- Die Serverbetriebssysteme oder die Virtualisierungsschicht
- Das Netzwerk
- Und vergessen wir nicht: Sie selbst (oder andere Personen mit privilegiertem Zugriff auf Ihre App).
Bedrohungen für Ihre App
Abhängig von Ihrer Hosting-Situation haben Sie möglicherweise nicht auf allen Ebenen Zugriff. In diesem Artikel geht es um die oberste Ebene – diejenige, auf die Sie definitiv Zugriff haben.
Ok, lassen Sie uns einen kurzen Blick darauf werfen, welcher Art von Angriffen Sie hier ausgesetzt sind, geordnet nach (aber nicht allen) der OWASP-Liste (Open Web Application Security Project) von 2010:
Injektion
Injektionen umfassen viele verschiedene Angriffe. Alle haben das Ziel, Code auf Ihrer (Server-) Seite auszuführen. Nur zwei Beispiele:
- SQL-Injection: Der Versuch, Ihre Datenbankabfragen zu manipulieren
Beispiel: http://foo.tld/?userid=‘ oder ‚1‘=’1
Das funktioniert natürlich auch mit jeder NoSQL-, LDAP- oder Whatnot-Datenbank - Code-Injektion:
Erinnerst du dich an register_globals? Dies war sicherlich ein einfach zu verwendender Vektor für diese Angriffe.
Anderes Beispiel: $res = eval(‚return 1 + ‚. $_GET[‚crazy‘]+ ‚;‘); mit ?crazy=0;exec(„rm+-rf+/“)
XSS
XSS steht für Cross Site Scripting. Die allgemeine Idee besteht darin, Code (Javascript, HTML, Flash) so einzufügen, dass er ausgeführt wird, wenn andere Benutzer / Sie eine Seite der angegriffenen App besuchen. Eine recht verbreitete Form wäre wohl das Script Planting (im Grunde ist dies auch eine Art Injection, aber der injizierte Code soll nicht von Ihrer App/Datenbank/.. ausgeführt werden, sondern von einem Benutzer/Besucher). Angenommen, das Folgende ist Teil eines Anmeldeformulars (ich habe den Attac-Code nicht URL-codiert, damit Sie ihn besser lesen können)
http://foo.tld/signup?username=&…
CSRF
CSRF steht für Cross Site Request Forgery. Es ist ein Angriff, der versucht, die Sitzung eines (gültigen) Benutzers zu nutzen/wiederzuverwenden, um Dinge in „seinem Namen“ zu tun. Daher lockt der Angriff das Ziel auf seine eigene Website (oder eine, die er kompromittiert hat, zB mit XSS) und führt im Hintergrund eine Abfrage aus. Das bekannteste Beispiel ist der Angriff, der ein img-Tag missbraucht. Stellen Sie sich vor, Sie haben einen Account beim Mailanbieter ACME und werden auf eine Seite mit folgendem Inhalt gelockt:
Wenn Sie die bösartige Seite besuchen, während Sie bei ACME angemeldet sind -> nach einem neuen Job suchen, wenn ACME nicht vor CSRF schützt und Ihr Chef keinen Sinn für Humor hat. CSRF-Angriffe sind im Allgemeinen eher zielgerichtet und auf Geldgewinn ausgerichtet. Ich hoffe aber, du verstehst es..
Schützen Sie Ihre App
Wie können Sie diese Angriffe vermeiden? Nun, es gibt kein magisches PHP-Modul, das Sie installieren können, und alles ist gut. Aber es gibt einen Weg, wie Sie die Bedrohung zumindest minimieren können: Werden Sie paranoid. Sehen Sie alles als Angriff. Alle Benutzereingaben sollten so behandelt werden, als ob es sich um einen Angriff handelt! Jede Anfrage ist als böswillig beabsichtigt anzusehen. Außerdem: Sie versuchen, Ihre App stabil zu machen, und wenn alles funktioniert, sind Sie zufrieden – vergessen Sie das: Versuchen Sie, sie zu beschädigen. Wenn Sie scheitern, geben Sie sich mehr Mühe! Oder, wenn Sie diese Möglichkeit haben: Bitten Sie jemand anderen, dies für Sie zu tun (alles natürlich am besten in einer Testumgebung!). Bruce Schneier hat einen großartigen Artikel zu diesem Thema geschrieben. Ich fordere Sie auf, es zu lesen.
Im Zusammenhang mit Validierung, Bereinigung und insbesondere Injektionen sollte ich zumindest Web Application Firewalls (WAF) und IPS/IDS erwähnen. Es würde jedoch den Rahmen dieses Artikels sprengen, hier ins Detail zu gehen.
Dieser Artikel versucht, einige Hintergründe zu zwei Hauptthemen zu liefern, die, wenn sie richtig verstanden und angewendet werden, die Kernverteidigung gegen XSS- und Injection-Angriffe darstellen.
Validierung vs. Bereinigung
Beide Techniken sind für sich genommen nützlich. Sie können als völlig unabhängig betrachtet werden, werden aber im Zusammenhang mit der (PHP-)Webentwicklung im selben Zusammenhang behandelt: Umgang mit Formularen.
Validierung
Die Datenvalidierung dreht sich um die Datenprüfung, ob die gegebene Eingabe wohlgeformt ist bzw. die gegebenen Qualifikationen (sprich: Regeln) sind.
Beispielsweise könnte die Zeichenfolge 2011-11-20 Ausnahmen bei der Datenvalidierung auslösen, wenn Sie ein Datum im Format TT-MM-JJJJ und nicht JJJJ-MM-TT erwarten würden. Ein weiteres Beispiel: Die Bereinigung könnte (vereinfacht) alle nicht a-z, „@“ und „.“ entfernen. Zeichen, aber die Validierung prüft, ob sie auch wie eine E-Mail aussehen und/oder ob die Domain dieser E-Mail wirklich existiert und so weiter.
Die Datenvalidierung wird häufig (wenn nicht immer) auf Parameterbasis verwendet (z. B. wird Ihre E-Mail-Eingabe anhand anderer Regeln als Ihrer Betrags-Wert-Eingabe geprüft).
Die Datenvalidierung verändert keine Daten (im Grunde: sollte niemals).
Eine „vollständige“ Datenvalidierung würde eine Bereinigung obsolet machen – allerdings würde sie entweder zu einer schlechten Usability führen (z. B. ist das „stille“ Umwandeln eines Benutzernamens in Kleinbuchstaben, URL-kompatible Zeichen für den Benutzer weniger lästig, als eine so lange Ausnahme zu werfen, wie er sie bekommt rechts) oder in einer Menge Arbeit enden (z. B. wenn Sie sicher sind, dass Sie dem Benutzer nicht erlauben werden, HTML in irgendein Feld zu schreiben, ist es einfacher, es aus jeder Eingabe zu entfernen, als eine Validierung hinzuzufügen, die einen Fehler auslöst, wenn HTML es ist für jeden Eingang separat enthalten).
Desinfektion
Wenn Sie zum Wikipedia-Artikel zur Datenbereinigung gehen, werden Sie sofort zum Code-Injection-Artikel weitergeleitet. Dies liegt daran, dass die Datenbereinigung die Implementierung der Code-Injection-Prävention ist.
Die Bereinigung kann die Daten ändern, an denen sie arbeitet und die sie versucht. Gute Beispiele wären das Entfernen von HTML-Code oder das Entfernen von Zeilenumbrüchen in E-Mail-Feldern Von oder An.
Die Bereinigung wird häufig sowohl global (z. B. sämtliches HTML aus allen Eingaben entfernen) als auch lokal (z. B. alle nicht-numerischen Zeichen aus dieser bestimmten vermeintlichen Integer-Eingabe entfernen) verwendet.
Auch als Bereinigung werden Filter bezeichnet, die die Daten ohne unmittelbare Sicherheitsgründe manipulieren. Zum Beispiel das Trimmen von Eingangssaiten oder ähnliches. Meiner Meinung nach sollten diese nur Filter genannt werden. Bereinigungen sind übrigens eine Untergruppe / spezielle Art von Filtern.
Die Bereinigung wird nicht nur für Eingabedaten verwendet – sie kann auch vor der Ausgabe von Daten angewendet werden. Wenn Sie beispielsweise eingehende Textdaten nicht auf (HTML-, JS-, ..)-Injektionen prüfen, können Sie sie bei jeder Textausgabe anwenden. Meiner Meinung nach ist dies nur in sehr seltenen Fällen eine gute Idee. In Anbetracht des vorherigen Beispiels würde es einfach keinen Sinn machen, Daten mit Injektionscode zu speichern – abgesehen von Sicherheitsüberprüfungen.
Wie zu beschäftigen
Einige Gedanken, denen ich eher folge:
- Erlaube ich jede Art von HTML in Benutzereingaben? Wenn nein (meistens): Stellen Sie sicher, dass dies so früh wie möglich für alle Eingaben bereinigt wird.
- Welche Sprachen sollen unterstützt werden? Nur Englisch? Dann lassen Sie alle anderen Zeichen (zB Koreanisch) weg!
- Beginnen Sie mit einer strengsten Validierung. Machen Sie die Regeln bei Bedarf offener – nicht vorher!
- Vertrauen Sie niemals dem Benutzer (Eingabe).
Allgemeine Implementierung für MVCs
Selbst innerhalb eines Frameworks gibt es oft mehrere Möglichkeiten, Daten zu validieren / zu bereinigen. Es gibt jedoch zwei allgemeine Ansätze, die dadurch definiert werden, wo diese stattfinden: Innerhalb der Steuerungslogik und innerhalb der Modelllogik (ORM). Oft sind beide in unterschiedlichem Umfang verfügbar und können kombiniert werden oder nativ zusammenarbeiten.
Controller zentriert
Controller-zentrische Validierung und Bereinigung verfolgt ein formularbasiertes Ziel: Bei einem bekannten Satz von Formularen, in die Benutzer Daten eingeben, definieren Sie Validierungsregeln für jedes dieser Formulare.
Pro
- Sie können für jedes Formular ein minimales und gezieltes Validierungsprofil schreiben. Ihre Validierungslogik ist unabhängig vom tatsächlich verwendeten Modell (oder Modellen, wenn
- Sie mehrere Datenquellen verwenden). Modelllose Validierung, wenn Sie ein Modell entweder gar nicht oder nicht überall dort einsetzen, wo Sie eine Validierung benötigen. Wenn Sie bereits bereinigte Daten auf der Controller-Ebene haben, können Sie damit einfacher arbeiten (z. B. ausgeben).
Kontra
- Die Bereinigung für datenquellentypspezifische Injektionen ist im Controller keine gute Idee. Wiederholung von Regeln für Formulare mit sich überschneidenden Eingabefeldern (z. B. Anmeldeformular und Anmeldeformular, beide validieren den Benutzernamen).
Modellzentriert
Bei einer rein modellzentrierten Validierung und Bereinigung definieren Sie Validierungsregeln einmal bei der Modelldefinition (des ORM). Sie gelten immer dann, wenn das Modell aktualisiert / eine neue Instanz erstellt wird. Bei der Bereinigung werden an dieser Stelle hauptsächlich Datenbankcode-Injektionen (z. B. SQL-Injections) berücksichtigt.
Pro
- Die modellzentrische Validierung kümmert sich nicht darum, „wo“ (in Ihrer Geschäftslogik, z. B. Controller-Code) Sie eine Validierung benötigen – daher ist die Validierung verschiedener Formulare (die teilweise ein Modell darstellen) einfach.
- Insbesondere die Bereinigung für Injektionen sollte datenquellenspezifisch sein -> es ist sinnvoll, dies im Modell zu tun (andernfalls müsste die Controller-Schicht den tatsächlich verwendeten Datenquellentyp kennen).
Kontra
- Komplexe Formulare, die mehrere Modelle mit voneinander abhängigen Validierungsanforderungen umfassen, sind möglicherweise nicht möglich.
- Modellschicht ist erforderlich (Validierung ohne Modell nicht möglich).
- Mehrere Modelle (sprich: Datenquellen) haben möglicherweise keine / unterschiedliche Validierungsansätze -> ärgerlich (dies gilt nicht für die Bereinigung).
Die PHP-Filtererweiterung
Die PHP-Filtererweiterung bietet einen einfachen Ansatz, um einzelne Dateneingaben zu validieren und zu bereinigen.
Es gibt einen vordefinierten Satz von Validierungsfiltern, die eine gute Vorgabe häufig verwendeter Validierungsanforderungen bieten (z. B.: E-Mail, Ganzzahl, IP, reguläre Ausdrücke oder URL). Es gibt auch einen nützlichen Satz von Desinfektionsfiltern, einschließlich der häufigsten Anforderungen. Beide können mit einem Satz Filter-Flags konfiguriert werden.
Ein kurzes Beispiel:
<?php
// ..
$email = filter_var($input, FILTER_SANITIZE_EMAIL);
if (filter_var($input, FILTER_VALIDATE_EMAIL)) {
echo "Your email '$email' was accepted";
} else {
echo "Sorry, but '$email' seems not to be an email";
}
//..
Diese Erweiterung ist sehr nützlich, wenn Sie ein minimales PHP-Skript für einen einzigen Zweck erstellen.
Überblick über Validierung & Bereinigung in MVCs
Die Schaufenster für die vorgestellten Frameworks könnten Ihnen einen schnellen Eindruck davon vermitteln, wie jedes von ihnen mit Validierung und Bereinigung umgeht. Ich werde nicht sehr ins Detail gehen (jeder von ihnen würde einen Artikel von der Länge dieses Artikels verdienen), aber einige Links bereitstellen, wenn Sie interessiert sind.
Vielleicht haben Sie schon mit einem von ihnen gearbeitet, dann interessiert Sie vielleicht, wie die anderen mit dem Problem umgehen.
Für alle drei Frameworks werde ich den gesamten Code bereitstellen, der zum Validieren eines einfachen Formulars erforderlich ist, bestehend aus einem Feld namens login, das erforderlich ist, aber keine Validierungsregeln hat, und einem Feld namens email, das eine E-Mail sein sollte und auch hat eine invertierte reguläre Ausdrucksregel. Beide E-Mail-Regeln sollten unterschiedliche Fehler ausgeben. Um die Bereinigung zu demonstrieren, werde ich alle Eingaben aus HTML entfernen. Außerdem werde ich einen Filter verwenden, der eine Eingabe in eine URL-kompatible Zeichenfolge umwandelt.
Symfony 2
Das verwendete Symfony 2.1.3 verwendet Doctrine als ORM. Die Lehre kommt von sich aus mit der Validierung und Bereinigung. Ich gehe nur auf die Symfony2-eigene Validierung & Desinfektionsmethoden.
Validierung
Validierung in Symfony2 auf Controller-Ebene (siehe oben) basiert auf POPOs (Plain Old PHP Objects). Wie immer haben Sie bei Symfony2 mehrere Möglichkeiten, dies zu tun: Verwenden Sie YAML, Anmerkungen, XML oder direktes PHP.
Sobald ein POPO erstellt wurde, verwenden Sie den Validierungsdienst im Controller, um die vom Benutzer eingegebenen Daten zu überprüfen.
In diesem Beispiel verwende ich die YAML-Konfiguration. Da die Symfony-Validierung mehr als eine Datei erfordert, überlasse ich alle drei:
src/Test/MyBundle/Resources/config/validation.yml (die Validierungsregeln)
Test\MyBundle\Entity\Author:
properties:
login:
- NotBlank: ~
email:
- NotBlank: ~
- Email:
message: "This is not an email"
- Regex:
pattern: "/a.*b/"
match: false
message: "I don't like this email"
src/Test/MyBundle/Entity/Dummy.php (the POPO)
<?php // .. class Dummy { public $login; public $email; } //..
src/Test/MyBundle/Controller/DefaultController.php
<?php
// ..
namespace Test\MyBundle\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Test\MyBundle\Entity\Dummy;
class DefaultController extends Controller
{
/**
* @Route("/check/{name}")
* @Template()
*/
public function indexAction($name)
{
$dummy = new Dummy();
$dummy->login = $this->get('request')->get('login');
$dummy->email = $this->get('request')->get('email');
$validator = $this->get('validator');
$errors = $validator->validate($dummy);
if (count($errors) > 0) {
return new Response("<pre>". print_r($errors, true). "</pre>");
} else {
return new Response("'$name' is valid");
}
}
}
//..
Desinfektion
Wie oben erwähnt, enthält Doctrine eine Bereinigung für Datenbankinjektionen (SQL). Abgesehen davon gibt es keine empfohlene/vorgesehene Eingabebereinigung auf Controller-Ebene. Wenn Sie jedoch Twig in der Ansicht verwenden, ist die Ausgabebereinigung verfügbar.
Laravel 3
Im Folgenden geht es um Laravel 3.2.12. Laravel 4 ist jedoch bereit und könnte das meiste, wenn nicht alles ändern.
Validierung
Laravel verfolgt eine Controller-zentrierte Validierungsstrategie. Die Validierung erfolgt mithilfe einer statischen Methode, die auf den angegebenen Eingabedaten ausgeführt wird, und einer Reihe von Regeln, die die Eingaben validieren. Ich konnte keine sofort einsatzbereite Methode finden, um den umgekehrten regulären Ausdruck zu verwenden (also habe ich eine andere verwendet). Es ist möglich, zusätzliche benutzerdefinierte Überprüfungen bereitzustellen, aber es gibt keine triviale Möglichkeit, dies zu tun.
Formfehler können von der Ansicht aus aufgerufen werden, sind aber nicht so nativ miteinander verwoben wie in den anderen beiden Frameworks. Noch auf dem Zaun, was ich bevorzuge.
<?php
// ..
class Home_Controller extends Base_Controller {
public function action_index()
{
$input = Input::all();
$rules = array(
'login' => 'required',
'email' => 'required|email|match:/a/',
);
$messages = array(
'required' => 'You missed the :attribute field',
'email' => 'This is not an email',
'match' => "I don't like this email"
);
$validator = Validator::make($input, $rules, $messages);
if ($validator->passes()) {
return "All is good";
} else {
return "<pre>". print_r($validator->errors, true). "</pre>";
}
}
}
//..
Desinfektion
Es gibt keine offizielle Implementierung oder Empfehlung zur Bereinigung von Benutzereingaben. Auf der Modellebene scheint das PDO-Quoting verwendet zu werden.
CakePHP 2
Ich habe die aktuelle stabile CakePHP-Version 2.2.3 verwendet.
Validierung
CakePHP verwendet einen modellzentrierten Ansatz für die Validierung. CakePHP bietet eine eigene Modellimplementierung, sodass die Validierung einzigartig ist.
Sie können Modellvalidierer zur Laufzeit erweitern, aber es gibt keine empfohlene Methode zum Validieren von Daten ohne Modelle.
app/Model/Dummy.php (das Modell)
<?php
// ..
class Dummy extends AppModel
{
public $validate = array(
'login' => array(
'required' => true,
'rule' => 'alphaNumeric',
),
'email' => array(
'isThere' => array(
'rule' => 'notEmpty',
'required' => true,
'message' => 'Email is missing',
),
'checkEmail' => array(
'rule' => 'email',
'required' => true,
'message' => 'This is not an email',
),
'checkLikeIt' => array(
'rule' => array('checkReverseRegex', '/a.*b/i'),
'message' => 'I don\'t like this email',
'required' => true,
),
)
);
public function checkReverseRegex($data, $regex)
{
return !preg_match($regex, $data['email']);
}
}
//..
app/Controller/DummyController.php (the controller -> using validation)
<?php
// ..
class DummyController extends AppController
{
public $uses = array('Dummy');
public function index()
{
$this->autoRender = false;
// used GET cause I am lazy, for POST: $this->data
$this->Dummy->set($this->params['url']['data']);
if ($this->Dummy->validates()) {
print "All is good";
} else {
print '<pre>'. print_r($this->Dummy->validationErrors, true). '</pre>';
}
}
}
//..
Die Validierung von CakePHP wird eng mit der Formulargenerierung in der Ansichtsebene interveniert.
Die Validierung von Nicht-Modelleingaben wird nicht nativ unterstützt (abgesehen vom Aufrufen der vordefinierten Validierungsmethoden pro Eingabe).
Desinfektion
Die Datenbereinigung wird als Dienstprogramm implementiert, auf das von überall aus zugegriffen werden kann (Controller, Komponente, Modell … sogar Ansicht). Es folgt einem Sanitize-All-Input-Ansatz mit einem festen Satz vordefinierter Bereinigungsfilter. Das Bereinigen bestimmter Eingaben mit dedizierten Regeln ist möglich, scheint jedoch nicht empfohlen zu werden. Die bestehenden Regeln konzentrieren sich auf SQL- und HTML-Injektionen und das Herausfiltern allgemein verdächtiger Unicode-Zeichen.
<?php
// ..
App::uses('Sanitize', 'Utility');
class DummyController extends AppController
{
public function index()
{
$this->autoRender = false;
// used GET cause I am lazy, for POST: $this->data
$clean = Sanitize::clean($this->params['url']['data'], array(
'remove_html' => 1
));
$clean['Dummy']['login'] = Inflector::slug($clean['Dummy']['login']);
print '<pre>'. print_r($clean, true). '</pre>';
}
}
//..
NOTE! Den englischen Originaltext finden Sie unter: https://web.archive.org/web/20160319094902/https://foaa.de/blog/2012/11/27/php-validation-and-sanitization/
Many thanks to: Ulrich Kautz