juetho
[Artikelserie] SQL-Befehle: auch bei Formatproblemen sicher durch Parameters
Diese Artikel bieten im Bereich
Datentechnologien Hilfe, wenn eines oder mehrere der genannten Probleme (und natürlich auch ähnliche Probleme) auftreten, und zwar mit folgenden Abschnitten:
Da die Artikelserie geschlossen bleibt, gibt es einen extra Diskussionsthread
Diskussion zur Artikelserie "Parameter von SQL Befehlen"
Allgemeiner Hinweis: Wenn ich für Klassen einen Db-Bezeichner verwende, ist dies immer durch den Klassennamen des betreffenden DbProviders zu ersetzen: Sql-Klassen für MS-SQL Server, SqlCe-Klassen für MS-SQL Compact, MySql-Klassen für MySql usw. Die Db-Klassen selbst sind nur bei providerunabhängiger Programmierung von Bedeutung; das ist aber im Rahmen dieser Artikel nicht relevant.
Zur Forumssuche: Ich kann verstehen, dass jemand, der das Stichwort "Parameter" im Zusammenhang mit SQL-Befehlen noch nicht gelesen hat, nicht darauf kommt. Deshalb kann die Suchfunktion nur zufällig helfen: Suchbegriffe wie "DateTime-Problem bei SQL-String" liefern zwar auch die richtige Lösung (denn mehrere Poweruser und andere Helfer haben in vielen Beiträgen darauf hingewiesen); aber weil die Suchbegriffe sehr allgemein sind, ist nicht gesagt, dass der Sucher auf dem richtigen Weg landet.
Problemstellungen
Im Forum treten immer wieder solche und ähnliche Fragen auf:
Problem: Hochkomma im SQL-String
Problem: Datentypen in Kriterienausdruck unverträglich
Problem: Die Zeichenfolge wurde nicht als gültiges DateTime erkannt.
Diese Diskussion führte zu weiteren Problemen, vor allem wenn kein Geburtsdatum angegeben wurde.
Problem: Währungsfeld und double-Konvertierung
Problem: Länderabhängige Trennzeichen für Zahlen
Problem: Zahlen aus Exponentialdarstellung nach double konvertieren
Problem: Wie verhindere ich SQL-Injection?
Wenn nun ein böswilliger Anwender in der TextBox als neuen Namen dies eingibt:
dann wird doch die gesamte Tabelle gelöscht. Wie kann ich das verhindern?
Nähere Angaben zu diesem Problem sind zu finden unter
Wikipedia: SQL-Injection.
Lösungsweg
Die Lösung für alle diese und viele weitere Probleme ist ganz einfach:
Benutze für variable Inhalte eines SQL-Befehls niemals String-Verknüpfung, sondern immer DbParameter!
Die Verwendung von Parametern verlangt zwar ein wenig mehr Schreibarbeit, auch wenn das durch die IDE weitgehend vereinfacht wird. Aber die erheblich vergrößerte Sicherheit für Programmierer und Anwender macht die Arbeit viel leichter:
3. Beitrag: Unterschiede der DbProvider hin.
Das Beispiel habe ich übernommen von
Khalid aus dem
2. Beitrag: Vorteile der Parameter-Lösung in der Praxis; dort erläutert er es ausführlich.
Mit Parametern entsteht ein einfaches und dennoch schönes und sicheres Verfahren:
Korrigiert am 03.03.2009 durch juetho: DBNull.Value war falsch und wurde nach Hinweisen im Forum durch Convert.DBNull ersetzt.
Hier ist gleich zu sehen, dass der Code übersichtlich und strukturiert aussieht. Es werden wirklich nur die Werte übergeben. Der DB-Server arbeitet auch intern nur mit den Parametern (siehe die ausführliche Erläuterung). Man muss also gar nicht mehr auf die Formatierung achten; das macht der Server selber.
Aus diesem Grund funktioniert Sql-Injection mit Parametern nicht: Der DB-Server übernimmt zuerst den Befehl selbst und bereitet ihn vor, dann fügt er die Parameter-Werte ein. In dem unter "Problem" genannten Beispiel würde eine einzelne Adresse den Namen "Unsinn; DROP TABLE Kunden;" erhalten; aber weiterer Schaden könnte nicht entstehen.
Auch das Problem von Dezimalzahlen in Strings mit Exponentialdarstellung entfällt. Es ist zwar nicht gesagt, dass ein solcher String richtig erkannt und übersetzt wird. Aber dieses Problem tritt nun dort auf, wohin es gehört, nämlich zu double.TryParse() o.ä., aber nicht bei der Verarbeitung im Sql-Befehl.
Weitere Einführungen
Vor allem gibt es natürlich die
mit den von DbParameter und DbParameterCollection abgeleiteten Klassen sowie den verschiedenen Parameters.Add-Methoden und den dort genannten Beispielen.
Außerdem ist die Dokumentation des jeweils benutzten DB-Systems und des DbProviders zu benutzen: zum einen wegen der unterschiedlichen Syntax der SQL-Dialekte, zum anderen wegen "eigener Regeln" der DbProvider.
Im Index der
sind die Db-Methoden nicht immer zu finden, aber die Methoden der abgeleiteten Klasse stehen zur Verfügung. Allerdings ist nicht sicher, dass jeder DbProvider alle Varianten anbietet. Auf die eigene Suche in der jeweiligen Dokumentation darf also nicht verzichtet werden.
Allgemein steht noch
Galileo-OpenBook
Kap. 26.3 Parametrisierte Abfragen zur Verfügung.
Ein herzliches Dankeschön gilt Khalid, FZelle und allen anderen Datenbank-Experten, deren vielfältige Hinweise über die Forumssuche zu dieser Artikelserie beigetragen haben.
Gutes Gelingen!
Khalid (für die ausführliche Lösung)
Jürgen (für die Artikel insgesamt)
Diese Artikel bieten im Bereich
Datentechnologien Hilfe, wenn eines oder mehrere der genannten Probleme (und natürlich auch ähnliche Probleme) auftreten, und zwar mit folgenden Abschnitten:
1. Beitrag: Problemstellungen und Lösung
2. Beitrag: Vorteile der Parameter-Lösung in der Praxis
3. Beitrag: Unterschiede der DbProvider
4. Beitrag: Ergänzende Hinweise
Da die Artikelserie geschlossen bleibt, gibt es einen extra Diskussionsthread
Diskussion zur Artikelserie "Parameter von SQL Befehlen"Allgemeiner Hinweis: Wenn ich für Klassen einen Db-Bezeichner verwende, ist dies immer durch den Klassennamen des betreffenden DbProviders zu ersetzen: Sql-Klassen für MS-SQL Server, SqlCe-Klassen für MS-SQL Compact, MySql-Klassen für MySql usw. Die Db-Klassen selbst sind nur bei providerunabhängiger Programmierung von Bedeutung; das ist aber im Rahmen dieser Artikel nicht relevant.
Zur Forumssuche: Ich kann verstehen, dass jemand, der das Stichwort "Parameter" im Zusammenhang mit SQL-Befehlen noch nicht gelesen hat, nicht darauf kommt. Deshalb kann die Suchfunktion nur zufällig helfen: Suchbegriffe wie "DateTime-Problem bei SQL-String" liefern zwar auch die richtige Lösung (denn mehrere Poweruser und andere Helfer haben in vielen Beiträgen darauf hingewiesen); aber weil die Suchbegriffe sehr allgemein sind, ist nicht gesagt, dass der Sucher auf dem richtigen Weg landet.
Problemstellungen
Im Forum treten immer wieder solche und ähnliche Fragen auf:
- SQL Abfragestring mit Timestamp
- Hochkomma im SQL-String
- Datentypen in Kriterienausdruck unverträglich
- Die Zeichenfolge wurde nicht als gültiges DateTime erkannt.
- Währungsfeld und double-Konvertierung
- Länderabhängige Trennzeichen für Zahlen
- Zahlen aus Exponentialdarstellung nach double konvertieren
- Wie verhindere ich SQL-Injection?
|
||||
| Zitat: |
Original siehe SQL AbfragestringWas ist daran falsch? Ich blick nicht mehr durch, denn genau so lernten wir es im Unterricht, zwar mit MySQL aber das sollte eigentlich ja nicht so einen grossen Unterschied machen. |
Problem: Hochkomma im SQL-String
C#-Code: |
string strSQLUpdate = "UPDATE Kundenstamm "
|
| Zitat: |
Original siehe Hochkomma im SQL-StringIn den Kundennamen befinden sich unzählige Hochkomma, wie z.B. Franz's Würstelbude Leider bekomme ich nun Probleme bei meinem SqlStatement mit den Hochkomma's Wie könnte ich hier am besten vorgegen? |
Problem: Datentypen in Kriterienausdruck unverträglich
C#-Code: |
string sql = "DELETE FROM Fragen WHERE ID = '" + txtID.Text + "'";
|
| Zitat: |
Original siehe Daten aus OleDB Datenbank LöschenIch habe ein Problem mit dem Löschen aus einer Access-Datenbank per ExecuteNonQuery() Die Spalte "ID" ist vom Typ "Zahl" (Anzeige von Access 2007) |
Problem: Die Zeichenfolge wurde nicht als gültiges DateTime erkannt.
C#-Code: |
string str_GeburtsDatum = txt_GeburtsDatum.Text;
|
| Zitat: |
Original siehe Kein gültiges DateTime für UpdateAllerdings bekomme ich, bei meinem Update (wenn das Feld txt_GeburtsDatum leer ist) diese Fehlermeldung. |
Diese Diskussion führte zu weiteren Problemen, vor allem wenn kein Geburtsdatum angegeben wurde.
Problem: Währungsfeld und double-Konvertierung
C#-Code: |
double ab = 123.45;
|
| Zitat: |
Original siehe Accessdatenbank Double auslesenich lese mittels OLEdbAdapter von einer access Datenbank aus, unteranderem auch ein Währungsfeld, welches ich als double konvertiere und zu einem Betrag aus einem Textfeld addiere... Der Gesamtbetrag soll dann wiederrum in die Datenbank geschrieben werden.. Problem ist jetzt nur das er nicht tut, da er irgendwie mit dem Währungsformat aus access probleme hat! |
Problem: Länderabhängige Trennzeichen für Zahlen
C#-Code: |
// Wert des Textfeldes in int, double oder decimal
|
| Zitat: |
Original siehe Länderabhängige TrennzeichenBeim abspeichern hatte ich keine Probleme Damit weil Oracle das selbstständig unwandelt ("."). Doch wenn ich nun so einen Datensatz editieren möchte wird z.B. für Deutschland auch ein 99.72 angezeigt (funktioniert). Aber wenn ich diesen Datensatz speichern möchte bekomme ich ein Problem. Sobald ich dann den . durch ein , ersteze funktioniert alles Prima. (Bei o.g. Code) wird das Komma in der Datenbank als Punkt gepeichert. Nachdem ich es wieder hole z.B. zum Editieren habe ich dann eben einen Punkt 5.5 - wenn ich diesen wiederum speichern möchte bekommen ich einen fehler. |
Problem: Zahlen aus Exponentialdarstellung nach double konvertieren
C#-Code: |
//string[][] data, string Werte werden in double konvertiert
|
| Zitat: |
Original siehe Datenbank einfügen, Konvertieren"Fehler beim Konvertieren des varchar-Datentyps in float." Die Werte die ich einlese, konvertiere und speichern möchte, sehe so "0,000 0,001121 5,15E-005 0,0006027 0,001153" aus. Die Zahlen in Exponentialdarstellung, z.B. 5,15E-004 werden korrekt bis "-004" in double konvertiert(0,000515), sieht die Zahl so aus 5,15E-005 also "-005"(5Stellen links) dann passiert nichts und die Zahl wird auch so ausgegeben (5,15E-005). Welchen Datentyp benutze ich da?? |
Problem: Wie verhindere ich SQL-Injection?
C#-Code: |
string sql = "UPDATE Kunden SET Name = '" + TextBoxName.Text
|
Wenn nun ein böswilliger Anwender in der TextBox als neuen Namen dies eingibt:
|
||||
dann wird doch die gesamte Tabelle gelöscht. Wie kann ich das verhindern?
Nähere Angaben zu diesem Problem sind zu finden unter
Wikipedia: SQL-Injection.Lösungsweg
Die Lösung für alle diese und viele weitere Probleme ist ganz einfach:
Benutze für variable Inhalte eines SQL-Befehls niemals String-Verknüpfung, sondern immer DbParameter!
Die Verwendung von Parametern verlangt zwar ein wenig mehr Schreibarbeit, auch wenn das durch die IDE weitgehend vereinfacht wird. Aber die erheblich vergrößerte Sicherheit für Programmierer und Anwender macht die Arbeit viel leichter:
- Auf Hochkommata (Gänsefüßchen) bei Strings muss überhaupt nicht geachtet werden.
- Ständiges, mehrfaches und fehleranfälliges Hin- und Herkonvertieren als String ist überflüssig.
- Spezielle Formatierungen für Datumsangaben oder Zahlen sind nicht erforderlich; jeder Wert wird im richtigen Datentyp übergeben.
- Bösartige Eingaben haben keine bösartigen Auswirkungen.
- Die Performance bei parametrisierten Abfragen ist in vielen Fällen wesentlich höher als bei "zusammengebauten" Abfragen.
3. Beitrag: Unterschiede der DbProvider hin.Das Beispiel habe ich übernommen von
Khalid aus dem
2. Beitrag: Vorteile der Parameter-Lösung in der Praxis; dort erläutert er es ausführlich.Mit Parametern entsteht ein einfaches und dennoch schönes und sicheres Verfahren:
C#-Code: |
public int AddEmployee(string lastName, string firstName, double sallary, DateTime birthday)
|
Korrigiert am 03.03.2009 durch juetho: DBNull.Value war falsch und wurde nach Hinweisen im Forum durch Convert.DBNull ersetzt.
Hier ist gleich zu sehen, dass der Code übersichtlich und strukturiert aussieht. Es werden wirklich nur die Werte übergeben. Der DB-Server arbeitet auch intern nur mit den Parametern (siehe die ausführliche Erläuterung). Man muss also gar nicht mehr auf die Formatierung achten; das macht der Server selber.
Aus diesem Grund funktioniert Sql-Injection mit Parametern nicht: Der DB-Server übernimmt zuerst den Befehl selbst und bereitet ihn vor, dann fügt er die Parameter-Werte ein. In dem unter "Problem" genannten Beispiel würde eine einzelne Adresse den Namen "Unsinn; DROP TABLE Kunden;" erhalten; aber weiterer Schaden könnte nicht entstehen.
Auch das Problem von Dezimalzahlen in Strings mit Exponentialdarstellung entfällt. Es ist zwar nicht gesagt, dass ein solcher String richtig erkannt und übersetzt wird. Aber dieses Problem tritt nun dort auf, wohin es gehört, nämlich zu double.TryParse() o.ä., aber nicht bei der Verarbeitung im Sql-Befehl.
Weitere Einführungen
Vor allem gibt es natürlich die
mit den von DbParameter und DbParameterCollection abgeleiteten Klassen sowie den verschiedenen Parameters.Add-Methoden und den dort genannten Beispielen. Außerdem ist die Dokumentation des jeweils benutzten DB-Systems und des DbProviders zu benutzen: zum einen wegen der unterschiedlichen Syntax der SQL-Dialekte, zum anderen wegen "eigener Regeln" der DbProvider.
Im Index der
sind die Db-Methoden nicht immer zu finden, aber die Methoden der abgeleiteten Klasse stehen zur Verfügung. Allerdings ist nicht sicher, dass jeder DbProvider alle Varianten anbietet. Auf die eigene Suche in der jeweiligen Dokumentation darf also nicht verzichtet werden.Allgemein steht noch
Galileo-OpenBook
Kap. 26.3 Parametrisierte Abfragen zur Verfügung.Ein herzliches Dankeschön gilt Khalid, FZelle und allen anderen Datenbank-Experten, deren vielfältige Hinweise über die Forumssuche zu dieser Artikelserie beigetragen haben.
Gutes Gelingen!
Khalid (für die ausführliche Lösung)
Jürgen (für die Artikel insgesamt)

)