{"id":637,"date":"2012-09-10T14:50:22","date_gmt":"2012-09-10T12:50:22","guid":{"rendered":"https:\/\/d-mueller.de\/blog\/?p=637"},"modified":"2012-09-19T23:14:01","modified_gmt":"2012-09-19T21:14:01","slug":"warum-url-validierung-mit-filter_var-keine-gute-idee-ist","status":"publish","type":"post","link":"https:\/\/d-mueller.de\/blog\/warum-url-validierung-mit-filter_var-keine-gute-idee-ist\/","title":{"rendered":"Warum URL-Validierung mit filter_var keine gute Idee ist"},"content":{"rendered":"<div style=\"background:#FFFD91;padding:10px;margin-bottom:20px\">Prefer this in English? <a href=\"https:\/\/d-mueller.de\/blog\/why-url-validation-with-filter_var-might-not-be-a-good-idea\/\">Why URL validation with filter_var might not be a good idea<\/a><\/div>\n<p>Als uns mit PHP 5.2 die <a href=\"http:\/\/php.net\/manual\/de\/function.filter-var.php\">filter_var<\/a>-Funktion geschenkt wurde, war die Zeit solcher Monster vorbei (<a href=\"http:\/\/phpcentral.com\/208-url-validation-in-php.html\">hier entliehen<\/a>):<\/p>\n<pre data-enlighter-language=\"php\" class=\"EnlighterJSRAW\">\r\n$urlregex = &quot;^(https?|ftp)\\:\\\/\\\/([a-z0-9+!*(),;?&amp;=\\$_.-]+(\\:[a-z0-9+!*(),;?&amp;=\\$_.-]+)?@)?[a-z0-9+\\$_-]+(\\.[a-z0-9+\\$_-]+)*(\\:[0-9]{2,5})?(\\\/([a-z0-9+\\$_-]\\.?)+)*\\\/?(\\?[a-z+&amp;\\$_.-][a-z0-9;:@\/&amp;%=+\\$_.-]*)?(#[a-z_.-][a-z0-9+\\$_.-]*)?\\$&quot;;\r\nif (eregi($urlregex, $url)) {echo &quot;good&quot;;} else {echo &quot;bad&quot;;}\r\n<\/pre>\n<p>Die simple, aber effektive Syntax:<\/p>\n<pre data-enlighter-language=\"php\" class=\"EnlighterJSRAW\">\r\nfilter_var($url, FILTER_VALIDATE_URL)\r\n<\/pre>\n<p>Als dritten Parameter k\u00f6nnen Filter-Flags \u00fcbergeben werden, im Bezug auf die URL-Validierung gibt es die folgenden 4 Kandidaten:<\/p>\n<pre data-enlighter-language=\"enlighter\" class=\"EnlighterJSRAW\">\r\nFILTER_FLAG_SCHEME_REQUIRED\r\nFILTER_FLAG_HOST_REQUIRED\r\nFILTER_FLAG_PATH_REQUIRED \r\nFILTER_FLAG_QUERY_REQUIRED \r\n<\/pre>\n<p>Dabei sind die ersten beiden <i>FILTER_FLAG_SCHEME_REQUIRED<\/i> und <i>FILTER_FLAG_HOST_REQUIRED<\/i> default.<\/p>\n<h2>Ans Eingemachte<\/h2>\n<p>So, dann schauen wir uns doch mal ein paar kritische Kandidaten an:<\/p>\n<pre data-enlighter-language=\"php\" class=\"EnlighterJSRAW\">\r\nfilter_var(&#039;http:\/\/example.com\/&quot;&gt;&lt;script&gt;alert(&quot;xss&quot;)&lt;\/script&gt;&#039;, FILTER_VALIDATE_URL) !== false; \/\/true\r\n<\/pre>\n<p>Gut, hat ja auch niemand gesagt, dass der URL-Filter XSS bek\u00e4mpfen soll &#8211; also ok. Weiter im Takt:<\/p>\n<pre data-enlighter-language=\"php\" class=\"EnlighterJSRAW\">\r\nfilter_var(&#039;php:\/\/filter\/read=convert.base64-encode\/resource=\/etc\/passwd&#039;, FILTER_VALIDATE_URL) !== false; \/\/true\r\n<\/pre>\n<p>Schon kritischer. Ein beliebiges Schema macht den Filter gl\u00fccklich. http(s) und ftp h\u00e4tte ich mir ja noch gefallen lassen. Potentiell problematisch. Demnach dann auch ok:<\/p>\n<pre data-enlighter-language=\"php\" class=\"EnlighterJSRAW\">\r\nfilter_var(&#039;foo:\/\/bar&#039;, FILTER_VALIDATE_URL) !== false; \/\/true\r\n<\/pre>\n<h2>Und die Kr\u00f6nung zum Schluss<\/h2>\n<pre data-enlighter-language=\"php\" class=\"EnlighterJSRAW\">\r\nfilter_var(&#039;javascript:\/\/test%0Aalert(321)&#039;, FILTER_VALIDATE_URL) !== false; \/\/true\r\n<\/pre>\n<p>Schauen wir grad mal genauer hin: javascript ist das Schema. Klar, in die Browser-Adresszeile <i>javascript:alert(1+2+3+4);<\/i> eingeben und los gehts:<\/p>\n<div id=\"attachment_638\" style=\"width: 469px\" class=\"wp-caption alignnone\"><a href=\"https:\/\/d-mueller.de\/blog\/wp-content\/uploads\/2012\/09\/javascript-url.png\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-638\" src=\"https:\/\/d-mueller.de\/blog\/wp-content\/uploads\/2012\/09\/javascript-url.png\" alt=\"Javascript-URL\" title=\"Javascript-URL\" width=\"459\" height=\"234\" class=\"size-full wp-image-638\" srcset=\"https:\/\/d-mueller.de\/blog\/wp-content\/uploads\/2012\/09\/javascript-url.png 459w, https:\/\/d-mueller.de\/blog\/wp-content\/uploads\/2012\/09\/javascript-url-300x152.png 300w\" sizes=\"auto, (max-width: 459px) 100vw, 459px\" \/><\/a><p id=\"caption-attachment-638\" class=\"wp-caption-text\">Javascript-URL<\/p><\/div>\n<p>Ist das Grundprinzip von Bookmarklets und auch kein Geheimnis. Aber weiter: Der doppelte \/\/ ist ein gew\u00f6hnlicher Javascript-Kommentar, \u00fcberzeugt aber filter_var davon, dass es sich um ein valides URLSchema handelt &#8211; siehe die Beispiele oben. Dann kommt die Zeichenfolge <i>%0A<\/i>, was genau der Output des folgenden Codes ist:<\/p>\n<pre data-enlighter-language=\"php\" class=\"EnlighterJSRAW\">\r\necho urlencode(&quot;\\n&quot;);\r\n<\/pre>\n<p>D\u00e4mmerts? 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\u00f6ses Einfallstor. <a href=\"http:\/\/codepen.io\/anon\/pen\/logCd\">Hier selbst ausprobieren<\/a>.<\/p>\n<h2>Und nun?<\/h2>\n<p>Zumindest eine h\u00e4ndische Anpassung folgender Form k\u00f6nnte sich bew\u00e4hren:<\/p>\n<pre data-enlighter-language=\"php\" class=\"EnlighterJSRAW\">\r\nfunction validate_url($url)\r\n{\r\n\t$url = trim($url);\r\n\t\r\n\treturn ((strpos($url, &quot;http:\/\/&quot;) === 0 || strpos($url, &quot;https:\/\/&quot;) === 0) &amp;&amp;\r\n\t\t    filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED | FILTER_FLAG_HOST_REQUIRED) !== false);\r\n}\r\n<\/pre>\n<p>Aber selbst nach dieser Anpassung kommt die doch sehr ungew\u00f6hnliche URL <i>http:\/\/x<\/i> durch die Validierung durch. Vielleicht sind die Regex-Monster doch nicht so schlecht ;). Ach, bevor ichs vergesse: filter_var ist nicht Multibyte-URL-f\u00e4hig. Die absolut korrekte URL <a href=\"http:\/\/\uc2a4\ud0c0\ubc85\uc2a4\ucf54\ub9ac\uc544.com\">http:\/\/\uc2a4\ud0c0\ubc85\uc2a4\ucf54\ub9ac\uc544.com<\/a> wird rejected:<\/p>\n<pre data-enlighter-language=\"php\" class=\"EnlighterJSRAW\">\r\nvar_dump(filter_var(&quot;http:\/\/\uc2a4\ud0c0\ubc85\uc2a4\ucf54\ub9ac\uc544.com&quot;, FILTER_VALIDATE_URL) !== false); \/\/bool(false)\r\n<\/pre>\n<p>Also: filter_var mit Bedacht einsetzen und an den jeweiligen Kontext anpassen. Abschlie\u00dfend m\u00f6chte ich noch auf diese <a href=\"http:\/\/www.hashbangcode.com\/examples\/filter_var_url_validate\/\">sch\u00f6ne Aufstellung<\/a> an URLs in Abh\u00e4ngigkeit der verschiedenen filter_var &#8211; Flags verweisen.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Prefer this in English? Why URL validation with filter_var might not be a good idea Als uns mit PHP 5.2 die filter_var-Funktion geschenkt wurde, war die Zeit solcher Monster vorbei (hier entliehen): $urlregex = &quot;^(https?|ftp)\\:\\\/\\\/([a-z0-9+!*(),;?&amp;=\\$_.-]+(\\:[a-z0-9+!*(),;?&amp;=\\$_.-]+)?@)?[a-z0-9+\\$_-]+(\\.[a-z0-9+\\$_-]+)*(\\:[0-9]{2,5})?(\\\/([a-z0-9+\\$_-]\\.?)+)*\\\/?(\\?[a-z+&amp;\\$_.-][a-z0-9;:@\/&amp;%=+\\$_.-]*)?(#[a-z_.-][a-z0-9+\\$_.-]*)?\\$&quot;; if (eregi($urlregex, $url)) {echo &quot;good&quot;;} &hellip; <a href=\"https:\/\/d-mueller.de\/blog\/warum-url-validierung-mit-filter_var-keine-gute-idee-ist\/\">Weiterlesen <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4,6,3],"tags":[],"class_list":["post-637","post","type-post","status-publish","format-standard","hentry","category-php","category-security","category-webdev"],"_links":{"self":[{"href":"https:\/\/d-mueller.de\/blog\/wp-json\/wp\/v2\/posts\/637","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/d-mueller.de\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/d-mueller.de\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/d-mueller.de\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/d-mueller.de\/blog\/wp-json\/wp\/v2\/comments?post=637"}],"version-history":[{"count":0,"href":"https:\/\/d-mueller.de\/blog\/wp-json\/wp\/v2\/posts\/637\/revisions"}],"wp:attachment":[{"href":"https:\/\/d-mueller.de\/blog\/wp-json\/wp\/v2\/media?parent=637"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/d-mueller.de\/blog\/wp-json\/wp\/v2\/categories?post=637"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/d-mueller.de\/blog\/wp-json\/wp\/v2\/tags?post=637"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}