„Sicherheit“ mit PHP/CGI, MySQL

sichere Internet-Anwendungen mit PHP, CGI und Datenbanken

$Date: 2007/02/11 19:42:22 $

Zusammenfassung

Dieses Dokument ist Resultat eines Referates von mir, nachdem ich im MaTA-Unterricht halten „musste“, nachdem ich während eines anderen Referates über PHP und MySQL das zur Vorführung genutzte Gästebuch gehackt hatte (es ließ sich ungeprüft HTML- und Javascript eingeben). Ich beschreibe im Folgenden welchen Gefahren CGI-Anwendungen, speziell welche in PHP, ausgesetzt sind, um auf generelle Schwachstellen hinzuweisen. Die Realität zeigt leider, dass sich immer noch nicht alle Webmaster und -programmierer mit dem Thema Sicherheit hinreichend auseinandersetzen. Neben dem Aufzeigen potenzieller Sicherheitslücken beschreibe ich außerdem, wie man diese schließen kann, wozu häufig nur ein geringer Aufwand nötig ist. Am einfachsten ist es allerdings, wenn man das Paradigma der Sicherheit von Anfang an herzigt und überlegt programmiert, statt „mal schnell ein Gästebuch zusammenzuhacken“.

Die jeweils aktuelle Version dieses Artikels finden Sie auf meiner Webseite unter der Adresse http://www.robertbienert.de/dokumente/web/PHPMySQLSicherheit.html.


Inhaltsverzeichnis

Einleitung
Eingabefelder
Der einkommende Datenstrom
HTML-, CSS- und Javascript-Injektion
SQL-Injektion
Übermittlung per URI/HTTP-GET
URI-Encoding
Übermittlung per Cookie
Dateihandling
Auslesen von Dateien
Spezielle Dateien
Weitere Ein-/Ausgabe
Verwendung von Funktionen
Verwendung von Modulen
Include-Schutz
A. Weitereführende Informationen
Funktionsreferenz

Einleitung

Im Rahmen der Ausbildung zum Mathematisch-Technischen Assistenten ist unser Jahrgang teilweise zufällig über gravierende Sicherheitsmängel bei diversen Webseiten gestolpert; auch war das Thema Sicherheit regelmäßig Gegenstand der Unterrichts-Diskussion. Wie wir dabei während eines Referates über PHP und MySQL (bei dem ich HTML-Code in das vorgestellte Gästebuch injizierte) sowie auf der Schulbanker-Seite (einige Formulare waren 2004 nicht XSS-sicher) gesehen haben, ist es teilweise recht einfach, eigenen Code in fremde Webseiten zu injizieren. Die folgenden Hinweise und Gedanken beziehen sich größtenteils auf PHP und MySQL, können aber auch unkompliziert auf andere Programmiersprachen für CGI-Anwendungen, wie z.B. Perl, Ruby, … übertragen werden.

Viele Beispiele und Ideen, die in diesem Dokument erläutert werden, basieren dabei teilweise auf eigenen Erfahrungen: Der unbedarfte Hobby-Webseitenprogrammierer betrachtet viele Probleme eher naiv oder ist für manche Probleme gar nicht sensibilisiert. Das Aufzeigen und Verdeutlichen von Sicherheitsproblemen und die Sensibilisierung des Programmierers ist Zweck dieses Artikels.

Eins vorweg: Obwohl PHP versucht es dem Anwender möglichst einfach zu machen, sollte man doch daran denken, was man bei C alles falsch machen kann und mit dieser fast schon paranoiden Vorsicht seine Programme schreiben.

Eingabefelder

… werden zur Ermittlung von „handgeschriebenen“ Daten des Nutzers verwendet und sind daher Bestandteil fast aller Webformulare. Als Übertragungswege vom Client zum Server stehen dabei die HTTP-Methoden GET und POST zur Verfügung, wobei für große Datenmengen (von einem <textarea>) und für nicht offensichtliche Daten besser POST verwendet wird (ich glaube in irgendeinem RFC ist eine maximale Größe des Datenstroms angegeben). Damit ist es allerdings noch längst nicht getan. Im folgenden werde ich zuerst die Risiken ungeprüfter Benutzereingaben allgemein besprechen, danach speziell die Gefahren der Übermittlung per GET.

Eine besondere Sicherheitslücke im Zusammenhang mit Eingabefeldern gleich welchen Typs (einzeilig, Dropdown, Radiobuttons, …) ist, dass sich viele Programmierer darauf verlassen, dass der Anwender auch tatsächlich ihr HTML-Formular benutzt hat, um Daten an ihr CGI-Skript zu senden. Sie gehen also davon aus, dass bestimmte Felder keine Zeilenumbrüche enthalten und andere Felder nur bestimmte Werte. Tatsächlich hindert einen kreativen Benutzer allerdings gar nichts daran, das CGI-Skript von seinem eigenen Formular aus aufzurufen. Um mögliche Sicherheitslücken zu finden könnte der Besucher z.B. ein Formular erstellen, in dem jedes Feld als Textarea ausgelegt ist oder gleich viel flexibler ein Programm schreiben, welches das CGI-Skript mit Daten füttert. Überlegen sie sich daher vor dem Erstellen eines Formulares, welche Form die zu übertragen Daten haben und richten sie die Plausibilitätsprüfungen in ihrem CGI-Skript danach.

Eine nützliche Hilfe beim Testen von Formularfeldern ist die Firefox-Erweiterung Firebug, mit der man u.a. den HTML-Code einer Webseite live verändern kann.

Der einkommende Datenstrom

HTML-, CSS- und Javascript-Injektion

Während in vielen Fällen das Problembewusstsein besteht, dass Text, der in einer <textarea> eingegeben werden kann, relativ frei gestaltbar ist und Inhalte aus so einem Eingabefeld auch bei der Ausgabe häufig korrekt behandelt werden (z.B. mit der PHP-Funktion htmlspecialchars()), ist dies bei einzeiligen Feldern (die gerne für die Emailadresse oder den Namen benutzt werden) nicht unbedingt der Fall; die Überlegung, welchen Sinn den Sonderzeichen beispielsweise im Namen haben, verdeckt dabei das real existierende Problem, dass ungeprüft HTML-, CSS- oder Javascript-Code eingeschleust werden kann. Das Bombardieren des Benutzers mit tausenden von Popups ist dabei noch harmlos, es lassen sich aber auch Session stehlen, andere Webseiten einbinden oder Browser-Bugs ausnutzen. Weitere Informationen hierzu findet man unter den Stichworten Cross-Site-Scripting (XSS) sowie HTML-Injektion.

Auf die weitere Einbettung aktiver Inhalte und Cookies brauche ich wohl in diesem Zusammenhang nicht weiter einzugehen, man kann froh sein, wenn bei einem ungesicherten Gästebuch nur das Logo von einem fremden Server prangt und nicht noch irgendwelche ActiveX-Sachen …

In anderen Sprachen für CGI-Anwendungen (moderner: Webservices) gibt es u.U. keine htmlspecialchars(), doch mit regulären Ausdrücken ist es relativ einfach wichtige HTML-Zeichen zu maskieren, vorallem < > & " '.

SQL-Injektion

SQL-Injektion bedeutet, dass es möglich ist, von außerhalb SQL-Abfragen des CGI-Skriptes zu manipulieren. In beschränktem Maßstab ist dies gewünscht, z.B. die Übergabe einer ID, die in die WHERE-Klausel eine Abfrage eingebaut wird. Eine „falsche Liberalität“ mit SQL-Abfragen öffnet allerdings eine Sicherheitslücke, durch die im Endeffekt beliebige SQL-Abfragen von außen manipuliert und erstellt werden können:

Beispiel 1. für SQL-Injektion anfälliger Code

$req = sprintf("INSERT INTO gb (name, email, text) VALUES ('%s', '%s', '%s')",
		$name, $email, $text);

So weit, so gut. Doch was ist nun, wenn – sagen wir $text – einen etwas ungewohnten Inhalt hat:

Beispiel 2. Injizierter Code

// $text enthält
// "Toll'); UPDATE users SET (passwd='meins') WHERE (name='root"
$req = sprintf("INSERT INTO gb (name, email, text) VALUES ('%s', '%s', '%s')",
		$name, $email, $text);

echo $req

// ergibt (der Leserlichkeit halber umbrochen):
// INSERT INTO gb (name, email, text) VALUES ('ich', 'user@invalid.com', 'Toll');
// UPDATE users SET (passwd='meins') WHERE (name='root')

Man stelle sich vor, die Datenbank habe ferner kein UNDO und ich würde noch ein DROP TABLE einfügen … Aber zum Glück haben wir ja noch ein Datenbank-Backup, oder? Aber so weit muss es gar nicht kommen, schließlich bietet jede Datenbank-API Funktionen, mit denen Bestandteile von Abfragen maskiert werden können; beim beliebten und verbreiteten MySQL beispielsweise ist dies mysql_real_escape_string(). Bei der Verwendung von so genannten Prepared Statements braucht man sich über die korrekte Maskierung sogar noch weniger Sorgen zu machen, da dies im Hintergrund erledigt wird. Von der Verwendung der PHP-Funktion addslashes() oder der Konfigurationseinstellung magic_quotes_gpc = On ist allerdings dringend abzuraten:

  • Da allen per GET, POST und COOKIE übergebenen Werten Backslashes \ vorangestellt werden, wird ein großer Overhead produziert, wobei einige Werte gar nicht in SQL-Datenbanken gespeichert werden sollen und so ein manuelles stripslashes() nötig ist.
  • Die Funktion bzw. die Einstellung stellen nur bestimmten Zeichen, von denen PHP meint, dass es Sonderzeichen sind einen Gegenschrägstrich voran. Dies müssen allerdings nicht unbedingt Sonderzeichen des Datenbanksystems (DBMS) sein, außerdem kann es sein, dass das DBMS noch andere Zeichen mit besonderer Bedeutung kennt.

Das folgende Beispiel zeigt exemplarisch, wie man SQL-Abfragen korrekt maskiert:

Beispiel 3. korrekte und sichere SQL-Abfrage

// filtere alle Eingaben, inklusive einfacher Quotes darum
$req = 'UPDATE users SET (passwd="' . mysql_escape_string($pwd) .
	'") WHERE (name="' . mysql_escape_string($name) .
	'")';

Und noch einmal der „worst case“, wie man es nie machen sollte: Wir verzichten aufs Escaping und die Quotes um Abfragewerte

Beispiel 4. sehr nachlässige und ausgesprochen unsichere SQL-Abfrage

// ausgesprochen unsichere Variante
$req = "UPDATE users SET (passwd=$pwd) WHERE (name=$name)";

Hier ist (fast) alles möglich, was das Hacker-Herz höher schlagen lässt, denn es werden keine Parameter geprüft oder Sonderzeichen maskiert. Es ist sogar möglich, wie oben komplette SQL-Abfragen an diese anzuhängen. Jenes Beispiel zeigt auch die nächste Entwicklungsstufe aus dem obigen und das Missbrauchspotential, weshalb ich nicht noch einmal darauf eingehe. Weiterhin liegt auf der Hand, das einfach nur das Maskieren der Werte nicht reicht:

Beispiel 5. nur scheinbar sichere SQL-Abfrage

$req = 'UPDATE users SET (passwd=' . mysql_escape_string($pwd) .
	') WHERE (name=' . mysql_escape_string($name) .
	')';

Hier ist es zwar nicht mehr möglich, spezielle Zeichen zu injizieren, allerdings kann die ich Abfrage nicht nur wie oben „verbiegen“:

Beispiel 6. Beweis der Unsicherheit

// $name enthält "gibtsnicht OR 1=1":

$req = 'UPDATE users SET (passwd=' . mysql_escape_string($pwd) .
	') WHERE (name=' . mysql_escape_string($name) .
	')';

// damit liefert

echo $req;

// 'UPDATE users SET (passwd=meinPasswort) WHERE (name=gibtsnicht OR 1=1)'

Das das System einen Benutzer mit dem Namen gibtsnicht kennt, ist eher unwahrscheinlich, allerdings ist dies auch nicht weiter relevant, denn wenn die Bedinung name=gibtsnicht fehlschlägt, wird stattdessen die zweite Bedingung 1=1 ausgewertet. Und das die wahr ist, sieht man sofort. [1]

Übermittlung per URI/HTTP-GET

Es gibt Anwendungen, für die GET gut geeignet ist, z.B. Suchmaschinen, bei denen man dem User die Möglichkeit zur Speicherung seiner Abfrage ermöglichen möchte oder bei PHP-basierten CMS (Content Managment Systeme). Erstens gilt das gleiche wie für die verschiedenen Wege der Injektionen, man kann aber auch zusätzliche Variablen in PHP injizieren, sofern in der php.ini die Einstellung register_globals auf On steht. Wenn register_globals aktiviert ist, hat man als Programmierer zwar (etwas) weniger Aufwand die Variablen nach PHP hereinzubekommen, dadurch ergeben sich allerdings auch große Sicherheitslücken:

Nehmen wir an, wir rufen eine Seite per folgender URI auf: http://www.example.org/secret/login.php, wobei im Login-Formular die Werte Name und Passwort gesetzt wurden. Das zugehörige PHP-Script check.php soll so aussehen:

Beispiel 7. Code zur Benutzer-Authentifizierung

<?php
$base = 'http://www.example.org/';

if (userExists($Name)) {
   if ($Passwort == getStoredPwd($Name)) {
         $is_a_user = true;
    }
}

// etwas später im Code ...

if ($is_a_user)
	header("Location: ${base}secret/very-secret.html");
else
	header('Location: ' . $base);

exit;
?>

Warum sollte man sich bei diesem Script noch die Mühe machen per Brute-Force Benutzername und Passwort zu erraten? Man kann diese zwar auch bequem per URI übermitteln, denn bei aktiviertem register_globals unterscheidet PHP nicht, ob eine Variable per GET oder POST hereinkommt. Die entsprechenden Anfragen braucht man nur zu generieren, indem man viele „sinnvolle“ Buchstaben-Kombinationen ausprobiert, am Besten automatisiert mit einem kleinen Programm. Viel einfacher ist es allerdings die Seite direkt mit /login.php?is_a_user=1 aufzurufen. Aus diesem Grund wurden in PHP die assoziativen Arrays $_POST, $_GET sowie $_COOKIE eingerichtet. Wenn es wirklich gleichgültig ist, woher die Daten stammen, kann man immer noch $_REQUEST verwenden. Außerdem mag es vielleicht hilfreich sein zu wissen, dass Konstanten in PHP nicht von außen gesetzt werden können.

Hinweis zum Beispiel

Generell sollte man Verzeichnisse mit sensiblen Daten nicht durch solche Methoden sowie „Security by Obscurity“ sichern, sondern z.B. durch eine .htaccess und/oder HTTPS-Übertragung. In diesem Zusammenhang möchte ich noch auf die Möglichkeit einer sogenannten robots.txt hinweisen, die unter der Document Root abgelegt wird. Damit teilt man Suchmaschinen-Robots mit, welche Verzeichnisse diese sehen und nicht sehen dürfen. Dies könnte allerdings von bösartigen Bots ausgenutzt werden.

URI-Encoding

Um Umlaute und Sonderzeichen per GET zu übertragen, werden diese nach einem bestimmten Muster codiert: Ein Prozentzeichen gefolgt von zwei hexadezimalen Ziffern, die den ASCII-Wert des Zeichens angegeben. Der Nachteil ist, dass man auch Nicht-Sonderzeichen auf diese Weise codieren kann. PHP nimmt einem die Aufgabe der Decodierung ab, sofern man auf die oben beschriebene Weise auf die Werte zugreift. Muss man hingegen direkt auf den Querystring zugreifen, sind die Zeichen nicht codiert. So können einige Prüfungen auf Werte fälschlicherweise doch positive Ergebnisse liefern. Mehr zu dieser Problematik später.

Weiterhin macht es bei einer Zusammenarbeit mit dem Betriebssystem oder Bibliotheken, die in C geschrieben sind, nach einem ganz besonderen Zeichen zu suchen: In C ist das NULL-Byte, d.h. ASCII-Wert 0, URI-enkodiert %00, die Kennung für das Ende einer Zeichenkette, viele andere Programmiersprachen, insbesondere beliebte Script-Sprachen für CGI-Anwendungen, stoßen sich dagegen nicht an diesem Zeichen und arbeiten mit ihm als Teil der Zeichenkette. Im Abschnitt Dateihandling gehe ich etwas genauer darauf ein.

Übermittlung per Cookie

Cookies werden auf Webseiten überall da verwendet, wo benutzerspezifische Daten wie Session-IDs, Warenkörbe, etc. auf dem Rechner des Nutzers gespeichert werden sollen. Da der Standardanwender im Allgemeinen nicht weiß, wie Cookies auf seinem System gespeichert werden und wie er diese bearbeiten kann, geht man davon aus, dass man den Daten eines Cookies vertrauen kann.

An dieser Stelle möchte ich eine Diskussion in unserem Wirtschaftswissenschaften-Unterricht einbringen, in der es um manipulierte Cookies im Zusammenhang mit Webshops ging. In einem konkreten Rechtsfall hatte wohl ein Onlineshop in den Cookies die Preise der Waren gespeichert. Ein findiger Nutzer hatte dies entdeckt und die Preise seines Einkaufs gesenkt. Nach dem Absenden der Bestellung bekam er seinen Einkauf mit den „neuen“ Preisen sogar bestätigt.

Das Ändern eines Cookies ist nicht so schwierig, auch wenn die aktuellen Browser dazu keine Funktion bereitstellen. (Mittlerweile gibt es sogar Cookie-Editoren als Bookmarklets.) Aus der Tradition heraus werden Cookies als Textdatei(en) gespeichert, beim Internet Explorer in einem Verzeichnis namens Cookies, bei Mozilla-Browsern in der Datei cookies.txt im Verzeichnis des eigenen Profiles. Das herauszufinden ist keine Kunst, ebenso wenig, wie das Format der Cookie-Datei(en) aufgebaut ist, d.h. welche Werte man wie ändern kann. Deshalb kann man sich nicht auf die Werte von Cookies verlassen.

Dateihandling

Für manche Anwendungen kann es unerlässlich sein, dass Dateinamen oder Teile davon per URI übergeben werden. Als abschreckendes Beispiel gilt dafür immer file.php?name=subdir/file.ext. Das mag bösartige Menschen zwar herausfordern, muss aber gar nicht so gefährlich sein: Wenn Dateinamen an eine CGI-Anwendung übergeben werden, muss beachtet werden, dass die Dateien nicht nach den Regeln des Webservers, sondern des zugrunde liegenden Systems behandelt werden. D.h. / steht für das Wurzelverzeichnis, nicht für die Document Root. Daraus ergibt sich, das Dateien entweder gar nicht oder nur kontrolliert die Server-Umgebung verlassen dürfen. Vor allem auf führende Slashes und /../ ist zu achten! Besser ist es aber die Dateinamen aus verschiedenen Parametern zusammenzubauen, z.B. file.php?category=subdir&name=file&type=ext; Achte darauf, dass das Muster dahinter nicht zu offensichtlich ist, d.h. file.php?category=/etc&name=passwd&type= darf nicht den (vom Hacker) beabsichtigten Zweck erfüllen. Andere Möglichkeiten sind Script-interne Vorgaben wie z.B. $dir = 'dieses/hier/';oder besser define('DIR', 'dieses/hier/'), wobei dann alle Dateien aus $dir stammen (müssen). Die PHP-Konfigurations-Einstellung open_basedir erledigt genau dies, allerdings sehr wahrscheinlich besser. Relativ sicher ist den endgültigen Dateinamen erst im Script zusammenzusetzen, z.B.

Beispiel 8. Zusammenbau eines Dateinamens

// URI: file.php?category=sub&name=file&type=ext

$filename = $category . 'dir/' . $name . '.' . $type;

// ergibt 'subdir/file.ext'

So schön das Beispiel auch aussieht, damit es wirklich sicher ist, müssen wir allerdings voraussetzen, dass alle drei Bestandteile category, name sowie type vorher mit basename() behandelt worden sind, um führende ../ und enthaltende NULL-Bytes auszufiltern. Dann ist selbst folgendes Beispiel harmlos (als Kommentar dahinter immer das, was basename() liefert):

Beispiel 9. Einbruchsversuch mit basename() abgewehrt

// URI: file.php?category=/etc&name=passwd%00&type=interessiertNicht

$category	= basename($_GET['category']);	// 'etc'
$name		= basename($_GET['name']);	// 'passwd'
$type		= basename($_GET['type']);	// 'interessiertNicht'

$filename = $category . '/' . $name . '.' . $type;

// Ergibt 'etc/passwd.interessiertNicht' anstelle von
// "/etc/passwd\x0.interessiertNicht", also '/etc/passwd'.

Auslesen von Dateien

Falls die Daten im HTML-Format vorliegen, kann man diese per readfile() direkt an den Browser senden. Ein include() stellt eine zusätzliche Gefahr dar und wird deshalb nur bei Modulen verwendet! Dieser Aspekt ist insbesondere dann zu beherzigen, wenn in den Dateien Inhalte stehen, die Benutzer eingeben können, z.B. ein dateibasiertes Gästebuch. In solchen Fällen müssen etwaige Schutzmaßnahmen schon früher greifen.

Die gleiche Überlegung gilt übrigens auch für die Verwendung von Variablen: eval() ist wirklich evil!

Spezielle Dateien

Im Zusammenhang mit der NULL-Byte-Problematik ergibt sich, speziell unter Windows, etwas Besonderes: Während auf Unix-Systemen die Geräte-Dateien in einem eigenen Verzeichnis /dev/ untergebracht sind, gibt es einige dieser unter Windows in jedem Verzeichnis. Gerade die Zugriffe auf Geräte des sogenannten „Zeichen-Subsystems“ sorgen schnell für einen Absturz des CGI-Scriptes oder sogar des Webservers. Im folgenden Beispiel haben wir ein Script, das den URL-Parameter zwar von führenden ../ befreit, aber nicht das String-Ende prüft. Der so bearbeitete Dateiname soll einen statischen HTML-Include darstellen, den wir per readfile() direkt an den Browser senden.

Beispiel 10. Zugriff auf ein Gerät unter Windows

// $file ist unser "geprüfter" Parameter, d.h. ohne '../' am Anfang
// $file enthält nun "CON\x0"

readfile('verzeichnis/' . $file . '.htminc');

Damit versucht PHP die Datei verzeichnis/CON zu lesen, da alles ab dem NULL-Byte ignoriert wird. Der Versuch wird dabei wohl nicht scheitern, das Lesen schon eher, denn CON ist die Gerätedatei zur Terminal-Ausgabe. Die Verwendung von basename() hätte uns vor dieser Sicherheitslücke bewahrt, denn die Funktion schneidet alles hinter dem NULL-Byte C-gerecht ab, d.h. obiges Beispiel sieht so schon viel besser aus:

Beispiel 11. Sicherer Dateizugriff

// $file ist komplett ungeprüft, wir verlassen uns auf basename()
// $file enthält "../../CON\x0"

readfile('verzeichnis/' . basename($file) . '.htminc');

Damit findet der Zugriff auf verzeichnis/CON.htminc statt, was genau das ist, was wir wollen (außer das ist immer noch eine Gerätedatei).

Weitere Ein-/Ausgabe

Eine – wenn auch eher bei C – beliebte Methode ist es Variableninhalte mit der Funktion printf() auszugegen, weil puts() automatisch einen Zeilenvorschub ausführt: printf(stringvar); Was ist nun, wenn der Variableninhalt ein String ist und printf-Formatcodes enthält? PHP-Fehler werden zum Glück durch den Interpreter abgefangen, aber C-Programme werden in solchen Fällen wegen Zugriffsfehler auf Grund eines Buffer Overflows gerne vom Betriebssystem gekillt, sofern es dies bemerkt. Die printf-Familie für C und Perl kennt den Formatcode n, mit dem in der übergebenem Variable die Anzahl der aktuell geschriebenen Bytes (C) bzw. Zeichen (Perl) gespeichert wird. Damit hat man einen direkten Stackzugriff und kann die Rücksprung-Adresse von printf verändern, so dass nach dem Ende der Funktion nicht zur aufrufenden Funktion zurückgekehrt wird, sondern der, vom Angreifer mit %n geschriebene, (Maschinen-) Code ausgeführt wird. Diese Form des Ausnutzens von Buffer Overflows ist dank schlampiger C-Programmierung eine ausgesprochen beliebte Methode Zugriff auf ein System zu erlangen (siehe auch Wikipedia-Artikel Pufferüberlauf).

Deshalb, wenn man printf() verwendet, dann z.B. so: printf("%s", stringvar);.

Verwendung von Funktionen

Neben der oben genannten Vorsicht, gerade bei Verzeichniszugriffen, gibt es noch weitere Gefahren im Zusammenhang mit Funktionsaufrufen. Viele eingebaute Funktionen geben im Fehlerfall eine Menge an Meldungen direkt an den Browser aus. Dies können Hinweise für potenzielle Kriminelle sein, nicht nur wo auf dem Serverrechner die Homepage liegt. Deshalb gibt es bei vielen (allen?) PHP-Funktionen die Möglichkeit durch ein vorangestelltes @ die Ausgabe von Fehlermeldungen zu unterdrücken, was bei Anwendungen auf Produktivsystemem auf jeden Fall zu empfehlen ist; Fehlermeldungen und Debug-Ausgaben sind nur beim Testen „erlaubt“.

Eine weitere Funktion, die man nur bedächtig einsetzen sollte, ist mail(). Wie der Name schon sagt, kann man damit Emails versenden. Durch schlampige Programmierung kann man dies ausnutzen um große Mengen Spam zu versenden. Da freuen sich neben dem Webhoster vor allem alle Menschen, die ein Emailpostfach besitzen – wer bekommt nicht gerne Post! Die Funktionsdeklaration mail sieht folgendermaßen aus:

bool mail(to,  
 subject,  
 message,  
 additional_headers,  
 additional_parameters); 
string  to;
string  subject;
string  message;
string  additional_headers;
string  additional_parameters;

Laut Dokumentation der Funktion mail() kann der Parameter subject die Emailadressen mehrerer Empfänger enthalten. Ganz große Vorsicht gilt außerdem dem Parameter additional_headers, der u.a. dazu benutzt werden muss, wenn eine Absenderadresse angegeben werden soll. Anwender dürfen hier unter keinen Umständen Newlines oder die Steuerzeichensequenz \r\n angeben, da sie sonst eigene Mailheader setzen können.

Verwendung von Modulen

Externe Sourcecodemodule werden als normale Dateien behandelt, d.h. für includes unter Benutzung von Variablen (z.B. include('pfad/zu/' . $inc) ist die gleiche Vorsicht wie beim Öffnen von Dateien notwendig. Weiterhin sollte man verschiedene Funktionalitäten in verschiedene Dateien ausgliedern, die sich die gleichbleibenden Funktionen per include holen. Der Grund ist, dass wir uns bei nur einer Datei mit vielen includes den flachen Namensraum „zumüllen“, d.h. zwei gleichlautende Funktionen führen sehr schnell zu Problemen, da PHP kein Überladen wie C++ oder Java erlaubt. Im Gegensatz zu einer Compilersprache konvertiert PHP einfach zwischen verschiedenen Typen, d.h. aus einem Array als Argument kann schnell ein int werden.

Include-Schutz

Mit den jetzt erworbenen Informationen über Variablen und Includes kann man relativ einfach einen Include-Schutz implementieren. Im Vergleich zum Beispiel der ersten Version dieses Artikels verwendet dieses Beispiel keine Variable mehr für den Include-Schutz, sondern eine Konstante. Der Vorteil ist, dass Konstanten selbst bei register_globals = on nicht von außen gesetzt werden können.

Beispiel 12. Beispiel eines Include-Schutzes

// in der Includedatei
if (! defined('canInclude'))
	die('Zugriff verweigert!');

// im Hauptprogramm
define('canInclude', 1);
include(modul);

A. Weitereführende Informationen

Funktionsreferenz

Anmerkung

Wenn nichts Anderes angegeben ist, handelt es sich hierbei um Verweise auf die entsprechenden PHP-Funktionen.


[1] Nach Aussage eines Beitrages im Selfhtml-Forum setzt dieser Ausdruck sogar die Passwörter aller Benutzer.