Teile einer Abfrage über raw_Input()

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
meneliel
User
Beiträge: 256
Registriert: Montag 25. Juni 2007, 08:35
Kontaktdaten:

Ich möchte gern für bestimmt Einträge meiner Datenbank Berechnungen durchführen. Dafür dachte ich: "okay, dann lass ich den WHERE Teil des Selects einfach den Anwender selber bestimmen und frag das mit raw_input() ab.

Code sieht so aus:

Code: Alles auswählen

sel = raw_input("Formulieren Sie für die SQL-Abfrage die WHERE Clausel:\n" )

cursor.execute('select * from Indizes where s%' ,sel)
Eingabe + Fehlermeldung:

Code: Alles auswählen

Evaluating Indikatoren_loop.py
Passwort für MySQL: ********
Formulieren Sie ür die SQL-Abfrage die WHERE Clausel:
calc_cat = "a"
C:\Programme\Wing IDE 101 3.0\src\debug\tserver\_sandbox.py:25: Warning: Truncated incorrect INTEGER value: 'calc_cat = "a"'
calc_cat ist aber gar kein INT, sondern CHAR, mit Länge 1
Daher verstehe ich die Fehlermeldung nicht.

Und:

Code: Alles auswählen

>>> cursor.execute('select * from Indizes where calc_cat = "a"')
24L
>>> 
funktioniert ja auch.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

`%s` oder `s%`? Das hast du in deinem oben geposteten Code falschrum.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
Rebecca
User
Beiträge: 1662
Registriert: Freitag 3. Februar 2006, 12:28
Wohnort: DN, Heimat: HB
Kontaktdaten:

Ausserdem musst du die einzusetztenden Daten mit % angeben, nicht Komma:

Code: Alles auswählen

'select * from Indizes where %s' % sel
Offizielles Python-Tutorial (Deutsche Version)

Urheberrecht, Datenschutz, Informationsfreiheit: Piratenpartei
meneliel
User
Beiträge: 256
Registriert: Montag 25. Juni 2007, 08:35
Kontaktdaten:

Leonidas hat geschrieben:`%s` oder `s%`? Das hast du in deinem oben geposteten Code falschrum.
*argggg* Danke.

EDIT: gerade bei mir im Code geguckt, da steht es aber richtig drin und gleiche Fehlermeldung, dann muss das umgedrehte beim Code hier rein posten passiert sein...

Code: Alles auswählen

'select * from Indizes where %s' % sel
Ich dachte dass sei böse... und gar nicht gut, aber vielleicht hab ich da auch was falsch verstanden
Zuletzt geändert von meneliel am Donnerstag 6. Dezember 2007, 16:05, insgesamt 1-mal geändert.
Benutzeravatar
keppla
User
Beiträge: 483
Registriert: Montag 31. Oktober 2005, 00:12

Rebecca hat geschrieben:Ausserdem musst du die einzusetztenden Daten mit % angeben, nicht Komma:

Code: Alles auswählen

'select * from Indizes where %s' % sel
für Stringersetzungen, ja, aber ich denke, dass er da die funktionalität der funktion Execute nutzt (die sich um escaping kümmert und so), was in diesem Fall doch auch der bevorzugte Weg sein sollte.

edit: @meneniel, ich habe es auch so verstanden.
meneliel
User
Beiträge: 256
Registriert: Montag 25. Juni 2007, 08:35
Kontaktdaten:

Hab es jetzt aber so gemacht, wie von Rebecca vorgeschlagen und nun funktioniert es.

EDIT
Ich bin gerade ein ganz klein wenig verwirrt:

Code: Alles auswählen

cursor.execute('select AGS, Datum, AREA, ID from ebenen where (DES = %s)' % sel_ebene)
...
OperationalError: (1054, "Unknown column 'xyz' in 'where clause'")
ABER:

Code: Alles auswählen

>>> cursor.execute('select AGS, Datum, AREA, ID from ebenen where (DES = %s)' ,sel_ebene)
349L
nochmal EDIT:

nun kann es aber je nach Eingabe sein, dass sel_ebene mehr beeinhaltet als z.B. nur "xyz" sondern könnte auch drin stehen '"xyz" OR DES = "abc"'

für DEN Fall funktioniert dann nur wieder:

Code: Alles auswählen

cursor.execute('select AGS, Datum, AREA, ID from ebenen where (DES = %s)' % sel_ebene)
Was doof ist, hab extra nen dict. gemacht, das je nach USER - Eingabe (die müssen nur noch eine Zahl eingeben), dann den richtigen Ausdruck wählt). Heißt dass, ich müsste an dieser Stelle dann am bestennoch mal if/elif einfügen, je nach sel_ebene verschiedene Anweisungen für das execute wählen, oder gibt es auch eine Variante, mit der ich das Problem umgehen kann.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

rebeccas Tipp ist aber wie keppla schrieb in diesem Fall, falsch. Er reißt dir eine klasse SQL-Injection-Lücke in dein Programm.

xkcds Exploits of a mum zeigt das ganz gut - um das "sanitize" kümmert sich `execute()`.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
Rebecca
User
Beiträge: 1662
Registriert: Freitag 3. Februar 2006, 12:28
Wohnort: DN, Heimat: HB
Kontaktdaten:

:oops:
Offizielles Python-Tutorial (Deutsche Version)

Urheberrecht, Datenschutz, Informationsfreiheit: Piratenpartei
meneliel
User
Beiträge: 256
Registriert: Montag 25. Juni 2007, 08:35
Kontaktdaten:

Was mich verwirrt ist, dass es manchmal nur mit "%variable" funktioniert und nicht mit ",variable"... warum?
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Weil das Datenbankmodul nicht überall Variablen zulässt - etwa bei Tabellennamen nicht, die möchte das Datenbankmodul statisch haben.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
meneliel
User
Beiträge: 256
Registriert: Montag 25. Juni 2007, 08:35
Kontaktdaten:

Leonidas hat geschrieben:Weil das Datenbankmodul nicht überall Variablen zulässt - etwa bei Tabellennamen nicht, die möchte das Datenbankmodul statisch haben.
Das kann ich ja dann aber mit Stringersetzungen umgehen. Das heißt ich komme im Prinzip eh nicht um sie herum.

Gibt es eine Möglichkeit, in dem Beispiel, dass einheitlich hin zu bekommen? Weil an einigen Stellen, dass ja mit den Stringersetzungen auch nicht funktionierte. Mir ist dann in dem Fall wohl egal ob es böse ist. Es muss nur funktionieren. :(
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

meneliel hat geschrieben:Weil an einigen Stellen, dass ja mit den Stringersetzungen auch nicht funktionierte.
Hallo meneliel!

Probier mal http://docs.python.org/lib/node40.html aus. Das nehme ich immer her, wenn in den Texten öfter ein %-Zeichen vorkommt und der Text mit der normalen Stringersetzung zu fehleranfällig wird.

mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

meneliel hat geschrieben:
Leonidas hat geschrieben:Weil das Datenbankmodul nicht überall Variablen zulässt - etwa bei Tabellennamen nicht, die möchte das Datenbankmodul statisch haben.
Das kann ich ja dann aber mit Stringersetzungen umgehen. Das heißt ich komme im Prinzip eh nicht um sie herum.
An sich nicht - oder doch. Je nach Problem ist es so das Tabellennamen fest sein sollten.

Nochmal um es festzuhalten:
Die Datenstruktur _sollte_ fest sein, lediglich die Daten sollten variabel sein.

Die einzige Möglichkeit die mir einfällt wo man Abfragen verwendet die mit variablen Strukturen arbeiten sind SQL-Editoren. Da _musst_ du dich aber dann um das escapen kümmern.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
meneliel
User
Beiträge: 256
Registriert: Montag 25. Juni 2007, 08:35
Kontaktdaten:

Leonidas hat geschrieben:An sich nicht - oder doch. Je nach Problem ist es so das Tabellennamen fest sein sollten.

Nochmal um es festzuhalten:
Die Datenstruktur _sollte_ fest sein, lediglich die Daten sollten variabel sein.

Die einzige Möglichkeit die mir einfällt wo man Abfragen verwendet die mit variablen Strukturen arbeiten sind SQL-Editoren. Da _musst_ du dich aber dann um das escapen kümmern.
Natürlich, die Datenstruktur ist fest. Variabel über User-Eingabe habe ich nur beim Erstellen der Datenbank, den Tabellennamen (kann ich zur Not weglassen), die Datenbank wird ja eh nur einmal erstellt^^ und das Passwort zur Datenbank.

Das Problem was ich jetzt habe, betrifft ja auch mehr den Where Teil der Abfragen. Und der kann ja sehr variabel sein, einmal nur ein einzelner "Wert" der eingesetzt wird, ein ander mal eine etwas längere, verknüpfte Abfrage.

@ gerold: Danke für den Tipp mit den Templates: das funktioniert prima und löst gerade alle oben genannten Probleme, zumindest soweit ich das beim Rumprobieren bisher feststellen konnte. Danke :)
meneliel
User
Beiträge: 256
Registriert: Montag 25. Juni 2007, 08:35
Kontaktdaten:

Noch zum gleichen Problem: ein Freund hat jetzt noch ganz doll geschimpft mit mir, dass ich doch nicht einfach den User per Eingabe Teile einer Abfrage formulieren lassen kann, ohne das abzuchecken.

Durch Verwendung von Templates hab ich ja praktisch immer noch so eine SQL-Injection-Lücke, richtig? Ich glaube zwar nicht, dass da wer Code einschleußen kann, weil 1. nur der Where Teil formuliert wird und der z.T ja nicht mal komplett. WAS aber passieren kann, dass da wer irgendwas eingibt was es in der Datenbank praktisch nicht gibt, oder sich einfahc nur vertippt. Das ganze hab ich jetzt mal so abgefangen:

Code: Alles auswählen

while True:
    try:
        sel_indicatoren = Template('select NR FROM Indizes Where $where'
                                   ).substitute(where = raw_input(
                                       "Welche Indikatoren wollen Sie berechnen? (Formulieren v.'WHERE'(ohne das Schlüsselwort 'Where'):\n"))
        cursor.execute(sel_indicatoren)
        break
    except MySQLdb.OperationalError:
        print "Ihre Eingabe war nicht korrekt, bitte wiederholen.\n\n\n"
calc_cat_selection = cursor.fetchall()

while True:
    sel_ebenen = raw_input("Welche Ebenen wollen sie berechnen? Wählen Sie 1 - Bundesland, \n\
                        2-Landkreis,\n\
                        3- Regierungsbezirk,\n\
                        4- Landkreis/kreisfreie Städte:   ")
    if sel_ebenen not in ("1","2","3","4"):
        print "Ungültige Eingabe, bitte wiederholen sie diese.\n\n\n"
    else:
        break

while True:
    sel_land = raw_input("Für welches Bundesland wollen sie berechnen? (Bl-Kürzel (Bitte eingabe mit '""', bzw ALLE:    ")
    if sel_land == "ALLE":
        ebene_query = Template('select AGS, Datum, AREA, ID from ebenen where (DES = $des)'
                           ).substitute(des = ebene[sel_ebenen])
        cursor.execute(ebene_query)
        break
    elif sel_land in bl_dict.values():
        ebene_query = Template('select AGS, Datum, AREA, ID from ebenen where (DES = $des) AND Land = $land'
                           ).substitute(des = ebene[sel_ebenen], land = sel_land)
        cursor.execute(ebene_query)
        break
    else:
        print "Ungültige Eingabe. Bitte wiederholen sie diese.\n\n\n"
ebenen_selection = cursor.fetchall()
print ebenen_selection

Gibt es irgendwelche eklatanten Sicherheitslücken? Oder geht das mit dem Checken nach gültiger Eingabe eleganter?
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

So eine "Es wird ja schon nichts passieren"-Mentalität ist eben der Grund, warum es zu SQL-Injections kommt. Wenn du nicht quotest, hast du eine Sicherheitslücke - so einfach ist das. Und nein,`MySQLdb.OperationalError` zu testen ist keine tolle Idee um sich das zu ersparen, denn 'DROP TABLE Indizes;' gibt keinen Fehler zurück, es funktioniert einfach.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

meneliel hat geschrieben:Noch zum gleichen Problem: ein Freund hat jetzt noch ganz doll geschimpft mit mir, dass ich doch nicht einfach den User per Eingabe Teile einer Abfrage formulieren lassen kann, ohne das abzuchecken.
Hallo meneliel!

Er hat natürlich Recht. Und deshalb musst du abwiegen, ob du dich darum kümmern musst oder nicht. So etwas ist immer dann gefährlich, wenn du etwas übers Internet machen möchtest oder wenn dieses Programm von Benutzern bedient werden muss, denen du nicht komplett vertraust.

Wenn du dieses Programm aber nur für dich und ein paar vertrauenswürdige Freunde schreibst, dann ist das kein Problem.

Ist das nicht der Fall, dann lasse ich die Benutzer des Programms **keinen** Teil des SQL-Strings direkt eingeben. Ich biete Werte zur Auswahl an. Fazit: Programme für nicht vertrauenswürdige Benutzer sind immer umständlicher zu schreiben als Programme für vertrauenswürdige Benutzer.

mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
meneliel
User
Beiträge: 256
Registriert: Montag 25. Juni 2007, 08:35
Kontaktdaten:

Mit dem MySQLdb.OperationalError, kann ich aber zumindest "falsche" Eingaben abfangen, da reicht ja nen Tippfehler.

Wie verhinder ich nun Injections? Ich könnt z.B. checken, ob beim raw_input() ein ";" vorkommt und dann schon abbrechen?*

Gefahr besteht ja in dem Fall auch nur für die erste Abfrage, da die anderen beiden über vorkommen der Eingabe in einem Dictionary geprüft werden.


EDIT: * oder ich pack alle "bösen KEywords" in eine Liste, z.B. UPDATE, DELETE; ALTER TABLE;DROP, was auch immer und gucke halt ob eins der Wörter dann in der Eingabe vorkommt... wäre das ein Idee, das das Problem lösen könnte?
Benutzeravatar
keppla
User
Beiträge: 483
Registriert: Montag 31. Oktober 2005, 00:12

Mit dem MySQLdb.OperationalError, kann ich aber zumindest "falsche" Eingaben abfangen, da reicht ja nen Tippfehler.
WESSEN Tippfehler ist hier die Frage. Der User DARF durch egal was für eingaben keinen OperationalError verursachen.
Wie verhinder ich nun Injections? Ich könnt z.B. checken, ob beim raw_input() ein ";" vorkommt und dann schon abbrechen?*
Indem du es nicht selber machst, sondern die API machen lässt, wie schon erwähnt, indem du nicht
cursor.execute('select AGS, Datum, AREA, ID from ebenen where (DES = %s)' % sel_ebene)
sondern
cursor.execute('select AGS, Datum, AREA, ID from ebenen where (DES = %s)', sel_ebene)
machst.
Der kleine, aber feine unterschied ist, dass beim ersten mal execute einen string bekommt, bei dem du (per %) die Ersetzungen auf unsichere Art gemachst hast, beim zweiten bekommt execute einen string, und argumente, die es dareinschreiben soll, und kümmert sich darum, dass die ' escaped werden, etc.
oder ich pack alle "bösen KEywords" in eine Liste
es gibt keine "bösen keywords". Ein insert kann deine datenbank genau so inkonsistent (=unbrauchbar) machen, wie ein delete.
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

keppla hat geschrieben:Indem du es nicht selber machst, sondern die API machen lässt
Hallo keppla!

Damit kannst du nur Werte ersetzen lassen. Tabellennamen oder Feldnamen kannst du damit nicht ergänzend in die SQL-Anweisung einfügen, da ein Text immer in Anführungszeichen gesetzt wird.

mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Antworten