URL-Manipulationen verhindern

Heute mal ein Beitrag, der nicht ganz so sehr auf Performance (aber auch) sondern auf Sicherheit abzielt. Ganz nebenbei hat das auch wirtschaftlich Sinn. Aus aktuellem Anlass bei einer meiner Seiten bin ich auf eine Idee gekommen, wie man URL-Manipulationen zwecks SQL-Injection effektiv verhindern kann. Getreu nach dem Motto „All incoming data is evil“…

Es gibt verschiedene Wege GET-Parameter, die per URL übermittelt wurden, zu überprüfen. Wir nehmen als Beispiel die URL
http://www.domain.de/seite.php?id=1&parameter=abc

Solche URLs sind immer von der Gefahr betroffen, dass die Parameter einfach über die Browserzeile bearbeitet werden können. Mit POST-Parametern geht das auch, aber es ist nicht ganz so einfach (ich sehe schon die Kommentare dazu, wie einfach das ist … ???? )
Jedenfalls werden diese Parameter anschließend meistens in einer SQL-Abfrage verwendet. Zum Beispiel so:

SELECT spalte1,spalte2  
 FROM tabelle   
WHERE ID=".$_GET['id']."

Das ist aber sehr gefährlich, da recht einfach eine SQL Injection durchgeführt werden kann. Deshalb muss die GET-Variable gefiltert werden (oder zumindest überprüft auf schädliche Zeichen).

Ein Ansatz der Filterung wäre ungültige Zeichen zu ersetzen – ganz am Anfang des Scripts.

$_GET['id'] = str_replace(array("'","\"",";",...),"",$_GET['id']);

Das ganze kann man auch über einen regulären Ausdruck lösen, das nenne ich aber mal im gleichen Atemzug.

Eleganter ist die Lösung über die Funktion mysql_real_escape_string. Diese benötigt allerdings zwingend eine aufgebaute Datenbankverbindung (würde hier funktionieren, aber es soll ja auch andere DBMS oder dynamische Seiten ohne Datenbank geben…).

Es wäre auch settype() oder ein Typ-Cast möglich, aber diese Prüfungen sind alle PHP-basiert, weshalb ich sie mal in eine Gruppe stecken möchte, um sie gegen folgende Variante zu vergleichen.

Die Idee, die ich nun letztens angewendet habe, basiert nicht auf PHP. Sie ergibt sich aus dem Einsatz von mod-rewrite. Dieses Modul für den Apache (und auch andere Server) ermöglicht das dynamische Umschreiben von URLs mit Hilfe von regulären Ausdrücken. Oben genannte URL könnte damit zum Beispiel so aussehen:
http://www.domain.de/seite-1-abc.html
Das hat vor allem auch für Suchmaschinen positive Auswirkungen, denn solche URLs sind erstens statisch und zweitens meist aussagekräftiger, weil Sie noch den Seitentiel oder ähnliche Schlüsselwörter enthalten. Aber das nur am Rande, hier gehts ja mehr um den Performance- und Sicherheitsaspekt an der Geschichte.

Wir gehen einmal davon aus, dass die zu der oben genannten SQL-Abfrage gehörige Spalte ID vom Typ UNSIGNED INT ist.
Welche mod-rewrite-Regel können wir also nun zum Umschreiben verwenden? Da gibt es mehrere Möglichkeiten, z.B.:
RewriteRule ^seite-(.*)-(.*).html$ seite.php?id=$1&parameter=$2 [L]
Und genau hier liegt der Hase im Pfeffer. Man sollte aus Sicherheitsgründen den Wertebereich in den mod-rewrite-Regeln so eng wie möglich (aber natürlich so weit wie nötig) fassen.
Viel besser ist deshalb folgende Variante:
RewriteRule ^seite-([0-9]+)-(.+).html$ seite.php?id=$1&parameter=$2 [L]
Nun können bei der ID auch wirklich nur noch numerische Werte übergeben werden. Folgende URL würde nun also nicht mehr funktionieren (404-Fehler):
seite-1 OR 1-abc.html

Genau diese URL wäre eine mögliche Angriffs-URL gewesen. Aber es gäbe noch viele weitere…
Wenn der mögliche Wertebereich für Parameter noch enger eingeschränkt ist – besonders bei einer überschaubaren Wertemenge der Größe 2 oder 3, kann man die Regel noch restriktiver fassen. Wir nehmen an, dass die Variable parameter nur die Werte „hilfe“,“seite“ und „datei“ annehmen kann. Dann können wir die Regel so formulieren:
RewriteRule ^seite-([0-9]+)-(hilfe|seite|datei).html$ seite.php?id=$1&parameter=$2 [L]

Die Variante über die .htaccess-Datei ist zudem schneller als die PHP-Variante (egal, wie gefiltert wird). Somit kann die .htaccess 3 Fliegen mit einer Klappe schlagen: suchmaschinenfreundliche URLs, Sicherheitsgewinn, Performancegewinn.

Übrigens habe ich es nicht geschafft Abkürzungen wie \d für Ziffern hier zu benutzen. Ich weiß nicht, ob das an einer Apache-Einstellung liegt oder ob das grundsätzlich nicht möglich ist.

Ich denke ich konnte diese einfache Methode etwas näher bringen, denn ich sehe immer wieder, dass nur einfach (.*) und (.+) in den Regeln benutzt wird. Das sollte man vermeiden, es sei denn der Wertebereich ist wirklich unüberschaubar (Produktnamen bei einem Onlineshop, die in der URL auftauschen sollen).


Deprecated: Directive 'allow_url_include' is deprecated in Unknown on line 0