SQL Schnellstart – SQL Injection (Sicherheit)

Immer häufiger hört man, dass Hacker in Datenbanken eingedrungen sind und eine Vielzahl an Nutzerdaten kompromittiert wurden.

Unternehmen wie Yahoo, Facebook oder Netflix geben viel Geld für ihre IT-Sicherheit aus, aber trotzdem lassen sich häufig Vorfälle nicht vermeiden. Ein Fehler in der Entwicklung reicht aus, und ein Hacker könnte in das System eindringen. Sicherheitslücken lassen sich zwar nicht immer vermeiden, aber aufmerksame Entwicklung und Wissen im Bereich IT-Sicherheit kann die Anzahl der Sicherheitslücken stark minimieren.

SQL Injections: Definition

Nach dem "Open Web Application Security Project" sind "Injection-Angriffe" die meistverbreiteten Sicherheitslücken bei Webseiten. Eine Injection ist zum Beispiel die SQL-Injection. Ein Besucher hat so die Möglichkeit, bösartigen SQL-Code auszuführen. SQL Injections treten normalerweise auf, wenn man eine Art Input bereitstellt, z.B. ein Login-Feld mit Username und Passwort. Dort gibt der Benutzer statt seiner Daten, den schadhaften SQL-Code ein.

SQL Injection: Beispiel

Okay, verständlicher wird das sicherlich an einem Beispiel.

Stell dir vor wir haben eine Datenbank "benutzer". Dort ist eine Tabelle "benutzerdaten". In dieser Tabelle werden Benutzername und Passwort (sollte natürlich immer gehasht sein!) gespeichert. Bisher haben wir nur den User "Tarik" mit dem Passwort "abc123".

Dazu haben wir noch eine Webseite, welche in PHP geschrieben wurde:

<?php
// Servername = localhost (localhost steht für die lokale IP d.h. der MySQL-Server läuft auf unserem Rechner)
$servername = "localhost";
$benutzername = "root"; // 'root' durch XAMPP voreingestellt
$passwort = "";	// Standardgemäß kein Passwort


// Neue Verbindung zur Datenbank erstellen
$verbindung = new mysqli($servername, $benutzername, $passwort);

// Verbindung überprüfen
if ($verbindung->connect_error) {
  die("Verbindungsfehler: " . $verbindung->connect_error);
}
echo "Erfolgreich mit der Datenbank verbunden. <br> <br>";

// isset prüft, ob es bestimmte Variablen mit einem Wert gibt, wir überprüfen, ob die
// Werte für das Passwort und den Usernamen übergeben worden sind, und versuchen dann einen login versuchen
// ansonsten geht es beim else weiter und wir geben ein Feld zum einloggen aus
if(isset($_GET['pass']) && isset($_GET['user']))
{
	// Wähle die Datenbank "benutzer" aus
	$verbindung->select_db("benutzer");
	// Frage die Benutzerdaten mit ab, mit dem jeweiligen Benutzername und Passwort
	$SQL_QUERY = "SELECT * FROM benutzerdaten WHERE benutzername = '" . $_GET['user'] . "' AND passwort = '" . $_GET['pass'] . "';";
	$selectErgebnis = $verbindung->query($SQL_QUERY);
	// Zur besseren Übersich nochmal die gesamte Query ausgeben
	echo "Deine Abfrage: " . $SQL_QUERY;
	
	// Sollte es ein Problem mit der Query an sich gegeben haben (zum Beispiel weil es keine Tabelle mit Nutzerdaten gibt) wird ein
	// Fehler ausgegeben
	if(!$selectErgebnis)
	{	
		?>
		<h4>Fehler bei der Abfrage.</h4>	 
		<?php
		// loginfeld ausgeben
		login();		
	}
	// Hat die Abfrage kein User mit dem übergebenen Passwort und User gefunden, also wenn die Abfrage weniger als 1 Ergebnis (also 0 Ergebnisse) zurückgeliefert hat
	// So ist wahrscheinlich dass Passwort falsch
	else if($selectErgebnis->num_rows < 1)
	{
		?>
		<h4>Falsches Passwort oder Benutzername.</h4>	 
		<?php
		// loginfeld ausgeben
		login();
	}
	// Ansonsten soll eine Wilkommensnachricht erscheinen
	else
	{
		// fetch_object zieht uns die erste Zeile unseres Ergebnisses heraus, sodass wir darauf zugreifen können
		$benutzerdaten = $selectErgebnis->fetch_object();
		?> 
		<h4>Wilkommen <?= $benutzerdaten->Benutzername ?><h4>
		<?php
	}
}
else
{
		login();
}
function login() {
	?>
	<h4>Bitte Einloggen:</h4>
	<form method="get">
		<label for="user">Benutzername:</label><br>
		<input type="text" id="user" name="user"><br>
		<label for="pass">Passwort:</label><br>
		<input type="text" id="pass" name="pass"><br>
		<input type="submit" value="anmelden">
	</form>
	<?php
}
?>

Die SQL-Query wird folgendermaßen zusammengesetzt:

$SQL_QUERY = "SELECT * FROM benutzerdaten WHERE benutzername = '" . $_GET['user'] . "' AND passwort = '" . $_GET['pass'] . "';"; $selectErgebnis = $verbindung->query($SQL_QUERY);

Geben wir nun zum Beispiel als Benutzername "Tarik" ein und das Passwort "gutesPasswort" (das natürlich nicht stimmt. In unserer Datenbank ist das Passwort "abc123" eingetragen), so erhalten wir folgende Ausgabe und Abfrage:

Was wäre nun aber, wenn wir als Passwort, folgendes eingeben: (' OR '1'='1)

Wie du siehst, wurde durch unser erstes einfaches Anführungszeichen im Passwortfeld die "Passwortabfrage" beendet (passwort = '') und danach OR '1'='1 dran gehängt, welches durch unseren PHP-Code zu OR '1'='1' vervollständigt wurde. Das war nun eine SQL-Injection: Die Bedingung OR '1'='1' ist nämlich immer true. Wir können uns also, ohne das Passwort zu kennen, anmelden.

SQL-Injection: So kann man sich schützen

Das aller wichtigste: Wie kann man so was verhindern? In der Dokumentation von PHP werden sehr viele verschiedene Maßnahmen aufgelistet. Du kannst diese hier nachlesen. Eine einfache Möglichkeit ist es, "Prepared Statements" zu benutzen. Dabei werden die SQL-Abfrage so vorgefertigt oder vorbereitet, dass das Einschleusen von fremden SQL-Code unmöglich ist. Sehen wir uns ein Beispiel an.

Wir können diesen Code:

$SQL_QUERY = "SELECT * FROM benutzerdaten WHERE benutzername = '" . $_GET['user'] . "' AND passwort = '" . $_GET['pass'] . "';"; 
$selectErgebnis = $verbindung->query($SQL_QUERY);

Zu diesem hier abändern:

// Vorbereitung eines neuen SQL-Statements die Fragezeichen sind Parameter, welche wir gleich setzen
// PHP bzw. mysqli kümmert sich darum, dass kein fremder SQL-Code eingeschleust wird.
$SQL_QUERY = $verbindung->prepare("SELECT * FROM benutzerdaten WHERE benutzername = ? AND passwort = ?;");
// "ss" bedeutet, dass wir zwei mal ein String, also ein Text übergeben wollen und danach geben wir in der Reihenfolge mit den ? die Werte an, welche wir übergeben wollen
$SQL_QUERY->bind_param("ss",  $_GET['user'], $_GET['pass']);
// Ausführen der SQL-Abfrage
$SQL_QUERY->execute();
// Das Ergebnis in einer Variable speichern
$selectErgebnis = $SQL_QUERY->get_result();

Unser injizierter SQL-Code wir jetzt normal als Abfrage verwendet d.h. wir prüfen, ob es einen User "Tarik" gibt, mit dem Passwort (' OR '1'='1). Das Passwort ist falsch und erhalten deshalb die zugehörige Nachricht.

Unser prepare-Statement hat also funktioniert.

Fazit

Generell sollte man immer aufpassen, wenn ein Nutzer einen bestimmten Input gibt. Es gibt nicht nur einen Weg, wie man SQL-Injections verhindern kann. Ein Weg bei PHP ist es, "prepared statements" zu verwenden. Bei anderen Technologien wie beispielsweise Python oder ASP.NET gibt es ähnliche Maßnahmen, welche man ergreifen kann. Achte also immer darauf, wo eventuell ein Nutzer bösartigen SQL-Code einführen kann.