PHP > SQL-Injection > Best Practice

Zur Beginn erst noch eine kleine Einführung in SQL-Injections.

Was ist eine SQL-Injection?
Wenn Jemand versucht eine Benutzereingabe so zu manipulieren, dass der generierte SQL-Befehl etwas anderes tut, als er soll. Nehmen wir mal folgendes Szenario an: Wir schicken eine URL an unseren WordPress-Blog, die einen zusätzlichen SQL-Befehl ausführt – nämlich die Tabelle xyz löschen.

http://web-union.de/?id=1; DROP TABLE xyz; --

Das generierte SQL-Query würde dann so aussehen:

$sql = "SELECT * FROM posts WHERE id = '{$_GET['id']}'";
SELECT * FROM posts WHERE id = '1'; DROP TABLE xyz; --';

Mit den letzten beiden Strichen wird das schließenden Anführungszeichen und das Semikolon am Ende der SQL-Query einfach auskommentiert.

Und wie lassen sich SQL-Injections effektiv verhindern?
Dazu gibt es einige Methoden, die ich unter anderem auch für Yumee verwende.

1. Benutzereingaben escapen
Das ist relativ schnell gemacht und sollte im Normalfall genug Schutz gegen Angreifer bieten. Ein blinder Angriff wird somit eigentlich ausgeschlossen.

$_GET = array_map('mysql_real_escape_string', $_GET);
$_POST = array_map('mysql_real_escape_string', $_POST);
$_COOKIE = array_map('mysql_real_escape_string', $_COOKIE);

Wer möchte kann so zusätzlich noch $_REQUEST und $_FILES escapen.

Damit diese Methode wirklich sicher funktioniert, muss bei der Verwendung noch einiges beachtet werden:

  • Die Benutzereingaben innerhalb der SQL-Query sollten immer mittels Hochkommata umschlossen werden. Wenn dies nicht möglich ist, unbedingt den Inhalt vorher prüfen (z.B. bei Feldernamen).
  • Auf keinen Fall die Eingaben mit einer anderen Funktion escapen. addslashes() und magic_quotes_gpc sind nicht sicher können ausgetrickst werden.

2. Value Binding mit PDO
Die sicherste Lösung ist wahrscheinlich das Value-Binding. Dabei werden die Werte erstmal durch Platzhalter vorbelegt und später von der PDO-Erweiterung aus PHP eigenständig ersetzt und vorher natürlich escaped. Hier mal ein kleines Beispiel:

$pdo = new PDO('mysql:dbname=DatenbankName;host=localhost', 'benutzer', 'ultra_sicheres_passwort');
$stmt = $pdo->prepare('SELECT * FROM blog_posts WHERE post_title LIKE ?');
$stmt->bindValue(1, $_POST['suche'], PDO::PARAM_STR);
if($stmt->execute())
{
	$result = $stmt->fetchAll();
}

Hier müssen keine Anführungszeichen oder Hochkommata verwendet werden. Die werden später von PDO automatisch – abhängig vom angegebenen Datentyp – gesetzt. Die MySQLi-Extension besitzt nebenbei bemerkt eine ähnliche Funktion.
Diese Lösung bedeutet zwar etwas mehr Code, dafür ist sie aber sauberer und bedeutend sicherer.

3. PHP IDS
Ich hatte bereits vor einiger Zeit über PHP IDS berichtet. Dabei handelt es sich um eine Bibliothek, die vor die eigentliche Anwendung geschaltet wird und vor dem Start sämtliche Benutzereingaben überwacht. Dabei werden nicht nur SQL-Injections erkannt, sondern auch XSS und andere schädliche Eingaben. Eventuelle Angriffe können so geloggt werden, oder auch direkt per Mail an einen Administrator verschickt werden.

Weitere Tipps und Best Practice:

  • Wenn ihr sicher seid, dass die Eingabe eine Zahl sein muss, reicht es auch die Variable in eine Zahl umzuwandeln (z.B. mit dem (int)-Cast). Das ist sicherer als jedes Escapen.
  • Schreibt sauberes SQL und umschließt am besten alles in Hochkommata was möglich ist. Nur so kann das Escapen wirklich funktionieren.
  • Verlasst euch nicht auf die PHP-internen Funktionen magic_quotes_runtime und magic_quotes_gpc. Diese sind seit PHP 5.3 sowieso veraltet und werden in PHP 6 mangels Sicherheit komplett entfernt.
  • Wer wirklich sicher gehen will, überprüft immer alle Eingaben vom Benutzer, ob diese überhaupt einen gültigen Wert haben. Unbedingt auch Tabellen- und Spaltennamen beachten, eben alles was vom Benutzer zur Datenbank geleitet wird.

7 einfache Tipps zur Verbesserung von Code-Qualität

PHP ist dafür bekannt, dass es kaum Anwendungen mit qualitativ hochwertigen Code gibt. Dabei gibt es viele Tools, die dabei helfen mit relativ wenig Aufwand die Code-Qualität enorm zu steigern. Die 7 einfachsten – und meiner Meinung nach auch wichtigsten – Tipps zur Steigerung von Code-Qualität habe ich hier einmal zusammengestellt.
Auch wenn diese Tipps speziell für PHP zugeschnitten sind, kann man fast alle Tipps auch für andere Programmiersprachen anwenden.

  1. Versionskontrolle verwenden
    Tools wie Subversion oder Git helfen das Versions-Wirr-Warr bei Anwendungen besser verwalten zu können. Und wenn Fehler auftreten, lässt sich schnell eine alte Version zurückholen, oder ein Patch erstellen, der anderen Entwicklern zur Verfügung gestellt werden kann.
    http://subversion.tigris.org/
    http://git-scm.com/
  2. Unit-Tests schreiben
    Wenn man ein neue Anwendung entwickelt, dann sollte man direkt zu Beginn Unit-Tests für jede Komponente schreiben. Das spart extrem viel Zeit, wenn die Anwendung zu einem späteren Zeitpunkt erweitert oder geändert wird und nochmal getestet werden muss.
    http://www.phpunit.de/
    http://www.junit.org/
  3. Profiling
    Ab und zu macht es Sinn einmal ein komplettes Profil der Anwendung generieren zu lassen. Dabei werden alle Funktionsaufrufe aufgenommen, die Zeit festgehalten und anschließend geguckt an welchen Stellen die Anwendung zu langsam ist oder sich die Anzahl der Funktionsaufrufe optimieren lässt. Das ist ein wichtiger Faktor für die Performance-Optimierung der Anwendung.
    http://www.xdebug.org/
  4. Kommentare
    Der komplette Code sollte immer mit einheitlichen Kommentarblöcken versehen werden, die Klassen, Funktionen oder Interfaces genauer beschreiben. Das hilft nicht nur dabei eine technische Dokumentation des Codes generieren zu lassen, sondern kann auch von einigen IDEs gelesen werden und unterstützt somit die Autovervollständigung bei der Entwicklung.
    http://www.phpdoc.org/
    http://www.stack.nl/~dimitri/doxygen/index.html
  5. Einheitlicher Code (Coding Standards)
    Der Code sollte immer einem einheitlich Coding Standard folgen. Das heißt gleiche Abstände, Einrückungen, Anführungszeichen, Namenskonventionen oder Zeilenumbrüche. Dadurch lässt sich der Code von jedem Entwickler leichter lesen.
    Manchmal gibt es auch Tools, die den Code untersuchen und anschließend überprüfen ob die vorher definierten Standards tatsächlich überall eingehalten wurden.
    http://pear.php.net/package/PHP_CodeSniffer
  6. Doppelter Code
    Manchmal findet man Code-Fragmente, die sich bis auf wenige Parameter gleichen, oder die selbe Aufgabe haben. Hier sollte man unbedingt überprüfen, ob sich diese Fragmente nicht ausgelagern lassen in eine eigene Klasse oder Methode. Auch dazu gibt es wieder Tools, die solche Stellen im Code automatisiert finden können.
    https://github.com/sebastianbergmann/phpcpd
  7. Code-Trennung
    Dieser Punkt ist besonders bei Webanwendungen wichtig: Der Code von verschiedenen Sprachen sollte immer getrennt in eigenen Dateien liegen. JavaScript-Code und CSS-Anweisungen haben nichts im HTML-Quellcode zu suchen. Und bei PHP-Templates z.B. sollte darauf geachtet werden, dass diese möglichst wenig Programmlogik enthalten. Wer mag, kann auch eine Template-Engine verwenden.

Wer sich an diese Tipps hält, hat schonmal ein solides Grundgerüst für eine hohe Code-Qualität in seiner Anwendung.

Laufvariablen mit MySQL

Wenn man mit PHP eine Laufvariable bauen will, dann weiß jeder sofort wie das geht. Nehmen wir beispielsweise mal eine Ranking, das die Punkte und den aktuellen Rang einer Benutzertabelle ausgeben soll. Das Skript würde dann so ähnlich aussehen:

$position = 1;
$result = mysql_query("SELECT benutzername, punkte FROM benutzer ORDER BY punkte DESC");
while($row = mysql_fetch_assoc($result))
{
	echo "{$row['benutzername']} - {$row['punkte']} Punkte, Rang {$position}<br>";
	$position++;
}

Aber wie löst man sowas, wenn man nur MySQL zur Verfügung hat?
Im Grunde funktioniert das genau wie in PHP, nur die Syntax ist ein wenig anders. Es wird genau wie in PHP eine neue Variable deklariert und diese beim jeden ausgelesenen Datensatz hochgezählt.
Das geänderte SQL-Query sieht dann so aus:

SET @position = 0;
SELECT @position := @position + 1 AS position, benutzername, punkte FROM benutzer ORDER BY punkte DESC;

In der ersten Zeile wird die Variable @position definiert und innerhalb des SELECT-Aufrufs wird die Variable dann jeweils um eins erhöht und der neue Wert zwischengespeichert.

So lässt sich zum Beispiel das aktuelle Ranking der Benutzertabelle direkt über MySQL in eine zweite Tabelle abspeichern, ohne jeden Datensatz einzeln mit PHP auslesen und wieder neu einfügen zu müssen:

SET @position = 0;
INSERT INTO benutzer_ranking (position, benutzername, punkte) SELECT @position := @position + 1 AS position, benutzername, punkte FROM benutzer ORDER BY punkte DESC;

Mir fällt gerade auf, dass dies hier der 50. Artikel auf meinem Blog ist – ein Jubiläum 🙂
Dann muss ich mir für den nächsten Artikel etwas besonderes einfallen lassen.