Teile einer Abfrage über raw_Input()

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
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.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Man müsste eben selbst quoten (ich habe jetzt auch nichts gefunden, das das erledigt - warscheinlich hat aber das DB-API 2.0 konforme Modul da schon etwas fertiges).

Wenn du es aber selbst implementierst kannst du dich ja orientieren was PgQuoteString macht.
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:Man müsste eben selbst quoten (ich habe jetzt auch nichts gefunden, das das erledigt - warscheinlich hat aber das DB-API 2.0 konforme Modul da schon etwas fertiges).

Wenn du es aber selbst implementierst kannst du dich ja orientieren was PgQuoteString macht.
:? öhm ... k ....
*verwirrt guck* ... damit kann ich jetzt gerade irgendwie gar nix anfangen :(

Ich glaub dann geht es doch einfacher und schneller ich beschränke die Eingabemöglichkeiten noch mehr ... so wie Gerold das macht, also Auswahlmöglicheiten stellen ... :( ...
(will/muss bis Ende Dezember mit Programmieren fertig sein für das Projekt und das mit dem selber quoten dauert dann wohl doch etwas länger, bis verstanden und umgesetzt, kommt aber auf jeden Fall mit auf die ToDO Liste zum angucken für danach ;-))

Danke euch allen.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Ich habe doch gesagt, dass selbst quoten in aller Regel nicht so einfach ist.

An sich ist es aber auch nicht so schwer, man muss nur unter Umständen "gefährliche" Zeichen, die nicht erlaubt sind escapen. Also etwa einen Backslash durch zwei, ein Quote durch ein escapetes-Quote, Tab als \t, etc.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
keppla
User
Beiträge: 483
Registriert: Montag 31. Oktober 2005, 00:12

gerold hat geschrieben:
keppla hat geschrieben:Indem du es nicht selber machst, sondern die API machen lässt
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.
Ja, das ist mir klar. Aber warum sollte ich tabellen- oder feldnamen escapen wollen? Wie sieht eine software aus, wo solche namen aus nicht vertrauenswürdigen Quellen kommen?
BlackJack

Na da wo sie ein Anwender eingeben kann.
Benutzeravatar
keppla
User
Beiträge: 483
Registriert: Montag 31. Oktober 2005, 00:12

Na da wo sie ein Anwender eingeben kann.
<loriot>ach?</loriot>

Ernsthaft: warum sollte man soetwas tun? Wenn ein Anwender bestimmen kann, auf welche Tabellen oder Felder queries losgelassen werden können, hilft escaping auch nicht mehr.

Um es mal plakativ darzustellen:

Code: Alles auswählen

delete from %s where %s = %s
Ich hab nun schon etwas länger mit Datenbanken zu tun, und die Notwendigkeit, dass Usereingaben als Felder oder Tabellen interpretiert werden können, kann ich einfach nicht sehen.
Und selbst _wenn_ es denn nötig sein sollte, braucht man da kein escaping, sondern nur eine positivliste:

Code: Alles auswählen

cursor = conn.cursor()
conn.execute('show tables')
tables = set(row[0] for row in conn.fetchall)

table = raw_input("auf welche tabelle soll der query losgelassen werden?")
if not table in tables:
   raise Exception("es sollte schon eine Tabelle sein")

cursor.execute("select * from %s" % table)
for row in cursor.fetchall():
   print row
analog kann man per "describe table" (zumindest in mysql, keine ahnung, ob das sql-standard ist) eine Positivliste für Felder anfertigen.
BlackJack

So etwas sollte man natürlich nicht tun wollen. Was aber nicht jeden davon abhält. ;-)
meneliel
User
Beiträge: 256
Registriert: Montag 25. Juni 2007, 08:35
Kontaktdaten:

keppla hat geschrieben: Ernsthaft: warum sollte man soetwas tun? Wenn ein Anwender bestimmen kann, auf welche Tabellen oder Felder queries losgelassen werden können, hilft escaping auch nicht mehr.

oh je oh je .... das will ich doch gar nicht :(

Alles was gehen soll, ist, das Teile des Where-Teiles formuliert werden sollen. Und das versuche ich so mininal wie möglich zu halten. Hab ich bei dem 2. Teil halt auch geschafft.

Ich will nur Indikatoren berechnen*, aus einer passenden Tabelle, wo die alle drin stehen, aber halt nicht alle auf einmal, sondern ich möchte das beschränken auf Kategorien - und da gibt es nicht nur eine, sondern 2 pro Indikator. Das kann am Ende recht viel werden. Also nicht nur KAT1 ="a" sondern vielleicht Kat 1 ="a" and (Kat2 ="A" OR Kat2 ="C"). Könnte aber auch sein, dass ich nru einen einzigen Indikator direkt selektiere über seine ID ... und das macht es irgenwie gerade etwas kompliziert ...

Funktioniert ja nun auch mit den Templates ganz gut :) ... eigentlich ... wenn man brav ist und/oder weiß was man tut ... Und so lange nur ich damit arbeite ist das auch okay. ... nur werde auf Dauer ich nicht allein damit arbeiten, nehme ich an...

* das eigentliche Berechnen sind im Prinzip das eigentlich zu lösende Problem... das Selektieren aus der Datenbank brauch ich nur um an die Argumente zu kommen, die der Funktion dann übergeben werden

*etwas hilflos guck* ... oh je oh je ... Ich versteh das mit dem escapen und quoten grade überhaupt nicht.... also so überhaupt nicht, dass ich gar nicht weiß was Ihr eigentlich meint und wie mir das jetzt weiterhelfen soll ... :oops: *schäm*
Benutzeravatar
keppla
User
Beiträge: 483
Registriert: Montag 31. Oktober 2005, 00:12

meneliel hat geschrieben:Alles was gehen soll, ist, das Teile des Where-Teiles formuliert werden sollen. Und das versuche ich so mininal wie möglich zu halten. Hab ich bei dem 2. Teil halt auch geschafft.
Minimal scheint mir das nicht.
Ich will nur Indikatoren berechnen*, aus einer passenden Tabelle, wo die alle drin stehen, aber halt nicht alle auf einmal, sondern ich möchte das beschränken auf Kategorien - und da gibt es nicht nur eine, sondern 2 pro Indikator. Das kann am Ende recht viel werden. Also nicht nur KAT1 ="a" sondern vielleicht Kat 1 ="a" and (Kat2 ="A" OR Kat2 ="C"). Könnte aber auch sein, dass ich nru einen einzigen Indikator direkt selektiere über seine ID ... und das macht es irgenwie gerade etwas kompliziert ...
nicht unbedingt.
in mysql kann man statt

Code: Alles auswählen

(kat = "1" or kat="2" ... or kat="n")
einfach

Code: Alles auswählen

kat in (1, 2, ... n)
schreiben, und das kann man auch mit dem escaping nutzen:

Code: Alles auswählen

query = "select * from tabelle where kat1 in %s and kat2 in %s"
values = (["A"], ["A", "C"])
cursor.execute(query, values) 
Funktioniert ja nun auch mit den Templates ganz gut Smile ... eigentlich ... wenn man brav ist und/oder weiß was man tut
also funktioniert es NICHT. Es bricht lediglich nicht sofort zusammen.
Bitte nicht falsch verstehen, das geht nicht gegen dich persönlich. Nur ist es einfach dumm, den komplizierten unsicheren weg, anstatt den einfachen, sicheren Weg zu gehen.
*etwas hilflos guck* ... oh je oh je ... Ich versteh das mit dem escapen und quoten grade überhaupt nicht.... also so überhaupt nicht, dass ich gar nicht weiß was Ihr eigentlich meint und wie mir das jetzt weiterhelfen soll ... Embarassed *schäm*
Escaping ist (ohne sql drumrum, plain python) z.B. sowas:

Code: Alles auswählen

text = 'hallo \'welt\''
die ' um welt werden mittels der \ "escaped", weil sonst python denkt, dass du einen string namens 'hallo ' hast, und danach wieder python kommt.
Bei sql hast du bei der "manuellen" ersetzung eben das Problmen, dass der user ' eingeben kann, und sql dann einen Teil seiner eingabe als befehl sieht.

Denke dir einen sql-string, der so aussieht:

Code: Alles auswählen

query = "select id from users where name='%s' and password='%s'"
und nun, wie er nach der ersetzung aussieht, wenn folgendes gilt

Code: Alles auswählen

query %= ("egal' or 1=1 --", "völlig egal")
print query # select id from users where name = 'egal' or 1=1 --'and password='völlig egal'
schwupps, hast du eine sicherheitslücke, über die sich jeder einloggen kann.
Auch, wenn man davon ausgehen könnte, dass alle user gutartig wären, müsste man sich drum kümmern, denn sonst kommt früher oder später ein user namens O'Hara daher, und macht völlig gutartigerweise eine Eingabe, die das Programm abschmieren lässt.

Aber, weil das Problem so häufig ist, hat unsere db-api da schon was vorbereitet, nämlich den zweiten Parameter der execute-Methode.
Wenn du

Code: Alles auswählen

c.execute("insert into users set name = %s", ["O'Hara"])
schreibst, kümmert sich execute darum, dass aus O'Hara O\'Hara wird, und du musst dir keine Sorgen mehr machen.
Zuletzt geändert von keppla am Dienstag 11. Dezember 2007, 22:30, insgesamt 1-mal geändert.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

So wie ich das sehe ist beim Escapen von SQL mehr nötig als nur die Apostrophe zu escapen, zumindest macht das von mir verlinkte Modul einige Sachen mehr.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
keppla
User
Beiträge: 483
Registriert: Montag 31. Oktober 2005, 00:12

Leonidas hat geschrieben:So wie ich das sehe ist beim Escapen von SQL mehr nötig als nur die Apostrophe zu escapen
Ich wollte es eigentlich auch nicht darauf reduzieren, ich hätte häufiger z.B. sagen sollen. Ich wollte nur anhand der Apostrophe die Problematik erklären
zumindest macht das von mir verlinkte Modul einige Sachen mehr.
Reicht das standardquoting von execute und co nicht aus?
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

keppla hat geschrieben:
zumindest macht das von mir verlinkte Modul einige Sachen mehr.
Reicht das standardquoting von execute und co nicht aus?
Was ist das "Standardquoting"? Ich denke, was `execute()` quotet ist ihm selbst überlassen, das heißt jedem Modul das es implementiert. Ich könnte mir vorstellen, dass die Module ganz unterschiedlich quoten - wollte zwar im Quellcode von psycopg2 nachschauen, aber dann hat sich herausgestellt dass das im C-Part implementiert zu sein scheint und dann hatte ich keine Lust mehr weiterzugucken.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
keppla
User
Beiträge: 483
Registriert: Montag 31. Oktober 2005, 00:12

Leonidas hat geschrieben:
keppla hat geschrieben:
zumindest macht das von mir verlinkte Modul einige Sachen mehr.
Reicht das standardquoting von execute und co nicht aus?
Was ist das "Standardquoting"? Ich denke, was `execute()` quotet ist ihm selbst überlassen, das heißt jedem Modul das es implementiert.
Ja, aber wir waren hier bei MySQL, und ich würde doch hoffen, dass das und die sonstigen gängigen apis da "vernünftig" handeln.
Hättest du da ein Gegenbeispiel?
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

keppla hat geschrieben:Ja, aber wir waren hier bei MySQL, und ich würde doch hoffen, dass das und die sonstigen gängigen apis da "vernünftig" handeln.
Hättest du da ein Gegenbeispiel?
Ja, sicherlich. Ich wollte nur betonen dass es womöglich nicht reicht einfach nur ' zu quoten. Daher sollte man sich umso mehr auf das Quoting des Moduls stützen.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
meneliel
User
Beiträge: 256
Registriert: Montag 25. Juni 2007, 08:35
Kontaktdaten:

ALSO :) ... nach noch einem Tipp von Keppla, und weiterer verbaler Prügel von Kollegen, ich könne dem USer bitte GAR nichts überlassen, egal ob da außer mir nur noch 1-2 andere jemals das Programm benutzen, hab ich mich jetzt doch hingesetzt und
1. eine Oberfläche zusammengebastelt, wo wirklich NICHTS weiter, außer NUR die Werte selber eingegeben werden.

und

2. die querys gestückelt zusammengebastelt, und das sieht nun so aus:
(also nur der kleine Ausschnitt:)

Code: Alles auswählen

elif indikator.get() == "category":
        where_i = 'select * from indizes where 1=1'
        
        # calc_cat
        c_list = calc_cat_entry.get().split(",")
        if len(c_list) > 0:
            i_values=[]
            where_i +=" and ("
            i = 0
            for c in c_list:
                        where_i +="calc_cat = %s"
                        i_values.append(c.lower())
                        i +=1
                        if len(c_list)-i > 0:
                                    where_i +=" or "
                        
            where_i +=")"
        # kategorie
        k_list = kat.get().split(",")
        # ... hier dann genau so, wie oben ....

       cursor.execute(where_i, i_values)
geht bestimmt wieder viel toller und kompakter.
Mich stört das i+=1 noch. Das krieg ich bestimtm gleich mit in die for schleife rein, aber irgendwie steh ich auch gerade auf dem Schlauch und ganzen tag Oberfläche zusammenbasteln schlaucht ...

Ist das so nun besser? und sicherer?
Antworten