Develop-Zone
== Das Sicherheitslabor == Last modified 11.03.1998

Falsch & Richtig: Der Kalkulator

Mit ein paar Zeilen Perl ist ein einfaches Cgi-Skript geschrieben, daß einen Benutzer erlaubt Rechnungen auszuführen. Die meisten Zeilen beschäftigen sich nur mit HTML, und unser Kalkulator kann sogar Klammern und Punktrechnung vor Strichrechnung!

Doch Oh-Weh, das Programm hat ein schweres Sicherheitsloch:

#!/usr/local/bin/perl
#----------------------------------------------------------------
# calc.pl - einfaches CGI fuer Arithmetik
# ACHTUNG: Dieses Beispiel enthaelt ein grosses Sicherheitsloch

 use CGI qw(:standard);                    # laden des CGI Modules
 print header;                             # erzeugt Content-type...
 print start_html('Einfacher Kalkulator'),
       h1("Kalkulator");                   # Ueberschrift
 
 print start_form,                         # erzeuge Eingabemaske
       'Berechne: ',
       textfield('calc');
       submit,
       endform,
       hr;
 
 if( param() )                          # falls nicht zum ersten mal 
 {                                      # aufgerufen
    $tocalc = param('calc');
    $result = eval($tocalc);            # hier kann ALLES passieren !!!
    print "Das Ergebnis von $tocalc ist $result";
 } 
 print end_html;                        # standard HTML Ende
 

Alles, was der Benutzer eintippt, wird ausgeführt. Nun gibt es jedoch in Perl nicht nur Befehle, die Zahlen addieren oder multiplizieren, sondern auch solche die komplette Dateiverzeichnisse durchsuchen oder Dateien löschen, Netzwerkverbindungen aufbauen, und vieles mehr. Dazu braucht man noch nicht mal einen Systembefehl; das obige Skript ist unter Windows und Macintosh genauso verheerend wie unter UNIX.

Erste Pflicht: Bei CGI immer Taint Checking einschalten:

#!/usr/local/bin/perl -wT

Auf den Macintosh gibt es ein paar Besonderheiten:
Infos über Taint Checking mit MacPerl.

Wenn wir jetzt das Skript ausführen, beschwert sich Perl über das unsichere eval(). Aber wie kriegen wir es wieder sicher? Mit Hilfe von regulären Ausdrücken. Wir betrachten hier eine einfache Version: erlaubt sind nur Zahlen sowie '+' und '*'. Ein regulärer Ausdruck sieht hierfür folgendermassen aus:

/^([0-9+*]+)$/
/.../ begrenzen und markieren den regulären Ausdruck
^ ist Zeichen für Zeilenanfang: unser Muster soll vorne anfangen
(...) bedeuted das innere der Klammer wird der Variablen $1 zugewiesen
[..]  definiert eine Menge, die gleichwertig vorkommen darf, speziell
0-9   definiert alle Ziffern von 0 bis 9
+*    definieren diese Zeichen
[..]+ bedeuted, das die Zeichen der Menge mindestens einmal vorkommen 
      jedoch beliebig haeufig
$     markiert das Ende der Zeile. Das bedeutet, das nix anderes
      als das vorher definierte in der Zeile vorkommen darf
Übersetzt ins deutsche bedeutet dieser Ausdrück: Prüfe, ob die Zeile aus einer Folge von Ziffern oder '+' oder '*' besteht, die am Anfang der Zeile anfängt und mit dem Ende der Zeile aufhört. Falls ja, weise diese Zeichenfolge der Variablen $1 zu.

Diese Prüfung ist natürlich sehr hart: Wir erlauben keine Klammern und kein Minus oder teilen. Wenn wir dies auch wollen, müssen wir die entsprechenden Zeichen aufnehmen, allerdings müssen wir Perl sagen, das die Klammern als Zeichen wörtlich zu verstehen sind, und nicht als Klammern im Sinne von Perl. Dieses tun wir, indem wir diesen Zeichen einen Backslash '\' voranstellen. Leider wird dadurch der Ausdruck etwas unübersichtlich:

/^([0-9+*\-\/\(\) ]+)$/

Unser ganzes Programm kann dann so aussehen:

#!/usr/local/bin/perl -w
#----------------------------------------------------------------
# calc.pl - einfaches CGI fuer Arithmetik
# mit hoher Sicherheit
$ENV{'PATH'} = '/usr;/usr/bin';        # Path auf definierten Wert setzen
                                       # hier nicht noetig, aber sicher ist sicher
use CGI qw(:standard);                 # laden des CGI Modules
 
print header;                          # erzeugt Content-type...
print start_html('Einfacher Kalkulator'),
       h1("Kalkulator");               # Ueberschrift
 
print start_form,
       "Berechne: ",
       textfield('calc'),
       submit,
       endform,
       hr, "\n";
 
if( param() )                             # falls nicht zum ersten mal 
{                                         # aufgerufen
    $userinput = param('calc'); 
    if($userinput =~ /^([0-9+*\-\/\(\) ]+)$/ )
    {
        $tocalc = $1;                     # $1 wird zu allem 
                                          # innerhalb der ersten Klammer
        $result = eval($tocalc);          # hier kann nur noch arithmetik
                                          # passieren
        print "Das Ergebnis von $tocalc ist $result";
    }
    else
    {
        print "Bitte geben Sie nur Zahlen und Rechenzeichen ein.";
    }
} 
print end_html;                           # standard HTML Ende


home - contact - dev-zone
Copyright © 1997 by Karsten Meier. All Rights reserved.