Seiten 1, 2
Eingabedaten überprüfen oder entschärfen
Wie Sie vom Browser übergebene Werte entschärfen können, haben Sie bereits oben gesehen: Vor der Übergabe an die Kommandozeile können Sie die Werte von escapeshellarg() in elektronische Quarantäne schicken lassen. Beim Einbau in SQL-Befehle können Sie gefährliche Anführungszeichen oder Apostrophe oft mit addslashes() oder magic_quotes_gpc On unschädlich machen. Manchmal ist das Entschärfen aber nicht möglich: wenn zum Beispiel Ihr Kommandozeilenprogramm keine Anführungszeichen mag oder auch wenn Sie in SQL numerische Beträge von der Größe her vergleichen müssen. In diesen Fällen hilft nur eine explizite Überprüfung. Die kann aber sowieso sinnvoll sein, z.B. wenn Sie in einer Logdatei "Einbruchsversuche" festhalten oder dem Benutzer Feedback über etwaige (harmlose) Fehleingaben geben wollen.
Vom Browser übergebene Werte überprüfen
Im Prinzip haben Sie zwei Strategien zur Auswahl. Die erste Strategie besteht darin, genau festzulegen, was in einem bestimmten Eingabefeld vorkommen darf. Wenn Sie beispielsweise eine deutsche Postleitzahl sehen wollen, sollte die Eingabe genau aus fünf Ziffern bestehen. Bei einer Eingabe, die aus einer fest definierten Menge kommt (z.B. "Januar", "Februar", "März", ...) können Sie direkt überprüfen, ob die Eingabe einem dieser Werte entspricht. Wenn Sie diese Strategie anwenden können, dann ist das gut – damit stellen Sie sicher, daß Sie wirklich nur Werte verarbeiten, mit denen Sie auch gerechnet haben.
Der Nachteil der ersten Strategie besteht darin, daß es manchmal etwas schwierig ist, genau festzulegen, was erlaubt ist. Wenn Sie beispielsweise nach einem Nachnamen fragen, sollte "d'Artagnan" noch durchgehen, während "x'utwfrt" vermutlich in keinem Reisepaß zu finden ist und deswegen als suspekt zu gelten hätte. Deswegen kann man die Regeln auch lockern. Für einen Namen könnte man dann z.B. verlangen, daß er zwar Apostrophe, aber keine doppelten Anführungszeichen, Semikola oder sonstige komischen Zeichen enthalten darf. Diese Lockerung kann allerdings auch wieder Hintertüren öffnen: Apostrophe können beispielsweise in SQL als Ersatz für doppelte Anführungsstriche verwendet werden.
Die zweite Strategie besteht darin, explizit nach verbotenen Zeichen zu suchen. Um den SQL-Befehl im letzten Beispiel (Wert in "" verpackt) zu unterwandern, brauchen wir beispielsweise unbedingt ein doppeltes Anführungszeichen. Wenn wir unseren String also explizit auf das Vorhandensein eines solchen Zeichens überprüfen, dann reicht das hier schon. Der Nachteil besteht darin, daß es evtl. mehrere "problematische" Zeichen gibt – das ist insbesondere der Fall, wenn es um Befehle geht, die an die Kommandozeile übergeben werden sollen. Also, wie machen wir das? Das Zauberwort hier heißt reguläre Ausdrücke.
Reguläre Ausdrücke
Sagt Ihnen nichts? Macht nichts – dazu kommen wir gleich. In PHP gibt es zwei Sorten regulärer Ausdrücke. Die eine Sorte (POSIX-kompatibel) ist eigentlich nur für Leute gedacht, die damit seit Jahren vertraut sind und sie noch brauchen. Wenn Sie in Sachen reguläre Ausdrücke ein Anfänger sind, können Sie sie gleich vergessen und sich auf die Perl-kompatible Sorte stürzen. Alle Funktionen, die in PHP mit Perl-kompatiblen regulären Ausdrücken zu tun haben, fangen mit preg_ an. Die wichtigste Funktion ist preg_match(). Der übergeben Sie ein Muster (den regulären Ausdruck) und eine Variable, und sie sagt Ihnen, ob das Muster auf den String paßt.
Ein Beispiel: Ihre Formularvariable plz soll eine Postleitzahl enthalten. Fünf Ziffern also. In PHP können wir korrupte Postleitzahlen so abfangen:
...
if (!preg_match("/^\d{5}$/",$_POST["plz"])) {
die("Dieser Brief kommt so nie an! Vergiß mein nicht ...");
}
...
|
Okay, wenn Sie mein Buch gelesen haben (oder sich sonstwie schlau gemacht haben), dann kennen Sie den Trick: Das Muster /^\d{5}$/ besteht aus zwei sogenannten Delimitern (den Schrägstrichen), dem Anker ^, der festlegt, daß das Muster am Anfang des Strings passen muß, sowie dem Anker $, der verlangt, daß es sich bis zum Ende des Strings erstrecken muß. Zwischen diesen beiden Ankern wollen wir Ziffern (\d) finden, und zwar genau fünf ({5}) davon.
Wenn dem so ist, gibt preg_match() den Wahrheitswert true zurück, den wir dann mit dem Ausrufezeichen auf false negieren. In diesem Fall hat unsere Variable die Klippe umschifft. Wenn nicht, lassen wir das Skript mit einer Fehlermeldung abkratzen. Zugegeben, das geht auch mit einer schnörkeligeren Seite, aber darum geht es uns hier ja nicht.
Wenn uns der Inhalt der Formularvariablen plz egal ist und sie nur keine Anführungszeichen enthalten darf, dann geht das so:
...
if (preg_match("/\"/",$_POST["plz"])) {
die("Dieser Brief kommt so nie an! Vergiß mein nicht ...");
}
...
|
In diesem Fall wird Alarm geschlagen, wenn irgendwo (= keine Anker) in unserem String ein doppeltes Anführungszeichen ist. Weil es hier zwischen zwei doppelten Anführungszeichen der PHP-Syntax steht, müssen wir es durch einen Rückwärtsstrich schützen. Weil diesmal ein positiver Befund Grund zur Beunruhigung ist, benötigen wir kein Ausrufezeichen vor preg_match().
So, jetzt brauchen Sie nur noch einen passenden regulären Ausdruck für Ihre Eingabedaten, richtig? In der folgenden Tabelle finden Sie ein paar nützliche Ausdrücke zusammengestellt:
| Verwendungszweck |
Muster |
| Ein String, der nur aus Buchstaben bestehen darf |
/^[\W_\d]*$/ |
| Ein String, der nur aus Buchstaben bestehen darf und der nicht leer
sein darf |
/^[^\W_\d]+$/ |
| Ein String, der nur aus Buchstaben bestehen darf und z.B. mit hallo anfangen muß |
/^hallo[^\W_\d]*$/ |
| Ein String, der ein deutsches KFZ-Kennzeichen enthält (ggf. mit bis zu einem Leerzeichen zwischen Trennstrich und Buchstaben/Zahlen) |
/^[A-Z]{1,3}\s?\-\s?[A-Z]{1,2}\s?\d{1,4}$/ |
| Ein String, der aus einer E-Mail-Adresse besteht |
/^\w[\w\-\+\&\.]*@([A-Za-z][A-Za-z0-9\-]{0,23}\.)*[A-Za-z]{3}/ |
Natürlich gibt es noch jede Menge anderer Muster – die Sie sich mit etwas Übung auch selbst stricken können. Wie immer geht hier das Probieren über das Studieren. Mit dem folgenden kleinen PHP-Skript können Sie Ihre eigenen Versuche unternehmen:
<html>
<body>
<?php
// ersetzen Sie den Ausdruck unten durch das Muster,
// das Sie ausprobieren wollen
$muster = "/^\d{4,6}$/"; // Beispiel: ein String, der
// aus 4-6 Ziffern besteht
// Testzeichenkette, auf die das Muster passen soll (oder auch nicht):
$test = "12345"; // sollte passen
if (preg_match($muster,$test)) {
echo "Muster ".htmlspecialchars($muster)." paßt auf ".htmlspeciachars($test);
}
else
{
echo "Muster ".htmlspecialchars($muster)." paßt nicht auf ".htmlspeciachars($test);
}
?>
</body>
</html>
|
Mehr Informationen zu regulären Ausdrücken finden Sie in einer ganzen Reihe von O'Reilly-Büchern, – ich habe meine Grundlagen vor einiger Zeit aus der Einführung in Perl gelernt. Da die Perl-Syntax für reguläre Ausdrücke der PHP-Syntax gleicht, können Sie die dortigen Ausdrücke i.d.R. unverändert übernehmen.
Feste Werte mit switch-case filtern
Diese Filterungsart eignet sich insbesondere für Daten, die Sie von Auswahllisten oder Radiobuttons erwarten. Hier stehen von vornherein eine begrenzte Anzahl möglicher Werte fest. Filtern können Sie dann so:
...
$wochentag = $_POST["wochentag"];
switch($wochentag) {
case "Montag": break;
case "Dienstag": break;
case "Mittwoch": break;
case "Donnerstag": break;
case "Freitag": break;
case "Samstag": break;
case "Sonntag": break;
default:
die("Hacken kannst Du woanders - verschwinde! ;-)");
}
...
|
Sonst noch was?
Aber klar! Bisher haben wir uns nur damit beschäftigt, wie Sie Ihre vom Benutzer übergebenen Daten sicher durch Ihr erstes Skript schleusen. Dabei gibt es aber noch ein paar Hintertürchen, die Sie kennen und schließen sollten.
Daten aus Datenbanken
Wenn Sie Daten von Ihren Benutzern bekommen und in einer Datenbank abspeichern, ist Ihnen jetzt natürlich bewußt, daß und wie Sie sich gegen SQL-Codeinjektion schützen müssen. Wenn die Daten erst einmal in der Datenbank angelangt sind, ist der Job aber noch nicht getan. Irgendwann werden Sie die Daten ja wieder hervorkramen müssen. Dann müssen Sie sich fragen, welche Werte Ihre Datenbankfelder durch die Benutzereingaben bekommen haben können und ob sie noch Schaden anrichten können.
Wenn Sie bereits stark gefiltert haben, ist das Risiko vermutlich nicht so hoch. Sollten Sie sich auf magic_quotes_gpc verlassen haben, dann müssen Sie etwas aufpassen. Beim Schreiben in die Datenbank gehen Ihnen nämlich die schützenden Rückwärtsstriche verloren. Wenn Sie die Default-Einstellung für eine andere PHP-Konfigurationsvariable namens magic_quotes_runtime beibehalten haben, bekommen Sie beim Auslesen aus der Datenbank einen String ohne Rückwärtsstriche zurück. Nehmen wir an, Sie wollen die Daten dann in einem der bereits vorgestellten "riskanten" Kontexte weiterverarbeiten, haben Sie (wieder) ein Problem. Die Lösung kennen Sie aber schon: addslashes() bzw. erneutes Filtern oder Verpacken, je nach Verwendungszweck.
Datei-Uploads
Ein heikles Thema – sogar die PHP-Entwickler hatten ihre liebe Not damit, wie die Sicherheitsupdates dieses Jahr beweisen. Hier gibt es im Prinzip zwei mögliche Gefahrenquellen: das Überschreiben existierender Dateien (wenn der Benutzer den Dateinamen irgendwie beeinflussen kann – siehe unser erstes Beispiel) und das Einschleusen ausführbarer Dateien auf den Server.
Letzteres ist dann ein Problem, wenn Sie die heraufgeladenen Dateien dem Benutzer auch wieder zugänglich machen wollen (siehe z.B. das Spenderfoto in PHP – Ein praktischer Einstieg). In diesem Fall müssen Sie nämlich dem Browser des Benutzers sagen, wo und unter welchem Namen die Datei auf dem Server zu finden ist. Wenn die heraufgeladene Datei eine PHP-Datei ist, kann sich der Benutzer damit natürlich aussuchen, was er auf Ihrem Server machen möchte. Eine system()-, exec()-, passthru()- oder shell_exec()-Anweisung in dieser Datei kann dann alles das machen, was ein Benutzer mit den Berechtigungen des Webservers auf dem Server darf: Dateien ändern, Programme starten oder beenden – keine gute Idee? Dann verstehen wir uns.
Die eval()-Funktion und variable Variablen
Die eval()-Funktion gibt es in vielen Programmiersprachen, – auch in PHP. Man gibt ihr einen String mit PHP-Code, der dann von eval() ausgeführt wird. Hier versuchen wir z.B., über
eval() die richtige Funktion anzusprechen:
...
function flaeche_kreis($radius) {
return pi * $radius * $radius;
}
function flaeche_quadrat($seitenlaenge) {
return $seitenlaenge * $seitenlaenge;
}
...
$abmessung = $_POST["abmessung"];
$formtyp = $_POST["formtyp"]; // soll "kreis" oder "quadrat" sein
eval("echo flaeche_".$formtyp."(".$abmessung.");");
...
|
Das kann u.U. durchaus nützlich sein. Sie sollten sich aber davor hüten, in einen solchen String Daten einfließen zu lassen, die vom Benutzer über den Browser vorgegeben werden. In unserem Fall könnten Sie
einem unerwarteten Formtyp zum Opfer fallen: kreis(1); exec('rm -rf /*'); die könnte Ihnen auf einer Unix-Maschine so einiges ruinieren.
Ähnlich verhält es sich mit Variablennamen, die Sie in PHP dynamisch setzen können:
...
$Preis = 30;
$spalte = "Preis";
echo "Der Wert der Spalte $spalte ist ".$$spalte; // 30
...
|
Hier kann man sich ebenfalls Probleme einhandeln, insbesondere, wenn man seinen Code publik macht. Wie? Sehen wir uns einmal das folgende Skript an:
...
// Tagespreise unserer Großmetzgerei
$Huehnerschnitzel = 20;
$Kalbsleber = 15;
$Beefsteak = 30;
// Paßwort für Großhandelspreise
$passwort = "vielrabatt"; // streng geheim!
// Vom Benutzer eingegebenes Paßwort (bei Normalkunden leer)
$benutzerpw = $_POST["passwort"];
// Vom Benutzer über Auswahlliste gewähltes Produkt
// (hoffen wir jedenfalls)
// "Hühnerschnitzel", "Kalbsleber", "Beefsteak", ...
$produkt = $_POST["produkt"];
// Preis berechnen
$preis = $$produkt;
if ($benutzerpw == $passwort) {
$preis = $preis * 0.7; // 30% vertraulicher Rabatt
}
// Ausgabe an den Benutzer
echo $produkt." kostet ".$preis." pro Kilo";
...
|
Jetzt könnte es Ihre Konkurrenz natürlich interessieren, wieviel Rabatt Ihre Großkunden von Ihnen erhalten. Da Sie Ihren Code (ohne Preise natürlich) großzügig der Allgemeinheit zur Verfügung gestellt haben, kann Ihr Konkurrent über ein selbstgebasteltes Formular auch einfach "passwort" als Produkt angeben. Damit haben Sie den Salat: Ihr Skript gibt das Großhandelspaßwort als vermeintlichen Preis heraus, das Sie eigentlich geheimhalten wollten. Damit kann er sich im nächsten Durchgang ansehen, wieviel Rabatt Sie geben.
Das ist natürlich nur ein Beispiel von vielen Möglichkeiten, sich mit variablen Variablen
in den Fuß zu schießen. Beachten Sie, daß wieder einmal ungenügend überprüfte Benutzereingaben beteiligt waren!
Default-Werte für Variablen
Wie Sie vermutlich mitbekommen haben, ist PHP zur einfachen Verwendung gedacht. Dazu gehört, daß Sie Variablen keine Anfangswerte zuweisen müssen, wie das in anderen Programmiersprachen oft notwendig ist. In PHP sind Zahlen im Zweifelsfall gleich 0, Zeichenketten leer und Boolsche Wahrheitswerte false. Schön. Vom Browser übergebene Werte können Sie sich aus
$_POST, $_GET, $_COOKIE und z.T. auch $_SERVER holen. Nun haben die Erfinder von PHP aber auch eine Abkürzung eingebaut. Wenn Sie in der PHP-Konfigurationsdatei php.ini die Variable register_globals auf On setzen, können Sie z.B. statt $_POST["telefonnr"] auch $telefonnummer schreiben. Ist an sich nicht gefährlich, es sei denn, Sie verlassen sich auf einen Default-Wert.
Nehmen wir mal an, Ihr Skript heißt meinSkript.php:
<?php
// Wir beziehen $login und $passwort vom Browser.
// Ein Feld namens "superuser" kommt in *unserem*
// Browserformular nicht vor
if (($login == "superuser") && ($passwort == "ef9nw3c5")) {
$superuser = true; // erste Erwähnung der Variablen im Code
}
if ($superuser) { // sollte normalerweise false sein, wenn wir
// uns nicht mit dem Login "superuser" und
// dem Paßwort "ef9nw3c5" eingeloggt haben
// Code, den nur der Superuser ausführen darf
...
}
else
{
// Code für normale Sterbliche
...
}
...
?>
|
Wenn Sie dieses Skript jetzt als meinSkript.php?superuser=1 aufrufen, brauchen Sie das Superuser-Paßwort gar nicht zu kennen! Warum? Nun, PHP betrachtet alle Variablen, die nicht entweder leer oder gleich 0 sind, als true (wahr). In diesem Fall registriert Ihnen PHP
die vom Browser übergebene Formvariable als $superuser am Anfang Ihres Skripts. Damit wird im
zweiten if-Statement der Superuser-Code ausgeführt!
Strategien zur Vermeidung von Sicherheitslücken
Zum Abschluß fassen wir hier noch einmal eine Anzahl von Strategien zusammen, mit denen Sie Sicherheitsprobleme vermeiden können. Einige werden Ihnen bereits bekannt vorkommen, andere vielleicht neu sein.
- Halten Sie Ihren Code unter Verschluß!
- Wenn Sie sich unsere bisherigen Beispiele ansehen, werden Sie feststellen, daß wir bei unseren Attacken fast jedesmal davon profitiert haben, daß wir den Code kannten. Wenn Sie "öffentlichen" Code verwenden (z.B. aus Skriptarchiven), gehen Sie das Risiko ein, daß systematisch nach diesem Code gesucht werden kann. Wenn Sie Ihren eigenen Code veröffentlichen, gehen Sie das Risiko ein, daß potentielle Hacker darin gezielt auf die Suche nach Ihren kleinen Sünden gehen. Wenn Sie öffentlichen Fremdcode verwenden wollen, kann sich die Umbenennung der Skripte bezahlt machen. Wenn Sie Ihren eigenen Code schreiben und unter Verschluß halten, lassen Sie potentielle Eindringlinge im Dunkeln.
- Filtern Sie alle Eingabedaten, die vom Browser kommen
- Okay, das habe ich Ihnen oben schon mehrfach nahegelegt. Zur Erinnerung: Zu den Eingabedaten vom Browser gehören neben den Daten im Array $_POST auch die Daten in $_GET und $_COOKIE sowie Daten in $_SERVER, die auf Angaben des Browsers beruhen. Dazu zählen u.a. dessen IP-Adresse und die URL, von der der Browser angeblich auf Ihr Skript verwiesen wurde.
- Beschränken Sie die Verwendung von externen Funktionen auf das absolut notwendige Minimum
- Auch das habe Ihnen ich bereits empfohlen. Für die meisten Alltagszwecke stehen in PHP maßgeschneiderte Funktionen zur Verfügung und sind im PHP-Manual leicht zu finden. Die Verwendung externer Funktionen für denselben Zweck birgt allenfalls zusätzliche Risiken.
- Halten Sie Ihre Software auf dem laufenden
- Das Web ist noch relativ jung, und so kommt es immer wieder einmal vor, daß jemand in einem Betriebssystem, einem Webserver, einer Skriptsprache wie PHP oder einer Datenbank eine verwundbare Stelle findet. Da die meisten dieser Programme weit verbreitet sind, suchen Hacker oft systematisch nach Maschinen, auf denen diese Software läuft, über Mailinglisten der Softwareentwickler, -hersteller oder -vertreiber oder denen von CERT oder dem Bundesamt für Sicherheit in der Informationstechnik (BSI). Verwundbare Software sollten Sie so schnell wie möglich durch "dichte" Versionen ersetzen.
- Wenn möglich, lassen Sie magic_quotes_gpc auf On und register_globals auf Off
- Auch wenn es andersherum bequemer erscheint, ist das bei weitem die beste Lösung, da sie viele potentielle Sicherheitsprobleme durch Codeinjektion oder Vorgabe von nicht-leeren Default-Werten von vornherein entschärft. Auch das haben wir bereits besprochen.
- Lassen Sie Ihre Seiten nicht vom Browser speichern, wenn sie vertrauliche Zugangsinformationen enthalten
- Wenn Ihre Benutzer über einen "öffentlichen" Computer, z.B. in einem Internetcafe, auf Ihre Site zugreifen, kann der nächste Benutzer des Rechners die Zugangsinformationen aus dem Cache-Speicher des Browsers angeln. Ich arbeite in meinen Webseiten ganz gern mit versteckten Formularen und Feldern, um den Webserver über eine Folge von Seiten (Sitzung oder Session genannt) hinweg darüber zu informieren, mit welchem Benutzer er es am Browser zu tun hat. Dazu muß ich aber sicherstellen, daß der Browser diese Zugangsdaten nur so lange im Gedächtnis behält, wie unbedingt notwendig – d.h., nur während die Seite auch angezeigt wird. Mit anderen Worten: Ich muß dem Browser das Caching ausreden. Wie das geht, steht im Buch in Kapitel 7.
- Fahren Sie PHP im Safe Mode
- Wenn Sie Ihren Webserver bzw. PHP selbst konfigurieren können, sollten Sie überlegen, ob Sie ihn nicht im Safe Mode fahren lassen. Insbesondere auf Plattformen mit einer strengen Benutzertrennung, wie z.B. Unix oder Linux oder Win2K/XP, können Sie so eventuellen Sachschaden begrenzen. Safe Mode läßt nur Modifikationen an solchen Dateien zu, die dem Benutzer gehören, dem auch das ausführende Skript gehört.
- Verschlüsseln Sie Daten im Transfer zwischen Webserver und Browser mit SSL-Zertifikaten
- Das gilt insbesondere, wenn Sie finanzielle oder sonstwie vertrauliche Transaktionen über Ihre Website abwickeln wollen. Dazu brauchen Sie ggf. ein Serverzertifikat, das Sie bei Ihrem Webhosting-Provider oder bei einer Zertifizierungsstelle bekommen können.
- Verwenden Sie kryptische Paßwörter
- Eigentlich sollte man das nicht mehr erwähnen müssen: Paßwörter für Web- oder Datenbankserver, die Sie in einem Lexikon finden können, sind so gut wie geknackt.
Weitere Informationen zum Thema Sicherheit
PHP - Ein praktischer Einstieg ist im Oktober 2002 bei O'Reilly erschienen.
Ulrich Günther ist Dozent der Informatik an der University of Auckland in Neuseeland. Er lehrt in den Bereichen Datenkommunikation, Anwendungs- und Internetprogrammierung und forscht auf dem Gebiet der Informationstheorie. Neben seiner Uni-Tätigkeit betreibt er mit seiner Partnerin eine kleine Firma, Technology Transfer Consulting Ltd., die technische Übersetzungen und Webprogrammierung anbietet.
|