|
|
Dieser Artikel ist verfübar in: English Castellano Deutsch Francais Nederlands Portugues Russian Turkce |
von Frédéric Raynal, Christophe Blaess, Christophe Grenier <pappy(at)users.sourceforge.net, ccb(at)club-internet.fr, grenier(at)nef.esiea.fr> Über den Autor: Christophe Blaess ist selbständiger Aeronautik-Ingenieur. Er ist Linuxfan und erledigt einen großen Teil seiner Arbeit auf diesem System. Er koordiniert die Übersetzung der Man-Seiten, die vom Linux Documentation Project herausgegeben werden. Christophe Grenier studiert im 5. Jahr an der ESIEA, wo er auch als Systemadministrator arbeitet. Er interessiert sich besonders für Computersicherheit. Frédéric Raynal benutzt Linux seit vielen Jahren, weil es nicht mit Fetten verseucht ist, frei von künstlichen Hormonen und ohne BSE ist, es enthält nur den Schweiß ehrlicher Leute und einige Tricks. Übersetzt ins Deutsche von: Hermann J. Beckers <beckerst(at)lst-oneline.de> Inhalt:
|
Zusammenfassung:
Abruf einer Datei, Start eines Programmes durch ein schlecht programmiertes Perl-Skript. ... "Viele Wege führen zum Ziel!"
Bisherige Artikel in dieser Serie :
Wenn ein Klient eine HTML-Datei anfordert, sendet der Server die gewünschte Seite (oder eine Fehlermeldung). Der Browser interpretiert die HTML-Anweisungen, um die Datei zu formatieren und anzuzeigen. Wenn zum Beispiel der
http://www.linuxdoc.org/ HOWTO/HOWTO-INDEX/howtos.html
URL (Uniform Request Locator) eingegeben wird, nimmt der Klient Verbindung mit dem www.linuxdoc.org
-Server auf und fordert die /HOWTO/HOWTO-INDEX/howtos.html
-Seite mittels des HTTP-Protokolls an. Wenn die Seite existiert, sendet der Server die angeforderte Datei.
In diesem statischen Modell wird die Datei, wenn sie auf dem Server vorhanden ist, "unverändert" an den Klient übertragen, andernfalls wird eine Fehlernachricht gesendet (das allzu gut bekannte 404 - Not Found).
Leider erlaubt dies keine Interaktivität mit der Benutzerin, daher sind Eigenschaften wie elektronische Geschäfte, ResErvierungen oder E-irgendwas sonst noch nicht möglich.
Glücklicherweise gibt es Lösungen, um HTML-Seiten dynamisch zu generieren. CGI (Common Gateway Interface)-Skripte gehören dazu. In diesem Fall ist der URL zum Zugriff auf diese Seite etwas anders aufgebaut:
http://<server><pathToScript>[?[param_1=val_1][...] [¶m_n=val_n]]
QUERY_STRING
gespeichert. In diesem Zusammenhang ist ein CGI-Skript nichts anderes als eine ausführbare Datei. Es benutzt stdin
(Standard-Eingabe) oder die Umgebungs-Variable QUERY_STRING
zur Übernahme der Argumente. Nach Ausführung des Programmtextes wird das Ergebnis über stdout
(Standard-Ausgabe) an den Web-Klient weitergeleitet. Fast jede Programmiersprache kann zur Erstellung eines CGI-Skriptes benutzt werden (kompilierte C-Programme, Perl,
Shell-Skripte...). Lassen Sie uns z. B. einmal herausfinden, was die HOWTOs von
www.linuxdoc.org
über ssh wissen:
http://www.linuxdoc.org/cgi-bin/ldpsrch.cgi?
svr=http%3A%2F%2Fwww.linuxdoc.org& srch=ssh&db=1&scope=0&rpt=20
www.linuxdoc.org
;/cgi-bin/ldpsrch.cgi
;?
ist der Anfang einer langen Argumentliste:
srv=http%3A%2F%2Fwww.linuxdoc.org
ist der Server, von dem die Anforderung kommt;srch=ssh
enthält die eigentliche Anfrage;db=1
bedeutet, dass sich die Anfrage nur auf
HOWTOs bezieht;scope=0
bedeutet, das sich die Anfrage auf den Dokumentinhalt und nicht den Titel bezieht;rpt=20
begrenzt die Anzahl angezeigter Antworten auf 20.Oft sind Argumentnamen und Werte eindeutig genug, um ihre Bedeutung zu verstehen. Außerdem ist der Inhalt der Seite mit den Antworten von größerer Bedeutung.
Nun wissen Sie, dass das schöne an CGI-Skripten die Möglichkeit für den Benutzer ist, Argumente zu übergeben ... der Nachteil ist aber, das ein schlecht geschriebenes Skript eine Sicherheitslücke öffnen kann.
Sie haben wahrscheinlich die seltsamen Zeichen bemerkt, die Ihr bevorzugter Browser benutzt oder die im Beispiel benutzt wurden. Diese Zeichen sind im URI-encoded-Format. Die Tabelle 1 stellt einige Zeichen mit ihrer Bedeutung dar. Einige IIS4.0- und IIS5.0-Server weisen Sicherheitslücken auf, die auf diesen Zeichen basieren.
SSI Server Side
Include
"Server Side Include
ist eine Funktion des Webservers. Diese erlaubt es, Anweisungen in Webseiten zu integrieren, entweder zum Einfügen einer Datei oder zur Ausführung eines Befehls (Shell- oder CGI-Skript).
In der Apache-Konfigurations-Datei httpd.conf
aktiviert die "AddHandler server-parsed .shtml
" -Anweisung diesen Mechanismus. Um eine Unterscheidung zwischen .html
und .shtml
zu vermeiden, wird oftmals die .html
-Erweiterung hinzugefügt. Das verlangsamt natürlich den Server ... Auf Verzeichnisebene kann dies mit folgenden Instruktionen kontrolliert werden:
Options Includes
aktiviert jedes SSI ;OptionsIncludesNoExec
verhindert die Ausführung von exec
cmd
und exec cgi
.Im angehängten guestbook.cgi
-Skript wird der von der Benutzerin eingegebene Text in eine HTML-Datei eingebunden, ohne die Zeichen '<' und ' >' in die <- und >-HTML-Kodierungen zu wandeln. Das kann jemand dazu anregen, folgende Instruktionen einzugeben:
<!--#printenv -->
(beachten Sie das Leerzeichen nach printenv
)<!--#exec cmd="cat /etc/passwd"-->
guestbook.cgi?email=pappy&texte=%3c%21--%23printenv%20--%3e
DOCUMENT_ROOT=/home/web/sites/www8080 HTTP_ACCEPT=image/gif, image/jpeg, image/pjpeg, image/png, */* HTTP_ACCEPT_CHARSET=iso-8859-1,*,utf-8 HTTP_ACCEPT_ENCODING=gzip HTTP_ACCEPT_LANGUAGE=en, fr HTTP_CONNECTION=Keep-Alive HTTP_HOST=www.esiea.fr:8080 HTTP_PRAGMA=no-cache HTTP_REFERER=http://www.esiea.fr:8080/~grenier/cgi/guestbook.cgi? email=&texte=%3C%21--%23include+file%3D%22guestbook.cgi%22--%3E HTTP_USER_AGENT=Mozilla/4.76 [fr] (X11; U; Linux 2.2.16 i686) PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/X11R6/bin REMOTE_ADDR=194.57.201.103 REMOTE_HOST=nef.esiea.fr REMOTE_PORT=3672 SCRIPT_FILENAME=/mnt/c/nef/grenier/public_html/cgi/guestbook.html SERVER_ADDR=194.57.201.103 [email protected] SERVER_NAME=www.esiea.fr SERVER_PORT=8080 SERVER_SIGNATURE=<ADDRESS>Apache/1.3.14 Server www.esiea.fr Port 8080</ADDRESS> SERVER_SOFTWARE=Apache/1.3.14 (Unix) (Red-Hat/Linux) PHP/3.0.18 GATEWAY_INTERFACE=CGI/1.1 SERVER_PROTOCOL=HTTP/1.0 REQUEST_METHOD=GET QUERY_STRING= REQUEST_URI=/~grenier/cgi/guestbook.html SCRIPT_NAME=/~grenier/cgi/guestbook.html DATE_LOCAL=Tuesday, 27-Feb-2001 15:33:56 CET DATE_GMT=Tuesday, 27-Feb-2001 14:33:56 GMT LAST_MODIFIED=Tuesday, 27-Feb-2001 15:28:05 CET DOCUMENT_URI=/~grenier/cgi/guestbook.shtml DOCUMENT_PATH_INFO= USER_NAME=grenier DOCUMENT_NAME=guestbook.shtml
Die exec
-Anweisung stellt Ihnen fast eine Shell-Umgebung bereit:
guestbook.cgi?email=ppy&texte=%3c%21--%23exec%20cmd="cat%20/etc/passwd"%20--%3e
Versuchen Sie nicht "<!--#include
file="/etc/passwd"-->
", die Pfadangabe ist relativ zum Verzeichnis der HTML-Datei und darf kein "..
" enthalten. Die Apache-error_log
-Datei enthält dann eine Nachricht, die einen Zugriffsversuch auf eine gesperrte Datei anzeigt. Der Benutzer sieht die Nachricht [an error occurred while
processing this directive]
in der HTML-Seite.
SSI werden oft gar nicht benötigt und dann ist es besser, sie auf dem Webserver zu deaktivieren. Die Ursache des Problems ist jedoch die Kombination der fehlerhaften Gästebuch-Anwendung und dem SSI .
In diesem Abschnitt präsentieren wir Sicherheitslücken von in Perl geschriebenen CGI-Skripten. Um die Übersicht zu wahren, zeigen wir nicht den vollständigen Programmtext, sondern nur die zum Problemverständnis erforderlichen Teile.
Alle Skripte sind nach folgendem Muster aufgebaut:
#!/usr/bin/perl -wT BEGIN { $ENV{PATH} = '/usr/bin:/bin' } delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; # Make %ENV safer =:-) print "Content-type: text/html\n\n"; print "<HTML>\n<HEAD>"; print "<TITLE>Remote Command</TITLE></HEAD>\n"; &ReadParse(\%input); # now use $input e.g like this: # print "<p>$input{filename}</p>\n"; # #################################### # # Start of problem description # # #################################### # # ################################## # # End of problem description # # ################################## # form: print "<form action=\"$ENV{'SCRIPT_NAME'}\">\n"; print "<input type=texte name=filename>\n </form>\n"; print "</BODY>\n"; print "</HTML>\n"; exit(0); # first arg must be a reference to a hash. # The hash will be filled with data. sub ReadParse($) { my $in=shift; my ($i, $key, $val); my $in_first; my @in_second; # Read in text if ($ENV{'REQUEST_METHOD'} eq "GET") { $in_first = $ENV{'QUERY_STRING'}; } elsif ($ENV{'REQUEST_METHOD'} eq "POST") { read(STDIN,$in_first,$ENV{'CONTENT_LENGTH'}); }else{ die "ERROR: unknown request method\n"; } @in_second = split(/&/,$in_first); foreach $i (0 .. $#in_second) { # Convert plus's to spaces $in_second[$i] =~ s/\+/ /g; # Split into key and value. ($key, $val) = split(/=/,$in_second[$i],2); # Convert %XX from hex numbers to alphanumeric $key =~ s/%(..)/pack("c",hex($1))/ge; $val =~ s/%(..)/pack("c",hex($1))/ge; # Associate key and value # \0 is the multiple separator $$in{$key} .= "\0" if (defined($$in{$key})); $$in{$key} .= $val; } return length($#in_second); }
Die an Perl übergebenen Argumente (-wT
) besprechen wir später. Wir beginnen mit der Bereinigung der $ENV
und $PATH
-Umgebungsvariablen und wir senden die HTML-Kopfzeilen (das ist Teil des html-Protokolls zwischen Browser und Server, Sie sehen dies nicht in der angezeigten Web-Seite). Die ReadParse()
-Funktion liest die dem Skript übergebenen Argumente. Dies kann mit Modulen einfacher gestaltet werden, aber auf diese Weise sehen Sie die gesamten Anweisungen. Als nächstes präsentieren wir die Beispiele. Dann enden wir mit der HTML-Datei.
Perl behandelt jedes Zeichen gleich, wodurch es sich z. B. von C-Funktionen unterscheidet. Für Perl ist das Null-Zeichen als Ende einer Zeichenkette ein Zeichen wie jedes andere. Wo liegt das Problem?
Wir fügen die folgenden Anweisungen zu unserem Skript hinzu und erhalten showhtml.cgi
:
# showhtml.cgi my $filename= $input{filename}.".html"; print "<BODY>File : $filename<BR>"; if (-e $filename) { open(FILE,"$filename") || goto form; print <FILE>; }
Die ReadParse()
-Funktion erhält als einziges Argument den Namen der anzuzeigenden Datei. Um jemanden mit "schlechten Manieren"
davon abzuhalten, etwas anderes als HTML-Dateien zu lesen, hängen wir die
".html
"-Erweiterung an das Ende des Dateinamens. Aber denken Sie daran, dass das Null-Byte ein Zeichen wie jedes andere ist ...
Wenn unsere Anforderung
showhtml.cgi?filename=%2Fetc%2Fpasswd%00
ist, heisst die Datei my $filename = "/etc/passwd\0.html"
und unsere verwunderten Augen schauen auf etwas, was nicht HTML ist.
Was passiert? Der strace
-Befehl zeigt, wie Perl eine Datei öffnet:
/tmp >>cat >open.pl << EOF > #!/usr/bin/perl > open(FILE, "/etc/passwd\0.html"); > EOF /tmp >>chmod 0700 open.pl /tmp >>strace ./open.pl 2>&1 | grep open execve("./open.pl", ["./open.pl"], [/* 24 vars */]) = 0 ... open("./open.pl", O_RDONLY) = 3 read(3, "#!/usr/bin/perl\n\nopen(FILE, \"/et"..., 4096) = 51 open("/etc/passwd", O_RDONLY) = 3
Der letzte open()
-Befehl, der von strace
angezeigt wird, korrespondiert mit dem entsprechenden Systemaufruf in C. Wir
sehen, dass die
.html
-Erweiterung verschwindet und dies erlaubt es uns, die Datei /etc/passwd zu öffnen.
Dieses Problem lässt sich mit einem regulären Ausdruck lösen, der alle Null-Bytes entfernt: :
s/\0//g;
Nun folgt ein Skript ohne jeden Schutz. Es zeigt eine Datei aus dem Verzeichnisbaum /home/httpd/ an:
#pipe1.cgi my $filename= "/home/httpd/".$input{filename}; print "<BODY>File : $filename<BR>"; open(FILE,"$filename") || goto form; print <FILE>;
Lachen Sie nicht über dieses Beispiel! Ich habe schon solche Skrispte gesehen.
Die erste Ausnutzung ist offensichtlich:
pipe1.cgi?filename=..%2F..%2F..%2Fetc%2FpasswdDies ist ausreichend, um innerhalb des Verzeichnisbaumes auf jede Datei zuzugreifen. Es gibt aber eine noch viel interessantere Möglichkeit: Sie können einen beliebigen Befehl ausführen. In Perl öffnet der
open(FILE, "/bin/ls")
-Befehl die Binär-Datei "/bin/ls
" ... aber open(FILE, "/bin/ls |")
führt den angegebenen Befehl aus.
Das Hinzufügen eines einzelnen Pipe-(|
)-Symbols ändert das Verhalten von open()
. Ein weiteres Problem rührt daher, dass nicht auf die Existenz der Datei getestet wird. Das erlaubt es, nicht nur jeden Befehl auszuführen, sondern auch beliebige Argumente zu übergeben: :
pipe1.cgi?filename=..%2F..%2F..%2Fbin%2Fcat%20%2fetc%2fpasswd%20|
zeigt den Inhalt der Passwort-Datei an.
Die Überprüfung auf das Vorhandensein der Datei schränkt die Möglichkeiten ein:
#pipe2.cgi my $filename= "/home/httpd/".$input{filename}; print "<BODY>File : $filename<BR>"; if (-e $filename) { open(FILE,"$filename") || goto form; print <FILE> } else { print "-e failed: no file\n"; }Das vorherige Beispiel klappt nicht mehr. Der "
-e
"
Test versagt, weil die "../../../bin/cat
/etc/passwd |
"-Datei nicht gefunden wird.Nun versuchen wir den /bin/ls
-Befehl. Das Verhalten wird gleich sein. Wenn wir z. B. versuchen, den Inhalt des /etc
-Verzeichnisses anzuzeigen, testet der "-e
"-Test auf das Vorhandensein von "../../../bin/ls /etc |
"
, aber das gibt es ja auch nicht. Solange wir nicht den Namen einer
"Phantom"-Datei angeben, werden wir nichts Interessantes zu sehen bekommen :(
Es gibt jedoch immer noch eine "Flucht-Möglichkeit", auch wenn die
Ergebnisse nicht so gut sind. Die /bin/ls
-Datei existiert
(jedenfalls in den meisten Systemen), aber wenn open()
mit
diesem Dateinamen aufgerufen wird, wird nicht der Befehl ausgeführt, sondern
es wird das Programm angezeigt. Wir müssen eine Möglichkeit finden, um ein
Pipe-(|
)-Zeichen an das Ende des Namens zu setzen, ohne dass es der Prüfung durch "-e
" unterzogen wird. Wir kennen die Lösung bereits: Das Null-Byte. Wenn wir "../../../bin/ls\0|
" als Name übergeben, ist der Existenz-Test erfolgreich, weil nur
"../../../bin/ls
" getestet wird, aber open()
erkennt die Pipe und führt dann den Befehl aus. Deshalb lautet der URL zum Anzeigen des aktuellen Verzeichnisinhalts:
pipe2.cgi?filename=../../../bin/ls%00|
Das finger.cgi-Skript führt den finger
-Befehl auf unserer Maschine aus:
#finger.cgi print "<BODY>"; $login = $input{'login'}; $login =~ s/([;<>\*\|`&\$!#\(\)\[\]\{\}:'"])/\\$1/g; print "Login $login<BR>\n"; print "Finger<BR>\n"; $CMD= "/usr/bin/finger $login|"; open(FILE,"$CMD") || goto form; print <FILE>
Dieses Skript benutzt (endlich) eine nützliche Massnahme: Es kümmert sich um einige seltsame Zeichen, um deren Interpretation durch eine Shell zu verhinden, indem es ihnen ein '\
' voransetzt. So wird das Semikolon durch den regulären Ausdruck in "\;
" umgewandelt. Aber die Liste enthält nicht jedes wichtige Zeichen. Neben anderen fehlt die Zeilenschaltung '\n
'.
In der Shell wird ein Befehl validiert, indem die RETURN
oder ENTER
-Taste gedrückt wird, wodurch das
'\n
'-Zeichen geschickt wird. In Perl können sie das gleiche
tun. Wir haben bereits gesehen, dass es die open()
-Anweisung erlaubt, einen Befehl auszuführen, wenn die Zeile mit dem Pipe-'|
'-Zeichen endet.
Um dieses Verhalten zu simulieren, reicht es, eine Zeilenschaltung und eine Anweisung nach dem Login an den finger-Befehl zu senden:
finger.cgi?login=kmaster%0Acat%20/etc/passwd
Um mehrere Anweisungen nacheinander auszuführen, sind auch folgende Sonderzeichen von Bedeutung:
;
: beendet die erste Anweisung und geht zur nächsten ;&&
: wenn die erste Anweisung erfolgreich ist (z. B. ergibt 0 in einer Shell), dann wird die nächste Anweisung ausgeführt;||
: wenn die erste Anweisung nicht erfolgreich ist (z. B. . ergibt einen Wert ungleich Null in einer Shell), dann wird die nächste Anweisung ausgeführt.Das finger.cgi
-Skript vermeidet Probleme mit einigen seltsamen Zeichen. So funktioniert der URL
<finger.cgi?login=kmaster;cat%20/etc/passwd
nicht, weil das Semikolon maskiert ist. Ein Zeichen ist jedoch nicht geschützt: der backslash '\
'.
Lassen Sie uns als Beispiel ein Skript nehmen, das uns durch den regulären Ausdruck s/\.\.//g
daran hindert, den Verzeichnisbaum hinaufzusteigen, indem "..
" gelöscht wird. Es bewirkt nichts! Shells können mehrere '/
' auf einmal verarbeiten (versuchen Sie einfach cat ///etc//////passwd
: überzeugt? ).
Als Beispiel wird im obigen pipe2.cgi
-Skript die $filename
-Variable durch den "/home/httpd/
"-Prefix. initialisiert. Die Benutzung des vorherigen regulären Ausdrucks scheint ausreichend zu sein, um Verzeichniswechsel zu verhinden. Natürlich schützt dieser Ausdruck vor "..
", aber was passiert, wenn wir das '.
'-Zeichen schützen? Der reguläre Ausdruck trifft nicht auf den Dateinamen .\./.\./etc/passwd
zu. Wie wollen erwähnen, das dies sehr gut mit system()
oder ( ` ... `
) klappt, aber open()
oder "-e
" versagen.
Wir gehen nun zurück zum finger.cgi
-Skript. Unter Benutzung des Semikolons ergibt der finger.cgi?login=kmaster;cat%20/etc/passwd
-URL nicht das erwartete Resultat, weil das Semikolon durch den regulären Ausdruck maskiert wird. Die Shell erhält die Anweisung:
/usr/bin/finger kmaster\;cat /etc/passwdFolgende Fehlermeldungen finden sich in den Log-Dateien des Webservers:
finger: kmaster;cat: no such user. finger: /etc/passwd: no such user.Diese Meldungen gleichen denen, die erzeugt werden, wenn diese Zeile in einer Shell eingegeben wird. Das Problem ist, dass das geschützte '
;
' als Bestandteil der Zeichenkette "kmaster;cat
" angesehen wird.Wir wollen beide Anweisungen trennen, d. h. die aus dem Skript und diejenige, die wir benutzen wollen. Wir müssen daher das ';
' schützen : <A
HREF="finger.cgi?login=kmaster\;cat%20/etc/passwd">
finger.cgi?login=kmaster\;cat%20/etc/passwd</A>
. Die
"\;
"-Zeichenkette wird dann durch das Skript in "\\;
" gewandelt und an die Shell gesendet. Es ergibt sich:
/usr/bin/finger kmaster\\;cat /etc/passwdDie Shell teilt das in 2 Anweisungen auf:
/usr/bin/finger kmaster\
was wohl fehlschlägt ... aber uns nicht interessiert ;-)cat /etc/passwd
wodurch die Passwort-Datei angezeigt wird.\
' muss ebenfalls maskiert werden. Manchmal wird der Parameter durch Anführungszeichen "geschützt". Wir haben das vorherige finger.cgi
-Skript leicht abgewandelt, um die $login
-Variable auf diese Weise zu schützen.
Wenn die Anführungszeichen jedoch nicht maskiert sind, ist dies nutzlos. Sie müssen nur ein Anführungszeichen in Ihrer Anforderung hinzufügen. So schliesst das erste Anführungszeichen das öffnende aus dem Skript. Dann geben Sie die Anweisung ein und ein zweites Anführungszeichen, welches das letzte (schliessende) Anführungszeichen aus dem Skript öffnet.
Das finger2.cgi -Skript illustriert dies:
#finger2.cgi print "<BODY>"; $login = $input{'login'}; $login =~ s/\0//g; $login =~ s/([<>\*\|`&\$!#\(\)\[\]\{\}:'\n])/\\$1/g; print "Login $login<BR>\n"; print "Finger<BR>\n"; #New (in)efficient super protection : $CMD= "/usr/bin/finger \"$login\"|"; open(FILE,"$CMD") || goto form; while(<FILE>) { print; }
Der auszuführende URL wird dann:
finger2.cgi?login=kmaster%22%3Bcat%20%2Fetc%2Fpasswd%3B%22Die Shell erhält die Anweisung
/usr/bin/finger "$login";cat
/etc/passwd""
und die Anführungszeichen sind kein Problem mehr.Wenn Sie die Parameter mittels Anführungszeichen schützen wollen, ist es daher wichtig, sie wie die bereits erwähnten Zeichen Semikolon und backslash zu maskieren.
Wenn Sie in Perl programmieren, sollten Sie die w
-Option oder
"use warnings;
" (Perl ab 5.6.0) benutzen. Dadurch erhalten Sie Informationen über mögliche Probleme wie uninitialiserte Variablen oder überholte Ausdrücke/Funktionen.
Die T
-Option ( taint mode/Ma(e)kel-Modus) bietet zusätzliche Sicherheit. Dieser Modus aktiviert verschiedene Tests. Der wichtigste ist eine mögliche Markierung von Variablen als makelhaft. Variablen sind entweder sauber oder makelhaft. Daten von ausserhalb des Programms werden als makelhaft angesehen, solange sie nicht bereinigt wurden. Eine so befleckte Variable kann keine Werte an Sachen zuweisen, die ausserhalb des Programms benutzt werden (Aufrufe anderer Shell-Befehle).
Im taint-Modus werden die Befehlszeilenargumente, die Umgebungsvariablen, einige Systemaufruf-Ergebnisse (readdir()
,
readlink()
, readdir()
, ...) und die Daten aus Dateien als verdächtig und damit als makelhaft angesehen.
Um eine Variable zu bereinigen, muss sie durch einen regulären Ausdruck gefiltert werden. Die Benutzung von .*
ist natürlich nutzlos. Das Ziel ist es, Sie dazu zu bringen, sich um die übergebenen Argumente zu kümmern. Spezifizieren Sie die regulären Ausdrücke so genau wie irgend möglich..
Trotzdem schützt dies nicht vor allen Fallen: Die Reinheit von Argumenten, die als Listen-Variablen an system()
oder
exec()
übergeben werden, wird nicht geprüft. Sie müssen also besonders vorsichtig sein, wenn Ihre Skripte diese Funktionen benutzen.
Die exec "sh", '-c', $arg;
-Anweisungen werden als sicher angesehen unabhängig davon, ob $arg
bereinigt ist oder nicht :(
Es wird ausserdem empfohlen, "use strict;" an den Beginn Ihrer Skripte zu setzen. Dies zwingt Sie dazu, Variablen zu deklarieren; einige werden es als lästig empfinden, aber es ist auf jeden Fall erforderlich, wenn Sie
mod-perl
benutzen.
Daher müssen Ihre Perl-CGI-Skripte wie folgt anfangen:
#!/usr/bin/perl -wT use strict; use CGI;oder ab Perl 5.6.0 :
#!/usr/bin/perl -T use warnings; use strict; use CGI;
open()
Viele Programmierer/innen öffnen Dateien einfach durch open(FILE,"$filename") || ...
. Wir haben die Risiken dieser Anweisungen bereits gesehen. Zur Reduzierung des Risikos reicht es, den Öffnungs-Modus anzugeben:
open(FILE,"<$filename") || ...
nur lesen;open(FILE,">$filename") || ...
nur schreiben.Vor dem Zugriff auf eine Datei sollten Sie deren Existenz überprüfen. Dies verhindert keine der im letzten Artikel besprochenen "race condiions", vermeidet aber einige Fallen wie Befehle mit Argumenten.
if ( -e $filename ) { ... }
Seit Perl 5.6 gibt es eine neue Syntax für
open()
: open(FILEHANDLE,MODE,LIST)
. Mit dem '<'-Modus wird die Datei zum Lesen geöffnet, mit dem '>';-Modus wird sie auf 0 Bytes gestutzt oder neu angelegt und zum Schreiben geöffnet. Dies ist interessant für Modi, die mit anderen Prozessen kommunizieren. Wenn der Modus '|-' oder '-|' ist, wird das LIST-Argument als Befehl interpretiert und entsprechend vor oder nach der Pipe eingesetzt.
Vor Perl 5.6 und open()
mit drei Argumenten haben einige auch den sysopen()
-Befehl benutzt.
Es gibt zwei Methoden: entweder spezifizieren Sie die verbotenen Zeichen oder Sie definieren ausdrücklich die zulässigen Zeichen durch reguläre Ausdrücke. Die Beispielprogramme sollten Sie davon überzeugt haben, dass es sehr leicht passiert, beim Filtern einige möglicherweise gefährlichen Zeichen zu vergessen; deshalb wird die zweite Methode empfohlen.
Was sie also tun müssen: zuerst überprüfen Sie, ob die Anforderung nur die erlaubten Zeichen enthält. Als nächstes werden von den zugelassenen Zeichen diejenigen maskiert, die als gefährlich angesehen werden.
#!/usr/bin/perl -wT # filtre.pl # The $safe and $danger variables respectively define # the characters without risk and the risky ones. # Enough to add/remove some to change the filter. # Only $input containing characters included in the # definitions are valid. use strict; my $input = shift; my $safe = '\w\d'; my $danger = '&`\'\\|"*?~<>^(){}\$\n\r\[\]'; #Note: # '/', space and tab are not part of the definitions on purpose if ($input =~ m/^[$safe$danger]+$/g) { $input =~ s/([$danger]+)/\\$1/g; } else { die "Bad input chars in $input\n"; } print "input = [$input]\n";
Dieses Skript definiert zwei Zeichen-Sätze:
$safe
enthält die als nicht riskant betrachteten (hier nur Buchstaben und Ziffern);$danger
enthält die zu maskierenden Zeichen, die zwar zulässig, aber auch potentiell gefährlich sind.Ich möchte keine Kontroverse auslösen, aber ich denke, es ist besser, Skripte in PHP als in Perl zu schreiben. Genauer, als Systemadministrator ziehe ich es vor, wenn meine Benutzer/innen ihre Skripte in PHP anstatt Perl schreiben. Wer in der falschen Art und Weise programmiert - nicht sicherheitsbewußt - ist in PHP genauso gefährlich wie in Perl. Warum bevorzuge ich dann PHP?
Bei Programmierproblemen in PHP können Sie den Safe-Modus (safe_mode=on
) aktivieren oder Funktionen deaktivieren (disable_functions=...
). Dieser Modus verhindert Zugriffe auf Dateien, die dem Benutzer nicht gehören, verhindert das Ändern von Umgebungsvariablen (sofern nicht ausdrücklich erlaubt), das Ausführen von Programmen usw.
Standardmäßig informiert der Apache-Banner uns über die benutztte PHP-Version.
$ telnet localhost 80 Trying 127.0.0.1... Connected to localhost.localdomain. Escape character is '^]'. HEAD / HTTP/1.0 HTTP/1.1 200 OK Date: Tue, 03 Apr 2001 11:22:41 GMT Server: Apache/1.3.14 (Unix) (Red-Hat/Linux) mod_ssl/2.7.1 OpenSSL/0.9.5a PHP/4.0.4pl1 mod_perl/1.24 Connection: close Content-Type: text/html Connection closed by foreign host.Es reicht,
expose_PHP = Off
in die
/etc/php.ini
zu schreiben, um diese Information zu unterdrücken:
Server: Apache/1.3.14 (Unix) (Red-Hat/Linux) mod_ssl/2.7.1 OpenSSL/0.9.5a mod_perl/1.24
Die /etc/php.ini
-Datei (PHP4) oder
/etc/httpd/php3.ini
kann viele Parameter enthalten, mit denen das System gesichert werden kann. Zum Beispiel fügt die "magic_quotes_gpc
"-Option Anführungszeichen zu den Argumenten hinzu, die über die GET
, POST
-Methoden und per Cookie erhalten werden; dies vermeidet eine Anzahl der Probleme aus unseren Perl-Beispielen.
Von den Artikeln in dieser Serie ist dieser wahrscheinlich am leichtesten zu verstehen. Er zeigt Schwachstellen auf, die täglich im Web ausgenutzt werden können. Es gibt viele andere Möglichkeiten, die oft auf schlechte Programmierung zurückzuführen sind (z. B. ein Skript, welches Mail versendet und als Argument das From:
-Feld akzeptiert, ist ein großer Anreiz für Massen-Mail-Versender). Es gibt (zu)viele Beispiele. Sobald sich ein Skript auf einer Webseite findet, können Sie darauf wetten, das irgendjemand versucht, es auf die falsche Art zu benutzen.
Dieser Artikel beendet die Serie über sichere Programmierurng. Wir hoffen, dass wir Ihnen geholfen haben, die größten Sicherheitslöcher in zu vielen Anwendungen zu entdecken und dass Sie den "Sicherheits"-Parameter berücksichtigen, wenn Sie Ihre Anwendungen gestalten und programmieren. Sicherheitsprobleme werden zu oft vernachlässigt wegen dem begrenzten Einsatzbereich (interne Benutzung, private Netzwerk-Nutzung, temporäres Modell usw.). Aber auch ein ursprünglich nur für einen sehr begrenzten Zweck vorgesehenes Modul kann die Basis für eine viel größere Anwendung werden und dann notwendig werdende Änderungen werden sehr viel teurer sein.
Unicode | Zeichen |
%00 | \0 (end of string) |
%0a | \n (carriage return) |
%20 | space |
%21 | ! |
%22 | " |
%23 | # |
%26 | & (ampersand) |
%2f | / |
%3b | ; |
%3c | < |
%3e | > |
man perlsec
: Perl-man-Seite über Sicherheit;#!/usr/bin/perl -w # guestbook.cgi BEGIN { $ENV{PATH} = '/usr/bin:/bin' } delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; # Make %ENV safer =:-) print "Content-type: text/html\n\n"; print "<HTML>\n<HEAD><TITLE>Buggy Guestbook</TITLE></HEAD>\n"; &ReadParse(\%input); my $email= $input{email}; my $texte= $input{texte}; $texte =~ s/\n/<BR>/g; print "<BODY><A HREF=\"guestbook.html\"> GuestBook </A><BR><form action=\"$ENV{'SCRIPT_NAME'}\">\n Email: <input type=texte name=email><BR>\n Texte:<BR>\n<textarea name=\"texte\" rows=15 cols=70> </textarea><BR><input type=submit value=\"Go!\"> </form>\n"; print "</BODY>\n"; print "</HTML>"; open (FILE,">>guestbook.html") || die ("Cannot write\n"); print FILE "Email: $email<BR>\n"; print FILE "Texte: $texte<BR>\n"; print FILE "<HR>\n"; close(FILE); exit(0); sub ReadParse { my $in =shift; my ($i, $key, $val); my $in_first; my @in_second; # Read in text if ($ENV{'REQUEST_METHOD'} eq "GET") { $in_first = $ENV{'QUERY_STRING'}; } elsif ($ENV{'REQUEST_METHOD'} eq "POST") { read(STDIN,$in_first,$ENV{'CONTENT_LENGTH'}); }else{ die "ERROR: unknown request method\n"; } @in_second = split(/&/,$in_first); foreach $i (0 .. $#in_second) { # Convert plus's to spaces $in_second[$i] =~ s/\+/ /g; # Split into key and value. ($key, $val) = split(/=/,$in_second[$i],2); # Convert %XX from hex numbers to alphanumeric $key =~ s/%(..)/pack("c",hex($1))/ge; $val =~ s/%(..)/pack("c",hex($1))/ge; # Associate key and value $$in{$key} .= "\0" if (defined($$in{$key})); $$in{$key} .= $val; } return length($#in_second); }
|
Der LinuxFocus Redaktion schreiben
© Frédéric Raynal, Christophe Blaess, Christophe Grenier, FDL LinuxFocus.org Einen Fehler melden oder einen Kommentar an LinuxFocus schicken |
Autoren und Übersetzer:
|
2001-10-24, generated by lfparser version 2.19