MySQL/PHP-Skripte absichern und schützen - Verhindern von MySQL-Injections, Datenmanipulation & Co.

Öffentliche Server wie ein klassischer Webserver stehen 24/7 die Woche im Blickfeld der Öffentlichkeit. Das ist letztendlich gewollt, so möchte man der breiten Masse rund um die Uhr Informationen zur Verfügung stellen. Leider gibt es unter den Besuchern auch einige schwarze Schaafe, die unbefugt Zugriff auf den Server erreichen oder den Betrieb stören möchten. Leider ist in den letzten Jahren eine rasant ansteigende Anzahl von Hacker-Angriffen zu beobachten und immer mehr Webseiten bzw. öffentliche Dienste im Internet werden attackiert (siehe Forum-Topic Hacker-Sammelthread).

Aus diesem Grund ist der Administrator gefordert, die Web-Applikationen (in der Regel PHP-Skripte in Verbindung mit einer MySQL-Datenbank) abzusichern. Der Fokus liegt primär auf allen PHP-Skripten, bei denen Nutzer Daten eingeben können. Das können Eingabemasken sein, um Listen zu Filtern und zu sortieren. Aber auch Formulare wie Gästebücher, Foren oder eine Kommentar-Funktion, bei denen Eingaben des Nutzers gespeichert werden.

In diesem Zusammenhang ist immer wieder die Rede von so genannten SQL-Injections, welche in diesem Artikel besprochen werden. Wikipedia fasst diesen Begriff sehr schön zusammen: "SQL-Injection (dt. SQL-Einschleusung) bezeichnet das Ausnutzen einer Sicherheitslücke in Zusammenhang mit SQL-Datenbanken, die durch mangelnde Maskierung oder Überprüfung von Metazeichen in Benutzereingaben entsteht. Der Angreifer versucht dabei, über die Anwendung, die den Zugriff auf die Datenbank bereitstellt, eigene Datenbankbefehle einzuschleusen. Sein Ziel ist es, Daten auszuspähen, in seinem Sinne zu verändern, oder Kontrolle über den Server zu erhalten."

Wo besteht die Gefahr bei $_GET und $_POST?

# Aufruf: index.php?aid=50

$article_id = $_GET['aid']
SELECT * FROM tbl_article WHERE id = $aid

Das Problem: Jeder kann die URL manipulieren und die mitgegebenen Paramater ändern. Da diese Parameter ungeprüft in den SQL-Query eingebunden werden, können Datenbank-Manipulationen problemlos durchgeführt werden.

Beispiel: Die URL wird wiefolgt geändert: index.php?aid=50 UNION SELECT benutzername, passwort FROM tbl_benutzer WHERE user_id = 1/*. Das Ergebnis ist, dass Benutzername samt Passwort ausgegeben werden, was ziemlich dramatisch ist.

Gefahr durch unkontrollierte URL-Parameter
Gefahr durch unkontrollierte URL-Parameter

MySQL Injection Test Tools - Wie kann man testen, ob das Skript gefährdet ist?

Ein nützliches Tool, mit dem man sein PHP-/MySQL-Skript bezüglich eines SQL-Injection Angriffes testen kann, ist Havij Advanced SQL Injection. Das Programm läuft unter Windows und testet automatisch mögliche Angriffe, um Daten aus der SQL-Datenbank über $_GET und $_POST Manipulationen auszuspionieren.

Havij - SQL-Injection Angriffe testen
Havij - SQL-Injection Angriffe testen
Erfolgreicher Hack: Havij kann Datenbanken, Tabellen und Datensätze auslesen
Erfolgreicher Hack: Havij kann Datenbanken, Tabellen und Datensätze auslesen

Schutzmaßnahme 1: Aktuelle Versionen verwenden

Eine allgemeine Regel ist, die eingesetzte Software möglichst auf dem neuesten Stand zu halten. Hierbei sollte darauf geachtet werden, dass die jeweilige Version auch als Stable deklariert wurde. Bekannte Sicherheitslöcher werden von der Entwicklergemeinde in neuen Versionen behoben, so dass man veraltete Software nicht einsetzen sollte.

Schutzmaßnahme 2: MySQL-Berechtigungen: read only!

Überlegen wir uns ein Worst Case Szenario: ein unbefugter Dritter hat über eine Sicherheitslücke Zugriff auf die MySQL-Datenbank bekommen. Er agiert nun mit den Rechten, mit welchen sich das PHP-Skript gegenüber der MySQL-Datenbank authentifiziert hat. An diesem Punkt wäre es dramatisch, wenn die MySQL-Verbindung mit Root-Rechten oder ähnlichen Rechten zustande gekommen ist.

Verbindungen zur SQL-Datenbanken sollten möglichst strikt erfolgen. D.h. der SQL-User hat nur Zugriff auf die Tabellen und Datenbanken, die benötigt werden. So können kritische Daten geschützt werden (bsp. die mysql table mit Passwörtern). Wenn Daten nur gelesen werden, sollte der SQL-Benuter auch nur read-only Berechtigungen besitzen, damit ein unbefugter Dritter keine Daten (Login, Passwort etc.) ändern kann.

Ein Blick in die MySQL-Berechtigungen ist ein absolutes Muss. PHPMyAdmin ist ein einfach und intuitiv zu bediendes Tool, um diese Aufgabe zu erledigen.

MySQL-DBMS: Berechtigungen für Datenbanken und Tabellen sollten möglichst strikt gesetzt werden
MySQL-DBMS: Berechtigungen für Datenbanken und Tabellen sollten möglichst strikt gesetzt werden

Schutzmaßnahme 3: Verwende die Funktion mysql_real_escape_string

mysql_real_escape_string sorgt dafür, dass bestimmte Zeichen wie das einfach Hochkommata nicht als Metazeichen, sondern als einfacher String interpretiert wird. Vereinfacht gesagt: wichtige Zeichen, welche den SQL-Query beeinflussen könnten, werden als einfache Zeichen deklariert. Es ist zum Beispiel nicht möglich, einen Wert mittels ' zu unterbrechen, um weitere SQL-Anweisungen einzuschleusen. In folgendem Beispiel wird aus ' ein \' und somit als (sinnloser) String an die Datenbank übermittelt.

$foo = " ' OR login = 1";
echo mysql_real_escape_string($foo); # Ausgabe: \' OR login = 1

Aus diesem Grund sollte jedem Wert, der von Extern eingegeben wurde, mit einem vorangeschaltetem mysql_real_escape_string an die Datenbank weitergegeben werden. Hier ein kurzes Beispiel von der offiziellen PHP-Seite (Hinweis: sprintf ist nicht zwingend notwendig. Wichtig ist, dass die Eingabewerte mit mysql_real_escape_string verarbeitet werden):


// Verbindung herstellen
$link = mysql_connect('mysql_host', 'mysql_user', 'mysql_password')
OR die(mysql_error());

// Anfrage erstellen
$query = sprintf("SELECT * FROM users WHERE user='%s' AND password='%s'",
mysql_real_escape_string($user),
mysql_real_escape_string($password));

Schutzmaßnahme 4: Nur sinnvolle Werte zulassen

Ein Türsteher vor der Diskothek ist dafür zuständig, um beispielsweise keine Jugendliche hereinzulassen. Kein Betreiber käme auf Idee, jedem blind zu vertrauen. Genauso ist es bei Formularen. Wenn man beispielsweise nur Integer-Zahlen erwartet, dann sollte alles andere prinzipiell verboten sein.

Beispiel: in einem Formular soll das Alter eingegeben werden. In diesem Fall sind nur Ganzzahlen erlaubt, die maximal (sagen wir großzügig) einen Wert zwischen 0 und 120 haben dürfen. Die Datenbankabfrage erfolgt also nur, wenn diese Bedingungen erfüllt sind. Das könnte wie folgt aussehen:

// Prüfe Typ
if (!is_numeric($eingabe)) {
die("Der Wert ist keine Zahl!");
}

// Prüfe Länge
if ($eingabe < 0 OR $eingabe > 120) {
die("Der Wert ist negativ oder zu groß!")
}

Als zusätzliche Sicherheit kann man noch intval verwenden. Diese Funktion extrahiert nur Zahlen aus einem Wert, so dass andere Zeichen elimiert werden:

$query = "SELECT * FROM articles WHERE produktid = '" . intval($eingabe) ."'";

Ähnlich kann man es bei Strings umsetzen, sprich bei Texteingaben. Folgendes PHP-Konstrukt prüft die Formular-Eingabe, ob nur bestimmte Zeichen (Groß-/Kleinbuchstaben, Zahlen und einige ausgewählte Sonderzeichen) in dem String enthalten sind.

if (!preg_match("/^([a-zA-Z0-9öäüßÖÄÜß?!,.;:_() \n\r-]+)$/is", $txt)) {
die("Unerlaubte Zeichen enthalten!");
}

Es lohnt sich, die Formular-Felder mittels regex bzw. der PHP-Funktion preg_match zu prüfen.

So könnten Werten aus einem Formular sicher in eine MySQL-Datenbank gespeichert werden.

function isValidString ($varValue, $varMaxValue = 0) {

$checkOK = true;
$errorMsg = "";
// Prüfe String
if (!preg_match("/^([a-zA-Z0-9öäüßÖÄÜß?!,.;:_()@ \n\r-]+)$/is", $varValue)) {
$checkOK = false;
$errorMsg .= "Error 102. ";
}

// Prüfe Länge
if ($varMaxValue > 0) {
if (strlen($varValue) > $varMaxValue) {
$checkOK = false;
$errorMsg .= "Error 103. ";
}
}

return $checkOK;
}

if (
!(
isValidString($_POST['TEXT'])
AND isValidString($_POST['NAME'])
AND isValidString($_POST['EMAIL'])
)
) {
... FEHLER!!! ...
} else {
... SQL INSERT INTO foo ...
}

Fazit & Verbesserungsvorschläge

PHP-Skripte sicherer machen ist eine wichtige Aufgabe. Kritik, Verbesserungsvorschläge und Tipps sind daher gerne erwünscht. Dazu einfach eine E-Mail an info@pc-erfahrung.de schicken oder folgenden Topic in unserem Forum aufsuchen:
Verhindern von MySQL-Injections, Datenmanipulation & Co.@ pce-forum.de

Vielen Dank an dieser Stelle an Mirko Wessels, der auf freiwilliger Basis auf Schwachstellen hingewiesen hat. Aus seiner ehrlichen Motivation ist dieser Artikel entstanden.

Weiterführende Links

The Open Web Application Security Project (Englisch)
Testing for SQL Injection (OWASP-DV-005) (Englisch)
PHP-NEt.de - SQL Injection
SQL-Injection @Wikipedia