1
0
mirror of https://github.com/php/doc-de.git synced 2026-03-24 07:12:15 +01:00
Files
archived-doc-de/security/database.xml
Martin Samesch 55b7f2fd8b Sync with EN
2024-04-13 22:53:01 +02:00

531 lines
21 KiB
XML

<?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
-->