SQL Injection
Viele Entwickler sind sich nicht bewusst, wie man sich an SQL Abfragen
zu schaffen machen kann und nehmen an, dass eine SQL Abfrage ein
vertrauenswürdiges Kommando ist. Das heißt, dass SQL Abfragen
Zugriffskontrollen hinters Licht führen, und dadurch Standard
Authentifizierungs- und Authorisationschecks umgehen können, und
manchmal können SQL Abfragen sogar Zugriff zu Kommandos auf
Betriebssystemebene erlauben.
Direkt SQL Command Injection ist eine Technik, wo ein Angreifer SQL
Kommandos erstellt oder existierende verändert, um versteckte Daten
sichtbar zu machen, wertvolle Daten zu überschreiben, oder sogar
gefährliche Kommandos auf Systemebene des Datenbank-Hosts auszuführen.
Dies wird durch die Applikation erreicht, welche den Input des Benutzers
mit statischen Parametern kombiniert, um eine SQL Abfrage zu erstellen.
Die folgenden Beispiele basieren - leider - auf wahren Begebenheiten.
Dank dem Mangel an Input Validierungen, und dem Verbinden zum
Datenbankserver als ein Superuser oder jemand der Benutzer anlegen kann,
kann ein Angreifer einen Superuser in Ihrer Datenbank anlegen.
Beispiel #1
Die Ergebnisliste in mehrere Seiten aufsplitten ... und Superuser anlegen
(PostgreSQL and MySQL)
$offset = $argv[0]; // Vorsicht, keine Validierung des Input !
$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";
// mit PostgreSQL
$result = pg_query($conn, $query);
// mit MySQL
$result = mysql_query($query);
Normale Benutzer klicken auf die 'nächste' bzw. 'vorige' Links, wo
$offset in der
URL enthalten ist. Das Skript erwartet,
dass die ankommende
$offset einen Dezimalwert enthält.
Ganz gleich, jemand versucht einzubrechen, indem er das folgende in einer
urlencode()'d Form an die
URL anhängt
Wenn es passiert ist, würde ihm das Skript einen Zugriff als Superuser
präsentieren. Beachten Sie, dass
0;
ein gültiges
Offset zur ursprünglichen Abfrage liefert, und sie beendet.
Hinweis:
Es ist eine übliche Technik, den SQL Parser mittels dem Kommentarzeichen
in SQL --
zu zwingen, den Rest der vom Entwickler
geschriebenen Abfrage zu ignorieren.
Ein gangbarer Weg um Passwörter zu finden ist, Ihre Seiten mit den
Suchergebnissen hinters Licht zu führen. 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 einer vorausgehenden Form gesetzt werden, indem
WHERE, ORDER BY, LIMIT
und OFFSET
Klauseln in SELECT
Statements umgebaut werden. Wenn
Ihre Datenbank das UNION
Konstrukt unterstützt, kann
der Angreifer versuchen, eine komplette Abfrage an das Original anzuhängen,
um Paßwörter aus einer willkürlichen Tabelle aufzulisten. Die Verwendung
von verschlüsselten Passwortfeldern wird ausdrücklich empfohlen.
Beispiel #2
Artikel auflisten ... und ein paar Passwörter (irgendein Datenbankserver)
$query = "SELECT id, name, inserted, size FROM products
WHERE size = '$size'";
$result = odbc_exec($conn, $query);
Der statische Teil der Abfrage kann mit einem anderen
SELECT
Statement kombiniert werden, welches alle
Passwörter preisgibt
Wenn diese Abfrage (mit dem
'
und
--
) einer der in
$query verwendeten
Variablen zugewiesen würde, wäre das "Abfragebiest" erwacht.
SQL UPDATEs sind ebenfalls ein Anlass, Ihre Datenbank anzugreifen. Diese
Abfragen sind auch durch das Ändern und Anhängen einer komplett neuen
Abfrage gefährdet. Aber der Angreifer könnte auch mit der
SET
Klausel herumspielen. In diesem Fall muss eine
Schemainformation vorhanden sein, um die Abfrage erfolgreich manipulieren
zu können. Diese kann durch Untersuchen der Variablennamen im Formular,
oder simpel mittels brute force gesammelt werden. Es gibt nicht so viele
Namenskonventionen für Felder, welche Passwörter oder Benutzernamen
speichern.
Beispiel #3
Vom Zurücksetzen eines Passwortes ... zum Erlangen von mehr Rechten
(irgendein Datenbankserver)
$query = "UPDATE usertable SET pwd='$pwd' WHERE uid='$uid';";
Aber 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:
Ein furchterregendes Beispiel, wie der Zugriff auf Kommandos auf
Betriebssystemebene bei manchen Datenbankservern erfolgen kann.
Beispiel #4 Angriff auf das Betriebssystem des Datenbank Hosts (MSSQL Server)
$query = "SELECT * FROM products WHERE id LIKE '%$prod%'";
$result = mssql_query($query);
Wenn ein Angreifer den Wert
a%' exec master..xp_cmdshell 'net user test testpass /ADD' --
zu
$prod überträgt, wird
$query zu:
Der MSSQL Server führt die SQL Statements in dem Batch aus, inklusive einem
Kommando zum Anlegen eines neuen Benutzers in der Datenbank Accounts. Würde
diese Applikation 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.
Hinweis:
Manche der obigen Beispiele sind an einen spezifischen Datenbankserver
gebunden. Das heißt jedoch nicht, dass nicht ein ähnlicher Angriff auf
andere Produkte möglich wäre. Ihr Datenbankserver könnte auf andere
Weise genauso verwundbar sein.
Bild mit freundlicher Genehmigung von
» xkcd
Techniken zur Vermeidung
Obwohl es offensichtlich ist, dass ein Angreifer zumindest ein
wenig Kenntnis der genutzten Datenbankarchitektur besitzen muss,
um einen erfolgreichen Angriff durchzuführen, ist das Erlangen
dieser Informationen oft sehr einfach. Wenn die Datenbank zum
Beispiel Teil eines Open Source oder anderweitig öffentlich
verfügbaren Paketes mit einer Standard Installation ist, dann
ist diese Information vollkommen frei zugänglich. Diese
Information kann auch durch Closed Source Code - selbst wenn
dieser kodiert, verschleiert oder kompiliert ist - und sogar
durch ihren ureigenen Code durch die Anzeige von
Fehlermeldungen. Andere Methoden beinhalten die Nutzung
typischer Tabellen und Spalten Namen. Ein Login Formular etwa,
dass eine Tabelle 'users' mit den Spaltennamen 'id', 'username'
und 'password' nutzt.
Diese Angriffe basieren hauptsächlich auf dem Ausnutzen des Codes, welcher
ohne Bedenken auf die Sicherheit geschrieben wurde. Vertrauen Sie nie auf
irgendeine Art von Input, speziell wenn er von der Clientseite kommt,
selbst wenn er von einer Auswahlbox, einem versteckten Eingabefeld, oder
einem Cookie kommt. Das erste Beispiel zeigt, dass solch eine untadelige
Abfrage ein Disaster anrichten kann.
-
Stellen Sie nie als Superuser oder Owner einer Datenbank eine Verbindung
zur Datenbank her. Verwenden Sie immer speziell angelegte Benutzer mit
sehr limitierten Rechten.
-
Verwenden Sie Prepared Statements mit gebundenden Variablen. Sie werden
von PDO,
MySQLi
und anderen Bibliotheken angeboten.
-
Prüfen Sie, ob der gegebene Input dem erwarteten Datentyp entspricht.
PHP bietet eine große Auswahl an Funktionen zum Validieren des Input,
von den einfachsten unter Variablenfunktionen und Character Type Functions (z.B.
is_numeric() bzw. ctype_digit()),
bis hin zu den Perl kompatiblen Regulären
Ausdrücken.
-
Wenn die Applikation numerischen Input erwartet, erwägen Sie die Prüfung
der Daten mit ctype_digit(), oder die Änderung des
Typs mit settype(), oder verwenden Sie die numerische
Repräsentation mittels sprintf().
Beispiel #5 Ein sicherer Weg, eine Abfrage zu erstellen
settype($offset, 'integer');
$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";
// Beachten Sie %d im Formatstring, %s zu verwenden wäre sinnlos
$query = sprintf("SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET %d;",
$offset);
-
Unterstützt der Datenbank-Layer das Binden von Variablen nicht, so
maskieren Sie jede nicht numerische Benutzereingabe, welche zur Datenbank
weitergereicht werden soll mit der jeweiligen datenbankspezifischen
Escape-Funktion (z.B. mysql_real_escape_string(),
sqlite_escape_string() usw.). Generische Funktionen
wie addslashes() sind nur in einer sehr spezifischen
Umgebung nüztlich (z.B. MySQL mit einem Single Byte Zeichensatz bei
deaktiviertem NO_BACKSLASH_ESCAPES), so dass es
besser ist diese zu vermeiden.
-
Geben Sie keinerlei datenbankspezifische Informationen aus, speziell
über das Schema, egal wie (auf ehrliche oder unehrliche Weise). Siehe
auch Fehlerbehandlung und
Error Handling and Logging
Functions.
-
Sie können stored procedures und vorher definierte Cursor verwenden,
um den Datenzugriff zu abstrahieren, sodass Benutzer nicht direkt auf
Tabellen oder Views zugreifen, aber diese Lösung hat andere
Auswirkungen.
Abgesehen davon profitieren Sie von einer Protokollierung der Abfragen
entweder in Ihrem Skript, oder durch die Datenbank selbst, wenn es hilft.
Klar, die Protokollierung kann nicht irgendeinen schädlichen Versuch
verhindern, aber es kann helfen herauszufinden, welche Applikation
umgangen wurde. Das Log ist durch die in ihm enthaltene Information
nützlich, und je mehr Details es enthält, desto besser ist es im
Allgemeinen.