Fehler bei Einfügen von Daten in der DB

Na ja, alles verwerfen muss man nicht. Es reicht, die Escaping-Funktionen hinzuzufügen.

Für Bücher/Tutorials/Vermittlung sind Ausnahmefälle immer ein kleines „Problem“. Ich finde auch, dass es von Beginn an dazugehört. Man muss sich aber vor Augen führen, dass entsprechender Code die Beispiele und Erklärungen teilweise ganz schön aufbläht. Ich habe ein gewisses Verständnis dafür, das nacheinander abzuhandeln, aber optimal finde ich es auch nicht. Andererseits kannst du als Autor auch wenig tun, wenn der Leser nach 10 von 20 Seiten das Kapitel zuklappt.

Es ist am Anfang umso schwieriger, wenn man mit "falschen" Code lernt, ich spreche da aus Erfahrungen,- meine ersten "Geh-Versuche" habe ich vor zig Jahren unter PHP-Nuke gemacht, erst nachdem ich rund 9 Monate am "coden" war, bin ich auf das Thema gestoßen.
Heute arbeite ich nur noch mit meiner Request-Klasse, welche den globalen Scope restlos sauber hält,- selbst wenn da außerhalb dieser Klasse etwas eingefügt werden sollte (per _GET, _POST usw.), wird es gleich wieder gekillt ;)
Daten von einem Formular sind über _POST oder _GET nicht verfügbar, sondern können nur noch über diese Klasse eingebunden werden,- und dann sind schädliche Vars gleich gefiltert und escapt :)
 
saftmeister hat gesagt.:
Da die Frage beantwortet wurde, kann man etwas OT werden

Jo. Immer sehr gerne. :)

PS löst nicht nur das Problem der unescapten Daten. Sie bringen auch Performance-Vorteile und bessere Wartbarkeit durch übersichtlicheren Code.

Performance-Vorteile vor allem dann, wenn die Query mehrfach mit unterschiedlichen Parametern aufgerufen wird. Das ist in PHP prinzipbedingt (Skripte werden ständig neu gestartet) nicht unbedingt der absolute Regelfall. Natürlich gibt es das auch häufig, manchmal fehlt da aber auch bloß mal ein JOIN, der etwa eine Schleife von Queries ersetzt.

Wenn die Abfrage nur einmal ausgeführt wird, ist ein PS theoretisch auch mal langsamer, weil zwei Roundtrips zur DB benötigt werden. Wie relevant das hinsichtlich der Gesamtperformance ist, sei dahingestellt.

Übersichtlicherer Code… Will ich nicht widersprechen, aber es ist irgendwie auch Ansichtssache.

PHP:
$query = sprintf(
    "SELECT row1 FROM table WHERE row2 = '%s' AND row3 = '%s'",
    $mysqli->real_escape_string($x),
    $mysqli->real_escape_string($y)
);

Mir ging es vor allem darum, dass man die Methode/Funktion auf Ebene des Library-Designs nicht abschaffen sollte, weil sie einen Zweck erfüllt. Das ist keine Aussage darüber, ob man das als Anwender der Library so nutzen sollte.

es birgt auch die Anfälligkeit mal was zu übersehen, das beste Beispiel dafür ist die Frage des TO

Wobei man bei bind_param genauso einen Wert vergessen kann.

Da kann man geteilter Meinung sein. Was soll es an der Schnittstelle noch zu verbessern geben? Warum sollte man da noch mal was drum herum bauen wollen/sollen/müssen?

Vor allem die Geschichte, dass bind_param und bind_result eine Liste von einzelnen Variablen-Referenzen (grob gesagt: einzelne Variablen) übergeben werden muss. Aus der Doku:

PHP:
$stmt = $mysqli->prepare("INSERT INTO CountryLanguage VALUES (?, ?, ?, ?)");
$stmt->bind_param('sssd', $code, $language, $official, $percent); // Einzelvariablen --merms

PHP:
if ($stmt = $mysqli->prepare("SELECT Code, Name FROM Country ORDER BY Name LIMIT 5")) {
    $stmt->execute();

    /* bind variables to prepared statement */
    $stmt->bind_result($col1, $col2); // Die Variablen sind für "Code" und "Name" --m

Im Vergleich dazu nutzt etwa PDO Arrays/keine Referenzen:

PHP:
$sql = 'SELECT name, colour, calories
    FROM fruit
    WHERE calories < :calories AND colour = :colour';
$sth = $dbh->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
$sth->execute(array(':calories' => 150, ':colour' => 'red'));

PHP:
$sth = $dbh->prepare("SELECT name, colour FROM fruit");
$sth->execute();

/* Fetch all of the remaining rows in the result set */
print("Fetch all of the remaining rows in the result set:\n");
$result = $sth->fetchAll();
print_r($result); // Hier muss nicht $col1 und $col2 angelegt werden --m

Der Vorteil von PDO ist an der Stelle vor allem, dass die Anzahl der Parameter, die in die Query eingefügt werden und die von der Query geliefert werden, nicht vorher bekannt sein muss. Das muss sie bei mysqli erst mal, weil für jeden dieser Werte eine Referenz existieren muss.

In PDO kann man deshalb sehr einfach generischen Code schreiben, der für eine Vielzahl von Szenarien funktioniert:

PHP:
$sth = $pdo->prepare($query);
$sth->execute($params);
$data = $sth->fetchAll();

In mysqli muss man da ungleich mehr mit Variablen rumhampeln oder sich generische Funktionen mit call_user_func_array und Arrays von Referenzen basteln, die ca. so aussehen:

- http://de1.php.net/manual/en/mysqli-stmt.bind-result.php#102179

Das meine ich, wenn ich sage, dass das mysqli-API an der Stelle nicht so toll ist beziehungsweise dass man mehr Infrastruktur-Code braucht (was natürlich generell kein Beinbruch ist), der einem so was abnimmt.

Das Anwendungsszenario hier wäre: Ich will eine SELECT-Query mit beliebiger Anzahl an Parametern ausführen und die Rückgabe als Liste von Elementen mit Key-Value-Paaren erhalten. Ich denke, das ist eine übliche Anforderung.

Wenn man es nicht so generisch will: Die Sache mit den Einzelreferenzen bei mysqli ist einfach umständlich, weil man seine Daten doch eigentlich meist als assoziative Arrays haben will. Als einzelne Variablen braucht man sie nicht immer.

Was ist ein dynamischer Query?

Eine Abfrage, bei der etwa Filterkriterien je nach Nutzerauswahl ergänzt werden. Beispielsweise ein Suchformular, in dem du nach Vorname und Nachname und Alter und Adresse suchen/filtern kannst oder auch nur nach etwa Adresse. Das würde man umsetzen, indem man den WHERE-Teil dynamisch zusammenbaut und eben auch eine dynamische Anzahl an Eingabeparametern für die Query hat. Das wird in mysqli mit bind_param dann schnell mal umständlich, wenn man nicht mit call_user_func_array oder dergleichen arbeitet.

Für diesen Fall ja, es handelt sich um ein Tutorial. Sobald es aber mal etwas mehr sein darf und man auf den Trichter kommt, dass durch PS auch mehr Leistung (Wortspiel ) in die Applikation kommt, darf man etliche Funktionen und Scripte umbauen. Dann lieber doch von Anfang an auf PS zurück greifen.

Dazu habe ich oben ja schon was gesagt, deshalb hier nur noch mal Stichwörter: Notwendigkeit mehrerer gleichartiger Abfragen, JOIN, Query-Cache.

Dann sollte sich der Leser überlegen, ob er die richtige Motivation an den Tag legt.

Jip. :D Dein Wort in… Ohr.

Bei so sicherheitsrelevanten Dingen sollte man gerade Anfängern abverlangen, den richtigen Weg zu gehen. Sonst hat man das 1mrdste Gästebuch, was defaced oder für XSS missbraucht wird. Das kann unser aller Wunsch nicht sein.

Nur so als Gedanke, ist nicht wirklich als Argument gemeint und auch nicht speziell auf dich bezogen:

Sagst du jedem Nutzer in jedem Thread, dass da bei seinen Ausgaben Aufrufe von htmlspecialchars fehlen? Escapest du in jeder deiner Antworten an der Stelle korrekt? Weist du auf jede mögliche Locking-Problematik oder Race-Condition hin? Oder dass URLs mit „javascript:“ beginnen könnten? Leitest du Leute dazu an, jede Nutzereingabe, die UTF-8-NFC sein sollte, auch darauf zu überprüfen, ob sie tatsächlich UTF-8-NFC ist? Weist du darauf hin, dass ein $ in einem regulären Ausdruck das Eingabeende matcht oder das Eingabeende mit einem \n davor? Oder dass \d und \w von Locale-Einstellungen abhängen können? Oder dass es sehr abstrakte Probleme mit Zeichensätzen geben kann? Was XSRF ist? (Ein einziges Elend ist das!) Erklärst du, wie man einen URL behandeln muss, um ihn passend escapet in einen String in einem JS-Eventhandler eines HTML-Attributs einzutragen? Wie erklärst du das Leuten, die sich zum ersten Mal in ihrem Leben mit Programmierung oder EDV-Zeugs beschäftigen?

Es gibt da ein wünschenswertes Ideal und es gibt „didaktischen Realismus“. :D Escaping in SQL-Queries ist dabei allerdings noch ein übersichtliches Problem, das gebe ich gerne zu.

Das also bitte alles auch als Offtopic vom Offtopic verstehen. :)



PS: „Wir“ haben da als Community in den letzten Jahren aber glaube ich schon kleine Fortschritte gemacht, was Qualitätssicherung angeht. SQL-Injections und die veraltete mysql-Extension sind etwa Standards, die eigentlich immer angemerkt werden. „Kontextwechsel“ als Begriff fällt auch ständig. Mein nächstes Ziel ist, den Stand auch für XSS/htmlspecialchars zu erreichen. Dass es zum Reflex wird, dazu was zu sagen, wenn es fehlt. Das ist halt sehr anstrengend.
 
Zuletzt bearbeitet:
Vielen Dank. An einigen Stellen habe ich zu engstirnig gedacht, insb. bei dyn. Queries. Wobei ich an dieser Stelle eher mehrere Query-Strings im Code hinterlegen würde und über if-Condition dann den jeweiligen gewünschten String preparen würde. An dieser Stelle wird es dann aber bereits so komplex, dass ich den Einsatz eines ORM wie bspw. ZendDB in Erwägung ziehen würde - da PHP auch in PDO keine Conditions-API bereit stellt.


Das PDO da natürlich geeigneter ist, was das binden angeht, habe ich nicht in Frage gestellt. Aber PS in MySQLi sind immer noch besser (aus genannten Gründen) als der althergebrachte Weg - nur meiner Meinung nach. Ich gebe zu, auch ein bisschen in die Ecke Fan-Boy zu tendieren. ;-) Wenn ich die Wahl habe, nehme ich PDO.

Sicher mag es Einsatzkriterien geben, die einen dazu bewegen, den xxx_query() => xxx_fetch_xxx() zu verwenden.


Performance: Ich bin mir nicht sicher, und es kommt evtl. auch auf die Situation an, aber selbst bei Einzel-Abfrage wäre es denkbar, mit PS bessere Resultate zu erzielen. Ich würde im Zweifelsfall ohnehin profilen und dann abwägen, ob mir die og. Punkte wichtig sind.


Deine unteren Fragen bezgl. Hinweise in Foren-Posts, was Security angeht:

Nein, ich mach das nicht. Oft gehe ich davon aus, dass der Fragesteller bereits weiß, dass er ein Auge auf die Sicherheit haben soll. Vor allem dann, wenn der Code des FS so aussieht:

Code:
$result = mysql_query("SELECT * FROM table WHERE col1 = '$foo' AND col2 = '$bar'");

Würdest du hier erwarten, dass die Variablen escaped wurden oder nicht?

Bei so offensichtlichen Fehlern wie

Code:
$foo = $_POST['form_foo_field'];
$bar = $_POST['form_bar_field'];

$result = mysql_query("SELECT * FROM table WHERE col1 = '$foo' AND col2 = '$bar'");

oder noch deutlicheren Anzeichen wie

Code:
$result = mysql_query("SELECT * FROM table WHERE col1 = '" . $_POST['form_foo_field'] . "' AND col2 = '" . $_POST['form_bar_field'] . "'");

muss man einfach was dazu sagen. Und nicht nur im Bezug auf MySQL => MySQLi/PDO.

Ich gebe zu, hier bin ich z.T. auch etwas blauäugig und vertraue bei einigen Posts darauf, dass der FS und sonstige Leser genau wissen, dass zusätzliche Arbeiten nötig sind, um das einigermaßen sicher zu bekommen. Hier gebe ich dir recht, etwas unsorgfältig zu sein.

Aber wie schon geschrieben, hier war es (wieder einmal) offensichtlich.
 
saftmeister hat gesagt.:
Das PDO da natürlich geeigneter ist, was das binden angeht, habe ich nicht in Frage gestellt.

Ne, hast du nicht. Die Erklärung von mir war wohl etwas abschweifend (aber vielleicht für irgendwen ganz interessant).

selbst bei Einzel-Abfrage wäre es denkbar, mit PS bessere Resultate zu erzielen

Ja, wahrscheinlich auch das. Wen das interessiert: In diesem Thread (habe ich weiter oben schon mal verlinkt) führen das einige Nutzer etwas aus. Gibt sicher bessere Quellen, aber ich denke, das zeigt schon ganz gut, dass solche Fragen nicht so wirklich pauschal zu beantworten sind. Letztlich würde ich da aber auch empfehlen, im Zweifel zu profilen, ob das überhaupt irgendwas ausmacht im Performance-Gesamtbild. Das tut es in der Regel wohl nicht.

Also, damit das nicht falsch rüberkommt: PS sind eine absolut sinnvolle Sache, die man gerne einsetzen soll.

Code:
$result = mysql_query("SELECT * FROM table WHERE col1 = '$foo' AND col2 = '$bar'");

Würdest du hier erwarten, dass die Variablen escaped wurden oder nicht?

Ich weiß es nicht. Erfahrungsgemäß eher nicht, aber ich merke das Injections-Zeug auch nicht immer auf Verdacht an. Manchmal schon, aber man kann natürlich falsch liegen (better safe than sorry), und es ist einfach anstrengend, immer wieder den Link zu einer Erklärung zu kopieren und so. Irgendwann kommt es einem zu den Ohren raus.

Ich gebe zu, hier bin ich z.T. auch etwas blauäugig und vertraue bei einigen Posts darauf, dass der FS und sonstige Leser genau wissen, dass zusätzliche Arbeiten nötig sind, um das einigermaßen sicher zu bekommen. Hier gebe ich dir recht, etwas unsorgfältig zu sein.

Das sollte jedenfalls wirklich kein Vorwurf sein, ich mache das nicht anders.

Es ging ja ursprünglich um PHP-Bücher. SQL-Injections müssen in so einem Buch behandelt werden, wenn das auch Datenbanken beinhaltet. Das ist klar. Mit meiner Auflistung im letzten Post wollte ich nur mal andeuten, wie viele Sicherheits-/Spezialfallaspekte es gibt, die man im Grunde beachten müsste. Etwa XSRF bei einer Einführung in Formularverarbeitung erklären oder diese ewigen Zeichensatzgeschichten… Von einem guten Buch wird man wohl erwarten dürfen, dass das zumindest erwähnt wird.

Na ja, ist wohl klar, was ich meine. :) Bei dem Thema diskutiere ich vielleicht etwas einseitig, weil ich aus der Didaktik-Richtung komme und weiß, dass es oft auch Absicht ist, Inhalte auf eine bestimmte Weise zu vermitteln, und dass es häufig mehr um das Konzept dahinter geht als um das konkrete Beispiel. Letzteres gilt für PHP-Praxisbücher natürlich nicht so.

Aber wie schon geschrieben, hier war es (wieder einmal) offensichtlich.

Ja, da habt ihr eigentlich auch Recht. Der Code sieht schon nicht doll aus.

bofh1337 hat gesagt.:
Es ist am Anfang umso schwieriger, wenn man mit "falschen" Code lernt, ich spreche da aus Erfahrungen,- meine ersten "Geh-Versuche" habe ich vor zig Jahren unter PHP-Nuke gemacht, erst nachdem ich rund 9 Monate am "coden" war, bin ich auf das Thema gestoßen.

Da könnte ich auch genug Stories erzählen. :) Ich habe etwa Ewigkeiten nicht gewusst, dass Webserver Charset-Angaben im HTML-Code überschreiben. Das hört nie völlig auf mit solchen „Gotchas“. Zuletzt bei mir vielleicht (C|X)SRF. Das war mir zwar nicht neu, aber was sich damit teilweise anstellen lässt, ist so ekelhaft, dass man das wirklich auch an allen Ecken und Enden verlinken sollte.
 
Naja, ich will ja nicht sagen, ich wäre der Über-Programmierer :D Ich programmiere ja nur als Hobby, aber trotzdem lege ich (seit damals) wohl Wert auf sicheren Code, schlank sollte er sein und mit einer gewissen Portion Logik,- und nun zerbreche ich mir den Kopf über das Teufelsthema "Namespace" für eine abstracte Klasse mit Interface und den ganzen Kram :D
 
Zurück