{"id":332,"date":"2009-07-09T21:11:46","date_gmt":"2009-07-09T19:11:46","guid":{"rendered":"http:\/\/blog.benny-baumann.de\/?p=332"},"modified":"2012-09-12T00:26:00","modified_gmt":"2012-09-11T22:26:00","slug":"der-evil-modifier","status":"publish","type":"post","link":"http:\/\/blog.benny-baumann.de\/?p=332","title":{"rendered":"Der evil-Modifier"},"content":{"rendered":"<p>Eine der bemerkenswerten Bibliotheken, die PHP zur Nutzung anbietet &#8211; sofern sie mal nicht wie so oft \u00fcberfordert ist &#8211; ist die PCRE-Bibliothek (Perl Compatible Regular Expressions), mit der sich auf einfache Weise (naja, wenn man die Syntax einmal verstanden hat) umfangreichste Dinge vollf\u00fchren lassen. Eine h\u00e4ufige Anwendung, die man hierbei regelm\u00e4\u00dfig als Betrachter fremder Bibliotheken zu Gesicht bekommt, ist hierbei Quelltext, der elegant Parsing von Eingabedaten vornimmt, Sicherheitspr\u00fcfungen ausf\u00fchrt oder auf andere Art einen Eingabestring verarbeitet. Mit ein wenig Magic drumherum l\u00e4sst sich so <a href=\"http:\/\/qbnz.com\/highlighter\/\">ein ziemlich schneller Syntax-Highlighter<\/a> schreiben, der mit einem Schlag ganze Gruppen von Schl\u00fcsselworten highlighted und dabei abh\u00e4ngig von den gefundenen Schl\u00fcsselworten z.B. Links auf deren Dokumentation fertigt.<\/p>\n<p>Doch wie so h\u00e4ufig bei PHP gibt es eine Reihe von Fallstricken. Einer dieser liegt bei eben dieser genannten Bibliothek. Diese bietet f\u00fcr umfangreiche, nicht statisch ausf\u00fchrbare Ersetzungen mit <a href=\"http:\/\/php.net\/preg_replace\">preg_replace<\/a> einen speziellen Modus: Den \/e-Modifier, der auf Grund seiner Arbeitsweise auch gern Evil-Modifier genannt werden darf. Aber dazu sollten wir erst einmal kl\u00e4ren, was diese Funktion tut.<!--more--><\/p>\n<p>Die Funktion preg_replace nimmt ein Suchmuster, sowie ein Ersetzungstemplate, gefolgt von dem zu durchsuchenden String. F\u00fcr jedes Vorkommen des Suchmusters wird nun der gefundene Text (und seine Sub-Matches) entsprechend dem Template eingesetzt. An der Fundstelle wird nun der durch das Templating entstandene String eingesetzt. Dies ist an sich ungef\u00e4hrlich, da zu keiner Zeit Benutzereingaben als Quelltext interpretiert werden.<\/p>\n<p>Naja, nicht ganz: Es gibt wie gesagt oben genannten Evil Modifier. Dieser sorgt daf\u00fcr, dass das Ersetzungs-Template mit (weitestgehend) unescapeten Werten des gefundenen Matches gef\u00fcllt wird. Der hierbei entstehende String wird anschlie\u00dfend mit eval ausgef\u00fchrt. Gelingt es also nun, die entstehende Ersetzung so zu beeinflussen, dass man seinen Match in den auszuf\u00fchrenden String nicht allein als Teil eines Strings, sondern als laufenden Code einbringen kann, so kann man PHP an dieser Stelle beliebigen Quelltext unterjubeln.<\/p>\n<p>Dies ist insbesondere deshalb problematisch, da in \u00e4lteren PHP-Versionen das Escaping der in den replacement-Parameter eingesetzten Strings nicht sauber funktionierte. Zudem hat PHP einen regul\u00e4ren Ausdruck auch dann als eval behandelt, wenn man durch einen ung\u00fcnstigen Zufall irgendwo eine Subexpression wie (?e:blablubb) einschmuggeln konnte; selbst wenn auf oberster Ebene kein <a href=\"http:\/\/php.net\/eval\">eval<\/a> vorgesehen war.<\/p>\n<p>Warum erz\u00e4hl ich das? Weil, trotz dessen dass dieses Problem inzwischen seit Jahren bekannt ist, es immer noch genug Software &#8211; gerade auch stark verbreitete Projekte &#8211; gibt, die preg_replace mit dieser gef\u00e4hrlichen Zutat nutzen, auch wenn die meisten Vorkommen als sicher anzusehen sind. Hierzu geh\u00f6ren z.B. phpBB (sowohl 2 als auch 3), SquirrelMail und bis vor kurzem WordPress (fixed in 2.8).<\/p>\n<p>Da der Evil-Modifier eine Code Injection zul\u00e4sst, gab es verschiedene Ans\u00e4tze, von denen der <a href=\"http:\/\/www.hardened-php.net\/suhosin\/\">Hardening-Patch und die Suhosin-Extension f\u00fcr PHP<\/a> zu den bekanntesten z\u00e4hlen d\u00fcrften. Diese erm\u00f6glichem es einem Server-Betreiber diesen Modifier explizit zu deaktivieren und somit effektiv f\u00fcr die Deaktivierung jeglicher eval-Befehle (direkt oder indirekt ausgef\u00fchrt) zu sorgen. Und genau hier liegt das Problem: M\u00f6chte man als Server-Betreiber eine &#8222;No-Eval, since it&#8217;s Evil&#8220;-Policy erzwingen, laufen viele Anwendungen nur mit entsprechenden Modifikationen. Diese upstream einzupflegen w\u00e4re Aufgabe der zust\u00e4ndigen Entwickler; doch trifft man hier gerne auf Unverst\u00e4ndnis, warum man etwas so Praktisches, wie den Evil-Modifier, entfernt wissen m\u00f6chte. Und das obwohl es gleichwertige, wenn nicht sogar bessere, Alternativen gibt: <a href=\"http:\/\/php.net\/preg_replace_callback\">preg_replace_callback<\/a> w\u00e4re eine davon. Aber soviel m\u00f6chten dann viele doch nicht wahrhaben.<\/p>\n<p>Die Nutzung von preg_replace_callback mag auf den ersten Blick durchaus etwas r\u00fcckschrittlich wirken, ist es aber bei genauerer Betrachtung \u00fcberhaupt nicht. Nehmen wir einfach ein kleines Beispiel her. M\u00f6chte man einen UTF-8-Dekoder schreiben, der jegliche UTF-8-Zeichen in HTML-Entities konvertiert, so bietet sich preg_replace auf Grund seiner Vielseitigkeit an &#8211; ab PHP6 gibt es hier zwar auch noch andere M\u00f6glichkeiten, aber die lassen wir vorerst beiseite.<\/p>\n<p>Nun findet man also in einem gar nicht so unbekannten Webmail-Client eine eben solche Funktion, die auf den ersten Blick auch durchaus \u00fcbersichtlich erscheint. Aber gut: Das wollen wir uns einmal genauer anschauen:<\/p>\n<pre lang=\"php\" escaped=\"true\">\/**\r\n * Decode utf-8 strings\r\n * @param string $string Encoded string\r\n * @return string Decoded string\r\n *\/\r\nfunction charset_decode_utf_8 ($string) {\r\n    \/\/ Some generic checks ...\r\n    \/\/ ...\r\n\r\n    \/\/ decode six byte unicode characters\r\n    $string = preg_replace(\"\/([\\374-\\375])([\\200-\\277])([\\200-\\277])([\\200-\\277])([\\200-\\277])([\\200-\\277])\/e\",\r\n       \"'&amp;#'.((ord('\\\\1')-252)*1073741824+(ord('\\\\2')-200)*16777216+(ord('\\\\3')-200)*262144+(ord('\\\\4')-128)*4096+(ord('\\\\5')-128)*64+(ord('\\\\6')-128)).';'\",\r\n       $string);\r\n\r\n    \/\/ decode five byte unicode characters\r\n    $string = preg_replace(\"\/([\\370-\\373])([\\200-\\277])([\\200-\\277])([\\200-\\277])([\\200-\\277])\/e\",\r\n        \"'&amp;#'.((ord('\\\\1')-248)*16777216+(ord('\\\\2')-200)*262144+(ord('\\\\3')-128)*4096+(ord('\\\\4')-128)*64+(ord('\\\\5')-128)).';'\",\r\n        $string);\r\n\r\n    \/\/ decode four byte unicode characters\r\n    $string = preg_replace(\"\/([\\360-\\367])([\\200-\\277])([\\200-\\277])([\\200-\\277])\/e\",\r\n        \"'&amp;#'.((ord('\\\\1')-240)*262144+(ord('\\\\2')-128)*4096+(ord('\\\\3')-128)*64+(ord('\\\\4')-128)).';'\",\r\n        $string);\r\n\r\n    \/\/ decode three byte unicode characters\r\n    $string = preg_replace(\"\/([\\340-\\357])([\\200-\\277])([\\200-\\277])\/e\",\r\n        \"'&amp;#'.((ord('\\\\1')-224)*4096+(ord('\\\\2')-128)*64+(ord('\\\\3')-128)).';'\",\r\n       \u00a0$string);\r\n\r\n    \/\/ decode two byte unicode characters\r\n    $string = preg_replace(\"\/([\\300-\\337])([\\200-\\277])\/e\",\r\n        \"'&amp;#'.((ord('\\\\1')-192)*64+(ord('\\\\2')-128)).';'\",\r\n        $string);\r\n\r\n    \/\/ remove broken unicode\r\n    $string = preg_replace(\"\/[\\200-\\237]|\\240|[\\241-\\377]\/\",'?',$string);\r\n\r\n    return $string;\r\n}<\/pre>\n<p>Was ist hieran nun so b\u00f6se? Im Grunde 2 Dinge:<\/p>\n<ol>\n<li>Es wird ein eval ausgef\u00fchrt, bei dem bin\u00e4re Daten in den Ausf\u00fchrungskontext des Skriptes eingebunden werden. Dies ist schon per Definition schlecht; nicht zuletzt, weil dies im Falle von PHP sogar ohne ausreichendes Escaping geschieht (neuere Versionen escapen zwar einige Zeichen, das reicht aber u.U. nicht aus).<\/li>\n<li>Der auszuf\u00fchrende Code entsteht erst zur Laufzeit. Somit kann keine Vorab-Optimierung oder Syntax-Pr\u00fcfung vorgenommen werden, bevor nicht der endg\u00fcltig auszuf\u00fchrende Source durch eine Ersetzung aufgebaut wurde. Dieser Schritt kann nicht gecacht werden.<\/li>\n<\/ol>\n<p>W\u00f6llte man es besser machen, hei\u00dft die Antwort somit: Nutzung eines Callbacks. Naiv kann dies wie folgt realisiert werden:<\/p>\n<pre lang=\"php\" escaped=\"true\">function charset_decode_utf_8_6byte($match) {\r\n    \/\/ decode six byte unicode characters\r\n    return '&amp;#'.((ord($match[1])-252)*1073741824+(ord($match[2])-200)*16777216+(ord($match[3])-200)*262144+(ord($match[4])-128)*4096+(ord($match[5])-128)*64+(ord($match[6])-128)).';';\r\n}\r\n\r\nfunction charset_decode_utf_8_5byte($match) {\r\n    \/\/ decode five byte unicode characters\r\n    return '&amp;#'.((ord($match[1])-248)*16777216+(ord($match[2])-200)*262144+(ord($match[3])-128)*4096+(ord($match[4])-128)*64+(ord($match[5])-128)).';';\r\n}\r\n\r\nfunction charset_decode_utf_8_4byte($match) {\r\n    \/\/ decode four byte unicode characters\r\n    return '&amp;#'.((ord($match[1])-240)*262144+(ord($match[2])-128)*4096+(ord($match[3])-128)*64+(ord($match[4])-128)).';';\r\n}\r\n\r\nfunction charset_decode_utf_8_3byte($match) {\r\n    \/\/ decode three byte unicode characters\r\n    return '&amp;#'.((ord($match[1])-224)*4096+(ord($match[2])-128)*64+(ord($match[3])-128)).';';\r\n}\r\n\r\nfunction charset_decode_utf_8_2byte($match) {\r\n    \/\/ decode two byte unicode characters\r\n    return '&amp;#'.((ord($match[1])-192)*64+(ord($match[2])-128)).';';\r\n}\r\n\r\n\/**\r\n * Decode utf-8 strings\r\n * @param string $string Encoded string\r\n * @return string Decoded string\r\n *\/\r\nfunction charset_decode_utf_8 ($string) {\r\n    \/\/Some generic checks ...\r\n    \/\/...\r\n\r\n    \/\/ decode six byte unicode characters\r\n    $string = preg_replace_callback(\"\/([\\374-\\375])([\\200-\\277])([\\200-\\277])([\\200-\\277])([\\200-\\277])([\\200-\\277])\/\",\r\n        'charset_decode_utf_8_6byte', $string);\r\n\r\n    \/\/ decode five byte unicode characters\r\n    $string = preg_replace_callback(\"\/([\\370-\\373])([\\200-\\277])([\\200-\\277])([\\200-\\277])([\\200-\\277])\/\",\r\n        'charset_decode_utf_8_5byte', $string);\r\n\r\n    \/\/ decode four byte unicode characters\r\n    $string = preg_replace_callback(\"\/([\\360-\\367])([\\200-\\277])([\\200-\\277])([\\200-\\277])\/\",\r\n        'charset_decode_utf_8_4byte', $string);\r\n\r\n    \/\/ decode three byte unicode characters\r\n    $string = preg_replace_callback(\"\/([\\340-\\357])([\\200-\\277])([\\200-\\277])\/\",\r\n        'charset_decode_utf_8_3byte', $string);\r\n\r\n    \/\/ decode two byte unicode characters\r\n    $string = preg_replace_callback(\"\/([\\300-\\337])([\\200-\\277])\/\",\r\n        'charset_decode_utf_8_2byte', $string);\r\n\r\n    \/\/ remove broken unicode\r\n    $string = preg_replace(\"\/[\\200-\\237]|\\240|[\\241-\\377]\/\",'?',$string);\r\n\r\n    return $string;\r\n}<\/pre>\n<p>Wer jetzt genau hinschaut, findet sogar noch mehr:<\/p>\n<pre lang=\"php\" escaped=\"true\">function charset_decode_utf_8_callback($match) {\r\n    $m = $match[0];\r\n\r\n    switch(strlen($m)) {\r\n    case 6:\r\n        \/\/ decode six byte unicode characters\r\n        return '&amp;#'.((ord($m[0])-252)*1073741824+(ord($m[1])-200)*16777216+(ord($m[2])-200)*262144+(ord($m[3])-128)*4096+(ord($m[4])-128)*64+(ord($m[5])-128)).';';\r\n    case 5:\r\n        \/\/ decode five byte unicode characters\r\n        return '&amp;#'.((ord($m[0])-248)*16777216+(ord($m[1])-200)*262144+(ord($m[2])-128)*4096+(ord($m[3])-128)*64+(ord($m[4])-128)).';';\r\n    case 4:\r\n        \/\/ decode four byte unicode characters\r\n        return '&amp;#'.((ord($m[0])-240)*262144+(ord($m[1])-128)*4096+(ord($m[2])-128)*64+(ord($m[3])-128)).';';\r\n    case 3:\r\n        \/\/ decode three byte unicode characters\r\n        return '&amp;#'.((ord($m[0])-224)*4096+(ord($m[1])-128)*64+(ord($m[2])-128)).';';\r\n    case 2:\r\n        \/\/ decode two byte unicode characters\r\n        return '&amp;#'.((ord($m[0])-192)*64+(ord($m[1])-128)).';';\r\n    default:\r\n        return $m;\r\n    }\r\n}\r\n\r\n\/**\r\n * Decode utf-8 strings\r\n * @param string $string Encoded string\r\n * @return string Decoded string\r\n *\/\r\nfunction charset_decode_utf_8 ($string) {\r\n    \/\/Some generic checks ...\r\n    \/\/...\r\n\r\n    \/\/ decode 2-6 byte unicode characters\r\n    $string = preg_replace_callback(\"\/[\\374-\\375][\\200-\\277][\\200-\\277][\\200-\\277][\\200-\\277][\\200-\\277]|\".\r\n        \"[\\370-\\373][\\200-\\277][\\200-\\277][\\200-\\277][\\200-\\277]|\".\r\n        \"[\\360-\\367][\\200-\\277][\\200-\\277][\\200-\\277]|\".\r\n        \"[\\340-\\357][\\200-\\277][\\200-\\277]|\".\r\n        \"[\\300-\\337][\\200-\\277]\/\",\r\n        'charset_decode_utf_8_callback', $string);\r\n\r\n    \/\/ remove broken unicode\r\n    $string = preg_replace(\"\/[\\200-\\237]|\\240|[\\241-\\377]\/\",'?',$string);\r\n\r\n    return $string;\r\n}<\/pre>\n<p>Und siehe da: Wir haben aus 5 Ersetzungen mal eben eine gemacht und dabei dem Parser sogar noch die Chance gegeben, unseren Quelltext vorab zu optimieren. Besser noch: Es gibt keine Stelle mehr, an der Nutzerdaten Teil des auszuf\u00fchrenden Quelltextes sind!<\/p>\n<p>Wenn das mal nichts ist! Und das Gute daran: Selbst in unbekanntem Source l\u00e4sst sich die f\u00fcr diese Verbesserung notwendige \u00c4nderung binnen weniger Minuten mit wenigen Handgriffen durchf\u00fchren und verbessert dabei sogar nebenbei sogar den Schutz der eigenen PHP-Anwendung gegen Code Injections.<\/p>\n<p>Die Anpassung einer bestehenden Software an die Richtlinie zur Vermeidung von preg_replace mit \/e mag f\u00fcr gewachsene Software vielleicht schmerzhaft sein, aber neben GeSHi, Joomla und WordPress sollten wesentlich mehr im gro\u00dfen Stil eingesetzte Anwendungen daf\u00fcr sorgen, auch auf stark eingeschr\u00e4nkten Systemen zurecht zu kommen. Die Entwickler von oben erw\u00e4hntem Webmail-Client wollen hier zumindest nachbessern; auch wenn noch kein konkreter Zeitpunkt genannt wurde &#8230;<\/p>\n<p class=\"wp-flattr-button\"><a href=\"http:\/\/blog.benny-baumann.de\/?flattrss_redirect&amp;id=332&amp;md5=8e88e75cf7321161f60ec04f6382ae50\" title=\"Flattr\" target=\"_blank\"><img src=\"http:\/\/blog.benny-baumann.de\/wp-content\/plugins\/flattr\/img\/flattr-badge-large.png\" srcset=\"http:\/\/blog.benny-baumann.de\/wp-content\/plugins\/flattr\/img\/flattr-badge-large.png\" alt=\"Flattr this!\"\/><\/a><\/p>","protected":false},"excerpt":{"rendered":"<p>Eine der bemerkenswerten Bibliotheken, die PHP zur Nutzung anbietet &#8211; sofern sie mal nicht wie so oft \u00fcberfordert ist &#8211; ist die PCRE-Bibliothek (Perl Compatible Regular Expressions), mit der sich auf einfache Weise (naja, wenn man die Syntax einmal verstanden hat) umfangreichste Dinge vollf\u00fchren lassen. Eine h\u00e4ufige Anwendung, die man hierbei regelm\u00e4\u00dfig als Betrachter fremder [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"ngg_post_thumbnail":0,"footnotes":""},"categories":[4],"tags":[345,215,21,275,216],"class_list":["post-332","post","type-post","status-publish","format-standard","hentry","category-server","tag-geshi","tag-pcre","tag-php","tag-squirrelmail","tag-suhosin"],"_links":{"self":[{"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=\/wp\/v2\/posts\/332","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=332"}],"version-history":[{"count":7,"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=\/wp\/v2\/posts\/332\/revisions"}],"predecessor-version":[{"id":1327,"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=\/wp\/v2\/posts\/332\/revisions\/1327"}],"wp:attachment":[{"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=332"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=332"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=332"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}