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.
Beispiel #1
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.
<?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);
?>
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.
Hinweis:
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.
Beispiel #2
Artikel auflisten ... und ein paar Passwörter (beliebiger Datenbankserver)
<?php
$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
Auch UPDATE
- und INSERT
-Anweisungen
sind für derartige Angriffe anfällig.
Beispiel #3
Vom Zurücksetzen eines Passworts ... zum Erlangen von mehr Rechten
(beliebiger Datenbankserver)
<?php
$query = "UPDATE usertable SET pwd='$pwd' WHERE uid='$uid';";
?>
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 z. B. Teil einer
Open-Source-Software 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.
Beispiel #4 Angriff auf das Betriebssystem des Datenbank-Hosts (MSSQL-Server)
Ein erschreckendes Beispiel, wie der Zugriff auf Befehle auf
Betriebssystemebene bei manchen Datenbankservern erfolgen kann:
<?php
$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' --
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.
Hinweis:
Manche der obigen Beispiele sind an einen spezifischen Datenbankserver
gebunden, aber das bedeutet nicht, dass ein ähnlicher Angriff auf andere
Produkte unmöglich wären. Ihr Datenbankserver könnte auf andere Weise
genauso verwundbar sein.
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.
Beispiel #5 Ein sicherer Weg, eine Abfrage zu erstellen
<?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}%"]);
?>
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 (z. B.
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 (z. B.
mysql_real_escape_string(),
sqlite_escape_string() usw.). Generische Funktionen
wie addslashes() sind nur in einer sehr
spezifischen Umgebung nützlich (z. B. 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.