Tobias Wassermann
Sichere Webanwendungen mit PHP
Sicherheit mit PHP, MySQL, Apache, JavaScript, AJAX Sichere Sessions und Uploads, Lösungen gegen SQL-Injection und Cross-Site Scripting Umgang mit sensitiven Daten, Verschlüsselung und Authentifizierung mit SSL
Sichere Webanwendungen mit PHP
Tobias Wassermann
Sichere Webanwendungen mit PHP
Bibliografische Information der Deutschen Nationalbibliothek Die Deutsche Nationalbibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie. Detaillierte bibliografische Daten sind im Internet über http://dnb.d-nb.de abrufbar. ISBN 978-3-8266-1754-6 1. Auflage 2007
Alle Rechte, auch die der Übersetzung, vorbehalten. Kein Teil des Werkes darf in irgendeiner Form (Druck, Fotokopie, Mikrofilm oder einem anderen Verfahren) ohne schriftliche Genehmigung des Verlages reproduziert oder unter Verwendung elektronischer Systeme verarbeitet, vervielfältigt oder verbreitet werden. Der Verlag übernimmt keine Gewähr für die Funktion einzelner Programme oder von Teilen derselben. Insbesondere übernimmt er keinerlei Haftung für eventuelle aus dem Gebrauch resultierende Folgeschäden. Die Wiedergabe von Gebrauchsnamen, Handelsnamen, Warenbezeichnungen usw. in diesem Werk berechtigt auch ohne besondere Kennzeichnung nicht zu der Annahme, dass solche Namen im Sinne der Warenzeichen- und Markenschutz-Gesetzgebung als frei zu betrachten wären und daher von jedermann benutzt werden dürften.
Printed in Austria © Copyright 2007 by REDLINE GMBH, Heidelberg www.mitp.de
Lektorat: Sabine Schulz Fachkorrektorat: Michael Seeboerger-Weichselbaum Sprachkorrektorat: Manuel Jansen Satz: DREI-SATZ, Husby Druck und Bindung: Holzhausen Druck & Medien, Wien
Inhaltsverzeichnis
Einleitung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
11
1 1.1
Sicherheit im Kontext von PHP und Webanwendungen. . . . . . . . . . . Historie: PHP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
17 17
1.2
PHP heute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
19
1.3
PHP und Apache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
20
1.4
PHP als eigenständige Anwendung. . . . . . . . . . . . . . . . . . . . . . . . . . . .
21
1.5
PHP mit alternativen Webservern . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
22
1.6
Sicherheitskonzepte für Webanwendungen . . . . . . . . . . . . . . . . . . . . .
22
2 2.1
Fehlerquellen, die jeder PHP-Entwickler kennen sollte. . . . . . . . . . . . Variablenverfügbarkeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
29 29
2.2
Superglobale Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
35
2.3
Zugriff auf Uploads. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
37
2.4
Verzeichnisindizierung und Suchmaschinen. . . . . . . . . . . . . . . . . . . .
38
2.5
Index- und Default-Dateien . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
40
3 3.1
PHP und Dateien: Die häufigsten Fehler . . . . . . . . . . . . . . . . . . . . . . . Temporäre Dateien . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
43 43
3.1.1
Backup-Dateien, Versionsverwaltung und Zugriffsschutz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
44
Sessions: Permissive Konfiguration oder »Alles ist erlaubt«. . . . . . . .
52
3.2.1
Session-IDs nur durch Server vergeben . . . . . . . . . . . . . . . .
54
3.2.2
Regenerierung der Session-ID bei sensitiven Aktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
55
Absicherung durch TAN . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
56
3.3
Globaler Dateisystemzugriff. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
57
3.4
Auslieferung von Dateien . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
65
3.5
Include-Dateien . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
67
3.5.1
Ungeparste Dateiendung . . . . . . . . . . . . . . . . . . . . . . . . . . . .
67
3.5.2
Dateien in öffentlich zugänglichem Verzeichnis . . . . . . . . .
71
3.5.3
Externe Dateien und include . . . . . . . . . . . . . . . . . . . . . . . . .
76
3.2
3.2.3
Inhaltsverzeichnis
4 4.1
4.2
5 5.1 5.2 5.3 5.4
5.5
5.6 6 6.1 6.2
6.3
6
Sensitive Daten richtig behandeln . . . . . . . . . . . . . . . . . . . . . . . . . . . . Grundsatzprobleme. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.1.1 Falsche Request-Methode . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.1.2 Fehlerhafte Verarbeitung . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.1.3 Fehlerhafte SQL-Verwertung . . . . . . . . . . . . . . . . . . . . . . . . 4.1.4 Zwischenspeicherung von Sessions und Cookies. . . . . . . . 4.1.5 Cross Site Scripting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Lösungsansätze . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.2.1 Hash-Austausch via AJAX. . . . . . . . . . . . . . . . . . . . . . . . . . . 4.2.2 Vermeidung von unsicheren Passwörtern. . . . . . . . . . . . . . 4.2.3 Vergessene Passwörter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.2.4 Nur notwendige Daten übermitteln . . . . . . . . . . . . . . . . . . . 4.2.5 Datenprüfung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
83 83 83 86 90 91 92 97 98 108 116 123 125
Sessions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Flexibilität . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Strikte Erzeugung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Gültigkeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Session-Umgebung sichern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.4.1 Clientüberprüfung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.4.2 Ausschließlich Cookies verwenden . . . . . . . . . . . . . . . . . . . 5.4.3 Session-ID aus dem Referrer entfernen . . . . . . . . . . . . . . . 5.4.4 Zugriff auf Session-Dateien . . . . . . . . . . . . . . . . . . . . . . . . . 5.4.5 TAN-System . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Speicherung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.5.1 files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.5.2 mm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.5.3 user . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Sessions und Frames. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
135 135 141 151 154 154 157 158 159 160 165 165 167 168 181
Upload und Download . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Upload und PHP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.1.1 Uploads beschränken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Uploads prüfen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2.1 Prüfen von Bilddateien . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2.2 Dateityp feststellen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2.3 Dateiarchive . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Download und PHP. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.3.1 Throttling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.3.2 Referenzierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
183 183 185 199 199 203 204 205 205 210
Inhaltsverzeichnis
7 7.1
7.2
7.3 7.4
7.5 8 8.1 8.2
8.3 8.4
8.5
8.6
Dateisystemzugriffe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Wie greift PHP auf Dateien zu? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.1.1 Verschlüsselung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.1.2 Verschleierung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.1.3 Skripte schützen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Angriff auf dateibasierte Webanwendungen. . . . . . . . . . . . . . . . . . . . . 7.2.1 Vollständiges Auslesen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.2.2 Direkte Ausgabe. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.2.3 Zeilen/-blockweises Auslesen . . . . . . . . . . . . . . . . . . . . . . . . 7.2.4 Vollständiges Speichern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.2.5 Blockweises Speichern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Pfade und ihre Tücken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Dateiangaben als Parameter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.4.1 include, include_once, require und require_once. . . . . . . . . 7.4.2 Datendateien auslesen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.4.3 Dateisystemoperationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.4.4 Prozesse ausführen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.4.5 dl: Module nachladen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Thread- und Binär-Sicherheit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
217 217 218 220 222 224 225 228 229 229 230 230 237 237 241 242 244 247 248
SSL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Allgemeine Hinweise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . OpenSSL-Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.2.1 Installation unter Linux und Unix (Sourcen) . . . . . . . . . . . . 8.2.2 Installation unter Linux (Paket) . . . . . . . . . . . . . . . . . . . . . . . 8.2.3 Installation unter Windows . . . . . . . . . . . . . . . . . . . . . . . . . . 8.2.4 Konfiguration. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Erzeugung eines Zertifikats . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ein eigenes Zertifikat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.4.1 Erzeugung einer CA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.4.2 Das eigene Zertifikat erstellen . . . . . . . . . . . . . . . . . . . . . . . . Zertifikat in den Webserver integrieren . . . . . . . . . . . . . . . . . . . . . . . . 8.5.1 Apache-Webserver . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.5.2 Roxen Webserver . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.5.3 Microsoft Internet Information Server (IIS) . . . . . . . . . . . . SSL zur Authentifizierung nutzen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.6.1 Apache-Webserver . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.6.2 Roxen Webserver . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
251 252 253 253 254 254 254 255 257 258 259 261 261 262 262 266 267 272
7
Inhaltsverzeichnis
8.6.3 Microsoft Internet Information Server (IIS) . . . . . . . . . . . . 8.6.4 Import der Zertifikate mit dem Mozilla Firefox . . . . . . . . . 8.6.5 Import der Zertifikate mit dem Internet Explorer . . . . . . . SSL-Funktionen in PHP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.7.1 OpenSSL für PHP aktivieren . . . . . . . . . . . . . . . . . . . . . . . . 8.7.2 Zertifikatszugriff unter dem Apache-Webserver. . . . . . . . . 8.7.3 Zertifikatszugriff unter dem Microsoft Internet Information Server (IIS) . . . . . . . . . . . . . . . . . . . . . . . . . . . .
272
9 9.1 9.2 9.3 9.4 9.5 9.6 9.7 9.8 9.9 9.10 9.11 9.12 9.13
Konfiguration: PHP. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . php.ini. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Safe Mode. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Basisverzeichnis mit open_basedir . . . . . . . . . . . . . . . . . . . . . . . . . . . Speicherlimits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ausführungszeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Klassen und Funktionen deaktivieren . . . . . . . . . . . . . . . . . . . . . . . . . dl: Nachladen von Erweiterungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . allow_url_fopen und allow_url_includeURLs . . . . . . . . . . . . . . . . . . . Variablenverfügbarkeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Uploads. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Sessions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Fehlerausgabe und -verfolgung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . PHP verbergen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
279 279
10 10.1
PHP ohne Webserver . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Buffer Overflows und andere Tücken . . . . . . . . . . . . . . . . . . . . . . . . . 10.1.1 Variablengültigkeit. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.1.2 Buffer und Stack Overflows . . . . . . . . . . . . . . . . . . . . . . . . . 10.1.3 Nullbytes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Einschränkungen der Kommandozeile . . . . . . . . . . . . . . . . . . . . . . . . 10.2.1 Keine Zeitbeschränkung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.2.2 Arbeitsverzeichnis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.2.3 Argumentübergabe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Netzwerkprogrammierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.3.1 Authentifizierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.3.2 Transaktionssicherung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.3.3 Eingelieferte Daten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
313 313
8.7
10.2
10.3
8
273 274 274 275 276 277
280 283 285 291 292 293 294 295 299 301 310 312
313 315 318 318 319 321 322 323 323 324 325
Inhaltsverzeichnis
Die Datenbank als Fehlerquelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Unzureichende Berechtigungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.1.1 MySQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.1.2 PostgreSQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.1.3 MS SQL Server 2000 und 2005. . . . . . . . . . . . . . . . . . . . . . . Temporäre Dateien . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.2.1 MySQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.2.2 PostgreSQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.2.3 MS SQL Server 2000 und 2005. . . . . . . . . . . . . . . . . . . . . . . Unix-Sockets, Named Pipes und Shared Memory . . . . . . . . . . . . . . . . 11.3.1 MySQL: Unix-Socket und Named Pipes . . . . . . . . . . . . . . . . 11.3.2 PostgreSQL: Unix-Socket und Loopback . . . . . . . . . . . . . . . . 11.3.3 MS SQL 2000: Shared Memory. . . . . . . . . . . . . . . . . . . . . . . 11.3.4 MS SQL 2005: Shared Memory . . . . . . . . . . . . . . . . . . . . . . . SSL-Verbindungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.4.1 MySQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.4.2 PostgreSQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.4.3 MS SQL Server 2000 und 2005. . . . . . . . . . . . . . . . . . . . . . .
331 331 331 333 337 337 338 339 339 339 340 342 343 345 346 347 350 350
12 12.1 12.2 12.3 12.4 12.5
Die SQL-Injection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Kommentare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ergänzung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Zusätzliche SQL-Anweisungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Falsche Daten. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abhilfe gegen SQL-Injections. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
351 353 355 364 366 367
13 13.1 13.2
13.3 13.4
JavaScript: Der Client als Fehlerquelle . . . . . . . . . . . . . . . . . . . . . . . . . Fremde Aufrufe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Fehlerhafte Parameterverwertung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.2.1 Datentypprüfung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.2.2 Größenprüfung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.2.3 Logikprüfung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Was nicht in JavaScript-Code gehört . . . . . . . . . . . . . . . . . . . . . . . . . . . Formularvervollständigung und Co. . . . . . . . . . . . . . . . . . . . . . . . . . . .
371 371 377 378 382 385 395 398
14 14.1 14.2 14.3
Konfiguration: Webserver. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . PHP-Konfiguration durch Apache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Einsicht von Konfigurationsdateien. . . . . . . . . . . . . . . . . . . . . . . . . . . . CGI oder integriertes Modul . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
401 401 402 404
11 11.1
11.2
11.3
11.4
9
Inhaltsverzeichnis
14.4 14.5 14.6 14.7
suExec . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . chroot mit mod_chroot . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . URL-Sicherung mit mod_rewrite . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Benutzerverfolgung mit mod_log_forensic . . . . . . . . . . . . . . . . . . . . .
406 410 415 417
15 15.1
Entwicklungs-Guidelines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Variablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15.1.1 Existenzprüfung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15.1.2 Typ- und Wertprüfung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15.1.3 Typumwandlung (Casting) . . . . . . . . . . . . . . . . . . . . . . . . . . 15.1.4 Übergabeparameter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Sessions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Klassen und Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15.3.1 Objektorientierte Programmierung . . . . . . . . . . . . . . . . . . . 15.3.2 Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Dateizugriff . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15.4.1 Lesen von Dateien . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15.4.2 Schreiben von Dateien. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15.4.3 Dateisuche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ausgabe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15.5.1 echo() und printf() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15.5.2 Übertragung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15.5.3 HTTP-Header. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Aufruf externer Programme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
419 419 419 419 422 423 428 428 429 435 438 438 442 444 445 445 446 447 448
Stichwortverzeichnis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
453
15.2 15.3
15.4
15.5
15.6
10
Einleitung PHP ist inzwischen eine der am meisten verbreiteten Sprachen zur Entwicklung von dynamischen Webseiten, wobei sich PHP allerdings nicht nur auf Webanwendungen beschränkt, sondern universell einsetzbar ist. Allerdings war PHP von Anfang an nicht unbedingt auf Sicherheit bedacht – das Hauptaugenmerk lag eher auf der Einfachheit und Flexibilität. Mit der Verbreitung des Internets ist allerdings auch die Gefährdung gestiegen, die durch unsichere Programmierung entsteht. Wie diese Fehler erkannt und beseitigt werden, soll in diesem Buch ausführlich behandelt werden. Für weitere Informationen gibt es auch eine begleitende Webseite: http:// www.php-aber-sicher.de.
Zielgruppe Das Buch wendet sich natürlich an PHP-Entwickler – allerdings auch an Administratoren von Webservern. Dabei handelt es sich hierbei allerdings um kein Buch, das einen Einstieg in die Sprache PHP gibt – es wird ausschließlich die Sicherheit behandelt. Dennoch empfiehlt es sich nicht nur für Fortgeschrittene und Profis, sich hiermit zu beschäftigen: Einsteiger sollten dieses Buch parallel zu anderer Lektüre benutzen, um von Anfang an »auf der sicheren Seite« zu sein. Es ist wesentlich schwerer, schlechte Programmierangewohnheiten wieder loszuwerden, sobald man diese einmal verinnerlicht hat. Wesentlich unkomplizierter ist es, diese Fehler bereits präventiv zu verhindern. Für Webserver-Administratoren gibt es auch einige Hinweise zur Absicherung des Servers, z.B. welche Tücken in der php.ini umschifft werden können oder wie etwa eine Authentifizierung mittels SSL gewährleistet werden kann. Selbstverständlich finden Sie jeweils detaillierte Anleitungen, wie eine Aufgabe bewältigt werden kann – also wie etwa ein SSL-Zertifikat in den Webserver integriert wird. Allerdings konzentriert sich dieses Buch nicht nur auf PHP und den Apache-Webserver – es wird auch auf die beiden alternativen Webserver Roxen und den Internet Information Server von Microsoft eingegangen.
Einleitung
Natürlich dürfen Kapitel zu JavaScript in Zeiten von AJAX und ein detailliertes Datenbankkapitel, in dem Einzelheiten von MySQL, MS-SQL und PostgreSQL behandelt werden, ebenfalls nicht fehlen – nutzt man doch in Verbindung mit PHP vor allem Datenbanken und erweitert solche Webanwendungen durch Client-Code.
Sicherheit in Software? Softwaresicherheit war schon immer ein Stiefkind in der IT. Zu Beginn der PC-Entwicklung kam es eher auf Speicherersparnis an, danach ging es primär um Performancegewinnung. Schließlich galt eine Kombination dieser beiden Ziele als Optimum: Software sollte schnell und speicherschonend laufen. Die Sicherheit ist lange Zeit vernachlässigt wurden – dies betraf natürlich nicht nur lokale Anwendungen, wie etwa Office-Pakete, sondern später auch Webseiten. Diese Entwicklung kann man auch deutlich bei PHP verfolgen: Dort stand in den ersten Versionen keineswegs die Sicherheit im Vordergrund. Vielmehr ging es darum, eine Sprache zu schaffen, die relativ einfach zu erlernen ist, mit der flexibel Aufgaben der verschiedensten Art ausgeführt werden können und die zudem relativ fehlertolerant ist. In den Anfangszeiten des Internets war es schlichtweg undenkbar, dass es einmal Unternehmen geben wird, die ausschließlich mit dem Internet Gewinne erwirtschaften; vor diesem Hintergrund sollte der Aufwand für Webseiten natürlich möglichst gering gehalten werden. Eine Sprache wie PHP, die durchaus Fehler verzeiht, ist das ideale Werkzeug dafür. Die Vernachlässigung der Sicherheit hatte natürlich irgendwann ihre Folgen: Offen existierende Möglichkeiten wecken schlichtweg Begehrlichkeiten – hier unterscheiden sich Webseiten nicht von einem unverschlossenen Haus: Irgendwann wird jemand dahinterkommen und die Chance nutzen und in das Haus gelangen, um sich zu nehmen, was er brauchen kann. Bei Webseiten ist es ähnlich: Sind sie ungesichert, ist die Verlockung für einige einfach zu groß, an sensitive Daten wie etwa Adressen zu gelangen. Dabei kann allerdings der Begriff Sicherheit in Bezug auf Software auf verschiedene Weise definiert werden. Grundsätzlich gibt es drei Schlüsselbegriffe: Datenschutz, Datensicherheit und organisatorische Sicherheit, alle drei Konzepte werden im Kapitel 1 erklärt. Dieses Buch wird dabei vorrangig auf den Datenschutz abzielen, jedoch sind Überschneidungen etwa mit der organisatorischen Sicherheit – etwa wenn es um das Thema Dateizugriff geht – kaum zu vermeiden. Dieses Buch erhebt dabei keinesfalls den Anspruch, vollständig zu sein, oder bis zur allerletzten Konsequenz den totalen Schutz gewährleisten zu können. Gerade im Bereich Sicherheit in Bezug auf Webanwendungen ist ständig viel Bewegung im Spiel, Änderungen sollten von jedem Entwickler und Administrator verfolgt werden (mögliche Informationsquellen sind im Abschnitt Anlaufpunkte auf Seite 13
12
Anlaufpunkte
aufgeführt). Auch der totale Schutz wird nie gewährleistet sein. Sofern man auf aktuelle Techniken und Interaktion mit anderen Systemen setzt, ist dies auch kaum zu bewältigen. Wer jedoch von vornherein gar nichts für die Sicherheit unternimmt, der hat bereits verloren. Es ist auch nicht unbedingt notwendig, alle Techniken dieses Buches umzusetzen – einige davon sind nicht in jeder Umgebung sinnvoll. Man sollte jedoch die Tücken zumindest kennen, um das Risiko, das den eigenen Projekten droht, besser bewerten zu können.
Anlaufpunkte Sicherheit bedeutet Dynamik: Neue Versionen von Bibliotheken, Webservermodulen oder gar Betriebssystemteilen bedeuten meist eine Verbesserung der Sicherheit – doch es werden immer wieder neue Gefährdungen entdeckt. Deshalb sollte man sich stets aktuell informieren, wo der Hase im Pfeffer liegt. Hier ein paar Informationsquellen: 쐽
Die Mailingliste Full Disclosure Auf dieser Liste werden entdeckte Sicherheitslücken im Detail gepostet. Dabei enthalten sind neben einer genauen Beschreibung meist auch ein Proof-of-Concept-Block, in dem sich z.B. Quellcode findet, der darstellt, wie diese Lücke genutzt werden kann. Dies ist natürlich umstritten, jedoch zwingt es die jeweiligen Hersteller zum schnellen Handeln – würden die Lücken nur an den Hersteller gemeldet, könnte dieser sich Zeit lassen, nach dem Motto »Was der Hacker nicht weiß, macht ihn nicht heiß« verfahren und Gras über die Sache wachsen lassen. Wenn jedoch ein Anwender eine Lücke entdeckt, ist es zwangsläufig nur eine Frage der Zeit, bis es auch einem anderen Benutzer auffällt. Full Disclosure hat allerdings ein wesentliches Problem: Diese Liste ist unmoderiert. Das hat zwar den Vorteil, dass neue Meldungen ohne Latenzzeit auf die Liste gelangen, jedoch gibt es auch viele Meldungen und Streitereien, die schlichtweg überflüssig sind. Dennoch empfiehlt sich aufgrund der Brisanz eine Anmeldung. Melden Sie sich einfach unter http://lists.netsys.com zur Full-Disclosure-Liste an. Beachten Sie aber, dass es hier nicht nur um PHPund Webserver-Sicherheit geht, sondern alle Sicherheitslücken in Software auf dieser Liste gepostet werden.
쐽
BugTraq Hierbei handelt es sich um eine moderierte Mailingliste, bei der das Volumen um einiges geringer ist als bei Full Disclosure. Auf BugTraq findet man inzwischen fast nur noch Ankündigungen von Updates – tatsächliche Meldungen über bisher unbekannte Sicherheitslücken sind selten. Zur Anmeldung senden Sie einfach eine leere E-Mail an die Adresse
[email protected].
13
Einleitung 쐽
Webappsec Ebenfalls eine Mailingliste von SecurityFocus, jedoch spezifischer auf Webapplikationen ausgerichtet. Zur Anmeldung genügt eine leere E-Mail an
[email protected].
쐽
Secure Programming Diese Liste beschäftigt sich intensiv mit der sicheren Programmierung. Dabei geht es nicht nur – wie man vielleicht vermuten möchte – um C-Code, sondern auch um ASP.NET und PHP. Diese Liste beschränkt sich auf keine Programmiersprache, es geht vielmehr um Techniken zur Programmierung, die das Endprodukt – also die Software – sicherer machen sollen. Eine Anmeldung erfolgt hier mit einer leeren E-Mail an
[email protected].
쐽
SecurityFocus Auf http://www.securityfocus.com finden sich viele weitere interessante Mailinglisten; dort gibt es z.B. Incidents – hier werden potenzielle Angriffe besprochen. Ein Blick auf die Liste der zur Verfügung stehenden Listen lohnt allemal. Zudem bietet diese Seite auch aktuelle Sicherheitsinformationen außerhalb von Mailinglisten, sollte also nach Möglichkeit zur regelmäßigen Lektüre gehören.
쐽
Open Web Application Security Project Dies ist ebenfalls eine Webseite, die sich intensiv mit der Sicherheit von Webanwendungen beschäftigt. Allerdings wird diese Seite von Ihren Mitgliedsunternehmen getragen – dadurch ist die Unabhängigkeit möglicherweise gefährdet. Die Adresse: http://www.owasp.org.
쐽
PHP Natürlich darf die PHP-Seite in einer solchen Auflistung nicht fehlen. Neben der stets aktuellen Dokumentation (http://www.php.net/docs.php) finden sich hier auch aktuelle Versionsankündigungen sowie eine Möglichkeit, Bugs zu melden und momentan vorhandene Bugs einzusehen (http:// bugs.php.net/). Interessant sind auch die Mailinglisten (http://www. php.net/mailing-lists.php) und die durchaus interessanten Projektseiten (http://www.php.net/sites.php).
Natürlich ist dies nicht alles: Auf jeden Fall im Blick haben sollte man auch die Seiten der verwendeten Webserver- und Datenbanksoftware sowie aller genutzten Module. Auf vielen Seiten gibt es inzwischen Announcement-Mailinglisten, bei denen man sich registrieren sollte, um über Versionsaktualisierungen immer aktuell informiert zu sein.
14
Danksagung
Allerdings nützen viele Anmeldungen nichts, wenn man die E-Mails der Listen nicht zumindest einmal überfliegt. Dies ist viel Arbeit und kostet viel Zeit, jedoch lohnt es sich.
Danksagung Ich möchte natürlich auch Dank sagen, denn es gibt einige Personen, ohne deren Unterstützung dieses Buch gar nicht oder zumindest nicht in dieser Form zustande gekommen wäre. An erster Stelle sei natürlich meiner Frau gedankt, die mich für dieses Projekt wohl öfter entbehren musste als für alle vorhergehenden und mich dennoch tatkräftig unterstützt hat. Dann sei meinen zwei Freunden Chris und Mark gedankt, die allerdings diesmal im Gegensatz zu meinen vorhergehenden Büchern »Postfix Ge-Packt« und »Versionsmanagement mit Subversion« (beide mitp) diesmal nicht als Beispielbenutzer herhalten mussten – ein durch das gesamte Buch führendes Beispiel war diesmal einfach zu komplex. Dennoch hat mir der tägliche Kontakt zu ihnen viel für dieses Buch gebracht: Durch sie konnte ich erfahren, mit welchem Verständnis an PHP herangegangen und wofür es benutzt wird. Zudem hat mich Chris erst auf das Thema für dieses Projekt gebracht, was ich im Nachhinein nicht bereue. Ein herzliches »Vergelt’s Gott« gilt dem Verlag – der trotz der unerwarteten Dauer dieses Buchs an dem Projekt festgehalten hat, besonders gedankt sei hier meiner Lektorin Sabine Schulz, die klasse Arbeit leistet. Eine ebenso unschätzbare Hilfe war mein Fachlektor Michael Seeboerger-Weichselbaum, der mir aufgezeigt hat, wann mehr Details notwendig waren und wann überflüssig; er hat somit maßgeblichen Anteil an der Qualität des Inhalts. Ohne Lektorin und Fachlektor würde dieses Buch wahrscheinlich eher chaotisch aufgebaut sein.
15
Kapitel 1
Sicherheit im Kontext von PHP und Webanwendungen In diesem Kapitel gibt es einen Rückblick auf PHP als Sprache für Webanwendungen – und warum PHP so unschlagbar oft in Verbindung mit webbasierten Applikationen verwendet wird. Schließlich gibt es noch einen Ausflug in die Welt der Sicherheitskonzepte.
1.1
Historie: PHP
Wäre PHP seit jeher konsequent mit Fokus auf Sicherheit entwickelt worden, wäre dieses Buch entweder nicht so umfangreich oder es würde schlichtweg nicht erscheinen. PHP wurde jedoch ursprünglich lediglich als einfacher Form-Interpreter entwickelt – er sollte lediglich HTML-Formulare auswerten. Erst mit der Zeit hat es sich ergeben, dass mehr daraus wurde. Doch selbst als PHP in C neu entwickelt wurde, war noch nicht absehbar, was aus dieser Sprache einmal werden würde – und vor allem konnte niemand ahnen, welche Sicherheitslücken und konzeptionellen Probleme sich mit der teilweise unkontrollierten Verbesserung des Interpreters ergeben würden. Weiterhin kann man feststellen: PHP stammt noch aus einer Zeit, zu der man in Bezug auf das Netz der Netze euphorisch war: Es gab diejenigen, die nichts damit anzufangen wussten, und diejenigen, die voller Hoffnung waren, was mit der globalen Vernetzung alles möglich sein wird. Keinesfalls kam jemand auf die Idee, dass es einmal – respektive heute – Menschen geben wird, die versuchen werden, Sicherheitslücken in Software auszunutzen um anderen wirtschaftlichen Schaden anzufügen. Hätte man das damals gewusst, so wäre die Entwicklung von PHP bestimmt eine andere gewesen: Entweder hätte es PHP nie gegeben, oder es wäre so restriktiv, dass es auf Dauer niemand verwenden würde. PHP stand einmal für »Personal Homepage Tools« und erschien am 8. Juni 1995, der damalige Entwickler war Rasmus Lerdorf. Seine Intention für die Entwicklung: Protokollierung der Zugriffe auf seinen Online-Lebenslauf. Er veröffentlichte danach noch PHP/FI (Forms Interpreter) Version 2.0 am 12. November 1997; danach gab es eine für damalige Verhältnisse interessante Wendung: Die Version 3.0 von PHP wurde nicht mehr von Rasmus Lerdorf sondern von Andi Gutmans und Zeev Suraski entwickelt.
Kapitel 1 Sicherheit im Kontext von PHP und Webanwendungen
Rasmus Lerdorf hat mit den beiden Entwicklern zusammengearbeitet, sogar der »Rewrite Call« kam ursprünglich von ihm. PHP/FI wurde somit offiziell eingestellt, PHP war der zukünftige Name, wobei auch die Bedeutung in ein Backronym verändert wurde: PHP: Hypertext Preprocessor. Die beiden neuen Entwickler handelten aus der Motivation heraus, eine Sprache zu schaffen, mit der es möglich ist, komplexe eCommerce-Anwendungen zu realisieren, die in der Folge natürlich hauptsächlich Daten dynamisch erzeugen sollten. Diese neue Version löste somit PHP/FI Version 2.0 ab, das eigentlich stets nur als Beta-Version existierte – PHP 3.0 erschien also am 6. Juni 1998, wobei es vorher eine neunmonatige öffentliche Testphase gab. Diese Version kann man als Durchbruch für die Verbreitung von PHP bezeichnen. Weiterhin kam hinzu, dass der Apache-Webserver immer größere Verbreitung fand (was auch daran lag, dass Microsoft die Entwicklung des Internets etwas »verschlafen« hatte, und somit kein weit verbreitetes Konkurrenzprodukt existierte). Die Synthese zwischen PHP und diesem Webserver wurde mit PHP 3.0 massiv vergrößert, wobei allerdings auch sichergestellt werden sollte, dass PHP 3.0 ebenfalls mit anderen Systemen oder ganz ohne Webserver-Software arbeiten kann. PHP 3.0 hatte allerdings keinen Funktionsumfang, der eine professionelle Nutzung rechtfertigen würde – zumindest aus heutiger Sicht, es war jedoch ein Grundstein, da es damals lediglich mit Perl ein vergleichbares System gab. PHP war und ist allerdings um einiges leichter zu erlernen; dies gilt besonders dann, wenn man bereits vorher Erfahrungen in den Programmiersprachen C und/oder Java gemacht hat, da die Syntax an diese beiden Sprachen angelehnt ist. Andi Gutmans und Zeev Suraski gründeten die Zend Technologies Ltd., die sich auch heute noch für die Entwicklung von PHP verantwortlich zeichnet, und starteten die Implementation der Zend Engine in Version 1; diese sollte später der Kern von PHP 4.0 werden. Mit dieser neuen Version wurde explizit die Sicherheit bei der Verwendung von globalen Variablen verbessert, es wurde auch ein SessionManagement eingeführt (das für Anwendungen, die Daten zwischen verschiedenen Aufrufen transportieren müssen, essentiell ist) und die Unterstützung für alternative Webserver wurde deutlich verbessert. Das Veröffentlichungsdatum von PHP 4.0 war der 22. Mai 2000. Doch die Entwicklung von PHP blieb natürlich nicht stehen, es ging weiter – auch wenn eine neue Version deutlich länger auf sich warten ließ, als man das von Version 3 und 4 gewohnt war. Am 13. Juli 2004 wurde PHP 5.0 freigegeben. Erstmals ist damit objektorientiertes programmieren möglich (Version 3 und 4 haben OOP nur sehr rudimentär unterstützt), auch wenn viele Funktionen von PHP selbst immer noch prozedural aufgerufen werden. Mit der Objektorientiertheit kam auch die Einführung von Exceptions, die die Fehlerbehandlung deutlich verbessern. Eine weitere wichtige Neuerung in Bezug auf das Web 2.0: DOM wurde objektorientiert realisiert, so ist es viel effizienter möglich, auf die Elemente eines DOM-
18
1.2 PHP heute
Baumes – wie er etwa bei der XML-Kommunikation mit einer AJAX-Client-Anwendung entsteht – zuzugreifen. Die neueste Version, die einen Meilenstein darstellt, erschien etwa anderthalb Jahre später (24. November 2005): PHP 5.1. Hier kam nun mit PDO eine Datenbankabstraktionsschicht hinzu, mit der es möglich ist, mit den gleichen Funktionsaufrufen auf verschiedene SQL-basierte Datenbanken zuzugreifen.
1.2
PHP heute
PHP wurde über die Jahre weiterentwickelt und entspricht heute einer Interpretersprache, die als typsicher klassifiziert werden kann.
Hinweis Typsicher gilt eine Sprache dann, wenn der Compiler bzw. Interpreter vor Verarbeitung einen Test daraufhin vornimmt, ob die gewünschte Aktion mit den übergebenen Variablentypen zulässig ist. Im Fall von PHP wird zusätzlich noch versucht, die Daten in einen passenden Typ zu konvertieren. Die Sprache an sich ist heute relativ mächtig; das liegt sowohl am vorhandenen Funktionsumfang als auch an der Möglichkeit, diese Funktionalität durch Module noch erweitern zu können. Beides – Komplexität und Modularität – tragen pragmatisch gesehen nicht sehr viel zur Sicherheit bei; auch war der Fokus bei PHP nicht auf ein extrem sicheres System gerichtet: Es sollte eine einfach erlernbare Sprache werden, die dennoch vielfältigste Aufgaben bei der Verarbeitung von Daten bewältigen kann. Diese Einfachheit hat vor allem beim Umgang mit Variablen seine Spuren hinterlassen, die es so in vielen anderen Programmiersprachen nicht gibt. Die Möglichkeit, recht lax mit Variablen und bereitgestellten Daten umzugehen, hat allerdings auch im Hinblick auf Teile des PHP-Clientels – Menschen, die erst mit PHP den Einstieg in die Programmierung geschafft haben – zu einer Menge Problemen geführt. So werden Variablen angesprochen, ohne vorher sicherzustellen, ob diese überhaupt existieren oder gar gültige Werte enthalten. Doch auch die universelle Einsetzbarkeit auf vielen verschiedenen Betriebssystemen hat zu teilweise starken Kompromissen bei der Sicherheit von beispielsweise temporären Dateien geführt. Diese Liste der Probleme, die durch das Konzept von PHP entstehen, eine Sprache zu schaffen, mit der es in kurzer Zeit möglich sein soll, komplexe eCommerce-Anwendungen zu erstellen, bedingt auf der anderen Seite Stolperfallen, die man umfahren muss, wenn man von der Sicherheit der Daten und der Applikation abhängig ist.
19
Kapitel 1 Sicherheit im Kontext von PHP und Webanwendungen
Und noch einmal: Auch wenn es so klingt, PHP ist keineswegs eine unsichere Sprache, die besser nicht verwendet werden sollte. Es handelt sich um ein System, mit dem viele komplexe Sachverhalte im Vergleich zu anderen Sprachen vergleichsweise einfach und dennoch effektiv abgebildet werden können. Diese übertriebene Negativbewertung von PHP auf diesen Seiten soll lediglich die Sensitivität für Softwaresicherheit besonders in Bezug auf Online-Applikationen erhöhen, denn dies ist das wichtigste um eine sichere Umgebung zu schaffen: Die Probleme müssen erkannt werden, bevor es möglich ist, sie zu beheben.
1.3
PHP und Apache
Schon seit PHP 3.0 gibt es eine starke Symbiose zwischen PHP und dem ApacheWebserver, der ebenfalls in seiner Umgebung eines der am meisten verbreiteten Systeme ist. Die Integration von PHP in Apache ist denkbar einfach, denn neben der PHP-Konsolenanwendung kann – sofern der Apache bzw. dessen Tool apxs vorhanden ist und beim Kompilierungsvorgang berücksichtigt wird – gleich das Apache-Module erstellt und in die Konfiguration des Webservers eingebunden werden. Weiterhin unterstützt der Apache nach dem Laden dieses Moduls auch einige neue Konfigurationsdirektiven, die Auswirkung auf die Funktionsweise von PHP haben und somit die PHP-seitige Konfiguration überschreiben. Diese sehr direkte Kopplung kann allerdings auch ziemliche Probleme bereiten: Ist PHP als Apache-Modul aktiviert, ist der jeweilige Apache-Prozess, auf dem gerade eine PHP-Datei verarbeitet wird, stark abhängig von der erfolgreichen Arbeitsweise des PHP-Interpreters. Kommt es in diesem Fall zu einem Fehler oder gar dem GAU – einem Deadlock – so wird der entsprechende Apache-Client-Prozess nicht mehr korrekt reagieren. Im harmloseren Fall wird der Prozess einfach serverseitig abgebrochen und der Benutzer erhält den HTTP-Fehler 500. Im wesentlich schlechteren Fall – etwa wenn ein Deadlock auftritt – wird der Apache-Prozess nicht mehr reagieren; der Client erhält irgendwann ein Verbindungs-Timeout, danach wird der Apache-Prozess zwar weiterhin auf dem Server laufen und nicht mehr verwendbar sein, aber er wird wahrscheinlich nur etwas Arbeitsspeicher verbrauchen (es gibt auch hier Ausnahmen: kritisch wird es, wenn der Prozess auch noch Rechenzeit verbraucht). Allerdings kann es in seltenen Fällen auch zu einer gewissen Dramatik kommen: So ein hängender Prozess kann einen Server so in Mitleidenschaft ziehen, dass ein Abbrechen nicht mehr möglich und eventuell ein harter Reboot (hart bedeutet Power off!) notwendig ist. Es gibt ein Szenario, bei dem dieser Fall definitiv und relativ einfach reproduzierbar auftritt: Beim Lesen von großen Dateien, beachten sie hierzu auch unbedingt Kapitel 8 Dateisystemzugriffe. Dies waren die Nachteile der starken Kopplung von Apache und PHP – jedoch kann man das ganze auch in das Gegenteil verkehren: Durch eine gut durchdachte Kom-
20
1.4 PHP als eigenständige Anwendung
bination der Konfigurationen beider Systeme ist schon einmal ein gutes Stück Arbeit auf dem Weg zum sicheren System geschafft. Zudem kann man einen zusätzlichen Puffer schaffen und so die direkte Abhängigkeit des Webservers von der Ausführung des PHP-Interpreters aufheben, indem man PHP nicht direkt als Apache-Modul lädt, sondern die Ausführung etwa als CGI oder mittels suExec und ähnlichen Modulen ermöglicht. Wenn Sie sich dafür interessieren, so wird sicherlich Kapitel 11 das Richtige für Sie sein.
1.4
PHP als eigenständige Anwendung
Es ist wie bei anderen Interpretersprachen auch bei PHP möglich, den Interpreter standalone zu betreiben. Dies ermöglicht es, PHP-Skripte losgelöst von einem Webserver zu betreiben. Grundsätzlich sollte klar sein: PHP selbst erzeugt als Ausgabe lediglich Bytes, ob es sich dabei nun um reinen Text, HTML-Quelltext oder gar ein PDF-Dokument handelt, ist für PHP selbst erst einmal uninteressant – es macht lediglich das, was der Programmierer vorgibt. PHP als alleinigen Interpreter zu verwenden, hat mehrere Makel und sollte wohlüberlegt sein. Zum Einen können natürlich alle Funktionen, die direkt auf eine HTTP-Verbindung abzielen, möglicherweise nicht benutzt werden. Dies trifft immer dann zu, wenn PHP wirklich eigenständig betrieben wird (und nicht etwa lediglich als CGI von Apache oder einem anderen Webserver aufgerufen wird). Selbstverständlich sind diese Funktionen aufrufbar, doch solange der Prozess, der aktuell PHP aufruft (also etwa die Kommandozeile), nicht damit umgehen kann, wird entsprechend die Ausgabe zum Teil unübersichtlich oder gar unnütz. Und die Probleme können noch weitergehen: Schutzmechanismen, die etwa über den Webserver aktiviert werden können, stehen bei einem direkten Aufruf meist nicht zur Verfügung. Wird PHP mit einem Webserver betrieben, kann etwa bereits mit dem Modul mod_rewrite verhindert werden, dass bestimmte Clients auf PHP-Skripte zugreifen, und es kann mittels der durch den Server bereitgestellten Authentifikation der Benutzerkreis eingegrenzt werden (diese Authentifizierung ist in jedem Fall sicherer als eine selbst implementierte Login-Funktionalität). Zum Anderen ist ein selbstständiger Betrieb von PHP aus sicherheitstechnischer Sicht durchaus etwas, was man, vor allem wenn man auf externe Tools oder Module, Klassen und Bibliotheken aus unsicheren Quellen angewiesen ist, in Betracht ziehen sollte, um einen eventuellen Schaden am System weiter zu begrenzen. Läuft diese Anwendung auf einem niedrig privilegierten Benutzer, so sind zumindest auf einem Linux-System die möglichen Schäden begrenzt (dies gilt auch für Windows-Systeme, doch wird dort leider von benutzerbasierten Zugriffsrechten selten Gebrauch gemacht). Stürzt PHP ab, wird es so auch wahrscheinlich keine anderen Prozesse in Mitleidenschaft ziehen, außer es wird beispielsweise eine rechenintensive Endlosschleife o.Ä. initiiert, die das System so stark auslasten, dass andere Prozesse nicht mehr zeitnah arbeiten können.
21
Kapitel 1 Sicherheit im Kontext von PHP und Webanwendungen
Und natürlich erweitert sich das Einsatzgebiet von PHP auch deutlich, wenn es ohne Webserver verwendet wird, da Skripte dann im Dauerbetrieb gestartet werden können und so beispielsweise eine Netzwerkserveranwendung realisierbar ist, die darauf wartet, dass sich Clients verbinden und Daten anfordern. Dieses Feature wird vor allem in Kapitel 12 beschrieben.
1.5
PHP mit alternativen Webservern
Mit anderen Webserversystemen (etwa dem Microsoft Internet Information Server oder dem Roxen Webserver) kann PHP entweder als CGI-Modul oder als SAPIModul betrieben werden. SAPI steht dabei für Server Application Programming Interface, es handelt sich also um eine festdefinierte Schnittstelle, über die PHP-Daten vom Webserver bereitgestellt werden und über die PHP selbst auch wieder Daten an den Server zurückliefern kann. Auch hier gilt wie bei der Symbiose zwischen Apache und PHP: SAPI ist eindeutig schneller als CGI, jedoch sollte die Konfiguration des Webservers genutzt werden, um gravierendere Angriffe auf das System zu vereiteln.
1.6
Sicherheitskonzepte für Webanwendungen
Schon immer gab es Versuche, in Datenverarbeitungsanlagen gewaltsam einzudringen und entweder an empfindliche Daten zu gelangen oder das System zu stören, so dass der Betreiber dieses mit hohem Aufwand wiederherstellen muss (und dabei eventuell auch Daten verliert). Bei komplexen Systemen, die Webserver zweifellos sind, ist es allerdings nicht ausreichend, lediglich die Webanwendungen selbst sowie die Software des Webservers abzusichern. Einem Angreifer, der über einen anderen Weg in das System gelangt, würde es dann auf jeden Fall möglich sein, entsprechende Aktionen durchzuführen, die die Bereitstellung der Webanwendung beeinträchtigen. Das Sichern der verwendeten PHP-Skripte sollte also nur ein Bestandteil eines Sicherheitskonzeptes sein. Hinzu kommt die restriktive Konfiguration des Webservers und des PHP-Moduls. Zudem sollte jeder Rechner, der Daten nach außen bereitstellt, durch eine Firewall abgesichert werden. Hier ist eine Hardware-Firewall vorzuziehen – doch eine softwarebasierte Version ist in jedem Fall immer noch besser, als ganz auf einen Schutz zu verzichten. Es ist wichtig, eine Absicherung der Webanwendungen nicht als Aufgabe zu sehen, die schnell nebenbei erledigt werden kann. Wichtig ist hier die Anwendung eines Sicherheitskonzeptes; dies umfasst dann nicht nur die zu schützende Software bzw. die sensitiven Daten (egal, ob es sich nun um den Quelltext der Applikation
22
1.6 Sicherheitskonzepte für Webanwendungen
selbst oder um damit gewonnene kritische Daten, wie etwa personenbezogene Daten von Kunden, handelt), sondern das System, auf dem eine Webanwendung betrieben werden soll, als Ganzes. Je nach Umgebung und Wichtigkeit kann das »Ganze« somit sowohl lediglich für den einzelnen Server als auch für das gesamte Netzwerk stehen.
Hinweis Auf den folgenden Seiten wird das Thema IT-Sicherheit lediglich angerissen; mehr ist im Rahmen dieses Buches auch nicht möglich, denn schließlich liegt der Fokus auf den möglichen Sicherheitslücken in Webanwendungen und direkt betroffener Software und nicht in Grundsatztheorien über sichere Systeme. Falls Sie sich bisher nie mit diesem Thema beschäftigt haben, ist weitergehende Lektüre auf jeden Fall sinnvoll. Jedes Sicherheitskonzept sollte die grundlegenden Ziele der IT-Sicherheit verfolgen. Haben Sie ein Konzept für Ihre Umgebung entwickelt (es gibt hierbei keine pauschale immer gleich effektive Lösung – Sicherheit ist individuell!), können Sie grundsätzlich feststellen, ob dieses Konzept allen Erfordernissen gerecht wird, indem Sie es bezüglich der folgenden Ziele prüfen; diese sollten stets erfüllt werden: 쐽
쐽
쐽
Datenschutz 쐽
Vertraulichkeit: Daten dürfen nur von autorisierten Anwendern einsehbar sein.
쐽
Übertragungssicherheit: Lediglich der Anwender und das System sollen die Daten während der Übertragung auslesen. Die Einsicht online durch Dritte muss unterbunden werden.
쐽
Privatsphäre: Persönliche Daten und die Anonymität müssen gewährleistet sein. Wichtig ist hier auch das Recht auf informationelle Selbstbestimmung.
쐽
Datenschutzgesetze: Die Datenschutzgesetze müssen eingehalten werden.
Datensicherheit 쐽
Deterministik: Hard- und Software sollten so funktionieren, wie es erwartet wird. Das Resultat sollte dabei immer dem gewünschten Ergebnis entsprechen.
쐽
Integrität: Software und Daten dürfen nicht unbemerkt verändert werden können.
쐽
Authentizität: Die Echtheit von Daten muss überprüfbar sein.
Organisatorische Sicherheit 쐽
Verbindlichkeit: Es sollte für jeden Anwender offensichtlich sein, zu welchem Ergebnis eine bestimmte Aktion führt. Dabei sollte die gleiche Aktion stets zum selben Resultat führen (vgl. auch Deterministik).
23
Kapitel 1 Sicherheit im Kontext von PHP und Webanwendungen 쐽
Beweisfestigkeit: Alle Übertragungen müssen so dokumentiert und gegebenenfalls vom Anwender bestätigt werden, dass sie auch im Falle eines Rechtsstreites Gültigkeit besitzen und ein Vorgang jeweils einwandfrei nachgewiesen werden kann.
쐽
Zugriffssteuerung: Der Zugriff muss reglementiert sein; hierbei gibt es allerdings eine deutliche Abgrenzung zum Punkt »Vertraulichkeit«. Bei der Vertraulichkeit geht es lediglich darum, dass Daten nur von berechtigten Benutzern gelesen und/oder verändert werden können. Bei der Zugriffssteuerung geht es zudem darum, dass Benutzer, die diese Daten nicht verwenden können, auch keinen Zugriff darauf haben – also dass es etwa für Internetbenutzer nicht ersichtlich ist, dass diese Daten überhaupt existieren.
쐽
Verfügbarkeit: Hier geht es primär um die Verhinderung von Datenverlust, sekundär sollte ein gutes Sicherheitskonzept natürlich auch nach Möglichkeit Systemausfälle verhindern.
Folgenden Risiken sollte zudem durch das Konzept explizit entgegnet werden:
24
쐽
Computerviren, Trojaner und Würmer: Ein Anti-Viren-Programm ist für jedes System unabdingbar.
쐽
Backdoors: Alle aus- und eingehenden Verbindungen, die nicht vom System und den verwendeten Anwendungen resultieren, sollten generell durch die Firewall gekappt werden.
쐽
Spionage, Hacking und Cracking: natürlich ist es ein Ziel jedes Administrators, solche Angriffe zu verhindern. Hierfür kann es zwei entscheidende Schritte geben: Zum Einen sollte sämtliche Software, die auf dem Server verwendet wird (auch Zusatzmodule, die beispielsweise lediglich mit PHP verlinkt sind!), ständig aktualisiert werden. Das Intervall zur regelmäßigen Aktualisierung sollte drei Wochen betragen. Zum Anderen ist der Einsatz eines Intrusion Detection Systems empfehlenswert; dabei sollte sowohl das klassische IDS, das eingehende und ausgehende Netzwerkverbindungen analysiert, als auch eine Dateisystemüberwachung, die Konfigurations- und Programmdateien auf Veränderungen überprüft, eingesetzt werden.
쐽
Phishing und andere Identitätsangriffe: Besonderes Augenmerk sollte – sofern mit einer Webanwendung personenbezogene Daten verarbeitet oder gespeichert werden – auf eindeutige Erkennbarkeit gelegt werden. Es ist erforderlich, dass Ihre Webanwendung für jeden Anwender erkennbar ist – dies kann z.B. durch ein SSL-Sicherheitszertifikat einer offiziellen Ausgabestelle sichergestellt werden.
쐽
Spoofing: Verlassen Sie sich niemals auf Informationen, die Sie durch eine dritte Ebene erlangen können. Akzeptieren Sie also bei einer Authentifizierung beispielsweise niemals eine IP-Adresse als gegeben – jede Herkunftsinformation lässt sich im Zweifelsfall ohne großen Aufwand fälschen.
1.6 Sicherheitskonzepte für Webanwendungen 쐽
Höhere Gewalt: Eine Absicherung gegen Stromausfall und Überspannung gehört auch zu einem Sicherheitskonzept, vor allem da durch Überspannung bedingt ein Datenverlust eintreten könnte.
쐽
Social Engineering: Dieses Thema ist lediglich dann interessant, wenn es sich um eine Umgebung in einem größeren Unternehmen handelt oder der Support, der etwa Passwörter neu vergibt, ausgelagert wurde. Wenn ein Anwender eine Information oder gar eine Änderung an seinem Account anfordert, ohne dass er sich vorher über das System authentifiziert hat, sollte stets sichergestellt werden, dass es sich wirklich um diesen Benutzer handelt. Aber: Die übliche obligatorische Frage nach dem Namen des Haustiers oder dem Geburtsnamen der Mutter sollte hier keine Anwendung finden, da sich viele dieser Informationen über Suchmaschinen recherchieren lassen.
Die Erstellung und die anschließende – kompromisslose – Umsetzung des Sicherheitskonzepts sollten in Firmen selbstverständlich sein. Doch leider ist dies vor allem in mittelständigen Unternehmen nicht der Fall; da IT-Sicherheit als solches lediglich offensichtliche Kosten jedoch keinen sichtbaren Nutzen und schon gar keinen in der Bilanz monetär sichtbaren Erfolg bringt, wird auf dieses Thema entweder aus finanziellen oder zeitlichen Gründen gern verzichtet. Doch auch der private Serverbetreiber sollte ein Konzept realisieren, auch wenn dies erst einmal mit sehr viel Aufwand verbunden ist. Dieser Aufwand scheint umso größer, wenn man betrachtet, dass er lediglich zur Sicherung eines Hobbys oder bestenfalls eines Nebenerwerbs dient. Doch die Gründe, die für mehr Sicherheit sprechen, sind bei einem privat betriebenen Server, der sensible Daten beinhaltet oder gar speichert, umso größer. Kommt es hier zu einem erfolgreichen Angriff und Daten werden entwendet und gar an zweifelhafte Dritte verkauft, können strafund zivilrechtliche Folgen umso schwerwiegender sein. Damit Sie einen Anhaltspunkt haben, wie Sicherheit in der Praxis gewährleistet werden kann, folgt nun eine Liste von Praktiken, die angewendet werden können, um ein Sicherheitskonzept umzusetzen; diese ist natürlich nicht vollständig und kann lediglich als Anregung dienen: 쐽
Software aktualisieren: Wie bereits erwähnt, sollte jede benutzte Software in regelmäßigen Abständen aktualisiert werden.
쐽
Anti-Viren-Software verwenden: Auch wenn lediglich Programme aus scheinbar zuverlässigen Quellen bezogen werden und niemand auf dem Server Anwendungen installieren oder ausführen kann, ist ein Virenprogramm ein Muss.
쐽
Diversifikation: Es sollte möglichst viel Software von verschiedenen (am besten auch nicht marktführenden) Herstellern verwendet werden. Applikationen von Markführern ist oft Ziel von Angriffen; das Risiko kann bereits etwas mini-
25
Kapitel 1 Sicherheit im Kontext von PHP und Webanwendungen
miert werden, wenn man sich softwaremäßig nicht vollkommen in die Hand eines Herstellers begibt.
26
쐽
Firewalls verwenden: Auch schon mehrfach aufgeführt: Eine Firewall ist unabdingbar.
쐽
Benutzerrechte einschränken: Vor allem unter Windowsbenutzern beliebt ist es, dass jeder Benutzer auf alles zugreifen darf. Für Server, die aus dem Internet zu erreichen sind, sollte ein differenziertes Rechtesystem verwendet werden. Vor allem ist es wichtig, dass die Benutzer, die die zentralen Dienste wie Webserver und Datenbankserver betreiben, entweder nur Leserechte auf die wirklich notwendigen Daten (dies gilt dann allen voran für den WebserverUser) oder nur volle Zugriffsrechte auf die zu ändernden Dateien (hiermit ist der Datenbankserver gemeint) erhalten.
쐽
Sensible Daten verschlüsseln: Dies ist vor allem in Webanwendungen eher schwer zu realisieren. Werden Daten in einer Datenbank abgelegt, sollten eventuell im DBMS vorhandene Möglichkeiten der verschlüsselten Speicherung der Datenbankdateien genutzt werden. Werden Daten hingegen direkt in Dateien – etwa in Text- oder XML-Dateien – abgelegt, sollte auf jeden Fall in Betracht gezogen werden, diese zu verschlüsseln. Alternativ kann auch die gesamte Festplatte verschlüsselt werden. Hier muss jedoch beachtet werden, dass bei Problemen mit dem System an sich eventuell eine Entschlüsselung und somit ein Zugriff auf die Daten nicht mehr möglich ist.
쐽
Sicherungskopien erstellen: Trotz aller Absicherungen von der unterbrechungsfreien Stromversorgung und Überspannungsschutz bis hin zum Booten des Betriebssystems von einer CD-ROM kann es dennoch zu einem Systemausfall kommen. Besonders wenn es zu Hardwaredefekten kommt und ein anderer Rechner verwendet werden muss, ist es notwendig, die bestehenden Daten relativ zeitnah und ohne Umstände (also z. B. den Umbau der Festplatte aus dem »alten« Server) bereitzustellen. Hierfür sollten regelmäßige Sicherheitskopien bereitstehen.
쐽
Protokollierung: Um Fehlverhalten nachvollziehen zu können, sollten Protokolle – soweit sie sinnvoll sind und die Performance des Systems nicht zu stark beeinträchtigen – aktiviert werden. Allerdings ist es damit nicht getan; selbst wenn das System »reibungslos« funktioniert, sollen diese Protokolle regelmäßig durchgesehen werden, da sich vor allem Hardwaredefekte schleichend ankündigen und so bereits frühzeitig über Protokolldateien erkannt werden können.
쐽
Überprüfung: IT-Sicherheit ist ein kontinuierlicher Prozess, der ständig angepasst werden muss. Es sollte also regelmäßig geprüft werden, ob die ergriffenen Maßnahmen noch zu den Risiken passen. Vor allem, wenn neue Software installiert wurde, sollte das Sicherheitskonzept gezielt geprüft werden.
1.6 Sicherheitskonzepte für Webanwendungen 쐽
Sensibilisierung: Das beste System ist nutzlos, wenn alle Sicherheitsbestimmungen durch unvorsichtige Mitarbeiter leichtfertig umgangen werden. Auf dem Produktivsystem sollte niemals etwas getestet werden, es sollten auch keinesfalls Dateien mal eben für irgendjemand freigegeben werden – diese vorübergehenden Freigaben werden gern vergessen und ein böswilliger Dritter ist über die Bereitstellung dieser vielleicht sogar sensiblen Daten sehr dankbar.
27
Kapitel 2
Fehlerquellen, die jeder PHP-Entwickler kennen sollte In diesem Kapitel lernen Sie Fehlerquellen kennen, die nicht in der Arbeit mit Dateien begründet liegen, sondern durch die Verwendung von Variablen entstehen; zusätzlich wird hier noch auf die gravierendsten Konfigurationsfehler eingegangen, die eine PHP-Anwendung angreifbar machen können.
2.1
Variablenverfügbarkeit
Hinweis Dieses Thema wird fast vollständig an dieser Stelle behandelt; lediglich im Kapitel 4 finden sich im Abschnitt register_globals auf Seite 86 noch weiterführende Hinweise. Bei der Variablenverfügbarkeit handelt es sich um eines der dunkelsten Kapitel der PHP-Geschichte; die Problematik des Umgangs mit Variablen stammt wohl noch aus der PHP-3-Zeit – und es ist historisch gewachsen. Die Mentalität von »damals« ist heute noch sehr tief bei vielen PHP-Entwicklern verwurzelt. PHP hat beim Umgang mit Variablen nur recht wenige Einschränkungen, so muss eine Variable nicht deklariert werden. Wird sie das erste Mal verwendet, so wird sie angelegt. Zudem hat eine Variable keinen bestimmten Typ. Der Typ ist nur solange der gleiche, wie die Variable den aktuellen Inhalt hat – mit einer neuen Zuweisung kann auch der Datentyp gewechselt werden. Problemlos ist also folgender Code möglich: $test = 500; $test = 500.01; $test = "abc";
Hierbei wird die Variable $test zu Beginn als Integer (Ganzzahl) initialisiert, dann als Double (Fließkomma) und als String verwendet. Auch eine Änderung durch eine Berechnung ist kein Problem:
Kapitel 2 Fehlerquellen, die jeder PHP-Entwickler kennen sollte
$test = 500; $test += 500.01:
Dieser Code würde in vielen anderen Programmiersprachen einen Fehler des Compilers bzw. des Interpreters verursachen – er würde darauf hinweisen, dass eine Addition eines Fließkommawertes zu einem Ganzzahlwert zu einer Verfälschung des Ergebnisses führen kann, da der Endwert wieder lediglich eine Ganzzahl sein darf. Unter PHP wird aus $test einfach eine Fließkommazahl – die Variable wird also das Ergebnis 1000.01 beinhalten. PHP verwendet eine schwache, dynamische und implizite Typisierung. Dies macht PHP zu dem, was es ist: Einer sehr flexiblen Sprache, die sowohl den Anforderungen von Einsteigern als auch Profis gerecht wird. Durch diesen sehr einfachen Umgang mit Datentypen ist es jedem Einsteiger möglich, mit PHP zu entwickeln, ohne sich vorher in Gültigkeitsbereiche einzuarbeiten oder sich vor der Entwicklung eines Scripts über die möglichen Werte von Variablen Gedanken machen zu müssen. Gleichzeitig ist es auf der anderen Seite fortgeschrittenen Benutzern problemlos möglich, flexibel mit Daten umzugehen und diese zu konvertieren, sobald dies notwendig ist. Leider hat dieser flexible Umgang auch einige Nachteile, die sogar zu schwerwiegenden Sicherheitslücken führen können. Bereits mit PHP 3 wurden Funktionen eingeführt, die einen gewissen Schutz vor der Übermacht der Variablenverwaltung mit PHP bieten sollten; jedoch wurden diese Funktionen nicht übermäßig publiziert – man muss also wissen, wo die Probleme liegen und mit welchen Funktionen man etwas dagegen tun kann. Es gibt nun zwei hauptsächliche Problemstellungen, die bei PHP im Umgang mit Variablen entstehen können: 1. Nicht vorhandene Variablen sollen ausgewertet werden. 2. Es wird eine Operation ausgeführt, die für den aktuell zugewiesenen Typ nicht sinnvoll ist. Beide lassen sich elegant mit PHP-Bordmitteln beseitigen bzw. umgehen. Vor allem das erste Problem geht auch mit Fehlern, die durch das Vorhandensein und den Umgang mit globalen Arrays entstehen, einher. Beachten Sie hierzu auch den Abschnitt 2.2 Superglobale Arrays auf Seite 35. Nicht definierte Variablen lassen sich mit der Funktion isset() prüfen. Jede Variable sollte, sofern sie nicht gesetzt ist und nachher in irgendeiner Form ausgewertet oder verwendet wird, initialisiert werden. Die einfache Schachtelung der Verwendung einer Variablen in einer if-Bedingung ist relativ unsicher gegenüber späteren Code-Erweiterungen. Dies soll das folgende Beispiel verdeutlichen:
30
2.1 Variablenverfügbarkeit
if(isset($test_file)) { syslog(LOG_INFO, "try to open file $test_file"); echo file_get_contents($test_file); } … $size = filesize($test_file);
Die letzte Zeile stellt dabei eine Codeerweiterung dar, die später vorgenommen wurde. $size wird im weiteren Code wahrscheinlich noch für weitere Auswertungen verwendet. In diesem Fall ist das Problem nicht so schwerwiegend, da $size im schlimmsten Fall den Wert Null annimmt. Doch dieses Beispiel soll nur das Problem an sich verdeutlichen: Wird die Verfügbarkeit einer Variable schlicht angenommen, kann dies zu unerwarteten Ergebnissen führen. In wesentlich gravierenderen Szenarios können Sie mit solch einem Code sogar dazu beitragen, Informationen an Dritte weiterzugeben, die eigentlich nicht für die Weitergabe gedacht sind. Wird nämlich beispielsweise eine Variable für die Erstellung einer SQL-Abfrage genutzt, kann dies durchaus zu Problemen führen. Als Beispiel dient folgender Code: $result = mysql_query("SELECT * FROM tableA WHERE id=$id");
Wenn nun $id nicht initialisiert ist, wird dies zu einer Fehlermeldung des MySQLModuls von PHP führen. Je nach Einstellung wird dann der fehlerhafte SQL-String an den Client durchgereicht, für diesen also sichtbar. In diesem Fall weiß der Endbenutzer ganz klar, welche Tabelle hier abgefragt wird; dies kann bei einer laxen Datenbankkonfiguration, die etwa einen Zugriff mit einem »schwachen« Passwort erlaubt, zu einem richtigen Problem werden. Sinnvoller wäre in oben genanntem SQL-Beispiel dann folgender Code: if(!isset($id)) $id = 0; $result = mysql_query("SELECT * FROM tableA WHERE id=$id");
Das Setzen von $id auf den Wert 0 würde zu keinem Fehler des Datenbankmoduls (vorausgesetzt id in der Tabelle tableA ist eine Zahl), sondern lediglich zu einem leeren Suchergebnis in $result führen. Alternativ wäre natürlich auch diese Lösung denkbar:
31
Kapitel 2 Fehlerquellen, die jeder PHP-Entwickler kennen sollte
if(isset($id)) $result = mysql_query("SELECT * FROM tableA WHERE id=$id"); else $result = null;
Dabei würde $id unberührt bleiben, das Endergebnis – nämlich die Variable $result – würde auf jedem Fall zu einem gültigen Wert führen. In diesem Fall müsste allerdings sichergestellt werden, dass $id später nicht mehr in einem anderen Zusammenhang verwendet wird. Nachdem die Existenz einer Variablen gesichert ist, stellt sich immer noch die Frage, ob der Wert auch einem erwarteten Typ entspricht. Hierfür gibt es unter PHP mehrere Möglichkeiten. Welche zum Einsatz kommt, hängt von der Toleranz ab, mit der ein Skript arbeiten soll. PHP kann auf der einen Seite entweder prüfen, ob eine Variable von einem bestimmten Typ ist, ob sie in Wert und Typ übereinstimmt oder sie gar in einen gewünschten Typ konvertieren (der Wert wird dabei so konvertiert, dass er zum neuen Typ passt). Soll eine Null-Toleranz umgesetzt werden, und das Script nur weiterarbeiten, wenn der Wert einer Variablen dem gewünschten Datentyp entspricht, so kommen die is_*-Funktionen zum Einsatz. Dabei kann man zurückgreifen auf: 쐽 is_array(): Die geprüfte Variable ist ein Array. 쐽 is_bool(): Die geprüfte Variable ist vom Typ bool. 쐽 is_double(), is_float()
und is_real(): Die geprüfte Variable ist vom Typ
float (Fließkommazahl). 쐽 is_int(), is_integer()
und is_long(): Die geprüfte Variable ist vom Typ
integer (Ganzzahl), 쐽 is_object():
Die geprüfte Variable ist vom Typ object, es handelt sich also um eine instanzierte Klasse.
gibt an, ob die geprüfte Variable eine Resource ist, also etwa das Ergebnis einer Datenbanksuche.
쐽 is_resource()
쐽 is_string(): Die zu prüfende Variable ist eine Zeichenkette.
Es gibt in diesem Zusammenhang noch die Funktionen is_callable(), is_null(), is_numeric() und is_scalar(). Diese prüfen jedoch lediglich den Wert auf eine bestimmte Bedingung oder Bedingungsgruppe; wenn diese Funktionen den Wert wahr liefern, bedeutet dies nicht automatisch, dass eine Variable von einem bestimmten Typ ist oder dass bestimmte Operationen ohne Weiteres möglich sind.
32
2.1 Variablenverfügbarkeit
Tipp Den is-Funktionen sollte generell ein isset()-Aufruf vorgeschaltet sein! Mit den oben genannten Funktionen kann nun der Typ einer Variablen geprüft werden. Nun wird es Situationen geben, in denen etwa über eine if-Anweisung ein Code-Block nur dann ausgeführt werden soll, wenn die Variable einen bestimmten Wert beinhaltet. Doch auch hier können wieder Probleme mit dem Datentypen entstehen, sofern er beim Vergleich keine Beachtung findet. Ein Beispiel: if($id=="001") { // do some stuff }
Dieser Code sieht zunächst relativ harmlos aus: Es wird verglichen, ob $id den Wert "001" enthält. Dies ist jedoch falsch: Was hier vom PHP-Interpreter tatsächlich verglichen und für wahr befunden wird, hängt stark vom Datentyp der Variable $id ab. PHP ist bei Vergleichen grundsätzlich darauf bestrebt, gleiche Datentypen herzustellen. Dies bedeutet beim Vergleich von einem Integer mit einem String, dass der Datentyp einer der beiden Vergleichswerte angepasst wird. Dabei wird Integer vor String bevorzugt. Somit wird "001" in einen Ganzzahlwert umgewandelt – also zu 1. Dies bedeutet, dass aus dem beabsichtigtem Vergleich $id=="001" ein $id==1 wird. Das ist natürlich nicht das, was mit der Bedingung eigentlich beabsichtigt wurde. Um nun tatsächlich auf die Zeichenkette "001" zu testen, ist es notwendig, dass auch der Typ mit verglichen wird. Um dies zu gewährleisten gibt es verschiedene Möglichkeiten. Der simpelste Weg ist das Konvertieren aller Werte zu dem Typ, der verglichen werden soll. Dies entspricht fast dem Automatismus von PHP, nur dass in diesem Fall der Programmierer festlegt, welcher Typ zum Vergleich verwendet werden soll und somit das Ergebnis nicht wie im obigen Beispiel unerwartet ist. Doch bereits hier wird es kniffelig: Es gibt mehrere Wege, über die eine solche Konvertierung vorgenommen werden kann. Jeder dieser Wege hat seine Berechtigung. Vor allem kommt es bei der Entscheidung darauf an, wie nachhaltig die Konvertierung sein soll: Soll ein Wert nur für einen Vergleich zu einem anderen Datentyp umgewandelt werden oder soll diese Veränderung von Dauer sein? Soll die Konvertierung dauerhaft sein, so wird die Variable selbst entsprechend konvertiert (mitsamt dem Wert); dafür kommt die Funktion settype() zum Einsatz, die die Variable und den neuen Typ als String erwartet. Folgende Typen sind erlaubt:
33
Kapitel 2 Fehlerquellen, die jeder PHP-Entwickler kennen sollte
bzw. boolean: Die Variable wird in den Wahrheitswert konvertiert. Für diese Konvertierung gibt es eine Liste von Werten, die zu einem false führen (alles andere wird zu einem true transformiert!):
쐽 bool
쐽 false selbst 쐽
Ganzzahl 0
쐽
Fließkommazahl 0.0
쐽
Zeichenketten "0" und ""
쐽
Array ohne Elemente
쐽
Objekt ohne Mitgliedsvariablen
쐽
Typ und Wert NULL
bzw. integer: Nach der Umwandlung wurde der vorherige Wert in eine Ganzzahl konvertiert.
쐽 int
Möchten Sie einen String in eine Ganzzahl umwandeln, so werden lediglich die Zahlen aus dem originalen Wert übernommen, ungültige Zeichen (wie etwa Buchstaben) werden einfach ausgefiltert; somit findet also in jedem Fall eine gültige Konvertierung statt. oder double: Steht für die Umwandlung in eine Fließkommazahl. Für die Umwandlung ist lediglich wichtig: Zeichenketten müssen als Dezimaltrennzeichen den Punkt verwenden – die Lokalisierungseinstellungen des Systems bleiben unbeachtet.
쐽 float
Ungültige Zeichen werden – wie bei der Umwandlung eines Strings zu integer – ausgefiltert. 쐽 string:
Der Wert wird in eine Zeichenkette transformiert. Es gibt hier allerdings auf Basis des originären Datentyps vielfältige Arbeitsweisen. Ist der Wert ein boolean, so wird true in den String "1" umgesetzt, für false hingegen wird der leere String "" geliefert. Ein Integer wird einfach in einen entsprechenden String umgewandelt, während bei Fließkommazahlen auch das Exponententeil in die Zeichenkette aufgenommen wird.
Variablen vom Typ array und object lassen sich nicht direkt in einen String umwandeln: Der String wird lediglich aus der Zeichenkette "Array" oder "Object" bestehen. Ressourcen hingegen werden stets in den String "Resource id #XX" (XX ist die PHP-interne Ressourcennummer) transformiert. Abschließend gilt noch: NULL wird immer in den leeren String "" umgewandelt. Es lässt sich also anhand des Strings nachher nicht mehr erkennen, ob der ursprüngliche Wert NULL oder false war (beides führt zum Leerstring). 쐽 array:
Die Umwandlung zu einem Array wird vorgenommen. Hierbei kommt es darauf an, von welchem Typ die Variable vorher war. Für integer, float,
34
2.2 Superglobale Arrays
string, bool und resource gilt: Das Array wird nach der Umwandlung einen Wert mit dem Schlüsselindex 0 beinhalten, in dem der Wert der ursprünglichen Variable gespeichert ist. Ein null-Wert wird in ein leeres Array transformiert, is_array() wird also später ein true ergeben, count() wird jedoch 0 zurückliefern, da sich keinerlei Elemente innerhalb des Feldes befinden.
Daten vom Typ object werden etwas komplexer umgewandelt: Jede Mitgliedsvariable wird als Schlüssel verwendet, dem der jeweilige Wert zugeordnet wird. 쐽 object:
Eins vorweg: Ist die Variable bereits vom Typ object, so ändert sich nichts, es findet keinerlei Umwandlung statt. Handelt es sich jedoch um einen anderen Basistyp, so wird eine Instanz der in PHP integrierten Klasse stdClass erzeugt; die Mitgliedsvariable scalar enthält dann den ursprünglichen Wert.
쐽 null:
Variablen dieses Typs können lediglich einen Wert annehmen: null. Eine solche Konvertierung ist nur dann sinnvoll, wenn Sie sicherstellen wollen, dass eine Variable zurückgesetzt werden soll und Sie gleichzeitig sicherstellen möchten, dass man nicht mehr erkennen kann, von welchem Typ die Variable ursprünglich einmal war.
Soll die Umwandlung nicht dauerhaft stattfinden oder ist Ihnen der Aufruf von settype() zu aufwändig, so kann auch die explizite Umwandlung (der soge-
nannte Cast) verwendet werden. Dabei wird einfach der neue Typ von Klammern umgeben der umzuwandelnden Variablen vorangestellt. Dabei erfolgt die Umwandlung sozusagen »live«; die Variable selbst wird nicht konvertiert. Natürlich ist es Ihnen freigestellt, den Wert im Rahmen einer Zuweisung in die gleiche Variable zu übernehmen und somit die Variable selbst zu konvertieren. Mögliche Typen sind hier: bool, int, float, string, object, null. So sind folgende Konstellationen für die Umwandlung möglich: settype($test, "boolean")
2.2
Superglobale Arrays
Seit PHP 3.0 existieren mehrere globale Arrays. Diese sind meist sogar superglobal, stehen also in jedem Script in jedem Gültigkeitsbereich zur Verfügung, ohne dass sie beispielsweise in Funktionen mittels global importiert werden müssen. Die am Häufigsten verwendeten superglobalen Arrays dürften $_GET, $_POST, $HTTP_GET_VARS, $HTTP_POST_VARS und $_REQUEST sein. Innerhalb dieser Arrays stehen alle Daten zur Verfügung, die von außen kommen. Hier finden sich also alle Parameter, die durch einen Client übermittelt wurden. All diese Eingaben werden unter dem Namen als Schlüssel abgelegt. Allerdings sollten übermittelte Parameter nicht zwingend als vorhanden vorausgesetzt werden. Es gibt verschie-
35
Kapitel 2 Fehlerquellen, die jeder PHP-Entwickler kennen sollte
dene Varianten, die dazu führen können, dass ein erwarteter Array-Eintrag nicht vorhanden ist: 쐽
Kein originärer Aufruf: Das Skript wurde nicht durch das erwartete Skript aufgerufen, dadurch fehlen etwa Parameter. Es ist z.B. denkbar, dass ein Suchmaschinen-Roboter Ihre Seiten indiziert; er wird diese eventuell ohne Parameter aufrufen.
쐽
Nichterwartetes Clientverhalten: Ein Client hat sich nicht so verhalten, wie es etwa durch eine HTML-Seite erwartet wurde und liefert teilweise invalide Aufrufparameter. Denkbar ist hier beispielsweise eine JavaScript-Inkonformität. Für JavaScript müssen URL-Parameter nicht mit dem für HTML spezifizierten &-Zeichen sondern mit der umgewandelten Variante & voneinander getrennt werden. In manchen Fällen kann es jedoch dazu kommen, dass ein Client diese Konvertierung nicht wieder in den erforderlichen Ursprungszustand umwandelt. Der Server nutzt dies dann als einen Parameter und trennt es nicht wie erwartet auf.
쐽
Fehler in der Übertragung: Bei der Übermittlung zum Webserver kann es zu Übertragungsproblemen kommen. Sind diese nicht schwerwiegend genug, wird der Webserver diese Daten verarbeiten, und dann eventuell Parameter falsch übermitteln.
Bedingt durch diese Fehlerquellen muss also stets vor einem Zugriff auf Parameter festgestellt werden, ob dieser im superglobalen Array vorhanden ist. Hier sollte also stets die Funktion isset verwendet werden. Beispiel: if(isset($_REQUEST["testparam"])) …
Hinweis isset() kann auch für Variablen verwendet werden!
Wird die Existenz eines Parameters oder einer Variable nicht geprüft, so wird diese von PHP für die Verwendung gesetzt und mit "" oder 0 (je nach Typ des Parameters, der dann etwa für eine Funktion notwendig ist) initialisiert. Dies kann fatale Folgen haben, etwa wenn es sich um Daten handelt, die in einer Datenbank eingetragen werden sollen oder gar als Kriterium für das Löschen von Daten verwendet werden. Wird dort auch noch ein Wildcard-Operator in der Datenbankabfrage verwendet, kann dies bedeuteten, dass alle Daten einer Tabelle gelöscht werden, ohne dass dies beabsichtigt war.
36
2.3 Zugriff auf Uploads
2.3
Zugriff auf Uploads
Uploads in Webanwendungen sind eine sehr zwiespältige Sache; für Downloads gilt dies umso mehr. Jedoch auch bei Uploads sollten Dateinamen und vor allem Inhalte nie für eine direkte Beeinflussung verwendet werden. Schlechter Stil ist es zum Beispiel, ein SQL-Statement aus einer hochgeladenen Datei auszuführen; noch ungeeigneter ist es jedoch, eine hochgeladene Datei als include-PHP-Datei zu verwenden. Bei extern eingelieferten Dateien kann deren Ungefährlichkeit nie zweifelsfrei festgestellt werden – deswegen sollten diese Daten auch sehr restriktiv behandelt werden. Uploads sind nötig, das steht außer Frage. Vor allem für ContentManagement-Systeme und Community-Sites (hier kann man an YouTube & Co. denken) lässt es sich nicht umgehen, beispielsweise den Upload von Bildern oder gar Dateien, die später heruntergeladen werden sollen, zuzulassen. Jedoch sollten hochgeladene Dateien nie einen direkten Einfluss auf die Arbeit einer Webanwendung haben. Doch selbst wenn diese Dateien recht harmlos verwendet werden, stellen Sie stets eine Gefahr dar (man denke hier an Buffer-Overflow-Attacken, die mit verschiedenen Lücken in Bildformaten durchgeführt werden können). Um die Möglichkeit der Einlieferung von kompromittierenden Dateien einzuschränken, sind bereits vor dem Upload einige Maßnahmen erforderlich. Es ist auch ohne Weiteres möglich, PHP-Code direkt in einem GIF-Bild einzufügen. Wird eine solche hochgeladene Datei mittels include direkt inkludiert, wird der eingeschleuste Code ausgeführt. Besser ist es dann, die Bild-Datei – wenn ein serverseitiges Öffnen notwendig ist – über andere Wege zu öffnen, etwa über einen fopen-Aufruf. Empfehlenswert ist hier etwa die Abfrage eines Sicherheitscodes, der in einem erzeugten Bild im Hochladeformular angezeigt wird. Ein Download wird nur zugelassen, wenn zuerst der richtige Code eingegeben wurde – es handelt sich hier also um einen zweistufigen Upload. Zuerst muss der entsprechende Sicherheitscode (der nicht im Formular in einem versteckten Feld, sondern lediglich innerhalb der Session hinterlegt sein sollte) verifiziert werden. War dieser gültig, kann im zweiten Schritt die Datei hochgeladen werden. Ein weiterer dramatischer Fehler ist es zu glauben, dass die Angabe von MAX_FILE_SIZE in entsprechenden HTML-Skripten dafür sorgt, dass nur Dateien
bestimmter Größe an den Server übertragen werden. Hier gilt es zu bedenken: MAX_FILE_SIZE ist eine Angabe, die vom Client interpretiert wird, der Client muss
dabei kein Browser sein. Außerdem gibt es bis heute keinen Browser, der den Benutzer etwa beim Absenden des Formulars warnt, dass diese Datei die angegebene Größe überschreitet. Doch kann man den Browser-Herstellern hier keinen Vorwurf machen: MAX_FILE_SIZE wurde bis heute in keiner RFC aufgenommen und ist somit kein Standard.
37
Kapitel 2 Fehlerquellen, die jeder PHP-Entwickler kennen sollte
Nähere Information zum richtigen Umgang mit Up- und Downloads finden Sie im Kapitel 6 dieses Buches.
2.4
Verzeichnisindizierung und Suchmaschinen
Jede Seite soll möglichst weit oben in der Auflistung der Suchmaschinen – allen voran Google – erscheinen. Heute gilt: Wer nicht in Google ist, den gibt es nicht. Diese Suchmaschine sollte man sich also zum besten Freund machen. Doch auch bei Freunden gilt: Gelegenheit macht Diebe. Nein, das hier ist keineswegs das Lexikon der Lebensweisheiten, jedoch sollte man bei Dateien auf einer Webseite, die von verschiedenen Robotern indiziert wird, stets daran denken, dass der falsche temporäre Link im falschen Moment dafür sorgen kann, dass sensitive Daten für jeden noch jahrelang – der Cache-Funktion der Suchmaschinen sei Dank – sichtbar sind. Besonders tragisch ist dies natürlich, wenn es sich um Zugangsdaten handelt. Es gibt hierfür ein Musterbeispiel, das leider immer wieder seinen Weg in die Realität findet. Zugangsdaten für den Apache-Webserver werden über die .htaccess-Methode gespeichert. In einer Datei, die meist auf den Namen .htaccess lautet, sind hierbei verschiedene Eckdaten notiert, etwa wo sich die Passwortdatei findet oder ob bestimmte IP-Adressen von Haus aus authentifiziert sind. Die Passwortdatei heißt dabei meist .htpasswd; der Punkt als Präfix der Datei stammt dabei aus der Linuxund Unix-Welt: Dies bedeutet, dass die Datei versteckt ist, sie wird vom normalen ls-Kommando (dem dir aus der DOS-Welt) nicht aufgelistet. Hinzu kommt, dass der Webserver meist so konfiguriert ist, dass Dateien, die mit einem Punkt beginnen nicht nach außen durchgereicht werden. Fordert ein Client beispielsweise die .htaccess an, so wird diese vom Webserver eben dann nicht übermittelt. Damit soll verhindert werden, dass Dritte an Zugangsdaten gelangen. Die Passwörter innerhalb der Passwortdatei sind zwar verschlüsselt, doch wer diese Datei einmal hat, kann sich meist mit dem Ermitteln der originären Kennwörter Zeit lassen. Weiterhin sind einige FTP-Server genauso konfiguriert. Sie lassen den Download von Dateien, die mit einem Punkt beginnen, nicht zu. Jedoch ist es manchmal notwendig, solche Dateien »mal eben« herunterzuladen. In vielen Fällen benennt man die Datei dann kurzfristig um; so wird aus .htaccess und .htpasswd etwa _htaccess und _htpasswd. Dies wäre nicht weiter tragisch, jedoch werden dann meist auch noch Links zu diesen Dateien gelegt, damit Kollegen und Mitentwickler diese Dateien schnell herunterladen können, etwa um ein Testsystem, das dem Produktivsystem gleicht, anzulegen. Kommt nun in diesem Moment ein Suchmaschinenroboter auf die Seite, wird er diese Links selbst auch indizieren – und die verlinkten Dateien _htaccess und _htpasswd ebenfalls. Besitzt nun eine Suchmaschine wie Google eine Cache-Funk-
38
2.4 Verzeichnisindizierung und Suchmaschinen
tion, ist es zwecklos, den Dateien wieder ihren ursprünglichen Namen zu geben und die Testlinks zu entfernen. In Google ist eine gezielte Suche nach solchen Dateien auch recht einfach möglich. Um beispielsweise nach htpasswd-Dateien zu suchen, kann als Suchmuster inurl:htpasswd verwendet werden. Zusammen mit der Cache-Funktion kann so die Benutzer- und Passwortliste einer Domain recht einfach bezogen werden. Schwerwiegend wirkt sich hier natürlich auch aus, dass Passwörter selten geändert werden. So kann »man« sich beim »entschlüsseln«1 der Passwörter recht viel Zeit lassen. Doch nicht nur Dateien, die der Authentifizierung dienen sollen und somit eine Webseite gegen Manipulationen Dritter absichern sollen, können sich in Verbindung mit Suchmaschinen als Gefahr herausstellen. Vor allem dann, wenn Seiten über eine Authentifizierung gesichert werden, die nicht vom Webserver bereitgestellt wird, sondern die auf PHP-Sessions aufbaut, wird es sehr kritisch. Hier kann es vor allem bei Suchmaschinen zu fatalen Folgen kommen. Wird etwa von einer Seite, die öffentlich zugänglich ist, auf eine andere innerhalb eines »Sicherheitsbereichs« verlinkt, die dann einfach davon ausgeht, dass der Benutzer, der auf diese Seite gelangt, die notwendigen Berechtigungen hat, wird dies mit einem Suchmaschinenroboter, der alles indiziert, was ihm in den Sinn kommt, außer Kraft gesetzt. Zum Einen gelangen Benutzer auf eine Seite, die für sie nicht gedacht war und zum Anderen gelangen Anwender durch dieses »Portal« (vielmehr handelt es sich dann um ein Scheunentor) auf andere Seiten, die eigentlich ebenfalls durch eine Passwortabfrage gesichert werden sollten. Übrigens: Nicht nur Suchmaschinen stellen hier eine Gefahr dar. Jeder Aufruf einer URL wird mit den entsprechenden GET-Parametern auch vom Webserver im Protokoll gespeichert. Diese werden dann teilweise auch von Statistiksoftware wieder ausgegeben. Hier besteht also auch die Möglichkeit, dass Benutzer, die die Statistik einsehen, an URLs der oben genannten Scheunentore gelangen und somit auf Teile des Systems Zugriff erhalten, die für sie gar nicht bestimmt waren. Um diesen Problemen vollkommen zu entgehen, sind einige Maßnahmen erforderlich. Zum Einen sollten Tests oder gar die (temporäre) Bereitstellung von sensitiven Links und Dateien nur auf vom Internet aus nicht erreichbaren Systemen stattfinden. Ist es jedoch notwendig, dass diese über das Internet erreicht werden können, so sollten dies Domains sein, die auf keinem anderen Host verlinkt sind, damit eine Suchmaschine diese Dateien auch niemals indiziert. Zum Anderen sollten solche Dienste natürlich durch Passwörter und Benutzernamen abgesichert sein.
1
Faktisch ist dies keine Entschlüsselung, da die Passwörter von htaccess mit md5 oder sha1 nichts anderes sind als Hashcodes. Man kann diese also nicht entschlüsseln, es lässt sich lediglich durch Probieren feststellen, welche Originalzeichenkette zum entsprechenden Hash führt.
39
Kapitel 2 Fehlerquellen, die jeder PHP-Entwickler kennen sollte
Um das Problem des Scheunentors zu verhindern, muss jede PHP-Seite erneut prüfen, ob eine Authentifizierung vorliegt und der Benutzer die entsprechenden Rechte hat, um die Funktionen der aktuellen Seite auszuführen. Dies ist zwar relativ aufwändig, doch anders lässt es sich kaum vermeiden, dass Dritte bei einem eigenen Authentifikatikonssystem Zugriff erlangen. Am Besten jedoch ist es, direkt auf ein System, das vom Webserver angeboten wird, zu setzen. Beim ApacheWebserver ist dies das bereits erwähnte .htaccess, wofür eine Vielzahl von Modulen bereitsteht; Benutzer und Passwörter müssen also nicht aus einer Textdatei gelangen, sondern können direkt von einem LDAP-Server oder gar einer Datenbank bezogen werden. Weiterhin lässt sich dieser Mechanismus ganz gut in PHP integrieren, so dass auch ein grafisch anspruchsvoller Login realisiert werden kann. Mehr zum richtigen Umgang mit sensitiven Daten und auch zur Integration von .htaccess in PHP erfahren Sie in Kapitel 4.
2.5
Index- und Default-Dateien
Ein oft gemachter Fehler in Verbindung mit PHP ist die fehlerhafte Einstellung der Index- bzw. Default-Datei. Auf vielen Webserversystemen handelt es sich um die index.htm/index.html-Datei – auf dem Microsoft Internet Information Server hingegen wurde sie default.htm-Datei getauft. Die falsche Verwendung dieser Einstellung hat dabei selten sicherheitskritische Auswirkungen. PHP-Anwendungen funktionieren jedoch nicht so wie erwartet, da nicht die richtige Startseite angezeigt wird.
Wichtig Dass es selten sicherheitskritische Folgen hat, heißt nicht, dass diese ausgeschlossen sind. Das Fehlen einer Index- oder Default-Datei in Verbindung mit weiteren Lücken in der Konfiguration kann dazu führen, dass ein Dritter etwa Datenbankpasswörter oder andere sensitive Daten einsehen kann. Diese spezielle Seite kommt immer dann zum Tragen, wenn lediglich ein Verzeichnis oder eine (Sub-)Domain aufgerufen wird, die URL also keinen Dateinamen enthält. Solche Aufrufe sind etwa: 쐽 http://testserver 쐽 http://test.testserver/ 쐽 http://testserver/directory
Nun kommt es stark auf den Webserver, dessen generelles Verhalten sowie die spezifische Konfiguration an, was an den anfordernden Client ausgeliefert wird. Am bedenklichsten wäre es, wenn dann ein Verzeichnislisting an den Client übermittelt wird. Denn dann kann dieser eventuell »gefährliche« Dateien direkt anfor40
2.5 Index- und Default-Dateien
dern. Kommt ein Fehler bei der Dateibenennung hinzu (siehe Abschnitt 3.5.1 Ungeparste Dateiendung auf Seite 67) lassen sich Datenbankpasswörter und andere sehr sensible Daten leicht ausspähen. Ist keine Standarddatei definiert und das Verzeichnislisting wurde ausgeschaltet, erhält der Client nur eine Fehlermeldung. Jedoch baut jede Anwendung – oder vielmehr jede Website – auf einer Startseite auf, die standardmäßig angezeigt wird, wenn keine spezielle Unterseite aufgerufen wird. Diese Einstiegsseite muss den Namen tragen, unter dem die Standarddatei im Webserver konfiguriert ist. Für den Apache-Webserver gibt es hier zudem die Möglichkeit, mehrere Startseiten zu definieren; dabei wird diese Liste Eintrag für Eintrag abgearbeitet; die erste Datei aus der Liste, die physikalisch im entsprechenden Verzeichnis vorhanden ist, wird dann an den Client ausgeliefert. Findet sich keine Datei der Liste im Verzeichnis, so wird je nach weitergehender Konfiguration entweder das Verzeichnislisting oder der HTTP-Statuscode 403 (Forbidden, Zugriff nicht erlaubt – das spielt auf das nicht zugelassene Verzeichnislisting an) an den Client übermittelt. Um die Standarddateien festzulegen, wird die Konfigurationsdirektive DirectoryIndex verwendet. Für diese ist es notwendig, das Apache-Modul dir_module entweder statisch im Webserverkern zu integrieren oder es dynamisch als Modul nachzuladen. Wird dir_module nicht mit einer –disable-Option bei der Kompilierung explizit ausgeschlossen, ist es stets im Kern integriert. Hinter der Direktive folgt eine Liste von Dateien, nach denen gesucht werden soll, wenn eine Standarddatei an den Client ausgeliefert werden soll. Der Standardwert des Apache-Webservers sieht dabei so aus: DirectoryIndex index.cgi index.pl index.html index.htm index.shtml
Dabei wird zuerst nach der Datei index.cgi gesucht, wird diese nicht gefunden, so sucht der Webserver nach der Datei index.pl usw. Sofern ein entsprechender Handler für die Dateiendung (bei .cgi und .pl könnte dies etwa Perl sein) konfiguriert ist, wird dieser auch mit der Interpretation der Datei beauftragt. Eine für PHP erweiterte Variante kann dann so aussehen: DirectoryIndex index.php index.php4 index.php3 index.cgi index.pl index.html index.htm index.shtml
Dabei ist zu erkennen, dass die PHP-Dateien vor den anderen Standarddateien gesucht werden sollen. Wenn sie ausschließlich PHP-Dateien direkt von außen zugänglich machen wollen, so lassen Sie die Einträge nach index.php3 einfach weg. Beschränkt sich Ihre Dateibenennung lediglich auf das Suffix .php, so kann natürlich auch index.php3 und index.php4 entfallen.
41
Kapitel 2 Fehlerquellen, die jeder PHP-Entwickler kennen sollte
Wichtig hierbei ist auch, dass PHP als Handler für die Dateiendungen eingetragen ist, denn ansonsten werden die Dateien mit ihrem Quelltext als Original ausgeliefert – sensible Daten sind dann für jeden Dritten einsehbar. Die Direktive DirectoryIndex kann dabei für jeden Directory- und VirtualHostEintrag separat festgelegt werden. Eine Definition für das Basisverzeichnis als Standard – auch wenn diese Basis bedingt durch eine ausgeklügelte VirtualHostKonfiguration nie genutzt wird – ist unbedingt zu empfehlen. Komplementär sollte noch das Verzeichnislisting in den Blöcken deaktiviert werden, die über eine DirectoryIndex-Einstellung verfügen. Dies kann auf verschiedene Weise erledigt werden, was stark von der restlichen Konfiguration und der Verschachtelung von Blöcken abhängt. Am einfachsten ist es, wenn noch keine Options-Direktive vorhanden ist, und diese anderweitig nicht gebraucht wird. Die Deaktivierung erfolgt dann einfach mittels Options None. Ist Options anderweitig notwendig – etwa um Server-Side-Includes (SSI) mittels Includes zu aktivieren – sollte Indexes gezielt abgeschaltet werden, denn nur dann wird der Verzeichnisinhalt auch nicht aufgelistet, wenn die per DirectoryIndex spezifizierte Datei nicht vorhanden ist: Options –Indexes Includes
Listing 2.1:
Gezielte Abschaltung der Verzeichnisauflistung bei fehlender Standard-Datei
Handelt es sich um eine verschachtelte Konfiguration und ist es im übergeordneten Block zwingend erforderlich, ein Verzeichnislisting zu ermöglichen, kann dies durch ein Options –Indexes gezielt für den untergeordneten Block ausgeschaltet werden.
Hinweis Sowohl DirectoryIndex als auch Options wirken sich rekursiv aus, gelten also auch für die Unterverzeichnisse – solange diese nicht explizit anders konfiguriert wurden.
Tipp Andere Webserversysteme bezeichnen die Option zur Festlegung der Index-Datei möglicherweise anders; Microsofts Internet Information Server IIS bezeichnet diese Option etwa als Default Content Page oder Default Document (je nach Version), der Standardwert ist dort zudem nicht index.html oder index.htm sondern default.htm.
42
Kapitel 3
PHP und Dateien: Die häufigsten Fehler Hier geht es um die verbreitetsten Fehler, die bei der Verwendung von PHP und der Arbeit mit Dateien auftreten können. Gerade diese häufigen Fehler bieten eine große Angriffsfläche, da sie von Webserverbetreibern meist nicht als Problemquelle wahrgenommen werden.
Hinweis Dieses Kapitel soll lediglich die verbreitetsten und gleichzeitig gefährlichsten Fehler, die in der Programmierung mit PHP »gern« gemacht werden erläutern. Es werden kurze Lösungsansätze genannt, eine detailliertere Ausführung gibt es im jeweiligen Spezialkapitel dieses Buches.
3.1
Temporäre Dateien
Dateien fallen bei der Entwicklung von Webanwendungen ständig an, einige davon sind lediglich für die temporäre Speicherung von Informationen gedacht. Dies können auch Dinge sein, die niemand anderes erfahren soll, da er mit diesem Wissen beispielsweise Daten beziehen kann, die nicht für ihn bestimmt sind. So sollten Dateien, die Benutzer und Passwörter etwa für Datenbankverbindungen enthalten, nicht für Dritte einsehbar sein. Andererseits sind nicht nur statische Zugangsdaten betroffen: Session-IDs können, so sie ausgelesen werden, ebenfalls missbraucht werden. Eine solche Session kann »entführt« (Session-Hijacking) werden – darüber kann ein Eindringling etwa an die Bankverbindung oder Kreditkartendaten eines Webshop-Benutzers kommen, der diese in seinem Profil hinterlegt hat. Temporäre Dateien sind, genau wie die Informationen, die sie enthalten, in ihrer Entstehung sehr vielschichtig – es gibt viele Möglichkeiten, wie es zu diesen Dateien kommen kann. Tragisch ist auch keinesfalls, dass es diese Dateien gibt. Es mag seine Gründe haben, Daten kurzfristig zwischenzuspeichern – fatal ist es lediglich, wenn Dritte Zugriff auf diese Dateien erlangen können und/oder diese Dateien nicht bemerkt und somit nicht gelöscht werden. Hier gilt: Je länger eine Datei mit sensiblen Informationen besteht, desto größer ist die Wahrscheinlichkeit, dass ein Unbefugter an diesen Inhalt gelangt.
Kapitel 3 PHP und Dateien: Die häufigsten Fehler
Doch wie kommt es nun zu temporären Dateien? 쐽
Backup-Dateien I: Editoren und Entwicklungsumgebungen legen je nach Einstellung einige alte Revisionen der veränderten Dateien als Sicherung an.
쐽
Backup-Dateien II: FTP-Programme verfahren je nach Einstellung eventuell genauso. Vor allem Tools, die eine automatische Übertragung ermöglichen, machen davon gern Gebrauch, damit überschriebene Dateien nicht verloren sind.
쐽
Protokolldateien: FTP-Clients – allen voran WS FTP – legen bei Übertragungen sowohl auf dem Server als auch auf dem Client Protokolldateien an, in denen vermerkt wird, welche Dateien übertragen wurden (bei WS-FTP heißt diese Datei z. B. wsftp.log). Diese Information ist noch nicht kritisch, doch könnten diese Daten die Suche durch einen Unbefugten nach sensiblen Dateien erleichtern.
쐽
Eigene Sicherungen: Zum Test legt man eventuell Kopien von Dateien an, damit das Original leichter wiederherstellbar ist. War der Test erfolgreich, wird meist vergessen, die Sicherungen zu löschen.
쐽
Sessionmanagement: Ist die Sessionverwaltung ungenügend konfiguriert und werden Sessions auf dem Server in Dateien gespeichert, sind auch diese Dateien eine Gefahr.
Vorsicht Denken Sie daran: Diese Liste stellt nur Beispiele dar – sie ist keinesfalls vollständig. Wenn Sie alle Punkte dieser Aufzählung beachtet haben, sollten Sie sich nicht in falscher Sicherheit wiegen!
Hinweis Für einen Benutzer ist es mehr als leicht, an solche Dateien zu gelangen: Der Suchausdruck inurl:wsftp.log führt bei Google dazu, dass alle indizierten wsftp.log-Dateien angezeigt werden. Es gibt nun mehrere Maßnahmen, die man ergreifen kann, um solche Dateien gar nicht erst entstehen zu lassen, sie – so sie denn notwendig sind – nach möglichst kurzer Zeit zu entfernen und den Zugriff durch Außenstehende zu verwehren.
3.1.1
Backup-Dateien, Versionsverwaltung und Zugriffsschutz
Ganz klar: In der Softwareentwicklung gibt es immer wieder die Notwendigkeit, alte Daten zu sichern, um etwas Neues ausprobieren zu können, das vielleicht noch gar nicht der Stein der Weisen sein wird. Um nun die alte Version der jeweiligen
44
3.1 Temporäre Dateien
Daten nicht zu verlieren, kann man die Datei vorher kopieren. Diese Dateien enthalten oft den Originalquelltext und die -konfiguration. Werden diese Dateien jedoch nicht gelöscht und versehentlich – oder gar absichtlich – auf einen für jeden erreichbaren Webserver gespielt, kommt es zu einem wesentlichen Problem in Verbindung mit Webservern und Interpretersprachen generell: Lediglich Dateien mit der konfigurierten Dateiendung werden durch den entsprechenden Interpreter übersetzt, alle anderen Dateien werden als Klartext ausgeliefert. Am einfachsten lässt sich das anhand einer Apache-Beispielkonfiguration erklären. In der httpd.conf finden sich folgende Einstellungen zum Einsatz von PHP: AddType application/x-httpd-php-source .phps AddType application/x-httpd-php .php .php5 .php4 .php3 .phtml
Hierbei werden alle von Clients angeforderten Dateien mit den Endungen .php, .php5, .php4, .php3 und .phtml an den PHP-Interpreter übergeben und übersetzt. Den Quelltext dieser Dateien wird ein Client also niemals einsehen können, da PHP vorher die Übersetzung vornimmt und lediglich die Daten, die vom jeweiligen Skript ausgegeben werden, an die Clients übermittelt. Dateien mit dem Suffix .phps werden hier auch an den PHP-Interpreter übergeben, von diesem jedoch nicht übersetzt, sondern speziell formatiert. Der Quelltext wird dabei neu gegliedert und an den Client ausgeliefert. Bei diesen Dateien erfolgt dies absichtlich. Werden jetzt Sicherungsdateien – egal ob manuell, durch einen Editor oder eine FTP-Applikation – angelegt, so erhalten diese meist auch eine andere Dateiendung (z.B. .txt, .old, .bak). Diese Endungen werden ebenfalls – sofern sie angefordert werden – nicht an PHP übergeben, sondern als Klartext ausgeliefert. Dabei werden sie nicht wie .phps-Dateien formatiert, sondern so ausgegeben, wie sie sind. Das spielt dabei auch keine Rolle, denn dieser Effekt war (im Gegensatz zu den .phps-Dateien) keine Absicht, die Auslieferung erfolgt versehentlich. Und diese Dateien enthalten dann wahrscheinlich Daten, die ein Dritter nicht einsehen sollte. Wurde im Apache die Indizierung des Verzeichnisses (Direktive Options) deaktiviert oder ist im jeweiligen Verzeichnis eine Standarddatei (Direktive DirectoryIndex) vorhanden, so scheint dies erst einmal nicht problematisch zu sein, da von außen nicht erkennbar ist, welche Dateien im jeweiligen Verzeichnis des Webservers vorhanden sind. Doch gegen ein solches Denken spricht vieles: Der Zufall spielt Hackern stets in die Hände, diese versuchen einfach anhand der bekannten Dateinamen (die durch das Surfen auf der jeweiligen Seite klar erkennbar sind) durch Veränderung der Dateiendung einen Zufallstreffer zu landen. Zudem ist es durchaus denkbar, dass die
45
Kapitel 3 PHP und Dateien: Die häufigsten Fehler
Verzeichnisindizierung aktiviert ist – und wenn genau in dem Moment, in dem man etwa selbst die Standarddatei des Verzeichnisses durch eine neuere Version austauscht, zudem noch ein Dritter das jeweilige Verzeichnis aufruft, kann er so etwa an die Auflistung der Datei gelangen und somit sehen, welche Dateien vorhanden und wahrscheinlich Sicherungskopien sind. Natürlich kann es auch hier Bruteforce-Attacken geben: Hierbei werden in einem automatisierten Verfahren viele übliche Dateinamen für Konfigurationsdateien – denn vor allem diese sind interessant, um an die eigentlichen Nutzdaten zu gelangen – mit bekannten Dateiendungen für Sicherungskopien kombiniert. Nur wenige Webserver sind so eingerichtet, dass nur eine begrenzte Zahl von Anforderungen innerhalb einiger Sekunden von einem Client auch tatsächlich bearbeitet wird. So gibt es meist keinen Schutz gegen diese automatisierten Systeme. Diese Backup-Dateien sind – wie erwähnt – notwendig, um alte Stände zu sichern. Zum Problem werden sie auch erst dann, wenn sie durch Unachtsamkeit mit einem FTP-Programm auf einen Produktivserver übertragen werden oder wenn die FTP-Anwendungen von Dateien, die ersetzt werden, eine Sicherungskopie oder gar ein serverseitiges Übertragungsprotokoll anlegen. Um diesem Problem zu entgehen, gibt es eine einfache und effektive Lösung: Versionsmanagement. Werden in einem versionierten Projekt, das ausschließlich automatisiert vom Versionsserver auf den produktiven Webserver übertragen wird, Sicherungsdateien auf dem Client angelegt, so werden diese nicht in den Speicher übernommen, es sei denn, sie werden explizit zur Versionsverwaltung hinzugefügt. Besonders prädestiniert für diese Aufgabe ist das Versionskontrollsystem Subversion. Die Vorgehensweise sollte dabei im Allgemeinen folgende sein: 쐽
Alle zum Projekt gehörenden Dateien stehen unter Versionsverwaltung; Ausnahme: Sicherungsdateien, die dann nicht mehr notwendig sind – alte Versionsstände können jederzeit über die Versionskontrolle wiederhergestellt werden.
쐽
Nach der Übernahme von lokalen Änderungen wird der neue Stand der Dateien auf dem Versionsserver gespeichert. Es werden dabei lediglich Dateien übernommen, die explizit vom Benutzer unter Versionskontrolle gestellt wurden.
쐽
Diese neue Revision überträgt automatisch die neuen Daten auf den produktiven Webserver.
Dieses Modell sorgt dafür, dass keine unerwünschten Dateien auf dem Webserver vorliegen; es ist auch sichergestellt, dass sich auf dem Webserver stets die aktuelle Version der Anwendung befindet.
46
3.1 Temporäre Dateien
Als vielversprechendes System hat sich hier Subversion hervorgetan, das die Alternative zum altbewährten CVS ist. Bei jeder neuen Revision (jede Änderung, die an das System übertragen wird, erzeugt eine solche Revision) kann ein sogenannter Hook ausgeführt werden. Dabei handelt es sich um ein Programm – egal ob BatchDatei, Skript oder voll funktionsfähiges Anwendungsprogramm – das vom Benutzer bereitgestellt wird. In diesem kann unter Verwendung der Subversion-Bordmittel z.B. festgestellt werden, welche Dateien mit der aktuellen Revision geändert wurden, so kann etwa eine Übertragung via FTP auf diese Dateien beschränkt werden. Jedoch selbst mit einer Versionsverwaltung kann es einmal dazu kommen, dass Dateien, die nicht von PHP übersetzt werden, auf dem Webserver gespeichert werden, etwa weil eine Textdatei mit Entwicklerinformationen unter Versionskontrolle steht. Um jedoch eine Projektverwaltung nicht unnötig zu verkomplizieren – etwa indem solche Dateien in Verzeichnissen abgelegt werden, die wiederum von der automatisierten Übertragung zum Produktivserver ausgeschlossen werden – sollte auf dem Webserver noch eine zweite Technik zur Anwendung kommen: Zugriffsschutz.
Hinweis Die folgenden Daten beziehen sich auf die Verwendung von .htaccess in Verbindung mit dem Apache-Webserver. Ein solcher Zugriffsschutz kann auch mit anderen Webserversystemen realisiert werden, konsultieren Sie hierzu die jeweilige Dokumentation. Der Apache-Webserver kann eine Konfiguration nicht nur über die zentralen Dateien – httpd.conf und Dateien, die dort inkludiert werden – verwalten. Er unterstützt auch die Möglichkeit, die Konfiguration in jedem Verzeichnis dynamisch zu beeinflussen. Diese Datei hat dasselbe Format wie die Hauptkonfiguration, jedoch gibt es einige Unterschiede: 쐽
Nicht alle Direktiven werden innerhalb dieser Dateien unterstützt, es kommt vor allem auf die Einstellung von AllowOverride innerhalb der Basiskonfiguration an.
쐽
Alle Konfigurationsänderungen innerhalb dieser Datei beziehen sich auf das Verzeichnis, in dem sie sich befindet. Eine Angabe eines Directory- oder Location-Blocks ist somit weder notwendig noch zulässig. Folgende Daten der Basiskonfiguration AddType application/x-httpd-php .php. php5 .php4 .php3 .phtml
47
Kapitel 3 PHP und Dateien: Die häufigsten Fehler
werden innerhalb einer .htaccess-Datei so umgesetzt: AddType application/x-httpd-php .php
Voraussetzung ist hierbei: Die .htaccess-Datei befindet sich im Verzeichnis selbst oder innerhalb eines übergeordneten Verzeichnisses. Alle Daten einer .htaccess-Konfiguration finden auch für Unterverzeichnisse Anwendung, sofern dort nicht eine eigene .htaccess-Datei vorliegt, die die Konfiguration entsprechend beeinflusst. 쐽
Es gibt Konfigurationsanweisungen, bei denen stets die Einstellungen der Basiskonfiguration den Anweisungen einer .htaccess-Datei bevorzugt werden; dies betrifft etwa eine notwendige Authentifizierung, um auf Dateien eines Verzeichnisses zuzugreifen: Egal, was in einer vorhandenen .htaccessDatei steht, sobald entsprechende Einträge in der httpd.conf vorhanden sind, werden diese beachtet. Für dieses Feature ist dieses Verhalten allerdings unerheblich.
.htaccess ist der Standardname dieser Datei, dieser Name kann jedoch auch verändert werden, indem die Konfigurationsdirektive AccessFileName innerhalb der Serverkonfiguration entsprechend gesetzt wird. Befindet sich Ihre Webanwendung also auf einem Server eines Providers und .htaccess scheint nicht zu funktionieren, so fragen Sie Ihren Provider nach dem erforderlichen Dateinamen.
Vorsicht Achten Sie unbedingt darauf, dass eine .htaccess-Datei auch von außen nicht zugreifbar ist! Haben Sie in dieser Datei verfügt, dass bestimmte Dateitypen von außen nur nach einer Authentifizierung angefordert werden können, kann ein Dritter nach Einsicht der .htaccess-Datei evtl. diese Informationen leichter erlangen und so an geschützte Dateien gelangen. Um dies zu verhindern, sollte in der Basiskonfiguration ein Zugriff auf diese Dateien verhindert werden (siehe folgende Abschnitte). Doch nun zum praktischen Teil. Im konkreten Problemfall – Sicherungsdateien – sollte der Webserver verhindern, dass ein Dritter an den Quelltext dieser Sicherungsdateien herankommt. Dies kann innerhalb einer .htaccess-Datei oder (was zu bevorzugen ist) der Basiskonfiguration festgelegt werden. Um möglichst viele Dateimuster abzudecken, können in Zusammenhang mit dem FilesMatch-Block reguläre Ausdrücke zur Erkennung der Dateinamen verwendet werden. Dabei können alle regulären Ausdrücke verwendet werden, die durch die PCRE (Perl compatible regular expressions, http://perldoc.perl.org/perlre.html) abgedeckt werden.
48
3.1 Temporäre Dateien
Hinweis Der Files-Block kann mit einem vorangestellten ~ (etwa ) ebenfalls mit regulären Ausdrücken verwendet werden. Um dies auf einen Blick zu verdeutlichen und nicht unnötig Verwirrung zu stiften, sollte für diese Art der Dateinamenserkennung FilesMatch verwendet werden. Innerhalb dieses Blocks kann man angeben, wer auf diese Dateien zugreifen darf und wer nicht. Dabei kommen nun für die Sicherungsdateien zwei Ansätze in Frage: 1. Der Zugriff auf Sicherungsdateien wird explizit verboten 2. Der Zugriff auf PHP-, HTML- und Grafikdateien wird explizit erlaubt Das verwendete Szenario hängt dabei stark davon ab, ob sicher davon ausgegangen werden kann, welche Dateiendungen von eingesetzter Software für Sicherungskopien und/oder temporäre Dateien verwendet werden. In Frage kommen hier beispielsweise: 쐽 .tmp 쐽 .bak 쐽 .sik 쐽 .log 쐽 .sql 쐽 .001, .002, .003, …, .099, …
Hinweis Beachten Sie, dass verschiedene Tools unterschiedliche Groß- bzw. Kleinschreibung dieser Endungen verwenden. Ein Editor speichert eine Sicherungskopie der Datei config.inc.php etwa in config.inc.php.bak – ein anderer vielleicht in config.inc.php.BAK. Wird der Webserver auf einem Unix-Derivat (Unix, Linux, BSD) betrieben, muss dies auch innerhalb des regulären Ausdrucks beachtet werden, während dies auf Windows-Servern keine Rolle spielt. Kann sichergestellt werden, dass in Frage kommende Dateisuffixe allesamt aus der oben genannten Liste stammen, so kann ein FilesMatch-Block etwa so aussehen: Order Deny, Allow
49
Kapitel 3 PHP und Dateien: Die häufigsten Fehler
Deny from all
In diesem Fall ist der Zugriff auf Dateien, deren Dateiname mit einer der angegebenen Suffixe endet, für keinen Client erlaubt. Serverseitige Anwendungen – etwa PHP-Skripts – können allerdings weiterhin auf diese Dateien zugreifen, sofern der Zugriff direkt über das Dateisystem und nicht über den Webserver erfolgt. Die Zugriffsbeschränkung dieses Blocks gilt auch für .htaccess-Dateien, es ist nicht notwendig, dass diese von außen zugreifbar sind – im Gegenteil bedeutet dies sogar ein Sicherheitsrisiko (konkret wird der Zugriff auf Dateien unterbunden, deren Name mit .ht beginnt, es würde also auch auf .htpasswd zutreffen, einen verbreiteten Namen für die Datei mit Benutzer- und Passwortdaten für den .htaccess-gesteuerten Zugriff). Möchten Sie allerdings auch, dass diese Dateien von bestimmten Hosts aus erreichbar sind, so verwenden Sie als zusätzliche Anweisung innerhalb des Blocks noch die Allow-Direktive, mit der dann der Zugriff für bestimmte IP-Adressen, auf Basis etwa eines bestimmten Browsers oder durch eine Authentifizierung erlaubt werden könnte; von einem solchen Verhalten ist allerdings abzuraten, temporäre oder Sicherungsdateien sollten für niemanden einsehbar sein – sie sollten auf einem Produktivserver nicht einmal existieren. Können Sie nicht sicherstellen, dass lediglich bestimmte, Ihnen bekannte, Dateiendungen für nicht von außen zugreifbare Dateien verwendet werden, müssen Sie den Spieß entsprechend umdrehen: Sie erlauben lediglich den Zugriff auf eine bestimmte Menge von Dateien. Im Beispiel sollen Dateien mit folgenden Suffixen von außen erreichbar sein: 쐽
HTML-Dateien: .htm, .html
쐽
PHP-Dateien: .php, .php3, .php4, .php5, .phtml; wichtig hierbei ist: Damit auch hier kein Quelltext an einen Client übermittelt wird, muss sichergestellt werden, dass Dateien mit diesen Endungen auch an den PHP-Interpreter übergeben werden (nutzen Sie hierzu die Direktive AddType in Verbindung mit application/x-httpd-php)
쐽
Grafikdateien: .jpg, .jpeg, .gif, .png
쐽
Zusätzlich existiert hier noch ein Downloadverzeichnis namens download; alle Dateien daraus sollen durch einen Client angefordert werden können
Der FilesMatch-Block, der nur Zugriff zu den oben genannten Dateien gestatten soll, sieht also so aus: Order Allow,Deny Deny from all
50
3.1 Temporäre Dateien
Order Deny,Allow Allow from all
Hierbei ist es erforderlich, erst den Zugriff generell zu verbieten und ihn dann für die einzelnen Dateien wieder freizuschalten. Dieser Code-Block kann direkt in eine .htaccess-Datei übernommen werden. Soll die Freischaltung direkt über die Basiskonfiguration vorgenommen werden, so sollte dieser Block in eine Directory-Direktive verpackt werden, sofern eine Begrenzung dieser Wirkung auf bestimmte Verzeichnisse beschränkt werden soll.
Hinweis Wird über die Basiskonfiguration der Zugriff gesteuert, müssen verzeichnisbezogene Anweisungen unbedingt innerhalb von Directory-Blöcken notiert werden – Zugriffssteuerung in Location-Blöcken ist nicht zulässig; entsprechende Anweisungen werden dort ignoriert! Um auch noch dem Downloadverzeichnis gerecht zu werden, kommt es auf den Weg an, der verwendet wird: 쐽
Wird die Basiskonfiguration für die Freischaltung genutzt, so muss die einleitende FilesMatch-Zeile erweitert werden:
쐽
Bei der Verwendung von .htaccess geht diese Methode allerdings nicht, denn FilesMatch wird dabei stets nur gegen den Dateinamen – ohne Verzeichnispfad – verglichen. Ist im aktuellen Verzeichnis keine .htaccess-Datei vorhanden, wird in den übergeordneten Verzeichnissen nach einer solchen gesucht und diese verwendet. Dies führt allerdings zu dem Problem, dass beim Aufruf der Datei download/abc.zip lediglich abc.zip mit dem FilesMatch-Muster verglichen wird; hier führt also die Erweiterung von FilesMatch nicht zum Erfolg. Der einzige Weg hier: Das Verzeichnis benötigt ein eigene .htaccessDatei, die den Zugriff auf alle Dateien erlaubt: Order Deny,Allow Allow from all
51
Kapitel 3 PHP und Dateien: Die häufigsten Fehler
3.2
Sessions: Permissive Konfiguration oder »Alles ist erlaubt«
HTTP als verbindungsloses Übertragungsprotokoll steht dem Sinn und Zweck einer individualisierten Webanwendung entgegen. HTTP liefert Daten aus – zwischen einzelnen Anfragen gibt es dabei keinerlei Zusammenhang; um jedoch individuelle Daten liefern zu können, muss es möglich sein, Daten, über die sich der Benutzer z.B. authentifiziert hat, erhalten zu können, um über diese später wieder verfügen zu können. Nur so ist es auch möglich, beispielsweise einen Onlineshop zu realisieren: Es muss nachher noch bekannt sein, was der Benutzer in seinen Warenkorb abgelegt hat oder welche Lieferadresse er angegeben hat. Um dennoch Daten seitenübergreifend bereitstellen zu können, ohne diese stets in der URL unterzubringen (was URLs immer länger werden lassen würde und was in Bezug auf sensitive Daten wie etwa die Kreditkartennummer sicherheitstechnisch nicht zu verantworten wäre), wird mit sogenannten Sessions gearbeitet. Dabei bekommt ein Client eine Session-ID zugewiesen; ist es nun notwendig, Daten des Clients auf dem Server zwischenzuspeichern, werden diese dort in Verbindung mit der eindeutigen Session-ID gespeichert – wird auf einer anderen Seite die Sitzung mit dieser ID wieder aktiviert, können auch diese Daten wieder ausgelesen werden. Dieses Prinzip existiert in dieser Form nicht nur in PHP, sondern auch in anderen Sprachen, die in Verbindung mit Webanwendungen genutzt werden (etwa JSP, Python, Ruby). Dieses grundsätzlich einfache Prinzip hat allerdings datenschutzrelevante Auswirkungen, besonders, falls noch die Verwendung von Proxyservern hinzukommt. Grundsätzlich wird eine Session nämlich nur anhand der ID identifiziert – so ist es auch bei PHP. Es gibt also keine Verknüpfung zwischen der Sitzung und der IPAdresse des Clients. So ist es auch möglich, dass ein Client die Sitzung eines anderen übernehmen kann und dann über die Daten, die mit dieser Sitzung bereits gespeichert wurden, verfügen kann. Wird eine Session – anhand einer per Zufall ermittelten oder in Erfahrung gebrachten ID – durch einen Dritten entführt bzw. genutzt, so spricht man von SessionHijacking. Allerdings: Kein Hijacking ohne korrekte Session-ID. Jedoch gibt es viele Möglichkeiten, an eine solche zu gelangen. Am einfachsten ist dies, wenn das System jede Session-ID, die übermittelt wird, zulässt und dann gegebenenfalls keine neue Session-ID vergibt. So wäre es möglich, eine Session namens ABC zu erstellen – eine Bezeichnung die sich äußerst leicht erraten lässt. Hier kommen Bruteforce-Attacken zum Tragen, die einfach erdenkbare IDs durchprobieren, bis eine Session entdeckt wird, die bereits Daten enthält. Dieses Verhalten wird im Allgemeinen als Session-Riding bezeichnet und ist somit eine mögliche Vorstufe zum Session-Hijacking.
52
3.2 Sessions: Permissive Konfiguration oder »Alles ist erlaubt«
Um an eine gültige Session-ID zu gelangen, ist jedoch nicht nur der Zufall notwendig. Es gibt auch andere Möglichkeiten, um einfacher und zuverlässiger an eine solche ID zu gelangen. Dies hängt stark davon ab, wie Client und Server jeweils mit der Session-ID umgehen, bzw. diese übermitteln und speichern. Die gebräuchlichsten Methoden sind hierbei: 쐽
Transport innerhalb der URL als Parameter, z.B. http://sessionserver/ testpage.php?PHPSESSID=77dherhr5z7dddda442gt
쐽
Transport innerhalb der URL als Subdomain oder Verzeichnis, Umformung der URL erst serverseitig, z.B. http://77dherhr5z7dddda442gt.session. .sessionserver/testpage.php oder http://sessionserver/77dherhr5z 7dddda442gt/testpage.php (Vorsicht! Dieses Verfahren ist zum Patent durch das deutsche Unternehmen 7val angemeldet!)
쐽
Verstecktes Formularfeld, z.B. innerhalb von HTML-Quelltext mittels
쐽
Speicherung als Cookie, Übermittlung jeweils im HTTP-Header. Dies kann besonders dann zu Problemen führen, wenn der Client ein öffentlich zugänglicher Rechner (z.B. im Internetcafé) ist, und das Cookie nicht mit dem Schließen des Browsers gelöscht wird – dann kann jeder nachfolgende Benutzer über die Cookiedatei die Session-ID in Erfahrung bringen.
All diese Methoden haben Ihre Vor- und Nachteile, doch eins ist klar: Irgendwann muss die Session-ID über das Netzwerk zum Server transportiert werden, damit dieser weiß, welche Daten er für die Webanwendung zur Verfügung stellen muss. Alles, was über das Netzwerk transferiert wird, kann im Zweifelsfall abgehört werden. Dies mag wie ein Horrorszenario klingen, jedoch wenden Hacker inzwischen eine Menge Energie auf, um an Kreditkarten und andere sensible Informationen zu gelangen, die vor allem im Rahmen von Onlinebestellungen durch die Benutzer an einen Server übermittelt werden müssen und dort innerhalb von Sessions zwischengespeichert werden. Vor allem die zunehmende Vernetzung und die Sorglosigkeit bei der Netzwerkkonfiguration sowohl in privaten Netzwerken als auch innerhalb von Rechenzentren und bei Providern sorgt dafür, dass der Erfolg solcher Abhöraktionen zunimmt. Es kommt noch eine weitere Tatsache hinzu: Verwendet der Server, auf dem ein Session-Hijacking durchgeführt werden soll, einen durchschaubaren Algorithmus zur Erzeugung der Session-IDs, so reicht es aus, wenn man sich eine gültige Session-ID besorgt, indem man sich beim Server anmeldet und dann anhand der dort erzeugten Session darauf schließen kann, welche anderen IDs ebenfalls gültig sind. So kann man auch ohne das Netzwerk abhören zu müssen, auf gültige und wahrscheinlich von anderen legitimen Benutzern verwendete Sessions gelangen.
53
Kapitel 3 PHP und Dateien: Die häufigsten Fehler
Um all diesen Problemen zu entgegnen, sind verschiedene Maßnahmen erforderlich. An erster Stelle sollten hierbei Methoden stehen, die ein erfolgreiches SessionRiding verhindern.
Hinweis Umfangreiche Informationen über Sessions, die Gefahren, die aus der Verwendung von Sessions resultieren, und wie man diesen mit einfachen Mitteln entgegnen kann, finden Sie im Kapitel 5 Sessions.
3.2.1
Session-IDs nur durch Server vergeben
Um einfache Session-IDs zu verhindern, muss dafür gesorgt werden, dass lediglich der Server eine Session-ID vergeben kann. Wird eine Seite mit einer ID angefragt, für die es noch keine Session gibt, wird normalerweise eine Session mit dieser ID erzeugt. Dies birgt im Wesentlichen zwei Gefahren: 쐽
Ein User könnte eine simple ID angeben, die von einem Dritten leicht durch Raten ermittelt werden kann.
쐽
Einem User kann von einem Dritten ein Link inklusive einer erdachten Session-ID geschickt werden; der Benutzer klickt darauf und loggt sich mit dieser Session-ID und seinen Benutzerdaten am Server an. Die Session wird nun Zugriff für diesen Benutzer erlauben. Der Dritte kann nun ebenfalls auf diese Daten zugreifen – er muss die Session-ID nicht mehr erraten, denn er kennt sie schon.
쐽
Falls eine Suchmaschine eine Session-ID als Link verwendet, kann es sein, dass ein User unbeabsichtigt Zugriff auf eine Session eines anderen Benutzers erlangen kann, der kurz zuvor Ihre Seite besucht hat. Dies kann dazu führen, dass der »neue« User sensitive Daten einsehen kann. Das gleiche kann natürlich auch dann passieren, wenn ein Benutzer ein Bookmark auf Ihre Seite setzt und dabei eine Session-ID mitgespeichert wird; besucht er später die Seite erneut, kann es sein, dass zwischenzeitlich ein anderer Benutzer diese SessionID zugewiesen bekam.
Um diesem zu begegnen, ist es notwendig, ein bestimmtes Modell der Sessions zu verwenden. Hier kann man grundsätzlich zwischen zwei Typen unterscheiden: permissive und restriktive Sessionverwaltung. Permissiv steht in der Soziologie für wenig kontrollierend, und diese Bedeutung trifft dann auch in der Sessionverwaltung zu: Die übergebene Session-ID wird verwendet; existiert noch keine Session mit dieser Identifikation, so wird sie erzeugt. PHP verwendet genau diese Methode. Beim restriktiven Modell hingegen ist es so, dass jeweils nur vom Server erzeugte Session-IDs verwendet werden können. In Kombination mit einem sicheren Algo-
54
3.2 Sessions: Permissive Konfiguration oder »Alles ist erlaubt«
rithmus zur Erzeugung dieser ID ist es schon wesentlich unwahrscheinlicher, dass ein Hacker eine solche ID errät und so eine Session verwendet, die Daten eines anderen Benutzers enthält.
Hinweis PHP lässt übrigens nicht jede ID, die vom Benutzer übergeben wurde, als Session-ID zu. So muss diese immer mit dem Parameter, der als session.name in der php.ini spezifiziert wurde, übergeben werden (Standard: PHPSESSID). Zudem hängt die Gültigkeit einer ID auch vom Parameter session.hash_ bits_per_character ab. Hier offenbart sich allerdings auch schon ein Problem in Zusammenhang mit PHP: Es lässt sich nicht direkt feststellen, ob eine Session-ID nun vom Benutzer stammt oder von einem Skript oder von PHP selbst erzeugt wurde. Es gibt auch keine Möglichkeit, dies etwa mit dem Session-Save-Handler zu beeinflussen, denn dieser ist wirklich nur zum Speichern und Laden der Session vorgesehen; um nun allerdings nicht mit einer Session-ID in eine Falle zu laufen, müssen Maßnahmen ergriffen werden. Doch alle Methoden, die man hier verwenden kann, haben einen Nachteil: Sie führen zu Einbußen beim Benutzerkomfort.
3.2.2 Regenerierung der Session-ID bei sensitiven Aktionen Solange keine benutzerspezifischen Daten in einer Session gespeichert werden, ist es irrelevant, ob die Session-ID »sicher« ist oder nicht – denn an diese Daten kann jeder gelangen, sobald er die fragliche Website besucht. Sobald allerdings der Zugriff auf sensible Daten gewährt werden soll, etwa indem sich ein Benutzer einloggt, ist es unbedingt erforderlich, dass die Session-ID eine sichere, vom Server erzeugte ist. Die Session-ID muss dabei durch eine neue ausgetauscht werden; diese wird dabei entweder vom Skript erzeugt oder es wird eine neue ID von PHP generiert. Soll selbst eine ID erzeugt werden, so ist es auch hier erforderlich, dass der Wert, der verwendet werden soll, eine hexadezimale Zahl ist. Besonders bewährt haben sich hier Hashcodes, die etwa mit den PHP-Funktionen md5() und sha1() erzeugt werden können. Dabei sollte man allerdings darauf achten, dass die Datenbasis nicht ohne weiteres nachvollziehbar ist – Hashcodes sind zwar eine one-way-»Verschlüsselung«, doch wird z.B. lediglich die Serverzeit gehasht, so ist es nur eine Frage der Zeit, bis ein Hacker hinter diese Taktik kommt. Da die Hash-Funktionen standardisiert sind, lässt sich also so ein Code jederzeit auf einem anderen Rechner erzeugen und man kann diesen Hashcode dann als Session-ID verwenden und somit eine evtl. bestehende Session eines anderen Benutzers missbrauchen.
55
Kapitel 3 PHP und Dateien: Die häufigsten Fehler
Die Basis für einen Hash sollte also nach Möglichkeit aus mehreren voneinander unabhängigen, sich ändernden Datenquellen bestehen. Am besten ist es dabei, aus jeder dieser Datenquellen einen Hash zu erstellen und dann aus der Aneinanderkettung dieser Codes einen erneuten Hash zu erzeugen, der schlussendlich dann als Session-ID verwendet wird. Denkbar als Datenquellen sind hierbei: 쐽
Serverzeit in Mikrosekunden; eine Angabe in Sekunden wäre zu leicht vorhersehbar
쐽
IP-Adresse des Benutzers, der die entsprechende Webseite anfragt
쐽
Freier Speicherplatz auf der Serverfestplatte (zu ermitteln mit disk_free_ space())
Nachdem ein Hash über jede dieser Datenquellen erstellt wurde, die Hashs in einem String verkettet wurden und darüber erneut ein anderer Hash erzeugt wurde, muss der Hash als neue Session-ID mit der Funktion session_id() gesetzt werden. Hierbei sollten die Funktionen md5() und sha1() zur größeren Absicherung vermischt werden. Ein Beispiel:
Die Alternative zu session_id() ist dabei die Verwendung der Funktion session_regenerate_id(); hier wird PHP eigenständig eine neue ID erzeugen und sie der existierenden Session zuweisen. Komplexere Algorithmen zur Erzeugung einer Session-ID führen allerdings auch zu mehr Sicherheit. Einen Weg für eine Vorgehensweise, bei der sowohl der Client als auch der Server ihren Beitrag dazu leisten, finden Sie ausführlich im Abschnitt 4.2.1 Hash-Austausch via AJAX auf Seite 98 beschrieben.
3.2.3 Absicherung durch TAN Alternativ bzw. zusätzlich zur Erneuerung der Session-ID kann eine Session auch noch durch die Verwendung von TANs (Transaktionsnummern) abgesichert werden.
56
3.3 Globaler Dateisystemzugriff
Hinweis Dieses Modell hat eine sehr hohe Sicherheit zur Folge – es ist allerdings sehr restriktiv und schränkt den Benutzerkomfort stark ein. Es sollte vorher klar abgewogen werden, ob man diese Einschränkungen hinnehmen möchte bzw. kann. Neben der PHP-Session wird hier jeder Link bzw. jede weitere serverseitige Verarbeitung durch eine Zufallszahl abgesichert. Dafür wird beim Anlegen einer Session – oder vielmehr zu dem Zeitpunkt, ab dem sensitive Daten zur Verfügung gestellt und bearbeitet werden – eine Liste von langen Zufallszahlen angelegt. Diese werden serverseitig der Session zugewiesen – beispielsweise in einer Datenbank oder innerhalb der Session selbst. Jedes mal, wenn nun ein Skript für diese Session aufgerufen wird, wird geprüft, ob eine solche TAN übergeben wurde und diese in der serverseitigen Liste vorhanden ist. Ist dies nicht der Fall, wird die Session zerstört bzw. der Zugriff auf die gewünschten Funktionen verweigert. War die Zufallszahl gültig, so wird sie aus der Liste gestrichen. Ein wesentlicher Nachteil hierbei ist, dass jeder Link mit dieser TAN durch eigenen Code erweitert werden muss, da sonst das gesamte Modell nicht funktioniert. Ebenso ist eine Unterstützung des »Zurück«-Buttons der Browser oder gar der JavaScript back()-Funktion nicht möglich. Mehr zu diesem Thema erfahren Sie im Abschnitt 5.4.5 TAN-System auf Seite 160.
3.3
Globaler Dateisystemzugriff
Ein gravierendes Problem in Verbindung mit PHP ist der Dateisystemzugriff. Dabei bleibt hier meist keines der für die Verwendung von PHP in Frage kommenden Betriebssysteme verschont. Sowohl Windows als auch Unix-/Linux-Derivate bieten hier Schlupflöcher für den Zugriff auf Dateien, auf die durch PHP nicht zugegriffen werden soll. Es können hier zwei Arten von nicht erwünschten Zugriffen klassifiziert werden: 1. Zugriff auf System- und Benutzerdateien Hierbei ist es PHP-Anwendungen möglich, auf Dateien des gesamten Serversystems außerhalb Ihrer Verzeichnisstruktur zuzugreifen. In solch einer Umgebung ist es einer Webanwendung beispielsweise möglich, Dateien aus dem Systemkonfigurationsverzeichnis (unter Linux /etc) auszulesen. Dies bedeutet auch, dass sensitive Daten über PHP an außenstehende Dritte übertragen werden können.
57
Kapitel 3 PHP und Dateien: Die häufigsten Fehler
Vorsicht In der denkbar schlechtesten Konfiguration wird PHP nicht Dateien außerhalb seines gedachten Wirkungsbereiches lesen, sondern auch schreiben können. Dies kann dazu führen, dass ein Dritter z.B. die Benutzerdateien von außen verändern könnte und sich so einen legitimen Benutzerzugang zum System erstellen kann. 2. Zugriff auf Dateien anderer Webanwendungen (shared hosting) Werden mehrere Projekte und/oder Domains über einen Webserver durch das sogenannte shared hosting bereitgestellt, kann es hier natürlich auch dazu kommen, dass ein Projekt auf die Dateien eines anderen zugreifen kann. Dieses Problem ist übrigens wesentlich wahrscheinlicher als der Zugriff auf Systemdateien. Kann man letzteres noch relativ einfach dadurch unterbinden, indem man den PHP-Interpreter unter einem eigenen Benutzer-Account, der auf keine weiteren Dateien Zugriffsrechte hat, betreibt, so ist die Auftrennung in Benutzer-Accounts für die zweite Problemstellung noch nicht die Lösung, denn alle PHP-Anwendungen werden dann unter demselben Benutzer gestartet – und somit hat jede Anwendung auch Zugriff auf andere Dateien. Doch auch hier muss Abhilfe geschaffen werden; selbst wenn Sie allen Ihren Benutzern blind vertrauen können und davon ausgehen dürfen, dass diese sich nicht um die Daten – mögen Sie auch noch so interessant sein – der Anderen kümmern werden, so bedeutet dies auch: Hat einer dieser Anwender schlecht programmierte PHP-Skripte im Einsatz, die durch Dritte manipuliert werden können, bedeutet das ein Sicherheitsrisiko für die Daten aller Benutzer. Beide Probleme sind mehr oder weniger einfach über eine sorgfältige Konfiguration oder den Einsatz von Wrappern in den Griff zu bekommen. Die Lösung ist hierbei auch mehrstufig zu sehen: Zuerst sollte die Konfiguration des Webservers so angepasst werden, dass eine hohe Sicherheit bereits durch die Umgebung gewährleistet ist; diese Sicherheit wird später durch Einstellungen der php.ini lediglich verbessert. Benutzerrechte
Die Änderung der grundsätzlichen Sicherheitskonfiguration bzw. dessen Benutzerberechtigung hängt stark davon ab, welches Betriebssystem auf dem Server eingesetzt wird. Ausschlaggebend hierbei ist jedoch, dass sowohl der Webserver als auch der PHPInterpreter unter einem eigenständigen Benutzer-Account betrieben werden. Dieser Benutzer sollte sehr eingeschränkte Zugriffsrechte besitzen, um im Falle eines Falles den Schaden durch einen Dritten so gut wie möglich zu begrenzen.
58
3.3 Globaler Dateisystemzugriff
Soweit möglich sollten für Webserver und PHP auch getrennte Benutzer verwendet werden. Linux/Unix Beim Apache-Webserver werden der Benutzer und die Gruppe, unterhalb dessen die Serverprozesse ausgeführt werden sollen, in der Konfigurationsdatei httpd.conf festgelegt.
Hinweis Diese Anweisungen können natürlich auch innerhalb einer in die Hauptkonfigurationsdatei inkludierten Datei stehen. Zur Festlegung eines Benutzers, unter dem der Webserver die Anfragen von Clients beantworten soll, wird die Direktive User verwendet. Sie wird entweder vom Benutzernamen oder einem Rautezeichen und der Benutzernummer gefolgt. Der Benutzer sollte auf dem gesamten System keinesfalls Berechtigungen auf Dateien besitzen, deren Inhalte nicht für die »Außenwelt« bestimmt sind.
Wichtig Verwenden Sie unbedingt einen eigenständigen Benutzer, der lediglich Leserechte (und falls notwendig: Schreibrechte) auf Dateien und Verzeichnisse besitzt, die er zur Bereitstellung von Webseiten und -anwendungen benötigt. Die Apache- und PHP-Konfigurationsdateien müssen nicht durch diesen Benutzer gelesen werden können! Ist es wirklich notwendig, bestimmte Dateien sowohl für den Webserver als auch für »interne« Zwecke bereitzustellen, so sollte dieser Zugriff nicht über die Benutzerberechtigung, sondern über die Gruppenrechte abgewickelt werden. Um die Benutzergruppe festzulegen, die der Apache-Webserver nutzen soll, kann die Direktive Group verwendet werden (ebenfalls gefolgt vom Gruppennamen oder der Raute mit anschließender Nummer). Auf vielen Systemen gibt es den sehr niedrig privilegierten Benutzer nobody und seine zugehörige Gruppe nogroup, diese eignen sich geradezu für die Verwendung durch den Apache-Webserver: User nobody Group nogroup
Listing 3.1:
Konfigurationsausschnitt aus der httpd.conf
59
Kapitel 3 PHP und Dateien: Die häufigsten Fehler
Hinweis Auf einigen Systemen existiert für diesen Zweck auch der Benutzer wwwuser mit der Gruppe wwwgroup, auf anderen wiederum tragen sowohl Benutzer als auch Gruppe den Namen www; wenn Sie sich nicht sicher sind, welchen Aufgaben ein solcher Benutzer bereits zugeordnet ist, sollte ein neuer Benutzer angelegt werden. So wird vermieden, dass der Webserver auf Dateien Zugriff erhalten kann, die dafür nicht gedacht sind. Dieser eingeschränkte Benutzer sollte dann auch nur Verzeichnisse einsehen können, die für den Betrieb unbedingt notwendig sind (etwa das temporäre Verzeichnis /tmp für die Ablage von temporären Dateien, etwa PHP-Uploads und natürlich die Verzeichnisse, in denen die bereitzustellenden Webseiten und Skripte abgelegt sind). Auf Schreibrechte sollte für diesen Benutzer weitestgehend verzichtet werden (wenn diese unumgänglich sind, etwa weil eine Upload-Funktion bereitgestellt werden soll, dann sollte der Schreibzugriff auf einzelne Unterverzeichnisse beschränkt werden). Nun kann man leicht auf den Gedanken kommen, den Apache-Webserver unter dem Superuser root zu betreiben – denn immerhin wird er auch von diesem Benutzer gestartet (was notwendig ist, da sonst der privilegierte TCP-Port 80 nicht durch den Serverdienst verwendet werden könnte). Doch dies birgt unkalkulierbare Sicherheitsrisiken: Der Webserver wird unmittelbar nach dem Startvorgang (eigentlich schon während des Starts) die »neue« Identität mittels setuid und setgid annehmen, die mit User und Group definiert wurde. Dies ist innerhalb des Betriebssystems ein geschlossener Vorgang – danach besitzt der Prozess keine Superuser-Rechte mehr. Schafft es also ein Dritter, den Webserver zu kompromittieren (etwa durch einen Buffer Overflow) kann er alle Aktionen nur mit den Rechten ausführen, die der angegebene Benutzer besitzt. Wird der Webserver jedoch weiter aktiv unter dem Superuser betrieben, hat ein solcher Angreifer natürlich freie Hand und könnte beispielsweise die Benutzerdatenbank des Systems verändern – und Sie somit aussperren. Wie gefährlich der Betrieb unter dem Superuser ist, bringt auch bereits der ApacheWebserver zum Ausdruck. Um die Verwendung des Superusers überhaupt erst zu ermöglichen, muss die Umgebungsvariable CFLAGS bereits zur Kompilierung des Apache-Webservers um die Option –DBIG_SECURITY_HOLE erweitert werden – die Bezeichnung BIG_SECURITY_HOLE sagt bereits deutlich aus, warum der Benutzer root keinesfalls als Laufzeitbenutzer des Apache-Systems verwendet werden soll. Windows Im Gegensatz zu Linux stehen User und Group als Direktiven für die Apache-Konfiguration hier nicht zur Verfügung.
60
3.3 Globaler Dateisystemzugriff
Es gibt nun mehrere Möglichkeiten, den Apache-Webserver dennoch unter einem bestimmten Benutzer zu starten. Alle haben eines gemein: Der entsprechende Prozess muss wirklich unter dem gewünschten Benutzer gestartet werden; es ist nicht wie unter Linux/Unix möglich, die Konfiguration einzulesen und danach den Prozess zu einem weniger privilegierten Benutzer wechseln zu lassen. Dies will heißen: Unter Windows müssen die Konfiguration und zusätzlich benötigte Dateien – wie etwa DLLs – durch den verwendeten Benutzer lesbar sein. Dies sollte allerdings keinesfalls ein Administrator oder Hauptbenutzer sein. Der Benutzer, unter dem der Webserver betrieben werden soll, sollte also nicht in folgenden Benutzergruppen Mitglied sein: 쐽
Administratoren
쐽
Benutzer
쐽
Hauptbenutzer
쐽
Debugger-Benutzer (steht nur auf Entwicklungssystemen zur Verfügung)
Die Apache-Installation für diesen Benutzer sollte zudem nicht allgemein zugänglich sein, empfiehlt es sich, den Webserver unterhalb des Benutzerprofils (unterhalb des »Dokumente und Einstellungen«-Verzeichnisses zu finden) zu installieren, das Installationsverzeichnis sollte also etwa C:\Dokumente und Einstellungen\ApacheBenutzer\Apache sein. Nach der Installation sollte zudem sichergestellt werden, dass nur dieser Benutzer Zugriff auf dieses Verzeichnis hat, die tatsächlichen Webseiten und Skripte lassen sich in einem anderen Verzeichnis ablegen, die dann auch von den Benutzern geschrieben werden können, die Änderungen an diesen Dateien vornehmen sollen (dies lässt sich mittels DocumentRoot innerhalb der httpd.conf leicht beeinflussen). Um den Apache-Webserver unter einem bestimmten Benutzer zu starten, gibt es drei Möglichkeiten: 1. Login und manueller Start Unter Windows werden – wie bei anderen Betriebssystemen auch – Programme immer mit den Rechten des aktuellen Benutzers gestartet. Für den Start eines Webservers bedeutet dies allerdings auch: Man muss sich stets unter diesem Benutzer erst einloggen, ein sofortiger Start des Webservers nach dem Systemstart ist somit nicht möglich. Alternativ wäre es natürlich denkbar, dass man die Auto-Login-Funktion von Windows benutzt. Hierbei wird nach dem Laden des Betriebssystems automatisch ein bestimmter Benutzer eingeloggt. In dessen Autostart könnte dann der Webserver aufgerufen werden – dieser wird dann mit den Rechten des Benutzers ausgeführt. Dieser Mechanismus macht allerdings einige andere Bestre-
61
Kapitel 3 PHP und Dateien: Die häufigsten Fehler
bungen zunichte, denn das Passwort dieses Benutzers steht im Klartext in der Registry.
Vorsicht Diese Vorgehensweise ist zwar bequem aber keinesfalls zu empfehlen. Wer immer sich vor den betroffenen Rechner setzt, kann mit den Rechten des eingeloggten Benutzers arbeiten. Zudem kann über die Registry das Passwort im Klartext eingesehen werden (dazu muss man sich nicht zwingend vor dem betroffenen System befinden – die Registry kann auch von entfernten Rechnern aus inspiziert werden), zudem ist es auch mit PHP möglich, die Registry »unter die Lupe zu nehmen«. 2. Aufruf von runas Mit Windows 2000 kam das Tool runas auf die Windows-PCs. Alternativ zu diesem Programm wurde der Menüpunkt AUSFÜHREN ALS in das Kontextmenü von ausführbaren Programmdateien eingeführt. Damit ist es möglich, ein Programm mit anderen Benutzerrechten zu starten – unabhängig vom aktuell angemeldeten Benutzer. Dies kann durchaus sinnvoll sein, wenn von einem zentralen Benutzer aus verschiedene Dienstprogramme gestartet werden sollen. Allerdings ist auch hier erst ein Login (egal ob manuell oder automatisch) notwendig.
Vorsicht Diese Möglichkeit sollte nicht verwendet werden, aus Sicherheitsgründen kann nicht ausgeschlossen werden, dass ein per runas gestartetes Programm die Rechte des eigentlich eingeloggten Benutzers erhält (ein interessanter Artikel dazu findet sich beispielsweise unter http://www.haxorcitos.com/ MSRC-6005bgs-EN.txt). 3. Installation als Systemdienst Diese Methode ist im Vergleich die effizienteste und sicherste. Der ApacheWebserver lässt sich mit dem Aufrufparameter –k install als Systemdienst installieren. Dieser kann dann – je nach Einstellung – entweder manuell oder bereits beim Systemstart vor einem Benutzer-Login gestartet werden. In diesem Fall ist es also nicht notwendig, dass sich ein Benutzer anmeldet. Weiterhin kann ein Service eine vollkommen autarke Berechtigung erhalten. Entweder wird der Dienst mit dem »lokalen Systemkonto« oder mit einem spezifischen Benutzer gestartet. Wie jeder Dienst wird der Apache-Webserver zu Beginn auch unter dem lokalen Systemkonto betrieben, dem umfangreiche Zugriffsrechte gewährt werden.
62
3.3 Globaler Dateisystemzugriff
Dies widerspricht natürlich jeder erdenklichen Sicherheitspolitik. Nicht nur, dass dieser Prozess dann Systemdateien manipulieren kann, vielmehr ist es auch anderen Prozessen, die unterhalb dieses Systemkontos betrieben werden, möglich, Dateien des Apache-Webservers einzusehen oder gar zu verändern. Es sollte also unbedingt ein eigener Benutzer für den Betrieb des Apache-Webserver-Dienstes angelegt werden, dieser sollte lediglich niedrige Rechte erhalten, also etwa der Benutzergruppe »eingeschränkte Benutzer« oder gar »Gäste« zugeordnet werden. Zudem sollte diesem Benutzer ein sehr komplexes und sehr langes Kennwort zugeordnet werden (dieses muss lediglich einmalig bei der Dienstkonfiguration angegeben werden, der Apache-Webserver selbst muss es nicht wissen), dem Benutzer sollte außerdem die Möglichkeit genommen werden, das Kennwort selbst zu ändern. Dieses Benutzerkonto sollte dem Dienst dann als aktives Konto übergeben werden. Die gesamte Konfiguration erfolgt daraufhin nach der Installation des Dienstes etwa so: 1. Start der »Computerverwaltung« über SYSTEMSTEUERUNG | VERWALTUNG | COMPUTERVERWALTUNG 2. Wechsel zu LOKALE BENUTZER UND GRUPPEN, Anlage eines neuen Benutzers mit den Eckdaten: 쐽
Komplexes Kennwort, das sowohl Buchstaben als auch Ziffern und Sonderzeichen enthält, die Mindestlänge sollte 12 Zeichen sein
쐽
Deaktivierung der Optionen BENUTZER MUSS KENNWORT BEI DER NÄCHSTEN ANMELDUNG ÄNDERN und BENUTZER KANN KENNWORT ÄNDERN
3. Dem Benutzer sollte lediglich die Mitgliedschaft in der Gruppe »Gäste« zugeordnet werden 4. Unterhalb von DIENSTE UND ANWENDUNGEN, DIENSTE sollte der Dienst »Apache2.2« (je nach Version variierend) editiert werden 5. Unter der Karte ANMELDEN sollte DIESES KONTO: aktiviert und der neu angelegte Benutzer eingetragen werden
Hinweis Allerdings steht diese Methode nicht unter Windows 95, 98 und ME zur Verfügung. Um die Sicherheit weiter zu erhöhen, könnte der Dienst in seiner allgemeinen Konfiguration auf den Starttyp MANUELL gestellt werden, somit würde er beim Systemstart nicht automatisch gestartet und es wäre erforderlich, dass ein Administrator sich am System anmeldet und den Dienst von Hand über die Dienstverwaltung startet.
63
Kapitel 3 PHP und Dateien: Die häufigsten Fehler
Rechte der Webanwendungen
In einer Shared-Hosting-Umgebung kann bereits auch ein weiteres Problem entschärft werden, wenn VirtualHost-Blöcke zur Konfiguration der einzelnen (Sub-) Domains verwendet werden. Hier lässt sich bereits verhindern, dass von einer Domain auf Dateien einer anderen zugegriffen werden kann, indem für jeden VirtualHost-Block ein eigener Benutzer verwendet wird. Die Einstellung der Direktive User dient dann nur noch als Fallback-Lösung, kommt also nur noch zum Einsatz, wenn der spezifische Benutzer nicht verwendet werden kann. Eines jedoch vorweg: Diese Lösung basiert auf suExec. Dies ist ein Wrapper, bei dem vor dem Start einer CGI-Anwendung ein zum Webserver-Prozess alternativer Benutzer und ebenso eine alternative Gruppe gesetzt werden können. Mit diesen Berechtigungen wird das CGI-Programm anschließend gestartet, während der Webserver weiter unter seinen bisherigen Benutzerrechten betrieben wird.
Vorsicht Die Nutzung von suExec selbst sollte abgewogen werden, da es zwar Sicherheit vermittelt, jedoch auch »gern« von Angreifern genutzt wird, um den Webserver zu kompromittieren. Einen detaillierteren Einstieg in suExec erhalten Sie im Abschnitt 14.4 suExec auf Seite 406! Der Betrieb als CGI-Modul kann für PHP jedoch starke Verluste bei der Ausführungsgeschwindigkeit von Skripten nach sich ziehen, es sollte also nicht einfach pauschal auf suExec und CGI umgestellt werden; die Performance unter CGI sollte zuvor mit jeder in Frage kommenden Webanwendung getestet und bewertet werden. Um Benutzer und Gruppe für suExec zu setzen, kann innerhalb eines VirtualHost-Blockes die Direktive suExecUserGroup <user> verwendet werden. Es ist allerdings vorher die Konfiguration von suExec notwendig. Es gibt jedoch auch eine Alternative zum suExec-Wrapper. Dabei werden alle PHPAnwendungen zwar durch den gleichen Benutzer gestartet, der Zugriff der einzelnen PHP-Skripte wird jedoch auf ein bestimmtes Verzeichnis und seine Unterverzeichnisse beschränkt. Die open_basedir-Direktive wird hierbei in jedem VirtualHost-Block spezifisch vergeben. Ein Zugriff auf Dateien außerhalb des angegebenen Verzeichnisbaums ist den PHP-Skripten dann nicht mehr möglich. Allerdings kommt es dann auch zu Einschränkungen beim Entwicklungskomfort solcher PHP-Anwendungen. Ein über mehrere VirtualHost-Blöcke gemeinsam genutztes Verzeichnis mit allgemein gültigen include()-Dateien ist dann nicht mehr möglich; ebenso Probleme bereiten können verlinkte Dateien.
64
3.4 Auslieferung von Dateien
3.4
Auslieferung von Dateien
Der Dateizugriff kann auch noch in ganz anderer Art und Weise sehr gefährlich sein, und zwar immer dann, sobald Dateinamen und -pfade für Operationen aus externen Parametern direkt und ungeprüft verwendet werden. Nicht nur der Zugriff auf Dateien anderer Serverbenutzer und Systemdateien durch ein PHP-Skript (siehe Abschnitt 3.3 Globaler Dateisystemzugriff auf Seite 57) stellt eine Gefahr dar, sondern auch die ungeprüfte Verwendung von Parametern als Dateipfade ist nicht ungefährlich. Im Abschnitt 3.5 Include-Dateien auf Seite 67 wird ausführlich beschrieben, inwiefern dies Auswirkungen haben kann, sofern Dateien aus solchen Parametern heraus ungeprüft per include() oder require() in den aktuellen Code eingebettet werden. In diesen Fällen ist es für den Angreifer sehr leicht möglich, eigenen Code einzuschleusen und auszuführen. Doch manchmal ist dies gar nicht notwendig. Wenn sich der Angreifer vom Skript des Webservers die Quelldateien im Originaltext besorgen kann und er nur bestimmte Metadaten in Erfahrung bringen muss, ist es nicht unbedingt notwendig, dass er erst aufwändig selbst PHP-Code entwickelt, der dann ausgeführt wird und die benötigten Informationen liefert. Dieses Problem der ungewollten Quellcode-Weitergabe besteht zwar bereits bei ungeparsten Dateierweiterungen (vgl. Abschnitt 3.5.1 Ungeparste Dateiendung auf Seite 67) doch bei sehr »freizügig« entwickelten Skripten ist es leicht möglich, eine sehr restriktive Konfiguration zu umgehen, bei der etwa die Auslieferung auf bestimmte Dateisuffixe bereits durch den Webserver begrenzt wird (siehe auch Abschnitt 3.1.1 Backup-Dateien, Versionsverwaltung und Zugriffsschutz auf Seite 44). Besonders gefährlich sind dabei vor allem Skripte, die dem Programmierer und dem Webmaster Arbeit abnehmen und dem Benutzer übermäßigen Komfort bieten sollen. Dies betrifft vor allem Skripte, bei denen Daten auf Anforderung an den Benutzer ausgeliefert werden, etwa ein Download-Skript:
Dies ist noch eine relativ einfache Variante eines solchen Skriptes, auch komplexere Abwandlungen sind denkbar (etwa wenn vor der Ausgabe der Datei noch ein Content-type:-Header übermittelt würde). Doch hier geht es um ein Kernproblem. Ein solches Skript wird normalerweise mit einem anderen Skript in Kombina-
65
Kapitel 3 PHP und Dateien: Die häufigsten Fehler
tion verwendet: Der Benutzer bekommt eine Liste von vorhandenen Dateien für den Download zur Auswahl angezeigt, klickt er auf einen solchen Link, so wird etwa ein Link http://testserver/download.php?file=test.txt aufgerufen. Das oben beschriebene Skript wird nun die Datei, die über den file-Parameter spezifiziert wurde, an den Benutzer übertragen. Die Grundidee ist gut, die Umsetzung jedoch denkbar schlecht. Der Dateipfad wird durch das Skript nicht geprüft, ein Angreifer kann nun also jeden erdenklichen Pfad angeben und erhält diese Datei. Wurde nun open_basedir spezifiziert, kann er selbst bei ungünstiger Rechtevergabe nicht auf Systemdateien zugreifen und diese auslesen. Jedoch kann er auch die Dateien auslesen, die durch den PHP-Interpreter geparst werden – und von denen er normalerweise den Quelltext nicht einsehen kann. Ein Link http://testserver/download.php?file=index.php führt also zur Ausgabe der index.php im Quelltext. Die index.php ist meist ein guter Einstieg, da sie auf vielen Webservern die Standarddatei des Verzeichnisses ist – über den Quelltext lassen sich dann sehr leicht inkludierte Dateien ermitteln, die Wahrscheinlichkeit, dass in einer solchen Datei Meta-Daten gespeichert sind (etwa Datenbankpasswörter), die für einen Angreifer sehr interessant sein könnten, ist sehr groß. Für dieses Dilemma gibt es an und für sich zwei wesentliche Lösungsmöglichkeiten: 1. Der übergebene Pfad wird geprüft und alle zum Download zugelassenen Dateien werden ausschließlich in einem Verzeichnis abgelegt. Dieses Verzeichnis wird als Präfix für die Dateipfade verwendet. Diese Parameterangaben müssen vorher überprüft werden, es darf darin dann natürlich kein Slash enthalten sein, da über diesen Weg ein Angreifer möglicherweise in ein anderes Verzeichnis übergehen könnte. 2. Es werden keine Dateinamen direkt übergeben, es wird vielmehr mit Referenzen gearbeitet – der Benutzer übergibt also im Link keinen direkten Pfad. Die Links, die der Benutzer übermittelt bekommt, werden nicht mit Dateinamen sondern etwa mit IDs versehen. Diese IDs werden vom Server fest für die jeweilige Datei vergeben und beim Aufruf des Download-Skriptes werden die IDs dann wieder in den tatsächlichen Dateinamen »umgewandelt«.
Hinweis Diese Problematik ist natürlich nicht nur auf die Funktion readfile() beschränkt, sondern sollte überall dort bedacht werden, wo der Benutzer direkten Einfluss auf eine zu öffnende Datei hat. Wesentlich schlimmer ist es noch, wenn in eine solche Datei geschrieben werden soll: Ein Angreifer könnte somit Quelldateien der Website überschreiben und somit die Website »außer Gefecht setzen«!
66
3.5 Include-Dateien
Die Referenzierung ist die wesentlich sicherere Methodik, da Pfadnamen nicht vom Skript geprüfte Kodierungen enthalten könnten, die das Skript dann zulässt, die jedoch tatsächlich dazu führen könnten, dass eine Datei aus einem übergeordneten Verzeichnis ausgelesen wird. Ein ausführliches Beispiel zur Referenzierung finden Sie im Abschnitt 6.3 Download und PHP auf Seite 205.
3.5
Include-Dateien
Viele Funktionen werden inzwischen in eigene Dateien ausgelagert, da sie an verschiedenen Stellen erneut verwendet werden. Mit der deutlich besseren Integration der objektorientierten Programmierung in PHP mit Version 5.0 hat dieses Verhalten auch deutlich zugenommen. Die Anweisungen include(), include_once(), require() und require_once() haben allerdings mehrere direkte und indirekte Angriffspunkte. Diese Schwachstellen kann man allerdings keinesfalls PHP zum Vorwurf machen, sie entstehen erst durch einen sorglosen Umgang mit Dateien und der Programmiersprache PHP. Dafür muss man sich ins Gedächtnis rufen, dass Sprachen wie PHP und Ruby einfach zu erlernen sind, mit ihnen allerdings dennoch mächtige Anwendungen entstehen können. Dabei stellt die Programmiersprache keinesfalls ein Rundum-Sorglos-Paket dar – bei einigen Fragen – vor allem der Sicherheit – mussten die PHP-Entwickler Abstriche vornehmen, um die Sprache nicht zu komplex und umständlich werden zu lassen. Häufige Fallen in Verbindung mit der Inkludierung von Quelltext sind: 쐽
Dateiendung der inkludierten Dateien wird nicht vom Webserver geparst
쐽
Dateien liegen in einem öffentlich zugänglichen Verzeichnis
쐽
Dateinamen, die von außen als Parameter kommen, werden für include() und require() verwendet
쐽
Auch hier gilt: Diese Liste ist keinesfalls vollständig, sie soll nur sensibilisieren!
3.5.1
Ungeparste Dateiendung
Dies ist leider ein sehr oft gemachter Fehler – fatal wird es, wenn dieser Fehler zu geglaubter Sicherheit führt, die dann keinesfalls vorhanden ist. Dieses Szenario ist dabei nicht einmal neu, es existiert schon seit jeher, und es existiert nicht nur mit PHP, sondern mit jeder serverseitigen Programmiersprache und einer wenig restriktiven Webserverkonfiguration. Um das Szenario zu verdeutlichen, hier ein kleines Beispiel mit folgender ApacheWebserverkonfiguration (Auszug):
67
Kapitel 3 PHP und Dateien: Die häufigsten Fehler
LoadModule php5_module "C:/Programme/xampp/apache/bin/php5apache2.dll" AddType application/x-httpd-php .php .php5 .php4 .php3 .phtml
Alle Dateien mit den Endungen .php, .php5, .php4, .php3 und .phtml werden dabei, nachdem sie von einem Client angefordert wurden, an PHP übergeben – erst dessen Ausgabe wird an den Client versendet. Problemlos ist in dieser Konfiguration ein Skript dieser Bauart:
Arbeiten nun jedoch mehrere Entwickler an diesem Projekt und die Datenbankverbindungsinformationen sollen nicht mehr für jeden Entwickler zugänglich sein, so werden diese in eine eigene Datei db.php ausgelagert. Die beiden Dateien sehen danach also wie folgt aus:
Listing 3.2:
db.php: Datenbankkonfigurationsdaten
68
3.5 Include-Dateien
Hinweis Um den Zugriff durch andere Entwickler zu unterbinden, wären natürlich weitere Maßnahmen – etwa Einschränkungen der jeweiligen FTP- oder Versionsmanagementzugänge – erforderlich. Alternativ kann es sein, dass sie die selben Zugangsdaten für mehrere kleine Subprojekte benötigen – so wäre es mit der Auslagerung nicht notwendig, diese Zugangsdaten in jedem Projekt einzeln zu führen; sie würden über die zentrale db.php inkludiert. Leider kommt es nun oft zu einem folgenschweren Fehler, der nicht einmal als solcher gesehen wird. Um die Dateien besser zu strukturieren, wird manch PHP-Entwickler die Dateien, die lediglich von anderen in irgendeiner Weise inkludiert werden, mit dem zusätzlichen Dateisuffix .inc versehen, so dass er anhand der Verzeichnislistings gleich erkennen kann, welche Dateien keine eigenständige Funktion, sondern lediglich Funktions- oder Datenlieferant für andere sind. Es kommt nun stark auf den Programmierer an, wie er den Dateinamen erweitert. Denkbar sind hier nun zwei Fälle: db.inc.php db.php.inc
Betrachtet man nun die Dateinamen in Zusammenhang mit der Webserverkonfiguration, sieht man, dass die erste Variante keinen Einfluss auf die Sicherheit hat. Der Webserver zieht für die Übergabe an den PHP-Interpreter ausschließlich die Dateiendung nach dem letzten Punkt zu Rate. Bei db.inc.php lautet diese also .php, und diese wird von der Zeile AddType application/x-httpd-php abgedeckt. Die andere Benennung mit dem Suffix .inc sorgt allerdings hier für Probleme: Diese Endung taucht in der erwähnten Zeile nicht auf. Doch was bedeutet das? In einer normalen Webserver-Standardkonfiguration, die nicht restriktiv ausgelegt ist, werden alle Dateien, die keinem Modul zugeordnet sind und vom Client angefordert werden, direkt als Klartext an den jeweiligen Client ausgeliefert. Hier bedeutet das: Weiß ein Angreifer, dass es eine Datei db.php.inc gibt und er fordert sie über den Webserver (z.B. mit einer URL http://testserver/db.php.inc) an, sieht er den Inhalt der Datei und somit die Datenbankzugangsdaten. Wäre die Datei vom PHP-Interpreter geparst worden, so sähe der Angreifer nichts, denn das Skript hat keine Ausgabe erzeugt. Dies ist durchaus eine vertrackte Problematik. Wenn ein Dritter davon ausgehen kann, dass es »ungeschützte« Skripte mit sensiblen Daten auf einem Server gibt, kann er systematisch in Frage kommende Dateinamen durchgehen und diese vom
69
Kapitel 3 PHP und Dateien: Die häufigsten Fehler
Server anfragen – früher oder später wird er dann an Daten, die für ihn interessant sein könnten, gelangen. Um dies zu verhindern, gibt es konkret zwei Möglichkeiten. Zum Einen gibt es hier die starke Disziplin, Dateien nur so zu benennen, dass sie auch durch den Webserver an den PHP-Interpreter übergeben und somit geparst werden. Besonders in größeren Projekten mit Entwicklern, die dieses »Namenssystem« bereits seit Jahren einsetzen, ist es nicht leicht, eine Umstellung der Benennung zu erreichen. Zum Anderen gibt es noch eine viel sicherere Methode, bei der keine Disziplin seitens der Entwickler erforderlich ist1. Hier muss einfach eine viel restriktivere Konfiguration des Webservers vorgenommen werden, so dass nur noch Dateien an Clients ausgeliefert werden dürfen, die eine bestimmte Endung tragen. Dies bedeutet auch, dass weniger Flexibilität geboten ist. Möchte man auf einem solchen System spontan eine PDF-Datei über den Webserver bereitstellen und das Suffix .pdf steht nicht auf der Liste der zugelassenen Dateien, so ist ein Download dieser Dateien nicht möglich – der Client wird den HTTP-Fehler 403 (Forbidden, Zugriff verweigert) erhalten. Diese Restriktion gibt allerdings die Gewissheit, dass Clients nur Dateien erhalten, die für sie auch bestimmt sind. Auf die Arbeit mit PHP haben diese Einstellungen übrigens keinen Einfluss. Wenn Dateien lokal mit include() oder require() in ein Skript geladen werden, erfolgt dieser Zugriff lediglich durch PHP und innerhalb des lokalen Dateisystems. Für eine solche Konfiguration muss man sich jedoch darüber im Klaren sein, welche Dateien von Clients angefordert werden sollen. In Frage kommen dabei zum Beispiel:
70
쐽
PHP-Skripte, die von Clients aufgerufen werden (Suffixe: .php, .php3, .php4, .php5, .phtml; alle Dateien dieser Gruppe sollten vom Webserver an PHP übergeben werden)
쐽
HTML-Dateien (Suffixe: .html, .htm)
쐽
Bild-Dateien (Suffixe: .jpg, .jpeg, .gif, .png; evtl. weitere, etwa .bmp, .tiff, …)
쐽
Filme (Suffixe: .mpg, .mpeg, .avi, .mov, .wmv)
쐽
Musik (Suffixe: .mp3, .wav, .wma)
쐽
Andere Multimedia-Dateien (etwa Shockwave-Flash)
쐽
Downloads (Suffixe: .exe, .pdf, .gz, .zip, .bin, .msi)
1
Das soll keinesfalls bedeuten, dass in solchen Projekten nun jeder Entwickler nach Gutdünken handeln soll!
3.5 Include-Dateien
Innerhalb einer Apache-Konfiguration ließe sich der Zugriff auf die oben genannten Dateien mit folgendem Block beschränken: Order Deny,Allow Deny from all Allow from none
Diese Konfiguration ist dabei restriktiv: Es wird angegeben, welche Dateiendungen erlaubt sind (dies wird durch die Negierung des regulären Ausdrucks mit ^ erreicht). Andere Webserversysteme sind hierbei teilweise weniger flexibel und erlauben es lediglich, Muster der Dateien anzugeben, die nicht an einen Client ausgeliefert werden sollen. Der Roxen-Webserver etwa unterstützt eine Liste der internen Dateien; dies stellt Dateien dar, die nicht von einem Client direkt angefordert werden können und die auch in keinem Verzeichnislisting aufgeführt werden. Dabei ist das Verfahren umgekehrt: Es müssen alle Dateien angegeben werden, die ausgeschlossen werden sollen; eine Negierungsmöglichkeit gibt es nicht. Das Problem der include()-Dateien lässt sich so leider nur schwer beheben. Ein Anfang ist allerdings folgender Roxen-Konfigurationseintrag: <str>*.php.* <str>*.inc
Wichtig Das Tag internal_files steht lediglich innerhalb von Filesystem-Modulen zur Verfügung. Benennt nun allerdings ein Entwickler seine include-Dateien etwa mit .php.req (in Anlehnung an require()), wirkt der Filter nicht mehr.
3.5.2 Dateien in öffentlich zugänglichem Verzeichnis Zunehmend werden vor allem kleinere PHP-Projekte nicht mehr auf einem einzelnen Webserver gehostet. Vielmehr teilen sie sich einen Server mit anderen Anwendungen auf einem Shared-Hosting-Server. Dies ist durchaus sinnvoll, denn es spart Kosten und minimiert den notwendigen Administrationsaufwand.
71
Kapitel 3 PHP und Dateien: Die häufigsten Fehler
Selten wird der Server dann ausschließlich vom gleichen Entwicklerteam genutzt. Meist ist es so, dass der Server von einem ISP bereit gestellt wird und verschiedene Kunden ihre eigenen PHP-Anwendungen darauf verwalten. Dies hat zur Folge, dass verschiedene Personen Zugriff auf den gleichen Server haben. Oft wird leider vergessen, dass dies auch eine Gefahr für sensible Daten darstellt; dies trifft vor allem dann zu, wenn der Server schlecht konfiguriert ist. Dieses Problem steigert sich umso mehr, desto mehr der einzelne Kunde darf: Ein Shell-Zugang mag für die Installation von zusätzlicher Software (etwa Foren- oder CM-Software) praktisch sein, stellt jedoch möglicherweise ein Problem dar. Diese »Lücke« entsteht durch einen Kompromiss, der zwischen Performance des Webservers und PHP sowie der Sicherheit der Benutzerdateien getroffen wird. Es gibt zwei Möglichkeiten für einen Webserver, den PHP-Interpreter anzusprechen: Entweder er lädt ein entsprechendes Modul, so dass der PHP-Interpreter immer zur Verfügung steht und leichter Instanzen für ein neues Skript gestartet werden können, oder PHP wird als CGI-Modul bzw. -Applikation gestartet. Für den Start als CGI (Common Gateway Interface) ist es allerdings notwendig, dass der PHP-Interpreter für jedes zu parsende Skript neu gestartet wird. Nach jedem Aufruf wird PHP wieder beendet. Jedes Skript wird also in einer abgeschlossenen Umgebung aufgerufen. Der Vorteil von CGI gegenüber einem Webserver-Modul ist in gewisser Weise fantastisch: Ein CGI-Modul lässt sich unter einem anderen Benutzer starten. Unter dessen Rechten wird dann das entsprechende Skript ausgeführt. In dieser Konfiguration ist es möglich, lediglich dem gewünschten Benutzer Zugriffsrechte auf die Skripte und Dateien, die vom Skript gelesen oder geschrieben werden, zu geben. Allerdings geht dieses Verhalten zu Lasten der Performance. Für jedes Skript wird der PHP-Interpreter als neuer Prozess geladen; zudem sind übergreifende Aufrufe – etwa persistente Datenbankverbindungen – nicht möglich. Jedes Skript muss also seine Datenbankverbindung erneut aufbauen, selbst wenn es nur eine einzige Abfrage ausführt. Performant besser ist der Aufruf über ein Webserver-Modul. Hierbei ist der Interpreter ständig geladen, wird allerdings immer mit den Rechten des Webservers aufgerufen. Da diese Methode jedoch deutliche Geschwindigkeitsvorteile bringt, müssen auch Skripte und zugehörige Dateien vom Benutzer des Webservers gelesen und nötigenfalls auch geschrieben werden können. Dieser Umstand für sich ist jedoch auch noch nicht tragisch – es fehlt hier noch ein Glied der Kette, das in vielen Serverkonfigurationen eine Selbstverständlichkeit ist. Um die Zusammenhänge hier klar zu erkennen, ist ein Streifzug in das UnixBerechtigungskonzept notwendig.
Hinweis Auch wenn hier ausschließlich von Linux bzw. Unix die Rede ist: Das Problem besteht in Abwandlung auch unter anderen Betriebssystemen wie etwa Windows.
72
3.5 Include-Dateien
Jeder PHP-Entwickler, dessen Projekte auf einem Linux- oder Unix-Server beheimatet sind, wird dem Rechtesystem schon einmal begegnet sein, auch wenn er – mangels Kentnisse der Administration solcher Systeme – dies nicht bemerkt hat. Der chmod-Befehl, der meist auf 755 gesetzt wird, steuert genau diese Berechtigungen für Dateien und Verzeichnisse. In einigen FTP-Programmen kommt man selbst mit diesem drei- und bei bestimmten Anwendungszwecken auch vierstelligem Zahlencode nicht in Berührung. In solchen Applikationen gibt man an, welche Benutzergattung inwieweit auf ein entsprechendes Objekt zugreifen darf und der FTP-Client setzt sich den entsprechenden Wert selbst zusammen und übermittelt diesen an den Server. Doch was bedeuten diese drei Ziffern? Das Konzept auf Unix-artigen Betriebssystemen ist im Grundsatz einfach, flexibel, transportabel und dennoch sehr mächtig. Auf anderen Betriebssystemen wird jedem Benutzer einzeln der Zugriff auf Dateien und Verzeichnisse erlaubt. Dabei erhält jeder Benutzer für jedes gewünschte Objekt eine individuelle Berechtigung. Dieses System ist sehr komplex, es können für einzelne Dateien seitenweise Berechtigungslisten entstehen (durch Anwendung von Benutzergruppen können diese verkürzt werden). Diese Methodik hat allerdings ihre Nachteile. Sie ist sehr unübersichtlich; es ist nicht innerhalb weniger Sekunden erkennbar, welcher Benutzer nun welche spezifischen Rechte besitzt. Steht der Benutzer nicht in der Liste der Zugriffsrechte eines Objektes, könnte eventuell eine der Gruppen spezifiziert sein, in der er Mitglied ist. Ist auch dies nicht der Fall, muss noch geprüft werden, ob er – oder eine seiner Gruppen – Zugriffsrechte für ein übergeordnetes Objekt besitzt. Weiterhin ist dieses Berechtigungsmodell nicht so leicht übertragbar; diese umfangreichen Daten können nicht innerhalb des Dateisystems als simple Attribute mitgespeichert werden, sie müssen in einer eigenen Datenbank mitgeführt werden. Schon das Verschieben oder Kopieren von Dateien und Verzeichnissen erfordert vom Betriebssystem einen großen Aufwand, um sicherzustellen, dass Berechtigungen nicht verlorengehen. Das Sichern dieser Einstellungen oder gar das Kopieren mittels normaler Dateiarchive ist da gänzlich unmöglich. Unix, Linux und andere Systeme dieser Linie gehen hier einen anderen Weg, der auf den ersten Blick nicht dieselben individuellen Berechtigungen bietet: Für jedes Objekt werden spezifisch die Berechtigungen für den Dateieigner, die Eignergruppe und alle anderen Benutzer festgelegt. Zusätzlich werden für jede Datei und jedes Verzeichnis der Besitzer und die Besitzergruppe – die keinesfalls etwas mit dem Besitzer zu tun hat – festgelegt. Zudem ist es nicht möglich, eine Vererbungsstruktur festzulegen. Werden Rechte für Verzeichnisse neu vergeben, so ist es möglich, diese Rechte auch für Unterverzeichnisse und enthaltene Dateien zu übernehmen, jedoch ist es nicht so, dass ein Benutzer, der ein Leserecht für ein Ver-
73
Kapitel 3 PHP und Dateien: Die häufigsten Fehler
zeichnis hat, auch alle Dateien darin lesen darf; erlaubt das spezifische Dateiattribut keinen Zugriff, so gibt es nichts, dass diesen Zustand anderweitig beeinflusst. Jede dieser drei Rechtegruppen (Eigentümer, Gruppen, andere) kann folgende Rechte erhalten2: 쐽
Kein Zugriff
쐽
Leserecht (Wert: 4)
쐽
Schreibrecht (Wert: 2)
쐽
Ausführrecht (Wert: 1)
쐽
Eine Kombination der drei vorgenannten
Werden verschiedene Rechte für eine Gruppe kombiniert, so werden die jeweiligen Zahlenwerte aufsummiert; die Einzelwerte sind mögliche Potenzen der Basis 2 und lassen somit keinerlei Verwechslungen zu. Der in Web-Umgebungen übliche Aufruf von chmod 755 bedeutet also: 쐽
Der Eigentümer hat Lese-, Schreib- und Ausführberechtigungen
쐽
Die Gruppe hat Lese- und Ausführrechte, kann also eine Datei nicht verändern
쐽
Alle anderen Benutzer haben ebenfalls lediglich Ausführ- und Leseberechtigungen
Doch was bedeutet das nun in der Praxis in Verbindung mit einem Webserver, sensitiven PHP-Skripten und Benutzern mit Zugriff auf das lokale Dateisystem (z.B. durch SSH-Zugang)? Eine Standardkonfiguration auf einem solchen System wird so aussehen: 쐽
Jeder Benutzer hat einen eigenen Account
쐽
Alle Benutzer befinden sich in der gleichen Benutzergruppe (z.B. users)
쐽
Der Webserver wird unter einem eigenen Benutzer (z.B. www) in einer eigenständigen Gruppe (z.B. wwwgroup) betrieben
Der Webserver-Benutzer befindet sich somit nicht in derselben Benutzergruppe wie der Benutzer, dem die Datei gehört – oder gar der Gruppe (die wohl bei allen Dateien dann der Gruppe des Benutzers entsprechen wird). Somit ist es erforderlich, dass die Berechtigung für alle »anderen« Anwender sowohl das Lese- und Ausführrecht umfasst, da sonst der Webserver die PHP-Dateien nicht lesen und sie nicht ausführen dürfte (das Ausführrecht ist erforderlich, damit diese von PHP interpretiert werden). Dass der Eigentümer selbst vollen Zugriff auf seine Dateien benötigt, ist selbstverständlich – denn sonst könnten diese nicht verändert werden 2
74
Die Spezialfälle SUID-/SGID/Sticky-Bit spielen für die Zugriffsproblematik keine Rolle und werden hier außer Acht gelassen.
3.5 Include-Dateien
(sicher: ein Ausführrecht ist wahrscheinlich im Falle von PHP-Dateien nicht zwingend erforderlich, da diese Dateien wohl lediglich vom Webserver und nicht an der Shell ausgeführt werden). Damit ergäbe sich bisher für PHP-Dateien in einem solchen System folgende Berechtigung: 705. Dies weicht lediglich in der Gruppenberechtigung vom »Standard« 755 ab. Doch was hat das auf einem System mit verschiedenen Benutzern, von denen jeder in PHP-Dateien sensitive Daten (etwa Passwörter) vorhält, zur Folge? Nun die 755 hat eine Lese- und Ausführrecht für alle Benutzer der Gruppe, zu der die Datei zugewiesen ist, zur Folge. Ist dies die Gruppe users, kann jeder Benutzer dieser Gruppe eine solche Datei auslesen. Dies bedeutet: Ein Benutzer A kann an der Shell etwa in das Basisverzeichnis der Benutzerverzeichnisse (meist wird dies /home sein) wechseln und anhand des Verzeichnislistings (soweit dies gestattet ist) in ein Verzeichnis eines anderen Benutzers wechseln und beispielsweise dessen Datenbankzugangsdaten auslesen. Dies ist natürlich ein fataler Umstand. Diesem lässt sich damit begegnen, dass ein Verzeichnislisting des /home-Verzeichnisses für Gruppenbenutzer nicht zulässig ist (dabei muss die Gruppe der Benutzer das /home-Verzeichnis besitzen!). Dies kann das Problem allerdings lediglich eindämmen: Kennt jemand den exakten Pfad eines solchen Verzeichnisses oder gar den Pfad einer spezifischen Datei, kann er auch ohne Recht auf das Basisverzeichnis an den fraglichen Inhalt gelangen. Effektiv verhindern lässt sich der Missbrauch nur dadurch, dass die Gruppe keine Berechtigungen erhält, also chmod 705 verwendet wird.
Wichtig Seien Sie bei der Rechtevergabe für alle »anderen« Benutzer nicht zu pessimistisch! Für Verzeichnisse muss immer auch das Ausführrecht vergeben werden, da sonst der Webserver bzw. PHP eventuell nicht auf dieses zugreifen kann; die Anforderung eines Verzeichnislistings wird als Ausführung und nicht als Lesen gewertet! Doch nicht in jeder Umgebung kann die Gruppe ohne Rechte bedacht werden; auf solchen Systemen ist es dann sogar meist üblich, dass verschiedene Kunden unter dem gleichen Systembenutzer Zugriff auf das Dateisystem erlangen, sie also nur über virtuelle Accounts verfügen. In solch einem Fall bringt es relativ wenig, der Gruppe alle Rechte zu entziehen: Der Benutzer kann bereits auf die Dateien anderer Personen zugreifen. Auf solchen Servern sollte es zum Einen selbstverständlich sein, dass keiner der virtuellen Benutzer auf irgendeine Weise direkten Zugriff auf den Server – etwa per SSH – erhält. Zum Anderen muss sichergestellt werden, dass alle Benutzer innerhalb ihres Verzeichnisses bleiben. Bei einem FTP-Server wird hier etwa ein chroot auf das jeweilige Verzeichnis gesetzt (die Wurzel des Dateisystems wird also auf
75
Kapitel 3 PHP und Dateien: Die häufigsten Fehler
den Ordner gesetzt, der Benutzer kann nicht auf Ebenen darüber zugreifen). Allerdings sind Dateisystemzugriffe auch mit PHP möglich; um auch hier die Sicherheit zu bieten, dass niemand Daten anderer ausliest, sollte die PHPKonfigurationsdirektive open_basedir verwendet werden. Mehr dazu erfahren Sie im Abschnitt 9.3 Basisverzeichnis mit open_basedir auf Seite 283.
3.5.3
Externe Dateien und include
Die Einfachheit von PHP basiert zu einem Teil auf der hohen Flexibilität, somit können auch Daten variabel gestaltet werden. Da PHP-Dateien stets neu übersetzt werden, kann auch der Pfad zu einer solchen Datei flexibel gestaltet sein – es spielt lediglich eine Rolle, wie der Pfad im Moment der Interpretation ist und dass die dann »genannte« Datei existent und eine für PHP übersetzbare Datei ist. Kurzum: Die Datei, die per include() oder require() geladen werden soll, muss kein feststehender Name sein, es kann sich auch um eine Variable handeln, die den Pfad enthält. Dies birgt dabei allerdings in mehrerlei Hinsicht eine Gefahr: Wird der Wert des jeweiligen Parameters durch externe Daten gespeist können bei ungenügender Konfiguration des Webservers sensible Daten des Serversystems ausgelesen oder gefährlicher Code auf dem System ausgeführt werden. Folgender Quelltext einer PHP-Datei ist also alles andere als unbedenklich:
Wichtig Grundsätzlich sollte man sich nie blind auf Pfadangaben aus externer Quelle verlassen! Wird eine Datei inkludiert, lässt sich im Gegensatz zu anderen Vorgehensweisen schon einmal ausschließen, dass etwa eine Systemdatei (sehr beliebt ist hier die passwd-Datei aus dem etc-Verzeichnis auf Unix-/Linux-Systemen) ausgelesen wird. Bei include() bzw. require() muss es sich um valide PHP-Dateien handeln, die eine Ausgabe über PHP-Befehle erzeugt. Jedoch ist es möglich, böswilligen Code auszuführen. Im Gegensatz zu anderen Sprachen ist es mit PHP möglich, entfernte Dateien in ein Skript einzubinden. Der Verweis muss also nicht auf eine Datei innerhalb des gleichen Dateisystems erfolgen, es kann auch eine Datei per HTTP oder FTP nachgeladen werden. Ein Zugriff auf entfernte Dateien kann nur erfolgen, wenn in der PHP-Konfiguration die Option allow_url_fopen() auf 1 steht (Standardwert). Es ist allerdings nicht
76
3.5 Include-Dateien
möglich, diese Zugriffsart generell für include und require gezielt abzuschalten. Wird diese Option deaktiviert, kann sie auch bei anderen Dateifunktionen und bei den Grafikfunktionen imagecreatefromXXX() nicht mehr angewendet werden. Was kann solch ein »eingeschleuster« Code bewirken? Dies kommt ganz auf die Konfiguration des Servers an; das Löschen von Dateien innerhalb der Webanwendung ist wahrscheinlich möglich – doch das dürfte selten das Ziel sein. Interessanter ist es, Zugangsdaten zu Datenbanken im ersten Schritt zu erhalten und im weiteren Verlauf sensitive Daten – etwa Adressen, E-Mail-Adressen oder gar Kreditkartennummern – aus diesen Datenbanken zu beziehen. Um an diese Informationen zu gelangen, ist es keinesfalls erforderlich, irgendwelche Kenntnisse der Anwendung oder des Programmierstils zu haben. Der Angreifer muss beispielsweise nicht wissen, wie die Variablen heißen, in denen die Zugangsdaten gespeichert werden. Zur Verdeutlichung ein Beispiel:
Listing 3.3:
main.php
Listing 3.4:
db.php
Im Skript main.php wird die db.php inkludiert, die automatisch eine Verbindung zur MySQL-Datenbank aufbaut. Dabei werden die Verbindungsparameter in Vari-
77
Kapitel 3 PHP und Dateien: Die häufigsten Fehler
ablen ausgelagert, damit sie leichter verändert werden können. Ist danach ein Verbindungsaufbau außerhalb der db.php notwendig, so können diese Variablen verwendet werden. Die main.php inkludiert allerdings auch eine weitere Datei, die aus dem Übergabeparameter page stammt. Wie kann hier nun böswilliger Code eingeschleust werden? Im ersten Schritt wird ein Angreifer – wenn er eine Datenbank vermutet – herausfinden wollen, ob es die Verbindungsdaten in Variablen gibt. In page wird er also die URL einer Datei übermitteln, die folgenden Code enthält:
Durch die Benennung der Variablen in db.php ist es nicht weiter schwer, die Variablen zu erkennen, die darüber Auskunft geben, wie die Zugangsdaten zur Datenbank lauten. Doch selbst wenn die Variablen anders benannt werden, so dass ein direkter Zusammenhang zur Datenbank nicht hergestellt werden kann (was dann wiederum für die eigene Programmierung weniger geeignet ist), muss man bedenken, dass ein Angreifer alle Zeit der Welt hat, um die Liste der Variablen entsprechend zu analysieren. Und da er jede externe PHP-Datei ausführen kann, solange die Lücke existiert, kann er sich an sein Ziel »heranpirschen«; im Beispiel wird er also als Nächstes entweder direkt auf die Datenbank mit einem entsprechenden Tool zugreifen, oder, falls dies bedingt durch Datenbankkonfiguration nicht möglich ist (Verbindungen etwa nur vom lokalen Server aus zulässig), mit einem PHP-Skript die vorhandenen Tabellen auslesen und danach wahrscheinlich Daten beziehen. Diese Lücke kann sich ein Angreifer übrigens nicht nur aus der Ferne zu Nutze machen: Wenn eine Datei hochgeladen und inkludiert wird, kann auch hier schadhafter Code eingeschleust werden – hinzu kommt, dass auch eine Filterung der Dateien nicht direkt wirkt. Ist es in einem CMS etwa erlaubt, Bilddateien heraufzuladen und wird eine solche hochgeladene Datei nachher auf einer anderen PHPSeite mittels include() inkludiert (so kann die Datei bzw. dessen Inhalt an den Client ausgeliefert werden, ohne dass dieser beispielsweise die Quelle im System des Webservers kennen muss), wird unter Umständen auch der eingeschleuste Schadcode ausgeführt.
Wichtig Das hier geschilderte Problem besteht nicht nur bei Bilddateien, sondern bei jeder Datei, die mittels include() »nur« an den Client ausgeliefert werden soll.
78
3.5 Include-Dateien
Um dies genauer zu verstehen, sollte man sich vor Augen führen, wie der PHPInterpreter arbeitet: Er sucht in einem Stream nach den Tokens bzw. , sofern diese Variante in der Konfiguration aktiviert wurde, und wertet alles, was sich zwischen diesen beiden Identifikatoren befindet, als PHP-Code aus und ersetzt den Codeblock durch die Ausgabe, die vom Quelltext erzeugt wurde. PHP ist es dabei egal, um was für eine Art Stream es sich handelt: Dies kann sowohl reiner Text (eine Textdatei, die lediglich PHP-Code oder eine Mischung aus anderem Text, z.B. HTML, und PHP-Source) oder eine binäre Datei sein, die eben als reinen Text den Code mit den entsprechenden Start- und End-Tokens enthält. Allerdings wird ein Webserver im Allgemeinen nur Dateien an den PHP-Interpreter übermitteln, wenn das Dateisuffix mit den freigeschalteten Dateiendungen übereinstimmt. Wird jedoch eine Datei via include() durch ein gültiges PHP-Skript geladen, spielt es keine Rolle, welches Suffix die zu inkludierende Datei trägt. Beim include() (und auch beim require()) wird davon ausgegangen, dass nur Dateien aus sicherer Quelle aufgerufen werden. Dies ist jedoch beim include() von Dateien aus Parametern oder bei hochgeladenen Dateien nicht der Fall. Dieser Code wird leider sehr oft verwendet, wenn es darum geht, Daten an einen Client zu übermitteln – etwa ein Bild oder eine Textdatei:
Hier spielt es keine Rolle, ob der GET-Parameter page vom Client oder von einem anderen Skript, das auf dem gleichen Server ausgeführt wurde, generiert wurde und ob der Wert eine lokale oder entfernte Datei darstellt. Dabei wird dieser Code oft verwendet, wenn der anfordernde Client den Dateinamen und dessen Pfad innerhalb des Webservers nicht kennen soll, weil sich dieser etwa vermutlich ändert, nicht direkt heruntergeladen werden soll oder die Daten aus einer Datenbank stammen und es somit keine physikalische Adresse gibt. Durch das include() – ein require() hätte den gleichen Effekt – wird die als Parameter spezifizierte Datei als PHP-Code zu interpretiert. Werden in dieser Datei die Tokens gefunden, kann so schadhafter Code eingeschleust werden. Der Angreifer hat beispielsweise folgende GIF-Datei: GIF89a ÷
€ € €€ €€ € €€€€€ÀÀÀÿ ÿ ÿÿ ÿÿ ÿ ÿÿÿÿÿ ÿ ;
3 f ™ Ì ÿ 3 33 3f 3™ 3Ì 3ÿ f f3 ff f™ fÌ fÿ ™ ™3 ™f ™™ ™Ì ™ÿ Ì Ì3 Ìf Ì™ ÌÌ Ìÿ ÿ ÿ3 ÿf ÿ™ ÿÌ ÿÿ3 3 33 f3 ™3 Ì3 ÿ33 33333f33™33Ì33ÿ3f 3f33ff3f™3fÌ3fÿ3™ 3™33™f3™™3™Ì3™ÿ3Ì 3Ì33Ìf3Ì™3ÌÌ3Ìÿ3ÿ 3ÿ33ÿf3ÿ™3ÿÌ3ÿÿf f
79
Kapitel 3 PHP und Dateien: Die häufigsten Fehler
3f ff ™f Ìf ÿf3 f33f3ff3™f3Ìf3ÿff ff3fffff™ffÌffÿf™ f™3f™ff™™f™Ìf™ÿfÌ fÌ3fÌffÌ™fÌÌfÌÿfÿ fÿ3fÿffÿ™fÿÌfÿÿ™ ™ 3™ f™ ™™ Ì™ ÿ™3 ™33™3f™3™™3Ì™3ÿ™f ™f3™ff™f™™fÌ™fÿ™™ ™™3™™f™™™™™Ì™™ÿ™Ì ™Ì3™Ìf™Ì™™ÌÌ™Ìÿ™ÿ ™ÿ3™ÿf™ÿ™™ÿÌ™ÿÿÌ Ì 3Ì fÌ ™Ì ÌÌ ÿÌ3 Ì33Ì3fÌ3™Ì3ÌÌ3ÿÌf Ìf3ÌffÌf™ÌfÌÌfÿÌ™ Ì™3Ì™fÌ™™Ì™ÌÌ™ÿÌÌ ÌÌ3ÌÌfÌÌ™ÌÌÌÌÌÿÌÿ Ìÿ3ÌÿfÌÿ™ÌÿÌÌÿÿÿ ÿ 3ÿ fÿ ™ÿ Ìÿ ÿÿ3 ÿ33ÿ3fÿ3™ÿ3Ìÿ3ÿÿf ÿf3ÿffÿf™ÿfÌÿfÿÿ™ ÿ™3ÿ™fÿ™™ÿ™Ìÿ™ÿÿÌ ÿÌ3ÿÌfÿÌ™ÿÌÌÿÌÿÿÿ ÿÿ3ÿÿfÿÿ™ÿÿÌÿÿÿ!ù ,
Es handelt sich um ein gültiges Bild mit einer Höhe und Breite von jeweils einem Pixel. Ein Angreifer kann nun diesen Bytestrom etwas erweitern, ohne dass das Bild ungültig wird: GIF89a ÷ € € €€ €€ € €€€€€ÀÀÀÿ ÿ ÿÿ ÿÿ ÿ ÿÿÿÿÿ 3 f ™ Ì ÿ 3 33 3f 3™ 3Ì 3ÿ f f3 ff f™ fÌ fÿ ™ ™3 ™f ™™ ™Ì ™ÿ Ì Ì3 Ìf Ì™ ÌÌ Ìÿ ÿ ÿ3 ÿf ÿ™ ÿÌ ÿÿ3 3 33 f3 ™3 Ì3 ÿ33 33333f33™33Ì33ÿ3f 3f33ff3f™3fÌ3fÿ3™ 3™33™f3™™3™Ì3™ÿ3Ì 3Ì33Ìf3Ì™3ÌÌ3Ìÿ3ÿ 3ÿ33ÿf3ÿ™3ÿÌ3ÿÿf f 3f ff ™f Ìf ÿf3 f33f3ff3™f3Ìf3ÿff ff3fffff™ffÌffÿf™ f™3f™ff™™f™Ìf™ÿfÌ fÌ3fÌffÌ™fÌÌfÌÿfÿ fÿ3fÿffÿ™fÿÌfÿÿ™ ™ 3™ f™ ™™ Ì™ ÿ™3 ™33™3f™3™™3Ì™3ÿ™f ™f3™ff™f™™fÌ™fÿ™™ ™™3™™f™™™™™Ì™™ÿ™Ì ™Ì3™Ìf™Ì™™ÌÌ™Ìÿ™ÿ ™ÿ3™ÿf™ÿ™™ÿÌ™ÿÿÌ Ì 3Ì fÌ ™Ì ÌÌ ÿÌ3 Ì33Ì3fÌ3™Ì3ÌÌ3ÿÌf Ìf3ÌffÌf™ÌfÌÌfÿÌ™ Ì™3Ì™fÌ™™Ì™ÌÌ™ÿÌÌ ÌÌ3ÌÌfÌÌ™ÌÌÌÌÌÿÌÿ Ìÿ3ÌÿfÌÿ™ÌÿÌÌÿÿÿ ÿ 3ÿ fÿ ™ÿ Ìÿ ÿÿ3 ÿ33ÿ3fÿ3™ÿ3Ìÿ3ÿÿf ÿf3ÿffÿf™ÿfÌÿfÿÿ™ ÿ™3ÿ™fÿ™™ÿ™Ìÿ™ÿÿÌ ÿÌ3ÿÌfÿÌ™ÿÌÌÿÌÿÿÿ ÿÿ3ÿÿfÿÿ™ÿÿÌÿÿÿ!ù , ÿ ;
Eine so modifizierte GIF-Datei würde eine Prüfung auf Gültigkeit, die bevorzugt mit getimagesize() durchgeführt wird, überstehen. getimagesize() würde bei dieser manipulierten Datei folgendes Array ergeben: Array ( [0] => 1 [1] => 1 [2] => 1 [3] => width="1" height="1" [bits] => 8 [channels] => 3 [mime] => image/gif )
Es ist also in keiner Weise zu erkennen, dass das »Bild« manipuliert wurde; wäre es nicht als korrektes Bild erkannt worden, so hätte getimagesize() Null zurückgegeben. Wird diese Datei nun mit include() durch ein anderes Skript inkludiert,
80
3.5 Include-Dateien
wird in den Bytestrom der GIF-Daten eine Liste der Dateien innerhalb des Verzeichnisses des aufrufenden Skriptes integriert. Der Angreifer kann diese wieder genauso einfach aus dem vom Server gelieferten »Bild« entnehmen. Noch einmal: Es spielt keine Rolle, ob dabei eine Datei inkludiert wird, deren Pfad oder URL aus einem externen Parameter übernommen wird oder ob diese Datei aus einer lokalen Datenquelle stammt: Es gibt immer einen Weg, ein solches include() zu missbrauchen. Diese Beispiele sollten aufzeigen, wie gefährlich das Inkludieren »irgendwelcher« Dateien sein kann. Doch wie macht man es richtig? Wie verhindert man, dass Schadcode über Dateien ausgeführt wird, die eigentlich nur an den Client ausgeliefert werden sollen? Ganz einfach: Man benutzt eine Aufrufkombination aus header() und dem Auslesen und Ausgeben einer solchen Datei. Mittels header() sollte dabei stets der Content-Type festgelegt werden, damit der Client stets weiß, wie er mir der erhaltenen Datei umgehen soll (so weiß ein Browser etwa bei einem Bild, dass er es anzeigen muss, auch wenn es nicht über einen -Tag in einer HTML-Datei referenziert wurde).
Tipp Ist der Mime-Type der auszuliefernden Datei nicht bekannt, sollte application/ octet-stream als Content-Type verwendet werden.
In diesem kleinen Skript wird das gesamte Problem umgangen: Die Datei wird ausgelesen und der Bytestream direkt an den Client ausgeliefert; dabei wird die Datei nicht vom PHP-Interpreter geparst, eventuell enthaltener PHP-Schadcode kommt somit nicht zur Ausführung.
81
Kapitel 4
Sensitive Daten richtig behandeln Heute bestehen Webanwendungen – im Gegensatz zu reinen Webseiten aus den ersten Tagen des Internets – aus Interaktion und Personalisierung. Dafür müssen persönliche Daten gespeichert und verwaltet werden; das gilt nicht nur für Applikationen, bei denen das offensichtlich ist, wie etwa Webshops. In Zeiten von Identitätsmissbrauch, Spam und Adresshandel sind solche Daten begehrter denn je, darum gilt es sie zu schützen.
4.1
Grundsatzprobleme
In diesem Abschnitt sollen absichtlich nur die Probleme, die beim Umgang mit Daten durch PHP und clientseitige Skripte entstehen können, beschrieben werden. Lösungen dazu finden Sie unter Abschnitt 4.2 Lösungsansätze auf Seite 97 – einen solchen Ansatz können Sie meist für mehrere »Aufgabenstellungen« anwenden, weshalb eine Auflistung im Sinne von »Problem = Lösung« dieses Kapitel nur unnötig aufblähen würde.
4.1.1
Falsche Request-Methode
Die Übertragung zwischen einem Client und einem Webserver wird über das HTTP-Protokoll abgewickelt. Dabei ist es egal, ob nun die heute als veraltet geltende Version 1.0 oder die aktuelle Version 1.1 verwendet wird. Grundsätzlich treffen folgende Eigenschaften auf dieses Protokoll immer noch zu: 쐽
verbindungslos
쐽
textbasiert
쐽
unverschlüsselt
Diese drei Merkmale stammen aus einer Zeit, in der Übertragungskapazität kaum vorhanden und die Übermittlung großer Datenmengen nicht notwendig war. Teilweise gilt das auch heute noch: Vor allem in ländlichen Gebieten und im asiatischen Raum erreicht man allzu oft keinen DSL-Standard. Diese drei Eigenschaften haben natürlich auch den Vorteil, dass sie so einfach gehalten sind, dass ein Client keine aufwändige Transportebene aufbauen muss.
Kapitel 4 Sensitive Daten richtig behandeln
Man kann sich sogar mit einem simplen telnet zu einem Webserver (meist TCPPort 80) verbinden und so den HTML-Quelltext der angeforderten Seite erhalten. Dynamik entsteht allerdings nicht dadurch, dass lediglich eine Seite angefordert und vom Webserver zurückgeliefert wird. Es ist notwendig, Parameter und individuelle Daten zu übertragen – ohne diese Fähigkeit wäre selbst ein umfangreiches Navigationsmenü nur umständlich zu realisieren. HTTP bietet dabei zwei Möglichkeiten, wie diese Daten übermittelt werden können. Jede dieser Methoden hat ihre Einschränkungen, ihre Berechtigungen und ihre Nachteile. Methoden GET Diese Methode ist nach der Protokollspezifikation für Anfragen gedacht, bei denen lediglich etwas erhalten (»get«) werden soll. Die übermittelten Daten stellen dabei etwa Such- oder Auswahlkriterien (Suchtext, Sprache) dar.
Die Parameter, die mit einer solchen Anfrage übertragen werden sollen, werden dabei innerhalb der URL notiert. Die Aufschlüsselung erfolgt stets als Schlüsselund Wertepaar, getrennt durch ein Gleichheitszeichen. Das erste Paar wird dabei direkt nach der angefragten Seite durch ein Fragezeichen, alle folgenden durch ein kaufmännisches Und-Zeichen eingeleitet. Eine URL mit einer GET-Anfrage sieht also etwa so aus: http://testserver/page.php?Parameter1=1234&Parameter2=abcdefg
Diese URLs sind sehr benutzerfreundlich, denn eine solche URL kann komplett übermittelt werden und führt immer zum gleichen Ergebnis. Allerdings hat diese Methode auch ihre Nachteile. Die Länge der URL ist begrenzt, dabei gibt es allerdings kein eindeutiges Limit: Der Apache-Webserver hat in einem Test von Phil Dawes (http://www.phildawes.net/blog/2006/05/09/server-length-limitations-on-http-get-urls/) mit einem eigenen Client GET-Anfragen mit einer 8000 Zeichen langen URL verarbeitet, die bekannte Grenze bei den meisten Browsern liegt hingegen bei 2048 Zeichen. Man sollte hier beachten, dass die Länge der URL nicht einfach aus der Summe der Zeichen besteht, denn Sonderzeichen müssen kodiert werden (eine Kodierungstabelle finden Sie unter http://www.bolege.de/url-encode/). GETAnfragen sind auch stärker für Manipulationen anfällig, da jeder Benutzer Parameter und Werte an die URL anhängen kann. Deswegen gilt für diese Methode mehr als sonst: Parameterwerte sollten vor der Verarbeitung auf ihren Typ und Plausibilität geprüft werden, besonders wenn sie dazu dienen, Daten in irgendeiner Weise zu selektieren. URLs werden von den meisten Webservern inklusive aller GET-Parameter protokolliert. Wird zusätzlich eine entsprechende Analysesoftware eingesetzt, sind diese
84
4.1 Grundsatzprobleme
Informationen möglicherweise für Dritte einsetzbar. Aufgrund dieser Gefahr sollten GET-Anfragen nicht für die Übermittlung sensitiver Daten, wie etwa Adressinformationen oder Passwörter, verwendet werden. Die »Angriffsmöglichkeiten« beschränken sich nicht nur auf Protokolldateien: Auch mit einer Man-in-the-Middle-Attacke ist es möglich, die GET-Parameter über einen Netzwerk-Sniffer auszuspionieren. Allerdings gehen auch von einer anderen Klientel durchaus Gefahren aus: Suchmaschinen indizieren ebenfalls vollständige GET-URLs. Dies kann dazu führen, dass Links in Suchmaschinen geführt werden, die von Anwendern gar nicht mehr aufgerufen werden sollen. Sessions sind in diesem Zusammenhang keinesfalls zu unterschätzen. Ist session.use_trans_sid aktiviert, wird die Session-ID stets als GET-Parameter mit der URL mitgeliefert. Bei der sehr laxen Prüfung der Session-Gültigkeit von PHP selbst könnte also ein Bekannter, dem man einen Link zu einem Produkt in einem Onlineshop gesendet hat, Zugriff auf den eigenen Warenkorb erlangen, da er sich in der gleichen Session befindet. Ebenfalls stellen Suchmaschinen hier ein Problem dar: Werden dort die Session-Informationen mit in den Link aufgenommen, ist es möglich, das zwei Benutzer der Suchmaschine gleichzeitig innerhalb der gleichen Session arbeiten und so eventuell die Daten des anderen einsehen können. POST Vollkommen anders als GET verhält sich die POST-Methode; hierbei werden die Parameter und ihre Werte nicht innerhalb der URL, sondern als Header-Information zwischen Client und Server übermittelt.
Ein Nachteil ist allerdings die fehlende Flexibilität. Die Zusammenstellung einer GET-URL ist an jeder Stelle im HTML-Quelltext möglich, die notwendigen Parameter werden einfach an die URL angehängt. POST-Anfragen sind über HTML allerdings nur mit einem Formular möglich. Es existiert keine clientseitige Limitierung der Datenmenge, die mit POST übertragen werden kann – das tatsächlich akzeptierte Übertragungsvolumen lässt sich jedoch serverseitig, etwa mit PHP durch die Konfigurationsdirektive post_max_ size, einschränken. Da Suchmaschinen lediglich auf Verlinkung basieren, und hier keine zusätzlichen Informationen als Header zwischengespeichert und bei der Suche übermittelt werden können, wird auch jeweils dann nur die Ziel-URL der POST-Anfrage ohne Parameter-Daten indiziert. Dies hat den Vorteil, das veraltete Links, die über die gleiche »Gateway«-Seite geleitet werden wie aktuelle Links, leicht ausgefiltert und Benutzer mit einem entsprechenden Hinweis »konfrontiert« werden können. Das Session-Problem stellt sich mit POST gar nicht erst: session.use_trans_sid wird die Session-Daten jeweils nur als GET-Parameter an die URL anhängen, sofern
85
Kapitel 4 Sensitive Daten richtig behandeln
dies notwendig ist. Ist die Verwendung von Cookies für die Session-Verwaltung nicht möglich und die ID soll aufgrund der Sicherheitsprobleme nicht genutzt werden, so kann die Session-ID mittels POST als verstecktes Formularfeld benutzt übertragen werden: -Deklaration. Durch das XML-Format lassen sich WDDXDaten natürlich um einiges leichter auslesen als die PHP-Serialisierungsdaten.
Hinweis Bei der XML-Verarbeitung kann es allerdings zu Performanceverlusten kommen: Die XML-Dateien müssen über einen Parser in das DOM (Document Object Model) eingelesen werden, damit man auf die darin enthaltenen Daten mit PHP zugreifen kann – dieses Parsen kostet natürlich Zeit. Ebenso nimmt auch die Datenmenge im Vergleich zum anderen Format erheblich zu. Ein solches WDDX-Paket, das also die gesamten serialisierten Daten repräsentiert, sieht in etwa so aus: <wddxPacket version=’1.0’><struct><string>66.249.72.23111764073531176407353<string>2Fwbboard%2Fmembers.php%3Fmode%3Dprofile%26userid%3D7% 26boardid%3D28%26sid%3D6d1rrgs2rm9mqm8l1taqf9rai1<string> members.php%3Fmode%3Dprofile%26userid%3D7%26boardid%3D28%26sid%3D6d1rrgs2rm 9mqm8l1taqf9rai1
179
Kapitel 5 Sessions
Diese Daten sind zwar länger, lassen sich jedoch relativ gut komprimieren. Allerdings ist eine Komprimierung von Sitzungsdaten auf einem System, das mit vielen Sessions »konfrontiert« wird, nicht empfehlenswert: Die Komprimierung und dadurch auch wieder notwendige Dekomprimierung kann sich zu einem Performanceproblem entwickeln. Die Aktivierung des WDDX-Formates ist relativ einfach: session.serialize_ handler muss dafür auf wddx gesetzt werden. Bedingung für die Funktion von WDDX unter PHP ist allerdings, das PHP mit WDDX-Unterstützung kompiliert wurde. Ob dies der Fall ist, lässt sich anhand der Ausgabe von phpinfo() erkennen: wddx sollte im Wert von Registered serializer handlers unterhalb von session enthalten sein. Übersetzen Sie PHP aus den Quellen selbst, ist es notwendig, configure den parameter --enable-wddx zu übergeben. Damit die Kompilierung funktioniert, ist es zudem erforderlich, dass der XML-Parser expat installiert ist (die Quellen dafür erhalten Sie unter http://sourceforge.net/projects/expat/).
Hinweis In den Binaries ist WDDX oftmals bereits aktiviert, die Windows-Binaries werden stets mit diesem Feature ausgeliefert. Allerdings sollte man auch Risiken bei der Verwendung dieses Formats bedenken. Grundsätzlich ist es egal, welches Format verwendet wurde, denn für die Speicherung spielt es keine Rolle. Entschließen Sie sich aber, über ihre eigenen read()und write()-Funktionen Session-Daten in Dateien abzulegen, kann dies problematisch sein: Vielleicht entschließen Sie sich, zum besseren Schutz diese Dateien zu verschlüsseln. Wird dabei ein symmetrisches Verfahren (für Ver- und Entschlüsselung wird das gleiche Passwort verwendet) wie etwa DES oder Blowfish verwendet, so kann sich ein Angreifer möglicherweise aus der Häufung der Tags var, string, number und name Rückschlüsse auf den Inhalt ziehen. Ist ihm das WDDXFormat zudem bekannt, kann er womöglich den Rest der Daten ohne Probleme entschlüsseln. WDDX bietet im Gegensatz zum PHP-Format auch einen Vorteil, der wiederum für das Session-Management genutzt werden kann. Wurde innerhalb der read()Funktion der WDDX-String einer Session ausgelesen, so kann mit wddx_serialize() geprüft werden, ob diese Zeichenkette ein gültiges WDDX-Paket darstellt. Bei dem PHP-eigenen Serialisierungsformat ist diese Überprüfung eher schwer, da unserialize() so seine Tücken hat (beispielsweise lässt sich der Rückgabewert FALSE für den Fehlerfall nicht vom Rückgabewert FALSE als Wertigkeit des deserialisierten Wertes unterscheiden, dies kann jedoch bei Sessions vernachlässigt werden, da hier auf jeden Fall ein Array deserialisiert werden muss). Auf kei-
180
5.6 Sessions und Frames
nen Fall sollte man jedoch read() und write() an einer Serialisierungsmethode »festmachen«, denn der Serialisierungs-Handler kann in der php.ini geändert werden, eine Prüfung, die von einem festen Wert ausgeht würde dann also scheitern. Möchte man also die Konformität der gelesenen oder erhaltenen Daten prüfen, sollte unbedingt der aktuelle Wert der Option session.serialize_handler berücksichtigt werden.
5.6
Sessions und Frames
Werden Frames verwendet, so kann es notwendig sein, dass mehrere Frames – oder vielmehr die PHP-Skripte, die in den jeweiligen Frames geladen werden – auf die aktuelle Session zugreifen. Im Allgemeinen funktioniert dies auch ohne Probleme, jedoch gibt es eine wesentliche Einschränkung. Wird das Standardspeichermodell benutzt, also Session-Daten werden durch PHP in Dateien abgelegt, so ist die jeweilige Datei nach einem session_start() so lange gesperrt, bis das jeweilige Skript beendet wird oder aber session_write_close() aufgerufen und die Session somit geschlossen wurde. Verwenden also Skripte zweier Frames die gleiche Session, so wird das zweite Skript erst dann effektiv ausgeführt, wenn die Session durch das andere Skript freigegeben wurde; dies trifft natürlich nicht ganz zu: Das Skript wird am session_start()-Aufruf blockiert, der Code vor dieser Funktion wird ausgeführt, da jedoch session_start() vor jeder Ausgabe erfolgen muss, steht diese Funktion meist am Anfang. Bei aktiviertem session.auto_start wird das zweite Skript natürlich vor Aufruf der ersten Codezeile unterbrochen. Dieses Vorgehen funktioniert allerdings nur dann korrekt, wenn das unterliegende Dateisystem Dateisperren voll und ganz unterstützt. Ist dies nicht der Fall, etwa beim alten FAT-16 und anderen sehr speziellen Dateisystemen, so kann es zu einem Parallelzugriff kommen. Dabei kann es durchaus passieren, dass beide Skripte zwar parallel auf die Session zugreifen können, jedoch eines der beiden Skripte keine Daten – also keinerlei Variablen innerhalb des $_SESSION-Arrays – erhält. Dies beeinflusst zum Einen natürlich das Verhalten dieses Skriptes, bedeutet jedoch auch, dass dieses Skript diese nicht existenten Daten speichert und somit alle in der Session zwischengespeicherten Werte verlorengehen. Dieses Problem lässt sich verhindern, indem entweder auf Frames verzichtet wird (wobei nicht nur Frames betroffen sind: Es könnten auch zwei per AJAX aufgerufene Skripte einen solchen Parallelzugriff verursachen). Aber auch parallele Zugriffe auf die gleiche Session aufgrund verschiedener Anfragen verursacht auf einem solchen Dateisystem dieses Problem, etwa wenn der Benutzer zwei verschiedene Seiten unterhalb der gleichen Session in zwei Browserfenstern lädt. Die Verwendung von mm als Speichermethode (siehe Abschnitt 5.5 Speicherung auf Seite 165) scheint eine Alternative zu sein, ist jedoch durchaus auch ein Risiko. mm
181
Kapitel 5 Sessions
garantiert definitiv nicht, dass ein paralleler Zugriff verhindert wird. Dabei werden zwar keine Daten zerstört, es kann jedoch zu Dateninkonsistenzen führen, wenn das erste Frame-Skript Änderung an der Session vorgenommen hat und speichert. Wurde währenddessen die Session jedoch durch ein anderes Skript geladen, bleiben die durchgeführten Änderungen dafür unbeachtet. Vielmehr gehen diese Änderungen wieder verloren, sobald das zweite Skript beendet und die Session mit dem alten Datenstand gespeichert wird. Falls dieses Problem auftritt, sollte überlegt werden, das Dateisystem für das Verzeichnis, in dem die Sitzungsdateien gespeichert werden, zu wechseln, eine Alternative ist lediglich noch ein eigener Save-Handler, bei dem etwa eine Sperre garantiert werden kann (etwa ein LOCK TABLES innerhalb einer Datenbankumgebung). Mehr zu einem benutzerdefinierten Speichern der Sessions erfahren Sie im Abschnitt 5.5 Speicherung auf Seite 165.
182
Kapitel 6
Upload und Download Immer mehr im Mittelpunkt von Webanwendungen – allen voran Portalen – steht der Austausch von Dateien. Sowohl Uploads als auch Downloads sind mit PHP relativ einfach möglich, werden jedoch Leichtsinnsfehler begangen, können auch hier ernsthafte Sicherheitslücken entstehen, die im schlimmsten Fall zum Ausfall des Webservers oder zum Diebstahl von sensitiven Daten führen können. Dieses Kapitel soll diese Problemquellen beleuchten und Lösungswege aufzeigen.
6.1
Upload und PHP
Ein HTML-Upload-Formular hat folgende Struktur:
Eines vorweg: Das Attribut maxlength des File-Feldes war im HTML-3.2-Standard aufgeführt, die Version 4.0 verliert jedoch darüber kein Wort mehr, es kann also auch nicht sicher davon ausgegangen werden, dass dieses Attribut in HTML-4.0bzw. XHTML-Dokumenten von allen Browsern berücksichtigt wird und somit der Browser nur Dateien bis zu dieser Größe zum Upload zulässt. Eine Validierung durch PHP ist also zwingend erforderlich. Doch auch hier entsteht schon ein Problem, denn diese Validierung kann erst nachgeordnet in einem Skript erfolgen, also nachdem die Datei bereits auf dem Server gespeichert ist. Angenommen, ein Angreifer möchte den Webserver mit einer Auslastung der Festplatte erreichen, so wird er beispielsweise eine Datei von 25 MB Dateigröße hochladen. In Ihrem Skript prüfen Sie jedoch die Dateigröße und begrenzen zulässige Dateien auf 10 MB. Allerdings wird PHP erst den Upload-Vorgang abschließen und anschließend Ihr Skript starten (anders wäre ein Zugriff auf die Datei ja auch gar nicht möglich). Auf der Server-Festplatte werden also erst einmal 25 MB belegt, die Prüfung verhindert die Weiterverarbeitung und die Datei wird wieder gelöscht. Dieser einzelne Vorgang stellt kein Problem dar; stehen dem Angreifer jedoch ein Netzwerk oder infizierte Rechner zur Verfügung, kann er einen verteilten Angriff starten: Wenn nun gleichfalls 100 Dateien mit der Größe von 25 MB hochgeladen
Kapitel 6 Upload und Download
werden, wird zuerst einmal der Webserver relativ stark ausgelastet, da nun einmal 100 Netzwerkverbindungen für die Dauer der Uploads belegt sind. In dieser Zeit wird der Server eventuell – je nach Hardware – andere, legitime Verbindungen nur zeitverzögert oder gar nicht annehmen. Auf der anderen Seite werden dann insgesamt 25 x 100 MB = 2500 MB Speicher auf der Festplatte belegt, bevor überhaupt ein Skript die Größe der Datei prüft. Wären nun auf dem Webserver lediglich 2000 MB freier Speicherplatz vorhanden, würde dieser voll belegt (nachfolgend würden alle bisher noch nicht vollständig erfolgten Uploads natürlich abgebrochen). Allerdings steht nun für den Server kein Speicherplatz mehr zur Verfügung: Es kann weder in eine Protokolldatei, noch in eine Datenbankdatei gespeichert werden. Nun gibt es zwei mögliche Konstellationen: 1. Vorübergehende Operationsproblematik: Ist genug Hauptspeicher vorhanden und es ist nicht erforderlich, dass das System zum Laden und Ausführen des Validierungsskripts anderen Arbeitsspeicher auf die Festplatte auslagert, kann das Skript nun die Dateigrößen prüfen und die weitere Verarbeitung verhindern. PHP wird nach Beendigung dieses Skripts jeweils die verbundene hochgeladene Datei löschen und der Speicher wird nach und nach wieder freigegeben. Aber: Während die Platte vollkommen ausgelastet ist, steht kein weiterer Platz zur Verfügung. Dies kann durchaus kritisch sein, wenn ein Datenbanksystem betrieben wird, das in diesem Moment einen Puffer auf die Platte schreiben möchte. Im Allgemeinen wird ein Datenbanksystem den Schreibvorgang auf später verschieben – in einer ungünstigen Konstellation sind jedoch die Daten verloren und die Datenbank danach in einem nicht konsistenten oder gar unbrauchbaren Zustand. Das betrifft natürlich nicht nur Datenbankprozesse, sondern auch alle anderen Programme, die auf die Festplatte schreiben möchten, während diese voll ist. 2. Permanente Operationsproblematik: Richtig problematisch wird es, wenn das Serversystem generell mit einer starken Arbeitsspeicherauslastung betrieben wird. Ist es zum Ausführen des Upload-Skripts und zum Laden des PHP-Interpreters – der nachher die temporär abgespeicherten Dateien löschen würde – notwendig, andere Dateien aus dem Arbeitsspeicher auszulagern, so kann dies aufgrund der vollen Festplatte nicht geschehen. Folglich werden die Dateien auch nicht gelöscht und die Festplatte bleibt voll. Dies führt natürlich bei allen anderen Prozessen des Servers zu schwerwiegenden Problemen, ein Speichern von Daten ist nicht mehr möglich. Der Webserver nimmt eventuell auch keine neuen Verbindungen mehr an (dies trifft dann zu, wenn es für einen neuen Webserver-Kind-Prozess notwendig wäre, andere Daten auf die Festplatte auszulagern). Im schlimmsten Fall führt dies zum Beispiel zu zerstörten Datenbanken und anderem Datenverlust.
184
6.1 Upload und PHP
Diese kritische Situation lässt sich nicht mit einer einzelnen Maßnahme beseitigen, viel mehr müssen verschiedene Methoden zusammen eingesetzt werden, um dieser Gefahr zu begegnen.
6.1.1
Uploads beschränken
Zuerst einmal sollte die maximale Dateigröße, die PHP für einen Upload akzeptiert, in der php.ini beschränkt werden. Hierfür kann die Direktive upload_max_filesize verwendet werden. Dateien werden von PHP lediglich bis zu dieser angegebenen Größe (Bytes) akzeptiert und auf dem Server gespeichert. Jedes Byte, das über diese Grenze hinaus geht, wird nicht mehr gespeichert, die entsprechende Netzwerkverbindung wird von PHP unterbrochen. Ist diese Grenze auf 1.000.000 Bytes gesetzt und eine Datei von 1.500.000 Bytes wird vom Client hochgeladen, so ist die Datei auf der Serverfestplatte nachher nur 1.000.000 Bytes groß und der Client hat zudem eine entsprechende Fehlermeldung erhalten.
Hinweis Beachten Sie unbedingt auch die Option post_max_size in der php.ini. Dieser Wert sollte stets etwas größer sein als upload_max_filesize. Für diesen Wert werden neben der Größe der hochgeladenen Datei auch die Daten der anderen per POST übermittelten Felder einbezogen, so dass die Menge an POST-Daten stets größer ist als die reine Dateigröße. Um die Upload-Funktionalität weiter einzuschränken und möglichen Schaden zu begrenzen, können noch folgende Einstellungen genutzt werden: 쐽 memory_limit:
Damit kann der maximale Speicherverbrauch eines Skriptes begrenzt werden, dies hat dann auch Einfluss auf Skripts, die Datei-Uploads verarbeiten, sofern dort die Dateien geöffnet und in den Speicher geladen (also ausgelesen) werden. Wird eine hochgeladene Datei lediglich verschoben, dann hat das Speicherlimit natürlich keinen Einfluss.
쐽 max_execution_time:
Die maximale Laufzeit eines Skriptes kann hier beschränkt werden. Die Zeit, in der die hochzuladende Datei vom Client zum Server übermittelt wird, zählt bereits zur Ausführungszeit des verarbeitenden Skriptes (das nachher die Datei in irgendeiner Weise verarbeitet). Eine restriktive Belegung dieser Option kann allerdings auch für legitime Clients mit einer langsamen Netzwerkanbindung Folgen haben: Diese benötigen generell länger, um eine Datei hochzuladen. Ist max_execution_time zu niedrig angesetzt, so kann es ein solcher Client unter Umständen nicht schaffen, eine gewünschte Datei hochzuladen.
All dies kann lediglich einen einzelnen Upload beschränken, doch wird hierdurch keinesfalls die Häufigkeit der Uploads begrenzt. Es nützt nur wenig, wenn die
185
Kapitel 6 Upload und Download
Uploads auf 1 MB pro Datei beschränkt werden, durch einen verteilten Angriff jedoch dennoch die Festplatte des Webservers gefüllt wird. Doch hier stößt man auf ein wesentliches Problem von PHP: Grundsätzlich weiß eine PHP-Session oder vielmehr ein Interpreter-Prozess nichts vom anderen; man kann also nicht innerhalb des Codes erkennen, wie oft das aktuelle Skript (hier das Upload-Skript) bereits aktiv »läuft«, um bei einer bestimmten Anzahl Clients die Notbremse zu ziehen. In diesem Skript lässt sich also nicht erkennen, wie viele Uploads momentan aktiv sind oder gerade verarbeitet werden. Es gibt allerdings einige Lösungsmöglichkeiten, die alle im Folgenden einmal genauer betrachtet werden sollen. Maximale Anzahl der Clients
Am einfachsten wäre es natürlich, wenn die Anzahl der Clients, die einen Upload durchführen können, beschränkt würden. PHP unterstützt dabei allerdings keine Möglichkeit, die maximale Anzahl gleichzeitiger Ausführungen eines Skriptes anzugeben, denn das würde wieder voraussetzen, dass ein PHP-Prozess vom anderen Kenntnis hat. Die meisten Webserver bieten genau diese Möglichkeit: Die maximale Anzahl gleichzeitig verbundener Clients kann beschränkt werden, zudem kann bei einer persistenten Verbindung sogar angegeben werden, wie viele Anforderungen pro Verbindung zulässig sind.
Hinweis Zum Verständnis für die Begrenzung der Anforderungen pro Verbindung: Normalerweise wird eine HTTP-Verbindung nach einer Übermittlung getrennt. Der Client baut beispielsweise eine Verbindung zum Webserver auf und fordert die Datei index.php an. Nachdem der Inhalt dieser Datei übermittelt wurde, trennt der Webserver die Verbindung. Ist es nun für den Client notwendig, eventuell in die Seite eingebettete Bilder nachzuladen, so muss er für jedes Bild wieder eine neue Verbindung öffnen und dieses anfordern. Genau so verhält es sich mit dem Transfer von Formulardaten in die andere Richtung: Sind die Daten eines Formulars an den Server versendet, so wird die Verbindung geschlossen. Da dieses Vorgehen auf Dauer wenig effizient ist, gibt es so genannte Keep-AliveVerbindungen, die auch nach einem Transfer geöffnet bleiben, und innerhalb einer bestimmten Zeitspanne kann eine weitere Anforderung darüber bedient werden. Dies verringert deutlich die Anzahl der notwendigen Netzwerkverbindungen. Die Begrenzung auf die maximal mögliche Anzahl der Client-Verbindungen – was unter einigen Serversystemen gleichbedeutend mit der maximalen Anzahl gleich-
186
6.1 Upload und PHP
zeitig ausführbarer Kindprozesse ist – ist eine so schwerwiegende Einstellung, dass sie nur global vorgenommen werden kann. Eine gezielte Begrenzung für ein Skript, ein Verzeichnis oder einen Sub-Host ist nicht möglich, da diese Direktive tiefgreifenden Einfluss auf den Betrieb des gesamten Webservers hat. Dies bedeutet allerdings auch, dass sich eine Limitierung, die ein »Flooding« von Uploads verhindern soll, auf die Erreichbarkeit der Webseiten durch legitime Benutzer auswirken kann. Wird unter dem Gesichtspunkt der Upload-Absicherung die entsprechende Option beispielsweise auf 50 Clients festgelegt, so bedeutet dies: Wenn ein Hacker mit 40 Uploads aktiv ist, können nur noch 10 »normale« Surfer eine andere Webseite vom Webserver anfordern. Nun kann man natürlich argumentieren, dass in Zeiten von DSL jede Übertragung sowieso relativ schnell abgeschlossen ist und somit auch ein Band von lediglich zehn Verbindungen für legitime Benutzer ausreichen sollte. Doch dies ist nicht so einfach: Die Verbindung ist solange offen, wie eine Übertragung dauert; besonders mit gleichzeitig aktivierter Keep-Alive-Funktion kann so eine Verbindung auch bei einem Client mit relativ hoher Leitungskapazität einige Zeit offen gehalten werden. Eine Verbindung für einen anderen Benutzer steht also nicht zur Verfügung. Verbindungen, die hingegen für Uploads genutzt werden, sind noch problematischer: ADSL ist viel verbreiteter als das für diese Aufgaben besser geeignete SDSL. Bei ADSL ist die Geschwindigkeit, die für Uploads zur Verfügung steht, deutlich geringer als die Download-Geschwindigkeit; im schlechtesten Fall steht als Upload lediglich eine Übertragungsrate auf ISDN-Niveau zur Verfügung. Ein einfaches Rechenbeispiel: 쐽
Übertragungsrate Upload: 128 kbit/s (entspricht 16 KB/s)
쐽
Größe der zu übertragenden Datei: 1 MB (1024 KB)
쐽
Dauer der Übertragung: 64 Sekunden
Diese Berechnung trifft auch nur dann zu, wenn die volle Übertragungsrate zur Verfügung steht. Dies ist selten der Fall, es sei denn der Benutzer steht direkt in der Vermittlungsstelle des Providers. Und auch andere Aktionen, die auf dem jeweiligen Client parallel ausgeführt werden – etwa das Abfragen der E-Mails – schmälern zumindest vorübergehend die Upload-Geschwindigkeit. Diese Verbindung wird also mindestens für über eine Minute blockiert, wahrscheinlich sind sogar gute anderthalb Minuten. Kommen währenddessen Verbindungen von anderen Benutzern hinzu, ist ein Limit von 50 Verbindungen schnell überschritten. Doch wie kann man dann gezielt Massen-Uploads einschränken, ohne andere Seiten zu behindern? Die offensichtlichste Möglichkeit – einen eigenen Server vorausgesetzt – ist auch relativ einfach zu verwirklichen: Die Uploads müssen über einen eigenen Webserver-Prozess erfolgen, der entsprechenden Limitierungen unterliegt. Dieser Prozess wird dann auf einem anderen Netzwerkport, da der Standardport (80 oder 443, je
187
Kapitel 6 Upload und Download
nachdem, ob HTTP oder HTTPS eingesetzt wird), bereits durch den »eigentlichen« Webserver belegt wird.
Hinweis Bei dieser Technik gibt es auch keinerlei Probleme mit Sessions. Ist es notwendig zu verifizieren, dass ein Benutzer authentifiziert ist, wenn er einen Upload durchführt, kann natürlich die bestehende Session genutzt werden, die vom »normalen« Webserver-Service angelegt wurde. Dafür muss allerdings die PHPSESSID im Formular übermittelt und im Upload-Skript ein session_start() aufgerufen werden. Danach stehen alle Session-Daten zur Verfügung. Allerdings kann dieses Vorgehen zum Trick 17 mit Selbstüberlistung und eingebauter Falltür mutieren, wenn man die Konfiguration des alternativen Webserverprozesses zu leichtfertig abhandelt. Wichtig ist, daran zu denken, dass mit der Direktive MaxClients (Apache) oder der entsprechenden Einstellung unterhalb eines anderen Webserversystems lediglich die Anzahl der gleichzeitigen Verbindungen und somit allenfalls die CPU- und Netzwerklast begrenzt werden kann, ohne dass der Webservice anderweitig beschränkt wird. Dies bedeutet aber auch, dass die Festplatte weiterhin mit unsinnigen Uploads »zugemüllt« werden kann. Theoretisch kann man dies verhindern, indem man beispielsweise lediglich 50 Client-Verbindungen auf diesem Server zulässt und PHP dabei eine maximale Dateigröße von 20 MB akzeptiert. Dabei wird dann im Fall X – wenn tatsächlich gleichzeitig 50 Clients versuchen, mindestens 20 MB große Dateien auf den Server zu laden – eine Plattenkapazität von 1000 MB gebunden; mit diesem Wissen im Hinterkopf kann man dafür sorgen, dass immer diese 1000 MB zuzüglich einer Reserve (für Daten, die beispielsweise von der Datenbank gespeichert werden) auf der Platte frei sind.
Hinweis Diese Speicherproblematik tritt seltener auf dedizierten Webservern auf, da dort im Allgemeinen ausreichend Speicherplatz zur Verfügung steht. Hat man jedoch nur Webspace angemietet und muss sich den Speicherplatz also mit anderen Benutzern teilen, kann es eher zu einer Auslastung kommen – in einer solchen Umgebung lässt sich der Webserver allerdings nicht durch die Benutzer konfigurieren! Im Normalfall wird eine hochgeladene Datei nach der Verarbeitung von PHP wieder aus dem temporären Verzeichnis gelöscht. Es kann jedoch vorkommen, dass dies nicht oder nicht unverzüglich der Fall ist (etwa bei Problemen mit der Skriptausführung, zu hoher Auslastung etc.), und dann kann sich eine Datenmenge größer als die berechneten 1000 MB ansammeln.
188
6.1 Upload und PHP
Auf der anderen Seite sind zu rigide Beschränkungen sehr kontraproduktiv: Wenn Sie eine Webseite betreiben, bei der Kunden etwa teilweise relativ große Dateien einliefern, können 50 Verbindungen schnell belegt sein. Kommen auf der anderen Seite noch Clients hinzu, die nur mittelgroße Dateien (z. B. 5 MB) hochladen, dafür aber eine langsame Anbindung haben, stößt man schnell an die Grenzen. Erhöht man die zulässige Clientanzahl hingegen, wird auch der theoretisch gebrauchte Plattenplatz steigen. Man kann natürlich nicht unbegrenzt viel Speicherplatz für die temporäre Speicherung von Upload-Dateien vorsehen. Ganz ist diese vertrackte Situation nicht zu vermeiden, jedoch kann man den verbrauchten Plattenplatz einschränken, indem der eigens für den Upload-Service eingerichtete Webserverprozess nicht unter dem gleichen Benutzer betrieben wird wie der originäre Webdienst. Denn in dieser Konstellation ist es möglich, dem »alternativen« Webserver eine Quota auf Betriebssystemebene zu definieren, also den maximalen Speicherbedarf festzulegen, der auf der Festplatte in Anspruch genommen werden darf. Dabei sollte die Quota auch nicht zu knapp bemessen werden, denn wird die Grenze erreicht, so kann keine Datei mehr hochgeladen werden, bis eine andere gelöscht wurde. Doch in diesem Fall lässt sich anhand der Upload-Fehler eine entsprechende Meldung via PHP ausgeben, die den Benutzer beispielsweise darüber informiert, dass der Upload nicht erfolgreich war und er es später erneut versuchen soll. Eine Fehlermeldung an den Endbenutzer und ein weiterhin funktionierender und zeitnah reagierender Webserver sind allemal besser als ein Service, bei dem ein Upload ohne Rücksicht auf Verluste möglich ist, bei dem nachher keinerlei Webseiten und andere Dienste mehr erreichbar sind.
Wichtig Wird der alternative Webserver unter einem anderen Benutzer betrieben und ist später ein Zugriff durch den originären Webserver auf die Uploads notwendig, so sollten die Rechte der hochgeladenen Dateien unter Linux, Unix und Mac OS X mit den PHP-Funktionen chmod(), chown() und chgrp() entsprechend verändert werden. Der Betrieb unter einem eigenen Benutzer hat allerdings auch einen weiteren Nachteil: Sollen Sessions zwischen den »normalen« und den »alternativen« ServerProzessen geteilt werden, stellt sich das Problem der Berechtigung. PHP wird beim Anlegen der Session-Dateien stets nur dem eigenen Benutzer das Lese- und Schreibrecht einräumen, ein Zugriff durch einen anderen Benutzer ist also nicht ohne Weiteres möglich. Für diesen Fall sollte auf ein anderes Speichermodell ausgewichen werden, mehr Informationen dazu erhalten Sie in Kapitel 5.
189
Kapitel 6 Upload und Download
Dedizierte Upload-Server
Wichtig Dieser Abschnitt soll lediglich die Probleme verdeutlichen, die bei einer Verteilung von Servern entstehen können. Die folgenden Seiten ersetzen keineswegs professionelles Consulting zu Themen wie Load Balancing1 (Lastverteilung) und Server Clustering2. Sofern eine solche Serverstruktur notwendig erscheint, sollten Sie unbedingt in die dafür notwendige Beratung und Hardware investieren – wenn Sie sich auf der sicheren Seite befinden möchten, werden Sie um ein Clustering nicht herumkommen. Doch was tun, wenn der Upload-Service stark frequentiert sein wird? Nun in diesem Fall wird man kaum ohne eine Serverfarm auskommen; um hier effizient zu arbeiten werden mindestens drei Server benötigt, mehr Rechner sind natürlich umso besser. In diesem Konzept liegt die Webseite beispielsweise auf der Subdomain www (z.B. www.abc.de) während die Upload-Server über die Subdomain upload (upload.abc.de) erreichbar sind. Damit man sich hier nicht mit der Verteilung der Anfragen auf verschiedene Server beschäftigen muss, kann man die Möglichkeiten des DNS-Server nutzen. Eine Bind-DNS-Konfiguration könnte beispielsweise so aussehen (Ausschnitt aus einer Zonendatei): www upload
A
100.100.100.1
A
100.100.100.10
A
100.100.100.11
Das Verfahren, das hier zum Einsatz kommt, trägt den Namen Round-Robin; dabei wird jeweils stets ein anderer A-Record für den upload-Eintrag zurückgegeben. Zwei nacheinander anfragende Clients werden also jeweils einen anderen Server verwenden. Das Ganze muss natürlich auch passend in die eigene Webanwendung integriert werden; es nützt recht wenig, die Uploads auf mehrere Server zu verteilen und somit die Last pro Server zu reduzieren, wenn danach ein Dateichaos entsteht, weil
1
2
190
Load Balancing ist eine Technik zur Lastverteilung. Dabei sollte nach Möglichkeit spezielle, redundant ausgelegt Hardware eingesetzt werden, die die eingehenden Anfragen dann auf mehrere dahinterliegende Server verteilt. Dabei werden auch ausgefallene Systeme erkannt und nicht mehr angesprochen. Ein erfolgreiches Load Balancing setzt allerdings voraus, dass sämtliche Daten allen Servern zur Verfügung stehen, denn es ist keinesfalls garantiert, dass ein Client bei zwei aufeinanderfolgenden Anfragen vom gleichen Server »bedient« wird. Gemeint sind hier HA-Cluster, also High-Availability-Cluster. Dabei werden mehrere Server zusammengeschaltet, die sich ständig im Hintergrund synchronisieren. Fällt einer der Server aus, kann – die technische Möglichkeit einer solchen Übernahme vorausgesetzt – ein anderer Server dessen Aufgaben ausfallfrei und ohne Datenverlust übernehmen.
6.1 Upload und PHP
nicht alle hochgeladenen Daten von jedem Rechner erreichbar sind. Im Großen und Ganzen sollte die Vorgehensweise sein: 1. Die Anwender nutzen die Funktionen der Webanwendung auf dem Server, der über die Subdomain www erreichbar ist. 2. Alle Uploads werden nicht an www, sondern an den Host upload gesendet (WebFormulare müssen entsprechend programmiert werden). 3. Durch das Round Robin verfahren werden beide im DNS eingetragene uploadServer relativ gleichmäßig verwendet. Auf diese Server wird jeweils die UploadDatei übertragen. 4. Nach einem Upload wird die Datei auf dem jeweiligen upload-Server verarbeitet und eventuell geprüft (z. B. Dateitypprüfung). 5. Danach wird die Datei per FTP oder über einen anderen Dienst auf den www-Server kopiert, damit diese der zentralen Webanwendung zur Verfügung steht. Alternativ könnte man natürlich auch einen weiteren unabhängigen File-Server betreiben, der nur die gesammelten Dateien bereitstellt – wodurch der eigentliche Webserver nachhaltig entlastet wird. 6. Die temporär angelegte Datei auf dem upload-Server wird von PHP nach Abschluss der Operationen automatisch gelöscht. Mit dieser Konstruktion schlägt man natürlich gleich mehrere Fliegen mit einer Klappe: 쐽
Die Anzahl der maximal zulässigen Client-Verbindungen lässt sich für die eigentliche Webanwendung unabhängig von Überlegungen zur UploadBeschränkung realisieren
쐽
Die Webanwendung funktioniert auch dann, wenn die Upload-Server »geflooded« werden und somit ihre Netzwerkanbindung stark ausgelastet ist
쐽
Selbst wenn ein Upload-Server aufgrund von Überlastung ausfällt, beeinträchtigt das nicht die Webseite als solche – und somit auch nicht die Nutzdaten und Funktionalität
쐽
Eine rechenzeit- und speicherintensive Verarbeitung (etwa der Import von hochgeladenen Dateien in eine Datenbank, wobei die Dateien jeweils komplett in den Speicher geladen werden müssen) wird dezentral organisiert.
So gut dieses System nun auch erscheinen mag, so gibt es dennoch ein paar offene Punkte, über die vor einer Realisierung zumindest einmal nachgedacht werden sollte. Das Round-Robin-Verfahren ist kein Verfügbarkeitsgarant: Jeder Client bekommt beim Abfragen der IP für den Upload-Host alle dazu vorliegenden Einträge übermittelt. Dabei rotiert der DNS-Server allerdings die Liste, so dass sich der erste Ein-
191
Kapitel 6 Upload und Download
trag dieser Liste für jede Anfrage ändert, die meisten Clients verwenden jedoch ausschließlich den ersten Eintrag und wechseln nicht zu einer anderen IP, falls keine Verbindung mit dem »primären« Host möglich ist. Dies bedeutet, dass bei einem Einsatz von zwei Upload-Servern nach einem Ausfall fast genau 50 % der Upload-Versuche fehlschlagen sein werden. Auf der anderen Seite ist einer der Vorteile verloren, wenn sich Upload- und Webserver nicht innerhalb des gleichen Netzwerks befinden. Da beide Upload-Server im Nachhinein die hochgeladenen Dateien wiederum auf den Webserver transferieren, wird in diesem Schritt jeweils Netzwerkkonnektivität und auch Rechenzeit gebunden; solange dieser Transfer – der vom PHP-Skript aus eingeleitet werden muss – aktiv ist, wird ebenfalls ein PHP-Prozess ausgeführt. Findet die Übertragung im selben Hochgeschwindigkeitsnetzwerk (100 oder 1000 MBit/s) statt, so sind auch große Dateien relativ schnell übertragen. Bei einer Übertragung zwischen verschiedenen Rechenzentren steht eventuell nicht die gleiche hohe Transferrate zur Verfügung, die Übermittlung dauert länger und in dieser Zeit werden sowohl auf dem Upload- als auch auf dem Anwendungsserver Netzwerksockets gebunden und unter besonders schlechten Umständen wird eventuell die Laufzeit des PHP-Skripts auf dem Upload-Server überschritten und der Transfer zum Anwendungsserver wird abgebrochen. Für Uploads wird es meist notwendig sein, dass der Benutzer in irgendeiner Form vorher authentifiziert wurde oder bestimmte Daten innerhalb der Session vorliegen und vom Upload-Skript geprüft werden. Ein Beispiel sind hier die CAPTCHAs: Der Benutzer bekommt im Upload-Formular eine Bilddatei angezeigt, in der sich »versteckt« Buchstaben und Zahlen finden, die er für einen erfolgreichen Upload eingeben muss. Das Upload-Skript muss den eingegebenen Wert dann mit der vorher festgelegten und innerhalb der Session3 gespeicherten Kombination vergleichen. Ist dieses Paar nicht identisch, so wird der Upload nicht weiter verarbeitet. Ist eine Session – solange sie existiert – von jedem PHP-Prozess auf dem gleichen System verwendbar, so bietet PHP keinen Mechanismus, um serverübergreifend auf Sitzungsdaten zugreifen zu können. Um dennoch zu gewährleisten, dass nicht jeder einen Upload auf das System starten kann, gibt es für serverübergreifende Sessions drei Systeme:
192
쐽
Mohawks msession/mcache: Hierbei wird der mcache-Daemon auf einem Server betrieben. Dabei wird eine Session innerhalb des mcache registriert, nun kann jeder zugelassene Server mit der entsprechenden ID Daten innerhalb dieser Sitzung speichern und auslesen. Die einzige Schwierigkeit besteht also lediglich darin, dem Upload-Skript auf dem Upload-Server die entspre-
3
Diese Daten im Formular zu übermitteln, wäre wenig zielführend: Der Angreifer könnte diese Daten aus dem HTML-Code auslesen und wüsste somit immer den richtigen Code. So würde ein Upload-Flooding wieder ermöglicht.
6.1 Upload und PHP
chende mcache-Session-ID mitzuteilen, das kann jedoch im Upload-Formular übermittelt werden. mcache kann dabei auf zwei verschiedene Arten eingesetzt werden:
1. Als PHP-Session-Handler: Innerhalb der php.ini wird mit der Eintragung session.save_handler = mcache der Session-Handler auf mcache gesetzt, über die Option session.save_path wird der Host definiert, auf dem der mcache-Daemon betrieben wird. Nun werden alle Sessions – auch die, die lediglich auf einem einzelnen Server verwendet werden – gespeichert. Dabei können Sessions wie gewohnt weiterverwendet werden, eine Code-Anpassung an mcache ist nicht notwendig. Soll die Session auf einem anderen Server genutzt werden, muss diesem lediglich die Session-ID (deren Name über die Option session.name definiert wird) mitgeteilt werden (etwa im Upload-Formular als verstecktes Feld), damit diese bei einem session_start verwendet wird. Dort muss lediglich der gleiche mcache-Server als Session-Handler konfiguriert sein. 2. Als zusätzliche Funktionalität: Sollen nicht alle Sessiondaten auf mehreren Servern verwendet werden oder es wird bereits ein anderer Session-Handler als mcache eingesetzt, so kann mcache auch lediglich bestimmte Daten speichern. Dabei müssen die betroffenen Daten allerdings über eine eigene API an den mcache-Daemon übermittelt werden. Die Funktionen finden sich in der PHP-Dokumentation im XCI. Kapitel. Diese haben als Präfix alle msession, was historisch bedingt ist: Im Jahr 2006 wurde mcache in msession umbe-
nannt. Dabei muss nach einem Verbindungsaufbau (msession_connect()) auf dem »auslösenden« Server eine eigene msession-Session erzeugt werden (msession_create()): Als Session-ID empfiehlt sich, entweder die ID der PHP-eigenen Session oder den Wert, den die Funktion msession_uniq() liefert, zu verwenden. Der Server, der lediglich Daten auslesen soll, muss nach einem msession_connect() nur einen Variablenwert mittels msession_get() auslesen. Auch hier gilt natürlich: Die Session-ID muss dem Ziel-Server übermittelt werden. 쐽
PostgreSQL Session Handler: Dieses Modul verhält sich wie mcache: Es handelt sich um einen alternativen Session-Handler (im Gegensatz zu mcache gibt es hier keinen ausgebauten »standalone«-Modus). In der php.ini wird session.save_handler der Wert session_pgsql zugewiesen. Davor muss allerdings das PostgreSQL-Session-Modul in PHP einkompiliert werden, mehr Hinweise dazu finden sich in der PHP-Dokumentation im CXLII. Kapitel und in der ReadMe-Datei des Moduls.
193
Kapitel 6 Upload und Download
Zusätzlich muss noch über die Direktive session_pgsql.db ein gültiger PostgreSQL-Connection-String angegeben werden, hinter dem die zu verwendende Datenbank steht. Bei diesem Modul wird weiterhin eine Session wie gewohnt genutzt, es ist also keine Code-Umstellung innerhalb der PHP-Skripte notwendig. Hier muss dem Zielserver auch die Session-ID mitgeteilt werden, damit ein session_start() auch auf die bereits bestehenden Daten zugreifen kann. Einen Nachteil bietet der PostgreSQL Session-Save-Handler allerdings: Er steht unter Windows nicht zur Verfügung. 쐽
Eine eigene Lösung: Stehen die oben genannten Lösungen etwa aus technischen Gründen nicht zur Verfügung, sie wirken zu »oversized« oder es gibt noch weitere Anforderungen, die durch diese Methoden nicht abgedeckt werden, kann es sinnvoll sein, eine eigene Funktionalität zu entwickeln, mit der es möglich ist, sitzungsspezifische Daten zwischen verschiedenen Servern auszutauschen. Hier ist der eigenen Erfindungsgabe natürlich keine Grenze gesetzt. Mehr Informationen zur Entwicklung eines eigenen Session-Save-Handlers erhalten Sie im Kapitel 5.
Beispiel einer Upload-Konfiguration
Im Folgenden noch ein Beispiel einer Upload-Konfiguration mit folgenden Elementen: 쐽
Es gibt drei Server: einen Webserver (www.testdomain.net) und zwei UploadServer (upload.testdomain.net)
쐽
Sessions werden auf dem Webserver mittels mcache verwaltet
쐽
Die Upload-Server sind innerhalb des DNS-Servers als Round-Robin-Einträge definiert
쐽
Die Anzahl der Clients sowie die Größe der Dateien werden auf den UploadServern begrenzt
쐽
Nach erfolgreichem Upload und Validierung des Benutzers werden die Dateien auf den Webserver per FTP transferiert
DNS-Server: Zonen-Datei testdomain.net … www
A
100.100.100.1
upload
A
100.100.100.2
upload
A
100.100.100.3
…
194
6.1 Upload und PHP
Anwendungsserver: php.ini … [Session] session.save_handler = mcache session.save_path = www.testdomain.net session.name = PHPSESSID session.auto_start = 1 [PHP] file_uploads = off …
Die Upload-Server werden mittels Round-Robin-Verfahren in die DNS-Zone aufgenommen. Upload-Server: php.ini … [Session] session.save_handler = mcache session.save_path = www.testdomain.net session.name = PHPSESSID session.auto_start = 1 [PHP] file_uploads = on upload_tmp_dir = /tmp upload_max_file_size = 20M post_max_size = 22M max_execution_time = 120 max_input_time = 120 memory_limit = 32M …
Der Webserver selbst akzeptiert keine Uploads mehr und die Sessions werden zentral per mcache verwaltet. Upload-Server: Webserverkonfiguration In der httpd.conf eines eingesetzten Apache-Servers: MaxClients 50
195
Kapitel 6 Upload und Download
Unter dem Roxen-Webserver kann die maximal zulässige Anzahl der Clients nicht direkt begrenzt werden. In den Globals kann unter Settings lediglich die Thread-Anzahl begrenzt werden, jedoch kann ein Thread auch mehrere Verbindungen bedienen. Microsofts IIS bietet hingegen keine Möglichkeit in dieser Richtung. Passend dazu kann für den Benutzer, unter dem der Webserverdienst auf den Upload-Servern betrieben wird, eine Quota festgelegt werden. Da PHP temporäre Upload-Dateien unter Umständen nicht sofort löscht, sollte dieser Wert mindestens 125% des erwarteten maximalen Platzverbrauchs (upload_max_file_size aus der php.ini multipliziert mit dem Wert von MaxClients aus der Webserverkonfiguration) sein. Die Festlegung eines Quotas ist dabei sehr stark vom Betriebssystem abhängig. Unter Windows (ab Windows 20004) wird es über die Eigenschaften des Laufwerks (oder Verzeichnisses) über die Registerkarte KONTINGENT festgelegt. Unter Linux ist es erforderlich, dass die Quota-Unterstützung in den Kernel einkompiliert wurde, eventuell muss noch das quota-Paket, das die Tools zur Verwaltung der Kontingente bereitstellt, installiert werden.
Wichtig Soll Disk-Quota unter Linux eingesetzt werden, empfiehlt sich unbedingt tiefer gehende Lektüre. Allerdings ist dieser Schritt lediglich optional, es reicht aus, wenn garantiert ist, dass genügend Platz vorhanden ist, um die maximal möglichen Uploads temporär speichern zu können. Webserver: Ausgabe eines Upload-Formulars mit CAPTCHA Das folgende Skript gibt ein Upload-Formular aus, dessen Daten an einen Upload-Server übermittelt werden. Dabei wird ein CAPTCHA erzeugt, in einer Grafik angezeigt und der Code in der Session gespeichert.
Tipp Hier wird auf eine Benutzeranmeldung verzichtet, generell ist es allerdings zu empfehlen, Uploads nur von registrierten Benutzern zuzulassen. Upload-Form