Moin Moin!
Mal vorweg: Ich arbeite seit Jahren mit Perl und DBI, wo Prepared Statements und Platzhalter der empfohlene Weg sind, von dem man nur in besonderen Ausnahmefällen abweichen sollte.
Ich vermisse ein Beispiel zu Prepared Statements.
Ich wollte die Prepared-Statements-Thematik nicht zu sehr ausbauen. Damit und mit den Eigenheiten und Unterschieden zwischen mysqli und PDO kann man gut einen eigenen Artikel füllen.
Ich wollte ja keine 100 Beispiele. Ein einziges, in etwa wie ...
my $sth=$dbh->prepare('select foo,bar from baz where bla=?');
$sth->execute($bla);
while (my $data=$sth->fetchrow_hashref()) {
print $data->{'foo'},' => ',$data->{'bar'},"\n";
}
$sth->finish();
... und dazu ein Satz wie "Egal welchen Wert $bla annimmt, ist SQL Injection unmöglich, ohne dass manuelles Quoting nötig ist."
Ich vermisse die Aussage, dass man bei zusammengeklebten Statements sich sehr leicht SQL Injections einfängt, wenn man versehentlich nicht die richtige Quoting-Funktion benutzt (von denen es in PHP wohl einen ganzen Haufen gibt) oder den Funktionsaufruf rund um jeden einzelnen Parameter bei auch nur einem Parameter vergißt.
Egal, welche der in Frage kommenden Funktionen (mysql_escape_string, mysql(i)_real_escape_string und auch addslashes) man verwendet, die kritischen Zeichen sind damit abgedeckt.
Keine Kritik an Dir, sondern an PHP: Quoting-Funktionen einzeln für jede einzelne DB und jedes einzelne Interface ist Krampf.
DBI hat eine(!) Quote-Methode, die Werte passend quotet. DBI definiert eine Basis-Methode, die für die meisten DBs paßt, Die einzelnen DB-Treiber können bei Bedarf ihre eigene Methode definieren. Aber egal wie, es endet immer damit, my $quotedValue=$dbh->quote($value);
zu schreiben, unabhängig von der Datenbank. Genau dieser Mechanismus greift auch, wenn eine Datenbank Prepared Statements nicht unterstützt: In dem Fall wird das SQL-Statement von prepare() einfach nur gespeichert und execute() ersetzt die Platzhalter durch gequotete Werte.
(DBI hat übrigens eine zweite Quote-Methode, die sich um das richige Quoten von Identifiern wie Tabellen- und Spaltennamen kümmert. Auch wieder komplett unabhängig von der DB.)
Der Unterschied zwischen addslashes und mysql_(real_)escape_string ist eher kosmetischer Natur - siehe den geklammerten Satz "Strictly speaking ...".
OK. (Wobei ich es schon merkwürdig finde, dass man sich beim Schreiben von SQL-Statements bzw. Programmcode Sorgen um das Log-Format des DB-Servers machen soll. Naja, MySQL-Philosophie halt ...)
Ich vermisse die Aussage, dass bei Prepared Statements das Quoting-Problem wegfällt und damit das Risiko der SQL-Injection wegfällt.
[...] Das Problem beim Kontextwechsel ist nicht primär der Sicherheitsaspekt. Deswegen reite ich auch nicht zu sehr auf dieser "Dramatik" rum.
Ich denke, Du solltest darauf herumreiten. SQL Injections kommen genau durch verpaßte Kontext-Wechsel zustande, genau wie Code Injections und Shell Injections.
Der ganze Absatz rund um die Prepared Statements kann leicht so gelesen werden, dass Prepared Statements eigentlich eine unnütze Erfindung sind, die man besser durch die altbewährte Cargo-Cult-Programmierung ersetzt.
Es ist Mehraufwand, der sich nicht in jedem Fall lohnt.
Schon alleine, weil man sich nicht fehlerträchtig selbst um das Quoting von Parametern kümmern muß und damit SQL-Injections garantiert und automatisch verhindert werden, lohnt sich der Weg über Prepared Statements. Ein Kontext-Wechsel weniger, um den man sich kümmern muß.
Erheblich wird der Mehraufwand unter mysqli, wenn das Statement auch noch erst zur Laufzeit zusammengebaut werden soll.
Irgendetwas verstehe ich hier nicht. Wo ist der Mehraufwand? sprintf und prepare unterscheiden sich nur im Platzhalter-Zeichen, quoting jedes einzelnen Parameters entfällt, do() wird durch execute() ersetzt.
Der Wiederverwendbarkeitsvorteil ist damit auch aus dem Rennen.
Wir scheinen aneinander vorbei zu reden.
Selbst wenn PHP für jeden Request eine neue DB-Connection öffnet, kann die DB selbst die Prepared Statements unabhängig von der Connection cachen und sich das tausendfache Neuparsen des selben Statements verkneifen -- und wenn nicht die DB, dann ein dazwischen geschaltetes Stückchen Software, z.B. zum Connection Pooling.
Wie soll das gehen? Nach dem Prepare bekommt man ein Handle auf das PS. Mit diesem Handle bindet und executiert man.
Du kannst execute() mit dem selben Handle mehrfach aufrufen. (Jedenfalls kann DBI das.)
Das Handle wird spätestens am Requestende entsorgt.
Nö, beim expliziten Aufruf von finish() oder implizit wenn $sth aus dem Scope läuft.
Ich wüsste nicht (alles kann ich auch nicht wissen), dass da bei MySQL (andere DBMS kenne ich nicht intensiv genug) noch ein Statement-Cache existiert, der mir dann das "alte" Handle wieder besorgt.
Du brauchst kein altes Handle.
Der normale Query-Cache jedenfalls vergleicht die SQL-Statements erst nach der Platzhalter-Ersetzung. Alles andere wäre ja auch nicht sinnvoll, weil das Ergebnis ja von diesen Werten abhängig ist.
Das ist MySQL, da funktioniert vieles etwas anders als bei anderen (ich bin versucht, "richtgen" zu schreiben) RBDMS.
Andere RDBMS parsen beim prepare() das SQL-Statement mit Platzhaltern, bauen daraus eine interne Repräsentation auf, wie sie durch die Tabellen graben wollen, und setzten für jeden Lauf von execute() nur noch die Werte in die interne Repräsentation ein. Das SQL-Statement mit Platzhalten kann zusammen mit der internen Repräsentation (ggf. auch über Session-Grenzen hinweg) im Speicher gehalten werden, so dass bei einem erneuten prepare() mit einem identischen Statement der SQL-Parser gar nicht erst gestartet wird.
DBI bietet mit prepare_cached() einen ähnlichen, anwendungsseitigen Cache an, der allerdings pro identischem Statement ein Handle belegt.
Ich sehe nur PHP und MySQL. Zugegeben, das ist die Standard-Lösung aller Billig- und Massenhoster. Aber es gibt noch andere Datenbanken (wie müßte da die entsprechende Quote-Funktion heißen?) und es gibt noch andere Programmiersprachen für die Serverseite (Perl, Ruby, Java, ...), die exakt die selben Kontextwechsel-Probleme haben.
Wer für das Thema erst einmal sensibilisiert ist, wird sich die entsprechenden Funktionen in anderen Systemen raussuchen können, finde ich.
OK. Vielleicht ergänzt Du dann den Titel um "in PHP" bzw. "mit PHP"?
Da ich nicht mit allen DBMS-Schnittstellen Erfahrung habe, möchte ich dazu auch nichts sagen. Manche sind auch zu exotisch. Der Artikel sollte auch nicht zu voll werden.
Genau das ist der Unterschied zwischen PHPs Datenbank-Anbindung und DBI. Mit DBI muß ich keine Erfahrungen mit unterschiedlichen Datenbanken haben. So lange ich keine DB-spezifischen SQL-Erweiterungen benutze, ist DBI-basierender Code komplett von der DB und der DB-Schnittstelle unabhängig, die einzige notwendige Änderung für die Migration auf eine andere DB ist der Connect-String, der ohnehin meistens in einer Konfigurationsdatei steht.
Alexander
--
Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so".