SQL-Injection kurz erklärt

Die SQL-Injection ist eine häufige, weil einfache und effiziente Angriffsform. Ihre Gefährlichkeit liegt darin, dass Angreifer unter Ausnutzung bestimmter Techniken Zugang zur Datenbankschnittstelle erlangen und damit beliebige SQL-Abfragen einschleusen (”injiziieren”, daher der Name) können. Die Möglichkeiten sind weitreichend und gehen vom Löschen der kompletten Datenbank (und damit aller Datenbestände auf welche die Applikation zugreift) über das Ausspähen von sensiblen, geschützten Daten bis hin zur Übernahme der Web-Applikation durch den Angreifer indem er Administratorrechte erlangt.

Aus Sicht des Serveradministrators ist es schwierig SQL-Injections konkret zu verhindern, er kann lediglich Schutzmaßnahmen ergreifen. Die sicherste und einzig wirklich effektive Möglichkeit, einem Angriff durch SQL-Injection zu begegnen ist keine solche Lücken zu produzieren. Anders als bei anderen Problemen (beispielsweise SYN-Floods) ist es nämlich in der Verantwortung des Programmierers, der die Web-Applikation schreibt, diesem Problem zu entgegnen. Die beste Methode ein Problem zu verstehen ist es, dieses aus Sicht des Angreifers anzugehen.

Die Angreifersicht - Das Problem

In dynamisch generierten Web-Applikationen gibt es eine Reihe von Features, die eine Privilegienseparation erfordern. Jedes Blog, jedes Forum und jedes Content Management System kennt Benutzer und zugehörige Rechte und Privilegien. Wenn ich hier einen Beitrag verfasse, logge ich mich mit meinen Benutzerdaten in das Backend ein und verfasse einen Beitrag. Der normale Benutzer hat diese Rechte nicht. Das selbe gilt, wenn ich einen Beitrag löschen oder für eine gesonderte Benutzergruppe vorbehalten möchte.

Der Angreifer hat diese Rechte nicht, möchte sie aber natürlich erlangen oder die Rechteüberprüfung zumindest umgehen. Über eine SQL-Injection versucht der Angreifer präparierte Anfragen einzuschleußen. Es gibt dafür grundsätzlich zwei Vorgangsweisen: In der einen Variante kennt der Angreifer das auf der Website eingesetzte Script (zum Beispiel weil die anzugreifende Website ein freies Programm einsetzt, beispielsweise die Blogsoftware “Wordpress”). Ist das der Fall kennt der Angreifer den Quellcode und Datenbankschema und muss nicht raten. Dies hat den Vorteil, dass der Angreifer die Art und Beschaffenheit der Datenbank kennt und entsprechend weiß, welche Sonderzeichen der SQL-Parser verarbeitet (zum Beispiel “\“, “,“, “;“), welchen SQL-Dialekt die Datenbank verarbeitet und wie das Datenbankschema, Spaltennamen und Datenbeschaffenheit aussieht (zum Beispiel die Zusammensetzung der “user“-Tabelle). Manche Applikationen machen es dem Angreifer auch besonders einfach und geben gar die SQL-Fehlermeldung mit aus, die für den Angreifer in diesem Fall sehr nützlich ist, weil sie die Arbeit enorm erleichtern. Wer kennt zum Beispiel nicht die Meldung “Access denied for user ‘root’@'localhost’ (using password: YES)“, die auch nur die Ausgabe einer Datenbankfehlermeldung ist.

Blind SQL-Injection

Hat der Angreifer diesen Wissensvorteil nicht, bedient er sich einer sogenannten Blind SQL-Injection. Das ist die wesentlich komplexere Angriffsform und wird an dieser Stelle daher nur kurz umrissen. Dabei schließt der Angreifer von der Art und Beschaffenheit von (nichtssagenden) Fehlermeldungen auf die Ursache in der Applikation. Der Angreifer “lernt” daher aus Fehlermeldungen und gewinnt Informationen aus Fehlern. Angenommen in einem Web-Shop wird ein bestimmtes Produkt über die Abfrage “/get_product.php?product=1337” ausgewählt. Verändert der Angreifer nun diese Anfrage auf das nicht existierende “product=2342” gibt die Applikation für gewöhnlich einen Fehler aus, zum Beispiel “Kein derartiges Profukt vorhanden“, übergibt er aber “product=testest” als Parameter ist das Ergebnis ein serverseitiger Fehler (zum Beispiel Statuscode 500). Das ist dann ein Indiz, dass eine SQL-Injection vorliegt. Von da an versucht der Angreifer dann durch gezielte, fehlerhafte Anfragen Erkenntnisse über die Anwendung zu gewinnen:

  • Ermittlung des verstandenen SQL-Dialekts durch plattformspezifische Besonderheiten (”||” deutet auf ORACLE oder PostgreSQL hin, “+” auf MS-SQL, “CONCAT()” auf MySQL und so weiter)
  • Verwendung von Sonderzeichen (”;”) und Syntax im SQL-Dialekt (”OR 1=1“) als Parameter und beachten der Folgen
  • Verwendung von semantisch korrekten Anfragen um auf den Datentyp der referenzierten Spalte zu schließen (”product=1332+1” oder “product=CONCAT(’133′,’7′)“)
  • Ermittlung valider Benutzer und Passwörter durch Raten, zum Beispiel durch langwieriges durchprobieren: “user=b%” - gibt Fehler, “user=a%” nicht, danach von vorne mit “user=aa%” - Fehler, “user=ad%” nicht und so weiter.

(Offene) SQL-Injection

Etwas detaillierter will ich auf die klassische, offene SQL-Injection eingehen. Dazu verwende ich ein simples Shoutbox-Skript. Benutzer legen sich einen Benutzerzugang an und können dann Kommentare verfassen. Zusätzlich gibt es einen Administrator; dieser hat einen Backendzugang über den er diese Kommentare freischalten kann. Alle für die Demonstration nicht nötigen Dinge wurden entfernt, so gibt es keine Möglichkeit hier tatsächlich Kommentare zu erzeugen oder freizuschalten, es handelt sich also lediglich um ein Teilabbild, das aber alles relevante zeigt (das komplette Skript gibts auch zum Download).

Dem Skript zu Grunde liegt folgende Datenbank:

mysql> DESCRIBE user;
+----------+--------------+------+-----+---------+-------+
| FIELD    | Type         | NULL | KEY | DEFAULT | Extra |
+----------+--------------+------+-----+---------+-------+
| owner    | int(11)      | YES  |     | NULL    |       | 
| name     | varchar(255) | YES  |     | NULL    |       | 
| password | varchar(255) | YES  |     | NULL    |       | 
+----------+--------------+------+-----+---------+-------+
3 rows IN SET (0.00 sec)
 
mysql> SELECT * FROM user;
+-------+---------------+----------+
| owner | name          | password |
+-------+---------------+----------+
|     1 | administrator | geheim   |
|     2 | benutzer      | geheimer |
+-------+---------------+----------+
2 rows IN SET (0.00 sec)
 
mysql> DESCRIBE shouts;
+------------+------------+------+-----+---------+-------+
| FIELD      | Type       | NULL | KEY | DEFAULT | Extra |
+------------+------------+------+-----+---------+-------+
| owner      | int(11)    | YES  |     | NULL    |       |
| shout      | text       | YES  |     | NULL    |       |
| visibility | tinyint(1) | YES  |     | NULL    |       |
| id         | int(11)    | NO   |     | NULL    |       |
+------------+------------+------+-----+---------+-------+
4 rows IN SET (0.00 sec)
 
mysql> SELECT * FROM shouts;
+-------+------------------------------------------+------------+----+
| owner | shout                                    | visibility | id |
+-------+------------------------------------------+------------+----+
|     1 | Hallo IN meiner Shoutbox!                |          1 |  1 |
|     2 | Jo! Dir auch ein Hallo                   |          1 |  2 |
|     1 | Ein geheimer, nicht sichtbarer Kommentar |          0 |  3 |
+-------+------------------------------------------+------------+----+
3 rows IN SET (0.00 sec)

Die “user“-Tabelle besteht lediglich aus einer eindeutigen ID, wobei jedem Datensatz ein Benutzername und ein Passwort zugeordnet ist, mit dem die Verifikation der Zugangsdaten ins Backend erfolgt. Die “shouts“-Tabelle besteht aus dem eigentlichen Kommentar, der mit einem Sichtbarkeitsflag (”visibility“) und dem Eigentümer (”owner“) verknüpft ist.

Die Web-Applikationslogik tut nicht viel mehr, als diese Daten einfach auszugeben. Der Benutzer übergibt über die Navigationsleiste in der Fußzeile einen spezifischen Kommentar als Parameter, das Skript überprüft die Leseberechtigung und gibt diesen aus. Der relevante PHP-Code dazu ist dieser:

$shout  = ($_GET['shout']) ? $_GET['shout'] : 1;
...
$result = mysql_query("SELECT user.name, shouts.shout FROM shouts INNER JOIN user USING(owner) WHERE visibility=1 AND shouts.id=$shout");
 
if (mysql_num_rows($result))
{
        while ($row = mysql_fetch_array($result, MYSQL_NUM))
        {
                echo "<hr />";
                echo "<strong>$row[0]</strong> schreibt (#$shout)<br /> $row[1]";
                echo "<hr />";
        }
}
else
{
        echo "<hr />";
        echo "Der angeforderte Kommentar ist nicht öffentlich bzw. vorhanden!";
        echo "<hr />";
}

Solange man keinen Unfug macht, funktioniert das wie gewünscht und sieht so aus, wie es der abgebildete Screenshot zeigt. Die erste Zeile liest aus der Benutzereingabe den angeforderten Kommentar und holt den selben anhand dieses Parameters aus der Datenbank. Scheinbare Sicherheit gibt offenbar die Einschränkung “WHERE visibility=1” die verhindert, das nicht sichtbare Kommentare ausgegeben werden. In dem Fall, dass kein sichtbarer Kommentar vorhanden ist, zweigt die Code-Ausführung ab und verweigert die Ausgabe des Kommentars (”Der angeforderte Kommentar ist nicht öffentlich bzw. vorhanden!“), wie in Bild zwei dargestellt.

Doch ist dies nur eine scheinbare und trügerische Sicherheit, da dieser Code für eine SQL-Injection anfällig ist. Problem ist der Parameter “shout“, dieser wird ohne jede weitere Prüfung an die Datenbank weitergegeben und auch dort nicht richtig behandelt, sondern direkt in das SQL-Query interpoliert. Anfällig ist folgende Zeile im PHP-Code:

Das funktioniert solange, wie die Variable $shout auch tatsächlich rein nummerische Ganzzahlwerte enthält. Gibt der Besucher beispielsweise über den Browser das Argument "2" an das Skript weiter, erzeugt dieser daraus folgende Datenbankabfrage (Die Ausgabe habe ich hinzugefügt, in der Anwendung würde diese an die Applikation übergeben und dort verarbeitet, zum Beispiel über den PHP-Befehl mysql_fetch_array()):

mysql> SELECT user.name, shouts.shout FROM shouts INNER JOIN user USING(owner) WHERE visibility=1 AND shouts.id=2;
+----------+------------------------+
| name     | shout                  |
+----------+------------------------+
| benutzer | Jo! Dir auch ein Hallo |
+----------+------------------------+
1 row IN SET (0.00 sec)

Für den unvorsichtigen Programmierer ist das Problem damit erledigt, denn es funktioniert, was funktionieren soll. Dabei lässt er aber außer Acht, dass interessierte Nutzer beliebige Daten an die Datenbank übergeben könnten. Das ist sogar erschreckend einfach, anstelle eines rein nummerischen Wertes könnte ein Angreifer - ganz simpel - über den Browser - folgende Anfrage senden: “/sqlinject.php?shout=0 OR 1=1“. Der Effekt ist groß, wie das nächste Bildschirmfoto zeigt.

Plötzlich und ganz unerwartet werden nämlich alle Kommentare ausgegeben, auch die eigentlich unsichtbaren. Der Grund ist so simpel wie naheliegend: MySQL interpretiert die Benutzereingabe als SQL-Code und führt eben das aus, was der Benutzer über den Browser einschleust (daher auch “Injection” - Englisch für “Injektion”). Die Applikation macht nämlich aus der genannten Anfrage folgenden PHP Code:

mysql> SELECT user.name, shouts.shout FROM shouts INNER JOIN user USING(owner) WHERE visibility=1 AND shouts.id=0 OR 1=1;
+---------------+------------------------------------------+
| name          | shout                                    |
+---------------+------------------------------------------+
| administrator | Hallo IN meiner Shoutbox!                |
| benutzer      | Jo! Dir auch ein Hallo                   |
| administrator | Ein geheimer, nicht sichtbarer Kommentar |
+---------------+------------------------------------------+
3 rows IN SET (0.00 sec)

Das “OR 1=1” macht die Bedingung, welche die Auswahl eigentlich einschränken sollte zur Tautologie, einer immer erfüllten Bedingung. Ganz egal welche Einschränkungen vorher durch den Programmierer gemacht wurden, dadurch wird jede Sicherheitsbeschränkung umgeben.

Einen ganz ähnlichen Effekt erzielt der Angreifer bei Verwendung von “UNION SELECT“-Anfragen. Es handelt sich dabei um Unterabfragen, die mit der ursprünglichen Anfrage untereinander verknüpft werden. Sinnvolle Anwendungen dafür sind beispielsweise die Aggregation von unterschiedlichen Tabellen in eine einzelne Abfrage, wenn sich diese zusammen sinnvoll ergänzen (also in Selektionskriterien, wie der Zahl gewählter Spalten übereinstimmen). Auch dafür ist die Shoutbox anfällig, wie ein neuerlicher Screenshot beweist, der die Browseranfrage “/sqlinject.php?shout=3 UNION SELECT 0,shout FROM shouts” zeigt. Diese erzeugt nämlich folgenden SQL-Code, der sämtliche Kommentare anzeigt, obwohl der eigentlich angefragte 3. Kommentar nicht sichtbar sein sollte:

 
mysql> SELECT user.name, shouts.shout FROM shouts INNER JOIN user USING(owner) WHERE visibility=1 AND shouts.id=3 UNION SELECT 0,shout FROM shouts;
+------+------------------------------------------+
| name | shout                                    |
+------+------------------------------------------+
| 0    | Hallo IN meiner Shoutbox!                |
| 0    | Jo! Dir auch ein Hallo                   |
| 0    | Ein geheimer, nicht sichtbarer Kommentar |
+------+------------------------------------------+
3 rows IN SET (0.00 sec)

Die “0″ steht allein deswegen da, damit die Anzahl selektierter Spalten mit der ursprünglichen Anfrage übereinstimmt. Doch sind “Union Selects” nicht auf ein und die selbe Tabelle beschränkt. Das macht diese erst richtig gefährlich. Während man durch eine Tautologie wie zuvor beschrieben lediglich alle Datensätze aus einer Tabelle auslesen kann, ermöglicht es ein “Union Select” dieses für die gesamte Datenbank zu tun.

Der abschließende Screenshot zeigt die Gefährlichkeit von solchen SQL-Injections. Durch eine Lücke in der Anzeigefunktion für Kommentare kann plötzlich der gesamte Inhalt der Benutzerdatenbank ausgegeben werden, einschließlich der Benutzernamen und Passwörter registrierter Benutzer. Die Browseranfrage “/sqlinject.php?shout=0 UNION SELECT name, password FROM user” tut genau dies, denn diese erzeugt folgenden SQL Code:

mysql> SELECT user.name, shouts.shout FROM shouts INNER JOIN user USING(owner) WHERE visibility=1 AND shouts.id=0 UNION SELECT name, password FROM user;
+---------------+----------+
| name          | shout    |
+---------------+----------+
| administrator | geheim   |
| benutzer      | geheimer |
+---------------+----------+
2 rows IN SET (0.00 sec)

Natürlich wäre es für den Angreifer ein leichtes Spiel, sich über das hieraus gewonnene Passwort Zugang zum System zu verschaffen. Dieses Beispiel zeigt übrigens auch einen guten Grund, warum auch in der Datenbank Passwörter verschlüsselt werden sollten. Ein SHA1-Hash des so erschlichenen Passworts wäre nämlich nutzlos.

Die Lösung

Die gute Nachricht ist, SQL-Injections kann man ganz einfach verhindern, man muss nur sauber programmieren. Die schlechte Nachricht ist, die wenigsten tun das (wirklich konsequent). Deshalb kann (und sollte) man sich bei manchen Skripten auch nicht darauf verlassen, das wirklich sauber programmiert wurde. Mit zunehmender Komplexität wird diese Aufgabe auch in der Tat immer schwieriger und unübersichtlicher. Abhilfe und dennoch sehr einfach zu bedienen ist PHPIDS.
Es ist dies ein web-basiertes Einbruchserkennungssystem (IDS). Es tut nichts anderes als Benutzeringaben zu überprüfen und abzufangen. Aktiviert man dieses beispielsweise für obiges, fehleranfälliges Skript verweigert dieses plötzlich seinen Dienst, wenn man eine SQL-Injection versucht, mit der Meldung:

 
Variable: GET.shout | Value: 3 UNION SELECT 0,shout FROM shouts
Impact: 12 | Tags: sqli, id, lfi
Description: Detects basic SQL authentication bypass attempts 2/3 | Tags: sqli, id, lfi | ID: 45
Description: Detects MSSQL code execution and information gathering attempts | Tags: sqli, id | ID: 55

Dafür reicht es aus, PHPIDS zu laden und auf Auffälligkeiten hin zu prüfen, folgender PHP-Code - natürlich vor jeder weiteren Bearbeitung aktiviert das System:

set_include_path(get_include_path() . PATH_SEPARATOR . './phpids/lib/');
require_once 'phpids/lib/IDS/Init.php';
 
 
$request = array(
        'REQUEST' => $_REQUEST,
        'GET' => $_GET,
        'POST' => $_POST,
        'COOKIE' => $_COOKIE
);
 
$init = IDS_Init::init(dirname(__FILE__) . '/phpids/lib/IDS/Config/Config.ini.php');
 
$init->config['General']['base_path'] = dirname(__FILE__) . '/phpids/lib/IDS/';
$init->config['General']['use_base_path'] = true;
 
 
$ids = new IDS_Monitor($request, $init);
$result = $ids->run();
 
if (!$result->isEmpty())
{
        echo $result;
        exit;
}

Natürlich kann und will ein IDS kein vollständiger und ausreichender Schutz sein. Wie jedes Einbruchserkennungssystem basiert es auf Heuristiken und erkennt gängige Sicherheitsprobleme. Es ist jedoch keinesfalls ein all umfassendes Sicherheitskonzept, auf das man sich verlassen sollte. Die Sicherheit ist nämlich nur dann gegeben wenn, das System vollständig und auf dem aktuellsten Stand ist. Leider ist es aber gerade nun einmal ein Prinzip von Sicherheitslücken neue, unbekannte Probleme auszunutzen.

Ein IDS kann und soll also immer nur Ergänzung zu ohnehin etablierten Lösungen sein, vor allem wenn die Lösung wie im konkreten Problem durch sauberes Programmieren umgangen werden kann. Hier gibt es zwei Ansätze, die das Problem mit Sicherheit aus der Welt schaffen. Zum einen ist dies die saubere Behandlung von Benutzereingaben (genau das selbe Prinzip, das auch Directory Traversal Schwachstellen behebt). In diesem Fall reicht es also aus, den “shout“-Parameter zu säubern:

$shout  = $_GET['shout'];
$shout = preg_replace("/\D/", "", $shout);
$shout = (!$shout) ? 1 : $shout;

Das ist zwar effektiv, ändert aber nichts am grauenvollen Programmierstil, mit dem der shout-Parameter an MySQL übergeben wird. Daran ändert übrigens auch nichts, wenn man den Parameter in Anführungszeichen an die Datenbank übergibt. Im Gegenteil, das beweist nur, dass sich der Programmierer des Problems bewusst ist, aber nicht in der Lage (naja, sagen wir es ruhig: zu dumm) ist, das Problem richtig zu beheben. Die richtige Lösung sieht so aus:

$query = sprintf("SELECT user.name, shouts.shout FROM shouts INNER JOIN user USING(owner) WHERE visibility=1 AND shouts.id=%d", mysql_real_escape_string($shout));
$result = mysql_query($query);

Das funktioniert auch für (hier nicht vorhandene) Argumente die als Zeichenketten übergeben werden. Aber dann bitte auch richtig escapen:

$query = sprintf("SELECT user.name, shouts.shout FROM shouts INNER JOIN user USING(owner) WHERE visibility=1 AND shouts.id='%s'", mysql_real_escape_string($string));

Diese Lösung ist schon ganz gut, aber noch nicht ideal. Die perfekte Lösung verwendet sogenannte “Prepared Statements”. In PHP benötigt man hierfür die mysqli-Extension, nicht die alte mysql-Erweiterung. Diese verwendet sich grundsätzlich genau wie die alte (und die beiden Klassen sind weitgehend miteinander austauschbar), unterstützt aber eben auch den neumodischen Schnickschnack, wie Prepared Statements. Unser problematischer Code sieht in objektorientiert und sauber so aus:

$statement = $link->prepare("SELECT user.name, shouts.shout FROM shouts INNER JOIN user USING(owner) WHERE visibility=1 AND shouts.id= ?");
$statement->bind_param("i", $shout);
$statement->execute();

Das ist in obigem Code jedoch nicht direkt so einfügbar und sollte in jedem neuen Code auch so angewendet werden. Schon nur, weil Prepared Statements unter Umständen viel schneller ausgeführt werden können, als normale. Diese Lösung ist natürlich auch für jede andere Datenbankschnittstelle übertragbar, in Perl zum Beispiel sieht dieser Code wie folgt aus:

$sth = $dbh->prepare("SELECT user.name, shouts.shout FROM shouts INNER JOIN user USING(owner) WHERE visibility=1 AND shouts.id= ?");
$sth->bind_param( 1, $shout, SQL_INTEGER );
$sth->execute();

Mit ein wenig Aufwand und dem Grundsatz keiner Benutzereingabe zu trauen, entgegnet man SQL-Injections also einfach und effizient.

Download

  sqlinject.tar.bz2 (1.7 KiB, 228 Downloads)

Kategorien

Eingeordnet unter: , , ,

Verwandte Artikel

6 Antworten auf »SQL-Injection kurz erklärt«

  1. wacken.com hat sich, wahrscheinlich aus dem grund, auch etwas verändert http://img27.imageshack.us/i/wackentinypic.jpg/

    wackenleser - 17. Januar 2010 um 20:19

  2. Also kurz ist was anderes :D
    Aber sonst sehr schön und sachlich geschrieben, auch wenn ichs mir nicht komplett durchgelesen habe, so weit geht mein Interesse an Datenbanken nun auch nicht. Die Dinger sollen halt tun und Webseiten bei denen Sicherheit eine ernsthafte Rolle spielt setze ich (besser) nicht auf ;).

    deadtronic - 19. Januar 2010 um 12:32

  3. [...] This post was mentioned on Twitter by Seraphyn and knut, Jeff. Jeff said: http://burnachurch.com/87/sql-injection-kurz-erklaert/ #php [...]

    Tweets that mention burn a church » SQL-Injection kurz erklärt -- Topsy.com - 19. Januar 2010 um 18:23

  4. [...] – So einfach, wie es einige gerne hätten ist es halt nicht mit dem Extremismus.burn a church » SQL-Injection kurz erklärt – Kann nie schaden, sich von Zeit zu Zeit wieder mal an solche Sachen erinnern zu lassen.Das [...]

    Dobschat » Links vom 20. Januar 2010 bis 21. Januar 2010 - 21. Januar 2010 um 23:01

  5. wäre es nicht viel einfacher, (um beim Beispiel der shoutbox zu bleiben) immer folgendes query abzuschicken: “”SELECT user.name, shouts.shout FROM shouts INNER JOIN user USING(owner) WHERE visibility=1″, dass Ergebnis in einer internen Liste speichern und auf die dann (bspw. mittels LINQ) zuzugreifen und die eigentliche Abfrage (nämlich ob ein Objekt mit der eingegebenen ID existiert) darauf auszuführen?

    Das könnte man dann noch soweit machen das nur ein SQL Statement auf die Datenbank ausgeführt wird wenn sich der Inhalt der Tabelle verändert hat (was ja nur passieren kann, wenn entweder der admin die visibility umsetzt, oder ein Benutzer einen neuen Kommentar hinterlässt)

    der DB Zugriff wäre dadurch sauber gekapselt und es wäre (soweit ich es beurteilen kann) nicht mehr möglich überhaupt auf die Datenbank zuzugreifen, solange man nicht das Adminpasswort hat (und die Adresse zum backend).

    detru - 27. Januar 2010 um 08:45

  6. Technisch hast du Recht. Das wäre dann eine saubere Lösung, die sich allerdings negativ auf die Performance auswirken würde.
    Andererseits gibt es natürlich immer mehr als einen Weg ein Problem anzugehen, ich wählte mein Beispiel natürlich so, dass das ursächliche Problem - die SQL-Injection - durchführbar ist. Ich hatte ja nicht behauptet, das meine Anwendung sinnvoll ist. ;)

    Arno - 27. Januar 2010 um 14:07

Einen Kommentar schreiben