phpBuddy

PHP und Sex

Was PHP und Sex gemeinsam haben? Beides sollte man niemals ohne ausreichenden Schutz praktizieren! Diese Rubrik gibt wichtige Tipps, wie man PHP gegen Attacken absichert und wie man sichere Scripts programmiert. Pflichtlektüre!

Sie sind hier: Startseite Fehlerbehandlung
Fehlermeldungen und Fehlerbehandlung
Beitragsseiten
Fehlermeldungen und Fehlerbehandlung
Error Reporting
Display Errors
Fehler protokollieren
Eigene Fehlerbehandlung
Exceptions
Fazit und Download
Alle Seiten

Fehlermeldungen, Warnhinweise, Fehlerbehandlung, und mehr

Um eine robuste Webanwendung zu programmieren, die möglichst wenig Angriffsfläche für "Hacker" bietet, muss man wissen wie Angreifer vorgehen und wie die Angriffe ablaufen. Man kann Angriffe in zwei Gruppen teilen:

1. Gezielte Angriffe mithilfe eines Exploit
Solche Angriffe finden meistens auf Webseiten statt, die eine bestimmte Fremd-Software, wie z.B. ein CMS, Forum, Newssystem, Gästebuch, usw., einsetzen. Diese Angriffe werden in der Regel von sogenannten Script Kiddies durchgeführt, die sich kaum selbst die Schuhe binden können, aber zufällig über einen Exploit gestolpert sind und sich nun für die besten Hacker der Welt halten, obwohl dies absolut nichts mit hacken zu tun hat. Solche Exploits sind detaillierte Anleitungen zum Ausnutzen einer Schwachstelle, die auf einschlägigen Seiten frei im Internet verfügbar sind. Auch bekannte Sicherheitsportal, wie etwa heise Security, berichten regelmäßig über Schwachstellen in bekannter Software und verlinken nicht selten Seiten die Exploits enthalten.
Gegen diese Art von Angriffe hilft es in den meisten Fällen sofort verfügbare Sicherheitsupdates für die eingesetzte Software einzuspielen.

2. Systematische Suche nach Schwachstellen
Diese Art Angriffe laufen in Etappen ab. Zuerst wird durch wildes herumprobieren das System veranlasst Informationen preiszugeben, wo man einen Hebel (Angriffsversuch) ansetzen kann, um anschliessend gezielt die potentielle Schwachstelle zu analysieren und zu attackieren.
Der Erste Schritt zielt also darauf ab Fehlermeldungen von unserer Webseite zu provozieren, die Aufschluß über Dateinamen, Pfade, Struktur des Dateisystem und Datenbank, usw. usw. liefert. Dies geschieht durch manipulierte Seitenaufrufe, indem scheinbar sinnfreie Parameter übergeben werden oder Sonderzeichen in die URL eingefügt werden. Unsere Webanwendung, bzw. PHP reagiert höflich und freundlich und teilt dem User mit, dass z.B. Datei /home/benutzer/pfad/zur/datei nicht eingebunden werden konnte. Dabei ahnt PHP (und wir!) nicht, dass es damit viel zu viele Informationen an Angreifer preisgibt.

Je mehr Info ein Angreifer über unser System hat, umso eher findet er Ansatzpunkte für seinen Angriff. Informationen die für uns zu Entwicklungszwecke gut und hilfreich sind, haben aber nichts auf einem produktiven, sprich öffentlich zugängliches, System zu suchen. Allgemine Fehlermeldungen und Hinweise auf die Sitestruktur erscheinen uns harmlos, liefern aber Angreifer potentiell gefährliche Informationen, die deren Arbeit erleichtert.
Und genau darum dreht sich dieser Artikel, der der Erste einer ganzen Reihe von Artikeln ist, wie man sich gegen unterschiedliche Angriffe wehren kann, bzw. diese verhindert, ehe sie stattfinden.

Je verschwiegener unser System ist, desto besser. Auf Benutzerfreundlichkeit muss dabei nicht verzichtet werden. Allerdings diktieren wir, und nicht PHP, welche Informationen bei Fehlermeldungen preisgegeben werden! So können wir Sitebesucher über eventuell auftretende Fehler informieren, während keine Informationen heraus gegeben werden, die gegen uns verwendet werden könnten.


PHP Error Reporting / Fehlermeldungen

Das Error Reporting, also die Meldungen die PHP bei Fehlern ausgibt, ist die Wurzel allen Übels. Okay, das klingt etwas hart und stimmt auch nur bedingt. Die vielseitigen und oftmals sehr detaillierten Fehlermeldung sind ein unerlässlicher Helfer bei der Entwicklung einer Webseite, haben aber auf einer Webseite im produktiven Einsatz nichts mehr verloren. Die PHP Funktion, mit der dieses Ausgabeverhalten gesteuert werden kann nennt sich:

Als Parameter kann man entweder den numerischen Wert des Level angeben oder die entsprechende PHP-interne Konstante für das gewünschte Level. Momentan gibt es insgesamt 18 Level (Stand PHP 5.3.1), wovon einige Level andere beinhalten. Eine komplette Übersicht findet man wie immer in der Dokumentation zu error_reporting.
Zwei Level sind dort nicht aufgelistet und das sind:

// Error Reporting unterdrücken
error_reporting( 0 );
// Error Reporting auf höchster Ebene anzeigen
error_reporting( -1 );

Der Wert -1 stellt das höchste Level der Fehlermeldungen dar, dass alle anderen Level beinhaltet - mehr Fehlermeldung geht also nicht.


Fazit für Error Reporting

Während der Entwicklung sollte das Error Reporting eingeschaltet sein und zwar so, dass jegliche Art von Meldung angezeigt wird: error_reporting(-1);
Die Applikation sollte dann soweit verbessert und von Fehlern bereinigt werden, bis keinerlei Fehler mehr angezeigt werden. Das gilt auch und im besonderen für Notice-Meldungen, die nicht initialisierte Variablen monieren. (Welche Gefahr im nicht initialisieren von Variablen lauern kann wird in einem späteren Teil dieser Serie erläutert)

Sobald die Webapplikation, bzw. Webseite dann öffentlcih zugänglich gemacht wird um ihren Dienst zu verrichten, sollten alle Meldungen deaktiviert werden: error_reporting(0);


Display Errors / Fehlerausgabe

Im Gegensatz zum Error Reporting, bei dem man festlegen kann welche Art von Fehlermeldung angezeigt werden sollen, dient Display Errors dazu das gesamte Fehlerausgabe-System an-/auszuschalten. Da die Fehlerausgabe tiefer in's System eingreift als das Ausgeben von Fehlermeldungen, muss man hier auch anders vorgehen um Einfluss auf das Verhalten zu nehmen.

Möchte man zur Laufzeit des Script die Fehlerausgabe deaktivieren, so gibt es dafür nicht etwa den Befehl display_errors(0), sondern wir müssen direkt die Konfigurationsdatei von PHP ansprechen, die PHP.ini:

ini_set( 'display_errors', 'Off' );

Diese Methode hat aber einen Haken. Da der Befehl natürlich erst dann ausgeführt werden kann, wenn das Script abgearbeitet wird, kann es aber bereits vorher schon zu fatalen Fehler kommen, die dann trotzdem ausgegeben werden.
Deswegen ist es besser, wenn man den Wert direkt in der PHP.ini ändert, sofern man darauf Zugriff hat. Leider ist dies häufig nicht der Fall, wenn man nur Mietwebspace sein Eigen nennt. Um den Wert in der PHP.ini zu ändern sucht man die entsprechende Zeile und ändert den Wert:

display_errors = Off

Sollte vor der Zeile ein Semikolon (;) sein, so ist dieses zu entfernen, da es ein Kommentarzeichen ist um die Zeile zu deaktivieren. Anschließend muss der Webserver, in der Regel Apache, neu gestartet werden, bzw. veranlasst werden, die Konfiguration neu zu laden.

Sollte man keinen direkten Zugriff auf die PHP.ini haben, so kann man versuchen die Fehlerausgabe via .htaccess zu beeinflussen. Dazu fügt man in die .htaccess Datei diese Zeile hinzu:

php_flag display_errors Off

Wenn man nun eine Webseite aufruft und es kommt zu einem Fehler, meistens 500 Internal Server Error, dann hat man leider keine Berechtigung diesen Wert per .htaccess zu ändern. In dem Fall hilft nur ein Anfragen an den Provider, ob sie display_errors für einen auf Off setzen können. Wenn man höflich anfragt, sind die Provider meistens bereit dazu.


Fazit für Display Errors

Auf einem Produktiv-System sollte man auf jeden Fall das Anzeigen von Fehlermeldungen komplett deaktivieren. Damit entziehen wir Angreifern quasi die Grundlage für viele Arten von Angriffe: das sammeln von Informationen über unsere Seite.


Error Log / Fehlermeldungen protokollieren

Wenn wir wie empfohlen Error Reporting und Display Errors deaktiviert haben, haben wir aber auch ein kleines Problemchen. Wir wissen nämlich nicht, ob Fehler auftreten und wo Fehler auftreten. Glücklicherweise hat PHP aber auch hier eine Lösung im Angebot. Wir können auftretende Fehler ganz einfach umbiegen, damit diese still und heimlich in ein Log (Protokolldatei) geschrieben werden. So können wir später analysieren, ob Fehler in unserer Anwendung auftreten, wo diese auftreten und ob es sich ggfs. um gezielte Angriffe handelt.
Das Schreiben in eine Protokolldatei findet in zwei Schritten statt:
1. PHP mitteilen das wir Fehler protokollieren möchten
2. PHP mitteilen wo sich unsere Logdatei befindet
Wie schon zuvor können wir dazu den Weg über die PHP.ini oder eine .htaccess Datei nehmen. In der PHP.ini sind beide Zeilen zu suchen und anzupassen:

log_errors = On
error_log = /pfad/zur/php/errorlog.txt

Die .htaccess-Variante sieht wie folgt aus:

php_flag log_errors On
php_value error_log /pfad/zur/php/errorlog.txt

Wichtig ist in beiden Varianten, dass die Logdatei bereits existieren muss und Schreibrechte hat, sonst kann es zu unliebsamen Überraschungen kommen!
Wir sehen in diesem .htaccess Beispiel noch etwas besonderes, nämlich das ein mal php_flag und ein mal php_value benutzt wird. Kurz zur Erklärung:
php_flag benutzt man immer dann, wenn ein PHP.ini Wert auf TRUE oder FALSE (On oder Off, 1 oder 0) gesetzt werden soll. php_value verwendet man dann, wenn ein String übergeben wird, wie z.B. hier der Pfad zur Logdatei.

Hat man das Logging aktiviert, kann man auch zur Laufzeit manuell Einträge in die Datei schreiben und sogar per Email verschicken. Dazu verwendet man die Funktion error_log(). Beispiel:

// Hängt eine Meldung an die Logdatei
error_log( "Es trat ein Fehler auf.\n", 3, "/pfad/zur/php/errorlog.txt" );
 
// Fehlermeldung per Email senden
error_log( "Es trat ein Fehler auf.", 1, "admin@domain.tld", "FROM: Fehlerreport<admin@domain.tld>" );

Fazit für Logdatei

Es ist sehr zu empfehlen eine eigene Logdatei zu verwenden, da man so schön kontrollieren und analysieren kann, falls Fehler aufgetreten sind. Wichtig ist natürlich, dass man auch regelmäßig die Logdatei nach Auffälligkeiten durchsuchst und ggfs. sofort aktiv wird, sobald etwas verdächtig erscheint!


Benutzerdefinierte Fehlerbehandlung

Bisher haben wir schon einiges geleistet um Angreifer ihre Arbeit zu erschweren indem wir vermeiden, zuviel Informationen über unser System zu verraten und gleichzeitig sorgen wir aber dafür, dass wir selbst Kontrolle über Fehlermeldungen haben. Was dabei auf der Strecke geblieben ist, ist der Besucher unserer Webseite. Wie soll dieser nun wissen, dass Datei xyz nicht gefunden wurde; dass gerade unser Mailsystem ausgefallen ist; das die Datenbank gerade nicht erreichbar ist; usw.?
Für diesen Fall bietet uns PHP die Möglichkeit eine eigene Fehlerbehandlung zu implementieren - oder wie es fachmännisch heisst, wir erstellen unseren eigenen Error Handler. Das hört sich schlimmer an als es das eigentlich ist, da es sich bei diesem Error Handler im Prinzip nur um eine gewöhnliche eigene Funktion handelt. Okay, diese Funktion sollte einige Besonderheiten berücksichtigen. Schauen wir uns mal ein Beispiel für einen eigenen Error Handler an:

function meineFehlerBehandlung( $fehlercode, $fehlermeldung, $datei, $zeile )
{
    switch ($fehlercode)
    {
        case E_NOTICE:
        case E_USER_NOTICE:
            echo 'HINWEIS: In Datei <' . basename( $datei ) . '>, Zeile <' . $zeile . '> wurde folgender Fehler ausgelöst:';
            echo $fehlermeldung;
        break;
 
        case E_WARNING:
        case E_USER_WARNING:
            echo 'WARNUNG: In Datei <' . basename( $datei ) . '>, Zeile <' . $zeile . '> wurde folgender Fehler ausgelöst:';
            echo $fehlermeldung;
        break;
 
        case E_ERROR:
        case E_USER_ERROR:
            echo 'FATALER FEHLER: In Datei <' . basename( $datei ) . '>, Zeile <' . $zeile . '> wurde folgender Fehler ausgelöst:';
            echo $fehlermeldung;
            // Weiterer Code der bei einem Fatalen Fehler ausgeführt werden soll, z.B.
            // Email-Benachrichtigung an Webmaster oder Eintrag in eine Logdatei/Datenbank
        break;
 
        default:
            echo 'FEHLER: Es ist ein unbekannter Fehler aufgetreten!';
        break;
    }
    return TRUE;
}

Der Name der Funktion ist zweitrangig. Wichtig ist hier jedoch, dass sie 4 Parameter verarbeiten können sollte. Der $fehlercode ist das Level des Fehlers, $fehlermeldung ist, na wer errät es?! ;-), $datei und $zeile geben die Datei und Position an in der ein Fehler ausgelöst wurde.
In der Funktion gibt es eine Switch, deren Bedingungen uns bekannt vorkommen sollten, wenn wir oben artig die Dokumentation von error_reporting() gelesen haben. Die Bedingungen entsprechen nämlich exakt den Level der Fehler, die sich in Hinweis, Warnung und kritischer Fehler unterteilen. Je nach Fehlertyp wird die passende Meldung ausgegeben, mit Informationen die wir mitteilen wollen. Auf die Art können wir Fehlermeldungen ausgeben die ähnlich denen von PHP sind, aber längst nicht so viel über unser System verraten.
Damit unsere eigene Fehlerbehandlung die von PHP ersetzen kann, müssen wir PHP noch mitteilen, welche Funktion dafür verwendet werden soll. Das geht mit:

set_error_handler ( 'meineFehlerBehandlung' );

Zusätzlich muss man das Error Reporting wieder einschalten: error_reporting(E_ALL)
Wenn wir nun eine Variable ausgeben möchten, die zuvor nicht initialisiert wurde:

echo $variable;

Wird PHP, wegen dem error_reporting(E_ALL), mit einem Hinweis reagieren, aber statt des internen Hinweis wird unser Hinweis (case E_USER_NOTICE) ausgegeben. Wie wir sehen wurde das Fehlverhalten unseres Scripts automatisch von PHP entdeckt und hat darauf reagiert. Wir können aber auch manuell eine PHP-Meldung provozieren:

if (file_exists( 'testdummy.txt' ) === FALSE)
{
    trigger_error( 'Kann Datei nicht finden!', E_USER_WARNING );
}

Hier prüfen wir ob eine Datei existiert, z.B. weil wir etwas hinein schreiben möchten, und falls die Datei nicht gefunden wurde teilen wir PHP mit, dass es eine Meldung ausgeben soll. Mit trigger_error lösen wir eine Meldung aus (Trigger = Auslöser), die den Typ E_USER_WARNING haben soll und als Meldung wird "Kann Datei nicht finden!" ausgegeben.

Noch ein Wort zu den verschiedenen Level einer Fehlermeldung. Wir unterscheiden da:

  • Hinweis (Notice) - Sind harmlose Meldungen die den Scriptablauf nicht beeinflussen. Das sind z.B. nicht initialisierte Variablen.
  • Warnung (Warning) - Das sind Fehler die im weiteren Verlauf zu weiteren Fehler führen können. Beispielsweise wäre das der Fall, wenn wir in eine Datei schreiben möchten. Wenn wir ohne zu prüfen ob die Datei existiert die versuchen zu öffnen, wir ein Fehler ausgelöst. Folgefehler wären hier dann, dass wir versuchen in die nicht geöffnete Datei zu schreiben und anschliessend die Datei schliessen. Logisch, dass man nicht in eine Datei schreiben und sie dann schliessen kann, wenn diese gar nicht geöffnet ist.
  • Fataler Fehler (Error) - Das ist ein Fehler der zu sofortigem Scriptabbruch führt. Das kann von Fehler im Parser über Speicherprobleme bis hin zum ableiten eines Objekts aus einer nicht existieren Klasse gehen. Eben alles, was ein Abarbeiten des Scripts unmöglich macht.

Fazit für Error Handler

Nicht vergessen error_reporting wieder einzuschalten, falls man einen eigenen Error Handler einsetzen möchte!
Ein eigener Error Handler ist eine elegante Möglichkeit den Site-Besucher über auftretende Fehler zu informieren. Es hat ebenfalls den Vorteil, dass man Meldungen klar formuliert in der eigenen Sprache ausgeben kann. Das ist zum informieren der Site-Besucher deutlich besser, als verwirrende PHP-eigene Meldungen.
In jedem Fall aber daran denken, dass man nicht mehr Informationen heraus gibt, als unbedingt nötig!


Exceptions / Ausnahmebehandlung

Exceptions, auf Deutsch Ausnahmen, wurden mit PHP 5 eingeführt und stellen im Prizip das Non Plus Ultra dar, was das Abfangen und Verarbeiten von Fehlern betrifft.
Eins vorneweg: Exceptions ist ein Werkzeug für Fortgeschrittene bis Profis und für Anfänger ziemlich ungeeignet. Der Grund liegt darin, dass Exceptions hervorragendes Verständnis von Programmabläufen und OOP voraussetzen.

Dennoch möchte ich ein kurzes Beispiel geben, wie man Exception in der Praxis anwendet und wie diese arbeiten. Zunächst der Code, dann die Erklärung:

try
{
    if (copy( 'quelldatei.txt', 'zieldatei.txt' ) === FALSE)
    {
        // Fehler wird geworfen, wenn die Datei nicht kopiert werden konnte
        throw new Exception( 'Die Datei konnte nicht kopiert werden!' );
    }
}
catch (Exception $error)
{
    echo $error->getMessage();
    // Eventuell weitere Aktionen zur Fehlerbehandlung
}

Dies ist ein stark vereinfachtes Beispiel einer Exception. Diese besteht aus zwei Teilen:
1. Der try-Block, in dem Code ausgeführt wird in dem ein Fehler erwartet werden kann
2. Der catch-Block, der einen geworfenen Fehler fängt und ggfs. Alternativen Code ausführt.

Voraussetzung ist einmal mehr die deaktivierte Fehlerausgabe von PHP, damit wir uns selbst um Fehler kümmern können. Im try-Block führen wir also Code aus, der einen Fehler verursachen könnte, der die weitere Verarbeitung des Scripts beeinträchtigen kann, z.B. herstellen einer Datenbankverbindung. Schlägt der Verbindungsaufbau fehl, brauchen wir auch nicht zu versuchen Daten aus der DB auszulesen und ebenso brauchen wir kein Template laden, durch das wir die Daten aus der DB ausgeben wollten.
Statt dessen, also im Fall eines Fehlers im try-Block, wird ein Fehler geworfen (throw) unter Angabe der zuständigen Fehlerklasse, in unserem Beispiel ist das die PHP-interne Standardklasse Exception. Als Parameter wird die Fehlermeldung und optional der Fehlercode übergeben. Letzteres ist z.B. sinnvoll einsetzbar, um den MySQL-internen Fehlercode an die Ausnahmebehandlung zu übergeben, damit dort dem Fehler entsprechende Meldungen ausgegeben werden können.
Wird ein Fehler mit throw geworfen, bricht die Scriptausführung augenblicklich ab und es wird der nächste catch-Block angesprungen, der den Fehler bearbeitet. Im Prinzip entspricht das dem Verhalten eines Fatal Error mit dem Unterschied, dass wir bei Exceptions noch Code ausführen können (Meldungen ausgeben, auf Fehlerseiten umleiten, Logeinträge schreiben, usw.) - und genau das macht Exceptions so flexibel einsetzbar. Der Einsatz von try/catch Blöcken lohnt sich also nur, wenn man mit Fehler vom Typ Warning und/oder Error rechnen kann, da ein manuell ausgelöster Scriptabbruch bei einer Notice nicht gerechtfertigt wäre.

Als Erklärung belasse ich es dabei, da das Thema zu komplex ist, als das ich hier in die Tiefe gehen könnte. Weitere Beispiele für den praktischen Einsatz von Exception finden sich in diversen Klassen von mir, wie etwa in der Datenbank-Klasse vom Gästebuch oder auch in der FTP Klasse.


Fazit für Exceptions

Wer programmiertechnisch schon so weit ist, dass er OOP beherrscht und Exceptions versteht, für den gibt es meistens nichts besseres als Fehlerbehandlung. Besonders in Verbindung mit den weiter oben vorgestellten Möglichkeiten bekommt man von PHP ein sehr mächtiges und flexibles Werkzeug in die Hand, mit dem man bei auftretenden Fehlern reagieren kann, seine Site-Besucher nicht im Dunkeln stehen lässt und Angreifern den Zugang zu Informationen erheblich erschwert.
Für alle Programmierer die noch nicht so weit sind Exceptions einzusetzen, sind die anderen vorgestellten Techniken absolut ausreichend um seine Webseite vor allzu neugierigen Augen zu schützen.


Fazit und Download

Der Erste Teil dieser Reihe war ziemlich theoretischer Natur, aber es ist sehr wichtig zu verstehen, wie Angriffe ablaufen und das diese quasi ausnahmslos damit beginnen, dass zunächst Informationen über ein System gesammelt werden. Indem man der Freizügigkeit, in Bezug auf Systeminformationen, von PHP entgegen wirkt, kann man sich bereits sehr viel Ärger ersparen. Ohne Informationen ist es für einen Angreifer schwer ein Einfallstor zu finden - oder wo steigt ein Einbrecher eher ein: in ein Haus das weder Fenster noch Türen hat oder in ein Haus, dass ringsum aus Türen und Fenster besteht, wovon vielleicht ein Fenster auf kipp steht? ;-)

Kommen wir zur Frage, für wen sich was auf einem Produktiv-System eignet:

  • Anfänger - error_reporting(0), display_errors Off. Somit kann es zwar passieren das ein Site-Besucher bei auftretenden Fehlern mal vor einer weißen Seite sitzt, weil nichts mehr geht, aber das ist allemal besser, als wenn das System zu geschwätzig ist und Angreifern wichtige Details liefert.
  • Fortgeschritten - error_reporting(E_ALL), display_errors dynamisch On/Off (je nach Scriptsituation und ob Fehler erwartet werden können), eigener Error Handler, log_errors On. Voraussetzung ist aber, dass man wirklich Sorge dafür trägt, dass alle Arten von Fehler vom eigenen Error Handler berücksichtigt wird!
  • Profi - error_reporting(0), display_errors Off, log_errors On, individuelle Ausnahmebehandlung (Exceptions)

So weit mal eine kleine Einführung in die Thematik. Aus Platzgründen kann ich leider nicht auf alle Feinheiten eingehen, auch wenn der Artikel schon recht lang wurde.
Vielen Dank für's Lesen und viel Erfolg beim sicherer machen eures Systems. :-)

Anwendungsbeispiele (1 KB)