Sie sind hier : sebastian1012.bplaced.net/ homepage-neu / Themen-Blog1 / warum-url-validierung-mit-filter_var-keine-gute-idee-ist.php

Warum URL-Validierung mit filter_var keine gute Idee ist

Als uns mit PHP 5.2 die filter_var-Funktion geschenkt wurde, war die Zeit solcher Monster vorbei (hier entliehen):

$urlregex = "^(https?|ftp)\:\/\/([a-z0-9+!*(),;?&=\$_.-]+(\:[a-z0-9+!*(),;?&=\$_.-]+)?@)?[a-z0-9+\$_-]+(\.[a-z0-9+\$_-]+)*(\:[0-9]{2,5})?(\/([a-z0-9+\$_-]\.?)+)*\/?(\?[a-z+&\$_.-][a-z0-9;:@/&%=+\$_.-]*)?(#[a-z_.-][a-z0-9+\$_.-]*)?\$";
if (eregi($urlregex, $url)) {echo "good";} else {echo "bad";}

Die simple, aber effektive Syntax:

filter_var($url, FILTER_VALIDATE_URL)

Als dritten Parameter können Filter-Flags übergeben werden, im Bezug auf die URL-Validierung gibt es die folgenden 4 Kandidaten:

FILTER_FLAG_SCHEME_REQUIRED
FILTER_FLAG_HOST_REQUIRED
FILTER_FLAG_PATH_REQUIRED 
FILTER_FLAG_QUERY_REQUIRED 

Dabei sind die ersten beiden FILTER_FLAG_SCHEME_REQUIRED und FILTER_FLAG_HOST_REQUIRED default.

Ans Eingemachte

So, dann schauen wir uns doch mal ein paar kritische Kandidaten an:

filter_var('http://example.com/"><script>alert("xss")</script>', FILTER_VALIDATE_URL) !== false; //true

Gut, hat ja auch niemand gesagt, dass der URL-Filter XSS bekämpfen soll – also ok. Weiter im Takt:

filter_var('php://filter/read=convert.base64-encode/resource=/etc/passwd', FILTER_VALIDATE_URL) !== false; //true

Schon kritischer. Ein beliebiges Schema macht den Filter glücklich. http(s) und ftp hätte ich mir ja noch gefallen lassen. Potentiell problematisch. Demnach dann auch ok:

filter_var('foo://bar', FILTER_VALIDATE_URL) !== false; //true

Und die Krönung zum Schluss

filter_var('javascript://test%0Aalert(321)', FILTER_VALIDATE_URL) !== false; //true

Schauen wir grad mal genauer hin: javascript ist das Schema. Klar, in die Browser-Adresszeile javascript:alert(1+2+3+4); eingeben und los gehts:

Javascript-URL

Javascript-URL

Ist das Grundprinzip von Bookmarklets und auch kein Geheimnis. Aber weiter: Der doppelte // ist ein gewöhnlicher Javascript-Kommentar, überzeugt aber filter_var davon, dass es sich um ein valides URLSchema handelt – siehe die Beispiele oben. Dann kommt die Zeichenfolge %0A, was genau der Output des folgenden Codes ist:

echo urlencode("\n");

Dämmerts? Durch das URL-encoded newline wird der eingeleitete Javascript-Kommentar beendet und es folgt beliebiger Javascript-Code. Stellen wir uns eine Dating-Seite vor, bei der Nutzer-URLs mit filter_var validiert werden und dann 1:1 dargestellt werden. Böses Einfallstor.

Und nun?

Zumindest eine händische Anpassung folgender Form könnte sich bewähren:

function validate_url($url)
{
	$url = trim($url);
	
	return ((strpos($url, "http://") === 0 || strpos($url, "https://") === 0) &&
		    filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED | FILTER_FLAG_HOST_REQUIRED) !== false);
}

Aber selbst nach dieser Anpassung kommt die doch sehr ungewöhnliche URL http://x durch die Validierung durch. Vielleicht sind die Regex-Monster doch nicht so schlecht ;). Ach, bevor ichs vergesse: filter_var ist nicht Multibyte-URL-fähig. Die absolut korrekte URL http://스타벅스코리아.com wird rejected:

var_dump(filter_var("http://스타벅스코리아.com", FILTER_VALIDATE_URL) !== false); //bool(false)

Also: filter_var mit Bedacht einsetzen und an den jeweiligen Kontext anpassen. Abschließend möchte ich noch auf diese schöne Aufstellung an URLs in Abhängigkeit der verschiedenen filter_var – Flags verweisen.