Sicherheit: Parameterübergabe an Kommandozeilen Tool

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
Benutzeravatar
jens
Moderator
Beiträge: 8483
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Mittwoch 11. Januar 2006, 08:03

(Thema geteilt von http://www.python-forum.de/viewtopic.php?t=4368 )

Ich hab gerade den Artikel im "Linux Magazin" 01/06 Seite 68 gelesen... Dort wird darauf hingewiesen, das Programmaufrufe ('Standard C function system()' in unserem Fall also os.system) gefählich sein können, wenn ein Teil des Befehls vom Anwender eingegeben wurde...

z.B. Haben wir ein Skript, welches irgendwas mit einer lokalen Datei machen soll... Der Dateiname muß der Anwender vorher eingeben. Unter Linux tippt der Anwender also fröhlich "foobar; rm -rf /home" ein... Das wird nun in irgendein Befehl eingefügt und per os.system() ausgeführt...
Aber auch unter Windows dürfte eine Eingabe wie "foobar; rmdir /s /q %HOMEPATH%" ein wenig Schaden anrichten...

Es ist natürlich kein unmittelbares Problem mit Python... Aber wie kann man damit umgehen???

Ich hab z.B. in MySQLdump in PyLucid so eine Schwachstelle. Ich biete ein HTML-Formular an: http://pylucid.python-hosting.com/file/ ... format=raw (sieht man hier ohne Styles!) und baue die Eingaben so zusammen:

Code: Alles auswählen

        try:
            compatible = self.CGIdata["compatible"]
        except KeyError:
            compatible = ""
        else:
            compatible = " --compatible=%s" % compatible

        default_command = "mysqldump --default-character-set=%(cs)s%(cp)s %(op)s -u%(u)s -p%(p)s -h%(h)s %(n)s" % {
            "cs" : self.CGIdata["character-set"],
            "cp" : compatible,
            "op" : self.CGIdata["options"],
            "u"  : self.config.dbconf["dbUserName"],
            "p"  : self.config.dbconf["dbPassword"],
            "h"  : self.config.dbconf["dbHost"],
            "n"  : self.config.dbconf["dbDatabaseName"],
        }
Nun kann man also auch damit schön unschöne Dinge machen :( (Allerdings kann das nur ein eingeloggter Admin tun, der wohl nicht seinen eigenen Server schrotten will)

In meinem Fall könnte ich natürlich hingehen und alle Parameter die mysqldump hat auflisten und nachsehen, ob die Eingaben auch wirklich ein gültiger Parameter ist... Aber das ist ziemlich eingeschränkt für den User, zumal es auch einige verschiedene Versionen von mysqldump gibt, mit unterschiedlichen parameter...

(btw. ich möchte das noch gern auf die Wiki Seite packen, möchte aber erstmal euer Feedback haben, damit ich keinen Mist verzapfe :lol: )
Zuletzt geändert von jens am Montag 16. Januar 2006, 09:19, insgesamt 1-mal geändert.

CMS in Python: http://www.pylucid.org
GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
mitsuhiko
User
Beiträge: 1790
Registriert: Donnerstag 28. Oktober 2004, 16:33
Wohnort: Graz, Steiermark - Österreich
Kontaktdaten:

Mittwoch 11. Januar 2006, 13:48

Code: Alles auswählen

str.encode("string-escape")
:wink:
TUFKAB – the user formerly known as blackbird
Benutzeravatar
jens
Moderator
Beiträge: 8483
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Mittwoch 11. Januar 2006, 14:03

Was soll das bringen?

Code: Alles auswählen

print "foobar; rm -rf /home".encode("string-escape")

CMS in Python: http://www.pylucid.org
GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Mittwoch 11. Januar 2006, 20:40

jens hat geschrieben:Nun kann man also auch damit schön unschöne Dinge machen :( (Allerdings kann das nur ein eingeloggter Admin tun, der wohl nicht seinen eigenen Server schrotten will)
[...]
(btw. ich möchte das noch gern auf die Wiki Seite packen, möchte aber erstmal euer Feedback haben, damit ich keinen Mist verzapfe :lol: )
Hi Jens

Ich sehe das nicht als Sicherheitslücke an, wenn das nur eine berechtigte Person tun darf. Man kann davon ausgehen, wenn sich jemand mit Benutzername und Passwort anmelden muss dass dadurch ein gewisses Maß an Sicherheit gegeben ist.

Wenn ein einfacher Benutzer, der sich evt. nicht einmal authentifizieren musste (z.B. bei Internetanwendungen), einen Befehl übergeben kann, der dann mit mehr Rechten ausgeführt wird, als der Benutzer normalerweise hat, dann ist das für mich eine Sicherheitslücke.

Wenn ein Benutzer einfach nur die Konsole öffnen muss um mit "rm -fr /home" den "/home"-Ordner zu löschen, dann macht es keinen Sinn, ein Programm so aubzusichern, dass dieser Befehl nicht über das eigene Python-Programm ausgeführt werden kann. Ganz besonders nicht, wenn der Benutzer sogar Zugriff auf den Quellcode, wie bei Python, hat.

Wir müssen natürlich darauf achten, dass es einem nicht zu einfach gemacht wird, Schaden am System anzurichten. Internetprogramme habe da leider einen schweren Stand, da der normale User keine Rechte auf dem Computer haben sollte. Aber schon ein einfacher Counter braucht mindestens das Schreibrecht für eine Datei. Da hier vom Benutzer auch kein Zugriff auf den Quellcode besteht, ist hier natürlich darauf zu achten, dass der Benutzer keine Befehle mit den Rechten des Serverprogramms ausführen kann. Das Serverprogramm sollte zusätzlich mit so wenig Rechten wie unbedingt nötig ausgestattet werden. Usw.

Das war es, was ich in so kurzer Zeit dazu zu sagen habe.

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

Mittwoch 11. Januar 2006, 20:45

gerold hat geschrieben:Aber schon ein einfacher Counter braucht mindestens das Schreibrecht für eine Datei.
Unter anderem deswegen werden auch gerne Datenbanken eingesetzt.. ihr wollt nicht wissen was ich für Probleme mit den Flatfiles des DokuWikis hatte. Besonders dumm wenn der FTP-User und der PHP-User zwei verschiedene sind und nichtmal in der selben gruppe sind (und man sie auch nicht in die gleiche Gruppe setzen kann).
My god, it's full of CARs! | Leonidasvoice vs Modvoice
mitsuhiko
User
Beiträge: 1790
Registriert: Donnerstag 28. Oktober 2004, 16:33
Wohnort: Graz, Steiermark - Österreich
Kontaktdaten:

Mittwoch 11. Januar 2006, 21:04

jens hat geschrieben:Was soll das bringen?

Code: Alles auswählen

print "foobar; rm -rf /home".encode("string-escape")
Das würd ich jetzt aber auch gerne wissen. Ich mein sowas:

Code: Alles auswählen

os.system("mysqldump '%(database)s' > '%(outputfile)'" % (database.encode("string-escape"), outputfile.encode("string-escape")))
TUFKAB – the user formerly known as blackbird
Benutzeravatar
jens
Moderator
Beiträge: 8483
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Montag 16. Januar 2006, 08:46

OK, das macht schon mehr Sinn:

Code: Alles auswählen

database = 'foobar; "rm -rf /home"'
outputfile = "foobar; 'rm -rf /home'"

print "mysqldump '%(database)s' > '%(outputfile)s'" % {
    "database": database.encode("string-escape"),
    "outputfile": outputfile.encode("string-escape")
}
mysqldump 'foobar; "rm -rf /home"' > 'foobar; \'rm -rf /home\''
Wobei ich nicht weiß, ob nicht evtl. doch die erste Variante funktionieren könnte? Weil vielleicht ' == " ???
Aber zumindest weiß ich, in welche Richtung dein Tipp geht...

(EDIT: Ich hab das Thema mal geteilt)

CMS in Python: http://www.pylucid.org
GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
modelnine
User
Beiträge: 670
Registriert: Sonntag 15. Januar 2006, 18:42
Wohnort: Celle
Kontaktdaten:

Montag 16. Januar 2006, 11:20

'nem ganz simplen Aufruf von os.exec*()?

Da hast Du eben nicht das Problem dass die Shell dazwischen sitzt und Parameter interpretiert. Genau das ist aber hier Dein Problem.

Beispiel:

Code: Alles auswählen

import os

if not os.fork():
  # Ich bin das Kind, ich darf meinen Prozess ersetzen. Nach dem os.execl-Aufruf
  # komme ich nicht mehr nach Python zurück.
  os.execl("/usr/bin/mysqldump","mysqldump","arg1","arg2",...)
else:
  # Ich bin der Parent, habe mich gerade geforked, kann ganz normal weitermachen.
Beachte, dass das Argument 0 des Aufrufs immer der Befehlsname selbst ist (also in diesem Fall mysqldump dupliziert ist).

Da die Shell nicht mehr bei der Interpretation dazwischen sitzt hast Du keinerlei Probleme auf Shell-Escapes achten zu müssen.

Weitere Informationen über os.exec* und os.fork (die es nur unter Unix gibt, Windows hat os.spawn()):

http://www.python.org/doc/2.4.2/lib/os-process.html

--- Heiko.
Benutzeravatar
jens
Moderator
Beiträge: 8483
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Montag 16. Januar 2006, 11:30

Das ist wirklich die beste Idee, die Kapslung der Argumente!

Jetzt ist mir auch klar, warum es bei subrpocess ein Argument shell gibt, denn ich dann wohl besser auf shell=False setzte :wink:

CMS in Python: http://www.pylucid.org
GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
modelnine
User
Beiträge: 670
Registriert: Sonntag 15. Januar 2006, 18:42
Wohnort: Celle
Kontaktdaten:

Montag 16. Januar 2006, 11:52

Du verstehst das falsch. Die Argumente sind nicht gekapselt, sie werden 1 zu 1 an den Unterprozess weitergegeben wie Du sie angibst.

Wenn Du os.system() aufrufst wird eine Subshell erzeugt, die dann den String als Befehl bekommt. Die shell macht aber allerlei Interpretationen der Parameter (wie zum Beispiel Wildcards durch entsprechende Dateinamen ersetzen, und ";" usw. interpretieren), um dann daraus einen os.exec*() Aufruf zu erzeugen.

Das bedeutet halt, dass Du mittels os.exec* keinerlei Shell-Befehle benutzen kannst. Unter Unix sind das aber im Normalfall eigentlich nur Befehle die Du eh nicht brauchst, weil cp, etc. alle als Binaries auf dem System liegen und nicht wie bei Windows Shell-Befehle sind.

--- Heiko.
Benutzeravatar
jens
Moderator
Beiträge: 8483
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Montag 16. Januar 2006, 12:18

OK, aber es ist schon richtig, das es dann bei subprocess, einfach mit einem shell=False getan ist, gehe ich mal von aus...

CMS in Python: http://www.pylucid.org
GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
modelnine
User
Beiträge: 670
Registriert: Sonntag 15. Januar 2006, 18:42
Wohnort: Celle
Kontaktdaten:

Montag 16. Januar 2006, 12:27

OK, aber es ist schon richtig, das es dann bei subprocess, einfach mit einem shell=False getan ist, gehe ich mal von aus...
Jo. ;-)

--- Heiko.
Antworten