Seite 1 von 2

Teile einer Abfrage über raw_Input()

Verfasst: Donnerstag 6. Dezember 2007, 13:06
von meneliel
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.

Verfasst: Donnerstag 6. Dezember 2007, 15:44
von Leonidas
`%s` oder `s%`? Das hast du in deinem oben geposteten Code falschrum.

Verfasst: Donnerstag 6. Dezember 2007, 15:49
von Rebecca
Ausserdem musst du die einzusetztenden Daten mit % angeben, nicht Komma:

Code: Alles auswählen

'select * from Indizes where %s' % sel

Verfasst: Donnerstag 6. Dezember 2007, 16:02
von meneliel
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

Verfasst: Donnerstag 6. Dezember 2007, 16:03
von keppla
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.

Verfasst: Donnerstag 6. Dezember 2007, 16:07
von meneliel
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.

Verfasst: Freitag 7. Dezember 2007, 14:23
von Leonidas
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()`.

Verfasst: Freitag 7. Dezember 2007, 14:48
von Rebecca
:oops:

Verfasst: Freitag 7. Dezember 2007, 21:41
von meneliel
Was mich verwirrt ist, dass es manchmal nur mit "%variable" funktioniert und nicht mit ",variable"... warum?

Verfasst: Freitag 7. Dezember 2007, 21:44
von Leonidas
Weil das Datenbankmodul nicht überall Variablen zulässt - etwa bei Tabellennamen nicht, die möchte das Datenbankmodul statisch haben.

Verfasst: Sonntag 9. Dezember 2007, 10:54
von meneliel
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. :(

Verfasst: Sonntag 9. Dezember 2007, 11:05
von gerold
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
:-)

Verfasst: Sonntag 9. Dezember 2007, 12:18
von Leonidas
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.

Verfasst: Montag 10. Dezember 2007, 10:10
von meneliel
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 :)

Verfasst: Dienstag 11. Dezember 2007, 12:10
von meneliel
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?

Verfasst: Dienstag 11. Dezember 2007, 12:28
von Leonidas
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.

Verfasst: Dienstag 11. Dezember 2007, 12:44
von gerold
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
:-)

Verfasst: Dienstag 11. Dezember 2007, 12:47
von meneliel
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?

Verfasst: Dienstag 11. Dezember 2007, 13:47
von keppla
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.

Verfasst: Dienstag 11. Dezember 2007, 14:13
von gerold
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
:-)