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. Hier selbst ausprobieren.

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.

Weitere Posts:

Dieser Beitrag wurde unter php, Security, webdev veröffentlicht. Setze ein Lesezeichen auf den Permalink.

2 Antworten auf Warum URL-Validierung mit filter_var keine gute Idee ist

  1. Micha sagt:

    Wir nutzen filter_var() auch ausschließlich um die URL’s aus fremden RSS Feeds zu validieren, hier wird erwartet dass es sich um eine http(s) saubere reine URL handelt. Hier habe ich schon die wildesten Auswüchse gesehen, fern ab von URLs. Für alle anderen Fälle sind deine Hinweise Gold wert, danke dafür!

  2. Georg sagt:

    Wenn man filter_var($url, FILTER_VALIDATE_URL) noch mit filter_var($url, FILTER_SANITIZE_URL) und noch besser mit filter_var($url, FILTER_SANITIZE_STRING) kombiniert, hat man schon eione Menge Mist draussen. Des Weiteren kann man hier auch noch mit eigenen „Blacklist“ Filtern arbeiten, hier muss man auf den jeweiligen Kontext achten…

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.