PHP und Apache Rewrite von Web-Requests – Ausschluss von Dateien des Typs CSS, JPG, etc. ?

Gestern bin ich in eine klassische Falle im Zusammenhang mit Apache Rewrites gestolpert.

Für ein CMS-Projekt hatte ich in einer ".htacces"-Datei eines Apache-Servers Rewrite-Direktiven für externe HTTP-Requests nach HTML-Dateien hinterlegt. Das CMS arbeitet intern ausschließlich mit PHP-Dateien und Parametern zur Erzeugung von Webseiten. Nach außen hin werden aber reguläre Adressen von HTML-Dateien angeboten. Angeforderte HTML-Seiten müssen daher auf dem Server auf bestimmte Generatorprogramme und zugehörige GET/POST-Parameter abgebildet werden.

Rewriting ist für solche Anforderungen eine Standardlösung (siehe etwa auch das Vorgehen von WordPress):

Der Request wird an eine zentrale PHP-Datei weitergereicht. Diese zerlegt den URL-String der angeforderten HTML-Datei; über Datenbank-Informationen werden dann Parameter für Webseitengeneratoren (PHP-Programme) ermittelt. Die zentrale Datei gibt danach die Kontrolle an die Generatoren ab. Die notwendige Datenbankinformation wird vom CMS bereits während der Anlage und Konfiguration der Webseiten durch den User erzeugt.

Im meinem Fall war ich bzgl. der Rewrite-Anweisung allerdings ein wenig bequem:

Alle (!) Abfragen zu nicht existierenden Dateien wurden zur Behandlung an eine zentrale PHP-Datei "pager.php5" meines CMS verwiesen.

Das funktionierte auch wunderbar - solange nur HTML-Dateien abgefragt wurden, zu denen die Website Links anbot und die im CMS auch mal angelegt worden waren. Traten bzgl. solcher Anfragen Fehler auf oder lies sich aus der Datenbank keine adäquate Info zur angeforderten HTML-Seite ermitteln, wich das PHP-Programm "pager.php5" kontrolliert auf Fehlerroutinen aus.

Nun sah ich bei der Überprüfung des Netzwerkverkehrs bei bestimmten Seiten allerdings, dass es gleich zig-fach zu einem wiederholten Abrufversuch für eine Datei "err_page.php5" in einem bestimmten Bild-Verzeichnis kam; diese PHP-Fehler-Datei existierte dort jedoch gar nicht und war dort auch nie vorgesehen.

Ursachenanalyse

Tatsächlich rufe ich solche PHP-Files zur Behandlung bestimmter Fehler auf, die im CMS im Zuge der Seitengenerierung entstehen können. Allerdings nicht in einem Bildverzeichnis ....

Nach einer Weile fand ich heraus, dass das Problem dennoch durch eine angeforderte, aber auf dem Test-Server nicht vorhandene Bilddatei ausgelöst wurde.

Das war keineswegs so einfach zu erkennen, wie man vielleicht meinen möchte - bei nicht vorhandener Datei übernimmt ja ordnungsgemäß "pager.php5" die Kontrolle - und somit erscheint im Browser nicht zwingend eine Warnung. Eine Warnung auf HTTP-Ebene würde im Einzelfall ja das gezielte Absetzen einer HTTP-Protokoll-Meldung im Verlauf der Situationsbehandlung erfordern. So schlau war ich bei der Konzeption aber nicht gewesen.

Ich dachte deshalb zunächst an einen Fehler in einer PHP-Routine zur automatischen Bildskalierung auf vom CMS-User vorgegebene Größen. Ein Fehler bzw. eine Fehlerbehandlung für nicht existierende Bilddateien in der festgestellten Form lag dort aber nicht vor.

Weitere Tests und ein genauerer Blick in den HTTP-Verkehr zeigten schließlich, dass der "Referrer" der fehlerhaften Datei-Anforderung eine CSS-Datei war! Selbige CSS-Datei existierte und wurde auch ordnungsgemäß gefunden.

Was war das eigentliche Problem?

In der CSS-Datei gab es eine Anweisung der Art

background-image:url(Pfad_zum_(fehlenden)_Bild);

für ein Hintergrundsbild - leider für eines, das auf dem Server nicht existierte.

Der entsprechende Abruf führte dann in Kombination mit der Rewrite-Anweisung zu einer Reaktion nach dem Muster

  • Abruf nicht existierende Datei aus CSS-Anweisung
  • => pager.php5
  • => Auslösen einer "Fehlerbehandlung" durch eine err_page.php5, die aus Gründen mangelnder Voraussicht im Bildverzeichnis erwartet wurde, dort aber nicht existierte
  • => Abruf einer nicht existierenden PHP-Datei
  • => pager.php5 =>. Erneuter Verweis auf Fehlerbehandlung durch eine nicht existierende "err_page.php5"
  • => Abruf einer nicht existierenden PHP-Datei
  • etc., etc.

Apache versucht es dann mehrfach und bricht schließlich ab.

Lösungsansatz 1: Klammere Dateien bestimmter Typen (jpg, png, css, js, ...) aus der Rewrite-Anweisung aus

Das Erlebnis brachte mich dazu, genauer darüber nachzudenken, wie ich eigentlich mit Rewrites normaler Dateien der Typen ".jpg, .gif, .png, .swf, .css, .js" etc. umgehen sollte, für die eine Ersetzung durch PHP-Programme gar nicht vorgesehen ist.

Eine Lösungsvariante ist das Ausklammern dieser Dateitypen von der Rewrite-Anweisung in der ".htaccess"-Datei. Das sieht im einfachsten Fall etwa so aus:

Options +FollowSymLinks
RewriteEngine On
RewriteBase /
RewriteRule ^php/hmenu/pager.php5(.*)$ - [L] 

RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule \.(js|css|ico|gif|jpg|png|swf|ttf|eot)$ - [NC,L]

RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ my_Rw_Php_Path/pager.php5?adr=$1 [PT]   

 
Hier werden zwei "Condition/Rewrite"-Sequenzen eingesetzt, da ohne besondere Tricks (Skip-Direktiven) zu einem Block aus Condition-Anweisungen nur genau eine Rewrite-Anweisung gehören sollte. "NC" sorgt für eine Nichtbeachtung von Groß-/Klein-Schreibung. "L" beendet die Rewrite-Analyse. "my_Rw_Php_Path" steht für einen Pfad zu einem Serververzeichnis, das die zentralen Programme zur Rewrite-Behandlung beherbergt.

Wird nun eine nicht vorhandene Datei der genannten Typen von einem Web-Client angefordert, wird diese Anforderung durchgereicht und vom Apache-Server mit HTTP-Fehlern der Art "404 Not Found" quittiert. Das reicht in Testphasen zur Prüfung der Lauffähigkeit einer CMS-basierten Website normalerweise aus.

Lösung 2: Behandle fehlende Dateien bestimmter, ausgewählter Typen als Sonderfälle in einer speziellen zentralen PHP-Datei

Eine kontrollierte Reaktion des Systems auf nicht vorhandene Dateien bestimmter Typen jenseits von HTML-Dateien lässt sich natürlich auch in einer weiteren zentralen PHP-Datei (etwa "missing.php5") vorsehen, auf die eine gesonderte Rewrite-Anweisung verweist. Beispielsweise könnte man den mittleren Teil der obigen ".htaccess" in diesem Sinne ersetzen durch:

RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)\.(js|css|ico|gif|jpg|png|swf|ttf|eot)$ my_Rw_Php_Path/missing.php5?missadr=$1\.$2 [NC,L]

Bzgl. der Problembehandlung in der "missing.php5" muss man sich aber genau überlegen, für welche Dateien man tatsächlich eine offene und für den User auch erkennbare Fehlermeldung vorsehen will. Ein fehlendes Bild z.B. ist meist nicht überlebenskritisch.

Ich tendiere im Moment dazu, gezielt Meldungen in eine eigene Log-Datei auf dem Server zu schreiben, die man sowohl im Test- als auch Produktivbetrieb regelmäßig auswertet. Ein Minimal-PHP-Skript "missing.php5" könnte für diesen Zweck dann in etwa so aussehen:

<?php
$missadr = 'unknown'; 
if (isset($_GET['missadr']) ) {
	$missadr = $_GET['missadr']; 
}

$fh = fopen("missing.log", 'a+'); 
$out_str = "\r\n" . date('d.m.Y :: H.I.s') . " :: A requested file (" .$missadr . ") is missing"; 
fputs($fh, $out_str); 
fclose($fh); 

header($_SERVER["SERVER_PROTOCOL"]." 404 Not Found");
exit;
?> 

 
Natürlich wäre das in dieser Einfachheit fahrlässig; der Inhalt von $_GET['missadr'] ist im produktiven Einsatz zu prüfen und ggf. zu bereinigen, um den Inhalt als Teil eines Angriffsvektors auszuschalten. In diesem Artikel geht es aber nur um einen ersten Ansatz.

Der Header-Output ist wichtig; durch ihn kann man z.B. auch in Browser-Tools (bei FF etwa in der Web-Konsole) erkennen, dass ein Fehler vorliegt und eine Datei tatsächlich nicht vorhanden ist.

Ein typischer Output in der Datei "missing.log" hat nach zwei Aufrufen bestimmter Webseite, für die indirekt eine Bilddatei "hg_dxm_7.jpg" angefordert wird, dann ggf. folgenden Inhalt:

27.06.2017 :: 12:0:38 :: A requested file (image/hg_dxm_7.jpg) is missing
27.06.2017 :: 12:0:39 :: A requested file (image/hg_dxm_7.jpg) is missing
27.06.2017 :: 12:0:02 :: A requested file (image/hg_dxm_7.jpg) is missing
27.06.2017 :: 12:0:02 :: A requested file (image/hg_dxm_7.jpg) is missing

Man erkennt hier an der Zeitangabe, dass die fehlende Datei pro Seitenaufruf gleich zweimal angefordert wird; in meinem Fall aus einer CSS-Datei heraus, aber auch direkt über ein HTML-Tag.

Fazit

Nicht nur in einem CMS will man ggf. Requests nach HTML-Dateien durch den gezielten Einsatz von PHP-Webgeneratoren beantworten. Die Nutzer (und auch Suchmaschinen) glauben, reguläre HTML-Dateien abzurufen. In Wirklichkeit sind die Dateien nicht vorhanden; Apache Rewrites sorgen vielmehr für die Erzeugung von HTML-Seiten durch PHP-Programme.

Zu einfach gehaltene Rewrite-Anweisungen für nicht vorhandene Dateien können dabei allerdings schnell zu schwer zu durchschauenden bis rekursiven Fehlern führen. Fordern HTTP-Requests evtl. nicht vorhandene Dateien eines bestimmten Typs an, für die eine gezielte Ersetzung gar nicht vorgesehen ist, so hängt es allein von der Voraussicht der Entwickler ab, was im Detail über Ersetzungen passiert. Es empfiehlt sich deshalb, solche Datei-Anforderungen

  • entweder von vornherein aus der Rewrite-Behandlung auszuschließen
  • oder sie aber einer gezielten Sonderbehandlung durch eine eigene PHP-Datei zuzuführen. Dabei sollten angemessene HTTP-Antwortcodes erzeugt werden.

Make PEAR IT-templates work on a PHP 7 server

Some of my older PHP programs use PEAR-IT-templates. At the core of this PHP template engine is the "IT.php" file defining a class "HTML_Template_IT" via an old fashioned "contructor" function of the same name (stemming from old PHP4's way to define classes).

Yesterday, I needed to make some IT-templates work on an Apache2 server with PHP 7.1. This was easier than expected: Setting PHP to display all warnings and errors leads directly to the right code statements which must be corrected:

Correcting the call of preg_replace

In a first step we can ignore the warning about the old fashioned class declaration, which will sooner or later disappear from PHP. What absolutely needs to be changed, however, is an error that arises due to a special usage of the function preg_replace():

Deprecated: preg_replace(): The /e modifier is deprecated, use preg_replace_callback

In my PEAR IT-installation the problem is located around line 956 of the file "IT.php". Fortunately, some other clever people have already provided a method, how to replace the critical statement:

        // Replace the following lines by the subsequent statements
        /*
        return preg_replace(
            "#<!-- INCLUDE (.*) -->#ime", "\$this->getFile('\\1')", $content
        );
        */
	return preg_replace_callback(
	   "#<!-- INCLUDE (.*) -->#im",
	    array($this, 'getFile'),
	    $content
	);

See the contribution of A. Preuss in the following stack overflow post:
http://stackoverflow.com/questions/22597102/preg-replace-the-e-modifier-is-deprecated-error

Worked perfectly for me. Many thanks to A. Preuss!

Changing to a __construct() function

To get rid of the warning regarding the old fashioned class constructor function just replace the function definition around line 376 by

    // function HTML_Template_IT($root = "", $options=null) {
    function __construct($root = "", $options=null) {

With both the replacements described above I could use my old IT-template dependent PHP programs successfully on a PHP 7.1 system! This, hopefully, gives me enough time to replace the PEAR IT engine by the Smarty engine wherever reasonable :-).

PHP and web application security – bad statistics and wrong conclusions

Sometimes I have discussions with developers of a company working mainly with Java. As I myself sometimes do development work with PHP I am regarded more or less as a freak in this community. Typical arguments evolve along the lines:

"PHP does not enforce well structured OO code, no 4-layer-architecture built in, problems with scalability", etc.... I do not take these points too seriously. I have seen very badly structured Java OO code, one can with proper techniques implement web services in a kind of logical 3rd layer on special servers and Facebook proves PHP scalability (with some effort), etc...

What is much more interesting for me these days is the question how security aspects fit into the use of different programming languages. And here comes the bad news - at least according to statistics published recently by the online magazine Hacker News - see
http://thehackernews.com/2015/12/programming-language-security.html

The statistics on OWASP 10 vulnerability types of the investigated PHP code looks extremely bad there - compared to the investigated Java code examples. I admit that this is an interesting result for hackers and that it is somewhat depressing for security aware PHP developers.

However, is this the bad statistics the fault of the programming language?

I doubt it - despite the fact that the named article recommends to "Choose Your Scripting Language Wisely". I would rather recommend: Educate your PHP developers properly and regularly, implement a proper quality assurance with special security check steps based on vulnerability scanning tools and invest in regular code reviews. Why?

The investigation revealed especially large deviations in the fields of XSS, SQL-injection, command injection (major elements on the OWASP 10 list). The countermeasures against the named attack vectors for PHP are all described in literature and very well known (see e.g. the books "PHP Sicherheit" of C.Kunz, S.Esser or "Pro PHP Security" of Snyder, Myer, Southwell). One of the primary key elements of securing PHP applications against attacks of the named types is a thorough inspection, analysis and correction treatment of submitted GET/POST-parameters (and avoidance of string parameters wherever possible). Never trust any input and escape all output! Define exceptions wisely and rewrite sensitive string elements according to your rules. Check whether input really comes from the right origin - e.g. your own domain, etc., etc.

Whether all relevant security measures are implemented in the PHP code of a web application has therefore more to do with the mentality, technical ability, the knowledge and on the negative side with the laziness of the programmer than with the programming language itself. As at least the technical capability is a matter of education, I conclude:

Tests regarding type, value range, length and of course tests of the contents of received string variables and e.g. image source references plus sanitizing/elimination/deactivation of problematic contents as well as the proper use of respective available library functions for such tests should be part of regular PHP training programs. In addition the use of web application scanning tools like OWASP's ZAP scanner or the Burp Suite Pro (if you have money to afford the latter) should be trained. Such tools should become part of the QA chain. As well as educated penetration testers with the perspective of the attacker .... The money a SW-company invests for such educational measures is well invested money.
See for the significant impact of education e.g.:
https://seclab.stanford.edu/websec/scannerPaper.pdf

I would regard the statistical results discussed in the "Hacker News" article much more conclusive if we were provided additional information about the type of applications analyzed and also the size and type of the companies behind the application development. And whether and what type of QA efforts were used. This would give a much better indication of why the Java code showed more quality regarding the prevention of OWASP 10 attacks. One reason could e.g. be that Java applications very often are developed for enterprise purposes - and bigger companies typically invest more time and effort into QA ...

So, another valid interpretation of the presented statistics could be that the QA for typical PHP web application SW is on average worse than for Java SW. I admit that such a finding would also be very interesting - but it does not prove that one cannot write secure Web applications with PHP or that the production of secure code is for whatever reasons easier with Java.

In addition the presented number of bugs per MB itself is questionable: if you only look at 3 bad PHP examples and 1 good Java example you may get the same type statistics - but it would be totally meaningless. The distribution of PHP-, Java-, JS-code etc. among the statistical sample is, however, nowhere discussed in the named article - neither in number of applications nor in MB percentages.

Therefore: Without further information the implied conclusion that already the proper choice of a web scripting language would help to improve security of web applications appears is misleading.

To improve the mood of PHP developers having read the article in "Hacker News": Have a look at the results of a similar investigation presented at this link
http://info.whitehatsec.com/rs/whitehatsecurity/images/statsreport2014-20140410.pdf

See also:
https://blog.whitehatsec.com/a-security-experts-thoughts-on-whitehat-securitys-2014-web-stats-report/
https://www.scriptrock.com/blog/which-web-programming-language-is-the-most-secure
https://threatpost.com/security-begins-with-choice-of-programming-language/105441/

It may help, really !

[But keep in mind: Only trust statistics you have manipulated yourself.]