Formatierung und Unicode -> Fehler?

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
kaineanung
User
Beiträge: 145
Registriert: Sonntag 5. April 2015, 20:57

Hallo Leute,

ich hätte da mal wieder ein Problem:

ich bekomme einen Fehler
UnicodeEncodeError: 'ascii' codec can't encode character u'\xfc' in position 1: ordinal not in range(128)
wenn ich folgendes benutze:

Code: Alles auswählen

sql = "select s.id from stadt s where s.name = '{stadt}'".format(stadt=dictStadt['name'])
Ich generiere also ein SQL-Select-Statement und ersetze das Gesuchte durch ein Formatierungsbefehl und dem Keyword 'Stadt'. Der Wert kommt aus einem Dict unter dem Key 'name' wiederrum der Stadt-Name gespeichert ist.
Bei anderen Funktionen klappt das wunderbar, nur bei diesem jetzt nicht. Das Besondere dabei ist wohl das ich ein deutschen Umlaut im Städtenamen habe.

Ich habe auch versucht den Städtenamen in ein unicode zu wandeln (obwohl dieser ja bereits ein Unicode sein sollte da ja python sowieso intern nur mit Unicode arbeitet..hat mir zumindest jemand hier gesagt gehabt).
Also wie folgt:

Code: Alles auswählen

sql = "select s.id from stadt s where s.name = '{stadt}'".format(stadt=unicode(dictStadt['name']))
Hat aber nichts genutzt, der gleiche Fehler....

Also, was mache ich falsch? Was soll ich machen?
Ich bedanke mich schonmal im Voraus für eure Mühe!
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@kaineanung: man formatiert keine Variablen in SQL-Statements, sondern nutzt Platzhalter!
.format auf Bytestrings ergibt wieder einen Bytestring.
kaineanung
User
Beiträge: 145
Registriert: Sonntag 5. April 2015, 20:57

@Sirus

Sorry, aber das habe ich jetzt nicht ganz verstanden...
Wie meinst du das? Ich formatiere ja ersteinmal nur ein Text. Dieser enthält zwar ein SQL-Statement, aber ist ersteinmal nur ein Text. Da will ich die Platzhalter nun ersetzen mit den entsprechenden Werten und erst dann als SQL-Statement über MySQLDB absetzen..

Wie soll ich es denn sonst machen? Soll ich Textverkettung machen mit dem Verkettungsoperator '+'? Ich hätte gedacht das es so 'eleganter' wäre mit dem Formatierungsbehfehl...

Also um sicherzugehen: wie soll ich es dann am besten machen?

Danke für deine Mühe so ganz nebenbei :D
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@kaineanung: am besten liest Du Dir erst mal ein Tutorial durch.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@kaineanung:
Du hast quasi 2 Probleme:

Du mischt Bytestrings mit Unicodestrings. Da kommt die Fehlermeldung her. Dabei versucht Python, Deinen Unicodestring zu einem Bytestring zu konvertieren, was aber fehlschlägt, da der Stadtname offensichtlich ein ü enthält. Bei der Autokonvertierung von Unicode- zu Bytestring geht Python 2.x über ASCII, da gibt es keine Umlaute. Abhilfe: Pythonintern alles mit Unicode machen.

Das zweite Problem betrifft SQL-injections, welche durch simple Stringoperationen auf SQL-Befehlen möglich werden, falls Teile des Strings aus nicht vertrauenswürdigen Quellen kommen. Deshalb sollte man sich angewöhnen, niemals SQL-Befehle auf diese Weise zusammen zusetzen. Alle ORMs und DB-bindings bieten für Deinen Fall Möglichkeiten, um die Parametrisierung sicher vorzunehmen.
kaineanung
User
Beiträge: 145
Registriert: Sonntag 5. April 2015, 20:57

@Sirius

Ich habe mir das kurze Tutorial nun durchgelesen. Aber wahrscheinlich sehe ich den Wald vor lauten Bäumen nicht...
Ich bin irgendwie an keiner Stelle auf mein konkretes Problem bzw. dessen Lösung gestossen?
Wie gesagt: ist wahrscheinlich das mit dem Wald und den vielen Bäumen....

@jerch

Da ich noch absoluter Neuling bin was Python angeht darf ich das doch sicherlich so fragen: wie mache ich alles unicode-intern?

Ich dachte python arbeitet intern immer mit Unicode?
Ausserdem versuche ich ja das Element des Dictes vor der Formatierung bereits mit unicode zu wandeln.
Ist doch sicherlich wie in anderen Programmiersprachen: alles wird von innen nach aussen abgearbeitet, oder? Also sollte das unicode-wandeln in meinem Beispiel bereits vor dem Formatieren stattfinden?

Wenn ich es nicht auf diese Weise 'zusammensetzen' darf, dann bleibt mir entweder die C++-Konforme formatierung mit %s oder eben das Verketten mit dem Verkettungsoperator '+', richtig?
Entshculdige, aber ORM und DB-Bindings sagen mir nichts...
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@kaineanung: mir raten kommt man beim Programmieren nicht sehr weit. Dokumentationen lesen und verstehen ist essentiell. Hier mal Dein kleines Beispiel:
[Codebox=python file=Unbenannt.py]
cursor.execute("select s.id from stadt s where s.name = %s", (dictStadt['name'],))
[/Codebox]
kaineanung
User
Beiträge: 145
Registriert: Sonntag 5. April 2015, 20:57

@Sirius

Das meinte ich ja als ich an @jerch geschrieben habe mit der bekannten formatierung aus C (..formatierung mit %s...).
Also wird das dann problemlos gehen? Ist zwar, meiner Meinung nach, nicht so elegent wie mit der o.g. Formatierung, aber immerhin besser als mit dem '+' alle Zeichenketten zu verketten....

Ich probiere es heute Abend dann mal aus! Danke dir!

Und du hast ja vollkommen Recht: mit Raten kommt man nicht weit. Aber ich rate ja nicht sondern lese und frage. Nur kann es aber eben manchmal sein das man das offensichtliche doch nciht erkennt weil man gerade auf dem 'Schlauch steht'....
Und bei mir mischen sich jetzt doch schon einige Programmiersprachen im Kopf die ich, das eine mehr das andere weniger, beherrsche. VB6 schon seit Jahrzehnten, C, SQL (in all den verschiedenen Versionen), VB.NET, PHP, HTML, Batch und vielleicht noch das eine oder andere ausgestorbene (kennt jemand noch Turbo Pascal oder QuickBasic?). Und mit Python habe ich erst vor 10 Tagen netto begonnen und meine Lernkurve ist gar nicht so übel...

Vor allem dank euch denn wenn ich schnell was brauche und keine Zeit habe für ein eigentlich marginales Problem jetzt Bücher zu wälzen (auch vorbeugend die Gefahr zu meiden dann irgendwann mal die Lust an Python deswegen zu verlieren)!

Also, danke nochmals! ;)
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Um ganz sicher zu gehen: Auch wenn das im Code von Sirius3 nach Formatierung aussieht, ist es keine. Komm ja nicht auf die Idee und ersetze `.format` durch den `%` Operator, sondern benutze die entsprechenden `execute` Methode und die Parameterkennzeichnung, die die DB-Anbindung unterstuetzt.
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

@kaineanung: Aus gegebenem Anlass ein Frage: Woher hast du die Idee, die format() Methode zu verwenden? Hast du das in diesem Zusammenhang (dh. SQL) irgendwo gesehen?
In specifications, Murphy's Law supersedes Ohm's.
BlackJack

Alternative zu allem bisherigen: SQLAlchemy verwenden. :-)
kaineanung
User
Beiträge: 145
Registriert: Sonntag 5. April 2015, 20:57

@BlackJack

Ist was genau? Wenn das aber von dir kommt dann muss ich es mir wohl mal anschauen...

@pillmuncher

Als Programmierer muss man ja schliesslich den Kopf einschalten. Will damit sagen: das muss ich nirgends gesehen haben...
Ich dachte wie folgt:
Ich verwende einen SQL-String (also ein ganz normaler String. Ist ein String, bleibt ein String und wird auch als String der entsprechendne Methode übergeben die das dann parst/intepretiert) in welchem mein SQL-Statement beschrieben ist.
Diesen setzte ich vorher also so zusammen wie ich es brauche. Wenn alles steht, übergebe ich es der entsprechenden Methode als SQL-Statement.
Funktioniert in VB bisher immer so wunderbar nur das ich dort (also in VB6 noch) eben mit der Replace-Funktion gearbeitet hatte.
Aber es geht ja um das 'vorbereiten' und 'generieren' des SQL und das benutzen von diesem nachdem der 'fertiggestellt' wurde.
Ich sehe aber das das so bei Python dann wohl nicht so ohne weiteres funktioniert. Darum fragei ch euch profis ja.

@cofi

Ja wie jetzt? Keine Formatierung mit %s?? Jetzt verstehe ich gar nichts mehr.... :K
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@kaineanung: dass Du schon unter VB unsauber gearbeitet hast, heißt ja nicht, dass man auch dazulernen kann. Und dass etwas "funktioniert" ist auch nur ein Zeichen dafür, dass man bisher Glück gehabt hat. Und nochmal zum Auswendiglernen: das %s im SQL-Statement ist kein Formatierungszeichen sondern ein Platzhalter.

PS: eine kurze Suche nach VB und MySQL hat bei mir ergeben, dass man unter VB drei Befehle mehr braucht:
1. Statement-Objekt erzeugen, wobei man Platzhalter per @name angibt.
2. Prepare
3. Parameter setzen
4. Execute
Das erhöht natürlich die Hürde, sauber zu programmieren.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

<rant>
Ich bin zwar kein Freund von harten Vorgaben, aber der Umstand das SQL injections wahrscheinlich für die Mehrzahl aller erfolgreichen Angriffe auf Webseiten verantwortlich ist, schreit danach, dass ein DBMS-Konsortium einen SQL-Format-Standard schafft, welcher nicht mehr mit simpler Stringfummelei nutzbar ist. Weg mit plaintext SQL-Strings! :evil:
</rant>
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

kaineanung hat geschrieben:Ja wie jetzt? Keine Formatierung mit %s?? Jetzt verstehe ich gar nichts mehr.... :K
Guckstu:

Code: Alles auswählen

>>> dictStadt = {}
>>> dictStadt['name'] = "Stuttgart'; DROP TABLE stadt;--"
>>> sql1 = "select s.id from stadt s where s.name = '{stadt}'".format(stadt=unicode(dictStadt['name']))
>>> sql1
"select s.id from stadt s where s.name = 'Stuttgart'; DROP TABLE stadt;--'"
>>> sql2 = "select s.id from stadt s where s.name = '%s'" % unicode(dictStadt['name'])
>>> sql2
u"select s.id from stadt s where s.name = 'Stuttgart'; DROP TABLE stadt;--'"
Siehe auch (wiedermal...): https://xkcd.com/327/
Zuletzt geändert von pillmuncher am Mittwoch 2. Dezember 2015, 14:25, insgesamt 3-mal geändert.
In specifications, Murphy's Law supersedes Ohm's.
kaineanung
User
Beiträge: 145
Registriert: Sonntag 5. April 2015, 20:57

@Sirius

Ich glaube in wintzigen, kaum messbaren 'Spuren' ein wenig Sarkasmus zu erkennen in deinen letzten Sätzen..... :)

In VB benutzt man die ADO-Klasse und man benötigt lediglich die ADO-Connection welche sich um die Verbindung zur DB kümmert als auch um Befehle per Execute absetzen zu können. Will man Resultate in ein Recordset zurückliefern lassen so benötigt man noch eine ADO-Recordset-Klasse.
SQL generiere ich in einem STINKNORMALEN String und übergebe es der Execute-Methode des ADO-Connection-Objektes.
Das hat nichts mit Glück zu tun sondern es ist einfach nur ein STRING in welchem man SQL-Befehle speichert. Nochmal: ein (ASCII-)STRING.
Da VB6 kein Unicode und sonstwas kann, war eben der 'Vorteil' das das ganz gut geklappt hat. Ohne Glück und immer und durchgehend ohne mit der Wimper zu zucken.
Soviel dazu....

Jetzt lerne ich Python und hier ist es eben anderes (vermute mal weil es eben mehr kann und auch Unicode usw... versteht).
Mir aber vorzuwerfen das ich mein lebenlang mit dem SQL in VB nur Glück hatte und es nicht richtig gemacht habe nach unzähligen erfolgreichen Projekten und ohne jeglichen Probleme, ist ein wenig zu heftig wie ich finde...
:roll:
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@kaineanung:
Das ist kein Problem von Unicode vs. ASCII, sondern ein Domänenproblem, sprich wo und wie Du die Datenbank einsetzt oder Teile zugänglich machst. Stringformatierung ist super handlich und es ist auch kein Problem bei SQL, solange man die DB lokal hat und allen, die Zugriff darauf haben, vertraut.

Das Problem kommt dann auf, wenn Du mit Deinem Wissen um "oh ein SQL-String, den kann ich mit Stringformatierung bearbeiten" den Datenbankzugriff auf nicht vertrauenswürdige Kanäle ausdehnst. So wie Du Deine Vorgehensweise in VB beschreibst, wäre eine Webseite hiermit programmiert, wahrscheinlich angreifbar. Leider sind wir alle Gewohnheitstiere, d.h. das hier nicht anwendbare Wissen um Stringformatierung für SQL nehmen wir dann doch, einfach weil wir es kennen. Und es geht ja auch - super. Nein nicht super, "es geht" reicht eben nicht, um es sicher zu bekommen.

Nachtrag:
Ich hatte das Vergnügen, bei einer großen Bibliothek vor 6 Jahren injection Tests zu machen. Das Ergebnis war sehr gruselig, 2/3 aller Webeingaben waren injectable. Zusätzlich war bei einem Teil noch die Debug-Protollierung angeschaltet, weshalb Oracle treu und brav die Statements zurückgab. Damit war dann in wenigen Angriffen die DB-Struktur klar und man hätte nach Herzenslust Schindluder treiben können. Das Problem betrifft also nicht nur Dich, selbst zertifizierte Oracle-Datenbänkler sind gerne mal zu faul dazu, die Datenbank korrekt abzusichern.
Zuletzt geändert von jerch am Mittwoch 2. Dezember 2015, 15:06, insgesamt 1-mal geändert.
kaineanung
User
Beiträge: 145
Registriert: Sonntag 5. April 2015, 20:57

@jerch

Ach so ist das. Jetzt verstehe ich die Bedenken. Es ist ein Sicherheitsproblem...
Dennoch kommt der von mir beschriebene Fehler durch das Formatieren meines SQL-Strings wleche ich aber auch gleichzeitig der Execute-Methode übergebe.

Nun ja, dann sagt mir bitte wie ich das grundsätzlich richtig machen kann...

Ich muss der Execute-Methode ja ein SQL-String übergeben können. Und diesen würde ich gerne 'dynamisch' halten können damit ich dann die Platzhalter lediglich duch die Korrekten Values ersetzen kann.
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@jerch: Es geht hier nicht um Böswilligkeit. Natürlich wird die Wahrscheinlichkeit, dass ein Fehler auftritt viel größer, wenn jemand explizit nach einer Lücke sucht, aber schon normale Strings, wenn sie nicht richtig escaped werden, machen SQL-Statements kaputt, auch wenn sie dann keinen weiteren Schaden anrichten. Nimm einfach irgendeinen irischen Namen (O'Bruadair).

@kaineanung: es ist wirklich kein VB/Python-Unterschied sondern elementarste Parameterübergabe an SQL, die egal welche Wirtssprache man nimmt, vom Prinzip her immer gleich abläuft.
Es ist aber wirklich schwierig zu VB ein vernünftiges Beispiel zu finden, aber hier ist eins:

Code: Alles auswählen

Dim cmd As ADODB.Command
Dim par As ADODB.Parameter
Dim sSQL As String
Dim lngLenName As Long
lngSize = ? <--- put you field size for Name
sSQL = "UPDATE MyTable SET Name = @name WHERE ID = @id"

Set cmd = New ADODB.Command
With cmd
    .ActiveConnection = CN
    .CommandType = adCmdText    
    .CommandText = sSQL
    Set par = .CreateParameter("@name", adVarWChar, adParamInput, lngSize, txtName.Text)
    .Parameters.Append par
    Set par = .CreateParameter("@id", adInteger, adParamInput, , lngID)
    .Parameters.Append par
    .Execute , , adCmdText And adExecuteNoRecords
End With
Schön ist das zwar nicht, aber das liegt wahrscheinlich an der Sprache.

EDIT: wie man es wirklich richtig macht, wurde hier schon mehrfach gezeigt.
BlackJack

@kaineanung: Es wurde doch jetzt schon mehrfach gesagt wie man es richtig macht: In das SQL Platzhalter für die Werte, und die nicht selber ersetzen sondern `execute()` das tun lassen.

Und Du hast doch Glück gehabt das Deine SQL-selber-zusammenbasteln-Sachen Dir nicht um die Ohren geflogen sind. Weil selbst in vertauenswürdigen Umgebungen kann man da nicht alles unbehandelt reinformatieren ohne Probleme mit der SQL-Syntax zu bekommen. Das heisst so wie ein Benutzer da ”Freitext” reinbekommt hast Du die Gefahr dass das SQL ungültig sein kann und wenn der Benutzer mitbekommt/ahnt das seine Eingaben da direkt in SQL formiert werden, kann er Böse™ Sachen anstellen. Der Ansatz ist auch potentiell ineffektiver weil die DB-Engine wenn sie jedes mal anderes SQL sieht, weil die Werte darin immer anders sind, auch immer das SQL erneut parsen muss, während bei Platzhaltern die geparste und in einen Ablaufplan für die Abfrage umgesetzter SQL-Anfrage als Schlüssel für einen Cache mit Ablaufplänen dienen kann. Ausserdem kann so eine vorbereitete Abfrage auch für viele Werte verwendet werden, so das die Abfrage nur einmal an den Server übertragen werden muss, und dann die ganzen Daten für mehrere Ausführungen der Abfrage übermittelt werden. SQL per Hand zusammen zu schrauben ist genau so falsch wie XML-Dateien mit regulären Ausdrücken oder allgemein wie normale Textdateien zu verarbeiten. Das ”funktioniert” alles, aber halt nur so lange bis es erwartbarer Weise auf die Nase fällt und damit halt doch nicht wirklich *funktioniert*. Nur so lange man Glück hat…
Antworten