16 Komplexe Abfragen mit Doctrine

Werbung
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&
&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&
&id=<?php
<?php echo
$seminartermin->getId(); ?>
?>"
23
>Details</a> ]
24
[ <a
25
href="index.php?aktion=edit_seminartermin&
&id=<?php
<?php echo
$seminartermin->getId(); ?>
?>"
26
>Termin editieren</a> ]
27
[ <a
28
href="index.php?aktion=delete_seminartermin&
&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:
»
»<?php
<?php echo $daten['suchbegriff']; ?>
?>«
«
</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:
»
»<?php
<?php e($daten['suchbegriff']); ?>
?>«
«
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
Herunterladen