Vermeidung von Sicherheitsl�cken bei der Anwendungsentwicklung - Teil 6: CGI-Skripte

ArticleCategory:

Software Development

AuthorImage:

[image of the authors]

TranslationInfo:

Original in fr Frédéric Raynal, Christophe Blaess, Christophe Grenier

fr to en Georges Tarbouriech

en to de Hermann J. Beckers

AboutTheAuthor:

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.

Abstract

Abruf einer Datei, Start eines Programmes durch ein schlecht programmiertes Perl-Skript. ... "Viele Wege f�hren zum Ziel!"

Bisherige Artikel in dieser Serie :

ArticleIllustration:[illustration]

[article illustration]

ArticleBody:[The real article: put the text and html-codes here]

Web-Server, URIs und Konfigurationsprobleme

Eine (zu kurze) Einf�hrung in die Arbeitsweise eines Webservers und zum Aufbau einer URL

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][...] [&param_n=val_n]]
Die Argumentliste wird in der Umgebungs-Variablen 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
Tats�chlich ist es einfacher als es aussieht. Wir analysieren den URL:

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.

Apache-Konfiguration mit "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:



Im angeh�ngten guestbook.cgi -Skript wird der von der Benutzerin eingegebene Text in eine HTML-Datei eingebunden, ohne die Zeichen '<' und ' >' in die &lt;- und &gt;-HTML-Kodierungen zu wandeln. Das kann jemand dazu anregen, folgende Instruktionen einzugeben:

Im ersten Beispiel
guestbook.cgi?email=pappy&texte=%3c%21--%23printenv%20--%3e
erhalten Sie einige Informationszeilen �ber das System:
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 .

Perl-Skripte

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.

Das Null-Byte

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;


Benutzung von Pipes

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%2Fpasswd
Dies 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|

Zeilenschaltungen

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:



Sie haben hier keine Auswirkung, weil sie durch den regul�ren Ausdruck maskiert werden. Aber lassen Sie uns nach einem Ausweg daf�r suchen.

Backslash und Semikolon

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/passwd
Folgende 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/passwd
Die Shell teilt das in 2 Anweisungen auf:
  1. /usr/bin/finger kmaster\ was wohl fehlschl�gt ... aber uns nicht interessiert ;-)
  2. cat /etc/passwd wodurch die Passwort-Datei angezeigt wird.
Die L�sung ist einfach: Der backslash '\' muss ebenfalls maskiert werden.

Benutzung eines ungesch�tzten "-Zeichens

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%22
Die 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.

Skripte in Perl schreiben

Warning- und tainting-Optionen

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;


Aufruf von 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:

�ffnen Sie Ihre Dateien also nie in einer unspezifizierten Weise.

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.

Eingabe-Maskierung und -Filterung

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:

Jede Anforderung, die ein Zeichen enth�lt, welches nicht in einem der beiden Zeichens�tze enthalten ist, wird sofort zur�ckgewiesen.

PHP-Skripte

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.

Schlussfolgerung

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.


Einige Unicode-Zeichen

Unicode Zeichen
%00 \0 (end of string)
%0a \n (carriage return)
%20 space
%21 !
%22 "
%23 #
%26 & (ampersand)
%2f /
%3b ;
%3c <
%3e >
Tab 1 : Unicode und Zeichen-Beziehung

Links


Das fehlerhafte guestbook.cgi Program

#!/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);
}