Objektorientierte Webentwicklung mit PHP und MySQL von Marc Remolt, Jan Teriete Art.-Nr. 011644335 Version 4.5.0 vom 31.7.2013 Autorisiertes Curriculum für das Webmasters Europe Ausbildungs- und Zertifizierungsprogramm © 2012 by Webmasters Press www.webmasters-press.de Nordostpark 7, 90411 Nürnberg, Germany Das vorliegende Fachbuch ist urheberrechtlich geschützt. Alle Rechte vorbehalten. Die Verwendung der Texte und Abbildungen, auch auszugsweise, ist ohne schriftliche Genehmigung des Verlags urheberrechtswidrig und daher strafbar. Dies gilt insbesondere für die Vervielfältigung, Übersetzung oder Verwendung in elektronischen Systemen sowie für die Verwendung in Schulungsveranstaltungen. Die Informationen in diesem Fachbuch wurden mit größter Sorgfalt erarbeitet. Trotzdem können Fehler nicht vollständig ausgeschlossen werden. Autoren und Herausgeber übernehmen keine juristische Verantwortung oder irgendeine Haftung für eventuell verbliebene fehlerhafte Angaben und deren Folgen. Informationen zu dieser Buchreihe Dieses Buch ist Teil unserer Buchreihe zum Zertifizierungsprogramm des Europäischen Webmasterverbandes, Webmasters Europe e.V. (WE). Das Webmasters Europe Ausbildungs- und Zertifizierungsprogramm ist modular aufgebaut und bietet Abschlüsse und Zertifizierungen auf verschiedenen Ebenen an – von der Experten-Zertifizierung für einzelne Themen bis hin zu Diploma-Abschlüssen für Webdesigner, Web-Entwickler, Webmaster und Online Marketing Manager. Nähere Informationen dazu finden Sie unter http://de.webmasters-europe.org/bildungsprogramm Unsere Buchreihe bietet Ihnen nicht nur eine fundierte und praxisnahe Einführung in das jeweilige Thema, sondern ist von Webmasters Europe e.V. offiziell autorisiert zur Vorbereitung auf die WE-Zertifikatsprüfungen. Damit haben Sie die Garantie, dass alle prüfungsrelevanten Themen behandelt werden. Inhaltsverzeichnis 1 2 3 4 Einführung 15 1.1 1.2 1.3 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.4 1.5 15 16 16 16 17 17 17 17 18 18 Einleitung Vorkenntnisse Aufbau des Lernhefts Einleitung Aufgaben im Fließtext Testen Sie Ihr Wissen Aufgaben zur Selbstkontrolle Optionale Aufgaben Anforderungen an PHP Was sind Entwurfsmuster? Strukturierung von PHP-Webprojekten 19 2.1 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.3 19 20 20 24 25 27 29 Das Problem Strukturierung von PHP-Code Trennung von PHP und HTML Lesbaren Code schreiben Kapselung in Funktionen Funktionen, die atomare Probleme lösen Zusammenfassung Einführung in die objektorientierte Programmierung 30 3.1 3.2 3.3 3.4 3.5 3.5.1 3.5.2 3.5.3 3.6 3.6.1 3.6.2 3.6.3 3.7 3.7.1 3.7.2 3.7.3 3.7.4 30 30 32 33 36 36 37 37 39 39 40 42 44 44 45 45 45 Das Problem Einführung in Objekte in PHP Objekte Klassen Attribute Attribute verändern Attribute auslesen Attribute in Klassen definieren Methoden Grundlagen Vorteil von Methoden Die Variable $this Namenskonventionen Klassen Attribute Methoden Variablen, die Objekte enthalten Getter und Setter-Methoden 48 4.1 4.2 4.3 48 48 49 Das Problem Kapselung Getter-Methoden 4.3.1 4.3.2 4.4 4.4.1 4.4.2 4.5 5 6 7 8 9 Vorteil von Getter-Methoden Ein Attribut »privat« machen Setter-Methoden Nachteile des direkten Änderns eines Attributs Methoden zum Ändern von Attributen Öffentliche und private Methoden 50 51 53 53 54 56 Arbeiten mit Objekten 58 5.1 5.2 5.3 5.3.1 5.3.2 5.3.3 58 58 60 60 63 65 Das Problem Methoden, die andere Methoden aufrufen Methoden in anderen Objekten aufrufen Grundlagen Der instanceof-Operator Type-Hinting Virtuelle Attribute 68 6.1 6.2 6.2.1 6.2.2 68 69 69 70 Das Problem Virtuelle Attribute Konzept Setter für virtuelle Attribute Magische Methoden 74 7.1 7.2 7.2.1 7.2.2 7.3 7.3.1 7.3.2 7.3.3 7.3.4 74 75 75 76 77 77 78 78 80 Das Problem Magische Methoden Konzept Die Methode __toString() Konstruktoren Konzept Die Methode __construct() Parameter an __construct() übergeben Ein assoziatives Array an den Konstruktor übergeben Beziehungen zwischen Objekten 87 8.1 8.2 8.3 87 87 90 Das Problem Objekte in anderen Objekten verstecken Ganze Objekte als Parameter übergeben Das MVC-Entwurfsmuster 9.1 9.2 9.3 9.3.1 9.3.2 9.4 9.5 9.5.1 9.5.2 9.6 Einleitung Model Templates Vollständige Templates Teil-Templates View Controller Controller mit Aktionen Die Standard-Aktion Zusammenfassung 93 93 94 97 97 98 100 101 102 104 106 10 11 12 13 Objekt-relationales Mapping 110 10.1 10.2 10.3 10.3.1 10.3.2 10.3.3 10.4 110 111 111 111 113 114 115 Das Problem Objekt-relationales Mapping Verbreitete ORM-Entwurfsmuster Table-Data-Gateway Active Record Data Mapper Zusammenfassung Composer, Packagist & Co. 116 11.1 11.2 11.2.1 11.2.2 11.2.3 11.3 11.3.1 11.3.2 11.4 116 117 117 118 119 120 120 121 121 Einleitung Composer & Packagist composer.phar composer.json Die eigentliche Installation Wichtige Composer-Dateien composer.lock autoload_namespaces.php Zusammenfassung Doctrine-Entities 123 12.1 12.2 12.2.1 12.2.2 12.3 12.3.1 12.3.2 12.4 12.4.1 12.4.2 12.4.3 12.5 12.6 12.6.1 12.6.2 12.6.3 12.6.4 12.6.5 12.7 12.8 123 123 124 126 128 129 130 131 131 131 131 132 133 133 134 134 134 135 135 136 Einleitung Die Beispiel-Datenbank Die reine Seminar-Datenklasse Namespaces Konfiguration per Annotationen Entity Table Der Primärschlüssel Id GeneratedValue Column Doctrine Datentypen Parameter von Column type length unique nullable precision und scale Konfiguration des Autoloaders Zusammenfassung Die Webmasters Doctrine Extensions 139 13.1 13.2 13.2.1 13.2.2 13.3 13.3.1 13.3.2 139 139 141 142 143 144 145 Einleitung Bootstrap $connectionOptions $applicationOptions EntityManager Chaining Vererbung 13.4 13.5 14 15 16 17 18 ArrayMapper Zusammenfassung 146 147 PHP-Objekte mit Doctrine speichern 149 14.1 14.2 14.3 14.4 14.4.1 14.4.2 14.5 149 149 150 151 152 152 154 Einleitung Das Controller-Skeleton Das SchemaTool Das eigentliche Speichern persist flush oder das Entwurfsmuster Unit of Work Zusammenfassung Datenbankabfragen mit Doctrine 156 15.1 15.2 15.2.1 15.2.2 15.2.3 15.2.4 15.2.5 15.3 Einleitung EntityRepository getRepository findAll find findBy findOneBy Zusammenfassung 156 156 156 157 157 158 158 159 Komplexe Abfragen mit Doctrine 160 16.1 16.2 16.2.1 16.2.2 16.2.3 16.2.4 16.3 16.3.1 16.3.2 16.3.3 16.4 160 160 161 161 162 162 162 163 163 164 165 Einleitung Per DQL createQuery getResult getSingleResult getOneOrNullResult Per QueryBuilder createQueryBuilder Die wichtigsten Methoden Benannte Parameter Zusammenfassung DateTime 166 17.1 17.2 17.2.1 17.2.2 17.2.3 17.3 17.4 17.5 166 166 167 168 169 169 172 173 Einleitung Die DateTime-Klasse format modify diff Timestampable Doctrine und die Nutzung von DateTime-Objekten Zusammenfassung Datenbank-Beziehungen mit Doctrine 175 18.1 18.2 18.3 18.3.1 175 175 176 176 Das Problem Beziehungen per Annotation definieren 1:n-Beziehungen abbilden Klasse Seminar 18.3.2 18.3.3 18.3.4 18.3.5 18.4 18.4.1 18.4.2 18.4.3 18.5 18.6 18.6.1 18.6.2 18.7 19 20 21 Klasse Seminartermin Das Problem Ein Beispiel Die Handhabung von 1:n-Beziehungen n:m-Beziehungen abbilden Klasse Benutzer und Klasse Seminartermin Die Zwischentabelle Ein Beispiel Lazy Loading JOINs Per DQL Per QueryBuilder Zusammenfassung 177 178 180 182 183 184 185 186 189 190 191 192 192 Der Controller im Überblick 194 19.1 19.2 19.3 19.3.1 19.3.2 19.3.3 19.3.4 19.3.5 19.4 19.5 194 194 195 199 200 202 204 206 207 212 Einleitung Vorbereitungen BREAD Browse Read Edit Add Delete Benutzer - Add und Edit Zusammenfassung Fortgeschrittene Techniken 214 20.1 20.1.1 20.1.2 20.2 20.3 214 216 217 217 220 Doctrine um Validierungen erweitern Validierungsbedingungen für Datumswerte Öffentliche Methoden von EntityValidator Datenbankabfragen in Repositories auslagern Zufallsdatensätze Eine Einführung in das Thema Sicherheit 224 21.1 21.2 21.3 21.3.1 21.3.2 21.3.3 21.3.4 21.3.5 21.4 21.4.1 21.4.2 21.4.3 21.5 21.5.1 21.5.2 21.5.3 21.6 224 225 226 227 228 229 229 230 232 234 234 235 237 237 241 247 249 Absolute Sicherheit ist unmöglich Fünf empfehlenswerte Tugenden Security through Obscurity www.webmasters-fernakademie.de blog-das-oertchen.de www.google.de localhost Server-Konfiguration Parametermanipulation Whitelist - Variante 1 Whitelist - Variante 2 Dateiangaben als Parameter Die bekanntesten Angriffsvarianten SQL-Injections Cross-Site-Scripting Weitere Angriffsmöglichkeiten Authentisierungssicherheit 21.6.1 21.6.2 21.6.3 21.6.4 21.7 21.7.1 21.7.2 21.7.3 21.8 22 Passwörter Passworthashing Benutzernamen und Kennungen Browserfeatures Ein paar grundsätzliche Hinweise Datenminimierung Missbrauch versteckter Webinterfaces HTTPS ist kein Allheilmittel Letzte Worte 250 255 262 262 266 266 266 269 270 Anhang: Weiterführende Informationen 272 22.1 22.2 22.2.1 22.2.2 22.2.3 22.3 272 272 272 272 273 273 Einführung Weblinks www.php.net www.phpdeveloper.org devzone.zend.com Buchtipps Lösungen 274 Index 283 160 16 16 Komplexe Abfragen mit Doctrine Komplexe Abfragen mit Doctrine In dieser Lektion lernen Sie: ➤ was DQL ist und wie es sich von SQL unterscheidet. ➤ wie Sie mit DQL Datensätze auslesen. ➤ welche Vorteile der QueryBuilder von Doctrine bietet. 16.1 Einleitung Sehr viele Fälle, in denen Sie Objekte aus der Datenbank holen möchten, können Sie schon mit den bisher genannten Methoden erschlagen. Für den Fall, dass die Abfragen komplexer werden, stellt uns Doctrine gleich zwei mächtige Möglichkeiten zur Verfügung. Welche Sie verwenden, bleibt Ihnen überlassen, ich persönlich bevorzuge den QueryBuilder . Doch wenden wir uns zuerst DQL zu. 16.2 Per DQL Der Begriff DQL (Doctrine Query Language) klingt nicht umsonst fast wie SQL. Es handelt sich im Prinzip um SQL, das von Doctrine um einige Konzepte erweitert wurde. Der Hauptunterschied besteht darin, dass Sie mit DQL nicht Tabellen befragen, sondern die Entities. Wir sagen also nicht mehr SELECT ... FROM seminare , sondern SELECT ... from Seminar , oder genauer SELECT ... from Entities\ Seminar , da wir den Namespace angeben müssen, wenn die Entity-Klasse in einem liegt. Es ist außerdem erforderlich, der Klasse einen kurzen Aliasnamen zu geben, da Sie den Namen sehr oft referenzieren müssen. Ansonsten ist DQL syntaktisch weitgehend identisch zu SQL, wir haben aber den Vorteil, nun direkt Objekte bei unseren Abfragen zu erhalten. Beispiel SELECT s FROM Entities\Seminar s WHERE s.preis = 1500 16.2 Per DQL 161 Der Buchstabe s ist in diesem Beispiel der Aliasname. Die vollständige Dokumentation zu DQL finden Sie unter docs.doctrine-project.org/projects/doctrine-orm/en/ latest/reference/dql-doctrine-query-language.html. 16.2.1 createQuery Ein DQL-Query erzeugen wir mit der Methode EntityManager#createQuery() , der wir das DQL als String übergeben. Beispiel $query = $em->createQuery( "SELECT s FROM Entities\Seminar ); $query = $em->createQuery( "SELECT s FROM Entities\Seminar 2000" ); $query = $em->createQuery( "SELECT s FROM Entities\Seminar s.kategorie = 'Webdesign'" ); $query = $em->createQuery( "SELECT s FROM Entities\Seminar ); s WHERE s.preis > 500" s WHERE s.preis BETWEEN 300 AND s WHERE s.preis = 1500 and s WHERE s.titel LIKE '%Doctrine%'" Wir erhalten in $query dann jeweils ein Objekt der Klasse Doctrine\ORM\Query . 16.2.2 getResult Dieses Query-Objekt repräsentiert nun unser SQL-Statement und erfüllt damit eine ähnliche Aufgabe, wie die Klasse PDOStatement bei PDO. Mit der Methode Query#getResult() holen wir uns das Ergebnis. Beachten Sie, dass das Query auch erst in dem Moment ausgeführt wird, in dem diese Methode aufgerufen wird. Beispiel $query = $em->createQuery( "SELECT s FROM Entities\Seminar s WHERE s.preis > 500" ); $seminare = $query->getResult(); Listing 16.1 Per DQL 162 16 Komplexe Abfragen mit Doctrine Wenn Sie die Meldung Doctrine\ORM\Query\QueryException: [Syntax Error] (...) Expected Literal, got '"' oder Doctrine\ORM\Query\ QueryException: [Syntax Error] (...) Expected Doctrine\ORM\ Query\Lexer::T_STRING, got '"' erhalten, so haben Sie im DQL die Anfüh- rungszeichen falschherum benutzt. 16.2.3 getSingleResult Die Methode Query#getSingleResult() macht prinzipiell das Gleiche wie ihr Kollege, nur liefert sie lediglich einen Ergebnis-Datensatz als Objekt zurück. Diese Methode ist für Fälle gedacht, wo Sie lediglich einen Datensatz erwarten und auch immer erhalten. Liefert das Query keinen oder mehrere Datensätze, so führt dies bei der Ausführung der Methode Query#getSingleResult() zu einer NoResultException oder einer NonUniqueResultException. 16.2.4 getOneOrNullResult Die Methode Query#getOneOrNullResult() unterscheidet sich lediglich in einem Detail von der Methode Query#getSingleResult() . Sie liefert nämlich null zurück, sofern sie keinen Datensatz findet. Liefert das Query mehrere Datensätze, so führt dies bei der Ausführung der Methode Query#getOneOrNullResult() zu einer NonUniqueResultException. 16.3 Per QueryBuilder Der QueryBuilder ist ein Aufsatz auf DQL und bietet ein Methoden-Interface für das Erzeugen von DQL. Anstatt DQL als einen String zu schreiben (und sich dort zu vertippen), rufen wir für jeden Funktionsbereich eine Methode auf, die so heißt, wie das DQL-Gegenstück. Anstatt SELECT s from Models\Seminar s WHERE s.preis = 1200 schreiben wir also select('s')->from('Models\Seminar', 's')->where('s.preis > 1200') . Der Vorteil des QueryBuilder ist, das wir durch die einzelnen Methoden im Fehlerfall eher Feedback erhalten, wo wir uns vertippt haben. Außerdem finde ich persönlich die Syntax mit verketteten Methoden übersichtlicher. Hier gilt aber: YMMV88. 16.3 Per QueryBuilder 163 16.3.1 createQueryBuilder Mit der Methode EntityManager#createQueryBuilder() erzeugen Sie eine neue Instanz der Klasse QueryBuilder . An diese Klasse können Sie dann die eigentlichen Methoden des QuieryBuilders anketten und schließlich mit der Methode EntityManager#getQuery() daraus ein Query-Objekt erzeugen. Beispiel $query = $em ->createQueryBuilder() ->select('s') ->from('Entities\Seminar', 's') ->where('s.preis > 500') ->getQuery() ; $seminare = $query->getResult(); Listing 16.2 Per QueryBuilder 16.3.2 Die wichtigsten Methoden Sehen wir uns nun einen Überblick über die wichtigsten Methoden des QueryBuilder an. ➤ QueryBuilder#select() : Diese Methoden repräsentiert das SELECT -Statement im SQL und wird hier eigentlich nur zum Selektieren des Klassen-Aliases verwendet. ➤ QueryBuilder#from() : Diese Methode steht für das SQL- FROM und benötigt zwei Parameter, den Klassennamen und den Alias, der ebenfalls ein Pflichtfeld darstellt. ➤ QueryBuilder#where() : Die WHERE -Bedingung ➤ QueryBuilder#andWhere() : Eine zweite (dritte ...) WHERE -Bedingung mit AND mit dem Vorgänger verknüpft. ➤ QueryBuilder#orWhere() : Eine zweite (dritte ...) WHERE -Bedingung mit OR mit dem Vorgänger verknüpft. ➤ QueryBuilder#orderBy() : Steht für das ORDER BY im SQL und akzeptiert zwei Parameter, das Attribut, nach dem sortiert wird und die Richtung, also ASC oder DESC . ➤ QueryBuilder#setParameter() : Mit dieser Methode können Sie einem benannten Parameter im DQL einen Wert zuweisen. Dazu gleich mehr! ➤ QueryBuilder#setMaxResults : Entspricht dem LIMIT im SQL ➤ QueryBuilder#setFirstResult : Entspricht dem OFFSET im SQL 88. Your mileage may vary, siehe en.wiktionary.org/wiki/your_mileage_may_vary 164 16 Komplexe Abfragen mit Doctrine ➤ QueryBuilder#getQuery() : Wandelt den QueryBuilder in ein Objekt der Klasse Doctrine\ORM\Query , das Sie dann weiterverarbeiten können. Die vollständige Dokumentation zum QB finden Sie unter docs.doctrine-project.org/ projects/doctrine-orm/en/latest/reference/query-builder.html. 16.3.3 Benannte Parameter Auch bei Doctrine-Queries können Sie selbstverständlich mit Prepared Statements und benannten Parametern arbeiten. Die Syntax entspricht der, die Sie wahrscheinlich schon von PDO kennen. Beispiel SELECT * FROM seminare WHERE preis > :preis Listing 16.3 Syntax - Benannte Platzhalter mit PDO Da der Platzhalter in der WHERE -Bedingung Anwendung finden soll, gehört dieser beispielsweise in QueryBuilder#where() . Bei verknüpften Bedingungen kann er natürlich auch in QueryBuilder#andWhere() oder QueryBuilder#orWhere() benutzt werden. Beispiel $query = $em ->createQueryBuilder() ->select('s') ->from('Entities\Seminar', 's') ->where('s.preis > :preis') ->setParameter('preis', 500) ->getQuery() ; $seminare = $query->getResult(); Listing 16.4 QueryBuilder und benannte Parameter Dem Parameter wird dann über die Methode QueryBuilder#setParameter() ein Wert zugewiesen. Wenn Sie mehrere Parameter verwenden, empfehle ich stattdessen QueryBuilder#setParameters() .89 89. Dieser Methode müsste bei der Verwendung von benannten Platzhaltern ein assoziatives Array übergeben werden. Der Name des Platzhalters ohne Doppelpunkt wäre jeweils der Array-Schlüssel. 16.4 Zusammenfassung 165 16.4 Zusammenfassung Sie haben in dieser Lektion zwei weitere Möglichkeiten für SELECT -Anweisungen kennengelernt. Mit diesen Möglichkeiten sind Sie nun in der Lage auch komplexere Datenbank-Abfragen zu formulieren. Testen Sie Ihr Wissen 1. Welche zwei Möglichkeiten existieren, um komplexere Datenbank-Abfragen mit Doctrine umzusetzen? Aufgaben zur Selbstkontrolle Aufgabe 1: Ersetzen Sie den Inhalt der Aktion default im Controller index.php. Ermitteln Sie nun mittels QueryBuilder (oder DQL) alle Seminare, deren Kategorie den String design enthält (Stichwort LIKE). Notieren Sie hierbei den String direkt in der WHERE-Bedingung. Optionale Aufgaben Aufgabe 2: Ändern Sie den Inhalt der Aktion nun so ab, dass Sie den QueryBuilder mit einem benannten Parameter benutzen. 21.3 Security through Obscurity 231 uns erreichte Unklarheit den Angreifer Zeit kosten. Je länger er sich jedoch mit uns beschäftigt, desto wahrscheinlicher wird seine Entdeckung. Es kann also Sinn machen, einem solchen Angreifer die Informationsgewinnung noch weiter zu erschweren. Hängen Sie beispielsweise einmal den Dateinamen composer.json oder composer.lock an die URL Ihres Projektverzeichnisses an, so werden Sie feststellen, dass diese Dateien problemlos im Browser anzeigbar sind. Dies liegt daran, dass der PHP-Interpreter lediglich Dateien mit bestimmten Endungen159 parst und Dateien mit unbekannter Endung vom Webserver sofort als text/plain ausgeliefert werden. Sofern eine Datei lediglich Funktions- bzw. Datenlieferant für eine andere Datei ist und per require(_once) oder include(_once) in diese eingebunden wird, so sollten sie (sofern möglich) eine gesonderte Dateiendung für die eingebundene Datei verwenden. Empfehlenswert ist hierbei eine doppelte Dateiendung, die eine Unterscheidung zu den normalen php-Dateien ermöglicht. Diese sollte jedoch zwingend auf .php enden. Zwei Beispiele haben Sie mit .inc.php und .tpl.php bereits kennengelernt.160 Die Composer-Dateien haben jedoch festgelegte Namen, weswegen eine Umbenennung nicht in Frage kommt. Wir müssen also einen anderen Ansatz wählen. Beispiel 1 # Browser-Zugriff verbieten 2 <Files composer.*> 3 Order Deny,Allow 4 Deny from all 5 Allow from none 6 </Files> Listing 21.5 .htaccess (Blacklist) Bei festgelegten Namen ist es ratsam, dem Browser einfach den kompletten Zugriff zu verbieten. Sofern Sie einen Apache-Webserver nutzen, ist dies meistens161 über eine sogenannte .htaccess-Datei möglich. Die dargestellte .htaccess-Datei verbietet übrigens den Browser-Zugriff auf alle Dateien mit dem Namen composer und einer beliebigen Dateiendung. Da die verbotenen Dateien festgelegt werden, handelt es sich hierbei um den sogenannten Blacklist-Ansatz (dt. schwarze Liste). Eine solche Blacklist erfordert jedoch eine sehr genaue Kenntnis aller problematischen Dateien, deswegen 159. Standard ist die Endung .php, in der httpd.conf können jedoch weitere Endungen erlaubt werden. 160. Backup-Dateien (z.B. .bak oder .old sollten auf einem Produktivserver niemals zu finden sein, auch nicht mit doppelter Dateiendung. 161. Auf ganz billigem Webspace fehlt diese Möglichkeit leider oftmals. 232 21 Eine Einführung in das Thema Sicherheit ist der entgegengesetzte Whitelist-Ansatz (dt. weisse Liste) in der Regel empfehlenswerter. Beispiel 1 2 3 4 5 6 7 # Browser-Zugriff komplett verbieten Order Allow,Deny # Browser-Zugriff selektiv erlauben <FilesMatch "^(index\.php|setup\.php|.*\.(css|js|gif|jpe?g|png))?$"> Allow from all </FilesMatch> Listing 21.6 .htaccess (Whitelist) Ein solcher Whitelist-Ansatz ist wesentlich restriktiver und somit sicherer. Die dargestellte Whitelist erlaubt beispielsweise lediglich Zugriff auf die index.php, die setup.php und Dateien mit den aufgeführten Dateiendungen (css, js, gif, jpg, jpeg oder png). Oftmals kennt man zwar zu Projektstart nicht alle benötigten Endungen, kann diese aber problemlos nach und nach ergänzen.162 Aufgabe 1: 1. Schützen Sie Composer mittels des obigen Whitelist-Ansatzes gegen einen Browser-Zugriff. 2. Testen Sie, ob Sie noch die Dateien composer.json, composer.lock oder composer.phar im Browser aufrufen können. 3. Löschen Sie die Controller benutzer_test.php und seminar_test.php aus Ihrem Projekt-Verzeichnis seminarverwaltung_d2. Diese benötigen wir nicht mehr. Eine Verschleierung von (veralteten) Versionsangaben entbindet den Betreiber und Entwickler einer Anwendung natürlich trotzdem nicht davon regelmäßige Updates der verwendeten Software-Komponenten vorzunehmen. 21.4 Parametermanipulation Schauen wir uns nun noch einmal das Template unserer Startseite genauer an. 162. Schalten Sie aber bitte nicht kommentarlos jede Dateiendung auf Zuruf frei. 21.4 Parametermanipulation 233 Beispiel 1 <?php foreach ($seminare as $seminar): ?> 2 <p> 3 <?php echo $seminar->getTitel(); ?> 4 <span class="kategorie">(<?php <?php echo $seminar->getKategorie(); ?> ?>)</span> 5 [ <a 6 href="index.php?aktion=add_seminartermin&amp; &amp;seminar_id=<?php <?php echo $seminar->getId(); ?> ?>" 7 >Termin anlegen</a> ] 8 </p> 9 10 <p> 11 <?php echo $seminar->getBeschreibung(); ?> 12 </p> 13 14 <?php if ($seminar->getSeminartermine()): ?> 15 <ul> 16 <?php foreach ($seminar->getSeminartermine() as $seminartermin): ?> 17 <li> 18 <?php echo $seminartermin; ?> ?>, 19 Anmeldungen: 20 <?php echo $seminartermin->getTeilnehmer()->count count(); ?> 21 [ <a 22 href="index.php?aktion=read_seminartermin&amp; &amp;id=<?php <?php echo $seminartermin->getId(); ?> ?>" 23 >Details</a> ] 24 [ <a 25 href="index.php?aktion=edit_seminartermin&amp; &amp;id=<?php <?php echo $seminartermin->getId(); ?> ?>" 26 >Termin editieren</a> ] 27 [ <a 28 href="index.php?aktion=delete_seminartermin&amp; &amp;id=<?php <?php echo $seminartermin->getId(); ?> ?>" 29 >Termin entfernen</a> ] 30 </li> 31 <?php endforeach endforeach; ?> 32 </ul> 33 <?php endif endif; ?> 34 <?php endforeach endforeach; ?> Listing 21.7 views/index.tpl.php Wenn Sie sich die Datei ansehen, so werden Sie feststellen, dass an mehreren Stellen eine ID als Link-Parameter ergänzt wird. Dies ist beispielsweise in Zeile 22 beim DetailsLink der Fall. Doch was ist, wenn ein Besucher diese ID-Angabe manipuliert? Dies muss nicht einmal ein böswilliger Angreifer, sondern kann genauso gut ein neugieriger Besucher sein, der einfach mal die ID auf 99 erhöht. Sofern die ID nicht existiert, erhält er hierdurch die Meldung Fatal error: Call to a member function getSeminar() on a read_seminartermin.tpl.php non-object in (...)\views\ on line 2 . Diese Meldung offenbart gleich drei Details über unsere Anwendung: Wir nutzen objektorientierte Programmierung, unsere Templates liegen im Ordner views und der Name des Templates entspricht der aktuellen Aktion im URL-Parameter. 234 21 Eine Einführung in das Thema Sicherheit 21.4.1 Whitelist - Variante 1 Doch wie lässt sich dieses Problem lösen? Wären die Seminartermine unveränderliche Daten und kämen niemals neue Datensätze hinzu, so könnten wir einfach die erlaubten IDs als Whitelist in unserem Code hinterlegen. Beispiel 1 case 'read_seminartermin': 2 $whitelist = array array(1, 2, 3, 4); 3 if (!in_array in_array($_REQUEST $_REQUEST['id'], $whitelist)) { 4 die die('ID nicht vorhanden!'); 5 } 6 $seminartermin = $em 7 ->getRepository('Entities\Seminartermin') 8 ->find($_REQUEST $_REQUEST['id']) 9 ; 10 break break; Listing 21.8 index.php - ID-Whitelist V1 Unveränderliche datenbankbasierte Daten begegnen uns in einer Web-Applikation jedoch so gut wie nie. Doch selbst wenn dies der Fall wäre, so wäre die Wartbarkeit des Codes relativ schlecht, da man im Falle einer Anpassung163 die Datenbankinhalte und Zeile 2 im Code verändern müsste. 21.4.2 Whitelist - Variante 2 Wir benötigen also eine andere Variante unserer Whitelist, bei der wir die bekannten IDs nicht im Code hinterlegen, sondern auf die vom DBMS gelieferten Daten reagieren. Beispiel 1 2 3 4 5 6 7 8 9 10 11 <?php // gekuerztes Beispiel function error404 error404() { header header('HTTP/1.0 404 Not Found'); die die('Error 404: Die angeforderte Seite wurde nicht gefunden.'); } ?> Listing 21.9 funktionen.inc.php 163. Und die kommt schneller als man hofft. 21.4 Parametermanipulation 235 1 case 'read_seminartermin': 2 $seminartermin = $em 3 ->getRepository('Entities\Seminartermin') 4 ->find($_REQUEST $_REQUEST['id']) 5 ; 6 $seminartermin || error404(); 7 break break; Listing 21.10 index.php - ID-Whitelist V2 Sie werden sich eventuell über die Schreibweise in Zeile 6 wundern. Dies ist eine sehr verkürzte Schreibweise einer if-Abfrage, welche sich die als Lazy Evaluation164 bekannte Auswertungstechnik von PHP zu Nutze macht. Der zweite Teil dieser oderBedingung wird nämlich nur ausgeführt, wenn der Finder anhand der ID keinen Datensatz findet und deswegen null als Rückgabewert zurückliefert. Bedenken Sie bei der Planung und Entwicklung einer Anwendung unbedingt Prüfroutinen für die verwendeten Parameter. 21.4.3 Dateiangaben als Parameter Haben Sie gedacht, dass der ID-Parameter nun sicher gegen jegliche Manipulationsversuche ist? Sie haben sich geirrt, denn wenn man den Parameter komplett entfernt und nur die Aktionsangabe übrig lässt, so erhält man eine Reihe von informativen PHP-Meldungen. Dieses Problem haben wir bereits in Abschnitt 9.5.1 für den AktionsParameter gelöst und hierbei den Trinitäts-Operator verwendet. Ich persönlich bin hierzu ehrlich gesagt zu faul und deaktiviere lediglich die Fehleranzeige. Die weisse Seite ist dann persönliches Pech, derjenige kennt ja den Grund. Denken Sie also unbedingt daran, im produktiven Umfeld den debug_mode in der config.inc.php zu deaktiveren. Trauen Sie niemals den Eingaben und Angaben eines Benutzers, hierzu zählen beispielsweise URL-Parameter und jegliche Formulardaten (auch die von versteckten Feldern und Radio-Buttons). URL-Parameter können problemlos direkt in der URLAngabe manipuliert werden und bei der Manipulation von Formulardaten helfen Browser-Plugins wie beispielsweise Firebug165. Betrachten wir nun noch einmal den grundsätzlichen Ablauf in unserer Anwendung: 1. Ein Benutzer ruft die Anwendung mit oder ohne URL-Parameter auf. 2. Ein vorhandener Aktions-Parameter landet 1:1 in der Variablen $view . 3. Sofern der Aktions-Parameter einem Case entspricht, wird dieser aufgerufen. 164. Siehe: de.wikipedia.org/wiki/Lazy_Evaluation 165. Siehe: https://addons.mozilla.org/de/firefox/addon/firebug/ 236 21 Eine Einführung in das Thema Sicherheit 4. Am Schluss des Controllers wird das Template layout.tpl.php per require_once eingebunden. 5. Das Layout-Template bindet wiederum per require_once den Inhalt der Variable $view ein, ergänzt jedoch vorher die doppelte Dateiendung .tpl.php . Gehen wir für das nachfolgende Beispiel davon aus, dass es keinen default -Case gibt.166 Manipulieren Sie nun den Aktions-Parameter Ihrer Anwendung mit den nachfolgenden Angaben. 1. index.php?aktion=composer.json 2. index.php?aktion=../composer.json 3. index.php?aktion=../composer.json%00 Die ersten beiden Varianten werden mit einem Warning und einem Fatal error quittiert. Bei der dritten Variante sehen Sie entweder den Inhalt der composer.json oder Sie haben mindestens PHP 5.3.4167 installiert und sehen lediglich den Fatal error. Im ersten Fall ist Ihr Code und Ihr PHP-Interpreter anfällig für eine Remote File Inclusion168. Schlimmstenfalls kann hiermit sogar Code von einem fremden Webserver ausgeführt werden (Stichwort allow_fopen_url ). Bedenken Sie bei der Absicherung von Include- und Require-Operationen aber auch, dass böswilliger PHP-Code durch Benutzer-Uploads auf Ihren Webserver gelangen und sich durchaus innerhalb einer Grafik-Datei befinden kann.169 Da man sofern möglich mehrere Verteidigungslinien nutzen sollte, sollten Sie sich nicht nur auf die gefixte PHP-Version verlassen. Nutzen Sie immer einen default Case, der den Inhalt von $view komplett ersetzt. Dies ist Verteidungslinie Nr. 1, bei der die Cases in unserem Switch als Whitelist für erlaubte Werte in $view dienen. 1 $aktion = isset isset($_REQUEST $_REQUEST['aktion']) ? $_REQUEST $_REQUEST['aktion'] : null; 2 $aktion = preg_replace preg_replace('/[^a-z_]/', '', $aktion); 3 $view = $aktion; Listing 21.11 Aktion-Whitelist Als Verteidungslinie Nr. 2 erlauben wir nur noch Kleinbuchstaben und den Unterstrich in der Variablen $aktion . Alle Zeichen, die dieser Whitelist nicht entsprechen, werden einfach gelöscht. So ist auch diese Variable nun sicher und kann problemlos mit echo in einem View ausgegeben werden. 166. Kommentieren Sie ihn gegebenenfalls aus. 167. Fix von Problemen im PHP-Core mit sogenannten null Bytes. Siehe: board.raidrush.ws/showthread.php?t=841424 168. Siehe: de.wikipedia.org/wiki/Remote_File_Inclusion 169. Siehe: php.webtutor.pl/en/2011/05/13/php-code-injection-a-simple-virus-written-in-php-andcarried-in-a-jpeg-image/ 21.5 Die bekanntesten Angriffsvarianten 237 Verwenden Sie in Prüfroutinen von Include- und Require-Operationen immer einen Whitelist-Ansatz, um eine Parametermanipulation zu verhindern. Aufgabe 2: 1. Schützen Sie die Seminarverwaltung mit der vorgestellten Error404-Whitelist gegen eine Manipulationen von ID-Parametern. 2. Integrieren Sie danach auch noch die Zeichen-basierte Whitelist für $aktion. 21.5 Die bekanntesten Angriffsvarianten Sie wissen nun, dass die Parameter Ihrer Anwendung durch Manipulationen angreifbar sind und wie Sie zum Schutz Prüfroutinen verwenden. Dabei können Sie entweder auf einen Blacklist-Ansatz setzen oder auf einen Whitelist-Ansatz was meist sinnvoller ist.170 Kommen wir nun zu den bekanntesten Angriffsvarianten. 21.5.1 SQL-Injections Eine SQL-Injection (dt. SQL-Einschleusung)171 ist, wie der Name schon sagt, eine Angriffsmethode im Zusammenhang mit einem SQL-basierten DBMS. Es werden jedoch keine Sicherheitslücken des DBMS ausgenutzt, sondern eine spezielle Variante der Parametermanipulation verwendet. Ziel ist es, die SQL- oder DQL-Anweisungen in unserem Code mit benutzerdefiniertem Code zu erweitern. Dies kann einem Angreifer jedoch nur gelingen, wenn unsere Anwendung dies zulässt. Angriffsszenarien In meiner Tätigkeit als Tutor begegne ich beispielsweise immer wieder Varianten des nachfolgenden Codes. 170. Siehe: www.phpbuddy.eu/file-inclusion-gefaehrliches-include-require.html 171. Siehe: de.wikipedia.org/wiki/SQL_Injection 238 21 Eine Einführung in das Thema Sicherheit Beispiel 1 <ul id="navi"> 2 <li><a href="index.php">Startseite Startseite</a></li> 3 <li><a href="index.php?aktion=suche_seminar">Seminarsuche Seminarsuche</a></li> 4 <li><a href="index.php?aktion=add_seminar">Seminar Seminar anlegen</a></li> 5 <li><a href="index.php?aktion=add_benutzer">Benutzer Benutzer anlegen</a></li> 6 </ul> Listing 21.12 _navi.tpl.php 1 <form action="index.php?aktion=suche_seminar" method="post"> 2 <label for="suchbegriff">Suchbegriff</label> 3 <input type="text" name="suchbegriff" id="suchbegriff" /><br /> 4 5 <label for="preis">Mindestpreis</label> 6 <input type="text" name="preis" id="preis" value="0" /><br /> 7 8 <button type="submit">Abschicken</button> 9 </form> 10 11 <?php require_once 'index.tpl.php'; ?> Listing 21.13 views/suche_seminar.tpl.php (Version 1) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php // gekuerztes Beispiel case 'suche_seminar': $daten = array_merge array_merge( array array('suchbegriff' => '', 'preis' => 0), $_POST ); $seminare = $em ->getRepository('Entities\Seminar') ->suche($daten['suchbegriff'], $daten['preis']) ; break break; // gekuerztes Beispiel ?> Listing 21.14 index.php (Version 1) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php namespace Repositories Repositories; use Doctrine Doctrine\ORM ORM\EntityRepository EntityRepository; class BenutzerRepository extends EntityRepository { // gekuerztes Beispiel function suche suche($suchbegriff, $preis) { $em = $this->getEntityManager(); $query = $em ->createQueryBuilder() ->select('s') 21.5 Die bekanntesten Angriffsvarianten 239 17 ->from('Entities\Seminar', 's') 18 ->where("s.titel LIKE '%$suchbegriff%'") 19 ->andWhere("s.preis >= $preis") 20 ->getQuery() 21 ; 22 23 //var_dump($query->getDQL()); 24 25 return $query->getResult(); 26 } 27 } 28 29 ?> Listing 21.15 models/Repositories/SeminarRepository.php (Version 1 - SQL-Injections) Der Code sieht eigentlich ganz harmlos aus und soll eine einfache Seminar-Suche bzw. -Filterung ermöglichen. Allerdings lässt die Umsetzung SQL-Injections zu, da sie die Benutzereingaben ungefiltert in die WHERE -Bedingungen des QueryBuilders und somit in den DQL-Code übernimmt. Testen Sie doch einmal nachfolgende Angaben. 1. 2. 3. 4. Preis: 500 Preis: 500 OR 1=1 Preis: 9999 OR s.id=1 Suchbegriff: honk' OR 1=1 OR s.titel LIKE ' Betrachten wir nun die entstandenen WHERE-Bedingungen im DQL-Code, indem wir den var_dump() in SeminarRepository#suche() aktivieren. 1. 2. 3. 4. s.titel LIKE '%%' AND s.titel LIKE '%%' AND s.titel LIKE '%%' AND (s.titel LIKE '%honk' >= 0 s.preis >= 500 (s.preis >= 500 OR 1=1) (s.preis >= 9999 OR s.id=1) OR 1=1 OR s.titel LIKE '%') AND s.preis Es ist uns also dreimal gelungen, die WHERE-Bedingung der SELECT-Anweisung mit eigenem Code zu erweitern. Bei einer Suche scheint dies nicht so schlimm zu sein, doch was ist, wenn Sie beispielsweise einen ähnlichen Code bei Ihrer Login-Prüfung verwenden? In den Beispielen wurde jeweils $_POST manipuliert und hierfür ein Text-Eingabefeld benutzt. Dies bedeutet natürlich nicht, dass dies bei anderen Daten-Quellen (z.B. $_GET oder $_SERVER) und Feld-Typen (z.B. hidden oder radio) nicht möglich ist. Bei Doctrine können SQL-Injections eigentlich nur bei SELECT -Statements vorkommen, da wir für INSERT , UPDATE und DELETE keinen eigenen DQL-Code schreiben. 240 21 Eine Einführung in das Thema Sicherheit Lösungsansätze Kommen wir jetzt zur guten Nachricht: So lange Sie in den SELECT -Statements Prepared Statements172 mit (benannten) Platzhaltern für alle Benutzereingaben verwenden, müssen Sie sich um SQL-Injections keine Sorgen machen. Sämtliche potentiell gefährlichen Anweisungen in Platzhaltern werden automatisch maskiert und sind somit ungefährlich. Beispiel 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <?php namespace Repositories Repositories; use Doctrine Doctrine\ORM ORM\EntityRepository EntityRepository; class BenutzerRepository extends EntityRepository { // gekuerztes Beispiel function suche suche($suchbegriff, $preis) { $em = $this->getEntityManager(); $query = $em ->createQueryBuilder() ->select('s') ->from('Entities\Seminar', 's') ->where('s.titel LIKE :suchbegriff') ->andWhere('s.preis >= :preis') ->setParameter('suchbegriff', '%' . $suchbegriff . '%') ->setParameter('preis', $preis) ->getQuery() ; return $query->getResult(); } } ?> Listing 21.16 models/Repositories/SeminarRepository.php (Version 2 - Prepared Statement) Prepared Statements schützen Ihre Anwendung gegen SQL-Injections und nur dagegen! Leider kann man Platzhalter nur für Werte und nicht für Spaltennamen einsetzen. Möchte man also beispielsweise eine Benutzereingabe in einem ORDER BY verwenden, so sollte man zur Absicherung eine Whitelist (beispielsweise mit einem im Code hinterlegten Array) nutzen. 172. Siehe: docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/security.html 21.5 Die bekanntesten Angriffsvarianten 241 Aufgabe 3: 1. Prüfen Sie den Code (Controller/Repositories) Ihrer Seminarverwaltung auf SQL-Injection-Lücken. Fixen Sie diese sofern nötig. 2. Integrieren Sie den Endstand der Suche. Was passiert, wenn Sie nach einem nicht vorhandenen Begriff, wie beispielsweise _x_ suchen? 3. Ergänzen Sie die Ausgabe einer Meldung, sofern die Suche keine Treffer erzielt. 21.5.2 Cross-Site-Scripting Anders als bei SQL-Injections ist bei einem Angriff mittels Cross-Site-Scripting (XSS)173 nicht unsere Anwendung auf dem Webserver das Ziel, sondern der Schadcode richtet sich gegen unsere Besucher bzw. deren Browser. Hierfür versucht ein Angreifer, seinen in einer clientseitigen Skriptsprache (z.B. JavaScript oder Visual Basic Script) erstellten Code in den HTML-Quelltext unserer Anwendung einzuschleusen. XSSLücken sind vorhanden, wenn eine Anwendung Daten von einem Benutzer annimmt und diese danach ungefiltert im Browser (eines anderen Benutzers) anzeigt. Gelingt eine XSS-Attacke, so sind nahezu beliebige Änderungen an Texten und Links möglich (Stichwort DOM-Manipulation174), womit beispielsweise Aktionen wie Phishing175 oder ein dauerhaftes Defacement176 eingeleitet werden können. Im schlimmsten Fall ermöglicht eine solche Lücke einem Angreifer sogar die Installation von Software (z.B. einem Trojaner) auf dem Rechner des Besuchers und verursacht einen damit verbundenen erheblichen Imageschaden. Eine XSS-Attacke ist meist breit gestreut und nicht zielgerichtet auf eine bestimmte Person gerichtet, obwohl dies theoretisch auch möglich wäre. Das perfide an einer solchen Attacke ist, dass das Opfer unserer Website vertraut und dieses Vertrauen vom Angreifer für seine Zwecke missbraucht wird. Angriffsszenarien Heutige Websites bestehen in den seltensten Fällen rein aus statischen Texten. Meistens gibt es zusätzlich nutzergenerierte Inhalte177 (z.B. Kommentare oder Kunden-Meinungen), auf deren Inhalt wir keinen direkten Einfluss haben. Dies ist dann auch der 173. Siehe: de.wikipedia.org/wiki/Cross-Site-Scripting 174. Siehe: de.wikipedia.org/wiki/Document_object_model 175. Siehe: de.wikipedia.org/wiki/Phishing 176. Siehe: de.wikipedia.org/wiki/Defacement 177. Siehe: de.wikipedia.org/wiki/User-Generated-Content 242 21 Eine Einführung in das Thema Sicherheit einfachste Angriffspunkt, da diese Inhalte vielfach sofort nach der Speicherung für die restlichen Besucher zur Verfügung stehen (persistentes Cross-Site-Scripting). Ein anderer Angriffsvektor ist das reflektierte (engl. reflected) Cross-Site-Scripting. Es nutzt beispielsweise eine in einem Suchformular enthaltene XSS-Lücke in Kombination mit einem Wurm178 aus. Diese Lücke wäre, wenn man lediglich die eigene Website betrachtet, relativ ungefährlich, da nur der Angreifer selbst den manipulierten HTML-Quelltext ausgeliefert bekäme. Dank der populären sozialen Netzwerke geht ein ansprechend präsentierter Link179 jedoch schnell um die Welt und erreicht viele potentielle Opfer. Ein gutes Beispiel ist ein XSS-Wurm, der sich 2006 auf StudiVZ verbreitete und dem Diebstahl der Logindaten diente.180 Beispiel 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <form id="suche" action="index.php?aktion=suche_seminar" method="post" > <label for="suchbegriff">Suchbegriff</label> <input type="text" name="suchbegriff" id="suchbegriff" /> <br /> <label for="preis">Mindestpreis</label> <input type="text" name="preis" id="preis" value="0" /> <br /> <button type="submit">Abschicken</button> </form> <?php if (!empty empty($daten['suchbegriff'])) : ?> <p> Sie suchten nach: &raquo; &raquo;<?php <?php echo $daten['suchbegriff']; ?> ?>&laquo; &laquo; </p> <?php endif endif; ?> // gekuerztes Beispiel Listing 21.17 views/suche_seminar.tpl.php (Version 2) 1 <?php 2 3 // gekuerztes Beispiel 4 5 case 'suche_seminar': 6 $daten = array_merge array_merge( 7 array array('suchbegriff' => '', 'preis' => 0), 8 $_REQUEST 9 ); 10 $seminare = $em 178. Siehe: de.wikipedia.org/wiki/Computerwurm 179. Alternativ kann der Link auch einfach mit einem Kurz-URL-Dienst wie Bit.ly getarnt sein. 180. Siehe: www.fixmbr.de/studivz-durch-wurm-lahmgelegt/ 21.5 Die bekanntesten Angriffsvarianten 243 11 ->getRepository('Entities\Seminar') 12 ->suche($daten['suchbegriff'], $daten['preis']) 13 ; 14 break break; 15 16 // gekuerztes Beispiel 17 18 ?> Listing 21.18 index.php (Version 2) Um die grundsätzliche Vorgehensweise zu demonstrieren, habe ich unsere Seminarsuche mit einer XSS-Lücke versehen. Diese Lücke besteht aus zwei Komponenten: 1. Der Suchbegriff wird ungefiltert in Zeile 19 des Templates suche_seminar.tpl.php ausgegeben. 2. Der Controller index.php akzeptiert in Zeile 8 den Suchbegriff nun auch aus der Superglobalen $_GET . Testen Sie die anfällige Anwendung mit den nachfolgenden Angaben: 1. Suchbegriff: <script type="text/javascript">alert("XSS")</script> 2. index.php?aktion=suche_seminar&suchbegriff=<script>alert("XSS")<%2Fscript> Ein Angreifer wird natürlich keine Alertbox anzeigen wollen, sondern beispielsweise eine Phishing-Aktion einleiten, indem er Sie auf einen anderen Server umleitet. Beispiel 1 window.location = 'http://www.google.com/'; 2 document.getElementById('suche').action = 'http://www.google.de/'; Listing 21.19 JavaScript-Umleitungen Mit dem JavaScript-Code in Zeile 1 würde die aktuell angezeigte Webseite sofort mit der Startseite von Google ersetzt. Dies wäre lediglich ein Ärgernis für unseren Besucher. Doch was ist, wenn ein Angreifer unsere Website optisch exakt nachbaut und unseren Besucher auf dieser Kopie zum Login verleitet? Dann hätte unsere Anwendung eine erfolgreiche Phishing-Attacke ermöglicht und wir vermutlich das Vertrauen des Besuchers in unsere Anwendung verspielt. Genauso verhält es sich mit dem JavaScript-Code in Zeile 2 mit dem kleinen Unterschied, dass der Besucher erst bei der Benutzung unseres Suchformulars umgeleitet würde. Lösungsansätze Wenn die Eingaben eines Benutzers für eine Anzeige im Browser benötigt werden, so sollte diese Anzeige nur nach einer Ausfilterung von unerwünschten Angaben erfolgen. Bedenken Sie herbei jedoch, dass sich JavaScript nicht nur in einem script -Tag verbergen kann. 244 21 Eine Einführung in das Thema Sicherheit Beispiel 1 <script type="text/javascript">alert("XSS")</script> 2 <a href="#" onmouseover=" "alert('XSS')" ">Klick mich</a> 3 <iframe src=javascript:alert('XSS')></iframe> Listing 21.20 JavaScript-Einbindung Dies sind nur ein paar Beispiele, die bei meinen Tests mit Firefox (Version 20) problemlos funktionierten, selbst jedoch lediglich die Spitze des Eisbergs bilden. Doch wie können wir solche Angriffe verhindern?181 Im Falle unserer Suche ist dies relativ simpel, da beim Suchbegriff keines der drei HTML-Tags überhaupt möglich sein muss. Um genau zu sein, muss das Eingabefeld keinerlei HTML-Tags erlauben. Wir können diese also bei der Ausgabe mit der Funktion strip_tags() komplett herausfiltern. Beispiel 1 2 3 4 5 6 7 8 9 10 <?php // gekuerztes Beispiel function e($dirty) { echo strip_tags strip_tags($dirty); } ?> Listing 21.21 helper.inc.php (Version 1) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <form id="suche" action="index.php?aktion=suche_seminar" method="post" > <label for="suchbegriff">Suchbegriff</label> <input type="text" name="suchbegriff" id="suchbegriff" value="<?php <?php e($daten['suchbegriff']); ?> ?>" /> <br /> <label for="preis">Mindestpreis</label> <input type="text" name="preis" id="preis" value="0" /> <br /> <button type="submit">Abschicken</button> </form> <?php if (!empty empty($daten['suchbegriff'])) : ?> <p> Sie suchten nach: &raquo; &raquo;<?php <?php e($daten['suchbegriff']); ?> ?>&laquo; &laquo; 181. Siehe auch: blog.astrumfutura.com/2013/04/20-point-list-for-preventing-cross-site-scripting-inphp/ 21.5 Die bekanntesten Angriffsvarianten 245 23 </p> 24 <?php endif endif; ?> 25 26 // gekuerztes Beispiel Listing 21.22 views/suche_seminar.tpl.php Die Funktion e() soll Ihnen einiges an Schreibarbeit ersparen und gehört in den Bereich der sogenannten Views-Helper. Anstatt bei jeder Ausgabe von Benutzereingaben echo strip_tags($wert) schreiben zu müssen, reicht ein einfaches e($wert) aus. Der Code wird also nicht nur sicherer, sondern auch schlanker. Dank des Einsatzes unseres neuen Helpers kann mit keinem der drei obigen Beispiele mehr eine Alertbox angezeigt werden. Um die Usability unserer Suche zu verbessern, habe ich jedoch in Zeile 8 des Templates suche_seminar.tpl.php eine Ausgabe des Suchbegriffs im value -Attribut ergänzt. Wenn Sie nun das erste Beispiel erneut ausprobieren, offenbart sich hierdurch ein weiteres Problem. Das erste doppelte Anführungszeichen von dem String XSS beendet nämlich die Ausgabe im value -Attribut. Testen Sie doch einmal Ihre Anwendung mit den nachfolgenden Angaben: 1. Suchbegriff: " style="border: 1px solid red 2. Suchbegriff: ">Defacement Unser Code erlaubt also trotz Helper immer noch einzelne (schließende) spitze Klammern und Anführungszeichen, was für ein Website-Defacement ausgenutzt werden kann. Um diese Zeichen an einem unerwünschten »Herauswuchern« zu hindern, ergänzen wir im Helper e() einen Aufruf der Funktion htmlspecialchars() 182. Beispiel 1 2 3 4 5 6 7 8 9 10 <?php // gekuerztes Beispiel function e($dirty) { echo htmlspecialchars htmlspecialchars(strip_tags strip_tags($dirty), ENT_QUOTES); } ?> Listing 21.23 helper.inc.php (Version 1b) Da auch einfache Anführungszeichen an manchen Stellen zu einem Problem werden könnten, benutzen wir den optionalen zweiten Parameter von 183 htmlspecialchars() , um auch diese in HTML-Entitäten umzuwandeln. 182. Siehe: stackoverflow.com/questions/46483/htmlentities-vs-htmlspecialchars 246 21 Eine Einführung in das Thema Sicherheit Leider ist das Leben nicht immer so einfach, wie im Beispiel unserer Suche. Oftmals muss beispielsweise HTML-Code im Rahmen einer Beschreibung erlaubt und ein XSSAngriff trotzdem verhindert werden. Um dies zu lösen, sollten Sie keinesfalls auf die Idee kommen, Ausnahmen über den optionalen zweiten Parameter von strip_tags() zu ermöglichen. Jedes der erlaubten Tags unterstützt nämlich weiterhin die Nutzung von JavaScript Event-Handlern. Wir benötigen stattdessen einen sogenannten XSS-Cleaner zur Bereinigung der Benutzereingaben. Eine XSS-Blacklist ist hierbei meiner Meinung nach wegen der unzähligen Ansätze und Varianten ein zum Scheitern verurteilter Ansatz und ich kann nur jedem Entwickler die Verwendung einer XSS-Whitelist (auch Purifier genannt) ans Herz legen. Beispiel 1 { 2 "autoload": { 3 "psr-0": { 4 "Entities": "./models/", 5 "Repositories": "./models/", 6 "Validators": "./models/" 7 } 8 }, 9 10 "require": { 11 "php": ">=5.3.2", 12 "doctrine/orm": "~2.2.2", 13 "gedmo/doctrine-extensions": "~2.3.2", 14 "webmasters/doctrine-extensions": "~2.3.7", 15 "ezyang/htmlpurifier": ">=4.5.0" 16 } 17 } Listing 21.24 composer.json (Version 1) Die Library HTML Purifier184 ist meines Wissens nach die bekannteste und erprobteste XSS-Whitelist. Allein über Packagist wurde sie bereits über 15.000 Mal installiert. Beispiel 1 2 3 4 5 6 7 8 9 10 11 <?php // gekuerztes Beispiel function e($dirty) { echo htmlspecialchars htmlspecialchars(strip_tags strip_tags($dirty), ENT_QUOTES); } function purify purify($dirty) { 183. Siehe: de.wikipedia.org/wiki/Zeichen-Entit%C3%A4t-Referenz 184. Siehe: htmlpurifier.org/ 21.5 Die bekanntesten Angriffsvarianten 247 12 $config = HTMLPurifier_Config HTMLPurifier_Config::createDefault(); 13 $purifier = new HTMLPurifier HTMLPurifier($config); 14 echo $purifier->purify($dirty); 15 } 16 17 ?> Listing 21.25 helper.inc.php (Version 2) Verwenden Sie den Helper purify() immer dann, wenn eine Benutzereingabe ausgegeben werden soll, die HTML-Code unterstützen muss. Wenn Ihnen die Standard-Whitelist für Tags und Attribute nicht zusagt, so können Sie diese mit etwas Aufwand im Helper anpassen.185 HTML Purifier macht die Ausgabe von Benutzereingaben mit HTML-Code XSSsicher.186 Aufgabe 4: 1. Installieren Sie die Library HTML Purifier in Ihrem vendor-Verzeichnis. 2. Integrieren Sie die beiden neuen Helper in der helper.inc.php in Ihrem includesVerzeichnis. 3. Integrieren Sie den aktuellen Stand des Views suche_seminar.tpl.php. 4. Schützen Sie auch die restliche Seminarverwaltung durch Verwendung der beiden neuen View-Helper anstatt echo. 21.5.3 Weitere Angriffsmöglichkeiten Bevor wir zu einem ganz anderen Thema übergehen, möchte ich noch kurz etwas zur Variante der CSRF-Angriffe (Cross-Site Request Forgery) erzählen. Hierbei handelt es sich im weitesten Sinne um das genaue Gegenteil einer XSS-Attacke. Anstatt mittels Schadcode den Browser eines Besuchers anzugreifen, werden die im Browser gespeicherten Informationen für eine Veränderung von gespeicherten Daten auf unserem Webserver missbraucht. Bei einer lohnenswerten Manipulation kann es sich im schlimmsten Fall um eine Überweisung oder einen Kaufvorgang handeln, aber auch eine Löschung von Datensätzen in unserer Anwendungs-Datenbank kann durchaus von Interesse sein. Der Angreifer missbraucht also das Vertrauen einer Anwendung in einen »bekannten« Benutzer. 185. Siehe: htmlpurifier.org/docs/enduser-customize.html 186. Dies gilt natürlich nur, wenn Sie die Library stets auf dem aktuellsten Stand halten. 248 21 Eine Einführung in das Thema Sicherheit Sicherheitsrelevante Aktionen sollten nur möglich sein, wenn der Benutzer bereits eingeloggt ist. Wird dieser Umstand bei einem CSRF-Angriff ausgenutzt, so handelt es sich um sogenanntes Session Riding. Eine CSRF-Lücke existiert immer dann, wenn eine Veränderung von Daten komplett über $_GET ausgelöst werden kann. Dies ist beispielsweise der Fall, wenn wir die Eingaben in einem Formular zwar mit der Methode post verschicken, im Controller aber $_REQUEST für die Verarbeitung verwenden.187 Aufgabe 5: 1. Überlegen Sie, welche zwei Aktionen unseres Controllers index.php derzeit für einen CSRF-Angriff anfällig sind. 2. Minimieren Sie das Problem, indem Sie PHP-basierte Sicherheitsabfragen einbauen. Wenn Sie einen Ansatz für die Umsetzung benötigen, so sehen Sie sich noch einmal im Controller die Aktion delete_seminartermin und das Template delete_seminartermin.tpl.php an. Und? Haben Sie herausgefunden, welche Aktionen anfällig waren? Nein? Dann schauen Sie noch einmal genau nach, welche Aktionen die Methode EntityManager#flush() aufrufen, diesen Methoden-Aufruf jedoch nicht an ein befülltes $_POST -Array koppeln. Erlauben Sie Änderungen in Ihrer Datenbank nur, wenn diese durch Daten in $_POST ausgelöst wurden. Dies können Sie beispielsweise mittels einer in PHP umgesetzten Sicherheitsabfrage lösen.188 Diese Maßnahme ist alleine jedoch kein wirksamer Schutz gegen CSRF-Angriffe. Für einen wirksameren Schutz müssen Sie stets sicherstellen, dass ein Request auch wirklich vom betreffenden Benutzer und nicht etwa vom Browser wegen einer eingebetteten Ressource (z.B. Bildern) ausgelöst wurde. 187. Erinnern Sie sich an die zweite Komponente unserer provozierten XSS-Lücke? 188. Eine JavaScript-Abfrage ist hier nicht ausreichend! 21.6 Authentisierungssicherheit 249 Beispiel 1 <img src="index.php?aktion=remove_teilnehmer&id=1&teilnehmer_id=2" style="display: none;" /> 2 <div style="background-image: url(index.php?aktion=remove_teilnehmer&id=1&teilnehmer_id=2)"></div> Listing 21.26 CSRF-Beispiele Die Ausgabe von benutzerdefinierten Ressourcen mit URL-Parametern sollten Sie auf jeden Fall in Ihrer Anwendung unterbinden. Dies verhindert jedoch nicht, dass ein solcher Link nicht auf einer anderen Website eingebunden wird und von dort einen Angriff auslöst. Deswegen sollten Sie zusätzlich folgende Ansätze bei der Planung Ihrer Anwendung berücksichtigen: ➤ ➤ ➤ ➤ ➤ ➤ Aufteilung von komplexen Vorgängen in einzelne Schritte (z.B. Buchungstunnel) erneute Kennworteingabe bei sicherheitskritischen Aktionen189 Einmalkennwörter oder Transaktionsnummern (TANs)190 eine Bestätigung per E-Mail mit einem Link zum Auslösen der Aktion191 CAPTCHAs192 Formular-Tokens193 Zum Abschluss dieses Themenbereichs möchte ich Ihnen empfehlen, sich auch mit den weniger bekannten Angriffsmöglichkeiten vertraut zu machen. Bedenken Sie auch, dass ein Angriff aus einer Kombination mehrerer Techniken bestehen kann. Zum Einstieg bieten sich folgende Quellen an: 1. phpmaster.com/8-practices-to-secure-your-web-app/ 2. phpmaster.com/top-10-php-security-vulnerabilities/ 3. https://www.owasp.org/index.php/Category:Attack 21.6 Authentisierungssicherheit Zusätzlich zu möglicherweise in Ihrem Code verborgenen Sicherheitslücken gibt es noch weitere Fallstricke, die Sie bei der Umsetzung einer mit einem Login geschützten Anwendung bedenken sollten. 189. Diese Variante nutzt beispielsweise Amazon beim Start eines Bestellvorgangs (engl. Checkout). 190. Diese Variante ist im Bereich des Online-Bankings sehr verbreitet. 191. Eine solche E-Mail wird häufig bei vergessenen Passwörtern und einem damit verbundenen Kennwortreset genutzt. 192. Siehe: de.wikipedia.org/wiki/CAPTCHA 193. Siehe: phpmaster.com/preventing-cross-site-request-forgeries/ 250 21 Eine Einführung in das Thema Sicherheit 21.6.1 Passwörter Der Bereich der Kennwörter scheint nicht wirklich in diese Lektion zu passen, da es sich zunächst einmal um kein technisches Problem sondern um ein menschliches auf Seiten unserer Benutzer handelt. Doch wenn Sie an den Abschnitt 21.1 zurückdenken, werden Sie schnell merken, dass aus diesem menschlichen Problem schnell eine Gefahr für unsere Anwendung erwachsen kann. Wir sollten also technische Maßnahmen ergreifen, um dieses Problem zu minimieren. Vermeidung von unsicheren Passwörten Unsichere Kennwörter sind ein Themenbereich mit dem sich jeder Entwickler vertraut machen sollte. Denn hat ein Angreifer erst einmal gültige Logindaten ermittelt, so kann er den kompromittierten194 Account voll ausnutzen. Im einfachsten Fall wird er lediglich ein neues Passwort vergeben und den eigentlichen Benutzer so aussperren. So gewinnt er Zeit, kann sich die zugänglichen Daten in Ruhe zu Gemüte führen und diese, sofern er genügend Rechte erlangt hat, beliebig manipulieren. Hat der Angreifer zunächst einen Account mit sehr eingeschränkten Rechten erwischt, so kann er trotzdem versuchen, diesen für weitergehende Angriffe zu verwenden. So ist es ihm möglicherweise doch noch möglich, erweiterte Rechte zu erlangen. Trauen Sie keinen Benutzereingaben, auch nicht denen von einem eingeloggten Benutzer. Um die Gefahr eines solchen Angriffs zu minimieren, sollten wir als Entwickler die Verwendung von unsicheren Passwörtern verhindern. Hierbei gibt es zwei mögliche Ansätze. 1. Unsere Anwendung vergibt serverseitig ein sicheres Kennwort (beispielsweise im Rahmen der Registrierung). 2. Die Anwendung unterstützt unsere Benutzer bei der Wahl eines sicheren Kennworts. Beide Varianten sind in der freien Wildbahn ungefähr gleich häufig anzutreffen und haben jeweils einen klaren Vorteil. Die serverseitige Variante erzeugt (sofern korrekt umgesetzt) die wesentlich sichereren (komplett zufälligen) Passwörter und die andere Variante hat einen höheren Benutzerkomfort, da man sich selbst festgelegte Kennwörter meist besser merken kann. Die serverseitige Variante missfällt mir persönlich, da man seine Benutzer durch vorgegebene Kennwörter schnell verärgern kann.195 Wir werden uns deswegen im Folgenden mit der zweiten Variante beschäftigen, da wir damit auch das Thema der Validierungen vertiefen können. 194. Siehe: de.wikipedia.org/wiki/Technische_Kompromittierung 195. Durch eine Kombination beider Varianten könnte man dies jedoch theoretisch abmildern. 21.6 Authentisierungssicherheit 251 Bevor wir uns mit der genauen Implementierung beschäftigen, benötigen wir zunächst ein Regelset für sichere Kennwörter. Würden wir unsere Benutzer nämlich komplett in Eigenregie walten lassen, so würden wir als Ergebnis Passwörter wie beispielsweise Passwort oder geheim erhalten. Diese Beispiele wären nicht einmal einen Fetzen Klopapier wert, um sie darauf zu notieren, da sie innerhalb von Sekunden durch einen sogenannten Wörterbuchangriff196 ermittelbar sind. Der Name dieser Angriffsweise basiert ursprünglich darauf, dass viele Menschen für ein neues Kennwort Begriffe aus einem Wörterbuch (Duden oder Lexikon, engl. dictionary) wählen.197 Heutzutage sind solche »Wörterbücher« sehr viel ausgereifter, da im Rahmen diverser Angriffe immer wieder tatsächlich verwendete Passwörter ergänzt werden konnten. Eine der größten Ergänzungen ermöglichte beispielsweise eine erfolgreiche SQLInjection beim Online-Spiele-Dienst RockYou.com198, durch welche eine Liste mit 32 Millionen Klartext-Kennwörtern ihren Weg in das Internet fand (nach Dubletten-Entfernung verblieben 14,3 Millionen). Je mehr dieser häufigen Passwörter wir also durch ein Regelset vermeiden, desto länger benötigt ein Angreifer für einen erfolgreichen Treffer. Bei der Erstellung eines solchen Regelsets müssen wir allerdings immer einen brauchbaren Kompromiss zwischen der Sicherheit der Passwörter und dem Komfort der Benutzer finden. Je größer die Länge eines Kennworts ist, desto höher fällt die benötigte Zeit für einen Angriff mit der sogenannten Brute-Force-Methode aus.199 Bei dieser Methode werden im Gegensatz zu einem Wörterbuchangriff einfach alle möglichen Zeichenkombinationen ermittelt und ausprobiert, was mit einem deutlich höheren Zeitaufwand verbunden ist. Aus dieser Erkenntnis ergibt sich direkt unsere erste Regel: 1. Mindestlänge: Empfehlenswert ist eine Mindestlänge von 8 Zeichen, besser sogar 10 Zeichen. Geben Sie niemals eine genaue Länge vor, sondern immer nur eine Mindestanzahl oder einen Bereich. So haben die Benutzer mehr Spielraum bei der Auswahl. Bedenken Sie aber auch, dass die Merkbarkeit bei vielen Benutzern mit steigender Länge sinkt und eine weitere Erhöhung somit kontraproduktiv sein kann. 2. Zulässige Zeichen: Aus je mehr Zeichen ein Kennwort besteht, desto größer fällt die Anzahl der möglichen Zeichenkombinationen aus. Würde man beispielsweise lediglich die Ziffern 0 bis 9 zulassen und gäbe eine vierstellige Pin vor, so gäbe es 10 * 10 * 10 * 10 = 10.000 Kombinationen. Würden wir hingegen die ursprüngliche 7-Bit ASCII-Tabelle200 erlauben, so stünden uns schon 95 (druckbare) Zeichen zur Auswahl und es ergäben sich 95 * 95 * 95 * 95 = 81.450.625 Kombinationen. 3. Zeichendiversifikation: Passwörter wie beispielsweise 1111, 1234 oder abcd sind leider weit verbreitet und wenig sinnvoll, da sie einfach zu erraten sind. Ein 196. Siehe: de.wikipedia.org/wiki/W%C3%B6rterbuchangriff 197. Das »Oxford English Dictionary« enthält beispielsweise ca. 600.000 Einträge. Siehe: public.oed.com/ about/ 198. Siehe: hackingexpose.blogspot.de/2009/12/rockyoucom-sql-injection-flaw-issue.html 199. Siehe: de.wikipedia.org/wiki/Brute-Force-Methode 200. Siehe: de.wikipedia.org/wiki/American_Standard_Code_for_Information_Interchange 252 21 Eine Einführung in das Thema Sicherheit sicheres Kennwort sollte deshalb Groß- und Kleinbuchstaben, Zahlen und Sonderzeichen und zu mindestens 50 Prozent unterschiedliche Zeichen enthalten. 4. Persönliche Daten: Durch die sozialen Netzwerke ist eine große Anzahl von Informationen über uns frei zugänglich, doch auch über andere Quellen sind viele Daten leicht herauszufinden. Eine sinnvolle Implementierung sollte also auf jeden Fall verhindern, dass die der Anwendung bekannten Profildaten im Kennwort verwendet werden können. Es wäre zwar auch sinnvoll Kennwörter auf Basis unserer Partner, Kinder oder Haustiere zu vermeiden, doch diese Informationen liegen unseren Anwendungen im Normalfall nicht vor. Ein sicheres Kennwort Machen wir uns nun einmal kurz Gedanken, wie wir nach diesen Regeln selbst ein sicheres Kennwort für uns erstellen würden. Dabei sollten wir jedoch bedenken, dass die RockYou-Liste viel über die Denkweise eines Benutzers bei der Erstellung eines Kennworts zeigte. So kamen nahezu alle Großbuchstaben am Anfang und Interpunktionszeichen wie das Komma oder der Punkt am Ende. Zudem gab es einen starken Trend zu Vornamen gefolgt von einer Jahreszahl (z.B. Julia1984). All dies sollten wir also vermeiden. Beginnen wir unseren Gedankengang mit einem einfachen Satz: »Ein PHP-Entwickler mag jQuery, aber er liebt Doctrine.« Daraus könnten wir zunächst das Passwort EP-Emj,aelD. entwickeln, indem wir lediglich jeden ersten Buchstaben und die Sonderzeichen berücksichtigen. In dieser ersten Variante beginnen wir allerdings immer noch mit einem Großbuchstaben und es fehlen noch Zahlen. Um beide Probleme zu lösen, möchte ich das Thema Leetspeak201 in den Raum werfen. Hierbei werden einzelne Zeichen durch eine ähnlich aussehende Entsprechung ersetzt. Da eine komplette Ersetzung die Anzahl der erlaubten Zeichen unnötig reduzieren würde, würde ich lediglich eine Ersetzung aller ungeraden Vokalbuchstaben202 (ohne die deutschen Umlaute) vorschlagen. Vokalbuchstabe Ersetzungen A 4 , @ , /\ , /-\ , ? , ^ , α , λ E 3,€,&,£,ε I ! , 1 , | , ][ , ỉ O 0 , 9 , () , [] , * , ° , <> , ø , {[]} U |_| , µ , [_] , v Tabelle 21.2 Leet-Speak 201. Siehe: de.wikipedia.org/wiki/Leetspeak 202. Siehe: de.wikipedia.org/wiki/Vokal 21.6 Authentisierungssicherheit Vokalbuchstabe Y 253 Ersetzungen `/ , °/ , ¥ Tabelle 21.2 Leet-Speak Unter Beachtung dieser Ersetzungen würde man also aus der ersten Variante EP-Emj,aelD. das relativ sichere Kennwort 3P-Emj,4elD. erhalten.203 Allerdings wäre hierbei die Zeichendiversifikation noch optimierbar, indem man eine der anderen Ersetzungen wählt. Das fertige Regelset Versuchen wir nun, unsere Erkenntnisse in einer Validierungsklasse zu berücksichtigen. Beispiel 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <?php namespace Validators Validators; use Webmasters Webmasters\Doctrine Doctrine\ORM ORM\EntityValidator EntityValidator; class BenutzerValidator extends EntityValidator { // Mindestlaenge des Kennworts protected $_minLaenge = 8; public function filterRegex filterRegex($wert, $regex) { return filter_var( $wert, FILTER_VALIDATE_REGEXP FILTER_VALIDATE_REGEXP, array array( 'options' => array array('regexp' => $regex) ) ); } public function validatePasswort validatePasswort($passwort) { $entity = $this->getEntity(); // komplettes Benutzer-Objekt // Ermittlung aller benutzten Zeichen $zeichenpool = count_chars count_chars($passwort, 1); // Es kommt min. ein Buchstabe vor 203. Einen vergleichbaren Ansatz empfiehlt übrigens Mozilla. Siehe: https://support.mozilla.org/de/kb/ Passw%C3%B6rter%20mit%20erh%C3%B6hter%20Sicherheit%20erstellen 254 21 Eine Einführung in das Thema Sicherheit 31 $nutztBuchstaben = $this->filterRegex($passwort, '/[a-zA-Z]+/'); 32 33 // Es kommt min. eine Zahl vor 34 $nutztZahlen = $this->filterRegex($passwort, '/\d+/'); 35 36 // Es kommt min. ein Sonderzeichen vor 37 $nutztSonderzeichen = $this->filterRegex($passwort, '/[_\W]+/'); 38 39 if (empty empty($passwort)) { 40 $this->addError('Das Feld Passwort ist leer.'); 41 } elseif (strlen strlen($passwort) < $this->_minLaenge) { 42 $this->addError( 43 sprintf sprintf('Das Passwort sollte mindestens %d Zeichen lang sein.', $this->_minLaenge) 44 ); 45 } elseif (count count($zeichenpool) < (strlen strlen($passwort) / 2)) { 46 $this->addError('Das Passwort sollte zu mindestens 50 Prozent unterschiedliche Zeichen enthalten.'); 47 } elseif ( 48 ($nutztBuchstaben === false) || 49 ($nutztZahlen === false) || 50 ($nutztSonderzeichen === false) 51 ) { 52 $this->addError('Das Passwort sollte mindestens einen Buchstaben, eine Zahl und ein Sonderzeichen enthalten.'); 53 } elseif ( 54 ( 55 $entity->getVorname() && 56 stristr stristr($passwort, $entity->getVorname()) !== false 57 ) || 58 ( 59 $entity->getName() && 60 stristr stristr($passwort, $entity->getName()) !== false 61 ) || 62 ( 63 $entity->getEmail() && 64 stristr stristr($passwort, $entity->getEmail()) !== false 65 ) 66 ) { 67 $this->addError('Das Passwort sollte keine privaten Daten enthalten.'); 68 } 69 } 70 } 71 72 ?> Listing 21.27 models/Validators/BenutzerValidator.php Diese Klasse ist nicht wirklich vollständig, zeigt aber gut, wohin die Reise geht. Vor allem hinkt es noch bei der Überprüfung der persönlichen Daten, da uns im Beispiel der Seminarverwaltung lediglich der Name und die E-Mail-Adresse vorliegt. Doch gehen wir kurz die Besonderheiten des Codes durch. In Zeile 10 legen wir die Mindestlänge von Kennwörtern als Attribut der Validatorklasse fest. In Zeile 12-21 definieren wir eine neue öffentliche Methode BenutzerValidator#filterRegex() , die lediglich einen verkürzten Zugriff auf die Funktion filter_var() 204 bietet. Diese 204. Siehe: www.php.net/manual/de/function.filter-var.php 21.6 Authentisierungssicherheit 255 Funktion existiert seit PHP 5.2.0 und bietet neben der verwendeten Alternative für preg_match() auch Filter für E-Mail-Adressen und URLs.205 Ich habe mich übrigens in diesem Beispiel gegen preg_match() entschieden, da diese Funktion einen nicht gefundenen regulären Ausdruck durch den Integer-Wert 0 signalisiert. Die Funktion filter_var() verwendet hierfür hingegen den lesbareren Boolean false . Die letzte Besonderheit sind die Zeilen 29 und 46. In Zeile 29 ermitteln wir zunächst mittels count_chars() 206 ein Array aller verwendeten Zeichen. In Zeile 46 prüfen wir dann, ob die Anzahl der Array-Elemente weniger als die halbe Stringlänge des Kennwort-Werts beträgt. Wäre dies der Fall, so läge ein Verstoß gegen die 50%-Regel der Zeichendiversifikation vor. Aufgabe 6: 1. Implementieren Sie die verbesserte Klasse BenutzerValidator in Ihrem Projekt-Verzeichnis. 2. Testen Sie das Regelset mit den nachfolgenden Kennwörten: geheim, 11111111, 1234567890, Julia1984 und 3P-Emj,4elD. Es ist wenig komfortabel, wenn wir den Sicherheitsstatus immer erst nach dem Absenden des Formulars anzeigen und auch nur das erste Validierungsproblem des Passworts melden. Bei derart hohen Anforderungen würden die meisten Benutzer nach ein paar Fehlschlägen entnervt aufgeben und auf eine Registrierung verzichten. Geben Sie also immer vorab im Formular entsprechende Hinweise oder nutzen Sie JavaScript, um den Status schon während der Eingabe anzuzeigen.207 Hierbei wäre es auch denkbar, unsere PHP-Validierungen mittels Ajax anzusprechen, um identische Regeln zu verwenden und diese nicht in zwei Programmiersprachen implementieren zu müssen. Eine alleinige JavaScript-Implementierung sollten Sie jedoch auf jeden Fall vermeiden, da man diese im Browser deaktivieren könnte. 21.6.2 Passworthashing Wie der Fall von RockYou.com zeigt, ist Klartext nicht die empfehlenswerteste Variante, um wichtige Daten (z.B. Kennwörter oder Kreditkarteninformationen) zu speichern. Solche Daten müssen zwar für uns und unsere Anwendung verfügbar sein, aber möglichst für keinen Dritten. Dies erreichen wir, indem wir die Klartextwerte verstecken. Zunächst müssen wir jedoch erst einmal verstehen, dass es hierfür zwei fundamental unterschiedliche Ansätze gibt: 205. Doch dies ist noch nicht alles. Siehe: www.php.net/manual/en/filter.examples.validation.php 206. Siehe: www.php.net/manual/de/function.count-chars.php 207. Siehe: www.freshdesignweb.com/10example-jqueryjavasript-password-strength-meter.html 256 21 Eine Einführung in das Thema Sicherheit 1. Verschlüsselung: Die Information wird mit einem Verschlüsselungs-Algorithmus behandelt. Dieser ist unter Zuhilfenahme eines Schlüssels (am sichersten sind sehr lange zufällige Werte) in der Lage, den Klartext in eine chiffrierte Form umzuwandeln und dies auch wieder rückgängig zu machen. Eine Verschlüsselung dient oftmals dazu, Informationen zwischen zwei Personen oder Gruppen auszutauschen. Es gibt symmetrische und asymmetrische Verfahren. Bei einem symmetrischen Verfahren müssen beide Gruppen Kenntnis über einen einzigen gemeinsamen Schlüssel haben. Beim asymmetrischen Verfahren wird mit einem geheimen privaten und einem öffentlichen Schlüssel gearbeitet. Die Daten werden dann beispielsweise mit dem öffentlichen Schlüssel chiffriert und können nur mit dem privaten Schlüssel wieder dekodiert werden. 2. Hashing: Die Information wird mit einem Einweg-Hash-Algorithmus (engl. OneWay Hash Function)208 behandelt. Der Klartextwert selbst dient als Schlüssel. Aus einem Klartextwert mit beliebiger Länge entsteht ein sogenannter Hash (engl. to hash, dt. zerhacken) mit im Normalfall fester Länge.209 Der Vorgang selbst ist nicht umkehrbar. Das Verfahren basiert auf der Annahme, dass der entstandene Hash eine einzigartige Signatur (engl. Fingerprint) des ursprünglichen Wertes ist und den Wert so eindeutig identifiziert, ohne etwas über seinen Inhalt zu verraten. Um einen Wert anhand eines Hashs zu überprüfen, muss lediglich der zu prüfende Wert mit dem gleichen Verfahren gehasht und dann die beiden Signaturen verglichen werden. Für Kennwörter ist das Hashing empfehlenswerter, da für eine Verifizierung (engl. Error Detection) des Passworts ein Vergleich der Signaturen ausreicht. Deswegen werden wir uns diese Variante einmal genauer ansehen. Doch welche Hashing-Algorithmen gibt es und was sollten wir bei der Auswahl bedenken? Ein guter und sicherer Einweg-Hash-Algorithmus sollte unbedingt ein paar wichtige Eigenschaften erfüllen: 1. Identische Ergebnisse: Der gleiche Algorithmus muss aus einem bestimmten Klartextwert immer den identischen Hash erzeugen. 2. Chaotische Ergebnisse: Ähnliche Klartextwerte müssen selbst bei nur einem sich unterscheidenden Zeichen zu völlig verschiedenen Hashwerten führen. 3. Einwegfunktion: Es muss praktisch unmöglich sein, aus einem Hash den zugehörigen Klartextwert zu ermitteln. 4. Starke Kollisionsresistenz: Es darf zwar theoretisch möglich sein, dass zwei Klartextwerte den gleichen Hash erzeugen (sogenannte Kollisionen). Es muss jedoch praktisch unmöglich sein, für einen bestimmten Hash einen weiteren Klartextwert zu ermitteln, der eine Kollision erzeugt (dt. kollisionsresistente Hashfunktion, engl. Collision Resistant Hash Function). 208. Siehe: de.wikipedia.org/wiki/Kryptologische_Hashfunktion 209. Siehe: de.wikipedia.org/wiki/Hashfunktion 21.6 Authentisierungssicherheit 257 Die bekanntesten und gleichzeitig für Kennwörter nicht empfehlenswerten Algorithmen sind: ➤ Message Digest: Es handelt sich hierbei um eine Reihe von Hashfunktionen, die von Ronald L. Rivest am Massachusetts Institute of Technology entwickelt wurden. Die Version 5 (MD5)210 ist derzeit noch weitverbreitet, aber bei kurzen Klartextwerten in minimaler Zeit mittels Brute-Force-Methode angreifbar.211 ➤ Secure Hash Algorithm: Der ursprüngliche Algorithmus wurde in einer Zusammenarbeit von NIST (National Institute of Standards and Technology) und NSA entwickelt. Hieraus entstand zunächst die fehlerbereinigte Fassung SHA-1212. Als Reaktion auf bekanntgewordene Angriffe gegen SHA-1 entstand die SHA-2-Familie (SHA-256, SHA-384, SHA-512 und SHA-224)213. Auch wenn SHA-2 laut NIST noch als sicher gilt, haben alle genannten Verfahren einen gravierenden Nachteil, da sie mit dem Ziel entwickelt worden sind, auch größere Datenmengen möglichst effizient zu hashen. Diese Effizienz und hierbei vor allem der Faktor Zeit erleichtert es aber Angreifern, die Passwörter mittels Brute-Force-Attacken zu ermitteln214 oder sogenannte Rainbow-Tables215 zu erstellen. Aus diesem Grund wurde bcrypt216 speziell für das Hashing von Kennwörtern entwickelt und verfügt über einen je nach verfügbarer Serverhardware einstellbaren Kosten- bzw. Zeitfaktor. Mit diesem Faktor kann später auch der Aufwand erhöht werden, wenn sich die Leistungsfähigkeit der Computer weiterentwickelt hat.217 Bei bcrypt handelt es sich jedoch um keinen eigenen Hashing-Algorithmus, sondern um einen Algorithmus welcher intern Teile des Verschlüsselungs-Algorithmus Blowfish zur Erzeugung eines Einweg-Hashes verwendet.218 Legen Sie Kennwörter stets als Hash in einer Datenbank ab! Anthony Ferrara hat am 26.06.2012 ein RFC219 für eine simple Passwort-Hashing-API auf Basis von bcrypt eingereicht, welche ab PHP 5.5 im Core von PHP implementiert ist. Um bereits jetzt schon die zukünftige API einsetzen zu können, gibt es die Composer-Bibliothek password_compat.220 210. Siehe: de.wikipedia.org/wiki/MD5 211. Siehe: arstechnica.com/security/2013/03/how-i-became-a-password-cracker/ 212. Siehe: de.wikipedia.org/wiki/Secure_Hash_Algorithm 213. Siehe: de.wikipedia.org/wiki/SHA-2 214. Siehe: codahale.com/how-to-safely-store-a-password/ 215. Siehe: https://de.wikipedia.org/wiki/Rainbow_Table 216. Siehe: de.wikipedia.org/wiki/Bcrypt 217. Siehe »Rechenleistung«: de.wikipedia.org/wiki/Mooresches_Gesetz 218. Siehe: stackoverflow.com/questions/1561174/sha512-vs-blowfish-and-bcrypt/1561245#1561245 219. Request for Comments (dt. Bitte um Kommentare). Siehe: https://wiki.php.net/rfc/password_hash 220. Siehe: https://github.com/ircmaxell/password_compat 258 21 Eine Einführung in das Thema Sicherheit Beispiel 1 { 2 "autoload": { 3 "psr-0": { 4 "Entities": "./models/", 5 "Repositories": "./models/", 6 "Validators": "./models/" 7 } 8 }, 9 10 "require": { 11 "php": ">=5.3.7", 12 "doctrine/orm": "~2.2.2", 13 "gedmo/doctrine-extensions": "~2.3.2", 14 "webmasters/doctrine-extensions": "~2.3.7", 15 "ezyang/htmlpurifier": ">=4.5.0", 16 "ircmaxell/password-compat": "~1.0.0" 17 } 18 } Listing 21.28 composer.json (Version 2) Die Bibliothek nutzt unter der Haube die Funktion crypt .221 Diese Funktion unterstützt zwar seit PHP 5.3.0 den Blowfish-Algorithmus, doch war dieser bis PHP 5.3.7 fehlerhaft implementiert. Die Bibliothek setzt deswegen PHP 5.3.7 voraus. Achtung Beachten Sie, dass Sie die nachfolgenden Beispiele nur nachvollziehen können, wenn Sie PHP 5.5 oder mindestens PHP 5.3.7 nutzen. Beachten Sie außerdem, dass bei allen Versionen vor PHP 5.5 die Composer-Bibliothek password_compat installiert und der Composer-Autoloader eingebunden sein muss. Am einfachsten ist es somit, wenn Sie den Code in einer Aktion Ihres Controllers testen. Die API besteht aus vier Funktionen222, die ich im Folgenden kurz vorstellen möchte. Beispiel 1 2 3 4 5 6 7 8 <?php $passwort = 'test'; // Benutzereingabe $hash = password_hash($passwort, PASSWORD_DEFAULT PASSWORD_DEFAULT); var_dump var_dump($hash); die die(); ?> Listing 21.29 password_hash() 221. Siehe: pl1.php.net/manual/de/function.crypt.php 222. Siehe: blog.ircmaxell.com/2012/11/designing-api-simplified-password.html 21.6 Authentisierungssicherheit 259 Zum Erzeugen eines Hashwertes dient die Funktion password_hash() 223, welche als ersten Parameter den Klartextwert des Kennworts erhält. Als zweiten Parameter teilen wir den gewünschten Algorithmus mit. Die Konstante PASSWORD_DEFAULT steht hierbei für den aktuell als sichersten betrachteten »Algorithmus«, was derzeit bcrypt ist. Mit zukünftigen PHP-Versionen kann sich dies jedoch ändern. Wenn Sie übrigens keine automatische Anpassung des Algorithmus im Rahmen von PHP-Updates wünschen, so können Sie stattdessen die Konstante PASSWORD_BCRYPT benutzen. Beispiel 1 2 3 4 5 6 7 8 9 <?php $passwort = 'test'; // Benutzereingabe $hash = password_hash($passwort, PASSWORD_DEFAULT PASSWORD_DEFAULT); $infos = password_get_info($hash); var_dump var_dump($hash, $infos); die die(); ?> Listing 21.30 password_get_info() Betrachten wir nun die Informationen zu unserem Hash, welche wir durch die Funktion password_get_info() erhalten. Bei Erstellung dieses Kapitels wurde mir beispielsweise mitgeteilt, dass der Algorithmus bcrypt und der Kostenfaktor 10 benutzt wurden. Beide Informationen kann man auch direkt dem Beginn des Hashwertes $2y$10$ entnehmen. Das 2y steht für die seit PHP 5.3.7 verfügbare, fehlerbereinigte Implementierung von bcrypt und die 10 für den Kostenfaktor. Für den Kostenfaktor sind Werte zwischen 4 und 31 möglich. Je nach verwendeter Serverhardware sollten Sie eine Erhöhung des Wertes (> 10) erwägen. Ein Aufwand von 0,5 Sekunden für die Hashermittlung sollte laut Anthony Ferrara jedoch die obere zeitliche Grenze bilden. Wenn Sie den Code mehrfach ausführen, so sollte Ihnen auffallen, dass Sie immer einen anderen Hash erhalten (mit Ausnahme der Informationen am Beginn). Dies ist kein Verstoß gegen die Regel der identischen Ergebnisse und liegt lediglich daran, dass man der Funktion password_hash() neben dem Kostenfaktor auch noch einen zweiten Optionsparameter mitteilen kann. Hierbei handelt es sich um einen sogenannten Salt (dt. Salz).224 Ein sinnvoller Salt ist für jeden Benutzer anders und dient dazu, dass man aus zwei identischen Hashwerten in einer Datenbank nicht auf ein identisches Passwort im Klartext folgern kann.225 Ein solcher Salt muss übrigens nicht geheim sein, sondern kann zusammen mit dem Kennwort in der Datenbank abgelegt werden. 223. Siehe: pl1.php.net/password_hash 224. Siehe: de.wikipedia.org/wiki/Salt_%28Kryptologie%29 225. Siehe: crackstation.net/hashing-security.htm 260 21 Eine Einführung in das Thema Sicherheit Beispiel 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php $passwort = $optionen = 'cost' => 'salt' => ); 'test'; // Benutzereingabe array array( 12, '1234567890123456789012' // min. 22 Zeichen "./0-9A-Za-z" $hash = password_hash($passwort, PASSWORD_DEFAULT PASSWORD_DEFAULT, $optionen); $infos = password_get_info($hash); var_dump var_dump($hash, $infos); die die(); ?> Listing 21.31 password_hash() mit Optionen Wenn Sie diesen Code mehrfach ausführen lassen, so sollten drei Dinge auffallen: 1. Der Salt taucht leider nicht unter options bei password_get_info() auf. 2. Der Hash beginnt mit $2y$12$123456789012345678901 . Dies liegt daran, dass wir nun einen Kostenfaktor von 12 und einen selbst festgelegten und leicht zu erkennenden Salt (dritte Angabe im Hash) verwenden. 3. Es werden nur 21 Stellen unseres Salts im Hash angezeigt. Dies liegt daran, dass der Salt von bcrypt grundsätzlich aus 21 und einem zerquetschten Zeichen besteht.226 Gibt man weniger als 22 Zeichen an, so erhält man die PHP-Warnung Provided salt is too short . Gibt man hingegen mehr als 22 Zeichen an, so werden die überzähligen Zeichen ignoriert. Persönlich sehe ich übrigens keinen Grund, den Salt manuell anzugeben und verzichte deswegen im Weiteren darauf. Beispiel 1 2 3 4 5 6 7 8 9 10 11 12 13 <?php $passwort = 'test'; // Benutzereingabe $hash = '$2y$12$kIFAPDlY.N06arvd7IKZN' . 'O7kXop6EngNgGz19yptFJyWGOoMjqSf.'; if (password_verify($passwort, $hash)) { // Passwort ist korrekt } else { // Passwort ist falsch } ?> Listing 21.32 password_verify() 226. Siehe: www.phpgangsta.de/schoener-hashen-mit-bcrypt 21.6 Authentisierungssicherheit 261 Um ein Kennwort im Klartext mit einem Hash zu vergleichen, benötigen wir die Funktion password_verify() . Diese Funktion benötigt lediglich die zu verifizierende Passworteingabe und den Hash als Parameter, da die verwendeten Optionen ja anhand des Hashbeginns ersichtlich sind. Beispiel 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php $passwort = 'test'; // Benutzereingabe $optionen = array array( 'cost' => 15 ); $hash = '$2y$12$kIFAPDlY.N06arvd7IKZN' . 'O7kXop6EngNgGz19yptFJyWGOoMjqSf.'; if (password_verify($passwort, $hash)) { // Kostenfaktor wurde von 12 auf 15 erhoeht if (password_needs_rehash($hash, PASSWORD_DEFAULT PASSWORD_DEFAULT, $optionen)) { // Neuen Hash mit dem aktuellen Kostenfaktor ermitteln $hash = password_hash($passwort, PASSWORD_DEFAULT PASSWORD_DEFAULT, $optionen); /* Hier fehlt die Aktualisierung des Hashwertes in der DB */ } } ?> Listing 21.33 password_needs_rehash() Persönlich neige ich dazu, für den Kostenfaktor wegen der Abhängigkeit von der verwendeten Hardware einen eigenen Wert und als Algorithmus PASSWORD_DEFAULT anzugeben. Dies verhindert zwar eine automatische Nutzung eines im Rahmen von PHP-Updates erhöhten Standard-Faktors und legt diese Anpassung227 in die Hand des Entwicklers, ermöglicht aber während der Nutzung von bcrypt einen besser geschützten Hashwert. Das obige Beispiel verdeutlich, wie im Rahmen einer Loginprüfung mittels der Funktion password_needs_rehash() ermittelt werden kann, ob ein Hash nach einer Parameteranpassung in der Datenbank aktualisiert werden muss. Wenn Sie den Standard-Algorithmus verwenden, sollten Sie unbedingt berücksichtigen, dass der Hash von bcrypt zwar nur 60 Zeichen benötigt, dies aber nicht zwingend für zukünftige Alternativen gilt. Erlauben Sie deshalb jetzt schon 255 Zeichen für den Hash in der Datenbank. 227. Eine erneute Entscheidung bezüglich der benutzten Parameter sollte regelmäßig (beispielsweise nach jedem Wechsel der Serverhardware) gefällt werden. 262 21 Eine Einführung in das Thema Sicherheit 21.6.3 Benutzernamen und Kennungen Wir haben nun also gelernt, wie wir relativ sichere Kenwörter erzwingen und diese mit der Passwort-Hashing-API verwenden. Ein Login ist jedoch um ein vielfaches sicherer, wenn er aus zwei schwer zu ermittelnden Komponenten besteht. Schenken Sie einem Angreifer also nicht einfach eine dieser Komponenten, indem Sie den Benutzernamen (engl. username) öffentlich anzeigen. Sollte dies unvermeidbar sein, so sollten Sie Ihre Benutzer -Entity um ein Attribut displayname erweitern, welches Sie dann für die öffentliche Anzeige (beispielsweise »Wer ist online«) benutzen. Wenn Sie in einer Anwendung eine Loginprüfung umsetzen, so sollten erfolglose Anmeldeversuche immer nur mit einer kurzen Fehlermeldung ohne Angabe von Einzelheiten abgelehnt werden. Insbesondere darf in einem solchen Fall nicht erkennbar sein, ob der eingegebene Benutzername oder das Kennwort (oder beides) falsch ist. Unterlassen Sie die Umsetzung dieser Empfehlung, so kann ein Angreifer mit einem Wörterbuchangriff zunächst gültige Benutzernamen ermitteln und hat somit schon die halbe Miete. 21.6.4 Browserfeatures Seit etlichen Jahren verfügen die bekannten Browser (IE, Firefox, Chrome, Safari und Opera) über Features, die den Benutzer beim Besuch von Websites unterstützen sollen. Doch auch aus diesen eigentlich hilfreichen Features können sich schnell Probleme für uns als Anwendungsentwickler ergeben. Ich möchte im Folgenden zwei dieser Features am Beispiel des Browsers Firefox vorstellen und Ihnen zeigen, wie Sie diese zu Lasten des Benutzerkomforts deaktiveren können. Formular-Autovervollständigung Firefox merkt sich, welche Daten man in einzeilige Textfelder eingegeben hat. Nach einer solchen Eingabe wird diese beim nächsten Besuch der Webseite wieder verfügbar sein und nach der Angabe des ersten Buchstabens als Vorschlag angezeigt werden.228 Um dies zu ermöglichen, reagiert der Browser auf eine Kombination von URL und Eingabefeldnamen. Dieses Feature hört sich zunächst einmal toll an. Doch was ist, wenn Ihr Benutzer einmal kurz seinen Rechner verlassen muss und in dieser Zeitspanne ein Unbefugter Zugriff auf den Browser hat? Der Angreifer bräuchte lediglich das erste Zeichen ausprobieren und bekäme sofort den kompletten Benutzernamen serviert. 228. Siehe: https://support.mozilla.org/de/kb/Formular-Autovervollstaendigung 21.6 Authentisierungssicherheit 263 Beispiel 1 <form 2 action="index.php?aktion=login" method="post" 3 > 4 <label for="username">Benutzername</label> 5 <input 6 type="text" name="username" id="username" 7 value="" 8 /> 9 10 <label for="password">Kennwort</label> 11 <input 12 type="password" name="password" id="password" 13 value="" 14 /> 15 16 <button type="submit">Anmelden</button> 17 </form> Listing 21.34 Loginformular (Version 1) Das Kennwort ist hiervon übrigens nicht betroffen, da es für password -Felder keine Vorschlagsfunktionalität gibt. Trotzdem besteht auch eine Gefahr für dieses. Erst vor kurzem hatte ich in meiner eigenen Familie den Fall, dass eine Login-Eingabe zu schnell erfolgte und dadurch die Tabulator-Taste nicht korrekt aktiviert wurde. Hierdurch landete eine kombinierte Eingabe von Benutzername und Passwort in den automatischen Vorschlägen für den Benutzernamen. Dank der anderen Vorschläge wäre es dann ein Leichtes gewesen, den Beginn des Kennworts zu identifizieren. Passwort-Manager Der Passwort-Manager von Firefox speichert die Benutzernamen und Kennwörter, die man auf Webseiten verwendet, und fügt sie beim nächsten Besuch automatisch in das Login-Formular ein. Hierbei kann man als Benutzer entscheiden, ob eine Speicherung erfolgen soll oder nicht (bzw. nie).229 Dieses Feature kann gleich in mehreren Szenarien nachteilig sein: 1. Ein Cross-Site-Scripting-Angriff auf das Login-Formular: Ein Angreifer muss nicht erst auf die Eingabe der Daten durch den Benutzer warten, sondern kann diese sofort übermitteln. 2. Ein Unbefugter mit direktem Browser-Zugriff: Der Angreifer sieht sofort den Benutzernamen und kann bei ausreichend Zeit das Passwort ändern. Eine Anwendung sollte deswegen vor einer solchen Änderung als zusätzliche Verteidigungslinie immer nach dem bisherigen Kennwort fragen. Wenn der Angreifer jedoch genug Zeit hat, könnte er auch eine Browser-Erweiterung installieren, um vorübergehend die Sternchen im Passwortfeld zu demaskieren.230 229. Siehe: https://support.mozilla.org/de/kb/passworter-verwalten-speichern-loeschen-aendern 264 21 Eine Einführung in das Thema Sicherheit 3. Ein Unbefugter, welcher die Festplatte (bzw. den kompletten Rechner) entwendet oder Daten kopiert: Obwohl der Passwort-Manager die Zugangsdaten in einem verschlüsselten Format auf der Festplatte speichert, kann sich ein Angreifer mit ausreichend Zeit Zugriff auf die Daten im Klartext verschaffen. Diese Klartextwerte benötigt nämlich auch der Browser für die Zuweisung in die jeweiligen Felder des Login-Formulars, weswegen eine Speicherung als Hash nicht möglich ist. Ich hoffe, Sie verwenden zumindest ein Masterkennwort (sofern Sie selbst einen Passwort-Manager verwenden), um so die benötigte Angriffszeit zu erhöhen.231 Um diese Probleme zu vermeiden, wurde seit dem Internet Explorer 5 (Firefox ab 0.9.4) die Unterstützung des nicht standardisierten Attributs autocomplete in die Browser implementiert. Es wird in den aktuellen Versionen von allen bekannten Browsern unterstützt und ist Teil des neuen HTML5-Standards.232 Beispiel 1 <form 2 action="index.php?aktion=login" method="post" 3 autocomplete="off" 4 > 5 <label for="username">Benutzername</label> 6 <input 7 type="text" name="username" id="username" 8 value="" 9 /> 10 11 <label for="password">Kennwort</label> 12 <input 13 type="password" name="password" id="password" 14 value="" 15 /> 16 17 <button type="submit">Anmelden</button> 18 </form> Listing 21.35 Loginformular (Version 2) Das Attribut kann im öffnenden form -Tag ergänzt werden und deaktiviert so die Autovervollständigung für alle Felder dieses Formulars. 230. Siehe: https://addons.mozilla.org/en-us/firefox/addon/unhide-passwords/ 231. Siehe auch die Maßnahme M 4.306: https://www.bsi.bund.de/DE/Themen/ITGrundschutz/ITGrundschutzKataloge/itgrundschutzkataloge_node.html 232. Siehe: https://developer.mozilla.org/en-US/docs/Mozilla/How_to_Turn_Off_Form_Autocompletion 21.6 Authentisierungssicherheit 265 Beispiel 1 <form 2 action="index.php?aktion=login" method="post" 3 > 4 <label for="username">Benutzername</label> 5 <input 6 type="text" name="username" id="username" 7 autocomplete="off" 8 value="" 9 /> 10 11 <label for="password">Kennwort</label> 12 <input 13 type="password" name="password" id="password" 14 autocomplete="off" 15 value="" 16 /> 17 18 <button type="submit">Anmelden</button> 19 </form> Listing 21.36 Loginformular (Version 2b) Alternativ ist auch der Einsatz in einzelnen Formularfeldern möglich, was ich persönlich bevorzuge.233 Das Attribut autocomplete funktioniert mit folgenden Typen des input-Tags: text, password, search, url, tel, email, range, color und den Datumstypen234. Wichtig ist jedoch, dass das Attribut lediglich als Empfehlung an den Browser angesehen werden kann. Da es sich um eine Angabe im HTML-Quelltext einer Webseite handelt, kann ein Benutzer diese »Gängelung durch den Seitenbetreiber«235 mittels Browser-Erweiterung236 umgehen. Sollten Sie das Attribut aus irgendeinem Grund nicht einsetzen wollen, so ist eine dynamische Änderung des name -Attributs der Formularfelder zumindest hilfreich bei den ersten beiden Szenarien. Um dies zu erreichen, könnten Sie beispielsweise mit PHP bei jedem Formularaufruf ein (zufälliges) Prefix festlegen, in der Session speichern und dieses vor jedem Wert im name -Attribut der betreffenden Formularfelder einfügen.237 233. Siehe: aktuell.de.selfhtml.org/artikel/html/autocomplete/ 234. Siehe: www.torbenleuschner.de/blog/601/html5-formulare-neue-input-types-attribute-und-mehr/ 235. Siehe: www.knetfeder.de/linux/index.php?id=126 236. Siehe: https://addons.mozilla.org/de/firefox/addon/remember-passwords/ 237. Alternativ kann das Prefix auch mittels eines versteckten Formularfelds (natürlich ohne Prefix im name -Attribut) zu den restlichen Formulardaten ergänzt werden. 266 21 Eine Einführung in das Thema Sicherheit 21.7 Ein paar grundsätzliche Hinweise In den bisherigen Kapiteln dieser Lektion bin ich relativ ausführlich auf einzelne Themen eingegangen. Im Folgenden möchte ich kurz ein paar ergänzende Themenbereiche anreißen, damit Sie auch diese auf dem Radar haben. 21.7.1 Datenminimierung Schafft es trotz aller bisherigen Maßnahmen ein Angreifer, sich Zugriff auf die Datenbank einer Anwendung zu verschaffen, so ist eine Schuld des Betreibers unvermeidlich. Aber auch sonst sollten Sie die Daten Ihrer Benutzer nicht einfach offen herumliegen lassen, jeder E-Mail-Harvester238 und »Social Bot«239 würde sich hierüber nämlich tierisch freuen. Beachten Sie bei der Umsetzung einer Web-Anwendung deswegen unbedingt ein paar einfache Regeln: 1. Speichern Sie immer nur die Daten, die für den Betrieb Ihrer Anwendung zwingend nötig sind. 2. Wenn Besucher sich Profile anderer Benutzer ansehen dürfen, so sollte dies nur eingeloggten Benutzern möglich sein. 3. Lassen Sie jeden Benutzer über sogenannte Privatsphären-Einstellungen selbst entscheiden, welche Informationen (beispielsweise im Benutzerprofil) öffentlich zugänglich sind. 4. Ist die Anzeige einer sensitiven Information unvermeidlich, so sollte möglichst nur ein Teil dieser Information angezeigt werden. Amazon zeigt in der abschließenden Übersicht eines Bestellvorgangs beispielsweise nur die letzten zwei Stellen einer Kontonummer an. Dies entspricht weniger als 30% der Gesamtinformation und ist ein guter Richtwert. 21.7.2 Missbrauch versteckter Webinterfaces Ein »verstecktes« Webinterface ist ein Teil einer Anwendung (z.B. ein Admin-Interface), welches ein Angreifer nutzen könnte, indem er ein Formular nachbaut bzw. manipuliert oder einen Request manuell auslöst. Admin-Interface - Variante 1 Stellen Sie sich beispielsweise vor, dass in unserer Seminarverwaltung fortan neue Benutzer nur noch von einem bereits eingeloggten Benutzer angelegt werden dürfen. Wie könnte man dies lösen? Es gibt zwei gebräuchliche Lösungsvarianten: 238. Siehe: de.wikipedia.org/wiki/E-Mail-Harvester 239. Siehe: t3n.de/news/facebook-daten-sammeln-leicht-gemacht-bots-greifen-339946/ 21.7 Ein paar grundsätzliche Hinweise 267 1. Eine if-Abfrage im Partial _navi.tpl.php fragt ab, ob ein Benutzer in der Session als eingeloggt markiert ist und zeigt nur dann den entsprechenden Menüpunkt an. 2. Im Case add_benutzer befindet sich direkt zu Beginn eine ähnliche if-Abfrage. Ist der Benutzer nicht eingeloggt, so wird er mittels der Funktion redirect() zum Login-Case umgeleitet. Doch welche dieser beiden Lösungsvarianten sollte man wählen? Betrachten wir zunächst die erste Variante. Der Menüpunkt wird zwar nicht angezeigt, doch nichts hindert einen Angreifer daran, den gültigen Namen einer Aktion einfach zu raten. Er könnte also ähnlich wie in Abschnitt 21.4 den Wert von aktion manipulieren und hätte Zugriff auf die Aktion. Er bräuchte hierfür lediglich Rückschlüsse über den Aufbau unseres Admin-Interfaces zu ziehen. Trotzdem sehe ich diese Variante leider allzu oft im Rahmen meiner Tätigkeit als Tutor. Betrachten wir nun die zweite Lösungsvariante. Der Menüpunkt ist nicht versteckt und es sind keine Rückschlüsse über unser Admin-Interface nötig, ein Klick genügt. Der Angreifer wird jedoch durch die Header-Umleitung sofort aus dem Case geworfen und ein Missbrauch wird so verhindert. Allerdings ist diese Vorgehensweise aus UsabilitySicht ziemlich unschön, da auch unsere regulären Besucher erst nach dem Klick bemerken, dass sie keinen Zugriff auf den Menüpunkt haben. Zum Schutz eines Admin-Interfaces sollte immer das Ausblenden von Bedienelementen im View mit einer Header-Umleitung im Controller kombiniert werden. Admin-Interface - Variante 2 Kommen wir nun zu einem anderen Beispiel. Anstatt einen kompletten Menüpunkt zu verbieten, kommt es häufig auch vor, dass ein Benutzer bestimmte Attribute nicht ändern können soll. Würden wir dies mit dem ersten Ansatz lösen, so würden wir mit einer if-Abfrage die Anzeige der entsprechenden Formularelemente verhindern. Beispiel 1 <?php 2 3 // gekuerztes Beispiel 4 5 case 'add_benutzer': 6 $benutzer = new Benutzer Benutzer(); 7 $anreden = get_anreden(); 8 9 if ($_POST $_POST) { 10 ArrayMapper ArrayMapper::setEntity($benutzer)->setData($_POST $_POST); 11 12 $validator = $em->getValidator($benutzer); 13 if ($validator->isValid()) { 14 $em->persist($benutzer); 15 $em->flush flush(); 268 21 Eine Einführung in das Thema Sicherheit 16 17 set_message('Benutzer wurde gespeichert.'); 18 redirect('index.php'); 19 } 20 21 $errors = $validator->getErrors(); 22 } 23 24 $view = 'edit_benutzer'; 25 break break; 26 27 // gekuerztes Beispiel 28 29 ?> Listing 21.37 index.php (Version 3) Auch in diesem Fall wäre diese Variante alleine nicht ausreichend, da in Zeile 10 die Methode ArrayMapper#setData() mit dem Parameter $_POST aufgerufen wird. Hierdurch wird jeder im Array vorhandene Schlüssel zum Aufruf des passenden Setters verwendet. Ein Angreifer muss also nur das Formular manipulieren und schon kann er den Inhalt von Attributen manipulieren. Soll ein Benutzer nur eingeschränkten Einfluss auf die Attribute eines Objekts haben, so dürfen wir nicht die (komplette) Superglobale $_POST zur Befüllung verwenden. Beispiel 1 <?php 2 3 // gekuerztes Beispiel 4 5 case 'add_benutzer': 6 $benutzer = new Benutzer Benutzer(); 7 $anreden = get_anreden(); 8 9 if ($_POST $_POST) { 10 $eingaben = array array( 11 'anrede' => $_POST $_POST['anrede'], 12 'vorname' => $_POST $_POST['vorname'], 13 'name' => $_POST $_POST['name'], 14 'email' => $_POST $_POST['email'], 15 'passwort' => $_POST $_POST['passwort'], 16 ); 17 ArrayMapper ArrayMapper::setEntity($benutzer)->setData($eingaben); 18 19 $validator = $em->getValidator($benutzer); 20 if ($validator->isValid()) { 21 $em->persist($benutzer); 22 $em->flush flush(); 23 24 set_message('Benutzer wurde gespeichert.'); 25 redirect('index.php'); 26 } 27 28 $errors = $validator->getErrors(); 21.7 Ein paar grundsätzliche Hinweise 269 29 } 30 31 $view = 'edit_benutzer'; 32 break break; 33 34 // gekuerztes Beispiel 35 36 ?> Listing 21.38 index.php (Version 3b) Wir nutzen zur Lösung eine Whitelist-Variante, bei der lediglich die erwünschten Angaben aus $_POST in das neue Array $eingaben übernommen werden. Das neue Array dient dann zur Befüllung des Objekts und unsere Sicherheitslücke ist geschlossen. 21.7.3 HTTPS ist kein Allheilmittel Secure Sockets Layer (SSL) ist ein hybrides Verschlüsselungsprotokoll240 zur Datenübertragung im Internet. Gleichzeitig kann ein Benutzer anhand eines sogenannten Zertifikats241, die Identität einer Website verifizieren. Ab Version 3.0 wurde das SSLProtokoll unter dem Namen TLS (Transport Layer Security)242 weiterentwickelt. TLS 1.0 meldet sich übrigens im Header noch als Version SSL 3.1. TLS-Verschlüsselung wird heute vor allem bei HTTPS (Hypertext Transfer Protocol Secure)243 eingesetzt. Hierbei handelt es sich um ein Kommunikationsprotokoll zur abhörsicheren Datenübertragung zwischen Webserver und Browser. Ohne eine solche Verschlüsselung sind für jeden Angreifer, der Zugriff auf den Datenverkehr hat, alle übertragenen Daten im Klartext lesbar. Sobald sensitive Daten zwischen Browser und Webserver übertragen werden (beispielsweise Zahlungsinformationen) sollte immer HTTPS eingesetzt werden. Wenn es um sensitive Daten aus den Bereichen Finanzen und Gesundheit geht, so wird die Verwendung von HTTPS von den Benutzern grundsätzlich erwartet und das Fehlen des »goldenen Schlosses« nicht akzeptiert. HTTPS ist jedoch kein Allheilmittel, da die benutzte Version von SSL/TLS von der Konfiguration des Webservers und dem verwendeten Browser bzw. Betriebssystem abhängig ist. Je älter diese Version ist, desto größer ist die Wahrscheinlichkeit, dass es inzwischen bekannte Angriffsmöglichkeiten gibt. So wurden beispielsweise Anfang 2013 Schwachstellen bekannt, die SSL 3.0, TLS 1.1 und 1.2 244 bzw. TLS 1.0 und 1.1 245 angreifbar machen. Zudem schützt HTTPS lediglich die Übertragung der Daten. Sind 240. Siehe: de.wikipedia.org/wiki/Hybride_Verschl%C3%BCsselung 241. Siehe: de.wikipedia.org/wiki/Digitales_Zertifikat 242. Siehe: de.wikipedia.org/wiki/Transport_Layer_Security 243. Siehe: de.wikipedia.org/wiki/Hypertext_Transfer_Protocol_Secure 244. Siehe: www.golem.de/news/lucky-thirteen-sicherheitsluecke-in-tls-dtls-und-ssl-1302-97358.html 245. Siehe: www.golem.de/news/ssl-tls-schwaechen-in-rc4-ausnutzbar-1303-98164.html 270 21 Eine Einführung in das Thema Sicherheit die Daten erstmal auf dem Webserver bzw. Browser angekommen, so können sie dort dennoch eine schädliche Wirkung entfalten. 21.8 Letzte Worte In den komplexen »Öko-Systemen«, in welchen sich Web-Anwendungen heutzutage bewegen, reicht eine alleinige Absicherung der Anwendung nicht aus. Auch die restlichen Software-Komponenten, die Hardware (Server- und Netzwerkkomponenten) und die verwendeten Räumlichkeiten sollten neben den eigentlichen Benutzern Bestandteil eines umfassenden Sicherheitskonzeptes sein. Schließlich könnte auch ein Angreifer, der über einen dieser Wege Zugriff erlangt, für sie und Ihre Anwendung gefährlich sein. Bedenken Sie zudem, dass Sie im Falle eines Falles immer ein aktuelles Backup Ihrer Anwendung zur Hand haben sollten. Dies betrifft sowohl die Dateien der Anwendung (inklusive möglicherweise vorhandenen Benutzer-Uploads) als auch die Inhalte der Datenbank. Es nützt übrigens nicht viel, wenn eine solche Datensicherung auf dem gleichen Gerät wie die eigentliche Anwendung gespeichert wird und somit beides von einem Hardwaredefekt betroffen wäre. Am besten ist es sogar, wenn Ihre Sicherungen an einem komplett anderen Ort aufbewahrt werden, damit ein Feuer nur eines von beidem vernichten kann. Haben Sie auch immer einen Blick auf die Neuigkeiten in der PHP-Welt. So sind Sie frühzeitig genug informiert, wenn es gravierende Änderungen am Öko-System Ihrer Anwendung geben sollte und können ohne Zeitdruck die nötigen Anpassungen planen und umsetzen.246 Persönlich lese ich beispielsweise täglich den Newsticker von <?PHPDeveloper.org und dem deutschsprachigen PHPmagazin. ➤ phpdeveloper.org/archive/ ➤ phpmagazin.de/news/ Nehmen Sie sich abschließend einen Satz aus dem fliegenden Klassenzimmer von Erich Kästner zu Herzen: »An jedem Unfug, der passiert, sind nicht nur die Schuld, die ihn begehen, sondern auch die, die ihn nicht verhindern«.247 Versuchen Sie also immer das Möglichste, um die Integrität Ihrer Anwendung nicht zu gefährden. Testen Sie Ihr Wissen 1. Welche Daten eines Benutzers sind sicher? 246. Nichts ist schlimmer, als wenn eine Anwendung nach dem Update einer Komponente für längere Zeit ausfällt. 247. Siehe: de.wikipedia.org/wiki/Das_fliegende_Klassenzimmer 21.8 Letzte Worte 271 2. Ist eine absolute Risiko-Eleminierung bei einer Web-Anwendung möglich? 3. Welche fünf Tugenden sollte sich ein sicherheitsbewusster Entwickler angewöhnen? 4. Unter welcher Prämisse sollte man heutzutage Anwendungen entwickeln? 5. Wieso ist ein Whitelist-Ansatz einer Blacklist meist überlegen? 6. Was ist die primäre Maßnahme zum Schutz einer Anwendung gegen SQL-Injections? 7. Was ist der Unterschied zwischen persistentem und reflektiertem Cross-Site-Scripting? 8. Welche primären Möglichkeiten hat ein Angreifer, um an das Kennwort eines Benutzers zu gelangen? 9. Was ist der wichtigste Unterschied zwischen Verschlüsselung und Hashing? 10. Reicht HTTPS als alleinige Verteidigungslinie für eine Web-Anwendung? Aufgaben zur Selbstkontrolle Aufgabe 7: Die Klasse Webmasters\Doctrine\ORM\Util\ArrayCollection enthält eine Methode getDuplicates. Mit dieser Methode kann man prüfen, ob eine ArrayCollection doppelte Objekte enthält. Erweitern Sie den BenutzerValidator um eine Methode validateSeminartermine, welche bei doppelten Terminen einen Validierungsfehler meldet. Optionale Aufgaben Aufgabe 8: Das Begleitmaterial enthält einen »finalen« Stand der Seminarverwaltung. Dieser verwendet Passwort-Hashes und demonstriert eine funktionierende Login-Aktion. Installieren Sie diesen Stand, welcher die neue Datenbank seminarverwaltung_final verwenden soll, auf Ihrem Entwicklungssystem.248 Vollziehen Sie die Änderungen an den Dateien index.php, includes/funktionen.inc.php, models/Entities/Benutzer.php und views/edit_benutzer.tpl.php nach. 248. Denken Sie daran, die Datenbanktabellen durch einen Aufruf der setup.php zu erstellen. 272 22 22 Anhang: Weiterführende Informationen Anhang: Weiterführende Informationen In dieser Lektion lernen Sie: ➤ interessante, weiterführende Quellen zum Thema PHP kennen. 22.1 Einführung Nachdem Sie nun wissen, was Sie alles in diesem Lernheft gelernt haben, muss ich Ihnen leider sagen, dass das noch lange nicht alles war, was es zum Thema PHP zu wissen gibt.249 Deshalb habe ich Ihnen in der letzten Lektion einige interessante Webseiten und Bücher zusammengestellt, über die Sie weiterführende Informationen rund um PHP erhalten können. 22.2 Weblinks 22.2.1 www.php.net Die offizielle Seite der PHP-Entwickler bietet nicht nur eine Übersicht über alle PHPFunktionen, sondern auch ein Handbuch in deutscher Sprache. Dieses Handbuch versteht sich selbst eher als Referenz, denn als Lernheft. Folglich sind auch die Erklärungen und Beispiele effizient, aber eher knapp gehalten. Die Seite eignet sich sehr gut, um in Ihrem PHP-Wissen gezielt Lücken zu füllen. Als entspannte Abend-Lektüre würde ich sie weniger empfehlen. 22.2.2 www.phpdeveloper.org <?PHPDeveloper.org versteht sich als Kommunikationsplattform rund um PHP. Sie finden dort eine Übersicht von Neuigkeiten aus der PHP-Welt, Informationen über Konferenzen, Jobangebote, aktuelle Diskussionen und eine Übersicht der interessantesten Fachartikel der letzten Zeit. 249. Sonst wäre dieses Skript mehrere tausend Seiten dick und das Wort »alles« wäre immer noch gelogen. 22.3 Buchtipps 273 22.2.3 devzone.zend.com In der Entwickler-Community von ZEND, den Entwicklern von PHP, finden Sie interessante und oft sehr aktuelle, aber doch recht fortgeschrittene Artikel (engl. Tutorials) zum Thema PHP. Die Seite wird für Sie umso wichtiger werden, je mehr Sie über PHP wissen. 22.3 Buchtipps Das Lehrwissen der Lektion zum Thema Sicherheit basiert auf den nachfolgenden Quellen. Die Lektion bietet jedoch aus Platzgründen lediglich einen Querschnitt der dortigen Themen. Christopher Kunz, Stefan Esser, Peter Prochaska: PHP-Sicherheit. PHP/MySQL-Webanwendungen sicher programmieren. 2. aktualisierte und überarbeitete Auflage. dpunkt.verlag 2007 (ISBN-13 978-3898644501) Tobias Wassermann: Sichere Webanwendungen mit PHP. mitp 2007 (ISBN-13 978-3826617546) Chris Snyder, Thomas Myer, Michael Southwell: Pro PHP Security. From Application Security Principles to the Implementation of XSS Defenses. Second Edition. Apress 2010 (ISBN-13 978-1430233183) Ich kann Ihnen nur ans Herz legen: Verlassen Sie sich nicht nur auf meine Kurzübersicht, sondern lesen Sie die ausführlicheren Originale (auch wenn sie oftmals etwas veraltet sind)! 274 Lösungen Lösungen Lektion 3: Testen Sie Ihr Wissen 1. Was ist ein Objekt? Ein Objekt repräsentiert etwas aus der realen Welt im PHP-Code. 2. Was ist eine Klasse? Eine Klasse bestimmt den Typ eines Objektes. Sie stellt eine Art Bauplan dar. 3. Was sind Attribute? Attribute sind die Eigenschaften, die ein Objekt hat. 4. Wie können Sie in PHP aus einer Klasse ein Objekt erzeugen? Indem Sie den Operator new und den Namen der Klasse verwenden und das Ergebnis einer Variablen zuweisen: $hans = new Person(); 5. Auf was bezieht sich die Variable $this ? Auf das aktuelle Objekt, in dem Sie sich gerade befinden. 6. Was unterscheidet Methoden von Funktionen? Methoden sind an Klassen gebunden und können über $this auf die Attribute ihres Objektes zugreifen. Ansonsten verhalten sie sich wie Funktionen. 7. Wie können Sie eine Methode eines Objektes aufrufen? $objekt->nameDerMethode(); Lektion 4: Testen Sie Ihr Wissen 1. Was unterscheidet Getter- und Setter-Methoden von normalen Methoden? Es sind ganz normale Methoden, also nichts. 2. Warum ist es wartbarer, Attribute über Methoden zu verändern als direkt? 22.3 Buchtipps 275 Da Sie auf diese Weise in das Ändern des Attributs eingreifen können, um den Wert zu prüfen oder zu ändern, ohne dass man davon außen etwas bemerkt. 3. Wie können Sie auf ein Attribut nur lesenden Zugriff erlauben? Sie markieren das Attribut als private und schreiben nur einen Getter. Lektion 5: Testen Sie Ihr Wissen 1. Wie können Sie eine Methode desselben Objektes von einer anderen Methode aus aufrufen? $this->methodenName() 2. Können Sie Objekte in der Session speichern? Ja! 3. Was testet der Operator instanceof ? Er testet, ob eine Variable ein Objekt enthält, das aus einer bestimmten Klasse erzeugt wurde, also z.B. $test instanceof TestKlasse überprüft, ob in $test eine Instanz von TestKlasse gespeichert ist. Lektion 6: Testen Sie Ihr Wissen 1. Was unterscheidet virtuelle Attribute von echten Attributen? Virtuelle Attribute sind nicht ausdrücklich in der Klasse definiert. Sie verfügen aber über Getter und Setter, die deren Existenz vorgaukeln. 2. Unter welchen Umständen sind virtuelle Attribute nützlich? Wenn Sie mehrere Attribute zur Verfügung stellen wollen, die sich aber aus einer Datenquelle ableiten. 3. Was ist bei Settern von virtuellen Attributen zu beachten? Diese Setter verändern nicht die virtuellen, sondern echte Attribute, die selbst ebenfalls Setter haben. Sie müssen dafür sorgen, dass sich diese Setter nicht in die Quere kommen. 276 Lösungen Lektion 7: Testen Sie Ihr Wissen 1. Was versteht man unter einer magic method? Magische Methoden sind Methoden, die von PHP automatisch aufgerufen werden, wenn ein vordefiniertes Ereignis eintritt. Sie haben festgelegte Namen. 2. Wann wird die Methode __toString() eines Objektes aufgerufen? Sobald Sie ein Objekt als String behandeln, also z.B. versuchen, es mit echo auszugeben. 3. Wann wird die Methode __construct() eines Objektes aufgerufen? Jedes Mal, wenn Sie ein neues Objekt erzeugen. 4. Benötigt jede Klasse eine Konstruktor-Methode? Es ist keine zwingende Voraussetzung. Wenn es nichts für einen Konstruktor zu tun gibt, können Sie ihn selbstverständlich weglassen. 5. Sie haben ein Person -Objekt in der Variable $person und möchten dessen Inhalt als Text verwenden, was müssen Sie aufrufen? echo $person oder $person->__toString() Lektion 8: Testen Sie Ihr Wissen 1. Wie können Sie ein Objekt in einem anderen Objekt ablegen? Indem das innere Objekt als Attribut des anderen Objekts abgelegt wird. 2. Wie können Sie auf die Attribute des innen abgelegten Objekts zugreifen? Über virtuelle Attribute, die auf die Attribute des inneren Objekts zugreifen. 3. Warum sollten Sie bevorzugt ganze Objekte als Parameter an Methoden übergeben? Es macht Ihren Code robuster gegen Änderungen, da Sie weniger Informationen über Ihre Klassen nach außen dringen lassen. 22.3 Buchtipps 277 Lektion 9: Testen Sie Ihr Wissen 1. Auf welche Sprachelemente sollte sich der PHP-Code in Templates beschränken? Nach Möglichkeit auf echo , Schleifen und if-else -Anweisungen. 2. Warum sollten Sie so wenig PHP wie möglich in Templates verwenden? Um so weit wie möglich HTML von PHP-Code zu trennen. Auf diese Weise ist der PHP-Teil übersichtlicher und der HTML-Teil kann leichter von Webdesignern ohne PHP-Kenntnisse angepasst werden. 3. Welche Aufgabe hat der Controller? Der Controller entscheidet, welche Aktion beim Aufruf der PHP-Seite ausgeführt werden soll. 4. Wie kann der Controller entscheiden, welche Aktion er ausführen soll? Über eine Variable, die beim Aufruf der Seite mit übergeben wird, beispielsweise $_REQUEST['aktion'] . 5. Warum sollte es eine Standard-Aktion geben? Falls kein Parameter übergeben wurde, muss trotzdem eine Aktion ausgeführt werden. Lektion 10: Testen Sie Ihr Wissen 1. Wie viele Klassen repräsentieren normalerweise eine Datenbank-Tabelle, wenn Sie Table-Data-Gateway verwenden? Nur eine. 2. Wie viele Objekte erhalten Sie in PHP, wenn Sie mit dem Table-Data-Gateway 30 Datensätze aus der DB auslesen? Nur ein Objekt, das die gesamte Tabelle repräsentiert. Die Datensätze existieren normalerweise als Arrays. 3. Wie viele Klassen repräsentieren normalerweise eine Datenbank-Tabelle, wenn Sie Active Record verwenden? Nur eine. 278 Lösungen 4. Wie viele Objekte erhalten Sie in PHP, wenn Sie mit Active Record 30 Datensätze aus der DB auslesen? 30 Objekte, eines für jeden Datensatz. 5. Wie viele Klassen repräsentieren normalerweise eine Datenbank-Tabelle, wenn Sie Data Mapper verwenden? Zwei Klassen, eine für die Datenhaltung und eine für die Datenverwaltung (speichern, löschen usw.). 6. Wie viele Objekte erhalten Sie in PHP, wenn Sie mit dem Data Mapper 30 Datensätze aus der DB auslesen? 31! 30 Objekte der Datenklasse und ein Objekt des Mappers. Lektion 11: Testen Sie Ihr Wissen 1. Welche Konsolenbefehle von Composer haben Sie kennengelernt? php composer.phar -V , php composer.phar install composer.phar self-update und php 2. Was erspart Ihnen die __autoload() -Funktion? Die Funktion erspart hauptsächlich viel Schreibarbeit und zudem die vorherige Festlegung der benötigten Klassen von Drittanbietern. Lektion 12: Testen Sie Ihr Wissen 1. Nennen Sie Beispiele für Namespaces abseits der PHP-Programmierung. z.B. Dateiverzeichnisse und Telefonvorwahlen 2. Was muss (sofern wir die anderen Varianten ignorieren) jede Entity enthalten, damit sie von Doctrine als solche erkannt wird? Annotationen 3. Überlegen Sie, ob jedes Attribut Annotationen (z.B. @ORM\Column ) benötigt. Attribute, welche nicht von Doctrine in der Datenbank gespeichert werden sollen, benötigen auch keine Annotationen. Solche Attribute sind allerdings relativ selten. 22.3 Buchtipps 279 Lektion 13: Testen Sie Ihr Wissen 1. Wie nennt man die Schnittstelle zu Doctrine, die man zum Auslesen, Speichern und Löschen von Datensätzen nutzt? EntityManager 2. Es gibt eine Namenskonvention für die Variable, in der das Objekt dieser Schnittstelle liegt. Wie lautet diese? $em Lektion 14: Testen Sie Ihr Wissen 1. Reicht es, die Methode persist() aufzurufen, um ein Objekt zu speichern? Nein. Erst wenn man die Methode EntityManager::flush() aufruft, werden alle SQL-Anweisungen ausgeführt, die sich bis zu diesem Zeitpunkt angesammelt haben. Alternativ könnte man auch bis zum Ende des Requests warten, wo dann ein automatisches Flushing stattfindet. 2. Wie nennt man das Vorgehen, Aufgaben zu sammeln und am Stück abzuarbeiten? Unit of Work Lektion 15: Testen Sie Ihr Wissen 1. Was repräsentiert bei Doctrine eine Tabelle mit allen enthaltenen Datensätzen und dient gleichzeitig dazu, Datensätze nach bestimmten Kriterien auszulesen? Die Tabelle wird durch ihr EntityRepository repräsentiert, welches man mit der Methode EntityManager#getRepository() erhält. Lektion 16: Testen Sie Ihr Wissen 1. Welche zwei Möglichkeiten existieren, um komplexere Datenbank-Abfragen mit Doctrine umzusetzen? DQL und der QueryBuilder 280 Lösungen Lektion 17: Testen Sie Ihr Wissen 1. Welche Alternative(n) zur Unmenge von Datums- und Zeitfunktionen haben Sie in dieser Lektion kennengelernt? Die Klasse DateTime bzw. Webmasters\Doctrine\ORM\Util\DateTime 2. Womit kann man automatisiert ein Erstellungs- oder Aktualisierungsdatum in einem Attribut pflegen? Mit der Annotation @Gedmo\Timestampable Lektion 18: Testen Sie Ihr Wissen 1. Wie definiert man Beziehungen zwischen Doctrine-Entities? Annotationen 2. Genauer gefragt, was nutzt man, um eine 1:n-Beziehung abzubilden? Auf der 1(One)-Seite der Beziehung nutzt man die Annotation @ORM\OneToMany und auf der n-Seite benötigt man ein @ORM\ManyToOne . 3. Und was nutzt man, um eine n:m-Beziehung abzubilden? Auf beiden Seiten nutzt man eine @ORM\ManyToMany -Annotation. Diese unterscheiden sich allerdings bei ihren Attributen ( mappedBy bzw. inversedBy ). 4. Welche vier Delegator-Methoden haben Sie kennengelernt? clear , add , has und remove 5. Was ist Lazy Loading? Als Lazy Loading bezeichnet man das Nachladen von Daten bei Bedarf. Bei Doctrine wird dies durch die Nutzung sogenannter Proxy-Klassen ermöglicht. Lektion 19: Testen Sie Ihr Wissen 1. Wofür steht das Akronym CRUD? Create, Read, Update und Delete 2. Wofür steht das Akronym BREAD? 22.3 Buchtipps 281 Browse, Read, Edit, Add und Delete 3. Weswegen beschreibt BREAD die nötigen Aktionen einer MVC-Anwendung besser als CRUD? BREAD berücksichtigt den Code-Unterschied bei der Anzeige eines und mehrerer Datensätze und trennt deswegen Browse und Read in zwei verschiedene Aktionen. 4. Wodurch unterscheiden sich die Aktionen Read, Edit und Delete von den (eher allgemeinen) Aktionen Browse und Add? Read, Edit und Delete benötigen zur Ausführung die ID eines Datensatzes. Lektion 20: Testen Sie Ihr Wissen 1. In welcher Datei kann klassenbasierter Validierungscode für unsere Seminar -Klasse abgelegt werden und wo wird diese gespeichert? models/Validators/SeminarValidator.php 2. Wozu werden Repository-Klassen benutzt? In Repository-Klassen können (längere) Datenbankabfragen ausgelagert werden, wie sie beispielsweise bei der Nutzung des QueryBuilders und JOINs auftreten. Lektion 21: Testen Sie Ihr Wissen 1. Welche Daten eines Benutzers sind sicher? Die Daten sind sicher, die erst gar nicht erhoben und gespeichert werden. 2. Ist eine absolute Risiko-Eleminierung bei einer Web-Anwendung möglich? Theoretisch ja, praktisch nein. Man spricht deshalb lediglich von einem RisikoManagement. 3. Welche fünf Tugenden sollte sich ein sicherheitsbewusster Entwickler angewöhnen? 1. 2. 3. 4. 5. Nichts ist zu 100 Prozent sicher. Traue niemals den Benutzern. Benutze immer mehrere Verteidigungslinien. Wartbaren Code kann man besser absichern. Vier Augen sehen mehr als zwei. 282 Lösungen 4. Unter welcher Prämisse sollte man heutzutage Anwendungen entwickeln? »Der Feind kennt das System« 5. Wieso ist ein Whitelist-Ansatz einer Blacklist meist überlegen? Bei einer Blacklist benötigt man eine genaue Kenntnis der nicht erlaubten Angaben, bei einer Whitelist hingegen schaltet man selektiv nach und nach die erlaubten (und getesteten) Werte frei. 6. Was ist die primäre Maßnahme zum Schutz einer Anwendung gegen SQLInjections? Prepared Statements mit (benannten) Platzhaltern für alle Benutzereingaben sind der wichtigste Schutz. Ihre Nutzung ist jedoch nur möglich, sofern die Eingabe als Wert im SQL-/DQL-Statement benötigt wird. 7. Was ist der Unterschied zwischen persistentem und reflektiertem Cross-SiteScripting? Beim persistenten XSS erfolgt der Angriff auf den Benutzer bei einem normalen Aufruf der Website. Der Schadcode ist dauerhaft in die Website integriert. Beim reflektierten XSS muss die Website hingegen auf eine spezielle Art und Weise aufgerufen werden (meist über einen präparierten Link). Der Schadcode gelangt erst über diesen Aufruf in die Ausgabe der Website. 8. Welche primären Möglichkeiten hat ein Angreifer, um an das Kennwort eines Benutzers zu gelangen? Wörterbuchangriff, Brute-Force-Methode oder einfach eine Tafel Schokolade ;-) 9. Was ist der wichtigste Unterschied zwischen Verschlüsselung und Hashing? Bei einem Einweg-Hash-Algorithmus ist im Gegensatz zu einer Verschlüsselung der Vorgang nicht umkehrbar. 10. Reicht HTTPS als alleinige Verteidigungslinie für eine Web-Anwendung? Nein, HTTPS schützt lediglich die Übertragung der Daten. Diese Daten können aber immer noch Schadcode enthalten. Index CRUD 195 212 280 281 D 1 1:n-Beziehung 176 178 182 191 193 280 Data Mapper 114 115 116 128 190 278 DateInterval 169 DateTime 133 166 167 168 169 170 171 172 173 216 217 280 A Active Record 113 114 115 277 278 Defacement 241 245 Delegator 87 179 182 185 186 187 193 280 Adder-Methoden 179 dependencies 118 Autoloader 116 121 140 258 design patterns 18 B Doctrine 1 115 129 214 Doctrine 2 115 214 220 Backup 231 270 Doctrine Datentyp 166 bidirektionale Beziehung 182 Blacklist 231 237 246 271 282 Doctrine Extensions 118 139 146 169 190 214 216 221 BREAD 195 212 280 281 Doctrine Query Language 160 Brute-Force-Methode 251 257 282 Doctrine-Entity 129 136 137 146 147 205 215 C DQL 160 161 162 163 165 191 192 220 237 239 279 282 Composer 116 117 118 119 120 121 122 135 136 138 139 140 231 232 236 257 258 278 E Cross-Site Request Forgery 247 Cross-Site-Scripting 241 242 263 271 282 EntityManager 139 143 144 145 146 152 156 161 163 182 187 189 216 219 248 279 EntityRepository 156 157 158 205 217 218 219 279 J EntityValidator 215 216 217 JOIN 191 192 Entwurfsmuster 15 18 87 93 110 111 112 113 114 115 116 152 F K Kapselung 25 29 48 90 126 Kerckhoffs Maxime 226 Flash-Notices 195 KISS 29 G Konstruktor 77 78 80 81 83 84 85 89 91 148 167 171 178 185 276 Getter-Methoden 30 49 50 57 69 L H Leetspeak 252 Hash 256 257 259 260 261 264 282 Lazy Evaluation 235 Helper 150 245 247 Lazy Loading 189 190 192 193 280 Hypertext Transfer Protocol Secure 269 Law of Demeter 51 lower CamelCase 45 I instanceof 63 64 66 183 190 275 M INSERT 111 113 149 152 154 204 239 magische Methode 74 75 76 85 146 Interface 48 49 111 162 266 267 Model-View-Controller 93 Inverse Side 175 MVC 93 106 108 153 195 212 281 ISO 8601 167 N R n:m-Beziehung 175 176 183 185 186 191 193 211 280 Refactoring 225 Remote File Inclusion 236 Namespaces 126 127 128 135 136 137 278 Namenskonvention 49 144 148 158 185 215 279 O Repositories 135 159 217 218 219 241 repository 156 157 159 219 220 222 281 S Salt 259 260 objekt-relationales Mapping 110 111 Secure Sockets Layer 269 Owning Side 175 Security through Obscurity 226 P SELECT 111 156 158 159 160 162 163 165 209 220 222 239 240 Packagist 116 117 139 246 Server-Banner 228 230 Parametermanipulation 232 237 Session Riding 248 Partial 100 197 198 216 267 Setter-Methoden 48 53 55 56 57 66 274 Peer Reviewing 226 Single Responsibility Principle 27 85 96 114 Phishing 241 243 SQL-Injection 237 241 251 Prepared Statements 164 240 282 statische Methode 113 140 Proxy-Klasse 190 Syntax-Highlighting 24 98 Purifier 246 247 Q T Table-Data-Gateway 111 115 277 QueryBuilder 160 162 163 164 165 191 192 193 199 221 279 Template 98 99 100 101 102 103 104 105 108 232 236 248 Timestampable 169 170 172 174 280 Vererbung 145 146 147 190 215 219 type-hinting 65 179 183 virtuelle Attribute 68 69 70 71 275 276 U W Uniform Access Principle 69 Whitelist 232 234 236 237 240 246 247 269 271 282 Unit of Work 152 153 279 Wörterbuchangriff 251 262 282 UPDATE 111 118 136 149 170 204 239 270 278 280 Upper CamelCase 44 X Usability 225 245 267 XSS-Cleaner 246 V Variablenfunktionen 82