Original in fr Frédéric Raynal, Christophe Blaess, Christophe Grenier
fr to en Georges Tarbouriech
en to de Hermann J. Beckers
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.
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); }