Datenbank-Sicherheit
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.
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
an einer SQL-Abfrage zu schaffen machen
kann.
Sie werden merken, dass PHP Ihre Datenbank nicht selbst
schützen kann. Die folgenden Abschnitte sind eine Einführung in die
Grundlagen, wie man innerhalb von PHP-Skripten auf
Datenbanken zugreift und diese manipuliert.
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.
Datenbanken entwerfen
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.
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.
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.
Mit der Datenbank verbinden
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.
Verschlüsseltes Speichermodell
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.
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.
Der einfachste Weg, dieses Problem zu umgehen ist, erst einmal Ihr
eigenes Verschlüsselungspaket zu erstellen, und dieses dann in Ihren
PHP-Skripten zu nutzen. PHP kann
Ihnen in diesem Fall mit seinen verschiedenen Erweiterungen helfen, wie
&zb; OpenSSL und
Sodium, 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.
Hashing
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.
Die Passwort-Funktionen bieten eine
bequeme Möglichkeit, sensible Daten zu hashen und mit diesen Hashes zu
arbeiten.
password_hash wird verwendet, um eine gegebene
Zeichenkette unter Verwendung des stärksten zurzeit verfügbaren
Algorithmus zu hashen, und password_verify prüft,
ob ein gegebenes Passwort mit dem Hash, der in der Datenbank gespeichert
ist, übereinstimmt.
Hashen eines Passwortfeldes
]]>
SQL-Injection
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.
Die Ergebnisliste in mehrere Seiten aufsplitten ... und Superuser anlegen
(PostgreSQL)
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.
]]>
Normale Benutzer klicken auf die 'nächste'- bzw. 'vorige'-Links, wo der
$offset in der URL enthalten ist.
Das Skript erwartet, dass der ankommende $offset eine
Zahl enthält. Was aber, wenn jemand versucht einzubrechen, indem er das
Folgende an die URL anhängt:
In diesem Fall würde das Skript dem Angreifer einen Superuser-Zugang zur
Verfügung stellen. Beachten Sie, dass 0; einen
gültigen Offset zur ursprünglichen Abfrage liefert, und sie beendet.
Es ist eine übliche Technik, den SQL-Parser mit dem SQL-Kommentarzeichen
-- zu zwingen, den Rest der vom Entwickler
geschriebenen Abfrage zu ignorieren.
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 WHERE-,
ORDER BY-, LIMIT- und OFFSET-Klauseln in
SELECT-Statements angepasst werden. Wenn Ihre
Datenbank das UNION-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.
Artikel auflisten ... und ein paar Passwörter (beliebiger Datenbankserver)
]]>
Der statische Teil der Abfrage kann mit einem anderen
SELECT-Statement kombiniert werden, welches alle
Passwörter preisgibt
Auch UPDATE- und INSERT-Anweisungen
sind für derartige Angriffe anfällig.
Vom Zurücksetzen eines Passworts ... zum Erlangen von mehr Rechten
(beliebiger Datenbankserver)
]]>
Wenn ein böswilliger Benutzer den Wert ' or uid
like'%admin% an $uid übermittelt, um das
Administrator-Passwort zu ändern, oder einfach $pwd
auf hehehe', trusted=100, admin='yes setzt, um mehr
Rechte zu erhalten, dann wird die Abfrage verdreht:
]]>
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.
Angriff auf das Betriebssystem des Datenbank-Hosts (MSSQL-Server)
Ein erschreckendes Beispiel, wie der Zugriff auf Befehle auf
Betriebssystemebene bei manchen Datenbankservern erfolgen kann:
]]>
Wenn ein Angreifer den Wert a%' exec master..xp_cmdshell 'net
user test testpass /ADD' -- auf $prod
überträgt, wird $query zu:
]]>
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 sa 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.
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.
Ein lustiges Beispiel für die Problematik der SQL-Injection
Bild mit freundlicher Genehmigung von
xkcd
Techniken zur Vermeidung
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 WHERE-, SET- und
VALUES-Klauseln müssen durch Platzhalter ersetzt
werden. Die eigentlichen Daten werden bei der Ausführung gebunden und
getrennt vom SQL-Befehl gesendet.
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.
Ein sicherer Weg, eine Abfrage zu erstellen
prepare("SELECT * FROM products WHERE id LIKE ? ORDER BY price {$sortingOrder}");
// Der Wert wird mit LIKE-Wildcards versehen
$stmt->execute(["%{$productId}%"]);
?>
]]>
Vorbereitete Anweisungen werden von
PDO,
MySQLi und
anderen Bibliotheken angeboten.
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.
Eine umfassende Verteidigungsstrategie umfasst mehrere bewährte
Programmierpraktiken:
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.
Prüfen Sie, ob die gegebene Eingabe dem erwarteten Datentyp
entspricht. PHP bietet eine große Auswahl an
Funktionen zum Validieren der Eingaben, von den einfachsten unter
Variablenfunktionen und
Character-Type-Funktionen (&zb;
is_numeric, bzw.
ctype_digit), bis hin zu den
Perl-kompatiblen regulären Ausdrücken.
Wenn die Anwendung eine numerische Eingabe erwartet, sollten Sie die
Daten mittels ctype_digit überprüfen, den Typ
stillschweigend mittels settype ändern oder
deren numerische Darstellung mittels sprintf
verwenden.
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;
mysql_real_escape_string,
sqlite_escape_string usw.). Generische Funktionen
wie addslashes sind nur in einer sehr
spezifischen Umgebung nützlich (&zb; MySQL mit einem
SingleByte-Zeichensatz bei deaktiviertem
NO_BACKSLASH_ESCAPES), sodass es besser ist, diese
zu vermeiden.
Geben Sie um keinen Preis irgendwelche datenbankspezifischen
Informationen aus, speziell über das Schema. Siehe auch
Fehlerbehandlung und
Fehlerbehandlungsfunktionen.
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.