SQL-In­jec­tions stellen für re­la­tio­na­le Da­ten­bank­mo­del­le und die darin ge­spei­cher­ten In­for­ma­tio­nen eine große Gefahr dar. Ein um­fas­sen­der Schutz gegen diese un­be­fug­ten externen Zugriffe, die durch Si­cher­heits­lü­cken möglich werden, ist daher es­sen­zi­ell.

Was ist eine SQL-Injection?

Unter einer SQL-Injection (dt. SQL-Ein­schleu­sung) versteht man das Ausnutzen einer Si­cher­heits­lü­cke in re­la­tio­na­len Da­ten­bank­sys­te­men, die bei der Da­ten­ein­ga­be auf die Ab­fra­ge­spra­che SQL zu­rück­grei­fen. Der Angreifer bzw. die An­grei­fe­rin macht sich dabei solche Be­nut­zer­ein­ga­ben zunutze, die nicht aus­rei­chend maskiert sind und Me­ta­zei­chen wie den doppelten Bin­de­strich, An­füh­rungs­zei­chen oder das Semikolon enthalten. Diese Zeichen besitzen Son­der­funk­tio­nen für den SQL-In­ter­pre­ter und erlauben die externe Be­ein­flus­sung der aus­ge­führ­ten Befehle. Oft tritt eine SQL-Injection im Zu­sam­men­hang mit PHP- und ASP-Pro­gram­men auf, die auf ältere In­ter­faces zu­rück­grei­fen. Hier erhalten die Eingaben in einigen Fällen nicht die not­wen­di­ge Mas­kie­rung und sind damit das perfekte Ziel für einen Angriff.

Mit dem gezielten Einsatz von Funk­ti­ons­zei­chen kann ein un­be­rech­tig­ter User auf diese Weise weitere SQL-Befehle ein­schleu­sen und die Einträge derart ma­ni­pu­lie­ren, dass er Daten verändern, löschen oder lesen kann. In gra­vie­ren­den Fällen ist es sogar möglich, dass sich ein Angreifer auf diesem Wege den Zugriff auf die Kom­man­do­zei­le des be­fehls­aus­füh­ren­den Systems und damit auf den gesamten Da­ten­bank­ser­ver ver­schafft.

SQL-Injection-Beispiele: So funk­tio­nie­ren die Datenbank-Angriffe

Da anfällige Da­ten­bank­ser­ver schnell auf­ge­spürt und SQL-Injection-Attacken ebenso einfach aus­ge­führt werden können, gehört die Methode weltweit zu den be­lieb­tes­ten. Dabei agieren die Kri­mi­nel­len mit ver­schie­de­nen An­griffs­mus­tern und machen sich aktuelle, aber vor allem auch alt­be­kann­te Si­cher­heits­lü­cken der am Da­ten­ma­nage­ment­pro­zess be­tei­lig­ten An­wen­dun­gen zunutze. Um zu ver­deut­li­chen, wie genau eine SQL-Injection funk­tio­niert, folgen ex­em­pla­risch zwei typische Methoden.

Beispiel 1: Zugriff über eine man­gel­haft maskierte Be­nut­zer­ein­ga­be

Damit Be­nut­ze­rin­nen und Benutzer auf eine Datenbank zugreifen können, müssen sie sich für ge­wöhn­lich zunächst au­then­ti­fi­zie­ren. Zu diesem Zweck exis­tie­ren Skripte, die bei­spiels­wei­se ein Login-Formular bestehend aus Nut­zer­na­me und Passwort prä­sen­tie­ren. User füllen das Formular aus und das Skript überprüft im Anschluss, ob in der Datenbank ent­spre­chen­de Einträge exis­tie­ren. Stan­dard­mä­ßig sind hierfür in der Datenbank eine Tabelle mit dem Namen users sowie den Spalten username und password angelegt. Bei einer be­lie­bi­gen Web­ap­pli­ka­ti­on könnten die be­tref­fen­den Skript­zei­len (Python-Pseu­do­code) für den Webserver-Zugriff fol­gen­der­ma­ßen lauten:

uname = request.POST['username']
passwd = request.POST['password']
sql = "SELECT id FROM users WHERE username='" + uname + "' AND password='" + passwd + "'"
database.execute(sql)
python

Ein Angreifer hat nun die Mög­lich­keit, das Pass­wort­feld per SQL-Injection gezielt zu ma­ni­pu­lie­ren, indem er bei­spiels­wei­se password' OR 1='1 eingibt, was zu folgender SQL-Abfrage führt:

sql = "SELECT id FROM users WHERE username='' AND password='password' OR 1='1'"
python

Auf diese Weise erhält er vollen Zugriff auf die gesamte Nut­zer­ta­bel­le der Datenbank, da das Passwort immer wahr ist (1='1'). Loggt er sich nun als Admin ein, kann er beliebige Ver­än­de­run­gen an den Einträgen vornehmen. Al­ter­na­tiv kann auf dem gleichen Weg auch das Feld des Be­nut­zer­na­mens ma­ni­pu­liert werden.

Beispiel 2: Daten ausspähen per ID-Ma­ni­pu­la­ti­on

In­for­ma­tio­nen aus einer Datenbank per ID ab­zu­fra­gen ist eine prak­ti­sche und gängige Methode, al­ler­dings auch ein mögliches Ein­falls­tor für eine SQL-Injection. So erfährt ein Webserver bei­spiels­wei­se durch eine in der URL über­mit­tel­te ID-Angabe, welche In­for­ma­tio­nen er aus der Datenbank abrufen soll. Das dazu passende PHP-Skript sieht in etwa so aus:

<?php
    $mysqli = new mysqli("localhost", "benutzername", "passwort", "datenbank");
    $id = intval($_GET['id']);
    $result = $mysqli->query("SELECT * FROM tabelle WHERE id=$id");
    while ($row = $result->fetch_assoc()) {
        echo print_r($row, true);
    }
?>
php

Die erwartete URL hat die Form .../script.php?id=22. In dem auf­ge­führ­ten Fall würde der Ta­bel­len­ein­trag mit der ID „22“ auf­ge­ru­fen werden. Hat eine fremde Person nun die Ge­le­gen­heit, diese an­fra­gen­de URL zu ma­ni­pu­lie­ren und schickt dem Webserver statt­des­sen die Anfrage …/script.php?id=22+OR+1=1, bewirkt der daraus re­sul­tie­ren­de Aufruf, dass alle Daten der Tabelle aus­ge­le­sen werden:

SELECT * FROM tabelle WHERE id=22 OR 1=1;
sql

Wie finden Kri­mi­nel­le ge­fähr­de­te Da­ten­bank­sys­te­me?

Prin­zi­pi­ell können alle Websites und Web­an­wen­dun­gen, die SQL-Da­ten­ban­ken ohne vor­be­rei­te­te Anfragen (Prepared State­ments) oder andere Schutz­me­cha­nis­men nutzen, anfällig für SQL-In­jec­tions sein. Entdeckte Schwach­stel­len bleiben in den Weiten des World Wide Webs nicht lange geheim und so exis­tie­ren bei­spiels­wei­se In­for­ma­ti­ons­sei­ten, die aktuelle Si­cher­heits­lü­cken prä­sen­tie­ren und Kri­mi­nel­len darüber hinaus auch gleich verraten, wie sie passende Web­pro­jek­te über die Google-Suche finden können. Falls eine Website de­tail­lier­te SQL-Feh­ler­mel­dun­gen zu­rück­gibt, können Kri­mi­nel­le diese nutzen, um po­ten­zi­el­le Schwach­stel­len zu iden­ti­fi­zie­ren. So kann man einer URL mit ent­hal­te­nem ID-Parameter einfach ein Apostroph anhängen (wie im folgenden Beispiel):

[Domainname].de/news.php?id=5‘

Eine an­greif­ba­re Website sendet dann eine Feh­ler­mel­dung zurück, die in etwa fol­gen­der­ma­ßen lautet:

Query failed: You have an error in your SQL Syntax …

Zu Deutsch: „Abfrage ge­schei­tert: Ihre SQL-Syntax ist feh­ler­haft …“. Mit ähnlichen Methoden lassen sich auch die Spal­ten­an­zahl, Tabellen- und Spal­ten­na­men, SQL-Version oder gar Nut­zer­na­men und Pass­wör­ter ausgeben. Mit ver­schie­de­nen Tools lassen sich die Suche und an­schlie­ßen­de SQL-In­jec­tions darüber hinaus auch au­to­ma­ti­sie­ren.

Wie Sie Ihre Datenbank vor SQL-Injection schützen

Sie können ver­schie­de­ne Maßnahmen ergreifen, um SQL-Injection-Attacken auf Ihr Da­ten­bank­sys­tem zu ver­hin­dern. Dabei sollten Sie sich mit allen in­vol­vier­ten Kom­po­nen­ten – dem Server, den einzelnen An­wen­dun­gen sowie dem Da­ten­bank­ma­nage­ment­sys­tem – aus­ein­an­der­set­zen.

Schritt 1: Die au­to­ma­ti­schen Eingaben der Ap­pli­ka­tio­nen über­wa­chen

Bei der Ver­ar­bei­tung von Eingaben durch externe oder ein­ge­bun­de­ne Ap­pli­ka­tio­nen ist es es­sen­zi­ell, die über­mit­tel­ten Werte zu va­li­die­ren und zu filtern, um SQL-In­jec­tions zu ver­hin­dern.

1. Da­ten­ty­pen über­prü­fen

Jede Eingabe sollte dem er­war­te­ten Datentyp ent­spre­chen. Falls bei­spiels­wei­se eine nu­me­ri­sche Eingabe er­for­der­lich ist, kann eine einfache Va­li­die­rung in PHP so aussehen:

if (filter_var($input, FILTER_VALIDATE_INT) === false) {
    throw new InvalidArgumentException("Ungültige Eingabe");
}
php

Ähnliche Prüfungen sollten für Strings, Da­tums­wer­te oder andere spe­zi­fi­sche Formate im­ple­men­tiert werden.

2. Son­der­zei­chen filtern

Spezielle Zeichen können Si­cher­heits­lü­cken ver­ur­sa­chen, ins­be­son­de­re in SQL- oder HTML-Kontexten. Eine sichere Methode ist die Nutzung von htmlspecialchars() für HTML-Eingaben und von PDO::quote() für SQL-Anfragen.

3. Feh­ler­mel­dun­gen vermeiden

Direkte Feh­ler­mel­dun­gen, die tech­ni­sche Details über die Datenbank oder das System enthalten, sollten vermieden werden. Statt­des­sen empfiehlt sich eine ge­ne­ri­sche Ausgabe wie:

echo "Ein Fehler ist aufgetreten. Bitte versuchen Sie es später erneut.";
error_log("Unerwarteter Fehler aufgetreten. Weitere Details siehe Systemlog.");
php

4. Prepared State­ments nutzen

Eine sichere Mög­lich­keit, SQL-In­jec­tions zu ver­hin­dern, sind die bereits erwähnten Prepared State­ments. Hierbei werden SQL-Befehle und Parameter getrennt über­mit­telt, sodass schäd­li­cher Code nicht aus­ge­führt werden kann. Ein passendes Beispiel in PHP mit PDO (PHP Data Objects) sieht wie folgt aus:

$stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");
$stmt->bindParam(':id', $user_id, PDO::PARAM_INT);
$stmt->execute();
php

Das Da­ten­bank­ma­nage­ment­sys­tem sorgt au­to­ma­tisch für eine sichere Ver­ar­bei­tung der Eingaben.

Schritt 2: Für einen um­fas­sen­den Server-Schutz sorgen

Die Si­cher­heit des Servers, auf dem Sie Ihr Da­ten­bank­ma­nage­ment­sys­tem ausführen, spielt natürlich auch bei der SQL-Injection-Prä­ven­ti­on eine große Rolle. An erster Stelle steht hierbei das Härten des Be­triebs­sys­tems nach dem bekannten Muster:

  • In­stal­lie­ren bzw. ak­ti­vie­ren Sie nur solche An­wen­dun­gen und Dienste, die für den Betrieb der Datenbank relevant sind.
  • Löschen Sie alle Be­nut­zer­kon­ten, die Sie nicht benötigen.
  • Sorgen Sie dafür, dass alle re­le­van­ten System- und Programm-Updates in­stal­liert sind.
  • Wenden Sie das Least-Privilege-Prinzip an, um si­cher­zu­stel­len, dass User und Dienste nur die minimal er­for­der­li­chen Be­rech­ti­gun­gen erhalten.

Je nach Si­cher­heits­an­for­de­run­gen Ihres Web­pro­jekts sollten zu­sätz­li­che Schutz­maß­nah­men in Betracht gezogen werden:

  • Intrusion-Detection-Systeme (IDS) und Intrusion-Pre­ven­ti­on-Systeme (IPS): Diese Systeme arbeiten mit ver­schie­de­nen Er­ken­nungs­me­tho­den, um Angriffe auf den Server früh­zei­tig zu erkennen, Warnungen aus­zu­ge­ben und im Falle von IPS auch au­to­ma­tisch ent­spre­chen­de Ge­gen­maß­nah­men ein­zu­lei­ten.
  • Ap­pli­ca­ti­on Layer Gateway (ALG): Ein ALG überwacht und filtert den Da­ten­ver­kehr zwischen An­wen­dun­gen und Web­brow­sern direkt auf Ap­pli­ka­ti­ons­ebe­ne.
  • Web Ap­pli­ca­ti­on Firewall (WAF): Eine WAF schützt Web­an­wen­dun­gen gezielt vor SQL-Injection und Cross-Site-Scripting (XSS), indem sie ver­däch­ti­ge Anfragen blockiert oder ent­schärft.
  • Zero-Trust-Ansatz: Dieser moderne Si­cher­heits­an­satz stellt sicher, dass jeder Zugriff, un­ab­hän­gig von seiner Quelle, überprüft und ve­ri­fi­ziert wird, bevor er zu­ge­las­sen wird.
  • Firewall-Re­ge­lun­gen und Netz­werk­seg­men­tie­rung: Diese Maßnahmen sind elementar, um die An­griffs­flä­che lang­fris­tig zu mi­ni­mie­ren.
  • Re­gel­mä­ßi­ge IT-Si­cher­heits­au­dits und Pe­ne­tra­ti­ons­tests: Diese helfen dabei, Schwach­stel­len früh­zei­tig zu erkennen und zu schließen.

Schritt 3: Datenbank härten und sichere Codes verwenden

Wie auch Ihr Be­triebs­sys­tem sollte die Datenbank von sämt­li­chen ir­rele­van­ten Faktoren befreit und re­gel­mä­ßig ak­tua­li­siert werden. Entfernen Sie zu diesem Zweck alle ge­spei­cher­ten Pro­ze­du­ren, die Sie nicht benötigen und de­ak­ti­vie­ren Sie alle unnötigen Dienste und Be­nut­zer­kon­ten. Richten Sie einen spe­zi­el­len Datenbank-Account ein, der aus­schließ­lich für den Zugriff aus dem Web vor­ge­se­hen ist und mit minimalen Zu­griffs­rech­ten auskommt.

Im Sinne der Prepared State­ments ist es dringend zu empfehlen, das PHP-Modul mysql nicht zu verwenden (seit PHP 7 entfernt) und statt­des­sen mysqli oder PDO zu wählen. Auf diese Weise können Sie sich zu­sätz­lich auch mit sicheren Codes schützen. Eine sichere mysqli-Abfrage sieht bei­spiels­wei­se fol­gen­der­ma­ßen aus:

$mysqli = new mysqli("localhost", "benutzer", "passwort", "datenbank");
if ($mysqli->connect_error) die("Verbindung fehlgeschlagen");
$stmt = $mysqli->prepare("SELECT password FROM users WHERE username = ?");
$stmt->bind_param("s", $_POST['username']);
$stmt->execute();
$stmt->bind_result($hashedPassword);
if ($stmt->fetch() && password_verify($_POST['password'], $hashedPassword)) {
    echo "Login erfolgreich";
} else {
    echo "Falsche Zugangsdaten";
}
$stmt->close();
$mysqli->close();
php

Zudem sollten Pass­wör­ter niemals direkt in einer Datenbank ge­spei­chert oder als Klartext abgefragt werden. Verwenden Sie statt­des­sen eine Hashing-Methode wie password_hash() in Kom­bi­na­ti­on mit password_verify(), um die Kenn­wör­ter optimal zu schützen. Eine sichere Lösung könnte fol­gen­der­ma­ßen aussehen:

$mysqli = new mysqli("localhost", "benutzer", "passwort", "datenbank");
$stmt = $mysqli->prepare("SELECT password FROM users WHERE username = ?");
$stmt->bind_param("s", $_POST['username']);
$stmt->execute();
$result = $stmt->get_result();
$row = $result->fetch_assoc();
if ($row && password_verify($_POST['password'], $row['password'])) {
    echo "Login erfolgreich!";
} else {
    echo "Falscher Benutzername oder falsches Passwort.";
}
php

Bobby Tables: SQL-Injection im Comicstil erklärt

Auf der Seite bobby-tables.com hat man sich anhand eines xkcd-Webcomics mit der Thematik un­si­che­rer Datenbank-Be­nut­zer­ein­ga­ben aus­ein­an­der­ge­setzt. Der Comic zeigt eine Mutter, die einen Anruf von der Schule ihres Sohnes – liebevoll „Klein Bobby Tables“ genannt – erhält. Sie bejaht die Frage, ob ihr Sohn tat­säch­lich Robert'); DROP TABLE Students;– – hieße und erfährt im Anschluss den Hin­ter­grund für den Anruf: Der Versuch, einen Eintrag für Robert in der Schü­ler­da­ten­bank zu erstellen, führte dazu, dass der komplette Da­ten­be­stand gelöscht wurde. Roberts Mutter hat daraufhin nur wenig tröstende Worte übrig und äußert ihre Hoffnung, dass die Schule aus dem Fehler gelernt hat und die Eingaben in die Datenbank zukünftig be­rei­ni­gen wird.

Der Comic zeigt ein­drück­lich, welche fatalen Folgen nicht über­prüf­te Be­nut­zer­ein­ga­ben in Da­ten­ban­ken haben können.

Zum Hauptmenü