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.