-
Notifications
You must be signed in to change notification settings - Fork 44
/
Copy pathdatabase.xml
530 lines (501 loc) · 21.3 KB
/
database.xml
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
<?xml version="1.0" encoding="utf-8"?>
<!-- $Revision$ -->
<!-- EN-Revision: 7204e2dbb9b484c8b67bb5ad4a93fa1369c5b317 Maintainer: hholzgra Status: ready -->
<!-- Credits: tom -->
<chapter xml:id="security.database" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Datenbank-Sicherheit</title>
<simpara>
Heutzutage sind Datenbanken die Hauptkomponenten jeder webbasierten
Anwendung, aufgrund welcher Websites verschiedene dynamische Inhalte
anbieten können. Nachdem sensible oder geheime Informationen in solch
einer Datenbank gespeichert werden können, sollten Sie deren Schutz
ernsthaft bedenken.
</simpara>
<simpara>
Um Informationen zu bekommen oder zu speichern, müssen Sie eine Verbindung
zur Datenbank herstellen, eine legitime Abfrage senden, das Ergebnis
holen, und die Verbindung schließen. Heutzutage ist die allgemein
verwendete Abfragesprache für solche Interaktionen die Structured Query
Language (SQL). Sehen Sie, wie sich ein Angreifer
<link linkend="security.database.sql-injection">an einer SQL-Abfrage zu schaffen machen</link>
kann.
</simpara>
<simpara>
Sie werden merken, dass <acronym>PHP</acronym> Ihre Datenbank nicht selbst
schützen kann. Die folgenden Abschnitte sind eine Einführung in die
Grundlagen, wie man innerhalb von <acronym>PHP</acronym>-Skripten auf
Datenbanken zugreift und diese manipuliert.
</simpara>
<simpara>
Behalten Sie diese einfache Regel im Hinterkopf: tief gestaffelte
Verteidigung. Je mehr Platz Sie den Maßnahmen zum Schutz Ihrer Datenbank
geben, desto geringer ist die Wahrscheinlichkeit, dass ein Angreifer
Erfolg hat und gespeicherte Geheiminformationen aufdeckt oder missbraucht.
Ein gutes Design des Datenbankschemas und die Anwendung wird mit Ihren
größten Befürchtungen fertig.
</simpara>
<sect1 xml:id="security.database.design">
<title>Datenbanken entwerfen</title>
<simpara>
Der erste Schritt ist immer das Erstellen der Datenbank, außer Sie
wollen eine bereits existierende von Dritten verwenden. Ist eine
Datenbank erstellt, ist sie einem Eigentümer zugewiesen, welcher das
Kommando zum Erstellen ausgeführt hat. Gewöhnlich kann nur der
Eigentümer (oder ein Superuser) alles mit den Objekten in dieser
Datenbank machen, und um anderen Benutzern die Verwendung zu erlauben,
müssen Rechte vergeben werden.
</simpara>
<simpara>
Anwendungen sollten sich mit der Datenbank nie als deren Eigentümer oder
als ein Superuser verbinden, da diese Benutzer jede beliebige Abfrage
ausführen können, um &zb; das Schema zu modifizieren (&zb; Tabellen
löschen) oder den gesamten Inhalt löschen.
</simpara>
<simpara>
Sie können verschiedene Datenbanknutzer für jeden Aspekt Ihrer Anwendung
mit sehr limitierten Rechten auf Datenbankobjekte anlegen. Nur die
wirklich benötigten Rechte sollten gewährt werden, und vermeiden Sie,
dass der gleiche Benutzer in verschiedenen Anwendungsfällen mit der
Datenbank interagieren kann. Das heißt, dass Eindringlinge, welche unter
Verwendung einer dieser Referenzen Zugriff auf Ihre Datenbank erlangt
haben, nur so viele Änderungen durchführen können, wie es Ihre Anwendung
kann.
</simpara>
</sect1>
<sect1 xml:id="security.database.connection">
<title>Mit der Datenbank verbinden</title>
<simpara>
Sie sollten die Verbindungen über SSL herstellen, um die
Client/Server-Kommunikation für eine erhöhte Sicherheit zu verschlüsseln,
oder aber auch SSH verwenden, um die Netzwerkverbindung zwischen den
Clients und dem Datenbankserver zu verschlüsseln. Wird eines davon
verwendet, dann wird es für einen Angreifer schwierig, Ihre
Datenübertragung zu überwachen und Informationen über Ihre Datenbank zu
erlangen.
</simpara>
<!--simpara>
Wenn Ihr Datenbankserver über native SSL-Unterstützung verfügt, sollten
Sie die Verwendung von <link
linkend="ref.openssl">OpenSSL-Funktionen</link> bei der Kommunikation
zwischen <acronym>PHP</acronym> und Datenbank über SSL in Betracht
ziehen.
</simpara-->
</sect1>
<sect1 xml:id="security.database.storage">
<title>Verschlüsseltes Speichermodell</title>
<simpara>
SSL/SSH schützt zwar Daten, die vom Client zum Server übertragen werden,
aber SSL/SSH schützt keine dauernd in einer Datenbank gespeicherten
Daten. SSL ist ein "auf-der-Leitung"-Protokoll.
</simpara>
<simpara>
Hat ein Angreifer direkten Zugriff auf Ihre Datenbank (unter Umgehung des
Webservers), können gespeicherte sensible Daten aufgedeckt oder
zweckentfremdet werden, außer wenn die Informationen durch die Datenbank
selbst geschützt sind. Die Daten zu verschlüsseln ist eine gute Methode,
diese Gefahr zu mildern, doch bieten nur wenige Datenbanken diese Art der
Datenverschlüsselung an.
</simpara>
<simpara>
Der einfachste Weg, dieses Problem zu umgehen ist, erst einmal Ihr
eigenes Verschlüsselungspaket zu erstellen, und dieses dann in Ihren
<acronym>PHP</acronym>-Skripten zu nutzen. <acronym>PHP</acronym> kann
Ihnen in diesem Fall mit seinen verschiedenen Erweiterungen helfen, wie
&zb; <link linkend="book.openssl">OpenSSL</link> und
<link linkend="book.sodium">Sodium</link>, welche eine große Auswahl an
Verschlüsselungsalgorithmen abdecken. Das Skript verschlüsselt die Daten
vor dem Speichern und entschlüsselt diese wieder beim Erhalt. Siehe die
Verweise für weitere Beispiele, wie Verschlüsselung funktioniert.
</simpara>
<sect2 xml:id="security.database.storage.hashing">
<title>Hashing</title>
<simpara>
Im Fall von wirklich versteckten Daten, wenn deren unverschlüsselte
Darstellung nicht nötig ist (&dh; die nicht angezeigt werden sollen),
ist Hashing ebenfalls überlegenswert. Das bekannte Beispiel für Hashing
ist das Speichern des kryptographischen Hashes eines Passwortes in einer
Datenbank, anstatt des Passworts selbst.
</simpara>
<simpara>
Die <link linkend="ref.password">Passwort</link>-Funktionen bieten eine
bequeme Möglichkeit, sensible Daten zu hashen und mit diesen Hashes zu
arbeiten.
</simpara>
<simpara>
<function>password_hash</function> wird verwendet, um eine gegebene
Zeichenkette unter Verwendung des stärksten zurzeit verfügbaren
Algorithmus zu hashen, und <function>password_verify</function> prüft,
ob ein gegebenes Passwort mit dem Hash, der in der Datenbank gespeichert
ist, übereinstimmt.
</simpara>
<example>
<title>Hashen eines Passwortfeldes</title>
<programlisting role="php">
<![CDATA[
<?php
// Speichern des Passwort-Hashes
$query = sprintf("INSERT INTO users(name,pwd) VALUES('%s','%s');",
pg_escape_string($username),
password_hash($password, PASSWORD_DEFAULT));
$result = pg_query($connection, $query);
// Abfragen, ob der User das richtige Passwort übermittelt hat
$query = sprintf("SELECT pwd FROM users WHERE name='%s';",
pg_escape_string($username));
$row = pg_fetch_assoc(pg_query($connection, $query));
if ($row && password_verify($password, $row['pwd'])) {
echo 'Willkommen, ' . htmlspecialchars($username) . '!';
} else {
echo 'Authentifizierung für ' . htmlspecialchars($username) . 'fehlgeschlagen.';
}
?>
]]>
</programlisting>
</example>
</sect2>
</sect1>
<sect1 xml:id="security.database.sql-injection">
<title>SQL-Injection</title>
<simpara>
SQL-Injection ist eine Technik, bei der ein Angreifer Schwachstellen im
Anwendungscode ausnutzt, mit dem dynamische SQL-Abfragen erstellt werden.
Ein Angreifer kann sich Zugang zu besonders geschützten Bereichen der
Anwendung verschaffen, sämtliche Informationen aus der Datenbank abrufen,
vorhandene Daten manipulieren oder sogar gefährliche Befehle auf der
Systemebene des Datenbank-Hosts ausführen. Die Schwachstelle entsteht,
wenn Entwickler in ihren SQL-Anweisungen beliebige Eingaben miteinander
verknüpfen oder einfügen.
</simpara>
<para>
<example>
<title>
Die Ergebnisliste in mehrere Seiten aufsplitten ... und Superuser anlegen
(PostgreSQL)
</title>
<simpara>
Im folgenden Beispiel wird die Benutzereingabe direkt in die
SQL-Abfrage eingefügt, wodurch der Angreifer ein Superuser-Konto für
die Datenbank erlangen kann.
</simpara>
<programlisting role="php">
<![CDATA[
<?php
$offset = $_GET['offset']; // Vorsicht, keine Validierung der Eingabe!
$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";
$result = pg_query($conn, $query);
?>
]]>
</programlisting>
</example>
Normale Benutzer klicken auf die 'nächste'- bzw. 'vorige'-Links, wo der
<varname>$offset</varname> in der <acronym>URL</acronym> enthalten ist.
Das Skript erwartet, dass der ankommende <varname>$offset</varname> eine
Zahl enthält. Was aber, wenn jemand versucht einzubrechen, indem er das
Folgende an die <acronym>URL</acronym> anhängt:
<informalexample>
<programlisting role="sql">
<![CDATA[
0;
insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd)
select 'crack', usesysid, 't','t','crack'
from pg_shadow where usename='postgres';
--
]]>
</programlisting>
</informalexample>
In diesem Fall würde das Skript dem Angreifer einen Superuser-Zugang zur
Verfügung stellen. Beachten Sie, dass <literal>0;</literal> einen
gültigen Offset zur ursprünglichen Abfrage liefert, und sie beendet.
</para>
<note>
<para>
Es ist eine übliche Technik, den SQL-Parser mit dem SQL-Kommentarzeichen
<literal>--</literal> zu zwingen, den Rest der vom Entwickler
geschriebenen Abfrage zu ignorieren.
</para>
</note>
<para>
Eine gangbare Methode, an Passwörter zu gelangen, ist, Ihre Seiten mit
den Suchergebnissen zu umgehen. Der Angreifer braucht nur zu probieren,
ob irgendeine übertragene Variable, die in dem SQL-Statement verwendet
wird, nicht richtig gehandhabt wird. Diese Filter können gewöhnlich in
einem vorausgehenden Formular gesetzt werden, indem <literal>WHERE-,
ORDER BY-, LIMIT-</literal> und <literal>OFFSET</literal>-Klauseln in
<literal>SELECT</literal>-Statements angepasst werden. Wenn Ihre
Datenbank das <literal>UNION</literal>-Konstrukt unterstützt, kann der
Angreifer versuchen, eine komplette Abfrage an das Original anzuhängen,
um Passwörter aus einer beliebigen Tabelle aufzulisten. Es wird dringend
empfohlen, nur sichere Hashes von Passwörtern zu speichern und nicht die
Passwörter selbst.
<example>
<title>
Artikel auflisten ... und ein paar Passwörter (beliebiger Datenbankserver)
</title>
<programlisting role="php">
<![CDATA[
<?php
$query = "SELECT id, name, inserted, size FROM products
WHERE size = '$size'";
$result = odbc_exec($conn, $query);
?>
]]>
</programlisting>
</example>
Der statische Teil der Abfrage kann mit einem anderen
<literal>SELECT</literal>-Statement kombiniert werden, welches alle
Passwörter preisgibt
<informalexample>
<programlisting role="sql">
<![CDATA[
'
union select '1', concat(uname||'-'||passwd) as name, '1971-01-01', '0' from usertable;
--
]]>
</programlisting>
</informalexample>
</para>
<para>
Auch <literal>UPDATE</literal>- und <literal>INSERT</literal>-Anweisungen
sind für derartige Angriffe anfällig.
<example>
<title>
Vom Zurücksetzen eines Passworts ... zum Erlangen von mehr Rechten
(beliebiger Datenbankserver)
</title>
<programlisting role="php">
<![CDATA[
<?php
$query = "UPDATE usertable SET pwd='$pwd' WHERE uid='$uid';";
?>
]]>
</programlisting>
</example>
Wenn ein böswilliger Benutzer den Wert <literal>' or uid
like'%admin%</literal> an <varname>$uid</varname> übermittelt, um das
Administrator-Passwort zu ändern, oder einfach <varname>$pwd</varname>
auf <literal>hehehe', trusted=100, admin='yes</literal> setzt, um mehr
Rechte zu erhalten, dann wird die Abfrage verdreht:
<informalexample>
<programlisting role="php">
<![CDATA[
<?php
// $uid: ' or uid like '%admin%
$query = "UPDATE usertable SET pwd='...' WHERE uid='' or uid like '%admin%';";
// $pwd: hehehe', trusted=100, admin='yes
$query = "UPDATE usertable SET pwd='hehehe', trusted=100, admin='yes' WHERE
...;";
?>
]]>
</programlisting>
</informalexample>
</para>
<simpara>
Obwohl es offensichtlich ist, dass ein Angreifer zumindest ein wenig
Kenntnis der genutzten Datenbankarchitektur besitzen muss, um einen
erfolgreichen Angriff durchzuführen, ist es oft sehr einfach, diese
Informationen zu erhalten. Der Code kann &zb; Teil einer
Open-Source-Software sein und öffentlich zugänglich sein. Diese
Informationen können auch durch Closed-Source-Code preisgegeben werden -
selbst wenn dieser kodiert, verschleiert oder kompiliert ist - und sogar
durch Ihren eigenen Code durch die Anzeige von Fehlermeldungen. Andere
Methoden beinhalten die Nutzung typischer Tabellen- und Spaltennamen. Ein
Login-Formular etwa, dass eine Tabelle 'users' mit den Spaltennamen 'id',
'username' und 'password' nutzt.
</simpara>
<para>
<example>
<title>Angriff auf das Betriebssystem des Datenbank-Hosts (MSSQL-Server)</title>
<simpara>
Ein erschreckendes Beispiel, wie der Zugriff auf Befehle auf
Betriebssystemebene bei manchen Datenbankservern erfolgen kann:
</simpara>
<programlisting role="php">
<![CDATA[
<?php
$query = "SELECT * FROM products WHERE id LIKE '%$prod%'";
$result = mssql_query($query);
?>
]]>
</programlisting>
</example>
Wenn ein Angreifer den Wert <literal>a%' exec master..xp_cmdshell 'net
user test testpass /ADD' --</literal> auf <varname>$prod</varname>
überträgt, wird <varname>$query</varname> zu:
<informalexample>
<programlisting role="php">
<![CDATA[
<?php
$query = "SELECT * FROM products
WHERE id LIKE '%a%'
exec master..xp_cmdshell 'net user test testpass /ADD' --%'";
$result = mssql_query($query);
?>
]]>
</programlisting>
</informalexample>
Der MSSQL-Server führt die SQL-Statements im Batch aus, inklusive eines
Kommandos um einen neuen Benutzer zur Datenbank der lokalen Konten
hinzuzufügen. Würde diese Anwendung als <literal>sa</literal> und der
MSSQLSERVER-Service mit genügend Rechten laufen, hätte der Angreifer nun
ein Konto, mit welchem er Zugriff auf diese Maschine hätte.
</para>
<note>
<para>
Manche der obigen Beispiele sind an einen spezifischen Datenbankserver
gebunden, aber das bedeutet nicht, dass ein ähnlicher Angriff auf andere
Produkte unmöglich wäre. Ihr Datenbankserver könnte auf andere Weise
genauso verwundbar sein.
</para>
</note>
<para>
<mediaobject>
<alt>Ein lustiges Beispiel für die Problematik der SQL-Injection</alt>
<imageobject>
<imagedata fileref="de/security/figures/xkcd-bobby-tables.png" format="PNG"/>
</imageobject>
<caption>
<simpara>
Bild mit freundlicher Genehmigung von
<link xlink:href="&url.xkcd;327">xkcd</link>
</simpara>
</caption>
</mediaobject>
</para>
<sect2 xml:id="security.database.avoiding">
<title>Techniken zur Vermeidung</title>
<para>
Um das Risiko einer SQL-Injection zu verringern, wird empfohlen, alle
Daten über vorbereitete Anweisungen zu binden. Die Verwendung von
parametrisierten Abfragen ist zwar nicht ausreichend, um SQL-Injections
vollständig zu verhindern, aber es ist der einfachste und sicherste Weg,
um Daten für SQL-Anweisungen bereitzustellen. Alle dynamischen
Datenliterale in <literal>WHERE</literal>-, <literal>SET</literal>- und
<literal>VALUES</literal>-Klauseln müssen durch Platzhalter ersetzt
werden. Die eigentlichen Daten werden bei der Ausführung gebunden und
getrennt vom SQL-Befehl gesendet.
</para>
<para>
Die Parameterbindung kann nur für Daten verwendet werden. Andere
dynamische Teile der SQL-Abfrage müssen gegen eine bekannte Liste
zulässiger Werte gefiltert werden.
</para>
<para>
<example>
<title>Ein sicherer Weg, eine Abfrage zu erstellen</title>
<programlisting role="php">
<![CDATA[
<?php
// Der dynamische SQL-Teil wird anhand der erwarteten Werte validiert
$sortingOrder = $_GET['sortingOrder'] === 'DESC' ? 'DESC' : 'ASC';
$productId = $_GET['productId'];
// Das SQL wird mit einem Platzhalter vorbereitet
$stmt = $pdo->prepare("SELECT * FROM products WHERE id LIKE ? ORDER BY price {$sortingOrder}");
// Der Wert wird mit LIKE-Wildcards versehen
$stmt->execute(["%{$productId}%"]);
?>
]]>
</programlisting>
</example>
</para>
<simpara>
Vorbereitete Anweisungen werden von
<link linkend="pdo.prepared-statements">PDO</link>,
<link linkend="mysqli.quickstart.prepared-statements">MySQLi</link> und
anderen Bibliotheken angeboten.
</simpara>
<simpara>
Angriffe durch SQL-Injection basieren hauptsächlich darauf, Code
auszunutzen, der nicht unter dem Aspekt der Sicherheit geschrieben
wurde. Vertrauen Sie niemals einer Eingabe, insbesondere nicht von der
Clientseite, selbst wenn sie von einer Auswahlbox, einem versteckten
Eingabefeld oder einem Cookie kommt. Das erste Beispiel zeigt, dass eine
so einfache Abfrage zu einer Katastrophe führen kann.
</simpara>
<para>
Eine umfassende Verteidigungsstrategie umfasst mehrere bewährte
Programmierpraktiken:
<itemizedlist>
<listitem>
<simpara>
Stellen Sie nie als Superuser oder Eigentümer einer Datenbank eine
Verbindung zur Datenbank her. Verwenden Sie immer speziell angelegte
Benutzer mit sehr limitierten Rechten.
</simpara>
</listitem>
<listitem>
<simpara>
Prüfen Sie, ob die gegebene Eingabe dem erwarteten Datentyp
entspricht. <acronym>PHP</acronym> bietet eine große Auswahl an
Funktionen zum Validieren der Eingaben, von den einfachsten unter
<link linkend="ref.var">Variablenfunktionen</link> und
<link linkend="ref.ctype">Character-Type-Funktionen</link> (&zb;
<function>is_numeric</function>, bzw.
<function>ctype_digit</function>), bis hin zu den
<link linkend="ref.pcre">Perl-kompatiblen regulären Ausdrücken</link>.
</simpara>
</listitem>
<listitem>
<simpara>
Wenn die Anwendung eine numerische Eingabe erwartet, sollten Sie die
Daten mittels <function>ctype_digit</function> überprüfen, den Typ
stillschweigend mittels <function>settype</function> ändern oder
deren numerische Darstellung mittels <function>sprintf</function>
verwenden.
</simpara>
</listitem>
<listitem>
<simpara>
Unterstützt die Datenbank-Ebene das Binden von Variablen nicht, so
maskieren Sie jede nichtnumerische Benutzereingabe, welche zur
Datenbank weitergereicht werden soll, mit der jeweiligen
datenbankspezifischen Maskierungsfunktion für Zeichenketten (&zb;
<function>mysql_real_escape_string</function>,
<function>sqlite_escape_string</function> usw.). Generische Funktionen
wie <function>addslashes</function> sind nur in einer sehr
spezifischen Umgebung nützlich (&zb; MySQL mit einem
SingleByte-Zeichensatz bei deaktiviertem
<varname>NO_BACKSLASH_ESCAPES</varname>), sodass es besser ist, diese
zu vermeiden.
</simpara>
</listitem>
<listitem>
<simpara>
Geben Sie um keinen Preis irgendwelche datenbankspezifischen
Informationen aus, speziell über das Schema. Siehe auch
<link linkend="security.errors">Fehlerbehandlung</link> und
<link linkend="ref.errorfunc">Fehlerbehandlungsfunktionen</link>.
</simpara>
</listitem>
</itemizedlist>
</para>
<simpara>
Abgesehen davon profitieren Sie von einer Protokollierung der Abfragen
entweder in Ihrem Skript oder durch die Datenbank selbst, falls sie
Protokollierung unterstützt. Klar, die Protokollierung kann keinen
schädlichen Versuch verhindern, aber sie kann helfen herauszufinden,
welche Anwendung umgangen wurde. Das Protokoll ist nicht von sich aus
nützlich, aber durch die in ihm enthaltenen Informationen, und je mehr
Details es enthält, desto besser ist es im Allgemeinen.
</simpara>
</sect2>
</sect1>
</chapter>
<!-- Keep this comment at the end of the file
Local variables:
mode: sgml
sgml-omittag:t
sgml-shorttag:t
sgml-minimize-attributes:nil
sgml-always-quote-attributes:t
sgml-indent-step:1
sgml-indent-data:t
indent-tabs-mode:nil
sgml-parent-document:nil
sgml-default-dtd-file:"~/.phpdoc/manual.ced"
sgml-exposed-tags:nil
sgml-local-catalogs:nil
sgml-local-ecat-files:nil
End:
vim600: syn=xml fen fdm=syntax fdl=2 si
vim: et tw=78 syn=sgml
vi: ts=1 sw=1
-->